// Copyright (c) AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.CSharp.Resolver { /// /// Traverses the DOM and resolves expressions. /// /// /// The ResolveVisitor does two jobs at the same time: it tracks the resolve context (properties on CSharpResolver) /// and it resolves the expressions visited. /// To allow using the context tracking without having to resolve every expression in the file (e.g. when you want to resolve /// only a single node deep within the DOM), you can use the interface. /// The navigator allows you to switch the between scanning mode and resolving mode. /// In scanning mode, the context is tracked (local variables registered etc.), but nodes are not resolved. /// While scanning, the navigator will get asked about every node that the resolve visitor is about to enter. /// This allows the navigator whether to keep scanning, whether switch to resolving mode, or whether to completely skip the /// subtree rooted at that node. /// /// In resolving mode, the context is tracked and nodes will be resolved. /// The resolve visitor may decide that it needs to resolve other nodes as well in order to resolve the current node. /// In this case, those nodes will be resolved automatically, without asking the navigator interface. /// For child nodes that are not essential to resolving, the resolve visitor will switch back to scanning mode (and thus will /// ask the navigator for further instructions). /// /// Moreover, there is the ResolveAll mode - it works similar to resolving mode, but will not switch back to scanning mode. /// The whole subtree will be resolved without notifying the navigator. /// public sealed class ResolveVisitor : DepthFirstAstVisitor { // The ResolveVisitor is also responsible for handling lambda expressions. static readonly ResolveResult errorResult = new ErrorResolveResult(SharedTypes.UnknownType); readonly ResolveResult voidResult; CSharpResolver resolver; SimpleNameLookupMode currentTypeLookupMode = SimpleNameLookupMode.Type; readonly ParsedFile parsedFile; readonly Dictionary resolveResultCache = new Dictionary(); readonly Dictionary resolverBeforeDict = new Dictionary(); readonly IResolveVisitorNavigator navigator; ResolveVisitorNavigationMode mode = ResolveVisitorNavigationMode.Scan; List undecidedLambdas; #region Constructor /// /// Creates a new ResolveVisitor instance. /// /// /// The CSharpResolver, describing the initial resolve context. /// If you visit a whole CompilationUnit with the resolve visitor, you can simply pass /// new CSharpResolver(typeResolveContext) without setting up the context. /// If you only visit a subtree, you need to pass a CSharpResolver initialized to the context for that subtree. /// /// /// Result of the for the file being passed. This is used for setting up the context on the resolver. /// You may pass null if you are only visiting a part of a method body and have already set up the context in the . /// /// /// The navigator, which controls where the resolve visitor will switch between scanning mode and resolving mode. /// If you pass null, then ResolveAll mode will be used. /// public ResolveVisitor(CSharpResolver resolver, ParsedFile parsedFile, IResolveVisitorNavigator navigator = null) { if (resolver == null) throw new ArgumentNullException("resolver"); this.resolver = resolver; this.parsedFile = parsedFile; this.navigator = navigator; this.voidResult = new ResolveResult(KnownTypeReference.Void.Resolve(resolver.Context)); if (navigator == null) mode = ResolveVisitorNavigationMode.ResolveAll; } #endregion #region Properties /// /// Gets the TypeResolveContext used by this ResolveVisitor. /// public ITypeResolveContext TypeResolveContext { get { return resolver.Context; } } /// /// Gets the CancellationToken used by this ResolveVisitor. /// public CancellationToken CancellationToken { get { return resolver.cancellationToken; } } #endregion #region ResetContext /// /// Resets the visitor to the stored position, runs the action, and then reverts the visitor to the previous position. /// void ResetContext(CSharpResolver storedContext, Action action) { var oldMode = this.mode; var oldResolver = this.resolver; var oldTypeLookupMode = this.currentTypeLookupMode; try { this.mode = (navigator == null) ? ResolveVisitorNavigationMode.ResolveAll : ResolveVisitorNavigationMode.Resolve; this.resolver = storedContext; this.currentTypeLookupMode = SimpleNameLookupMode.Type; action(); } finally { this.mode = oldMode; this.resolver = oldResolver; this.currentTypeLookupMode = oldTypeLookupMode; } } #endregion #region Scan / Resolve bool resolverEnabled { get { return mode != ResolveVisitorNavigationMode.Scan; } } public void Scan(AstNode node) { if (node == null || node.IsNull) return; switch (node.NodeType) { case NodeType.Token: case NodeType.Whitespace: return; // skip tokens, identifiers, comments, etc. } if (mode == ResolveVisitorNavigationMode.ResolveAll) { Resolve(node); } else { ResolveVisitorNavigationMode oldMode = mode; mode = navigator.Scan(node); switch (mode) { case ResolveVisitorNavigationMode.Skip: if (node is VariableDeclarationStatement) { // Enforce scanning of variable declarations. goto case ResolveVisitorNavigationMode.Scan; } break; case ResolveVisitorNavigationMode.Scan: if (node is LambdaExpression || node is AnonymousMethodExpression) { // lambdas must be resolved so that they get stored in the 'undecided' list only once goto case ResolveVisitorNavigationMode.Resolve; } resolverBeforeDict[node] = resolver.Clone(); node.AcceptVisitor(this, null); break; case ResolveVisitorNavigationMode.Resolve: case ResolveVisitorNavigationMode.ResolveAll: Resolve(node); break; default: throw new InvalidOperationException("Invalid value for ResolveVisitorNavigationMode"); } mode = oldMode; } } public ResolveResult Resolve(AstNode node) { if (node == null || node.IsNull) return errorResult; bool wasScan = mode == ResolveVisitorNavigationMode.Scan; if (wasScan) mode = ResolveVisitorNavigationMode.Resolve; ResolveResult result; if (!resolveResultCache.TryGetValue(node, out result)) { resolver.cancellationToken.ThrowIfCancellationRequested(); resolverBeforeDict[node] = resolver.Clone(); result = resolveResultCache[node] = node.AcceptVisitor(this, null) ?? errorResult; Log.WriteLine("Resolved '{0}' to {1}", node, result); ProcessConversionsInResult(result); } if (wasScan) mode = ResolveVisitorNavigationMode.Scan; return result; } protected override ResolveResult VisitChildren(AstNode node, object data) { ScanChildren(node); return null; } void ScanChildren(AstNode node) { for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { Scan(child); } } #endregion #region Process Conversions /// /// Processes conversions within this resolve result. /// void ProcessConversionsInResult(ResolveResult result) { ConversionResolveResult crr = result as ConversionResolveResult; if (crr != null) { ProcessConversion(crr.Input, crr.Conversion, crr.Type); } else { foreach (ResolveResult argumentResult in result.GetChildResults()) { crr = argumentResult as ConversionResolveResult; if (crr != null) ProcessConversion(crr.Input, crr.Conversion, crr.Type); } } } /// /// Convert 'rr' to the target type. /// void ProcessConversion(ResolveResult rr, IType targetType) { ProcessConversion(rr, resolver.conversions.ImplicitConversion(rr, targetType), targetType); } sealed class AnonymousFunctionConversionData { public readonly IType ReturnType; public readonly ExplicitlyTypedLambda ExplicitlyTypedLambda; public readonly LambdaTypeHypothesis Hypothesis; public AnonymousFunctionConversionData(IType returnType, LambdaTypeHypothesis hypothesis) { if (returnType == null) throw new ArgumentNullException("returnType"); this.ReturnType = returnType; this.Hypothesis = hypothesis; } public AnonymousFunctionConversionData(IType returnType, ExplicitlyTypedLambda explicitlyTypedLambda) { if (returnType == null) throw new ArgumentNullException("returnType"); this.ReturnType = returnType; this.ExplicitlyTypedLambda = explicitlyTypedLambda; } } /// /// Convert 'rr' to the target type using the specified conversion. /// void ProcessConversion(ResolveResult rr, Conversion conversion, IType targetType) { if (conversion.IsAnonymousFunctionConversion) { Log.WriteLine("Processing conversion of anonymous function to " + targetType + "..."); AnonymousFunctionConversionData data = conversion.data as AnonymousFunctionConversionData; if (data != null) { Log.Indent(); if (data.Hypothesis != null) data.Hypothesis.MergeInto(this, data.ReturnType); if (data.ExplicitlyTypedLambda != null) data.ExplicitlyTypedLambda.ApplyReturnType(this, data.ReturnType); Log.Unindent(); } else { Log.WriteLine(" Data not found."); } } } /// /// Resolves the specified expression and processes the conversion to targetType. /// void ResolveAndProcessConversion(Expression expr, IType targetType) { if (targetType.Kind == TypeKind.Unknown) { // no need to resolve the expression right now Scan(expr); } else { ProcessConversion(Resolve(expr), targetType); } } #endregion #region GetResolveResult /// /// Gets the cached resolve result for the specified node. /// Returns null if no cached result was found (e.g. if the node was not visited; or if it was visited in scanning mode). /// public ResolveResult GetResolveResult(AstNode node) { MergeUndecidedLambdas(); ResolveResult result; if (resolveResultCache.TryGetValue(node, out result)) return result; else return null; } /// /// Gets the resolver state in front of the specified node. /// Returns null if no cached resolver was found (e.g. if the node was skipped by the navigator) /// public CSharpResolver GetResolverStateBefore(AstNode node) { MergeUndecidedLambdas(); CSharpResolver r; if (resolverBeforeDict.TryGetValue(node, out r)) return r; else return null; } #endregion #region Track UsingScope public override ResolveResult VisitCompilationUnit(CompilationUnit unit, object data) { UsingScope previousUsingScope = resolver.UsingScope; try { if (parsedFile != null) resolver.UsingScope = parsedFile.RootUsingScope; ScanChildren(unit); return voidResult; } finally { resolver.UsingScope = previousUsingScope; } } public override ResolveResult VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) { UsingScope previousUsingScope = resolver.UsingScope; try { if (parsedFile != null) { resolver.UsingScope = parsedFile.GetUsingScope(namespaceDeclaration.StartLocation); } ScanChildren(namespaceDeclaration); return new NamespaceResolveResult(resolver.UsingScope.NamespaceName); } finally { resolver.UsingScope = previousUsingScope; } } #endregion #region Track CurrentTypeDefinition ResolveResult VisitTypeOrDelegate(AstNode typeDeclaration) { ITypeDefinition previousTypeDefinition = resolver.CurrentTypeDefinition; try { ITypeDefinition newTypeDefinition = null; if (resolver.CurrentTypeDefinition != null) { foreach (ITypeDefinition nestedType in resolver.CurrentTypeDefinition.NestedTypes) { if (nestedType.Region.IsInside(typeDeclaration.StartLocation)) { newTypeDefinition = nestedType; break; } } } else if (parsedFile != null) { newTypeDefinition = parsedFile.GetTopLevelTypeDefinition(typeDeclaration.StartLocation); } if (newTypeDefinition != null) resolver.CurrentTypeDefinition = newTypeDefinition; for (AstNode child = typeDeclaration.FirstChild; child != null; child = child.NextSibling) { if (child.Role == TypeDeclaration.BaseTypeRole) { currentTypeLookupMode = SimpleNameLookupMode.BaseTypeReference; Scan(child); currentTypeLookupMode = SimpleNameLookupMode.Type; } else { Scan(child); } } return newTypeDefinition != null ? new TypeResolveResult(newTypeDefinition) : errorResult; } finally { resolver.CurrentTypeDefinition = previousTypeDefinition; } } public override ResolveResult VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) { return VisitTypeOrDelegate(typeDeclaration); } public override ResolveResult VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration, object data) { return VisitTypeOrDelegate(delegateDeclaration); } #endregion #region Track CurrentMember public override ResolveResult VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) { return VisitFieldOrEventDeclaration(fieldDeclaration); } public override ResolveResult VisitFixedFieldDeclaration(FixedFieldDeclaration fixedFieldDeclaration, object data) { return VisitFieldOrEventDeclaration(fixedFieldDeclaration); } public override ResolveResult VisitEventDeclaration(EventDeclaration eventDeclaration, object data) { return VisitFieldOrEventDeclaration(eventDeclaration); } ResolveResult VisitFieldOrEventDeclaration(AttributedNode fieldOrEventDeclaration) { int initializerCount = fieldOrEventDeclaration.GetChildrenByRole(FieldDeclaration.Roles.Variable).Count; ResolveResult result = null; for (AstNode node = fieldOrEventDeclaration.FirstChild; node != null; node = node.NextSibling) { if (node.Role == FieldDeclaration.Roles.Variable) { if (resolver.CurrentTypeDefinition != null) { resolver.CurrentMember = resolver.CurrentTypeDefinition.Fields.FirstOrDefault(f => f.Region.IsInside(node.StartLocation)); } if (resolverEnabled && initializerCount == 1) { result = Resolve(node); } else { Scan(node); } resolver.CurrentMember = null; } else { Scan(node); } } return result; } public override ResolveResult VisitVariableInitializer(VariableInitializer variableInitializer, object data) { if (resolverEnabled) { ResolveResult result = errorResult; if (variableInitializer.Parent is FieldDeclaration) { if (resolver.CurrentMember != null) { result = new MemberResolveResult(null, resolver.CurrentMember, resolver.CurrentMember.ReturnType.Resolve(resolver.Context)); } } else { string identifier = variableInitializer.Name; foreach (IVariable v in resolver.LocalVariables) { if (v.Name == identifier) { object constantValue = v.IsConst ? v.ConstantValue.GetValue(resolver.Context) : null; result = new LocalResolveResult(v, v.Type.Resolve(resolver.Context), constantValue); break; } } } ResolveAndProcessConversion(variableInitializer.Initializer, result.Type); return result; } else { ScanChildren(variableInitializer); return null; } } public override ResolveResult VisitFixedVariableInitializer(FixedVariableInitializer fixedVariableInitializer, object data) { if (resolverEnabled) { ResolveResult result = errorResult; if (resolver.CurrentMember != null) { result = new MemberResolveResult(null, resolver.CurrentMember, resolver.CurrentMember.ReturnType.Resolve(resolver.Context)); } ResolveAndProcessConversion(fixedVariableInitializer.CountExpression, KnownTypeReference.Int32.Resolve(resolver.Context)); return result; } else { ScanChildren(fixedVariableInitializer); return null; } } ResolveResult VisitMethodMember(AttributedNode member) { try { if (resolver.CurrentTypeDefinition != null) { resolver.CurrentMember = resolver.CurrentTypeDefinition.Methods.FirstOrDefault(m => m.Region.IsInside(member.StartLocation)); } ScanChildren(member); if (resolverEnabled && resolver.CurrentMember != null) return new MemberResolveResult(null, resolver.CurrentMember, resolver.Context); else return errorResult; } finally { resolver.CurrentMember = null; } } public override ResolveResult VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) { return VisitMethodMember(methodDeclaration); } public override ResolveResult VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) { return VisitMethodMember(operatorDeclaration); } public override ResolveResult VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) { return VisitMethodMember(constructorDeclaration); } public override ResolveResult VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, object data) { return VisitMethodMember(destructorDeclaration); } // handle properties/indexers ResolveResult VisitPropertyMember(MemberDeclaration propertyOrIndexerDeclaration) { try { if (resolver.CurrentTypeDefinition != null) { resolver.CurrentMember = resolver.CurrentTypeDefinition.Properties.FirstOrDefault(p => p.Region.IsInside(propertyOrIndexerDeclaration.StartLocation)); } for (AstNode node = propertyOrIndexerDeclaration.FirstChild; node != null; node = node.NextSibling) { if (node.Role == PropertyDeclaration.SetterRole && resolver.CurrentMember != null) { resolver.PushBlock(); resolver.AddVariable(resolver.CurrentMember.ReturnType, DomRegion.Empty, "value"); Scan(node); resolver.PopBlock(); } else { Scan(node); } } if (resolverEnabled && resolver.CurrentMember != null) return new MemberResolveResult(null, resolver.CurrentMember, resolver.Context); else return errorResult; } finally { resolver.CurrentMember = null; } } public override ResolveResult VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data) { return VisitPropertyMember(propertyDeclaration); } public override ResolveResult VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data) { return VisitPropertyMember(indexerDeclaration); } public override ResolveResult VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration, object data) { try { if (resolver.CurrentTypeDefinition != null) { resolver.CurrentMember = resolver.CurrentTypeDefinition.Events.FirstOrDefault(e => e.Region.IsInside(eventDeclaration.StartLocation)); } if (resolver.CurrentMember != null) { resolver.PushBlock(); resolver.AddVariable(resolver.CurrentMember.ReturnType, DomRegion.Empty, "value"); ScanChildren(eventDeclaration); resolver.PopBlock(); } else { ScanChildren(eventDeclaration); } if (resolverEnabled && resolver.CurrentMember != null) return new MemberResolveResult(null, resolver.CurrentMember, resolver.Context); else return errorResult; } finally { resolver.CurrentMember = null; } } public override ResolveResult VisitParameterDeclaration(ParameterDeclaration parameterDeclaration, object data) { ScanChildren(parameterDeclaration); if (resolverEnabled) { string name = parameterDeclaration.Name; // Look in lambda parameters: foreach (IParameter p in resolver.LocalVariables.OfType()) { if (p.Name == name) return new LocalResolveResult(p, p.Type.Resolve(resolver.Context)); } IParameterizedMember pm = resolver.CurrentMember as IParameterizedMember; if (pm != null) { foreach (IParameter p in pm.Parameters) { if (p.Name == name) { return new LocalResolveResult(p, p.Type.Resolve(resolver.Context)); } } } return errorResult; } else { return null; } } public override ResolveResult VisitTypeParameterDeclaration(TypeParameterDeclaration typeParameterDeclaration, object data) { ScanChildren(typeParameterDeclaration); if (resolverEnabled) { string name = typeParameterDeclaration.Name; IMethod m = resolver.CurrentMember as IMethod; if (m != null) { foreach (var tp in m.TypeParameters) { if (tp.Name == name) return new TypeResolveResult(tp); } } if (resolver.CurrentTypeDefinition != null) { var typeParameters = resolver.CurrentTypeDefinition.TypeParameters; // look backwards so that TPs in the current type take precedence over those copied from outer types for (int i = typeParameters.Count - 1; i >= 0; i--) { if (typeParameters[i].Name == name) return new TypeResolveResult(typeParameters[i]); } } return errorResult; } else { return null; } } public override ResolveResult VisitEnumMemberDeclaration(EnumMemberDeclaration enumMemberDeclaration, object data) { try { if (resolver.CurrentTypeDefinition != null) { resolver.CurrentMember = resolver.CurrentTypeDefinition.Fields.FirstOrDefault(f => f.Region.IsInside(enumMemberDeclaration.StartLocation)); } ScanChildren(enumMemberDeclaration); if (resolverEnabled && resolver.CurrentMember != null) return new MemberResolveResult(null, resolver.CurrentMember, resolver.Context); else return errorResult; } finally { resolver.CurrentMember = null; } } #endregion #region Track CheckForOverflow public override ResolveResult VisitCheckedExpression(CheckedExpression checkedExpression, object data) { bool oldCheckForOverflow = resolver.CheckForOverflow; try { resolver.CheckForOverflow = true; if (resolverEnabled) { return Resolve(checkedExpression.Expression); } else { ScanChildren(checkedExpression); return null; } } finally { resolver.CheckForOverflow = oldCheckForOverflow; } } public override ResolveResult VisitUncheckedExpression(UncheckedExpression uncheckedExpression, object data) { bool oldCheckForOverflow = resolver.CheckForOverflow; try { resolver.CheckForOverflow = false; if (resolverEnabled) { return Resolve(uncheckedExpression.Expression); } else { ScanChildren(uncheckedExpression); return null; } } finally { resolver.CheckForOverflow = oldCheckForOverflow; } } public override ResolveResult VisitCheckedStatement(CheckedStatement checkedStatement, object data) { bool oldCheckForOverflow = resolver.CheckForOverflow; try { resolver.CheckForOverflow = true; ScanChildren(checkedStatement); return voidResult; } finally { resolver.CheckForOverflow = oldCheckForOverflow; } } public override ResolveResult VisitUncheckedStatement(UncheckedStatement uncheckedStatement, object data) { bool oldCheckForOverflow = resolver.CheckForOverflow; try { resolver.CheckForOverflow = false; ScanChildren(uncheckedStatement); return voidResult; } finally { resolver.CheckForOverflow = oldCheckForOverflow; } } #endregion #region Visit Expressions IType ResolveType(AstType type) { return Resolve(type).Type; } static string GetAnonymousTypePropertyName(Expression expr, out Expression resolveExpr) { if (expr is NamedArgumentExpression) { var namedArgExpr = (NamedArgumentExpression)expr; resolveExpr = namedArgExpr.Expression; return namedArgExpr.Identifier; } // no name given, so it's a projection initializer if (expr is MemberReferenceExpression) { resolveExpr = expr; return ((MemberReferenceExpression)expr).MemberName; } if (expr is IdentifierExpression) { resolveExpr = expr; return ((IdentifierExpression)expr).Identifier; } resolveExpr = null; return null; } public override ResolveResult VisitAnonymousTypeCreateExpression(AnonymousTypeCreateExpression anonymousTypeCreateExpression, object data) { ScanChildren(anonymousTypeCreateExpression); // 7.6.10.6 Anonymous object creation expressions var anonymousType = new DefaultTypeDefinition(resolver.CurrentTypeDefinition, "$Anonymous$"); anonymousType.IsSynthetic = true; foreach (var expr in anonymousTypeCreateExpression.Initializers) { Expression resolveExpr; var name = GetAnonymousTypePropertyName(expr, out resolveExpr); if (string.IsNullOrEmpty(name)) continue; var property = new DefaultProperty(anonymousType, name) { Accessibility = Accessibility.Public, ReturnType = new VarTypeReference(this, resolver.Clone(), resolveExpr, false) }; anonymousType.Properties.Add(property); } return new ResolveResult(anonymousType); } public override ResolveResult VisitArrayCreateExpression(ArrayCreateExpression arrayCreateExpression, object data) { ScanChildren(arrayCreateExpression); if (!resolverEnabled) { return null; } IType arrType; if (arrayCreateExpression.Type.IsNull) { var elements = new List(); foreach (var init in arrayCreateExpression.Initializer.Elements) { var rr = Resolve(init); if (!rr.IsError) elements.Add(rr); } TypeInference typeInference = new TypeInference(resolver.Context, new Conversions(resolver.Context)); bool success; IType elementType = typeInference.GetBestCommonType(elements, out success); arrType = new ArrayType(elementType, 1); } else { arrType = ResolveType(arrayCreateExpression.Type); foreach (var spec in arrayCreateExpression.AdditionalArraySpecifiers.Reverse()) { arrType = new ArrayType(arrType, spec.Dimensions); } // HACK: find a better way to represent this in the AST if (arrayCreateExpression.Arguments.Count == 0) { arrType = new ArrayType(arrType, 1); } else { arrType = new ArrayType(arrType, arrayCreateExpression.Arguments.Count); } } return new ResolveResult (arrType); } public override ResolveResult VisitAsExpression(AsExpression asExpression, object data) { if (resolverEnabled) { Scan(asExpression.Expression); return new ResolveResult(ResolveType(asExpression.Type)); } else { ScanChildren(asExpression); return null; } } public override ResolveResult VisitAssignmentExpression(AssignmentExpression assignmentExpression, object data) { if (resolverEnabled) { ResolveResult left = Resolve(assignmentExpression.Left); ResolveAndProcessConversion(assignmentExpression.Right, left.Type); return new ResolveResult(left.Type); } else { ScanChildren(assignmentExpression); return null; } } public override ResolveResult VisitBaseReferenceExpression(BaseReferenceExpression baseReferenceExpression, object data) { if (resolverEnabled) { return resolver.ResolveBaseReference(); } else { ScanChildren(baseReferenceExpression); return null; } } public override ResolveResult VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, object data) { if (resolverEnabled) { ResolveResult left = Resolve(binaryOperatorExpression.Left); ResolveResult right = Resolve(binaryOperatorExpression.Right); return resolver.ResolveBinaryOperator(binaryOperatorExpression.Operator, left, right); } else { ScanChildren(binaryOperatorExpression); return null; } } public override ResolveResult VisitCastExpression(CastExpression castExpression, object data) { if (resolverEnabled) { return resolver.ResolveCast(ResolveType(castExpression.Type), Resolve(castExpression.Expression)); } else { ScanChildren(castExpression); return null; } } public override ResolveResult VisitConditionalExpression(ConditionalExpression conditionalExpression, object data) { if (resolverEnabled) { return resolver.ResolveConditional( Resolve(conditionalExpression.Condition), Resolve(conditionalExpression.TrueExpression), Resolve(conditionalExpression.FalseExpression)); } else { ScanChildren(conditionalExpression); return null; } } public override ResolveResult VisitDefaultValueExpression(DefaultValueExpression defaultValueExpression, object data) { if (resolverEnabled) { return resolver.ResolveDefaultValue(ResolveType(defaultValueExpression.Type)); } else { ScanChildren(defaultValueExpression); return null; } } public override ResolveResult VisitDirectionExpression(DirectionExpression directionExpression, object data) { if (resolverEnabled) { ResolveResult rr = Resolve(directionExpression.Expression); return new ByReferenceResolveResult(rr, directionExpression.FieldDirection == FieldDirection.Out); } else { ScanChildren(directionExpression); return null; } } public override ResolveResult VisitEmptyExpression(EmptyExpression emptyExpression, object data) { return errorResult; } public override ResolveResult VisitIndexerExpression(IndexerExpression indexerExpression, object data) { if (resolverEnabled) { ResolveResult target = Resolve(indexerExpression.Target); string[] argumentNames; ResolveResult[] arguments = GetArguments(indexerExpression.Arguments, out argumentNames); return resolver.ResolveIndexer(target, arguments, argumentNames); } else { ScanChildren(indexerExpression); return null; } } public override ResolveResult VisitIsExpression(IsExpression isExpression, object data) { ScanChildren(isExpression); if (resolverEnabled) return new ResolveResult(KnownTypeReference.Boolean.Resolve(resolver.Context)); else return null; } public override ResolveResult VisitNullReferenceExpression(NullReferenceExpression nullReferenceExpression, object data) { if (resolverEnabled) { return resolver.ResolvePrimitive(null); } else { return null; } } public override ResolveResult VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) { if (resolverEnabled) { IType type = ResolveType(objectCreateExpression.Type); string[] argumentNames; ResolveResult[] arguments = GetArguments(objectCreateExpression.Arguments, out argumentNames); Scan(objectCreateExpression.Initializer); // TODO return resolver.ResolveObjectCreation(type, arguments, argumentNames); } else { ScanChildren(objectCreateExpression); return null; } } public override ResolveResult VisitParenthesizedExpression(ParenthesizedExpression parenthesizedExpression, object data) { if (resolverEnabled) { return Resolve(parenthesizedExpression.Expression); } else { Scan(parenthesizedExpression.Expression); return null; } } public override ResolveResult VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, object data) { if (resolverEnabled) { ResolveResult target = Resolve(pointerReferenceExpression.Target); ResolveResult deferencedTarget = resolver.ResolveUnaryOperator(UnaryOperatorType.Dereference, target); List typeArguments = new List(); foreach (AstType typeArgument in pointerReferenceExpression.TypeArguments) { typeArguments.Add(ResolveType(typeArgument)); } return resolver.ResolveMemberAccess(deferencedTarget, pointerReferenceExpression.MemberName, typeArguments, IsTargetOfInvocation(pointerReferenceExpression)); } else { ScanChildren(pointerReferenceExpression); return null; } } public override ResolveResult VisitPrimitiveExpression(PrimitiveExpression primitiveExpression, object data) { if (resolverEnabled) { return resolver.ResolvePrimitive(primitiveExpression.Value); } else { return null; } } public override ResolveResult VisitSizeOfExpression(SizeOfExpression sizeOfExpression, object data) { if (resolverEnabled) { return resolver.ResolveSizeOf(ResolveType(sizeOfExpression.Type)); } else { ScanChildren(sizeOfExpression); return null; } } public override ResolveResult VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data) { if (resolverEnabled) { ResolveAndProcessConversion(stackAllocExpression.CountExpression, KnownTypeReference.Int32.Resolve(resolver.Context)); return new ResolveResult(new PointerType(ResolveType(stackAllocExpression.Type))); } else { ScanChildren(stackAllocExpression); return null; } } public override ResolveResult VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, object data) { if (resolverEnabled) return resolver.ResolveThisReference(); else return null; } public override ResolveResult VisitTypeOfExpression(TypeOfExpression typeOfExpression, object data) { ScanChildren(typeOfExpression); if (resolverEnabled) return new ResolveResult(KnownTypeReference.Type.Resolve(resolver.Context)); else return null; } public override ResolveResult VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, object data) { if (resolverEnabled) { return Resolve(typeReferenceExpression.Type); } else { Scan(typeReferenceExpression.Type); return null; } } public override ResolveResult VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) { if (resolverEnabled) { ResolveResult expr = Resolve(unaryOperatorExpression.Expression); return resolver.ResolveUnaryOperator(unaryOperatorExpression.Operator, expr); } else { ScanChildren(unaryOperatorExpression); return null; } } public override ResolveResult VisitUndocumentedExpression(UndocumentedExpression undocumentedExpression, object data) { ScanChildren(undocumentedExpression); if (resolverEnabled) { ITypeReference resultType; switch (undocumentedExpression.UndocumentedExpressionType) { case UndocumentedExpressionType.ArgListAccess: case UndocumentedExpressionType.ArgList: resultType = typeof(RuntimeArgumentHandle).ToTypeReference(); break; case UndocumentedExpressionType.RefValue: var tre = undocumentedExpression.Arguments.ElementAtOrDefault(1) as TypeReferenceExpression; if (tre != null) resultType = ResolveType(tre.Type); else resultType = SharedTypes.UnknownType; break; case UndocumentedExpressionType.RefType: resultType = KnownTypeReference.Type; break; case UndocumentedExpressionType.MakeRef: resultType = typeof(TypedReference).ToTypeReference(); break; default: throw new InvalidOperationException("Invalid value for UndocumentedExpressionType"); } return new ResolveResult(resultType.Resolve(resolver.Context)); } else { return null; } } #endregion #region Visit Identifier/MemberReference/Invocation-Expression // IdentifierExpression, MemberReferenceExpression and InvocationExpression // are grouped together because they have to work together for // "7.6.4.1 Identical simple names and type names" support List GetTypeArguments(IEnumerable typeArguments) { List result = new List(); foreach (AstType typeArgument in typeArguments) { result.Add(ResolveType(typeArgument)); } return result; } ResolveResult[] GetArguments(IEnumerable argumentExpressions, out string[] argumentNames) { argumentNames = null; ResolveResult[] arguments = new ResolveResult[argumentExpressions.Count()]; int i = 0; foreach (AstNode argument in argumentExpressions) { NamedArgumentExpression nae = argument as NamedArgumentExpression; AstNode argumentValue; if (nae != null) { if (argumentNames == null) argumentNames = new string[arguments.Length]; argumentNames[i] = nae.Identifier; argumentValue = nae.Expression; } else { argumentValue = argument; } arguments[i++] = Resolve(argumentValue); } return arguments; } static bool IsTargetOfInvocation(AstNode node) { InvocationExpression ie = node.Parent as InvocationExpression; return ie != null && ie.Target == node; } bool IsVariableReferenceWithSameType(ResolveResult rr, string identifier, out TypeResolveResult trr) { if (!(rr is MemberResolveResult || rr is LocalResolveResult)) { trr = null; return false; } trr = resolver.LookupSimpleNameOrTypeName(identifier, EmptyList.Instance, SimpleNameLookupMode.Type) as TypeResolveResult; return trr != null && trr.Type.Equals(rr.Type); } /// /// Gets whether 'rr' is considered a static access on the target identifier. /// /// Resolve Result of the MemberReferenceExpression /// Resolve Result of the InvocationExpression bool IsStaticResult(ResolveResult rr, ResolveResult invocationRR) { if (rr is TypeResolveResult) return true; MemberResolveResult mrr = (rr is MethodGroupResolveResult ? invocationRR : rr) as MemberResolveResult; return mrr != null && mrr.Member.IsStatic; } public override ResolveResult VisitIdentifierExpression(IdentifierExpression identifierExpression, object data) { // Note: this method is not called when it occurs in a situation where an ambiguity between // simple names and type names might occur. if (resolverEnabled) { var typeArguments = GetTypeArguments(identifierExpression.TypeArguments); return resolver.ResolveSimpleName(identifierExpression.Identifier, typeArguments, IsTargetOfInvocation(identifierExpression)); } else { ScanChildren(identifierExpression); return null; } } public override ResolveResult VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data) { // target = Resolve(identifierExpression = memberReferenceExpression.Target) // trr = ResolveType(identifierExpression) // rr = Resolve(memberReferenceExpression) IdentifierExpression identifierExpression = memberReferenceExpression.Target as IdentifierExpression; if (identifierExpression != null && identifierExpression.TypeArguments.Count == 0) { // Special handling for §7.6.4.1 Identicial simple names and type names ResolveResult target = resolver.ResolveSimpleName(identifierExpression.Identifier, EmptyList.Instance); TypeResolveResult trr; if (IsVariableReferenceWithSameType(target, identifierExpression.Identifier, out trr)) { // It's ambiguous ResolveResult rr = ResolveMemberReferenceOnGivenTarget(target, memberReferenceExpression); resolveResultCache[identifierExpression] = IsStaticResult(rr, null) ? trr : target; Log.WriteLine("Ambiguous simple name '{0}' was resolved to {1}", identifierExpression, resolveResultCache[identifierExpression]); return rr; } else { // It's not ambiguous resolveResultCache[identifierExpression] = target; Log.WriteLine("Simple name '{0}' was resolved to {1}", identifierExpression, target); if (resolverEnabled) { return ResolveMemberReferenceOnGivenTarget(target, memberReferenceExpression); } else { // Scan children (but not the IdentifierExpression which we already resolved) for (AstNode child = memberReferenceExpression.FirstChild; child != null; child = child.NextSibling) { if (child != identifierExpression) Scan(child); } return null; } } } else { // Regular code path if (resolverEnabled) { ResolveResult target = Resolve(memberReferenceExpression.Target); return ResolveMemberReferenceOnGivenTarget(target, memberReferenceExpression); } else { ScanChildren(memberReferenceExpression); return null; } } } ResolveResult ResolveMemberReferenceOnGivenTarget(ResolveResult target, MemberReferenceExpression memberReferenceExpression) { var typeArguments = GetTypeArguments(memberReferenceExpression.TypeArguments); return resolver.ResolveMemberAccess( target, memberReferenceExpression.MemberName, typeArguments, IsTargetOfInvocation(memberReferenceExpression)); } public override ResolveResult VisitInvocationExpression(InvocationExpression invocationExpression, object data) { // rr = Resolve(invocationExpression) // target = Resolve(memberReferenceExpression = invocationExpression.Target) // idRR = Resolve(identifierExpression = memberReferenceExpression.Target) // trr = ResolveType(identifierExpression) MemberReferenceExpression mre = invocationExpression.Target as MemberReferenceExpression; IdentifierExpression identifierExpression = mre != null ? mre.Target as IdentifierExpression : null; if (identifierExpression != null && identifierExpression.TypeArguments.Count == 0) { // Special handling for §7.6.4.1 Identicial simple names and type names ResolveResult idRR = resolver.ResolveSimpleName(identifierExpression.Identifier, EmptyList.Instance); ResolveResult target = ResolveMemberReferenceOnGivenTarget(idRR, mre); resolveResultCache[mre] = target; Log.WriteLine("Member reference '{0}' on potentially-ambiguous simple-name was resolved to {1}", mre, target); TypeResolveResult trr; if (IsVariableReferenceWithSameType(idRR, identifierExpression.Identifier, out trr)) { // It's ambiguous ResolveResult rr = ResolveInvocationOnGivenTarget(target, invocationExpression); resolveResultCache[identifierExpression] = IsStaticResult(target, rr) ? trr : idRR; Log.WriteLine("Ambiguous simple name '{0}' was resolved to {1}", identifierExpression, resolveResultCache[identifierExpression]); return rr; } else { // It's not ambiguous Log.WriteLine("Simple name '{0}' was resolved to {1}", identifierExpression, idRR); resolveResultCache[identifierExpression] = idRR; if (resolverEnabled) { return ResolveInvocationOnGivenTarget(target, invocationExpression); } else { // Scan children (but not the MRE which we already resolved) for (AstNode child = invocationExpression.FirstChild; child != null; child = child.NextSibling) { if (child != mre) Scan(child); } return null; } } } else { // Regular code path if (resolverEnabled) { ResolveResult target = Resolve(invocationExpression.Target); return ResolveInvocationOnGivenTarget(target, invocationExpression); } else { ScanChildren(invocationExpression); return null; } } } ResolveResult ResolveInvocationOnGivenTarget(ResolveResult target, InvocationExpression invocationExpression) { string[] argumentNames; ResolveResult[] arguments = GetArguments(invocationExpression.Arguments, out argumentNames); return resolver.ResolveInvocation(target, arguments, argumentNames); } #endregion #region Lamdbas / Anonymous Functions public override ResolveResult VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, object data) { return HandleExplicitlyTypedLambda( anonymousMethodExpression.Parameters, anonymousMethodExpression.Body, isAnonymousMethod: true, hasParameterList: anonymousMethodExpression.HasParameterList); } public override ResolveResult VisitLambdaExpression(LambdaExpression lambdaExpression, object data) { Debug.Assert(resolverEnabled); bool isExplicitlyTyped = false; bool isImplicitlyTyped = false; foreach (var p in lambdaExpression.Parameters) { isImplicitlyTyped |= p.Type.IsNull; isExplicitlyTyped |= !p.Type.IsNull; } if (isExplicitlyTyped || !isImplicitlyTyped) { return HandleExplicitlyTypedLambda( lambdaExpression.Parameters, lambdaExpression.Body, isAnonymousMethod: false, hasParameterList: true); } else { return new ImplicitlyTypedLambda(lambdaExpression, this); } } #region Explicitly typed ExplicitlyTypedLambda HandleExplicitlyTypedLambda( AstNodeCollection parameterDeclarations, AstNode body, bool isAnonymousMethod, bool hasParameterList) { List parameters = new List(); resolver.PushLambdaBlock(); foreach (var pd in parameterDeclarations) { ITypeReference type = MakeTypeReference(pd.Type); if (pd.ParameterModifier == ParameterModifier.Ref || pd.ParameterModifier == ParameterModifier.Out) type = ByReferenceTypeReference.Create(type); var p = resolver.AddLambdaParameter(type, MakeRegion(pd), pd.Name, isRef: pd.ParameterModifier == ParameterModifier.Ref, isOut: pd.ParameterModifier == ParameterModifier.Out); parameters.Add(p); Scan(pd); } var lambda = new ExplicitlyTypedLambda(parameters, isAnonymousMethod, resolver.Clone(), this, body); Scan(body); resolver.PopBlock(); return lambda; } DomRegion MakeRegion(AstNode node) { return new DomRegion(parsedFile.FileName, node.StartLocation, node.EndLocation); } sealed class ExplicitlyTypedLambda : LambdaBase { readonly IList parameters; readonly bool isAnonymousMethod; CSharpResolver storedContext; ResolveVisitor visitor; AstNode body; IType inferredReturnType; IList returnValues; bool isValidAsVoidMethod; bool success; // The actual return type is set when the lambda is applied by the conversion. IType actualReturnType; internal override bool IsUndecided { get { return actualReturnType == null; } } internal override AstNode LambdaExpression { get { return body.Parent; } } public ExplicitlyTypedLambda(IList parameters, bool isAnonymousMethod, CSharpResolver storedContext, ResolveVisitor visitor, AstNode body) { this.parameters = parameters; this.isAnonymousMethod = isAnonymousMethod; this.storedContext = storedContext; this.visitor = visitor; this.body = body; if (visitor.undecidedLambdas == null) visitor.undecidedLambdas = new List(); visitor.undecidedLambdas.Add(this); Log.WriteLine("Added undecided explicitly-typed lambda: " + this.LambdaExpression); } public override IList Parameters { get { return parameters ?? EmptyList.Instance; } } bool Analyze() { // If it's not already analyzed if (inferredReturnType == null) { Log.WriteLine("Analyzing " + this.LambdaExpression + "..."); Log.Indent(); visitor.ResetContext( storedContext, delegate { visitor.AnalyzeLambda(body, out success, out isValidAsVoidMethod, out inferredReturnType, out returnValues); }); Log.Unindent(); Log.WriteLine("Finished analyzing " + this.LambdaExpression); if (inferredReturnType == null) throw new InvalidOperationException("AnalyzeLambda() didn't set inferredReturnType"); } return success; } public override Conversion IsValid(IType[] parameterTypes, IType returnType, Conversions conversions) { Log.WriteLine("Testing validity of {0} for return-type {1}...", this, returnType); Log.Indent(); bool valid = Analyze() && IsValidLambda(isValidAsVoidMethod, returnValues, returnType, conversions); Log.Unindent(); Log.WriteLine("{0} is {1} for return-type {2}", this, valid ? "valid" : "invalid", returnType); if (valid) { return Conversion.AnonymousFunctionConversion(new AnonymousFunctionConversionData(returnType, this)); } else { return Conversion.None; } } public override IType GetInferredReturnType(IType[] parameterTypes) { Analyze(); return inferredReturnType; } public override bool IsImplicitlyTyped { get { return false; } } public override bool IsAnonymousMethod { get { return isAnonymousMethod; } } public override bool HasParameterList { get { return parameters != null; } } public override string ToString() { return "[ExplicitlyTypedLambda " + this.LambdaExpression + "]"; } public void ApplyReturnType(ResolveVisitor parentVisitor, IType returnType) { if (returnType == null) throw new ArgumentNullException("returnType"); if (parentVisitor != visitor) { // Explicitly typed lambdas do not use a nested visitor throw new InvalidOperationException(); } if (actualReturnType != null) { if (actualReturnType.Equals(returnType)) return; // return type already set throw new InvalidOperationException("inconsistent return types for explicitly-typed lambda"); } actualReturnType = returnType; visitor.undecidedLambdas.Remove(this); Analyze(); Log.WriteLine("Applying return type {0} to explicitly-typed lambda {1}", returnType, this.LambdaExpression); foreach (var returnValue in returnValues) { visitor.ProcessConversion(returnValue, returnType); } } internal override void EnforceMerge(ResolveVisitor parentVisitor) { ApplyReturnType(parentVisitor, SharedTypes.UnknownType); } } #endregion #region Implicitly typed sealed class ImplicitlyTypedLambda : LambdaBase { internal readonly LambdaExpression lambda; readonly CSharpResolver storedContext; readonly ParsedFile parsedFile; readonly List hypotheses = new List(); readonly List parameters = new List(); internal LambdaTypeHypothesis winningHypothesis; internal readonly ResolveVisitor parentVisitor; internal override bool IsUndecided { get { return winningHypothesis == null; } } internal override AstNode LambdaExpression { get { return lambda; } } public ImplicitlyTypedLambda(LambdaExpression lambda, ResolveVisitor parentVisitor) { this.lambda = lambda; this.parentVisitor = parentVisitor; this.storedContext = parentVisitor.resolver.Clone(); this.parsedFile = parentVisitor.parsedFile; foreach (var pd in lambda.Parameters) { parameters.Add(new DefaultParameter(SharedTypes.UnknownType, pd.Name) { Region = parentVisitor.MakeRegion(pd) }); } if (parentVisitor.undecidedLambdas == null) parentVisitor.undecidedLambdas = new List(); parentVisitor.undecidedLambdas.Add(this); Log.WriteLine("Added undecided implicitly-typed lambda: " + lambda); } public override IList Parameters { get { return parameters; } } public override Conversion IsValid(IType[] parameterTypes, IType returnType, Conversions conversions) { Log.WriteLine("Testing validity of {0} for parameters ({1}) and return-type {2}...", this, string.Join(", ", parameterTypes), returnType); Log.Indent(); var hypothesis = GetHypothesis(parameterTypes); Conversion c = hypothesis.IsValid(returnType, conversions); Log.Unindent(); Log.WriteLine("{0} is {1} for return-type {2}", hypothesis, c ? "valid" : "invalid", returnType); return c; } public override IType GetInferredReturnType(IType[] parameterTypes) { return GetHypothesis(parameterTypes).inferredReturnType; } LambdaTypeHypothesis GetHypothesis(IType[] parameterTypes) { if (parameterTypes.Length != parameters.Count) throw new ArgumentException("Incorrect parameter type count"); foreach (var h in hypotheses) { bool ok = true; for (int i = 0; i < parameterTypes.Length; i++) { if (!parameterTypes[i].Equals(h.parameterTypes[i])) { ok = false; break; } } if (ok) return h; } ResolveVisitor visitor = new ResolveVisitor(storedContext.Clone(), parsedFile); LambdaTypeHypothesis newHypothesis = new LambdaTypeHypothesis(this, parameterTypes, visitor); hypotheses.Add(newHypothesis); return newHypothesis; } /// /// Get any hypothesis for this lambda. /// This method is used as fallback if the lambda isn't merged the normal way (AnonymousFunctionConversion) /// internal LambdaTypeHypothesis GetAnyHypothesis() { if (hypotheses.Count == 0) { // make a new hypothesis with unknown parameter types IType[] parameterTypes = new IType[parameters.Count]; for (int i = 0; i < parameterTypes.Length; i++) { parameterTypes[i] = SharedTypes.UnknownType; } return GetHypothesis(parameterTypes); } else { // We have the choice, so pick the hypothesis with the least missing parameter types LambdaTypeHypothesis bestHypothesis = hypotheses[0]; int bestHypothesisUnknownParameters = bestHypothesis.CountUnknownParameters(); for (int i = 1; i < hypotheses.Count; i++) { int c = hypotheses[i].CountUnknownParameters(); if (c < bestHypothesisUnknownParameters || (c == bestHypothesisUnknownParameters && hypotheses[i].success && !bestHypothesis.success)) { bestHypothesis = hypotheses[i]; bestHypothesisUnknownParameters = c; } } return bestHypothesis; } } internal override void EnforceMerge(ResolveVisitor parentVisitor) { GetAnyHypothesis().MergeInto(parentVisitor, SharedTypes.UnknownType); } public override bool IsImplicitlyTyped { get { return true; } } public override bool IsAnonymousMethod { get { return false; } } public override bool HasParameterList { get { return true; } } public override string ToString() { return "[ImplicitlyTypedLambda " + lambda + "]"; } } /// /// Every possible set of parameter types gets its own 'hypothetical world'. /// It uses a nested ResolveVisitor that has its own resolve cache, so that resolve results cannot leave the hypothetical world. /// /// Only after overload resolution is applied and the actual parameter types are known, the winning hypothesis will be merged /// with the parent ResolveVisitor. /// This is done when the AnonymousFunctionConversion is applied on the parent visitor. /// sealed class LambdaTypeHypothesis { readonly ImplicitlyTypedLambda lambda; internal readonly IType[] parameterTypes; readonly ResolveVisitor visitor; internal readonly IType inferredReturnType; IList returnValues; bool isValidAsVoidMethod; internal bool success; public LambdaTypeHypothesis(ImplicitlyTypedLambda lambda, IType[] parameterTypes, ResolveVisitor visitor) { Debug.Assert(parameterTypes.Length == lambda.Parameters.Count); this.lambda = lambda; this.parameterTypes = parameterTypes; this.visitor = visitor; Log.WriteLine("Analyzing " + ToString() + "..."); Log.Indent(); visitor.resolver.PushLambdaBlock(); int i = 0; foreach (var pd in lambda.lambda.Parameters) { visitor.resolver.AddLambdaParameter(parameterTypes[i], visitor.MakeRegion(pd), pd.Name, false, false); i++; visitor.Scan(pd); } visitor.AnalyzeLambda(lambda.lambda.Body, out success, out isValidAsVoidMethod, out inferredReturnType, out returnValues); visitor.resolver.PopBlock(); Log.Unindent(); Log.WriteLine("Finished analyzing " + ToString()); } internal int CountUnknownParameters() { int c = 0; foreach (IType t in parameterTypes) { if (t.Kind == TypeKind.Unknown) c++; } return c; } public Conversion IsValid(IType returnType, Conversions conversions) { if (success && IsValidLambda(isValidAsVoidMethod, returnValues, returnType, conversions)) { return Conversion.AnonymousFunctionConversion(new AnonymousFunctionConversionData(returnType, this)); } else { return Conversion.None; } } public void MergeInto(ResolveVisitor parentVisitor, IType returnType) { if (returnType == null) throw new ArgumentNullException("returnType"); if (parentVisitor != lambda.parentVisitor) throw new InvalidOperationException("parent visitor mismatch"); if (lambda.winningHypothesis == this) return; else if (lambda.winningHypothesis != null) throw new InvalidOperationException("Trying to merge conflicting hypotheses"); lambda.winningHypothesis = this; foreach (var returnValue in returnValues) { visitor.ProcessConversion(returnValue, returnType); } visitor.MergeUndecidedLambdas(); Log.WriteLine("Merging " + ToString()); foreach (var pair in visitor.resolveResultCache) { parentVisitor.resolveResultCache.Add(pair.Key, pair.Value); } foreach (var pair in visitor.resolverBeforeDict) { parentVisitor.resolverBeforeDict.Add(pair.Key, pair.Value); } parentVisitor.undecidedLambdas.Remove(lambda); } public override string ToString() { StringBuilder b = new StringBuilder(); b.Append("[LambdaTypeHypothesis ("); for (int i = 0; i < parameterTypes.Length; i++) { if (i > 0) b.Append(", "); b.Append(parameterTypes[i]); b.Append(' '); b.Append(lambda.Parameters[i].Name); } b.Append(") => "); b.Append(lambda.lambda.Body.ToString()); b.Append(']'); return b.ToString(); } } #endregion #region MergeUndecidedLambdas abstract class LambdaBase : LambdaResolveResult { internal abstract bool IsUndecided { get; } internal abstract AstNode LambdaExpression { get; } internal abstract void EnforceMerge(ResolveVisitor parentVisitor); } void MergeUndecidedLambdas() { if (undecidedLambdas == null) return; Log.WriteLine("MergeUndecidedLambdas()..."); Log.Indent(); while (undecidedLambdas.Count > 0) { LambdaBase lambda = undecidedLambdas[0]; AstNode parent = lambda.LambdaExpression.Parent; while (ActsAsParenthsizedExpression(parent)) parent = parent.Parent; CSharpResolver storedResolver; if (parent != null && resolverBeforeDict.TryGetValue(parent, out storedResolver)) { Log.WriteLine("Trying to resolve '" + parent + "' in order to merge the lambda..."); Log.Indent(); ResetContext(storedResolver, delegate { Resolve(parent); }); Log.Unindent(); } else { Log.WriteLine("Could not find a suitable parent for '" + lambda); } if (lambda.IsUndecided) { // Lambda wasn't merged by resolving its parent -> enforce merging Log.WriteLine("Lambda wasn't merged by conversion - enforce merging"); lambda.EnforceMerge(this); } } Log.Unindent(); Log.WriteLine("MergeUndecidedLambdas() finished."); } /// /// Gets whether the node acts as a parenthesized expression, /// that is, it directly returns /// static bool ActsAsParenthsizedExpression(AstNode node) { return node is ParenthesizedExpression || node is CheckedExpression || node is UncheckedExpression; } #endregion #region AnalyzeLambda void AnalyzeLambda(AstNode body, out bool success, out bool isValidAsVoidMethod, out IType inferredReturnType, out IList returnValues) { mode = ResolveVisitorNavigationMode.ResolveAll; Expression expr = body as Expression; if (expr != null) { isValidAsVoidMethod = ExpressionPermittedAsStatement(expr); returnValues = new[] { Resolve(expr) }; inferredReturnType = returnValues[0].Type; } else { Scan(body); AnalyzeLambdaVisitor alv = new AnalyzeLambdaVisitor(); body.AcceptVisitor(alv, null); isValidAsVoidMethod = (alv.ReturnExpressions.Count == 0); if (alv.HasVoidReturnStatements) { returnValues = EmptyList.Instance; inferredReturnType = KnownTypeReference.Void.Resolve(resolver.Context); } else { returnValues = new ResolveResult[alv.ReturnExpressions.Count]; for (int i = 0; i < returnValues.Count; i++) { returnValues[i] = resolveResultCache[alv.ReturnExpressions[i]]; } TypeInference ti = new TypeInference(resolver.Context); bool tiSuccess; inferredReturnType = ti.GetBestCommonType(returnValues, out tiSuccess); // Failure to infer a return type does not make the lambda invalid, // so we can ignore the 'tiSuccess' value } } Log.WriteLine("Lambda return type was inferred to: " + inferredReturnType); // TODO: check for compiler errors within the lambda body success = true; } static bool ExpressionPermittedAsStatement(Expression expr) { UnaryOperatorExpression uoe = expr as UnaryOperatorExpression; if (uoe != null) { switch (uoe.Operator) { case UnaryOperatorType.Increment: case UnaryOperatorType.Decrement: case UnaryOperatorType.PostIncrement: case UnaryOperatorType.PostDecrement: case UnaryOperatorType.Await: return true; default: return false; } } return expr is InvocationExpression || expr is ObjectCreateExpression || expr is AssignmentExpression; } static bool IsValidLambda(bool isValidAsVoidMethod, IList returnValues, IType returnType, Conversions conversions) { if (returnType.Kind == TypeKind.Void) { return isValidAsVoidMethod; } else { if (returnValues.Count == 0) return false; foreach (ResolveResult returnRR in returnValues) { if (!conversions.ImplicitConversion(returnRR, returnType)) return false; } return true; } } sealed class AnalyzeLambdaVisitor : DepthFirstAstVisitor { public bool HasVoidReturnStatements; public List ReturnExpressions = new List(); public override object VisitReturnStatement(ReturnStatement returnStatement, object data) { Expression expr = returnStatement.Expression; if (expr.IsNull) { HasVoidReturnStatements = true; } else { ReturnExpressions.Add(expr); } return null; } public override object VisitAnonymousMethodExpression(AnonymousMethodExpression anonymousMethodExpression, object data) { // don't go into nested lambdas return null; } public override object VisitLambdaExpression(LambdaExpression lambdaExpression, object data) { return null; } } #endregion #endregion #region Local Variable Scopes (Block Statements) public override ResolveResult VisitBlockStatement(BlockStatement blockStatement, object data) { resolver.PushBlock(); ScanChildren(blockStatement); resolver.PopBlock(); return voidResult; } public override ResolveResult VisitUsingStatement(UsingStatement usingStatement, object data) { resolver.PushBlock(); if (resolverEnabled) { for (AstNode child = usingStatement.FirstChild; child != null; child = child.NextSibling) { if (child.Role == UsingStatement.ResourceAcquisitionRole && child is Expression) { ITypeDefinition disposable = resolver.Context.GetTypeDefinition( "System", "IDisposable", 0, StringComparer.Ordinal); ResolveAndProcessConversion((Expression)child, disposable ?? SharedTypes.UnknownType); } Scan(child); } } else { ScanChildren(usingStatement); } resolver.PopBlock(); return voidResult; } public override ResolveResult VisitFixedStatement(FixedStatement fixedStatement, object data) { resolver.PushBlock(); VariableInitializer firstInitializer = fixedStatement.Variables.FirstOrDefault(); ITypeReference type = MakeTypeReference(fixedStatement.Type, firstInitializer != null ? firstInitializer.Initializer : null, false); for (AstNode node = fixedStatement.FirstChild; node != null; node = node.NextSibling) { if (node.Role == FixedStatement.Roles.Variable) { VariableInitializer vi = (VariableInitializer)node; resolver.AddVariable(type, MakeRegion(vi) , vi.Name); } Scan(node); } resolver.PopBlock(); return voidResult; } public override ResolveResult VisitForeachStatement(ForeachStatement foreachStatement, object data) { resolver.PushBlock(); ITypeReference type = MakeTypeReference(foreachStatement.VariableType, foreachStatement.InExpression, true); resolver.AddVariable(type, MakeRegion(foreachStatement.VariableNameToken), foreachStatement.VariableName); ScanChildren(foreachStatement); resolver.PopBlock(); return voidResult; } public override ResolveResult VisitCatchClause(CatchClause catchClause, object data) { resolver.PushBlock(); IVariable v = null; if (catchClause.VariableName != null) { v = resolver.AddVariable(MakeTypeReference(catchClause.Type, null, false), MakeRegion(catchClause.VariableNameToken), catchClause.VariableName); } ScanChildren(catchClause); resolver.PopBlock(); if (resolverEnabled && v != null) { return new LocalResolveResult(v, v.Type.Resolve(resolver.Context)); } else { return null; } } #endregion #region VariableDeclarationStatement public override ResolveResult VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data) { bool isConst = (variableDeclarationStatement.Modifiers & Modifiers.Const) != 0; VariableInitializer firstInitializer = variableDeclarationStatement.Variables.FirstOrDefault(); ITypeReference type = MakeTypeReference(variableDeclarationStatement.Type, firstInitializer != null ? firstInitializer.Initializer : null, false); int initializerCount = variableDeclarationStatement.Variables.Count; ResolveResult result = null; for (AstNode node = variableDeclarationStatement.FirstChild; node != null; node = node.NextSibling) { if (node.Role == VariableDeclarationStatement.Roles.Variable) { VariableInitializer vi = (VariableInitializer)node; IConstantValue cv = null; if (isConst) throw new NotImplementedException(); resolver.AddVariable(type, MakeRegion(vi), vi.Name, cv); if (resolverEnabled && initializerCount == 1) { result = Resolve(node); } else { Scan(node); } } else { Scan(node); } } return result; } #endregion #region Condition Statements public override ResolveResult VisitForStatement(ForStatement forStatement, object data) { resolver.PushBlock(); HandleConditionStatement(forStatement); resolver.PopBlock(); return voidResult; } public override ResolveResult VisitIfElseStatement(IfElseStatement ifElseStatement, object data) { HandleConditionStatement(ifElseStatement); return voidResult; } public override ResolveResult VisitWhileStatement(WhileStatement whileStatement, object data) { HandleConditionStatement(whileStatement); return voidResult; } public override ResolveResult VisitDoWhileStatement(DoWhileStatement doWhileStatement, object data) { HandleConditionStatement(doWhileStatement); return voidResult; } void HandleConditionStatement(Statement conditionStatement) { if (resolverEnabled) { for (AstNode child = conditionStatement.FirstChild; child != null; child = child.NextSibling) { if (child.Role == AstNode.Roles.Condition) { ResolveAndProcessConversion((Expression)child, KnownTypeReference.Boolean.Resolve(resolver.Context)); } else { Scan(child); } } } else { ScanChildren(conditionStatement); } } #endregion #region Return Statements public override ResolveResult VisitReturnStatement(ReturnStatement returnStatement, object data) { if (resolverEnabled && !resolver.IsWithinLambdaExpression && resolver.CurrentMember != null) { ResolveAndProcessConversion(returnStatement.Expression, resolver.CurrentMember.ReturnType.Resolve(resolver.Context)); } else { Scan(returnStatement.Expression); } return voidResult; } public override ResolveResult VisitYieldStatement(YieldStatement yieldStatement, object data) { if (resolverEnabled && resolver.CurrentMember != null) { IType returnType = resolver.CurrentMember.ReturnType.Resolve(resolver.Context); IType elementType = GetElementType(returnType, resolver.Context, true); ResolveAndProcessConversion(yieldStatement.Expression, elementType); } else { Scan(yieldStatement.Expression); } return voidResult; } public override ResolveResult VisitYieldBreakStatement(YieldBreakStatement yieldBreakStatement, object data) { return voidResult; } #endregion #region Local Variable Type Inference /// /// Creates a type reference for the specified type node. /// If the type node is 'var', performs type inference on the initializer expression. /// ITypeReference MakeTypeReference(AstType type, AstNode initializerExpression, bool isForEach) { bool needsResolving; if (mode == ResolveVisitorNavigationMode.ResolveAll) { needsResolving = true; } else { var modeForType = navigator.Scan(type); needsResolving = (modeForType == ResolveVisitorNavigationMode.Resolve || modeForType == ResolveVisitorNavigationMode.ResolveAll); } if (initializerExpression != null && IsVar(type)) { var typeRef = new VarTypeReference(this, resolver.Clone(), initializerExpression, isForEach); if (needsResolving) { // Hack: I don't see a clean way to make the 'var' SimpleType resolve to the inferred type, // so we just do it here and store the result in the resolver cache. IType actualType = typeRef.Resolve(resolver.Context); if (actualType.Kind != TypeKind.Unknown) { resolveResultCache.Add(type, new TypeResolveResult(actualType)); } else { resolveResultCache.Add(type, errorResult); } return actualType; } else { return typeRef; } } else { // Perf: avoid duplicate resolving of the type (once as ITypeReference, once directly in ResolveVisitor) // if possible. By using ResolveType when we know we need to resolve the node anyways, the resolve cache // can take care of the duplicate call. if (needsResolving) return ResolveType(type); else return MakeTypeReference(type); } } static bool IsVar(AstType returnType) { SimpleType st = returnType as SimpleType; return st != null && st.Identifier == "var" && st.TypeArguments.Count == 0; } ITypeReference MakeTypeReference(AstType type) { return TypeSystemConvertVisitor.ConvertType(type, resolver.CurrentTypeDefinition, resolver.CurrentMember as IMethod, resolver.UsingScope, currentTypeLookupMode); } sealed class VarTypeReference : ITypeReference { ResolveVisitor visitor; CSharpResolver storedContext; AstNode initializerExpression; bool isForEach; IType result; public VarTypeReference(ResolveVisitor visitor, CSharpResolver storedContext, AstNode initializerExpression, bool isForEach) { this.visitor = visitor; this.storedContext = storedContext; this.initializerExpression = initializerExpression; this.isForEach = isForEach; } public IType Resolve(ITypeResolveContext context) { if (visitor == null) return result ?? SharedTypes.UnknownType; visitor.ResetContext( storedContext, delegate { result = visitor.Resolve(initializerExpression).Type; if (isForEach) { result = GetElementType(result, storedContext.Context, false); } }); visitor = null; storedContext = null; initializerExpression = null; return result; } public override string ToString() { if (visitor == null) return "var=" + result; else return "var (not yet resolved)"; } } static IType GetElementType(IType result, ITypeResolveContext context, bool allowIEnumerator) { bool foundSimpleIEnumerable = false; foreach (IType baseType in result.GetAllBaseTypes(context)) { ITypeDefinition baseTypeDef = baseType.GetDefinition(); if (baseTypeDef != null && ( baseTypeDef.Name == "IEnumerable" || (allowIEnumerator && baseType.Name == "IEnumerator"))) { if (baseTypeDef.Namespace == "System.Collections.Generic" && baseTypeDef.TypeParameterCount == 1) { ParameterizedType pt = baseType as ParameterizedType; if (pt != null) { return pt.TypeArguments[0]; } } else if (baseTypeDef.Namespace == "System.Collections" && baseTypeDef.TypeParameterCount == 0) { foundSimpleIEnumerable = true; } } } // System.Collections.IEnumerable found in type hierarchy -> Object is element type. if (foundSimpleIEnumerable) return KnownTypeReference.Object.Resolve(context); return SharedTypes.UnknownType; } #endregion #region Attributes public override ResolveResult VisitAttribute(Attribute attribute, object data) { if (resolverEnabled) { var type = ResolveType(attribute.Type); // Separate arguments into ctor arguments and non-ctor arguments: var constructorArguments = attribute.Arguments.Where(a => !(a is AssignmentExpression)); var nonConstructorArguments = attribute.Arguments.Where(a => a is AssignmentExpression); // Scan the non-constructor arguments foreach (var arg in nonConstructorArguments) Scan(arg); // TODO: handle these like object initializers // Resolve the ctor arguments and find the matching ctor overload string[] argumentNames; ResolveResult[] arguments = GetArguments(constructorArguments, out argumentNames); return resolver.ResolveObjectCreation(type, arguments, argumentNames); } else { ScanChildren(attribute); return null; } } #endregion #region Using Declaration public override ResolveResult VisitUsingDeclaration(UsingDeclaration usingDeclaration, object data) { currentTypeLookupMode = SimpleNameLookupMode.TypeInUsingDeclaration; ScanChildren(usingDeclaration); currentTypeLookupMode = SimpleNameLookupMode.Type; return null; } public override ResolveResult VisitUsingAliasDeclaration(UsingAliasDeclaration usingDeclaration, object data) { currentTypeLookupMode = SimpleNameLookupMode.TypeInUsingDeclaration; ScanChildren(usingDeclaration); currentTypeLookupMode = SimpleNameLookupMode.Type; return null; } #endregion #region Type References public override ResolveResult VisitPrimitiveType(PrimitiveType primitiveType, object data) { if (!resolverEnabled) return null; IType type = MakeTypeReference(primitiveType).Resolve(resolver.Context); if (type.Kind != TypeKind.Unknown) return new TypeResolveResult(type); else return errorResult; } ResolveResult HandleAttributeType(AstType astType) { ScanChildren(astType); IType type = TypeSystemConvertVisitor.ConvertAttributeType(astType, resolver.CurrentTypeDefinition, resolver.CurrentMember as IMethod, resolver.UsingScope).Resolve(resolver.Context); if (type.Kind != TypeKind.Unknown) return new TypeResolveResult(type); else return errorResult; } public override ResolveResult VisitSimpleType(SimpleType simpleType, object data) { if (!resolverEnabled) { ScanChildren(simpleType); return null; } if (simpleType.Parent is Attribute) { return HandleAttributeType(simpleType); } var typeArguments = GetTypeArguments(simpleType.TypeArguments); return resolver.LookupSimpleNameOrTypeName(simpleType.Identifier, typeArguments, currentTypeLookupMode); } public override ResolveResult VisitMemberType(MemberType memberType, object data) { if (!resolverEnabled) { ScanChildren(memberType); return null; } if (memberType.Parent is Attribute) { return HandleAttributeType(memberType); } ResolveResult target; if (memberType.IsDoubleColon && memberType.Target is SimpleType) { target = resolver.ResolveAlias(((SimpleType)memberType.Target).Identifier); } else { target = Resolve(memberType.Target); } var typeArguments = GetTypeArguments(memberType.TypeArguments); return resolver.ResolveMemberAccess(target, memberType.MemberName, typeArguments); } public override ResolveResult VisitComposedType(ComposedType composedType, object data) { if (!resolverEnabled) { ScanChildren(composedType); return null; } IType t = ResolveType(composedType.BaseType); if (composedType.HasNullableSpecifier) { t = NullableType.Create(t, resolver.Context); } for (int i = 0; i < composedType.PointerRank; i++) { t = new PointerType(t); } foreach (var a in composedType.ArraySpecifiers.Reverse()) { t = new ArrayType(t, a.Dimensions); } return new TypeResolveResult(t); } #endregion #region Query Expressions public override ResolveResult VisitQueryExpression(QueryExpression queryExpression, object data) { throw new NotImplementedException(); } #endregion #region Constructor Initializer public override ResolveResult VisitConstructorInitializer(ConstructorInitializer constructorInitializer, object data) { if (!resolverEnabled) { ScanChildren(constructorInitializer); return null; } ResolveResult target; if (constructorInitializer.ConstructorInitializerType == ConstructorInitializerType.Base) { target = resolver.ResolveBaseReference(); } else { target = resolver.ResolveThisReference(); } string[] argumentNames; ResolveResult[] arguments = GetArguments(constructorInitializer.Arguments, out argumentNames); return resolver.ResolveObjectCreation(target.Type, arguments, argumentNames); } #endregion public override ResolveResult VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, object data) { // TODO: array initializers are valid expressions if the parent node is a variable/field declaration // that explicitly defines an array type ScanChildren(arrayInitializerExpression); return errorResult; } public override ResolveResult VisitNamedArgumentExpression(NamedArgumentExpression namedArgumentExpression, object data) { throw new NotImplementedException(); } #region Token Nodes public override ResolveResult VisitIdentifier(Identifier identifier, object data) { return null; } public override ResolveResult VisitComment(Comment comment, object data) { return null; } public override ResolveResult VisitCSharpTokenNode(CSharpTokenNode cSharpTokenNode, object data) { return null; } #endregion } }