mirror of
https://github.com/AssetRipper/ILSpy.git
synced 2026-11-13 20:05:48 -10:00
2959 lines
108 KiB
C#
2959 lines
108 KiB
C#
// 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.Collections.Immutable;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
|
||
using ICSharpCode.Decompiler.CSharp.Syntax;
|
||
using ICSharpCode.Decompiler.CSharp.TypeSystem;
|
||
using ICSharpCode.Decompiler.Semantics;
|
||
using ICSharpCode.Decompiler.TypeSystem;
|
||
using ICSharpCode.Decompiler.TypeSystem.Implementation;
|
||
using ICSharpCode.Decompiler.Util;
|
||
|
||
namespace ICSharpCode.Decompiler.CSharp.Resolver
|
||
{
|
||
/// <summary>
|
||
/// Contains the main resolver logic.
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// This class is thread-safe.
|
||
/// </remarks>
|
||
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(nameof(compilation));
|
||
this.compilation = compilation;
|
||
this.conversions = CSharpConversions.Get(compilation);
|
||
this.context = new CSharpTypeResolveContext(compilation.MainModule);
|
||
}
|
||
|
||
public CSharpResolver(CSharpTypeResolveContext context)
|
||
{
|
||
if (context == null)
|
||
throw new ArgumentNullException(nameof(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<Dictionary<string, IVariable>> 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
|
||
/// <summary>
|
||
/// Gets the compilation used by the resolver.
|
||
/// </summary>
|
||
public ICompilation Compilation {
|
||
get { return compilation; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current type resolve context.
|
||
/// </summary>
|
||
public CSharpTypeResolveContext CurrentTypeResolveContext {
|
||
get { return context; }
|
||
}
|
||
|
||
IModule ITypeResolveContext.CurrentModule {
|
||
get { return context.CurrentModule; }
|
||
}
|
||
|
||
CSharpResolver WithContext(CSharpTypeResolveContext newContext)
|
||
{
|
||
return new CSharpResolver(compilation, conversions, newContext, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets whether the current context is <c>checked</c>.
|
||
/// </summary>
|
||
public bool CheckForOverflow {
|
||
get { return checkForOverflow; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets whether the current context is <c>checked</c>.
|
||
/// </summary>
|
||
public CSharpResolver WithCheckForOverflow(bool checkForOverflow)
|
||
{
|
||
if (checkForOverflow == this.checkForOverflow)
|
||
return this;
|
||
return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets whether the resolver is currently within a lambda expression or anonymous method.
|
||
/// </summary>
|
||
public bool IsWithinLambdaExpression {
|
||
get { return isWithinLambdaExpression; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets whether the resolver is currently within a lambda expression.
|
||
/// </summary>
|
||
public CSharpResolver WithIsWithinLambdaExpression(bool isWithinLambdaExpression)
|
||
{
|
||
return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, localVariableStack, objectInitializerStack);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current member definition that is used to look up identifiers as parameters
|
||
/// or type parameters.
|
||
/// </summary>
|
||
public IMember CurrentMember {
|
||
get { return context.CurrentMember; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets the current member definition.
|
||
/// </summary>
|
||
/// <remarks>Don't forget to also set CurrentTypeDefinition when setting CurrentMember;
|
||
/// setting one of the properties does not automatically set the other.</remarks>
|
||
public CSharpResolver WithCurrentMember(IMember member)
|
||
{
|
||
return WithContext(context.WithCurrentMember(member));
|
||
}
|
||
|
||
ITypeResolveContext ITypeResolveContext.WithCurrentMember(IMember member)
|
||
{
|
||
return WithCurrentMember(member);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current using scope that is used to look up identifiers as class names.
|
||
/// </summary>
|
||
public ResolvedUsingScope CurrentUsingScope {
|
||
get { return context.CurrentUsingScope; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets the current using scope that is used to look up identifiers as class names.
|
||
/// </summary>
|
||
public CSharpResolver WithCurrentUsingScope(ResolvedUsingScope usingScope)
|
||
{
|
||
return WithContext(context.WithUsingScope(usingScope));
|
||
}
|
||
#endregion
|
||
|
||
#region Per-CurrentTypeDefinition Cache
|
||
readonly TypeDefinitionCache currentTypeDefinitionCache;
|
||
|
||
/// <summary>
|
||
/// Gets the current type definition.
|
||
/// </summary>
|
||
public ITypeDefinition CurrentTypeDefinition {
|
||
get { return context.CurrentTypeDefinition; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sets the current type definition.
|
||
/// </summary>
|
||
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<string, ResolveResult> SimpleNameLookupCacheExpression = new Dictionary<string, ResolveResult>();
|
||
public readonly Dictionary<string, ResolveResult> SimpleNameLookupCacheInvocationTarget = new Dictionary<string, ResolveResult>();
|
||
public readonly Dictionary<string, ResolveResult> SimpleTypeLookupCache = new Dictionary<string, ResolveResult>();
|
||
|
||
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<Dictionary<string, IVariable>> localVariableStack = ImmutableStack<Dictionary<string, IVariable>>.Empty;
|
||
|
||
CSharpResolver WithLocalVariableStack(ImmutableStack<Dictionary<string, IVariable>> stack)
|
||
{
|
||
return new CSharpResolver(compilation, conversions, context, checkForOverflow, isWithinLambdaExpression, currentTypeDefinitionCache, stack, objectInitializerStack);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds new variableŝ or lambda parameters to the current block.
|
||
/// </summary>
|
||
public CSharpResolver AddVariables(Dictionary<string, IVariable> variables)
|
||
{
|
||
if (variables == null)
|
||
throw new ArgumentNullException(nameof(variables));
|
||
return WithLocalVariableStack(localVariableStack.Push(variables));
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all currently visible local variables and lambda parameters.
|
||
/// Does not include method parameters.
|
||
/// </summary>
|
||
public IEnumerable<IVariable> LocalVariables {
|
||
get {
|
||
return localVariableStack.SelectMany(s => s.Values);
|
||
}
|
||
}
|
||
#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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Pushes the type of the object that is currently being initialized.
|
||
/// </summary>
|
||
public CSharpResolver PushObjectInitializer(ResolveResult initializedObject)
|
||
{
|
||
if (initializedObject == null)
|
||
throw new ArgumentNullException(nameof(initializedObject));
|
||
return WithObjectInitializerStack(new ObjectInitializerContext(initializedObject, objectInitializerStack));
|
||
}
|
||
|
||
public CSharpResolver PopObjectInitializer()
|
||
{
|
||
if (objectInitializerStack == null)
|
||
throw new InvalidOperationException();
|
||
return WithObjectInitializerStack(objectInitializerStack.prev);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets whether this context is within an object initializer.
|
||
/// </summary>
|
||
public bool IsInObjectInitializer {
|
||
get { return objectInitializerStack != null; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the current object initializer. This usually is an <see cref="InitializedObjectResolveResult"/>
|
||
/// or (for nested initializers) a semantic tree based on an <see cref="InitializedObjectResolveResult"/>.
|
||
/// Returns ErrorResolveResult if there is no object initializer.
|
||
/// </summary>
|
||
public ResolveResult CurrentObjectInitializer {
|
||
get {
|
||
return objectInitializerStack != null ? objectInitializerStack.initializedObject : ErrorResult;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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).
|
||
/// </summary>
|
||
public IType CurrentObjectInitializerType {
|
||
get { return CurrentObjectInitializer.Type; }
|
||
}
|
||
#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<ResolveResult>.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<IType>.Instance, NameLookupMode.InvocationTarget);
|
||
ResolveResult getAwaiterInvocation = ResolveInvocation(getAwaiterMethodGroup, Empty<ResolveResult>.Array, argumentNames: null, allowOptionalParameters: false);
|
||
|
||
var lookup = CreateMemberLookup();
|
||
IMethod getResultMethod;
|
||
IType awaitResultType;
|
||
var getResultMethodGroup = lookup.Lookup(getAwaiterInvocation, "GetResult", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;
|
||
if (getResultMethodGroup != null)
|
||
{
|
||
var getResultOR = getResultMethodGroup.PerformOverloadResolution(compilation, Empty<ResolveResult>.Array, 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<IType>.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);
|
||
*/
|
||
// Not adjusted to TS changes for interface impls
|
||
// But I believe this is dead code for ILSpy anyways...
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
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 || type.IsCSharpNativeIntegerType())
|
||
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
|
||
else
|
||
return new ErrorResolveResult(expression.Type);
|
||
case UnaryOperatorType.Plus:
|
||
if (type.IsCSharpNativeIntegerType())
|
||
{
|
||
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
|
||
}
|
||
methodGroup = operators.UnaryPlusOperators;
|
||
break;
|
||
case UnaryOperatorType.Minus:
|
||
if (type.IsCSharpNativeIntegerType())
|
||
{
|
||
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
|
||
}
|
||
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 if (type.IsCSharpNativeIntegerType())
|
||
{
|
||
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 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<IParameterizedMember> userOperatorCandidates = new HashSet<IParameterizedMember>();
|
||
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 || op == BinaryOperatorType.UnsignedShiftRight)
|
||
{
|
||
// 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<CSharpOperators.OperatorMethod> 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.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))
|
||
{
|
||
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.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.UnsignedShiftRight:
|
||
methodGroup = operators.UnsignedShiftRightOperators;
|
||
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);
|
||
}
|
||
else if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType())
|
||
{
|
||
if (lhsType.Equals(rhsType))
|
||
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs, isLifted: isNullable);
|
||
else
|
||
return new ErrorResolveResult(compilation.FindType(KnownTypeCode.Boolean));
|
||
}
|
||
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality)
|
||
{
|
||
if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true
|
||
&& (conversions.IdentityConversion(lhsType, rhsType)
|
||
|| conversions.ExplicitConversion(lhsType, rhsType).IsReferenceConversion
|
||
|| conversions.ExplicitConversion(rhsType, lhsType).IsReferenceConversion))
|
||
{
|
||
// 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();
|
||
}
|
||
if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType())
|
||
{
|
||
if (lhsType.Equals(rhsType))
|
||
{
|
||
return BinaryOperatorResolveResult(
|
||
isNullable ? NullableType.Create(compilation, lhsType) : lhsType,
|
||
lhs, op, rhs, isLifted: isNullable);
|
||
}
|
||
// mixing nint/nuint is not allowed
|
||
return new ErrorResolveResult(lhsType);
|
||
}
|
||
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 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle the case where an enum value is compared with another enum value
|
||
/// bool operator op(E x, E y);
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Handle the case where an enum value is subtracted from another enum value
|
||
/// U operator –(E x, E y);
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 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);
|
||
/// </summary>
|
||
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
|
||
var lhsUType = NullableType.GetUnderlyingType(lhs.Type);
|
||
var rhsUType = NullableType.GetUnderlyingType(rhs.Type);
|
||
TypeCode lhsCode = ReflectionHelper.GetTypeCode(lhsUType);
|
||
TypeCode rhsCode = ReflectionHelper.GetTypeCode(rhsUType);
|
||
// Treat C# 9 native integers as falling between int and long.
|
||
// However they don't have a TypeCode, so we hack around that here:
|
||
if (lhsUType.Kind == TypeKind.NInt)
|
||
{
|
||
lhsCode = TypeCode.Int32;
|
||
}
|
||
else if (lhsUType.Kind == TypeKind.NUInt)
|
||
{
|
||
lhsCode = TypeCode.UInt32;
|
||
}
|
||
if (rhsUType.Kind == TypeKind.NInt)
|
||
{
|
||
rhsCode = TypeCode.Int32;
|
||
}
|
||
else if (rhsUType.Kind == TypeKind.NUInt)
|
||
{
|
||
rhsCode = TypeCode.UInt32;
|
||
}
|
||
// 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 (lhsUType.Kind == TypeKind.NUInt || rhsUType.Kind == TypeKind.NUInt)
|
||
{
|
||
bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs);
|
||
lhs = CastTo(SpecialType.NUInt, isNullable, lhs, allowNullableConstants);
|
||
rhs = CastTo(SpecialType.NUInt, isNullable, rhs, allowNullableConstants);
|
||
return !bindingError;
|
||
}
|
||
else if (lhsCode == TypeCode.UInt32 || rhsCode == TypeCode.UInt32)
|
||
{
|
||
targetType = (IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs)) ? TypeCode.Int64 : TypeCode.UInt32;
|
||
}
|
||
else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64)
|
||
{
|
||
targetType = TypeCode.Int64;
|
||
}
|
||
else if (lhsUType.Kind == TypeKind.NInt || rhsUType.Kind == TypeKind.NInt)
|
||
{
|
||
lhs = CastTo(SpecialType.NInt, isNullable, lhs, allowNullableConstants);
|
||
rhs = CastTo(SpecialType.NInt, isNullable, rhs, allowNullableConstants);
|
||
return !bindingError;
|
||
}
|
||
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)
|
||
{
|
||
return CastTo(compilation.FindType(targetType), isNullable, expression, allowNullableConstants);
|
||
}
|
||
|
||
ResolveResult CastTo(IType targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants)
|
||
{
|
||
IType nullableType = MakeNullable(targetType, isNullable);
|
||
if (nullableType.Equals(expression.Type))
|
||
return expression;
|
||
if (allowNullableConstants && expression.IsCompileTimeConstant)
|
||
{
|
||
if (expression.ConstantValue == null)
|
||
return new ConstantResolveResult(nullableType, null);
|
||
ResolveResult rr = ResolveCast(targetType, expression);
|
||
if (rr.IsError)
|
||
return rr;
|
||
if (rr.IsCompileTimeConstant)
|
||
return new ConstantResolveResult(nullableType, rr.ConstantValue);
|
||
}
|
||
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.UnsignedShiftRight:
|
||
return "op_UnsignedRightShift";
|
||
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
|
||
public IEnumerable<IParameterizedMember> GetUserDefinedOperatorCandidates(IType type, string operatorName)
|
||
{
|
||
if (operatorName == null)
|
||
return EmptyList<IMethod>.Instance;
|
||
TypeCode c = ReflectionHelper.GetTypeCode(type);
|
||
if (TypeCode.Boolean <= c && c <= TypeCode.Decimal)
|
||
{
|
||
// 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<IMethod>.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<IMethod> operators)
|
||
{
|
||
int nonLiftedMethodCount = operators.Count;
|
||
// Construct lifted operators
|
||
for (int i = 0; i < nonLiftedMethodCount; i++)
|
||
{
|
||
var liftedMethod = CSharpOperators.LiftUserDefinedOperator(operators[i]);
|
||
if (liftedMethod != null)
|
||
operators.Add(liftedMethod);
|
||
}
|
||
}
|
||
|
||
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 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
/// <param name="rr">The input resolve result that should be converted.
|
||
/// If a conversion exists, it is applied to the resolve result</param>
|
||
/// <param name="targetType">The target type that we should convert to</param>
|
||
/// <param name="isNullable">Whether we are dealing with a lifted operator</param>
|
||
/// <param name="enumRR">The resolve result that is enum-typed.
|
||
/// If necessary, a nullable conversion is applied.</param>
|
||
/// <param name="allowConversionFromConstantZero">
|
||
/// Whether the conversion from the constant zero is allowed.
|
||
/// </param>
|
||
/// <returns>True if the conversion is successful; false otherwise.
|
||
/// If the conversion is not successful, the ref parameters will not be modified.</returns>
|
||
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)
|
||
{
|
||
IType underlyingType = targetType.GetEnumUnderlyingType();
|
||
TypeCode code = ReflectionHelper.GetTypeCode(underlyingType);
|
||
if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null)
|
||
{
|
||
if (expression.ConstantValue is string)
|
||
{
|
||
return new ErrorResolveResult(targetType);
|
||
}
|
||
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 ((underlyingType.Kind == TypeKind.NInt || underlyingType.Kind == TypeKind.NUInt) && expression.ConstantValue != null)
|
||
{
|
||
if (expression.ConstantValue is string)
|
||
{
|
||
return new ErrorResolveResult(targetType);
|
||
}
|
||
code = (underlyingType.Kind == TypeKind.NInt ? TypeCode.Int32 : TypeCode.UInt32);
|
||
try
|
||
{
|
||
return new ConstantResolveResult(targetType, Util.CSharpPrimitiveCast.Cast(code, expression.ConstantValue, checkForOverflow: true));
|
||
}
|
||
catch (OverflowException)
|
||
{
|
||
// If constant value doesn't fit into 32-bits, the conversion is not a compile-time constant
|
||
return new ConversionResolveResult(targetType, expression, c, checkForOverflow);
|
||
}
|
||
catch (InvalidCastException)
|
||
{
|
||
return new ErrorResolveResult(targetType);
|
||
}
|
||
}
|
||
}
|
||
return new ConversionResolveResult(targetType, expression, c, checkForOverflow);
|
||
}
|
||
|
||
internal object CSharpPrimitiveCast(TypeCode targetType, object input)
|
||
{
|
||
return Util.CSharpPrimitiveCast.Cast(targetType, input, this.CheckForOverflow);
|
||
}
|
||
#endregion
|
||
|
||
#region ResolveSimpleName
|
||
public ResolveResult ResolveSimpleName(string identifier, IReadOnlyList<IType> 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, IReadOnlyList<IType> typeArguments, NameLookupMode lookupMode)
|
||
{
|
||
// C# 4.0 spec: §3.8 Namespace and type names; §7.6.2 Simple Names
|
||
|
||
if (identifier == null)
|
||
throw new ArgumentNullException(nameof(identifier));
|
||
if (typeArguments == null)
|
||
throw new ArgumentNullException(nameof(typeArguments));
|
||
|
||
int k = typeArguments.Count;
|
||
|
||
if (k == 0)
|
||
{
|
||
if (lookupMode == NameLookupMode.Expression || lookupMode == NameLookupMode.InvocationTarget)
|
||
{
|
||
// Look in local variables
|
||
foreach (Dictionary<string, IVariable> variables in localVariableStack)
|
||
{
|
||
if (variables.TryGetValue(identifier, out var v))
|
||
{
|
||
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<string, ResolveResult> 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<IType>.Instance, NameLookupMode.Type) as TypeResolveResult;
|
||
return trr != null && trr.Type.Equals(rr.Type);
|
||
}
|
||
|
||
ResolveResult LookInCurrentType(string identifier, IReadOnlyList<IType> 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, IReadOnlyList<IType> 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, IReadOnlyList<IType> 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 && TopLevelTypeDefinitionIsAccessible(def))
|
||
{
|
||
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.Accessibility == Accessibility.Internal)
|
||
{
|
||
return typeDef.ParentModule.InternalsVisibleTo(compilation.MainModule);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Looks up an alias (identifier in front of :: operator)
|
||
/// </summary>
|
||
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, IReadOnlyList<IType> 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<MethodListWithDeclaringType>.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;
|
||
}
|
||
|
||
ResolveResult ResolveMemberAccessOnNamespace(NamespaceResolveResult nrr, string identifier, IReadOnlyList<IType> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a MemberLookup instance using this resolver's settings.
|
||
/// </summary>
|
||
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.MainModule, isInEnumMemberInitializer);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a MemberLookup instance using this resolver's settings.
|
||
/// </summary>
|
||
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.MainModule, false);
|
||
}
|
||
else
|
||
{
|
||
return CreateMemberLookup();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region ResolveIdentifierInObjectInitializer
|
||
public ResolveResult ResolveIdentifierInObjectInitializer(string identifier)
|
||
{
|
||
MemberLookup memberLookup = CreateMemberLookup();
|
||
return memberLookup.Lookup(this.CurrentObjectInitializer, identifier, EmptyList<IType>.Instance, false);
|
||
}
|
||
#endregion
|
||
|
||
#region ResolveForeach
|
||
public ForEachResolveResult ResolveForeach(ResolveResult expression)
|
||
{
|
||
var memberLookup = CreateMemberLookup();
|
||
|
||
IType collectionType, enumeratorType, elementType;
|
||
ResolveResult getEnumeratorInvocation;
|
||
ResolveResult currentRR = null;
|
||
// C# 4.0 spec: §8.8.4 The foreach statement
|
||
if (expression.Type.Kind == TypeKind.Array || expression.Type.Kind == TypeKind.Dynamic)
|
||
{
|
||
collectionType = compilation.FindType(KnownTypeCode.IEnumerable);
|
||
enumeratorType = compilation.FindType(KnownTypeCode.IEnumerator);
|
||
if (expression.Type.Kind == TypeKind.Array)
|
||
{
|
||
elementType = ((ArrayType)expression.Type).ElementType;
|
||
}
|
||
else
|
||
{
|
||
elementType = SpecialType.Dynamic;
|
||
}
|
||
getEnumeratorInvocation = ResolveCast(collectionType, expression);
|
||
getEnumeratorInvocation = ResolveMemberAccess(getEnumeratorInvocation, "GetEnumerator", EmptyList<IType>.Instance, NameLookupMode.InvocationTarget);
|
||
getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, Empty<ResolveResult>.Array);
|
||
}
|
||
else
|
||
{
|
||
var getEnumeratorMethodGroup = memberLookup.Lookup(expression, "GetEnumerator", EmptyList<IType>.Instance, true) as MethodGroupResolveResult;
|
||
if (getEnumeratorMethodGroup != null)
|
||
{
|
||
var or = getEnumeratorMethodGroup.PerformOverloadResolution(
|
||
compilation, Empty<ResolveResult>.Array,
|
||
allowExtensionMethods: false, allowExpandingParams: false, allowOptionalParameters: false);
|
||
if (or.FoundApplicableCandidate && !or.IsAmbiguous && !or.BestCandidate.IsStatic && or.BestCandidate.Accessibility == Accessibility.Public)
|
||
{
|
||
collectionType = expression.Type;
|
||
getEnumeratorInvocation = or.CreateResolveResult(expression);
|
||
enumeratorType = getEnumeratorInvocation.Type;
|
||
currentRR = memberLookup.Lookup(new ResolveResult(enumeratorType), "Current", EmptyList<IType>.Instance, false);
|
||
elementType = currentRR.Type;
|
||
}
|
||
else
|
||
{
|
||
CheckForEnumerableInterface(expression, out collectionType, out enumeratorType, out elementType, out getEnumeratorInvocation);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CheckForEnumerableInterface(expression, out collectionType, out enumeratorType, out elementType, out getEnumeratorInvocation);
|
||
}
|
||
}
|
||
IMethod moveNextMethod = null;
|
||
var moveNextMethodGroup = memberLookup.Lookup(new ResolveResult(enumeratorType), "MoveNext", EmptyList<IType>.Instance, false) as MethodGroupResolveResult;
|
||
if (moveNextMethodGroup != null)
|
||
{
|
||
var or = moveNextMethodGroup.PerformOverloadResolution(
|
||
compilation, Empty<ResolveResult>.Array,
|
||
allowExtensionMethods: false, allowExpandingParams: false, allowOptionalParameters: false);
|
||
moveNextMethod = or.GetBestCandidateWithSubstitutedTypeArguments() as IMethod;
|
||
}
|
||
|
||
if (currentRR == null)
|
||
currentRR = memberLookup.Lookup(new ResolveResult(enumeratorType), "Current", EmptyList<IType>.Instance, false);
|
||
IProperty currentProperty = null;
|
||
if (currentRR is MemberResolveResult)
|
||
currentProperty = ((MemberResolveResult)currentRR).Member as IProperty;
|
||
|
||
var voidType = compilation.FindType(KnownTypeCode.Void);
|
||
return new ForEachResolveResult(getEnumeratorInvocation, collectionType, enumeratorType, elementType,
|
||
currentProperty, moveNextMethod, voidType);
|
||
}
|
||
|
||
void CheckForEnumerableInterface(ResolveResult expression, out IType collectionType, out IType enumeratorType, out IType elementType, out ResolveResult getEnumeratorInvocation)
|
||
{
|
||
elementType = expression.Type.GetElementTypeFromIEnumerable(compilation, false, out bool? isGeneric);
|
||
if (isGeneric == true)
|
||
{
|
||
ITypeDefinition enumerableOfT = compilation.FindType(KnownTypeCode.IEnumerableOfT).GetDefinition();
|
||
if (enumerableOfT != null)
|
||
collectionType = new ParameterizedType(enumerableOfT, new[] { elementType });
|
||
else
|
||
collectionType = SpecialType.UnknownType;
|
||
|
||
ITypeDefinition enumeratorOfT = compilation.FindType(KnownTypeCode.IEnumeratorOfT).GetDefinition();
|
||
if (enumeratorOfT != null)
|
||
enumeratorType = new ParameterizedType(enumeratorOfT, new[] { elementType });
|
||
else
|
||
enumeratorType = SpecialType.UnknownType;
|
||
}
|
||
else if (isGeneric == false)
|
||
{
|
||
collectionType = compilation.FindType(KnownTypeCode.IEnumerable);
|
||
enumeratorType = compilation.FindType(KnownTypeCode.IEnumerator);
|
||
}
|
||
else
|
||
{
|
||
collectionType = SpecialType.UnknownType;
|
||
enumeratorType = SpecialType.UnknownType;
|
||
}
|
||
getEnumeratorInvocation = ResolveCast(collectionType, expression);
|
||
getEnumeratorInvocation = ResolveMemberAccess(getEnumeratorInvocation, "GetEnumerator", EmptyList<IType>.Instance, NameLookupMode.InvocationTarget);
|
||
getEnumeratorInvocation = ResolveInvocation(getEnumeratorInvocation, Empty<ResolveResult>.Array);
|
||
}
|
||
#endregion
|
||
|
||
#region GetExtensionMethods
|
||
/// <summary>
|
||
/// Gets all extension methods that are available in the current context.
|
||
/// </summary>
|
||
/// <param name="name">Name of the extension method. Pass null to retrieve all extension methods.</param>
|
||
/// <param name="typeArguments">Explicitly provided type arguments.
|
||
/// An empty list will return all matching extension method definitions;
|
||
/// a non-empty list will return <see cref="SpecializedMethod"/>s for all extension methods
|
||
/// with the matching number of type parameters.</param>
|
||
/// <remarks>
|
||
/// 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 }
|
||
/// }
|
||
/// </remarks>
|
||
public List<List<IMethod>> GetExtensionMethods(string name = null, IReadOnlyList<IType> typeArguments = null)
|
||
{
|
||
return GetExtensionMethods(null, name, typeArguments);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets the extension methods that are called 'name'
|
||
/// and are applicable with a first argument type of 'targetType'.
|
||
/// </summary>
|
||
/// <param name="targetType">Type of the 'this' argument</param>
|
||
/// <param name="name">Name of the extension method. Pass null to retrieve all extension methods.</param>
|
||
/// <param name="typeArguments">Explicitly provided type arguments.
|
||
/// An empty list will return all matching extension method definitions;
|
||
/// a non-empty list will return <see cref="SpecializedMethod"/>s for all extension methods
|
||
/// with the matching number of type parameters.</param>
|
||
/// <param name="substituteInferredTypes">
|
||
/// Specifies whether to produce a <see cref="SpecializedMethod"/>
|
||
/// when type arguments could be inferred from <paramref name="targetType"/>. This parameter
|
||
/// is only used for inferred types and has no effect if <paramref name="typeArguments"/> is non-empty.
|
||
/// </param>
|
||
/// <remarks>
|
||
/// 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 }
|
||
/// }
|
||
/// </remarks>
|
||
public List<List<IMethod>> GetExtensionMethods(IType targetType, string name = null, IReadOnlyList<IType> typeArguments = null, bool substituteInferredTypes = false)
|
||
{
|
||
var lookup = CreateMemberLookup();
|
||
List<List<IMethod>> extensionMethodGroups = new List<List<IMethod>>();
|
||
foreach (var inputGroup in GetAllExtensionMethods(lookup))
|
||
{
|
||
List<IMethod> outputGroup = new List<IMethod>();
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks whether the specified extension method is eligible on the target type.
|
||
/// </summary>
|
||
/// <param name="targetType">Target type that is passed as first argument to the extension method.</param>
|
||
/// <param name="method">The extension method.</param>
|
||
/// <param name="useTypeInference">Whether to perform type inference for the method.
|
||
/// Use <c>false</c> if <paramref name="method"/> is already parameterized (e.g. when type arguments were given explicitly).
|
||
/// Otherwise, use <c>true</c>.
|
||
/// </param>
|
||
/// <param name="outInferredTypes">If the method is generic and <paramref name="useTypeInference"/> is <c>true</c>,
|
||
/// 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.
|
||
/// </param>
|
||
public static bool IsEligibleExtensionMethod(IType targetType, IMethod method, bool useTypeInference, out IType[] outInferredTypes)
|
||
{
|
||
if (targetType == null)
|
||
throw new ArgumentNullException(nameof(targetType));
|
||
if (method == null)
|
||
throw new ArgumentNullException(nameof(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 (thisParameterType.Kind == TypeKind.ByReference)
|
||
{
|
||
// extension method with `this in` or `this ref`
|
||
thisParameterType = ((ByReferenceType)thisParameterType).ElementType;
|
||
}
|
||
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 = { thisParameterType };
|
||
var inferredTypes = ti.InferTypeArguments(method.TypeParameters, arguments, parameterTypes, out _);
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all extension methods available in the current using scope.
|
||
/// This list includes inaccessible methods.
|
||
/// </summary>
|
||
IList<List<IMethod>> GetAllExtensionMethods(MemberLookup lookup)
|
||
{
|
||
var currentUsingScope = context.CurrentUsingScope;
|
||
if (currentUsingScope == null)
|
||
return EmptyList<List<IMethod>>.Instance;
|
||
List<List<IMethod>> extensionMethodGroups = LazyInit.VolatileRead(ref currentUsingScope.AllExtensionMethods);
|
||
if (extensionMethodGroups != null)
|
||
{
|
||
return extensionMethodGroups;
|
||
}
|
||
extensionMethodGroups = new List<List<IMethod>>();
|
||
List<IMethod> 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<IMethod> 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<ResolveResult> 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<MethodListWithDeclaringType>();
|
||
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<IType>.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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resolves an invocation.
|
||
/// </summary>
|
||
/// <param name="target">The target of the invocation. Usually a MethodGroupResolveResult.</param>
|
||
/// <param name="arguments">
|
||
/// Arguments passed to the method.
|
||
/// The resolver may mutate this array to wrap elements in <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
/// <param name="argumentNames">
|
||
/// The argument names. Pass the null string for positional arguments.
|
||
/// </param>
|
||
/// <returns>InvocationResolveResult or UnknownMethodResolveResult</returns>
|
||
public ResolveResult ResolveInvocation(ResolveResult target, ResolveResult[] arguments, string[] argumentNames = null)
|
||
{
|
||
return ResolveInvocation(target, arguments, argumentNames, allowOptionalParameters: true);
|
||
}
|
||
|
||
List<IParameter> CreateParameters(ResolveResult[] arguments, string[] argumentNames)
|
||
{
|
||
List<IParameter> list = new List<IParameter>();
|
||
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], referenceKind: brrr.ReferenceKind));
|
||
}
|
||
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.None)
|
||
{
|
||
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
|
||
/// <summary>
|
||
/// Resolves an indexer access.
|
||
/// </summary>
|
||
/// <param name="target">Target expression.</param>
|
||
/// <param name="arguments">
|
||
/// Arguments passed to the indexer.
|
||
/// The resolver may mutate this array to wrap elements in <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
/// <param name="argumentNames">
|
||
/// The argument names. Pass the null string for positional arguments.
|
||
/// </param>
|
||
/// <returns>ArrayAccessResolveResult, InvocationResolveResult, or ErrorResolveResult</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts all arguments to int,uint,long or ulong.
|
||
/// </summary>
|
||
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
|
||
/// <summary>
|
||
/// Resolves an object creation.
|
||
/// </summary>
|
||
/// <param name="type">Type of the object to create.</param>
|
||
/// <param name="arguments">
|
||
/// Arguments passed to the constructor.
|
||
/// The resolver may mutate this array to wrap elements in <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
/// <param name="argumentNames">
|
||
/// The argument names. Pass the null string for positional arguments.
|
||
/// </param>
|
||
/// <param name="allowProtectedAccess">
|
||
/// Whether to allow calling protected constructors.
|
||
/// This should be false except when resolving constructor initializers.
|
||
/// </param>
|
||
/// <param name="initializerStatements">
|
||
/// Statements for Objects/Collections initializer.
|
||
/// <see cref="InvocationResolveResult.InitializerStatements"/>
|
||
/// </param>
|
||
/// <returns>InvocationResolveResult or ErrorResolveResult</returns>
|
||
public ResolveResult ResolveObjectCreation(IType type, ResolveResult[] arguments, string[] argumentNames = null, bool allowProtectedAccess = false, IList<ResolveResult> 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(input.Type) { invoke } },
|
||
typeArguments: EmptyList<IType>.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<IMethod>() : 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
|
||
/// <summary>
|
||
/// Resolves 'sizeof(type)'.
|
||
/// </summary>
|
||
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
|
||
/// <summary>
|
||
/// Resolves 'this'.
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resolves 'base'.
|
||
/// </summary>
|
||
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
|
||
/// <summary>
|
||
/// Converts the input to <c>bool</c> using the rules for boolean expressions.
|
||
/// That is, <c>operator true</c> is used if a regular conversion to <c>bool</c> is not possible.
|
||
/// </summary>
|
||
public ResolveResult ResolveCondition(ResolveResult input)
|
||
{
|
||
if (input == null)
|
||
throw new ArgumentNullException(nameof(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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Converts the negated input to <c>bool</c> using the rules for boolean expressions.
|
||
/// Computes <c>!(bool)input</c> if the implicit cast to bool is valid; otherwise
|
||
/// computes <c>input.operator false()</c>.
|
||
/// </summary>
|
||
public ResolveResult ResolveConditionFalse(ResolveResult input)
|
||
{
|
||
if (input == null)
|
||
throw new ArgumentNullException(nameof(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.None && 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
|
||
/// <summary>
|
||
/// Resolves an array creation.
|
||
/// </summary>
|
||
/// <param name="elementType">
|
||
/// The array element type.
|
||
/// Pass null to resolve an implicitly-typed array creation.
|
||
/// </param>
|
||
/// <param name="sizeArguments">
|
||
/// 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.
|
||
/// </param>
|
||
/// <param name="initializerElements">
|
||
/// The initializer elements. May be null if no array initializer was specified.
|
||
/// The resolver may mutate this array to wrap elements in <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Resolves an array creation.
|
||
/// </summary>
|
||
/// <param name="elementType">
|
||
/// The array element type.
|
||
/// Pass null to resolve an implicitly-typed array creation.
|
||
/// </param>
|
||
/// <param name="sizeArguments">
|
||
/// 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 <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
/// <param name="initializerElements">
|
||
/// The initializer elements. May be null if no array initializer was specified.
|
||
/// The resolver may mutate this array to wrap elements in <see cref="ConversionResolveResult"/>s!
|
||
/// </param>
|
||
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);
|
||
elementType = typeInference.GetBestCommonType(initializerElements, out _);
|
||
}
|
||
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
|
||
}
|
||
}
|