From a7e253e3eab8b8759ddf6ec0c32386723a2787fc Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 19 Nov 2010 17:58:08 +0100 Subject: [PATCH] Add C# resolve visitor. --- .../GeneralScope/AttributeSectionTests.cs | 68 +++++ .../CSharp/Parser/ParseUtil.cs | 17 ++ .../ICSharpCode.NRefactory.Tests.csproj | 2 + ...actDomVisitor.cs => AbstractDomVisitor.cs} | 4 +- .../CSharp/Dom/DomLocation.cs | 10 +- .../CSharp/Parser/CSharpParser.cs | 12 +- .../CSharp/Resolver/CSharpResolver.cs | 85 ++++++ .../CSharp/Resolver/ResolveVisitor.cs | 272 ++++++++++++++++++ .../ICSharpCode.NRefactory.csproj | 3 +- 9 files changed, 460 insertions(+), 13 deletions(-) create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs rename ICSharpCode.NRefactory/CSharp/Dom/{AbtractDomVisitor.cs => AbstractDomVisitor.cs} (99%) create mode 100644 ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs new file mode 100644 index 0000000000..a257e0d730 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/GeneralScope/AttributeSectionTests.cs @@ -0,0 +1,68 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Linq; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Parser.GeneralScope +{ + [TestFixture, Ignore] + public class AttributeSectionTests + { + [Test, Ignore] + public void GlobalAttributeCSharp() + { + string program = @"[global::Microsoft.VisualBasic.CompilerServices.DesignerGenerated()] +[someprefix::DesignerGenerated()] +public class Form1 { +}"; + // TODO This test checks that [global] attributes are incorrectly applies to the following type??? + + //TypeDeclaration decl = ParseUtilCSharp.ParseGlobal(program); + //Assert.AreEqual("Microsoft.VisualBasic.CompilerServices.DesignerGenerated", decl.Attributes.First().Attributes.Single().Name); + //Assert.AreEqual("someprefix.DesignerGenerated", decl.Attributes.Last().Attributes.Single().Name); + } + + [Test] + public void AssemblyAttributeCSharp() + { + string program = @"[assembly: System.Attribute()]"; + AttributeSection decl = ParseUtilCSharp.ParseGlobal(program); + Assert.AreEqual(new DomLocation(1, 1), decl.StartLocation); + Assert.AreEqual("assembly", decl.AttributeTarget); + } + + [Test] + public void AssemblyAttributeCSharpWithNamedArguments() + { + string program = @"[assembly: Foo(1, namedArg: 2, prop = 3)]"; + AttributeSection decl = ParseUtilCSharp.ParseGlobal(program); + Assert.AreEqual("assembly", decl.AttributeTarget); + var a = decl.Attributes.Single(); + Assert.AreEqual("Foo", a.Name); + Assert.AreEqual(3, a.Arguments.Count()); + + // TODO: check arguments + } + + [Test] + public void ModuleAttributeCSharp() + { + string program = @"[module: System.Attribute()]"; + AttributeSection decl = ParseUtilCSharp.ParseGlobal(program); + Assert.AreEqual(new DomLocation(1, 1), decl.StartLocation); + Assert.AreEqual("module", decl.AttributeTarget); + } + + [Test] + public void TypeAttributeCSharp() + { + string program = @"[type: System.Attribute()] class Test {}"; + TypeDeclaration type = ParseUtilCSharp.ParseGlobal(program); + AttributeSection decl = type.Attributes.Single(); + Assert.AreEqual(new DomLocation(1, 1), decl.StartLocation); + Assert.AreEqual("type", decl.AttributeTarget); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseUtil.cs b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseUtil.cs index 1bd60bc495..4dba042a30 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseUtil.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Parser/ParseUtil.cs @@ -13,6 +13,23 @@ namespace ICSharpCode.NRefactory.CSharp.Parser /// public class ParseUtilCSharp { + public static T ParseGlobal(string code, bool expectErrors = false) where T : INode + { + CSharpParser parser = new CSharpParser(); + CompilationUnit cu = parser.Parse(new StringReader(code)); + + // TODO check for parser errors + /*if (expectErrors) + Assert.IsTrue(parser.Errors.ErrorOutput.Length > 0, "There were errors expected, but parser finished without errors."); + else + Assert.AreEqual("", parser.Errors.ErrorOutput);*/ + + INode node = cu.Children.Single(); + Type type = typeof(T); + Assert.IsTrue(type.IsAssignableFrom(node.GetType()), String.Format("Parsed node was {0} instead of {1} ({2})", node.GetType(), type, node)); + return (T)node; + } + public static T ParseStatement(string stmt, bool expectErrors = false) where T : INode { Assert.Ignore("ParseExpression not yet implemented"); diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 46dace3930..949fe50e74 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -84,6 +84,7 @@ + @@ -117,6 +118,7 @@ + \ No newline at end of file diff --git a/ICSharpCode.NRefactory/CSharp/Dom/AbtractDomVisitor.cs b/ICSharpCode.NRefactory/CSharp/Dom/AbstractDomVisitor.cs similarity index 99% rename from ICSharpCode.NRefactory/CSharp/Dom/AbtractDomVisitor.cs rename to ICSharpCode.NRefactory/CSharp/Dom/AbstractDomVisitor.cs index e2f1d8a5d9..55d63601a3 100644 --- a/ICSharpCode.NRefactory/CSharp/Dom/AbtractDomVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Dom/AbstractDomVisitor.cs @@ -1,4 +1,4 @@ -// +// // IDomVisitor.cs // // Author: @@ -28,7 +28,7 @@ using System; namespace ICSharpCode.NRefactory.CSharp { - public abstract class AbtractDomVisitor : IDomVisitor + public abstract class AbstractDomVisitor : IDomVisitor { protected S VisitChildren (INode node, T data) { diff --git a/ICSharpCode.NRefactory/CSharp/Dom/DomLocation.cs b/ICSharpCode.NRefactory/CSharp/Dom/DomLocation.cs index 7c20308de5..e5ba733781 100644 --- a/ICSharpCode.NRefactory/CSharp/Dom/DomLocation.cs +++ b/ICSharpCode.NRefactory/CSharp/Dom/DomLocation.cs @@ -1,4 +1,4 @@ -// +// // DomLocation.cs // // Author: @@ -63,14 +63,16 @@ namespace ICSharpCode.NRefactory.CSharp public override bool Equals (object other) { - if (!(other is DomLocation)) + if (!(other is DomLocation)) return false; return (DomLocation)other == this; } public override int GetHashCode () { - return Line + Column * 5000; + unchecked { + return Line + Column * 5000; + } } public bool Equals (DomLocation other) @@ -97,7 +99,7 @@ namespace ICSharpCode.NRefactory.CSharp if (invariantString.ToUpper () == "EMPTY") return DomLocation.Empty; string[] splits = invariantString.Split (',', '/'); - if (splits.Length == 2) + if (splits.Length == 2) return new DomLocation (Int32.Parse (splits[0]), Int32.Parse (splits[1])); return DomLocation.Empty; } diff --git a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs index ad8c104637..4ad90c9dae 100644 --- a/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs +++ b/ICSharpCode.NRefactory/CSharp/Parser/CSharpParser.cs @@ -2259,12 +2259,12 @@ namespace ICSharpCode.NRefactory.CSharp { // TODO: can we optimize this to avoid the text->stream->text roundtrip? using (MemoryStream stream = new MemoryStream ()) { - using (StreamWriter w = new StreamWriter(stream, Encoding.UTF8)) { - char[] buffer = new char[2048]; - int read; - while ((read = reader.ReadBlock(buffer, 0, buffer.Length)) > 0) - w.Write(buffer, 0, read); - } + StreamWriter w = new StreamWriter(stream, Encoding.UTF8); + char[] buffer = new char[2048]; + int read; + while ((read = reader.ReadBlock(buffer, 0, buffer.Length)) > 0) + w.Write(buffer, 0, read); + w.Flush(); // we can't close the StreamWriter because that would also close the MemoryStream stream.Position = 0; return Parse(stream); } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs index 1dc532bd27..94fdb11a28 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/CSharpResolver.cs @@ -19,6 +19,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver { static readonly ResolveResult ErrorResult = new ErrorResolveResult(SharedTypes.UnknownType); static readonly ResolveResult DynamicResult = new ResolveResult(SharedTypes.Dynamic); + static readonly ResolveResult NullResult = new ResolveResult(SharedTypes.Null); readonly ITypeResolveContext context; @@ -32,6 +33,13 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver #endregion #region Properties + /// + /// Gets the type resolve context used by the resolver. + /// + public ITypeResolveContext Context { + get { return context; } + } + /// /// Gets/Sets whether the current context is checked. /// @@ -1555,6 +1563,11 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return ErrorResult; } + public ResolveResult ResolveIndexer(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null) + { + throw new NotImplementedException(); + } + static List CreateParameters(ResolveResult[] arguments, string[] argumentNames) { List list = new List(); @@ -1653,5 +1666,77 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver return new ConstantResolveResult(int32, size); } #endregion + + #region This/Base + /// + /// Resolves 'this'. + /// + public ResolveResult ResolveThisReference() + { + ITypeDefinition t = CurrentTypeDefinition; + if (t != null) { + return new ResolveResult(t); + } + return ErrorResult; + } + + /// + /// Resolves 'base'. + /// + public ResolveResult ResolveBaseReference() + { + ITypeDefinition t = CurrentTypeDefinition; + if (t != null) { + foreach (IType baseType in t.GetBaseTypes(context)) { + ITypeDefinition baseTypeDef = baseType.GetDefinition(); + if (baseTypeDef != null && baseTypeDef.ClassType != ClassType.Interface) { + return new ResolveResult(baseType); + } + } + } + return ErrorResult; + } + #endregion + + #region ResolveConditional + public ResolveResult ResolveConditional(ResolveResult trueExpression, ResolveResult falseExpression) + { + // C# 4.0 spec §7.14: Conditional operator + Conversions c = new Conversions(context); + bool isValid; + IType resultType; + if (HasType(trueExpression) && HasType(falseExpression)) { + bool t2f = c.ImplicitConversion(trueExpression.Type, falseExpression.Type); + bool f2t = c.ImplicitConversion(falseExpression.Type, trueExpression.Type); + resultType = (f2t && !t2f) ? falseExpression.Type : trueExpression.Type; + isValid = (t2f != f2t) || (t2f && f2t && c.IdentityConversion(trueExpression.Type, falseExpression.Type)); + } else if (HasType(trueExpression)) { + resultType = trueExpression.Type; + isValid = c.ImplicitConversion(falseExpression, resultType); + } else if (HasType(falseExpression)) { + resultType = falseExpression.Type; + isValid = c.ImplicitConversion(trueExpression, resultType); + } else { + return ErrorResult; + } + return isValid ? new ResolveResult(resultType) : new ErrorResolveResult(resultType); + } + + bool HasType(ResolveResult r) + { + return r.Type != SharedTypes.UnknownType && r.Type != SharedTypes.Null; + } + #endregion + + public ResolveResult ResolvePrimitive(object value) + { + if (value == null) { + return NullResult; + } else { + TypeCode typeCode = Type.GetTypeCode(value.GetType()); + IType type = typeCode.ToTypeReference().Resolve(context); + return new ConstantResolveResult(type, value); + } + } } } diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs new file mode 100644 index 0000000000..8777b0728a --- /dev/null +++ b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs @@ -0,0 +1,272 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under MIT X11 license (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.TypeSystem.Implementation; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + /// + /// Traverses the DOM and resolves every expression. + /// + public class ResolveVisitor : AbstractDomVisitor + { + static readonly ResolveResult errorResult = new ErrorResolveResult(SharedTypes.UnknownType); + readonly CSharpResolver resolver; + readonly Dictionary cache = new Dictionary(); + + /// + /// Set this property to false to skip resolving all sub expressions. + /// + public bool FullyResolveSubExpressions { get; set; } + + public ResolveVisitor(CSharpResolver resolver) + { + if (resolver == null) + throw new ArgumentNullException("resolver"); + this.resolver = resolver; + this.FullyResolveSubExpressions = true; + } + + public ResolveResult Resolve(INode node) + { + ResolveResult result; + if (!cache.TryGetValue(node, out result)) { + result = cache[node] = node.AcceptVisitor(this, null) ?? errorResult; + } + return result; + } + + #region Checked / Unchecked + public override ResolveResult VisitCheckedExpression(CheckedExpression checkedExpression, object data) + { + bool oldCheckForOverflow = resolver.CheckForOverflow; + try { + resolver.CheckForOverflow = true; + return checkedExpression.Expression.AcceptVisitor(this, data); + } finally { + resolver.CheckForOverflow = oldCheckForOverflow; + } + } + + public override ResolveResult VisitUncheckedExpression(UncheckedExpression uncheckedExpression, object data) + { + bool oldCheckForOverflow = resolver.CheckForOverflow; + try { + resolver.CheckForOverflow = false; + return uncheckedExpression.Expression.AcceptVisitor(this, data); + } finally { + resolver.CheckForOverflow = oldCheckForOverflow; + } + } + + public override ResolveResult VisitCheckedStatement(CheckedStatement checkedStatement, object data) + { + bool oldCheckForOverflow = resolver.CheckForOverflow; + try { + resolver.CheckForOverflow = true; + return base.VisitCheckedStatement(checkedStatement, data); + } finally { + resolver.CheckForOverflow = oldCheckForOverflow; + } + } + + public override ResolveResult VisitUncheckedStatement(UncheckedStatement uncheckedStatement, object data) + { + bool oldCheckForOverflow = resolver.CheckForOverflow; + try { + resolver.CheckForOverflow = true; + return base.VisitUncheckedStatement(uncheckedStatement, data); + } finally { + resolver.CheckForOverflow = oldCheckForOverflow; + } + } + #endregion + + static bool IsTargetOfInvocation(INode node) + { + InvocationExpression ie = node.Parent as InvocationExpression; + return ie != null && ie.Target == node; + } + + IType ResolveType(INode node) + { + return SharedTypes.UnknownType; + } + + public override ResolveResult VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitArgListExpression(ArgListExpression argListExpression, object data) + { + return new ResolveResult(resolver.Context.GetClass(typeof(RuntimeArgumentHandle)) ?? SharedTypes.UnknownType); + } + + public override ResolveResult VisitArrayObjectCreateExpression(ArrayObjectCreateExpression arrayObjectCreateExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitAsExpression(AsExpression asExpression, object data) + { + if (FullyResolveSubExpressions) + Resolve(asExpression.Expression); + return new ResolveResult(ResolveType(asExpression.TypeReference)); + } + + public override ResolveResult VisitAssignmentExpression(AssignmentExpression assignmentExpression, object data) + { + ResolveResult left = Resolve(assignmentExpression.Left); + if (FullyResolveSubExpressions) { + Resolve(assignmentExpression.Right); + } + return new ResolveResult(left.Type); + } + + public override ResolveResult VisitBaseReferenceExpression(BaseReferenceExpression baseReferenceExpression, object data) + { + return resolver.ResolveBaseReference(); + } + + public override ResolveResult VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, object data) + { + ResolveResult left = Resolve(binaryOperatorExpression.Left); + ResolveResult right = Resolve(binaryOperatorExpression.Right); + return resolver.ResolveBinaryOperator(binaryOperatorExpression.BinaryOperatorType, left, right); + } + + public override ResolveResult VisitCastExpression(CastExpression castExpression, object data) + { + return resolver.ResolveCast(ResolveType(castExpression.CastTo), Resolve(castExpression.Expression)); + } + + public override ResolveResult VisitConditionalExpression(ConditionalExpression conditionalExpression, object data) + { + if (FullyResolveSubExpressions) + Resolve(conditionalExpression.Condition); + return resolver.ResolveConditional(Resolve(conditionalExpression.TrueExpression), + Resolve(conditionalExpression.FalseExpression)); + } + + public override ResolveResult VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, object data) + { + return new ConstantResolveResult(ResolveType(defaultValueExpression.TypeReference), null); + } + + public override ResolveResult VisitDirectionExpression(DirectionExpression directionExpression, object data) + { + ResolveResult rr = Resolve(directionExpression.Expression); + return new ResolveResult(new ByReferenceType(rr.Type)); + } + + public override ResolveResult VisitIdentifierExpression(IdentifierExpression identifierExpression, object data) + { + // TODO: type arguments? + return resolver.ResolveSimpleName(identifierExpression.Identifier.Name, null, + IsTargetOfInvocation(identifierExpression)); + } + + public override ResolveResult VisitIndexerExpression(IndexerExpression indexerExpression, object data) + { + ResolveResult target = Resolve(indexerExpression.Target); + // TODO: add support for named arguments + var argumentExpressions = indexerExpression.Arguments.ToList(); + ResolveResult[] arguments = new ResolveResult[argumentExpressions.Count]; + for (int i = 0; i < arguments.Length; i++) { + arguments[i] = Resolve(argumentExpressions[i]); + } + return resolver.ResolveIndexer(target, arguments); + } + + public override ResolveResult VisitInvocationExpression(InvocationExpression invocationExpression, object data) + { + ResolveResult target = Resolve(invocationExpression.Target); + // TODO: add support for named arguments + var argumentExpressions = invocationExpression.Arguments.ToList(); + ResolveResult[] arguments = new ResolveResult[argumentExpressions.Count]; + for (int i = 0; i < arguments.Length; i++) { + arguments[i] = Resolve(argumentExpressions[i]); + } + return resolver.ResolveInvocation(target, arguments); + } + + public override ResolveResult VisitIsExpression(IsExpression isExpression, object data) + { + if (FullyResolveSubExpressions) + ResolveType(isExpression.TypeReference); + return new ResolveResult(TypeCode.Boolean.ToTypeReference().Resolve(resolver.Context)); + } + + public override ResolveResult VisitLambdaExpression(LambdaExpression lambdaExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression, object data) + { + return resolver.ResolvePrimitive(null); + } + + public override ResolveResult VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, object data) + { + return Resolve(parenthesizedExpression.Expression); + } + + public override ResolveResult VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, object data) + { + throw new NotImplementedException(); + } + + public override ResolveResult VisitPrimitiveExpression(PrimitiveExpression primitiveExpression, object data) + { + return resolver.ResolvePrimitive(primitiveExpression.Value); + } + + public override ResolveResult VisitSizeOfExpression(SizeOfExpression sizeOfExpression, object data) + { + return resolver.ResolveSizeOf(ResolveType(sizeOfExpression.Type)); + } + + public override ResolveResult VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data) + { + if (FullyResolveSubExpressions) + Resolve(stackAllocExpression.CountExpression); + return new ResolveResult(new PointerType(ResolveType(stackAllocExpression.Type))); + } + + public override ResolveResult VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, object data) + { + return resolver.ResolveThisReference(); + } + + static readonly GetClassTypeReference systemType = new GetClassTypeReference("System.Type", 0); + + public override ResolveResult VisitTypeOfExpression(TypeOfExpression typeOfExpression, object data) + { + if (FullyResolveSubExpressions) + ResolveType(typeOfExpression.Type); + return new ResolveResult(systemType.Resolve(resolver.Context)); + } + + public override ResolveResult VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) + { + ResolveResult expr = Resolve(unaryOperatorExpression.Expression); + return resolver.ResolveUnaryOperator(unaryOperatorExpression.UnaryOperatorType, expr); + } + } +} diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 7f4f31c5ed..fd507acf13 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -56,7 +56,7 @@ - + @@ -167,6 +167,7 @@ +