// Copyright (c) 2010-2013 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 ICSharpCode.NRefactory.CSharp.TypeSystem; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.Utils; namespace ICSharpCode.NRefactory.CSharp.Resolver { /// /// Contains the main resolver logic. /// /// /// This class is thread-safe. /// public class CSharpResolver : ICodeContext { static readonly ResolveResult ErrorResult = ErrorResolveResult.UnknownError; readonly ICompilation compilation; internal readonly CSharpConversions conversions; readonly CSharpTypeResolveContext context; readonly bool checkForOverflow; readonly bool isWithinLambdaExpression; #region Constructor public CSharpResolver(ICompilation compilation) { if (compilation == null) throw new ArgumentNullException("compilation"); this.compilation = compilation; this.conversions = CSharpConversions.Get(compilation); this.context = new CSharpTypeResolveContext(compilation.MainAssembly); var pc = compilation.MainAssembly.UnresolvedAssembly as CSharpProjectContent; if (pc != null) { this.checkForOverflow = pc.CompilerSettings.CheckForOverflow; } } public CSharpResolver(CSharpTypeResolveContext context) { if (context == null) throw new ArgumentNullException("context"); this.compilation = context.Compilation; this.conversions = CSharpConversions.Get(compilation); this.context = context; if (context.CurrentTypeDefinition != null) currentTypeDefinitionCache = new TypeDefinitionCache(context.CurrentTypeDefinition); } private CSharpResolver(ICompilation compilation, CSharpConversions conversions, CSharpTypeResolveContext context, bool checkForOverflow, bool isWithinLambdaExpression, TypeDefinitionCache currentTypeDefinitionCache, ImmutableStack localVariableStack, ObjectInitializerContext objectInitializerStack) { this.compilation = compilation; this.conversions = conversions; this.context = context; this.checkForOverflow = checkForOverflow; this.isWithinLambdaExpression = isWithinLambdaExpression; this.currentTypeDefinitionCache = currentTypeDefinitionCache; this.localVariableStack = localVariableStack; this.objectInitializerStack = objectInitializerStack; } #endregion #region Properties /// /// Gets the compilation used by the resolver. /// public ICompilation Compilation { get { return compilation; } } /// /// Gets the current type resolve context. /// public CSharpTypeResolveContext CurrentTypeResolveContext { get { return context; } } IAssembly ITypeResolveContext.CurrentAssembly { get { return context.CurrentAssembly; } } CSharpResolver WithContext(CSharpTypeResolveContext newContext) { return new CSharpResolver(compilation, conversions, newContext, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack); } /// /// Gets whether the current context is checked. /// public bool CheckForOverflow { get { return checkForOverflow; } } /// /// Sets whether the current context is checked. /// public CSharpResolver WithCheckForOverflow(bool checkForOverflow) { if (checkForOverflow == this.checkForOverflow) return this; return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack); } /// /// Gets whether the resolver is currently within a lambda expression or anonymous method. /// public bool IsWithinLambdaExpression { get { return isWithinLambdaExpression; } } /// /// Sets whether the resolver is currently within a lambda expression. /// public CSharpResolver WithIsWithinLambdaExpression(bool isWithinLambdaExpression) { return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack); } /// /// Gets the current member definition that is used to look up identifiers as parameters /// or type parameters. /// public IMember CurrentMember { get { return context.CurrentMember; } } /// /// Sets the current member definition. /// /// Don't forget to also set CurrentTypeDefinition when setting CurrentMember; /// setting one of the properties does not automatically set the other. public CSharpResolver WithCurrentMember(IMember member) { return WithContext(context.WithCurrentMember(member)); } ITypeResolveContext ITypeResolveContext.WithCurrentMember(IMember member) { return WithCurrentMember(member); } /// /// Gets the current using scope that is used to look up identifiers as class names. /// public ResolvedUsingScope CurrentUsingScope { get { return context.CurrentUsingScope; } } /// /// Sets the current using scope that is used to look up identifiers as class names. /// public CSharpResolver WithCurrentUsingScope(ResolvedUsingScope usingScope) { return WithContext(context.WithUsingScope(usingScope)); } #endregion #region Per-CurrentTypeDefinition Cache readonly TypeDefinitionCache currentTypeDefinitionCache; /// /// Gets the current type definition. /// public ITypeDefinition CurrentTypeDefinition { get { return context.CurrentTypeDefinition; } } /// /// Sets the current type definition. /// public CSharpResolver WithCurrentTypeDefinition(ITypeDefinition typeDefinition) { if (this.CurrentTypeDefinition == typeDefinition) return this; TypeDefinitionCache newTypeDefinitionCache; if (typeDefinition != null) newTypeDefinitionCache = new TypeDefinitionCache(typeDefinition); else newTypeDefinitionCache = null; return new CSharpResolver(compilation, conversions, context.WithCurrentTypeDefinition(typeDefinition), checkForOverflow, isWithinLambdaExpression, newTypeDefinitionCache, localVariableStack, objectInitializerStack); } ITypeResolveContext ITypeResolveContext.WithCurrentTypeDefinition(ITypeDefinition typeDefinition) { return WithCurrentTypeDefinition(typeDefinition); } sealed class TypeDefinitionCache { public readonly ITypeDefinition TypeDefinition; public readonly Dictionary SimpleNameLookupCacheExpression = new Dictionary(); public readonly Dictionary SimpleNameLookupCacheInvocationTarget = new Dictionary(); public readonly Dictionary SimpleTypeLookupCache = new Dictionary(); public TypeDefinitionCache(ITypeDefinition typeDefinition) { this.TypeDefinition = typeDefinition; } } #endregion #region Local Variable Management // We store the local variables in an immutable stack. // The beginning of a block is marked by a null entry. // This data structure is used to allow efficient cloning of the resolver with its local variable context. readonly ImmutableStack localVariableStack = ImmutableStack.Empty; CSharpResolver WithLocalVariableStack(ImmutableStack stack) { return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, stack, objectInitializerStack); } /// /// Opens a new scope for local variables. /// public CSharpResolver PushBlock() { return WithLocalVariableStack(localVariableStack.Push(null)); } /// /// Closes the current scope for local variables; removing all variables in that scope. /// public CSharpResolver PopBlock() { var stack = localVariableStack; IVariable removedVar; do { removedVar = stack.Peek(); stack = stack.Pop(); } while (removedVar != null); return WithLocalVariableStack(stack); } /// /// Adds a new variable or lambda parameter to the current block. /// public CSharpResolver AddVariable(IVariable variable) { if (variable == null) throw new ArgumentNullException("variable"); return WithLocalVariableStack(localVariableStack.Push(variable)); } /// /// Removes the variable that was just added. /// public CSharpResolver PopLastVariable() { if (localVariableStack.Peek() == null) throw new InvalidOperationException("There is no variable within the current block."); return WithLocalVariableStack(localVariableStack.Pop()); } /// /// Gets all currently visible local variables and lambda parameters. /// Does not include method parameters. /// public IEnumerable LocalVariables { get { return localVariableStack.Where(v => v != null); } } #endregion #region Object Initializer Context sealed class ObjectInitializerContext { internal readonly ResolveResult initializedObject; internal readonly ObjectInitializerContext prev; public ObjectInitializerContext(ResolveResult initializedObject, CSharpResolver.ObjectInitializerContext prev) { this.initializedObject = initializedObject; this.prev = prev; } } readonly ObjectInitializerContext objectInitializerStack; CSharpResolver WithObjectInitializerStack(ObjectInitializerContext stack) { return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, stack); } /// /// Pushes the type of the object that is currently being initialized. /// public CSharpResolver PushObjectInitializer(ResolveResult initializedObject) { if (initializedObject == null) throw new ArgumentNullException("initializedObject"); return WithObjectInitializerStack(new ObjectInitializerContext(initializedObject, objectInitializerStack)); } public CSharpResolver PopObjectInitializer() { if (objectInitializerStack == null) throw new InvalidOperationException(); return WithObjectInitializerStack(objectInitializerStack.prev); } /// /// Gets whether this context is within an object initializer. /// public bool IsInObjectInitializer { get { return objectInitializerStack != null; } } /// /// Gets the current object initializer. This usually is an /// or (for nested initializers) a semantic tree based on an . /// Returns ErrorResolveResult if there is no object initializer. /// public ResolveResult CurrentObjectInitializer { get { return objectInitializerStack != null ? objectInitializerStack.initializedObject : ErrorResult; } } /// /// Gets the type of the object currently being initialized. /// Returns SharedTypes.Unknown if no object initializer is currently open (or if the object initializer /// has unknown type). /// public IType CurrentObjectInitializerType { get { return CurrentObjectInitializer.Type; } } #endregion #region Clone /// /// Creates a copy of this CSharp resolver. /// [Obsolete("CSharpResolver is immutable, cloning is no longer necessary")] public CSharpResolver Clone() { return this; } #endregion #region ResolveUnaryOperator #region ResolveUnaryOperator method public ResolveResult ResolveUnaryOperator(UnaryOperatorType op, ResolveResult expression) { if (expression.Type.Kind == TypeKind.Dynamic) { if (op == UnaryOperatorType.Await) { return new AwaitResolveResult(SpecialType.Dynamic, new DynamicInvocationResolveResult(new DynamicMemberResolveResult(expression, "GetAwaiter"), DynamicInvocationType.Invocation, EmptyList.Instance), SpecialType.Dynamic, null, null, null); } else { return UnaryOperatorResolveResult(SpecialType.Dynamic, op, expression); } } // C# 4.0 spec: §7.3.3 Unary operator overload resolution string overloadableOperatorName = GetOverloadableOperatorName(op); if (overloadableOperatorName == null) { switch (op) { case UnaryOperatorType.Dereference: PointerType p = expression.Type as PointerType; if (p != null) return UnaryOperatorResolveResult(p.ElementType, op, expression); else return ErrorResult; case UnaryOperatorType.AddressOf: return UnaryOperatorResolveResult(new PointerType(expression.Type), op, expression); case UnaryOperatorType.Await: { ResolveResult getAwaiterMethodGroup = ResolveMemberAccess(expression, "GetAwaiter", EmptyList.Instance, NameLookupMode.InvocationTarget); ResolveResult getAwaiterInvocation = ResolveInvocation(getAwaiterMethodGroup, new ResolveResult[0], argumentNames: null, allowOptionalParameters: false); var lookup = CreateMemberLookup(); IMethod getResultMethod; IType awaitResultType; var getResultMethodGroup = lookup.Lookup(getAwaiterInvocation, "GetResult", EmptyList.Instance, true) as MethodGroupResolveResult; if (getResultMethodGroup != null) { var getResultOR = getResultMethodGroup.PerformOverloadResolution(compilation, new ResolveResult[0], allowExtensionMethods: false, conversions: conversions); getResultMethod = getResultOR.FoundApplicableCandidate ? getResultOR.GetBestCandidateWithSubstitutedTypeArguments() as IMethod : null; awaitResultType = getResultMethod != null ? getResultMethod.ReturnType : SpecialType.UnknownType; } else { getResultMethod = null; awaitResultType = SpecialType.UnknownType; } var isCompletedRR = lookup.Lookup(getAwaiterInvocation, "IsCompleted", EmptyList.Instance, false); var isCompletedProperty = (isCompletedRR is MemberResolveResult ? ((MemberResolveResult)isCompletedRR).Member as IProperty : null); if (isCompletedProperty != null && (!isCompletedProperty.ReturnType.IsKnownType(KnownTypeCode.Boolean) || !isCompletedProperty.CanGet)) isCompletedProperty = null; var interfaceOnCompleted = compilation.FindType(KnownTypeCode.INotifyCompletion).GetMethods().FirstOrDefault(x => x.Name == "OnCompleted"); var interfaceUnsafeOnCompleted = compilation.FindType(KnownTypeCode.ICriticalNotifyCompletion).GetMethods().FirstOrDefault(x => x.Name == "UnsafeOnCompleted"); IMethod onCompletedMethod = null; var candidates = getAwaiterInvocation.Type.GetMethods().Where(x => x.ImplementedInterfaceMembers.Select(y => y.MemberDefinition).Contains(interfaceUnsafeOnCompleted)).ToList(); if (candidates.Count == 0) { candidates = getAwaiterInvocation.Type.GetMethods().Where(x => x.ImplementedInterfaceMembers.Select(y => y.MemberDefinition).Contains(interfaceOnCompleted)).ToList(); if (candidates.Count == 1) onCompletedMethod = candidates[0]; } else if (candidates.Count == 1) { onCompletedMethod = candidates[0]; } return new AwaitResolveResult(awaitResultType, getAwaiterInvocation, getAwaiterInvocation.Type, isCompletedProperty, onCompletedMethod, getResultMethod); } default: return ErrorResolveResult.UnknownError; } } // If the type is nullable, get the underlying type: IType type = NullableType.GetUnderlyingType(expression.Type); bool isNullable = NullableType.IsNullable(expression.Type); // the operator is overloadable: OverloadResolution userDefinedOperatorOR = CreateOverloadResolution(new[] { expression }); foreach (var candidate in GetUserDefinedOperatorCandidates(type, overloadableOperatorName)) { userDefinedOperatorOR.AddCandidate(candidate); } if (userDefinedOperatorOR.FoundApplicableCandidate) { return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, UnaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow)); } expression = UnaryNumericPromotion(op, ref type, isNullable, expression); CSharpOperators.OperatorMethod[] methodGroup; CSharpOperators operators = CSharpOperators.Get(compilation); switch (op) { case UnaryOperatorType.Increment: case UnaryOperatorType.Decrement: case UnaryOperatorType.PostIncrement: case UnaryOperatorType.PostDecrement: // C# 4.0 spec: §7.6.9 Postfix increment and decrement operators // C# 4.0 spec: §7.7.5 Prefix increment and decrement operators TypeCode code = ReflectionHelper.GetTypeCode(type); if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer) return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); else return new ErrorResolveResult(expression.Type); case UnaryOperatorType.Plus: methodGroup = operators.UnaryPlusOperators; break; case UnaryOperatorType.Minus: methodGroup = CheckForOverflow ? operators.CheckedUnaryMinusOperators : operators.UncheckedUnaryMinusOperators; break; case UnaryOperatorType.Not: methodGroup = operators.LogicalNegationOperators; break; case UnaryOperatorType.BitNot: if (type.Kind == TypeKind.Enum) { if (expression.IsCompileTimeConstant && !isNullable && expression.ConstantValue != null) { // evaluate as (E)(~(U)x); var U = compilation.FindType(expression.ConstantValue.GetType()); var unpackedEnum = new ConstantResolveResult(U, expression.ConstantValue); var rr = ResolveUnaryOperator(op, unpackedEnum); rr = WithCheckForOverflow(false).ResolveCast(type, rr); if (rr.IsCompileTimeConstant) return rr; } return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); } else { methodGroup = operators.BitwiseComplementOperators; break; } default: throw new InvalidOperationException(); } OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { expression }); foreach (var candidate in methodGroup) { builtinOperatorOR.AddCandidate(candidate); } CSharpOperators.UnaryOperatorMethod m = (CSharpOperators.UnaryOperatorMethod)builtinOperatorOR.BestCandidate; IType resultType = m.ReturnType; if (builtinOperatorOR.BestCandidateErrors != OverloadResolutionErrors.None) { if (userDefinedOperatorOR.BestCandidate != null) { // If there are any user-defined operators, prefer those over the built-in operators. // It'll be a more informative error. return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, UnaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow)); } else if (builtinOperatorOR.BestCandidateAmbiguousWith != null) { // If the best candidate is ambiguous, just use the input type instead // of picking one of the ambiguous overloads. return new ErrorResolveResult(expression.Type); } else { return new ErrorResolveResult(resultType); } } else if (expression.IsCompileTimeConstant && m.CanEvaluateAtCompileTime) { object val; try { val = m.Invoke(this, expression.ConstantValue); } catch (ArithmeticException) { return new ErrorResolveResult(resultType); } return new ConstantResolveResult(resultType, val); } else { expression = Convert(expression, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]); return UnaryOperatorResolveResult(resultType, op, expression, builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator); } } OperatorResolveResult UnaryOperatorResolveResult(IType resultType, UnaryOperatorType op, ResolveResult expression, bool isLifted = false) { return new OperatorResolveResult( resultType, UnaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow), null, isLifted, new[] { expression }); } #endregion #region UnaryNumericPromotion ResolveResult UnaryNumericPromotion(UnaryOperatorType op, ref IType type, bool isNullable, ResolveResult expression) { // C# 4.0 spec: §7.3.6.1 TypeCode code = ReflectionHelper.GetTypeCode(type); if (isNullable && type.Kind == TypeKind.Null) code = TypeCode.SByte; // cause promotion of null to int32 switch (op) { case UnaryOperatorType.Minus: if (code == TypeCode.UInt32) { type = compilation.FindType(KnownTypeCode.Int64); return Convert(expression, MakeNullable(type, isNullable), isNullable ? Conversion.ImplicitNullableConversion : Conversion.ImplicitNumericConversion); } goto case UnaryOperatorType.Plus; case UnaryOperatorType.Plus: case UnaryOperatorType.BitNot: if (code >= TypeCode.Char && code <= TypeCode.UInt16) { type = compilation.FindType(KnownTypeCode.Int32); return Convert(expression, MakeNullable(type, isNullable), isNullable ? Conversion.ImplicitNullableConversion : Conversion.ImplicitNumericConversion); } break; } return expression; } #endregion #region GetOverloadableOperatorName static string GetOverloadableOperatorName(UnaryOperatorType op) { switch (op) { case UnaryOperatorType.Not: return "op_LogicalNot"; case UnaryOperatorType.BitNot: return "op_OnesComplement"; case UnaryOperatorType.Minus: return "op_UnaryNegation"; case UnaryOperatorType.Plus: return "op_UnaryPlus"; case UnaryOperatorType.Increment: case UnaryOperatorType.PostIncrement: return "op_Increment"; case UnaryOperatorType.Decrement: case UnaryOperatorType.PostDecrement: return "op_Decrement"; default: return null; } } #endregion #endregion #region ResolveBinaryOperator #region ResolveBinaryOperator method public ResolveResult ResolveBinaryOperator(BinaryOperatorType op, ResolveResult lhs, ResolveResult rhs) { if (lhs.Type.Kind == TypeKind.Dynamic || rhs.Type.Kind == TypeKind.Dynamic) { lhs = Convert(lhs, SpecialType.Dynamic); rhs = Convert(rhs, SpecialType.Dynamic); return BinaryOperatorResolveResult(SpecialType.Dynamic, lhs, op, rhs); } // C# 4.0 spec: §7.3.4 Binary operator overload resolution string overloadableOperatorName = GetOverloadableOperatorName(op); if (overloadableOperatorName == null) { // Handle logical and/or exactly as bitwise and/or: // - If the user overloads a bitwise operator, that implicitly creates the corresponding logical operator. // - If both inputs are compile-time constants, it doesn't matter that we don't short-circuit. // - If inputs aren't compile-time constants, we don't evaluate anything, so again it doesn't matter that we don't short-circuit if (op == BinaryOperatorType.ConditionalAnd) { overloadableOperatorName = GetOverloadableOperatorName(BinaryOperatorType.BitwiseAnd); } else if (op == BinaryOperatorType.ConditionalOr) { overloadableOperatorName = GetOverloadableOperatorName(BinaryOperatorType.BitwiseOr); } else if (op == BinaryOperatorType.NullCoalescing) { // null coalescing operator is not overloadable and needs to be handled separately return ResolveNullCoalescingOperator(lhs, rhs); } else { return ErrorResolveResult.UnknownError; } } // If the type is nullable, get the underlying type: bool isNullable = NullableType.IsNullable(lhs.Type) || NullableType.IsNullable(rhs.Type); IType lhsType = NullableType.GetUnderlyingType(lhs.Type); IType rhsType = NullableType.GetUnderlyingType(rhs.Type); // the operator is overloadable: OverloadResolution userDefinedOperatorOR = CreateOverloadResolution(new[] { lhs, rhs }); HashSet userOperatorCandidates = new HashSet(); userOperatorCandidates.UnionWith(GetUserDefinedOperatorCandidates(lhsType, overloadableOperatorName)); userOperatorCandidates.UnionWith(GetUserDefinedOperatorCandidates(rhsType, overloadableOperatorName)); foreach (var candidate in userOperatorCandidates) { userDefinedOperatorOR.AddCandidate(candidate); } if (userDefinedOperatorOR.FoundApplicableCandidate) { return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow)); } if (lhsType.Kind == TypeKind.Null && rhsType.IsReferenceType == false || lhsType.IsReferenceType == false && rhsType.Kind == TypeKind.Null) { isNullable = true; } if (op == BinaryOperatorType.ShiftLeft || op == BinaryOperatorType.ShiftRight) { // special case: the shift operators allow "var x = null << null", producing int?. if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null) isNullable = true; // for shift operators, do unary promotion independently on both arguments lhs = UnaryNumericPromotion(UnaryOperatorType.Plus, ref lhsType, isNullable, lhs); rhs = UnaryNumericPromotion(UnaryOperatorType.Plus, ref rhsType, isNullable, rhs); } else { bool allowNullableConstants = op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality; if (!BinaryNumericPromotion(isNullable, ref lhs, ref rhs, allowNullableConstants)) return new ErrorResolveResult(lhs.Type); } // re-read underlying types after numeric promotion lhsType = NullableType.GetUnderlyingType(lhs.Type); rhsType = NullableType.GetUnderlyingType(rhs.Type); IEnumerable methodGroup; CSharpOperators operators = CSharpOperators.Get(compilation); switch (op) { case BinaryOperatorType.Multiply: methodGroup = operators.MultiplicationOperators; break; case BinaryOperatorType.Divide: methodGroup = operators.DivisionOperators; break; case BinaryOperatorType.Modulus: methodGroup = operators.RemainderOperators; break; case BinaryOperatorType.Add: methodGroup = operators.AdditionOperators; { if (lhsType.Kind == TypeKind.Enum) { // E operator +(E x, U y); IType underlyingType = MakeNullable(GetEnumUnderlyingType(lhsType), isNullable); if (TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs)) { return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs); } } if (rhsType.Kind == TypeKind.Enum) { // E operator +(U x, E y); IType underlyingType = MakeNullable(GetEnumUnderlyingType(rhsType), isNullable); if (TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs)) { return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs); } } if (lhsType.Kind == TypeKind.Delegate && TryConvert(ref rhs, lhsType)) { return BinaryOperatorResolveResult(lhsType, lhs, op, rhs); } else if (rhsType.Kind == TypeKind.Delegate && TryConvert(ref lhs, rhsType)) { return BinaryOperatorResolveResult(rhsType, lhs, op, rhs); } if (lhsType is PointerType) { methodGroup = new [] { PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int32), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt32), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int64), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt64) }; } else if (rhsType is PointerType) { methodGroup = new [] { PointerArithmeticOperator(rhsType, KnownTypeCode.Int32, rhsType), PointerArithmeticOperator(rhsType, KnownTypeCode.UInt32, rhsType), PointerArithmeticOperator(rhsType, KnownTypeCode.Int64, rhsType), PointerArithmeticOperator(rhsType, KnownTypeCode.UInt64, rhsType) }; } if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null) return new ErrorResolveResult(SpecialType.NullType); } break; case BinaryOperatorType.Subtract: methodGroup = operators.SubtractionOperators; { if (lhsType.Kind == TypeKind.Enum) { // U operator –(E x, E y); if (TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs, allowConversionFromConstantZero: false)) { return HandleEnumSubtraction(isNullable, lhsType, lhs, rhs); } // E operator –(E x, U y); IType underlyingType = MakeNullable(GetEnumUnderlyingType(lhsType), isNullable); if (TryConvertEnum(ref rhs, underlyingType, ref isNullable, ref lhs)) { return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs); } } if (rhsType.Kind == TypeKind.Enum) { // U operator –(E x, E y); if (TryConvertEnum(ref lhs, rhs.Type, ref isNullable, ref rhs, allowConversionFromConstantZero: false)) { return HandleEnumSubtraction(isNullable, rhsType, lhs, rhs); } // E operator -(U x, E y); IType underlyingType = MakeNullable(GetEnumUnderlyingType(rhsType), isNullable); if (TryConvertEnum(ref lhs, underlyingType, ref isNullable, ref rhs)) { return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs); } } if (lhsType.Kind == TypeKind.Delegate && TryConvert(ref rhs, lhsType)) { return BinaryOperatorResolveResult(lhsType, lhs, op, rhs); } else if (rhsType.Kind == TypeKind.Delegate && TryConvert(ref lhs, rhsType)) { return BinaryOperatorResolveResult(rhsType, lhs, op, rhs); } if (lhsType is PointerType) { if (rhsType is PointerType) { IType int64 = compilation.FindType(KnownTypeCode.Int64); if (lhsType.Equals(rhsType)) { return BinaryOperatorResolveResult(int64, lhs, op, rhs); } else { return new ErrorResolveResult(int64); } } methodGroup = new [] { PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int32), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt32), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.Int64), PointerArithmeticOperator(lhsType, lhsType, KnownTypeCode.UInt64) }; } if (lhsType.Kind == TypeKind.Null && rhsType.Kind == TypeKind.Null) return new ErrorResolveResult(SpecialType.NullType); } break; case BinaryOperatorType.ShiftLeft: methodGroup = operators.ShiftLeftOperators; break; case BinaryOperatorType.ShiftRight: methodGroup = operators.ShiftRightOperators; break; case BinaryOperatorType.Equality: case BinaryOperatorType.InEquality: case BinaryOperatorType.LessThan: case BinaryOperatorType.GreaterThan: case BinaryOperatorType.LessThanOrEqual: case BinaryOperatorType.GreaterThanOrEqual: { if (lhsType.Kind == TypeKind.Enum && TryConvert(ref rhs, lhs.Type)) { // bool operator op(E x, E y); return HandleEnumComparison(op, lhsType, isNullable, lhs, rhs); } else if (rhsType.Kind == TypeKind.Enum && TryConvert(ref lhs, rhs.Type)) { // bool operator op(E x, E y); return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs); } else if (lhsType is PointerType && rhsType is PointerType) { return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs); } if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) { if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) { // If it's a reference comparison if (op == BinaryOperatorType.Equality) methodGroup = operators.ReferenceEqualityOperators; else methodGroup = operators.ReferenceInequalityOperators; break; } else if (lhsType.Kind == TypeKind.Null && IsNullableTypeOrNonValueType(rhs.Type) || IsNullableTypeOrNonValueType(lhs.Type) && rhsType.Kind == TypeKind.Null) { // compare type parameter or nullable type with the null literal return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs); } } switch (op) { case BinaryOperatorType.Equality: methodGroup = operators.ValueEqualityOperators; break; case BinaryOperatorType.InEquality: methodGroup = operators.ValueInequalityOperators; break; case BinaryOperatorType.LessThan: methodGroup = operators.LessThanOperators; break; case BinaryOperatorType.GreaterThan: methodGroup = operators.GreaterThanOperators; break; case BinaryOperatorType.LessThanOrEqual: methodGroup = operators.LessThanOrEqualOperators; break; case BinaryOperatorType.GreaterThanOrEqual: methodGroup = operators.GreaterThanOrEqualOperators; break; default: throw new InvalidOperationException(); } } break; case BinaryOperatorType.BitwiseAnd: case BinaryOperatorType.BitwiseOr: case BinaryOperatorType.ExclusiveOr: { if (lhsType.Kind == TypeKind.Enum) { // bool operator op(E x, E y); if (TryConvertEnum(ref rhs, lhs.Type, ref isNullable, ref lhs)) { return HandleEnumOperator(isNullable, lhsType, op, lhs, rhs); } } if (rhsType.Kind == TypeKind.Enum) { // bool operator op(E x, E y); if (TryConvertEnum (ref lhs, rhs.Type, ref isNullable, ref rhs)) { return HandleEnumOperator(isNullable, rhsType, op, lhs, rhs); } } switch (op) { case BinaryOperatorType.BitwiseAnd: methodGroup = operators.BitwiseAndOperators; break; case BinaryOperatorType.BitwiseOr: methodGroup = operators.BitwiseOrOperators; break; case BinaryOperatorType.ExclusiveOr: methodGroup = operators.BitwiseXorOperators; break; default: throw new InvalidOperationException(); } } break; case BinaryOperatorType.ConditionalAnd: methodGroup = operators.LogicalAndOperators; break; case BinaryOperatorType.ConditionalOr: methodGroup = operators.LogicalOrOperators; break; default: throw new InvalidOperationException(); } OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { lhs, rhs }); foreach (var candidate in methodGroup) { builtinOperatorOR.AddCandidate(candidate); } CSharpOperators.BinaryOperatorMethod m = (CSharpOperators.BinaryOperatorMethod)builtinOperatorOR.BestCandidate; IType resultType = m.ReturnType; if (builtinOperatorOR.BestCandidateErrors != OverloadResolutionErrors.None) { // If there are any user-defined operators, prefer those over the built-in operators. // It'll be a more informative error. if (userDefinedOperatorOR.BestCandidate != null) return CreateResolveResultForUserDefinedOperator(userDefinedOperatorOR, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow)); else return new ErrorResolveResult(resultType); } else if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && m.CanEvaluateAtCompileTime) { object val; try { val = m.Invoke(this, lhs.ConstantValue, rhs.ConstantValue); } catch (ArithmeticException) { return new ErrorResolveResult(resultType); } return new ConstantResolveResult(resultType, val); } else { lhs = Convert(lhs, m.Parameters[0].Type, builtinOperatorOR.ArgumentConversions[0]); rhs = Convert(rhs, m.Parameters[1].Type, builtinOperatorOR.ArgumentConversions[1]); return BinaryOperatorResolveResult(resultType, lhs, op, rhs, builtinOperatorOR.BestCandidate is OverloadResolution.ILiftedOperator); } } bool IsNullableTypeOrNonValueType(IType type) { return NullableType.IsNullable(type) || type.IsReferenceType != false; } ResolveResult BinaryOperatorResolveResult(IType resultType, ResolveResult lhs, BinaryOperatorType op, ResolveResult rhs, bool isLifted = false) { return new OperatorResolveResult( resultType, BinaryOperatorExpression.GetLinqNodeType(op, this.CheckForOverflow), null, isLifted, new[] { lhs, rhs }); } #endregion #region Pointer arithmetic CSharpOperators.BinaryOperatorMethod PointerArithmeticOperator(IType resultType, IType inputType1, KnownTypeCode inputType2) { return PointerArithmeticOperator(resultType, inputType1, compilation.FindType(inputType2)); } CSharpOperators.BinaryOperatorMethod PointerArithmeticOperator(IType resultType, KnownTypeCode inputType1, IType inputType2) { return PointerArithmeticOperator(resultType, compilation.FindType(inputType1), inputType2); } CSharpOperators.BinaryOperatorMethod PointerArithmeticOperator(IType resultType, IType inputType1, IType inputType2) { return new CSharpOperators.BinaryOperatorMethod(compilation) { ReturnType = resultType, Parameters = { new DefaultParameter(inputType1, string.Empty), new DefaultParameter(inputType2, string.Empty) } }; } #endregion #region Enum helper methods IType GetEnumUnderlyingType(IType enumType) { ITypeDefinition def = enumType.GetDefinition(); return def != null ? def.EnumUnderlyingType : SpecialType.UnknownType; } /// /// Handle the case where an enum value is compared with another enum value /// bool operator op(E x, E y); /// ResolveResult HandleEnumComparison(BinaryOperatorType op, IType enumType, bool isNullable, ResolveResult lhs, ResolveResult rhs) { // evaluate as ((U)x op (U)y) IType elementType = GetEnumUnderlyingType(enumType); if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && !isNullable && elementType.Kind != TypeKind.Enum) { var rr = ResolveBinaryOperator(op, ResolveCast(elementType, lhs), ResolveCast(elementType, rhs)); if (rr.IsCompileTimeConstant) return rr; } IType resultType = compilation.FindType(KnownTypeCode.Boolean); return BinaryOperatorResolveResult(resultType, lhs, op, rhs, isNullable); } /// /// Handle the case where an enum value is subtracted from another enum value /// U operator –(E x, E y); /// ResolveResult HandleEnumSubtraction(bool isNullable, IType enumType, ResolveResult lhs, ResolveResult rhs) { // evaluate as (U)((U)x – (U)y) IType elementType = GetEnumUnderlyingType(enumType); if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && !isNullable && elementType.Kind != TypeKind.Enum) { var rr = ResolveBinaryOperator(BinaryOperatorType.Subtract, ResolveCast(elementType, lhs), ResolveCast(elementType, rhs)); rr = WithCheckForOverflow(false).ResolveCast(elementType, rr); if (rr.IsCompileTimeConstant) return rr; } IType resultType = MakeNullable(elementType, isNullable); return BinaryOperatorResolveResult(resultType, lhs, BinaryOperatorType.Subtract, rhs, isNullable); } /// /// Handle the following enum operators: /// E operator +(E x, U y); /// E operator +(U x, E y); /// E operator –(E x, U y); /// E operator &(E x, E y); /// E operator |(E x, E y); /// E operator ^(E x, E y); /// ResolveResult HandleEnumOperator(bool isNullable, IType enumType, BinaryOperatorType op, ResolveResult lhs, ResolveResult rhs) { // evaluate as (E)((U)x op (U)y) if (lhs.IsCompileTimeConstant && rhs.IsCompileTimeConstant && !isNullable) { IType elementType = GetEnumUnderlyingType(enumType); if (elementType.Kind != TypeKind.Enum) { var rr = ResolveBinaryOperator(op, ResolveCast(elementType, lhs), ResolveCast(elementType, rhs)); rr = WithCheckForOverflow(false).ResolveCast(enumType, rr); if (rr.IsCompileTimeConstant) // only report result if it's a constant; use the regular OperatorResolveResult codepath otherwise return rr; } } IType resultType = MakeNullable(enumType, isNullable); return BinaryOperatorResolveResult(resultType, lhs, op, rhs, isNullable); } IType MakeNullable(IType type, bool isNullable) { if (isNullable) return NullableType.Create(compilation, type); else return type; } #endregion #region BinaryNumericPromotion bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants) { // C# 4.0 spec: §7.3.6.2 TypeCode lhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(lhs.Type)); TypeCode rhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rhs.Type)); // if one of the inputs is the null literal, promote that to the type of the other operand if (isNullable && lhs.Type.Kind == TypeKind.Null && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) { lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants); lhsCode = rhsCode; } else if (isNullable && rhs.Type.Kind == TypeKind.Null && lhsCode >= TypeCode.Boolean && lhsCode <= TypeCode.Decimal) { rhs = CastTo(lhsCode, isNullable, rhs, allowNullableConstants); rhsCode = lhsCode; } bool bindingError = false; if (lhsCode >= TypeCode.Char && lhsCode <= TypeCode.Decimal && rhsCode >= TypeCode.Char && rhsCode <= TypeCode.Decimal) { TypeCode targetType; if (lhsCode == TypeCode.Decimal || rhsCode == TypeCode.Decimal) { targetType = TypeCode.Decimal; bindingError = (lhsCode == TypeCode.Single || lhsCode == TypeCode.Double || rhsCode == TypeCode.Single || rhsCode == TypeCode.Double); } else if (lhsCode == TypeCode.Double || rhsCode == TypeCode.Double) { targetType = TypeCode.Double; } else if (lhsCode == TypeCode.Single || rhsCode == TypeCode.Single) { targetType = TypeCode.Single; } else if (lhsCode == TypeCode.UInt64 || rhsCode == TypeCode.UInt64) { targetType = TypeCode.UInt64; bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs); } else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) { targetType = TypeCode.Int64; } else if (lhsCode == TypeCode.UInt32 || rhsCode == TypeCode.UInt32) { targetType = (IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs)) ? TypeCode.Int64 : TypeCode.UInt32; } else { targetType = TypeCode.Int32; } lhs = CastTo(targetType, isNullable, lhs, allowNullableConstants); rhs = CastTo(targetType, isNullable, rhs, allowNullableConstants); } return !bindingError; } bool IsSigned(TypeCode code, ResolveResult rr) { // Determine whether the rr with code==ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rr.Type)) // is a signed primitive type. switch (code) { case TypeCode.SByte: case TypeCode.Int16: return true; case TypeCode.Int32: // for int, consider implicit constant expression conversion if (rr.IsCompileTimeConstant && rr.ConstantValue != null && (int)rr.ConstantValue >= 0) return false; else return true; case TypeCode.Int64: // for long, consider implicit constant expression conversion if (rr.IsCompileTimeConstant && rr.ConstantValue != null && (long)rr.ConstantValue >= 0) return false; else return true; default: return false; } } ResolveResult CastTo(TypeCode targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants) { IType elementType = compilation.FindType(targetType); IType nullableType = MakeNullable(elementType, isNullable); if (nullableType.Equals(expression.Type)) return expression; if (allowNullableConstants && expression.IsCompileTimeConstant) { if (expression.ConstantValue == null) return new ConstantResolveResult(nullableType, null); ResolveResult rr = ResolveCast(elementType, expression); if (rr.IsError) return rr; Debug.Assert(rr.IsCompileTimeConstant); return new ConstantResolveResult(nullableType, rr.ConstantValue); } else { return Convert(expression, nullableType, isNullable ? Conversion.ImplicitNullableConversion : Conversion.ImplicitNumericConversion); } } #endregion #region GetOverloadableOperatorName static string GetOverloadableOperatorName(BinaryOperatorType op) { switch (op) { case BinaryOperatorType.Add: return "op_Addition"; case BinaryOperatorType.Subtract: return "op_Subtraction"; case BinaryOperatorType.Multiply: return "op_Multiply"; case BinaryOperatorType.Divide: return "op_Division"; case BinaryOperatorType.Modulus: return "op_Modulus"; case BinaryOperatorType.BitwiseAnd: return "op_BitwiseAnd"; case BinaryOperatorType.BitwiseOr: return "op_BitwiseOr"; case BinaryOperatorType.ExclusiveOr: return "op_ExclusiveOr"; case BinaryOperatorType.ShiftLeft: return "op_LeftShift"; case BinaryOperatorType.ShiftRight: return "op_RightShift"; case BinaryOperatorType.Equality: return "op_Equality"; case BinaryOperatorType.InEquality: return "op_Inequality"; case BinaryOperatorType.GreaterThan: return "op_GreaterThan"; case BinaryOperatorType.LessThan: return "op_LessThan"; case BinaryOperatorType.GreaterThanOrEqual: return "op_GreaterThanOrEqual"; case BinaryOperatorType.LessThanOrEqual: return "op_LessThanOrEqual"; default: return null; } } #endregion #region Null coalescing operator ResolveResult ResolveNullCoalescingOperator(ResolveResult lhs, ResolveResult rhs) { if (NullableType.IsNullable(lhs.Type)) { IType a0 = NullableType.GetUnderlyingType(lhs.Type); if (TryConvert(ref rhs, a0)) { return BinaryOperatorResolveResult(a0, lhs, BinaryOperatorType.NullCoalescing, rhs); } } if (TryConvert(ref rhs, lhs.Type)) { return BinaryOperatorResolveResult(lhs.Type, lhs, BinaryOperatorType.NullCoalescing, rhs); } if (TryConvert(ref lhs, rhs.Type)) { return BinaryOperatorResolveResult(rhs.Type, lhs, BinaryOperatorType.NullCoalescing, rhs); } else { return new ErrorResolveResult(lhs.Type); } } #endregion #endregion #region Get user-defined operator candidates IEnumerable GetUserDefinedOperatorCandidates(IType type, string operatorName) { if (operatorName == null) return EmptyList.Instance; TypeCode c = ReflectionHelper.GetTypeCode(type); if (TypeCode.Boolean <= c && c <= TypeCode.Decimal || c == TypeCode.String) { // The .NET framework contains some of C#'s built-in operators as user-defined operators. // However, we must not use those as user-defined operators (we would skip numeric promotion). return EmptyList.Instance; } // C# 4.0 spec: §7.3.5 Candidate user-defined operators var operators = type.GetMethods(m => m.IsOperator && m.Name == operatorName).ToList(); LiftUserDefinedOperators(operators); return operators; } void LiftUserDefinedOperators(List operators) { int nonLiftedMethodCount = operators.Count; // Construct lifted operators for (int i = 0; i < nonLiftedMethodCount; i++) { var liftedMethod = LiftUserDefinedOperator(operators[i]); if (liftedMethod != null) operators.Add(liftedMethod); } } LiftedUserDefinedOperator LiftUserDefinedOperator(IMethod m) { if (IsComparisonOperator(m)) { if (!m.ReturnType.Equals(compilation.FindType(KnownTypeCode.Boolean))) return null; // cannot lift this operator } else { if (!NullableType.IsNonNullableValueType(m.ReturnType)) return null; // cannot lift this operator } for (int i = 0; i < m.Parameters.Count; i++) { if (!NullableType.IsNonNullableValueType(m.Parameters[i].Type)) return null; // cannot lift this operator } return new LiftedUserDefinedOperator(m); } static bool IsComparisonOperator(IMethod m) { var type = OperatorDeclaration.GetOperatorType(m.Name); return type.HasValue && type.Value.IsComparisonOperator(); } sealed class LiftedUserDefinedOperator : SpecializedMethod, OverloadResolution.ILiftedOperator { internal readonly IParameterizedMember nonLiftedOperator; public LiftedUserDefinedOperator(IMethod nonLiftedMethod) : base((IMethod)nonLiftedMethod.MemberDefinition, nonLiftedMethod.Substitution) { this.nonLiftedOperator = nonLiftedMethod; var substitution = new MakeNullableVisitor(nonLiftedMethod.Compilation, nonLiftedMethod.Substitution); this.Parameters = base.CreateParameters(substitution); // Comparison operators keep the 'bool' return type even when lifted. if (IsComparisonOperator(nonLiftedMethod)) this.ReturnType = nonLiftedMethod.ReturnType; else this.ReturnType = nonLiftedMethod.ReturnType.AcceptVisitor(substitution); } public IList NonLiftedParameters { get { return nonLiftedOperator.Parameters; } } public override bool Equals(object obj) { LiftedUserDefinedOperator op = obj as LiftedUserDefinedOperator; return op != null && this.nonLiftedOperator.Equals(op.nonLiftedOperator); } public override int GetHashCode() { return nonLiftedOperator.GetHashCode() ^ 0x7191254; } } sealed class MakeNullableVisitor : TypeVisitor { readonly ICompilation compilation; readonly TypeParameterSubstitution typeParameterSubstitution; public MakeNullableVisitor(ICompilation compilation, TypeParameterSubstitution typeParameterSubstitution) { this.compilation = compilation; this.typeParameterSubstitution = typeParameterSubstitution; } public override IType VisitTypeDefinition(ITypeDefinition type) { return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); } public override IType VisitTypeParameter(ITypeParameter type) { return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); } public override IType VisitParameterizedType(ParameterizedType type) { return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); } public override IType VisitOtherType(IType type) { return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); } } ResolveResult CreateResolveResultForUserDefinedOperator(OverloadResolution r, System.Linq.Expressions.ExpressionType operatorType) { if (r.BestCandidateErrors != OverloadResolutionErrors.None) return r.CreateResolveResult(null); IMethod method = (IMethod)r.BestCandidate; return new OperatorResolveResult(method.ReturnType, operatorType, method, isLiftedOperator: method is OverloadResolution.ILiftedOperator, operands: r.GetArgumentsWithConversions()); } #endregion #region ResolveCast bool TryConvert(ref ResolveResult rr, IType targetType) { Conversion c = conversions.ImplicitConversion(rr, targetType); if (c.IsValid) { rr = Convert(rr, targetType, c); return true; } else { return false; } } /// /// /// /// The input resolve result that should be converted. /// If a conversion exists, it is applied to the resolve result /// The target type that we should convert to /// Whether we are dealing with a lifted operator /// The resolve result that is enum-typed. /// If necessary, a nullable conversion is applied. /// /// Whether the conversion from the constant zero is allowed. /// /// True if the conversion is successful; false otherwise. /// If the conversion is not successful, the ref parameters will not be modified. bool TryConvertEnum(ref ResolveResult rr, IType targetType, ref bool isNullable, ref ResolveResult enumRR, bool allowConversionFromConstantZero = true) { Conversion c; if (!isNullable) { // Try non-nullable c = conversions.ImplicitConversion(rr, targetType); if (c.IsValid && (allowConversionFromConstantZero || !c.IsEnumerationConversion)) { rr = Convert(rr, targetType, c); return true; } } // make targetType nullable if it isn't already: if (!targetType.IsKnownType(KnownTypeCode.NullableOfT)) targetType = NullableType.Create(compilation, targetType); c = conversions.ImplicitConversion(rr, targetType); if (c.IsValid && (allowConversionFromConstantZero || !c.IsEnumerationConversion)) { rr = Convert(rr, targetType, c); isNullable = true; // Also convert the enum-typed RR to nullable, if it isn't already if (!enumRR.Type.IsKnownType(KnownTypeCode.NullableOfT)) { var nullableType = NullableType.Create(compilation, enumRR.Type); enumRR = new ConversionResolveResult(nullableType, enumRR, Conversion.ImplicitNullableConversion); } return true; } return false; } ResolveResult Convert(ResolveResult rr, IType targetType) { return Convert(rr, targetType, conversions.ImplicitConversion(rr, targetType)); } ResolveResult Convert(ResolveResult rr, IType targetType, Conversion c) { if (c == Conversion.IdentityConversion) return rr; else if (rr.IsCompileTimeConstant && c != Conversion.None && !c.IsUserDefined) return ResolveCast(targetType, rr); else return new ConversionResolveResult(targetType, rr, c, checkForOverflow); } public ResolveResult ResolveCast(IType targetType, ResolveResult expression) { // C# 4.0 spec: §7.7.6 Cast expressions Conversion c = conversions.ExplicitConversion(expression, targetType); if (expression.IsCompileTimeConstant && !c.IsUserDefined) { TypeCode code = ReflectionHelper.GetTypeCode(targetType); if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null) { try { return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); } catch (OverflowException) { return new ErrorResolveResult(targetType); } catch (InvalidCastException) { return new ErrorResolveResult(targetType); } } else if (code == TypeCode.String) { if (expression.ConstantValue == null || expression.ConstantValue is string) return new ConstantResolveResult(targetType, expression.ConstantValue); else return new ErrorResolveResult(targetType); } else if (targetType.Kind == TypeKind.Enum) { code = ReflectionHelper.GetTypeCode(GetEnumUnderlyingType(targetType)); if (code >= TypeCode.SByte && code <= TypeCode.UInt64 && expression.ConstantValue != null) { try { return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); } catch (OverflowException) { return new ErrorResolveResult(targetType); } catch (InvalidCastException) { return new ErrorResolveResult(targetType); } } } } return new ConversionResolveResult(targetType, expression, c, checkForOverflow); } internal object CSharpPrimitiveCast(TypeCode targetType, object input) { return Utils.CSharpPrimitiveCast.Cast(targetType, input, this.CheckForOverflow); } #endregion #region ResolveSimpleName public ResolveResult ResolveSimpleName(string identifier, IList typeArguments, bool isInvocationTarget = false) { // C# 4.0 spec: §7.6.2 Simple Names return LookupSimpleNameOrTypeName( identifier, typeArguments, isInvocationTarget ? NameLookupMode.InvocationTarget : NameLookupMode.Expression); } public ResolveResult LookupSimpleNameOrTypeName(string identifier, IList typeArguments, NameLookupMode lookupMode) { // C# 4.0 spec: §3.8 Namespace and type names; §7.6.2 Simple Names if (identifier == null) throw new ArgumentNullException("identifier"); if (typeArguments == null) throw new ArgumentNullException("typeArguments"); int k = typeArguments.Count; if (k == 0) { if (lookupMode == NameLookupMode.Expression || lookupMode == NameLookupMode.InvocationTarget) { // Look in local variables foreach (IVariable v in this.LocalVariables) { if (v.Name == identifier) { return new LocalResolveResult(v); } } // Look in parameters of current method IParameterizedMember parameterizedMember = this.CurrentMember as IParameterizedMember; if (parameterizedMember != null) { foreach (IParameter p in parameterizedMember.Parameters) { if (p.Name == identifier) { return new LocalResolveResult(p); } } } } // look in type parameters of current method IMethod m = this.CurrentMember as IMethod; if (m != null) { foreach (ITypeParameter tp in m.TypeParameters) { if (tp.Name == identifier) return new TypeResolveResult(tp); } } } bool parameterizeResultType = !(typeArguments.Count != 0 && typeArguments.All(t => t.Kind == TypeKind.UnboundTypeArgument)); ResolveResult r = null; if (currentTypeDefinitionCache != null) { Dictionary cache = null; bool foundInCache = false; if (k == 0) { switch (lookupMode) { case NameLookupMode.Expression: cache = currentTypeDefinitionCache.SimpleNameLookupCacheExpression; break; case NameLookupMode.InvocationTarget: cache = currentTypeDefinitionCache.SimpleNameLookupCacheInvocationTarget; break; case NameLookupMode.Type: cache = currentTypeDefinitionCache.SimpleTypeLookupCache; break; } if (cache != null) { lock (cache) foundInCache = cache.TryGetValue(identifier, out r); } } if (foundInCache) { r = (r != null ? r.ShallowClone() : null); } else { r = LookInCurrentType(identifier, typeArguments, lookupMode, parameterizeResultType); if (cache != null) { // also cache missing members (r==null) lock (cache) cache[identifier] = r; } } if (r != null) return r; } if (context.CurrentUsingScope == null) { // If no using scope was specified, we still need to look in the global namespace: r = LookInUsingScopeNamespace(null, compilation.RootNamespace, identifier, typeArguments, parameterizeResultType); } else { if (k == 0 && lookupMode != NameLookupMode.TypeInUsingDeclaration) { if (context.CurrentUsingScope.ResolveCache.TryGetValue(identifier, out r)) { r = (r != null ? r.ShallowClone() : null); } else { r = LookInCurrentUsingScope(identifier, typeArguments, false, false); context.CurrentUsingScope.ResolveCache.TryAdd(identifier, r); } } else { r = LookInCurrentUsingScope(identifier, typeArguments, lookupMode == NameLookupMode.TypeInUsingDeclaration, parameterizeResultType); } } if (r != null) return r; if (typeArguments.Count == 0 && identifier == "dynamic") { return new TypeResolveResult(SpecialType.Dynamic); } else { return new UnknownIdentifierResolveResult(identifier, typeArguments.Count); } } public bool IsVariableReferenceWithSameType (ResolveResult rr, string identifier, out TypeResolveResult trr) { if (!(rr is MemberResolveResult || rr is LocalResolveResult)) { trr = null; return false; } trr = LookupSimpleNameOrTypeName (identifier, EmptyList.Instance, NameLookupMode.Type) as TypeResolveResult; return trr != null && trr.Type.Equals (rr.Type); } ResolveResult LookInCurrentType(string identifier, IList typeArguments, NameLookupMode lookupMode, bool parameterizeResultType) { int k = typeArguments.Count; MemberLookup lookup = CreateMemberLookup(lookupMode); // look in current type definitions for (ITypeDefinition t = this.CurrentTypeDefinition; t != null; t = t.DeclaringTypeDefinition) { if (k == 0) { // Look for type parameter with that name var typeParameters = t.TypeParameters; // Look at all type parameters, including those copied from outer classes, // so that we can fetch the version with the correct owner. for (int i = 0; i < typeParameters.Count; i++) { if (typeParameters[i].Name == identifier) return new TypeResolveResult(typeParameters[i]); } } if (lookupMode == NameLookupMode.BaseTypeReference && t == this.CurrentTypeDefinition) { // don't look in current type when resolving a base type reference continue; } ResolveResult r; if (lookupMode == NameLookupMode.Expression || lookupMode == NameLookupMode.InvocationTarget) { var targetResolveResult = (t == this.CurrentTypeDefinition ? ResolveThisReference() : new TypeResolveResult(t)); r = lookup.Lookup(targetResolveResult, identifier, typeArguments, lookupMode == NameLookupMode.InvocationTarget); } else { r = lookup.LookupType(t, identifier, typeArguments, parameterizeResultType); } if (!(r is UnknownMemberResolveResult)) // but do return AmbiguousMemberResolveResult return r; } return null; } ResolveResult LookInCurrentUsingScope(string identifier, IList typeArguments, bool isInUsingDeclaration, bool parameterizeResultType) { // look in current namespace definitions ResolvedUsingScope currentUsingScope = this.CurrentUsingScope; for (ResolvedUsingScope u = currentUsingScope; u != null; u = u.Parent) { var resultInNamespace = LookInUsingScopeNamespace(u, u.Namespace, identifier, typeArguments, parameterizeResultType); if (resultInNamespace != null) return resultInNamespace; // then look for aliases: if (typeArguments.Count == 0) { if (u.ExternAliases.Contains(identifier)) { return ResolveExternAlias(identifier); } if (!(isInUsingDeclaration && u == currentUsingScope)) { foreach (var pair in u.UsingAliases) { if (pair.Key == identifier) { return pair.Value.ShallowClone(); } } } } // finally, look in the imported namespaces: if (!(isInUsingDeclaration && u == currentUsingScope)) { IType firstResult = null; foreach (var importedNamespace in u.Usings) { ITypeDefinition def = importedNamespace.GetTypeDefinition(identifier, typeArguments.Count); if (def != null) { IType resultType; if (parameterizeResultType && typeArguments.Count > 0) resultType = new ParameterizedType(def, typeArguments); else resultType = def; if (firstResult == null || !TopLevelTypeDefinitionIsAccessible(firstResult.GetDefinition())) { if (TopLevelTypeDefinitionIsAccessible(resultType.GetDefinition())) firstResult = resultType; } else if (TopLevelTypeDefinitionIsAccessible(def)) { return new AmbiguousTypeResolveResult(firstResult); } } } if (firstResult != null) return new TypeResolveResult(firstResult); } // if we didn't find anything: repeat lookup with parent namespace } return null; } ResolveResult LookInUsingScopeNamespace(ResolvedUsingScope usingScope, INamespace n, string identifier, IList typeArguments, bool parameterizeResultType) { if (n == null) return null; // first look for a namespace int k = typeArguments.Count; if (k == 0) { INamespace childNamespace = n.GetChildNamespace(identifier); if (childNamespace != null) { if (usingScope != null && usingScope.HasAlias(identifier)) return new AmbiguousTypeResolveResult(new UnknownType(null, identifier)); return new NamespaceResolveResult(childNamespace); } } // then look for a type ITypeDefinition def = n.GetTypeDefinition(identifier, k); if (def != null) { IType result = def; if (parameterizeResultType && k > 0) { result = new ParameterizedType(def, typeArguments); } if (usingScope != null && usingScope.HasAlias(identifier)) return new AmbiguousTypeResolveResult(result); else return new TypeResolveResult(result); } return null; } bool TopLevelTypeDefinitionIsAccessible(ITypeDefinition typeDef) { if (typeDef.IsInternal) { return typeDef.ParentAssembly.InternalsVisibleTo(compilation.MainAssembly); } return true; } /// /// Looks up an alias (identifier in front of :: operator) /// public ResolveResult ResolveAlias(string identifier) { if (identifier == "global") return new NamespaceResolveResult(compilation.RootNamespace); for (ResolvedUsingScope n = this.CurrentUsingScope; n != null; n = n.Parent) { if (n.ExternAliases.Contains(identifier)) { return ResolveExternAlias(identifier); } foreach (var pair in n.UsingAliases) { if (pair.Key == identifier) { return (pair.Value as NamespaceResolveResult) ?? ErrorResult; } } } return ErrorResult; } ResolveResult ResolveExternAlias(string alias) { INamespace ns = compilation.GetNamespaceForExternAlias(alias); if (ns != null) return new NamespaceResolveResult(ns); else return ErrorResult; } #endregion #region ResolveMemberAccess public ResolveResult ResolveMemberAccess(ResolveResult target, string identifier, IList typeArguments, NameLookupMode lookupMode = NameLookupMode.Expression) { // C# 4.0 spec: §7.6.4 bool parameterizeResultType = !(typeArguments.Count != 0 && typeArguments.All(t => t.Kind == TypeKind.UnboundTypeArgument)); NamespaceResolveResult nrr = target as NamespaceResolveResult; if (nrr != null) { return ResolveMemberAccessOnNamespace(nrr, identifier, typeArguments, parameterizeResultType); } if (target.Type.Kind == TypeKind.Dynamic) return new DynamicMemberResolveResult(target, identifier); MemberLookup lookup = CreateMemberLookup(lookupMode); ResolveResult result; switch (lookupMode) { case NameLookupMode.Expression: result = lookup.Lookup(target, identifier, typeArguments, isInvocation: false); break; case NameLookupMode.InvocationTarget: result = lookup.Lookup(target, identifier, typeArguments, isInvocation: true); break; case NameLookupMode.Type: case NameLookupMode.TypeInUsingDeclaration: case NameLookupMode.BaseTypeReference: // Don't do the UnknownMemberResolveResult/MethodGroupResolveResult processing, // it's only relevant for expressions. return lookup.LookupType(target.Type, identifier, typeArguments, parameterizeResultType); default: throw new NotSupportedException("Invalid value for NameLookupMode"); } if (result is UnknownMemberResolveResult) { // We intentionally use all extension methods here, not just the eligible ones. // Proper eligibility checking is only possible for the full invocation // (after we know the remaining arguments). // The eligibility check in GetExtensionMethods is only intended for code completion. var extensionMethods = GetExtensionMethods(identifier, typeArguments); if (extensionMethods.Count > 0) { return new MethodGroupResolveResult(target, identifier, EmptyList.Instance, typeArguments) { extensionMethods = extensionMethods }; } } else { MethodGroupResolveResult mgrr = result as MethodGroupResolveResult; if (mgrr != null) { Debug.Assert(mgrr.extensionMethods == null); // set the values that are necessary to make MethodGroupResolveResult.GetExtensionMethods() work mgrr.resolver = this; } } return result; } [Obsolete("Use ResolveMemberAccess() with NameLookupMode.Type instead")] public ResolveResult ResolveMemberType(ResolveResult target, string identifier, IList typeArguments) { return ResolveMemberAccess(target, identifier, typeArguments, NameLookupMode.Type); } ResolveResult ResolveMemberAccessOnNamespace(NamespaceResolveResult nrr, string identifier, IList typeArguments, bool parameterizeResultType) { if (typeArguments.Count == 0) { INamespace childNamespace = nrr.Namespace.GetChildNamespace(identifier); if (childNamespace != null) return new NamespaceResolveResult(childNamespace); } ITypeDefinition def = nrr.Namespace.GetTypeDefinition(identifier, typeArguments.Count); if (def != null) { if (parameterizeResultType && typeArguments.Count > 0) return new TypeResolveResult(new ParameterizedType(def, typeArguments)); else return new TypeResolveResult(def); } return ErrorResult; } /// /// Creates a MemberLookup instance using this resolver's settings. /// public MemberLookup CreateMemberLookup() { ITypeDefinition currentTypeDefinition = this.CurrentTypeDefinition; bool isInEnumMemberInitializer = this.CurrentMember != null && this.CurrentMember.SymbolKind == SymbolKind.Field && currentTypeDefinition != null && currentTypeDefinition.Kind == TypeKind.Enum; return new MemberLookup(currentTypeDefinition, this.Compilation.MainAssembly, isInEnumMemberInitializer); } /// /// Creates a MemberLookup instance using this resolver's settings. /// public MemberLookup CreateMemberLookup(NameLookupMode lookupMode) { if (lookupMode == NameLookupMode.BaseTypeReference && this.CurrentTypeDefinition != null) { // When looking up a base type reference, treat us as being outside the current type definition // for accessibility purposes. // This avoids a stack overflow when referencing a protected class nested inside the base class // of a parent class. (NameLookupTests.InnerClassInheritingFromProtectedBaseInnerClassShouldNotCauseStackOverflow) return new MemberLookup(this.CurrentTypeDefinition.DeclaringTypeDefinition, this.Compilation.MainAssembly, false); } else { return CreateMemberLookup(); } } #endregion #region ResolveIdentifierInObjectInitializer public ResolveResult ResolveIdentifierInObjectInitializer(string identifier) { MemberLookup memberLookup = CreateMemberLookup(); return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList.Instance, false); } #endregion #region GetExtensionMethods /// /// Gets all extension methods that are available in the current context. /// /// Name of the extension method. Pass null to retrieve all extension methods. /// Explicitly provided type arguments. /// An empty list will return all matching extension method definitions; /// a non-empty list will return s for all extension methods /// with the matching number of type parameters. /// /// The results are stored in nested lists because they are grouped by using scope. /// That is, for "using SomeExtensions; namespace X { using MoreExtensions; ... }", /// the return value will be /// new List { /// new List { all extensions from MoreExtensions }, /// new List { all extensions from SomeExtensions } /// } /// public List> GetExtensionMethods(string name = null, IList typeArguments = null) { return GetExtensionMethods(null, name, typeArguments); } /// /// Gets the extension methods that are called 'name' /// and are applicable with a first argument type of 'targetType'. /// /// Type of the 'this' argument /// Name of the extension method. Pass null to retrieve all extension methods. /// Explicitly provided type arguments. /// An empty list will return all matching extension method definitions; /// a non-empty list will return s for all extension methods /// with the matching number of type parameters. /// /// Specifies whether to produce a /// when type arguments could be inferred from . This parameter /// is only used for inferred types and has no effect if is non-empty. /// /// /// The results are stored in nested lists because they are grouped by using scope. /// That is, for "using SomeExtensions; namespace X { using MoreExtensions; ... }", /// the return value will be /// new List { /// new List { all extensions from MoreExtensions }, /// new List { all extensions from SomeExtensions } /// } /// public List> GetExtensionMethods(IType targetType, string name = null, IList typeArguments = null, bool substituteInferredTypes = false) { var lookup = CreateMemberLookup(); List> extensionMethodGroups = new List>(); foreach (var inputGroup in GetAllExtensionMethods(lookup)) { List outputGroup = new List(); foreach (var method in inputGroup) { if (name != null && method.Name != name) continue; if (!lookup.IsAccessible(method, false)) continue; IType[] inferredTypes; if (typeArguments != null && typeArguments.Count > 0) { if (method.TypeParameters.Count != typeArguments.Count) continue; var sm = method.Specialize(new TypeParameterSubstitution(null, typeArguments)); if (IsEligibleExtensionMethod(compilation, conversions, targetType, sm, false, out inferredTypes)) outputGroup.Add(sm); } else { if (IsEligibleExtensionMethod(compilation, conversions, targetType, method, true, out inferredTypes)) { if (substituteInferredTypes && inferredTypes != null) { outputGroup.Add(method.Specialize(new TypeParameterSubstitution(null, inferredTypes))); } else { outputGroup.Add(method); } } } } if (outputGroup.Count > 0) extensionMethodGroups.Add(outputGroup); } return extensionMethodGroups; } /// /// Checks whether the specified extension method is eligible on the target type. /// /// Target type that is passed as first argument to the extension method. /// The extension method. /// Whether to perform type inference for the method. /// Use false if is already parameterized (e.g. when type arguments were given explicitly). /// Otherwise, use true. /// /// If the method is generic and is true, /// and at least some of the type arguments could be inferred, this parameter receives the inferred type arguments. /// Since only the type for the first parameter is considered, not all type arguments may be inferred. /// If an array is returned, any slot with an uninferred type argument will be set to the method's /// corresponding type parameter. /// public static bool IsEligibleExtensionMethod(IType targetType, IMethod method, bool useTypeInference, out IType[] outInferredTypes) { if (targetType == null) throw new ArgumentNullException("targetType"); if (method == null) throw new ArgumentNullException("method"); var compilation = method.Compilation; return IsEligibleExtensionMethod(compilation, CSharpConversions.Get(compilation), targetType, method, useTypeInference, out outInferredTypes); } static bool IsEligibleExtensionMethod(ICompilation compilation, CSharpConversions conversions, IType targetType, IMethod method, bool useTypeInference, out IType[] outInferredTypes) { outInferredTypes = null; if (targetType == null) return true; if (method.Parameters.Count == 0) return false; IType thisParameterType = method.Parameters[0].Type; if (useTypeInference && method.TypeParameters.Count > 0) { // We need to infer type arguments from targetType: TypeInference ti = new TypeInference(compilation, conversions); ResolveResult[] arguments = { new ResolveResult(targetType) }; IType[] parameterTypes = { method.Parameters[0].Type }; bool success; var inferredTypes = ti.InferTypeArguments(method.TypeParameters, arguments, parameterTypes, out success); var substitution = new TypeParameterSubstitution(null, inferredTypes); // Validate that the types that could be inferred (aren't unknown) satisfy the constraints: bool hasInferredTypes = false; for (int i = 0; i < inferredTypes.Length; i++) { if (inferredTypes[i].Kind != TypeKind.Unknown && inferredTypes[i].Kind != TypeKind.UnboundTypeArgument) { hasInferredTypes = true; if (!OverloadResolution.ValidateConstraints(method.TypeParameters[i], inferredTypes[i], substitution, conversions)) return false; } else { inferredTypes[i] = method.TypeParameters[i]; // do not substitute types that could not be inferred } } if (hasInferredTypes) outInferredTypes = inferredTypes; thisParameterType = thisParameterType.AcceptVisitor(substitution); } Conversion c = conversions.ImplicitConversion(targetType, thisParameterType); return c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion); } /// /// Gets all extension methods available in the current using scope. /// This list includes inaccessible methods. /// IList> GetAllExtensionMethods(MemberLookup lookup) { var currentUsingScope = context.CurrentUsingScope; if (currentUsingScope == null) return EmptyList>.Instance; List> extensionMethodGroups = LazyInit.VolatileRead(ref currentUsingScope.AllExtensionMethods); if (extensionMethodGroups != null) { return extensionMethodGroups; } extensionMethodGroups = new List>(); List m; for (ResolvedUsingScope scope = currentUsingScope; scope != null; scope = scope.Parent) { INamespace ns = scope.Namespace; if (ns != null) { m = GetExtensionMethods(lookup, ns).ToList(); if (m.Count > 0) extensionMethodGroups.Add(m); } m = scope.Usings .Distinct() .SelectMany(importedNamespace => GetExtensionMethods(lookup, importedNamespace)) .ToList(); if (m.Count > 0) extensionMethodGroups.Add(m); } return LazyInit.GetOrSet(ref currentUsingScope.AllExtensionMethods, extensionMethodGroups); } IEnumerable GetExtensionMethods(MemberLookup lookup, INamespace ns) { // TODO: maybe make this a property on INamespace? return from c in ns.Types where c.IsStatic && c.HasExtensionMethods && c.TypeParameters.Count == 0 && lookup.IsAccessible(c, false) from m in c.Methods where m.IsExtensionMethod select m; } #endregion #region ResolveInvocation IList AddArgumentNamesIfNecessary(ResolveResult[] arguments, string[] argumentNames) { if (argumentNames == null) { return arguments; } else { var result = new ResolveResult[arguments.Length]; for (int i = 0; i < arguments.Length; i++) { result[i] = (argumentNames[i] != null ? new NamedArgumentResolveResult(argumentNames[i], arguments[i]) : arguments[i]); } return result; } } private ResolveResult ResolveInvocation(ResolveResult target, ResolveResult[] arguments, string[] argumentNames, bool allowOptionalParameters) { // C# 4.0 spec: §7.6.5 if (target.Type.Kind == TypeKind.Dynamic) { return new DynamicInvocationResolveResult(target, DynamicInvocationType.Invocation, AddArgumentNamesIfNecessary(arguments, argumentNames)); } bool isDynamic = arguments.Any(a => a.Type.Kind == TypeKind.Dynamic); MethodGroupResolveResult mgrr = target as MethodGroupResolveResult; if (mgrr != null) { if (isDynamic) { // If we have dynamic arguments, we need to represent the invocation as a dynamic invocation if there is more than one applicable method. var or2 = CreateOverloadResolution(arguments, argumentNames, mgrr.TypeArguments.ToArray()); var applicableMethods = mgrr.MethodsGroupedByDeclaringType.SelectMany(m => m, (x, m) => new { x.DeclaringType, Method = m }).Where(x => OverloadResolution.IsApplicable(or2.AddCandidate(x.Method))).ToList(); if (applicableMethods.Count > 1) { ResolveResult actualTarget; if (applicableMethods.All(x => x.Method.IsStatic) && !(mgrr.TargetResult is TypeResolveResult)) actualTarget = new TypeResolveResult(mgrr.TargetType); else actualTarget = mgrr.TargetResult; var l = new List(); foreach (var m in applicableMethods) { if (l.Count == 0 || l[l.Count - 1].DeclaringType != m.DeclaringType) l.Add(new MethodListWithDeclaringType(m.DeclaringType)); l[l.Count - 1].Add(m.Method); } return new DynamicInvocationResolveResult(new MethodGroupResolveResult(actualTarget, mgrr.MethodName, l, mgrr.TypeArguments), DynamicInvocationType.Invocation, AddArgumentNamesIfNecessary(arguments, argumentNames)); } } OverloadResolution or = mgrr.PerformOverloadResolution(compilation, arguments, argumentNames, checkForOverflow: checkForOverflow, conversions: conversions, allowOptionalParameters: allowOptionalParameters); if (or.BestCandidate != null) { if (or.BestCandidate.IsStatic && !or.IsExtensionMethodInvocation && !(mgrr.TargetResult is TypeResolveResult)) return or.CreateResolveResult(new TypeResolveResult(mgrr.TargetType), returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); else return or.CreateResolveResult(mgrr.TargetResult, returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); } else { // No candidate found at all (not even an inapplicable one). // This can happen with empty method groups (as sometimes used with extension methods) return new UnknownMethodResolveResult( mgrr.TargetType, mgrr.MethodName, mgrr.TypeArguments, CreateParameters(arguments, argumentNames)); } } UnknownMemberResolveResult umrr = target as UnknownMemberResolveResult; if (umrr != null) { return new UnknownMethodResolveResult(umrr.TargetType, umrr.MemberName, umrr.TypeArguments, CreateParameters(arguments, argumentNames)); } UnknownIdentifierResolveResult uirr = target as UnknownIdentifierResolveResult; if (uirr != null && CurrentTypeDefinition != null) { return new UnknownMethodResolveResult(CurrentTypeDefinition, uirr.Identifier, EmptyList.Instance, CreateParameters(arguments, argumentNames)); } IMethod invokeMethod = target.Type.GetDelegateInvokeMethod(); if (invokeMethod != null) { OverloadResolution or = CreateOverloadResolution(arguments, argumentNames); or.AddCandidate(invokeMethod); return new CSharpInvocationResolveResult( target, invokeMethod, //invokeMethod.ReturnType.Resolve(context), or.GetArgumentsWithConversionsAndNames(), or.BestCandidateErrors, isExpandedForm: or.BestCandidateIsExpandedForm, isDelegateInvocation: true, argumentToParameterMap: or.GetArgumentToParameterMap(), returnTypeOverride: isDynamic ? SpecialType.Dynamic : null); } return ErrorResult; } /// /// Resolves an invocation. /// /// The target of the invocation. Usually a MethodGroupResolveResult. /// /// Arguments passed to the method. /// The resolver may mutate this array to wrap elements in s! /// /// /// The argument names. Pass the null string for positional arguments. /// /// InvocationResolveResult or UnknownMethodResolveResult public ResolveResult ResolveInvocation(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null) { return ResolveInvocation(target, arguments, argumentNames, allowOptionalParameters: true); } List CreateParameters(ResolveResult[] arguments, string[] argumentNames) { List list = new List(); if (argumentNames == null) { argumentNames = new string[arguments.Length]; } else { if (argumentNames.Length != arguments.Length) throw new ArgumentException(); argumentNames = (string[])argumentNames.Clone(); } for (int i = 0; i < arguments.Length; i++) { // invent argument names where necessary: if (argumentNames[i] == null) { string newArgumentName = GuessParameterName(arguments[i]); if (argumentNames.Contains(newArgumentName)) { // disambiguate argument name (e.g. add a number) int num = 1; string newName; do { newName = newArgumentName + num.ToString(); num++; } while(argumentNames.Contains(newName)); newArgumentName = newName; } argumentNames[i] = newArgumentName; } // create the parameter: ByReferenceResolveResult brrr = arguments[i] as ByReferenceResolveResult; if (brrr != null) { list.Add(new DefaultParameter(arguments[i].Type, argumentNames[i], isRef: brrr.IsRef, isOut: brrr.IsOut)); } else { // argument might be a lambda or delegate type, so we have to try to guess the delegate type IType type = arguments[i].Type; if (type.Kind == TypeKind.Null || type.Kind == TypeKind.Unknown) { list.Add(new DefaultParameter(compilation.FindType(KnownTypeCode.Object), argumentNames[i])); } else { list.Add(new DefaultParameter(type, argumentNames[i])); } } } return list; } static string GuessParameterName(ResolveResult rr) { MemberResolveResult mrr = rr as MemberResolveResult; if (mrr != null) return mrr.Member.Name; UnknownMemberResolveResult umrr = rr as UnknownMemberResolveResult; if (umrr != null) return umrr.MemberName; MethodGroupResolveResult mgrr = rr as MethodGroupResolveResult; if (mgrr != null) return mgrr.MethodName; LocalResolveResult vrr = rr as LocalResolveResult; if (vrr != null) return MakeParameterName(vrr.Variable.Name); if (rr.Type.Kind != TypeKind.Unknown && !string.IsNullOrEmpty(rr.Type.Name)) { return MakeParameterName(rr.Type.Name); } else { return "parameter"; } } static string MakeParameterName(string variableName) { if (string.IsNullOrEmpty(variableName)) return "parameter"; if (variableName.Length > 1 && variableName[0] == '_') variableName = variableName.Substring(1); return char.ToLower(variableName[0]) + variableName.Substring(1); } OverloadResolution CreateOverloadResolution(ResolveResult[] arguments, string[] argumentNames = null, IType[] typeArguments = null) { var or = new OverloadResolution(compilation, arguments, argumentNames, typeArguments, conversions); or.CheckForOverflow = checkForOverflow; return or; } #endregion #region ResolveIndexer /// /// Resolves an indexer access. /// /// Target expression. /// /// Arguments passed to the indexer. /// The resolver may mutate this array to wrap elements in s! /// /// /// The argument names. Pass the null string for positional arguments. /// /// ArrayAccessResolveResult, InvocationResolveResult, or ErrorResolveResult public ResolveResult ResolveIndexer(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null) { switch (target.Type.Kind) { case TypeKind.Dynamic: return new DynamicInvocationResolveResult(target, DynamicInvocationType.Indexing, AddArgumentNamesIfNecessary(arguments, argumentNames)); case TypeKind.Array: case TypeKind.Pointer: // §7.6.6.1 Array access / §18.5.3 Pointer element access AdjustArrayAccessArguments(arguments); return new ArrayAccessResolveResult(((TypeWithElementType)target.Type).ElementType, target, arguments); } // §7.6.6.2 Indexer access MemberLookup lookup = CreateMemberLookup(); var indexers = lookup.LookupIndexers(target); if (arguments.Any(a => a.Type.Kind == TypeKind.Dynamic)) { // If we have dynamic arguments, we need to represent the invocation as a dynamic invocation if there is more than one applicable indexer. var or2 = CreateOverloadResolution(arguments, argumentNames, null); var applicableIndexers = indexers.SelectMany(x => x).Where(m => OverloadResolution.IsApplicable(or2.AddCandidate(m))).ToList(); if (applicableIndexers.Count > 1) { return new DynamicInvocationResolveResult(target, DynamicInvocationType.Indexing, AddArgumentNamesIfNecessary(arguments, argumentNames)); } } OverloadResolution or = CreateOverloadResolution(arguments, argumentNames); or.AddMethodLists(indexers); if (or.BestCandidate != null) { return or.CreateResolveResult(target); } else { return ErrorResult; } } /// /// Converts all arguments to int,uint,long or ulong. /// void AdjustArrayAccessArguments(ResolveResult[] arguments) { for (int i = 0; i < arguments.Length; i++) { if (!(TryConvert(ref arguments[i], compilation.FindType(KnownTypeCode.Int32)) || TryConvert(ref arguments[i], compilation.FindType(KnownTypeCode.UInt32)) || TryConvert(ref arguments[i], compilation.FindType(KnownTypeCode.Int64)) || TryConvert(ref arguments[i], compilation.FindType(KnownTypeCode.UInt64)))) { // conversion failed arguments[i] = Convert(arguments[i], compilation.FindType(KnownTypeCode.Int32), Conversion.None); } } } #endregion #region ResolveObjectCreation /// /// Resolves an object creation. /// /// Type of the object to create. /// /// Arguments passed to the constructor. /// The resolver may mutate this array to wrap elements in s! /// /// /// The argument names. Pass the null string for positional arguments. /// /// /// Whether to allow calling protected constructors. /// This should be false except when resolving constructor initializers. /// /// /// Statements for Objects/Collections initializer. /// /// /// InvocationResolveResult or ErrorResolveResult public ResolveResult ResolveObjectCreation(IType type, ResolveResult[] arguments, string[] argumentNames = null, bool allowProtectedAccess = false, IList initializerStatements = null) { if (type.Kind == TypeKind.Delegate && arguments.Length == 1) { ResolveResult input = arguments[0]; IMethod invoke = input.Type.GetDelegateInvokeMethod(); if (invoke != null) { input = new MethodGroupResolveResult( input, invoke.Name, methods: new[] { new MethodListWithDeclaringType(invoke.DeclaringType) { invoke } }, typeArguments: EmptyList.Instance ); } return Convert(input, type); } OverloadResolution or = CreateOverloadResolution(arguments, argumentNames); MemberLookup lookup = CreateMemberLookup(); var allApplicable = (arguments.Any(a => a.Type.Kind == TypeKind.Dynamic) ? new List() : null); foreach (IMethod ctor in type.GetConstructors()) { if (lookup.IsAccessible(ctor, allowProtectedAccess)) { var orErrors = or.AddCandidate(ctor); if (allApplicable != null && OverloadResolution.IsApplicable(orErrors)) allApplicable.Add(ctor); } else or.AddCandidate(ctor, OverloadResolutionErrors.Inaccessible); } if (allApplicable != null && allApplicable.Count > 1) { // If we have dynamic arguments, we need to represent the invocation as a dynamic invocation if there is more than one applicable constructor. return new DynamicInvocationResolveResult(new MethodGroupResolveResult(null, allApplicable[0].Name, new[] { new MethodListWithDeclaringType(type, allApplicable) }, null), DynamicInvocationType.ObjectCreation, AddArgumentNamesIfNecessary(arguments, argumentNames), initializerStatements); } if (or.BestCandidate != null) { return or.CreateResolveResult(null, initializerStatements); } else { return new ErrorResolveResult(type); } } #endregion #region ResolveSizeOf /// /// Resolves 'sizeof(type)'. /// public ResolveResult ResolveSizeOf(IType type) { IType int32 = compilation.FindType(KnownTypeCode.Int32); int? size = null; var typeForConstant = (type.Kind == TypeKind.Enum) ? type.GetDefinition().EnumUnderlyingType : type; switch (ReflectionHelper.GetTypeCode(typeForConstant)) { case TypeCode.Boolean: case TypeCode.SByte: case TypeCode.Byte: size = 1; break; case TypeCode.Char: case TypeCode.Int16: case TypeCode.UInt16: size = 2; break; case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Single: size = 4; break; case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Double: size = 8; break; } return new SizeOfResolveResult(int32, type, size); } #endregion #region Resolve This/Base Reference /// /// Resolves 'this'. /// public ResolveResult ResolveThisReference() { ITypeDefinition t = CurrentTypeDefinition; if (t != null) { if (t.TypeParameterCount != 0) { // Self-parameterize the type return new ThisResolveResult(new ParameterizedType(t, t.TypeParameters)); } else { return new ThisResolveResult(t); } } return ErrorResult; } /// /// Resolves 'base'. /// public ResolveResult ResolveBaseReference() { ITypeDefinition t = CurrentTypeDefinition; if (t != null) { foreach (IType baseType in t.DirectBaseTypes) { if (baseType.Kind != TypeKind.Unknown && baseType.Kind != TypeKind.Interface) { return new ThisResolveResult(baseType, causesNonVirtualInvocation: true); } } } return ErrorResult; } #endregion #region ResolveConditional /// /// Converts the input to bool using the rules for boolean expressions. /// That is, operator true is used if a regular conversion to bool is not possible. /// public ResolveResult ResolveCondition(ResolveResult input) { if (input == null) throw new ArgumentNullException("input"); IType boolean = compilation.FindType(KnownTypeCode.Boolean); Conversion c = conversions.ImplicitConversion(input, boolean); if (!c.IsValid) { var opTrue = input.Type.GetMethods(m => m.IsOperator && m.Name == "op_True").FirstOrDefault(); if (opTrue != null) { c = Conversion.UserDefinedConversion(opTrue, isImplicit: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); } } return Convert(input, boolean, c); } /// /// Converts the negated input to bool using the rules for boolean expressions. /// Computes !(bool)input if the implicit cast to bool is valid; otherwise /// computes input.operator false(). /// public ResolveResult ResolveConditionFalse(ResolveResult input) { if (input == null) throw new ArgumentNullException("input"); IType boolean = compilation.FindType(KnownTypeCode.Boolean); Conversion c = conversions.ImplicitConversion(input, boolean); if (!c.IsValid) { var opFalse = input.Type.GetMethods(m => m.IsOperator && m.Name == "op_False").FirstOrDefault(); if (opFalse != null) { c = Conversion.UserDefinedConversion(opFalse, isImplicit: true, conversionBeforeUserDefinedOperator: Conversion.None, conversionAfterUserDefinedOperator: Conversion.None); return Convert(input, boolean, c); } } return ResolveUnaryOperator(UnaryOperatorType.Not, Convert(input, boolean, c)); } public ResolveResult ResolveConditional(ResolveResult condition, ResolveResult trueExpression, ResolveResult falseExpression) { // C# 4.0 spec §7.14: Conditional operator bool isValid; IType resultType; if (trueExpression.Type.Kind == TypeKind.Dynamic || falseExpression.Type.Kind == TypeKind.Dynamic) { resultType = SpecialType.Dynamic; isValid = TryConvert(ref trueExpression, resultType) & TryConvert(ref falseExpression, resultType); } else if (HasType(trueExpression) && HasType(falseExpression)) { Conversion t2f = conversions.ImplicitConversion(trueExpression, falseExpression.Type); Conversion f2t = conversions.ImplicitConversion(falseExpression, trueExpression.Type); // The operator is valid: // a) if there's a conversion in one direction but not the other // b) if there are conversions in both directions, and the types are equivalent if (IsBetterConditionalConversion(t2f, f2t)) { resultType = falseExpression.Type; isValid = true; trueExpression = Convert(trueExpression, resultType, t2f); } else if (IsBetterConditionalConversion(f2t, t2f)) { resultType = trueExpression.Type; isValid = true; falseExpression = Convert(falseExpression, resultType, f2t); } else { resultType = trueExpression.Type; isValid = trueExpression.Type.Equals(falseExpression.Type); } } else if (HasType(trueExpression)) { resultType = trueExpression.Type; isValid = TryConvert(ref falseExpression, resultType); } else if (HasType(falseExpression)) { resultType = falseExpression.Type; isValid = TryConvert(ref trueExpression, resultType); } else { return ErrorResult; } condition = ResolveCondition(condition); if (isValid) { if (condition.IsCompileTimeConstant && trueExpression.IsCompileTimeConstant && falseExpression.IsCompileTimeConstant) { bool? val = condition.ConstantValue as bool?; if (val == true) return trueExpression; else if (val == false) return falseExpression; } return new OperatorResolveResult(resultType, System.Linq.Expressions.ExpressionType.Conditional, condition, trueExpression, falseExpression); } else { return new ErrorResolveResult(resultType); } } bool IsBetterConditionalConversion(Conversion c1, Conversion c2) { // Valid is better than ImplicitConstantExpressionConversion is better than invalid if (!c1.IsValid) return false; if (c1 != Conversion.ImplicitConstantExpressionConversion && c2 == Conversion.ImplicitConstantExpressionConversion) return true; return !c2.IsValid; } bool HasType(ResolveResult r) { return r.Type.Kind != TypeKind.Unknown && r.Type.Kind != TypeKind.Null; } #endregion #region ResolvePrimitive public ResolveResult ResolvePrimitive(object value) { if (value == null) { return new ResolveResult(SpecialType.NullType); } else { TypeCode typeCode = Type.GetTypeCode(value.GetType()); IType type = compilation.FindType(typeCode); return new ConstantResolveResult(type, value); } } #endregion #region ResolveDefaultValue public ResolveResult ResolveDefaultValue(IType type) { return new ConstantResolveResult(type, GetDefaultValue(type)); } public static object GetDefaultValue(IType type) { ITypeDefinition typeDef = type.GetDefinition(); if (typeDef == null) return null; if (typeDef.Kind == TypeKind.Enum) { typeDef = typeDef.EnumUnderlyingType.GetDefinition(); if (typeDef == null) return null; } switch (typeDef.KnownTypeCode) { case KnownTypeCode.Boolean: return false; case KnownTypeCode.Char: return '\0'; case KnownTypeCode.SByte: return (sbyte)0; case KnownTypeCode.Byte: return (byte)0; case KnownTypeCode.Int16: return (short)0; case KnownTypeCode.UInt16: return (ushort)0; case KnownTypeCode.Int32: return 0; case KnownTypeCode.UInt32: return 0U; case KnownTypeCode.Int64: return 0L; case KnownTypeCode.UInt64: return 0UL; case KnownTypeCode.Single: return 0f; case KnownTypeCode.Double: return 0.0; case KnownTypeCode.Decimal: return 0m; default: return null; } } #endregion #region ResolveArrayCreation /// /// Resolves an array creation. /// /// /// The array element type. /// Pass null to resolve an implicitly-typed array creation. /// /// /// The size arguments. /// The length of this array will be used as the number of dimensions of the array type. /// Negative values will be treated as errors. /// /// /// The initializer elements. May be null if no array initializer was specified. /// The resolver may mutate this array to wrap elements in s! /// public ArrayCreateResolveResult ResolveArrayCreation(IType elementType, int[] sizeArguments, ResolveResult[] initializerElements = null) { ResolveResult[] sizeArgResults = new ResolveResult[sizeArguments.Length]; for (int i = 0; i < sizeArguments.Length; i++) { if (sizeArguments[i] < 0) sizeArgResults[i] = ErrorResolveResult.UnknownError; else sizeArgResults[i] = new ConstantResolveResult(compilation.FindType(KnownTypeCode.Int32), sizeArguments[i]); } return ResolveArrayCreation(elementType, sizeArgResults, initializerElements); } /// /// Resolves an array creation. /// /// /// The array element type. /// Pass null to resolve an implicitly-typed array creation. /// /// /// The size arguments. /// The length of this array will be used as the number of dimensions of the array type. /// The resolver may mutate this array to wrap elements in s! /// /// /// The initializer elements. May be null if no array initializer was specified. /// The resolver may mutate this array to wrap elements in s! /// public ArrayCreateResolveResult ResolveArrayCreation(IType elementType, ResolveResult[] sizeArguments, ResolveResult[] initializerElements = null) { int dimensions = sizeArguments.Length; if (dimensions == 0) throw new ArgumentException("sizeArguments.Length must not be 0"); if (elementType == null) { TypeInference typeInference = new TypeInference(compilation, conversions); bool success; elementType = typeInference.GetBestCommonType(initializerElements, out success); } IType arrayType = new ArrayType(compilation, elementType, dimensions); AdjustArrayAccessArguments(sizeArguments); if (initializerElements != null) { for (int i = 0; i < initializerElements.Length; i++) { initializerElements[i] = Convert(initializerElements[i], elementType); } } return new ArrayCreateResolveResult(arrayType, sizeArguments, initializerElements); } #endregion public ResolveResult ResolveTypeOf(IType referencedType) { return new TypeOfResolveResult(compilation.FindType(KnownTypeCode.Type), referencedType); } #region ResolveAssignment public ResolveResult ResolveAssignment(AssignmentOperatorType op, ResolveResult lhs, ResolveResult rhs) { var linqOp = AssignmentExpression.GetLinqNodeType(op, this.CheckForOverflow); var bop = AssignmentExpression.GetCorrespondingBinaryOperator(op); if (bop == null) { return new OperatorResolveResult(lhs.Type, linqOp, lhs, this.Convert(rhs, lhs.Type)); } ResolveResult bopResult = ResolveBinaryOperator(bop.Value, lhs, rhs); OperatorResolveResult opResult = bopResult as OperatorResolveResult; if (opResult == null || opResult.Operands.Count != 2) return bopResult; return new OperatorResolveResult(lhs.Type, linqOp, opResult.UserDefinedOperatorMethod, opResult.IsLiftedOperator, new [] { lhs, opResult.Operands[1] }); } #endregion } }