public override Conversion GetMethodGroupDelegateConversion(BoundMethodGroup source, TypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Must be a bona fide delegate type, not an expression tree type. if (!(destination.IsDelegateType() || destination.SpecialType == SpecialType.System_Delegate)) { return(Conversion.NoConversion); } var(methodSymbol, isFunctionPointer, callingConventionInfo) = GetDelegateInvokeOrFunctionPointerMethodIfAvailable(source, destination, ref useSiteInfo); if ((object)methodSymbol == null) { return(Conversion.NoConversion); } Debug.Assert(destination.SpecialType == SpecialType.System_Delegate || methodSymbol == ((NamedTypeSymbol)destination).DelegateInvokeMethod); var resolution = ResolveDelegateOrFunctionPointerMethodGroup(_binder, source, methodSymbol, isFunctionPointer, callingConventionInfo, ref useSiteInfo); var conversion = (resolution.IsEmpty || resolution.HasAnyErrors) ? Conversion.NoConversion : ToConversion(resolution.OverloadResolutionResult, resolution.MethodGroup, methodSymbol.ParameterCount); resolution.Free(); return(conversion); }
internal override void LookupSymbolsInSingleBinder( LookupResult result, string name, int arity, ConsList <TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(result.IsClear); if (IsSubmissionClass) { this.LookupMembersInternal(result, _container, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); return; } var imports = GetImports(basesBeingResolved); // first lookup members of the namespace if ((options & LookupOptions.NamespaceAliasesOnly) == 0 && _container != null) { this.LookupMembersInternal(result, _container, name, arity, basesBeingResolved, options, originalBinder, diagnose, ref useSiteInfo); if (result.IsMultiViable) { // symbols cannot conflict with using alias names if (arity == 0 && imports.IsUsingAlias(name, originalBinder.IsSemanticModelBinder)) { CSDiagnosticInfo diagInfo = new CSDiagnosticInfo(ErrorCode.ERR_ConflictAliasAndMember, name, _container); var error = new ExtendedErrorTypeSymbol((NamespaceOrTypeSymbol)null, name, arity, diagInfo, unreported: true); result.SetFrom(LookupResult.Good(error)); // force lookup to be done w/ error symbol as result } return; } } // next try using aliases or symbols in imported namespaces imports.LookupSymbol(originalBinder, result, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); }
internal override ManagedKind GetManagedKind(ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) => ManagedKind.Managed;
public override Conversion GetMethodGroupFunctionPointerConversion(BoundMethodGroup source, FunctionPointerTypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Conversions involving method groups require a Binder. throw ExceptionUtilities.Unreachable; }
protected override Conversion GetInterpolatedStringConversion(BoundExpression source, TypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Conversions involving interpolated strings require a Binder. throw ExceptionUtilities.Unreachable; }
private bool CandidateOperators(ArrayBuilder <UnaryOperatorSignature> operators, BoundExpression operand, ArrayBuilder <UnaryOperatorAnalysisResult> results, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { bool anyApplicable = false; foreach (var op in operators) { var conversion = Conversions.ClassifyConversionFromExpression(operand, op.OperandType, ref useSiteInfo); if (conversion.IsImplicit) { anyApplicable = true; results.Add(UnaryOperatorAnalysisResult.Applicable(op, conversion)); } else { results.Add(UnaryOperatorAnalysisResult.Inapplicable(op, conversion)); } } return(anyApplicable); }
private void UnaryOperatorOverloadResolution( BoundExpression operand, UnaryOperatorOverloadResolutionResult result, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // SPEC: Given the set of applicable candidate function members, the best function member in that set is located. // SPEC: If the set contains only one function member, then that function member is the best function member. if (result.SingleValid()) { return; } // SPEC: Otherwise, the best function member is the one function member that is better than all other function // SPEC: members with respect to the given argument list, provided that each function member is compared to all // SPEC: other function members using the rules in 7.5.3.2. If there is not exactly one function member that is // SPEC: better than all other function members, then the function member invocation is ambiguous and a binding-time // SPEC: error occurs. var candidates = result.Results; // Try to find a single best candidate int bestIndex = GetTheBestCandidateIndex(operand, candidates, ref useSiteInfo); if (bestIndex != -1) { // Mark all other candidates as worse for (int index = 0; index < candidates.Count; ++index) { if (candidates[index].Kind != OperatorAnalysisResultKind.Inapplicable && index != bestIndex) { candidates[index] = candidates[index].Worse(); } } return; } for (int i = 1; i < candidates.Count; ++i) { if (candidates[i].Kind != OperatorAnalysisResultKind.Applicable) { continue; } // Is this applicable operator better than every other applicable method? for (int j = 0; j < i; ++j) { if (candidates[j].Kind == OperatorAnalysisResultKind.Inapplicable) { continue; } var better = BetterOperator(candidates[i].Signature, candidates[j].Signature, operand, ref useSiteInfo); if (better == BetterResult.Left) { candidates[j] = candidates[j].Worse(); } else if (better == BetterResult.Right) { candidates[i] = candidates[i].Worse(); } } } }
private UserDefinedConversionResult AnalyzeImplicitUserDefinedConversions( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); // User-defined conversions that involve generics can be quite strange. There // are two basic problems: first, that generic user-defined conversions can be // "shadowed" by built-in conversions, and second, that generic user-defined // conversions can make conversions that would never have been legal user-defined // conversions if declared non-generically. I call this latter kind of conversion // a "suspicious" conversion. // // The shadowed conversions are easily dealt with: // // SPEC: If a predefined implicit conversion exists from a type S to type T, // SPEC: all user-defined conversions, implicit or explicit, are ignored. // SPEC: If a predefined explicit conversion exists from a type S to type T, // SPEC: any user-defined explicit conversion from S to T are ignored. // // The rule above can come into play in cases like: // // sealed class C<T> { public static implicit operator T(C<T> c) { ... } } // C<object> c = whatever; // object o = c; // // The built-in implicit conversion from C<object> to object must shadow // the user-defined implicit conversion. // // The caller of this method checks for user-defined conversions *after* // predefined implicit conversions, so we already know that if we got here, // there was no predefined implicit conversion. // // Note that a user-defined *implicit* conversion may win over a built-in // *explicit* conversion by the rule given above. That is, if we created // an implicit conversion from T to C<T>, then the user-defined implicit // conversion from object to C<object> could be valid, even though that // would be "replacing" a built-in explicit conversion with a user-defined // implicit conversion. This is one of the "suspicious" conversions, // as it would not be legal to declare a user-defined conversion from // object in a non-generic type. // // The way the native compiler handles suspicious conversions involving // interfaces is neither sensible nor in line with the rules in the // specification. It is not clear at this time whether we should be exactly // matching the native compiler, the specification, or neither, in Roslyn. // Spec (6.4.4 User-defined implicit conversions) // A user-defined implicit conversion from an expression E to type T is processed as follows: // SPEC: Find the set of types D from which user-defined conversion operators... var d = ArrayBuilder <TypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, target, d, ref useSiteInfo); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U... var ubuild = ArrayBuilder <UserDefinedConversionAnalysis> .GetInstance(); ComputeApplicableUserDefinedImplicitConversionSet(sourceExpression, source, target, d, ubuild, ref useSiteInfo); d.Free(); ImmutableArray <UserDefinedConversionAnalysis> u = ubuild.ToImmutableAndFree(); // SPEC: If U is empty, the conversion is undefined and a compile-time error occurs. if (u.Length == 0) { return(UserDefinedConversionResult.NoApplicableOperators(u)); } // SPEC: Find the most specific source type SX of the operators in U... TypeSymbol sx = MostSpecificSourceTypeForImplicitUserDefinedConversion(u, source, ref useSiteInfo); if ((object)sx == null) { return(UserDefinedConversionResult.NoBestSourceType(u)); } // SPEC: Find the most specific target type TX of the operators in U... TypeSymbol tx = MostSpecificTargetTypeForImplicitUserDefinedConversion(u, target, ref useSiteInfo); if ((object)tx == null) { return(UserDefinedConversionResult.NoBestTargetType(u)); } int?best = MostSpecificConversionOperator(sx, tx, u); if (best == null) { return(UserDefinedConversionResult.Ambiguous(u)); } return(UserDefinedConversionResult.Valid(u, best.Value)); }
private TypeSymbol MostSpecificSourceTypeForImplicitUserDefinedConversion(ImmutableArray <UserDefinedConversionAnalysis> u, TypeSymbol source, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // SPEC: If any of the operators in U convert from S then SX is S. if ((object)source != null) { if (u.Any(conv => TypeSymbol.Equals(conv.FromType, source, TypeCompareKind.ConsiderEverything2))) { return(source); } } // SPEC: Otherwise, SX is the most encompassed type in the set of // SPEC: source types of the operators in U. return(MostEncompassedType(u, conv => conv.FromType, ref useSiteInfo)); }
private static void ComputeUserDefinedImplicitConversionTypeSet(TypeSymbol s, TypeSymbol t, ArrayBuilder <TypeSymbol> d, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Spec 6.4.4: User-defined implicit conversions // Find the set of types D from which user-defined conversion operators // will be considered. This set consists of S0 (if S0 is a class or struct), // the base classes of S0 (if S0 is a class), and T0 (if T0 is a class or struct). AddTypesParticipatingInUserDefinedConversion(d, s, includeBaseTypes: true, useSiteInfo: ref useSiteInfo); AddTypesParticipatingInUserDefinedConversion(d, t, includeBaseTypes: false, useSiteInfo: ref useSiteInfo); }
private void ComputeApplicableUserDefinedImplicitConversionSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <TypeSymbol> d, ArrayBuilder <UserDefinedConversionAnalysis> u, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo, bool allowAnyTarget = false) { Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert(((object)target != null) == !allowAnyTarget); Debug.Assert(d != null); Debug.Assert(u != null); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U. // SPEC: The set consists of the user-defined and lifted implicit conversion operators // SPEC: declared by the classes and structs in D that convert from a type encompassing // SPEC: E to a type encompassed by T. If U is empty, the conversion is undefined and // SPEC: a compile-time error occurs. // SPEC: Give a user-defined conversion operator that converts from a non-nullable // SPEC: value type S to a non-nullable value type T, a lifted conversion operator // SPEC: exists that converts from S? to T?. // DELIBERATE SPEC VIOLATION: // // The spec here essentially says that we add an applicable "regular" conversion and // an applicable lifted conversion, if there is one, to the candidate set, and then // let them duke it out to determine which one is "best". // // This is not at all what the native compiler does, and attempting to implement // the specification, or slight variations on it, produces too many backwards-compatibility // breaking changes. // // The native compiler deviates from the specification in two major ways here. // First, it does not add *both* the regular and lifted forms to the candidate set. // Second, the way it characterizes a "lifted" form is very, very different from // how the specification characterizes a lifted form. // // An operation, in this case, X-->Y, is properly said to be "lifted" to X?-->Y? via // the rule that X?-->Y? matches the behavior of X-->Y for non-null X, and converts // null X to null Y otherwise. // // The native compiler, by contrast, takes the existing operator and "lifts" either // the operator's parameter type or the operator's return type to nullable. For // example, a conversion from X?-->Y would be "lifted" to X?-->Y? by making the // conversion from X? to Y, and then from Y to Y?. No "lifting" semantics // are imposed; we do not check to see if the X? is null. This operator is not // actually "lifted" at all; rather, an implicit conversion is applied to the // output. **The native compiler considers the result type Y? of that standard implicit // conversion to be the result type of the "lifted" conversion**, rather than // properly considering Y to be the result type of the conversion for the purposes // of computing the best output type. // // MOREOVER: the native compiler actually *does* implement nullable lifting semantics // in the case where the input type of the user-defined conversion is a non-nullable // value type and the output type is a nullable value type **or pointer type, or // reference type**. This is an enormous departure from the specification; the // native compiler will take a user-defined conversion from X-->Y? or X-->C and "lift" // it to a conversion from X?-->Y? or X?-->C that has nullable semantics. // // This is quite confusing. In this code we will classify the conversion as either // "normal" or "lifted" on the basis of *whether or not special lifting semantics // are to be applied*. That is, whether or not a later rewriting pass is going to // need to insert a check to see if the source expression is null, and decide // whether or not to call the underlying unlifted conversion or produce a null // value without calling the unlifted conversion. // DELIBERATE SPEC VIOLATION (See bug 17021) // The specification defines a type U as "encompassing" a type V // if there is a standard implicit conversion from U to V, and // neither are interface types. // // The intention of this language is to ensure that we do not allow user-defined // conversions that involve interfaces. We have a reasonable expectation that a // conversion that involves an interface is one that preserves referential identity, // and user-defined conversions usually do not. // // Now, suppose we have a standard conversion from Alpha to Beta, a user-defined // conversion from Beta to Gamma, and a standard conversion from Gamma to Delta. // The specification allows the implicit conversion from Alpha to Delta only if // Beta encompasses Alpha and Delta encompasses Gamma. And therefore, none of them // can be interface types, de jure. // // However, the dev10 compiler only checks Alpha and Delta to see if they are interfaces, // and allows Beta and Gamma to be interfaces. // // So what's the big deal there? It's not legal to define a user-defined conversion where // the input or output types are interfaces, right? // // It is not legal to define such a conversion, no, but it is legal to create one via generic // construction. If we have a conversion from T to C<T>, then C<I> has a conversion from I to C<I>. // // The dev10 compiler fails to check for this situation. This means that, // you can convert from int to C<IComparable> because int implements IComparable, but cannot // convert from IComparable to C<IComparable>! // // Unfortunately, we know of several real programs that rely upon this bug, so we are going // to reproduce it here. if ((object)source != null && source.IsInterfaceType() || (object)target != null && target.IsInterfaceType()) { return; } HashSet <NamedTypeSymbol> lookedInInterfaces = null; foreach (TypeSymbol declaringType in d) { if (declaringType is TypeParameterSymbol typeParameter) { ImmutableArray <NamedTypeSymbol> interfaceTypes = typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); if (!interfaceTypes.IsEmpty) { lookedInInterfaces ??= new HashSet <NamedTypeSymbol>(Symbols.SymbolEqualityComparer.AllIgnoreOptions); // Equivalent to has identity conversion check foreach (var interfaceType in interfaceTypes) { if (lookedInInterfaces.Add(interfaceType)) { addCandidatesFromType(constrainedToTypeOpt: typeParameter, interfaceType, sourceExpression, source, target, u, ref useSiteInfo, allowAnyTarget); } } } } else { addCandidatesFromType(constrainedToTypeOpt: null, (NamedTypeSymbol)declaringType, sourceExpression, source, target, u, ref useSiteInfo, allowAnyTarget); } } void addCandidatesFromType( TypeParameterSymbol constrainedToTypeOpt, NamedTypeSymbol declaringType, BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ArrayBuilder <UserDefinedConversionAnalysis> u, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo, bool allowAnyTarget) { foreach (MethodSymbol op in declaringType.GetOperators(WellKnownMemberNames.ImplicitConversionName)) { // We might have a bad operator and be in an error recovery situation. Ignore it. if (op.ReturnsVoid || op.ParameterCount != 1) { continue; } TypeSymbol convertsFrom = op.GetParameterType(0); TypeSymbol convertsTo = op.ReturnType; Conversion fromConversion = EncompassingImplicitConversion(sourceExpression, source, convertsFrom, ref useSiteInfo); Conversion toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteInfo); if (fromConversion.Exists && toConversion.Exists) { // There is an additional spec violation in the native compiler. Suppose // we have a conversion from X-->Y and are asked to do "Y? y = new X();" Clearly // the intention is to convert from X-->Y via the implicit conversion, and then // stick a standard implicit conversion from Y-->Y? on the back end. **In this // situation, the native compiler treats the conversion as though it were // actually X-->Y? in source for the purposes of determining the best target // type of an operator. // // We perpetuate this fiction here, except for cases when Y is not a valid type // argument for Nullable<T>. This scenario should only be possible when the corlib // defines a type such as int or long to be a ref struct (see // LiftedConversion_InvalidTypeArgument02). if ((object)target != null && target.IsNullableType() && convertsTo.IsValidNullableTypeArgument()) { convertsTo = MakeNullableType(convertsTo); toConversion = allowAnyTarget ? Conversion.Identity : EncompassingImplicitConversion(null, convertsTo, target, ref useSiteInfo); } u.Add(UserDefinedConversionAnalysis.Normal(constrainedToTypeOpt, op, fromConversion, toConversion, convertsFrom, convertsTo)); } else if ((object)source != null && source.IsNullableType() && convertsFrom.IsValidNullableTypeArgument() && (allowAnyTarget || target.CanBeAssignedNull())) { // As mentioned above, here we diverge from the specification, in two ways. // First, we only check for the lifted form if the normal form was inapplicable. // Second, we are supposed to apply lifting semantics only if the conversion // parameter and return types are *both* non-nullable value types. // // In fact the native compiler determines whether to check for a lifted form on // the basis of: // // * Is the type we are ultimately converting from a nullable value type? // * Is the parameter type of the conversion a non-nullable value type? // * Is the type we are ultimately converting to a nullable value type, // pointer type, or reference type? // // If the answer to all those questions is "yes" then we lift to nullable // and see if the resulting operator is applicable. TypeSymbol nullableFrom = MakeNullableType(convertsFrom); TypeSymbol nullableTo = convertsTo.IsValidNullableTypeArgument() ? MakeNullableType(convertsTo) : convertsTo; Conversion liftedFromConversion = EncompassingImplicitConversion(sourceExpression, source, nullableFrom, ref useSiteInfo); Conversion liftedToConversion = !allowAnyTarget? EncompassingImplicitConversion(null, nullableTo, target, ref useSiteInfo) : Conversion.Identity; if (liftedFromConversion.Exists && liftedToConversion.Exists) { u.Add(UserDefinedConversionAnalysis.Lifted(constrainedToTypeOpt, op, liftedFromConversion, liftedToConversion, nullableFrom, nullableTo)); } } } } }
private static (bool definitelyManaged, bool hasGenerics) DependsOnDefinitelyManagedType( NamedTypeSymbol type, HashSet <Symbol> partialClosure, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo ) { Debug.Assert((object)type != null); var hasGenerics = false; if (partialClosure.Add(type)) { foreach (var member in type.GetInstanceFieldsAndEvents()) { // Only instance fields (including field-like events) affect the outcome. FieldSymbol field; switch (member.Kind) { case SymbolKind.Field: field = (FieldSymbol)member; Debug.Assert( (object)(field.AssociatedSymbol as EventSymbol) == null, "Didn't expect to find a field-like event backing field in the member list." ); break; case SymbolKind.Event: field = ((EventSymbol)member).AssociatedField; break; default: throw ExceptionUtilities.UnexpectedValue(member.Kind); } if ((object)field == null) { continue; } TypeSymbol fieldType = field.NonPointerType(); if (fieldType is null) { // pointers are unmanaged continue; } fieldType.AddUseSiteInfo(ref useSiteInfo); NamedTypeSymbol fieldNamedType = fieldType as NamedTypeSymbol; if ((object)fieldNamedType == null) { if (fieldType.IsManagedType(ref useSiteInfo)) { return(true, hasGenerics); } } else { var result = IsManagedTypeHelper(fieldNamedType); hasGenerics = hasGenerics || result.hasGenerics; // NOTE: don't use ManagedKind.get on a NamedTypeSymbol - that could lead // to infinite recursion. switch (result.isManaged) { case ThreeState.True: return(true, hasGenerics); case ThreeState.False: continue; case ThreeState.Unknown: if (!fieldNamedType.OriginalDefinition.KnownCircularStruct) { var(definitelyManaged, childHasGenerics) = DependsOnDefinitelyManagedType( fieldNamedType, partialClosure, ref useSiteInfo ); hasGenerics = hasGenerics || childHasGenerics; if (definitelyManaged) { return(true, hasGenerics); } } continue; } } } } return(false, hasGenerics); }
protected override Conversion GetInterpolatedStringConversion(BoundUnconvertedInterpolatedString source, TypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // An interpolated string expression may be converted to the types // System.IFormattable and System.FormattableString return((TypeSymbol.Equals(destination, Compilation.GetWellKnownType(WellKnownType.System_IFormattable), TypeCompareKind.ConsiderEverything2) || TypeSymbol.Equals(destination, Compilation.GetWellKnownType(WellKnownType.System_FormattableString), TypeCompareKind.ConsiderEverything2)) ? Conversion.InterpolatedString : Conversion.NoConversion); }
public override Conversion GetMethodGroupFunctionPointerConversion(BoundMethodGroup source, FunctionPointerTypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { var resolution = ResolveDelegateOrFunctionPointerMethodGroup( _binder, source, destination.Signature, isFunctionPointer: true, new CallingConventionInfo(destination.Signature.CallingConvention, destination.Signature.GetCallingConventionModifiers()), ref useSiteInfo); var conversion = (resolution.IsEmpty || resolution.HasAnyErrors) ? Conversion.NoConversion : ToConversion(resolution.OverloadResolutionResult, resolution.MethodGroup, destination.Signature.ParameterCount); resolution.Free(); return(conversion); }
public void UnaryOperatorOverloadResolution(UnaryOperatorKind kind, BoundExpression operand, UnaryOperatorOverloadResolutionResult result, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(operand != null); Debug.Assert(result.Results.Count == 0); // We can do a table lookup for well-known problems in overload resolution. UnaryOperatorEasyOut(kind, operand, result); if (result.Results.Count > 0) { return; } // SPEC: An operation of the form op x or x op, where op is an overloadable unary operator, // SPEC: and x is an expression of type X, is processed as follows: // SPEC: The set of candidate user-defined operators provided by X for the operation operator // SPEC: op(x) is determined using the rules of 7.3.5. bool hadUserDefinedCandidate = GetUserDefinedOperators(kind, operand, result.Results, ref useSiteInfo); // SPEC: If the set of candidate user-defined operators is not empty, then this becomes the // SPEC: set of candidate operators for the operation. Otherwise, the predefined unary operator // SPEC: implementations, including their lifted forms, become the set of candidate operators // SPEC: for the operation. if (!hadUserDefinedCandidate) { result.Results.Clear(); GetAllBuiltInOperators(kind, operand, result.Results, ref useSiteInfo); } // SPEC: The overload resolution rules of 7.5.3 are applied to the set of candidate operators // SPEC: to select the best operator with respect to the argument list (x), and this operator // SPEC: becomes the result of the overload resolution process. If overload resolution fails // SPEC: to select a single best operator, a binding-time error occurs. UnaryOperatorOverloadResolution(operand, result, ref useSiteInfo); }
private TypeSymbol MostSpecificTargetTypeForImplicitUserDefinedConversion(ImmutableArray <UserDefinedConversionAnalysis> u, TypeSymbol target, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // SPEC: If any of the operators in U convert to T then TX is T. // SPEC: Otherwise, TX is the most encompassing type in the set of // SPEC: target types of the operators in U. // DELIBERATE SPEC VIOLATION: // The native compiler deviates from the specification in the way it // determines what the "converts to" type is. The specification is pretty // clear that the "converts to" type is the actual return type of the // conversion operator, or, in the case of a lifted operator, the lifted-to- // nullable type. That is, if we have X-->Y then the converts-to type of // the operator in its normal form is Y, and the converts-to type of the // operator in its lifted form is Y?. // // The native compiler does not do this. Suppose we have a user-defined // conversion X-->Y, and the assignment Y? y = new X(); -- the native // compiler will consider the converts-to type of X-->Y to be Y?, surprisingly // enough. // // We have previously written the appropriate "ToType" into the conversion analysis // to perpetuate this fiction. if (u.Any(conv => TypeSymbol.Equals(conv.ToType, target, TypeCompareKind.ConsiderEverything2))) { return(target); } return(MostEncompassingType(u, conv => conv.ToType, ref useSiteInfo)); }
private void GetAllBuiltInOperators(UnaryOperatorKind kind, BoundExpression operand, ArrayBuilder <UnaryOperatorAnalysisResult> results, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // The spec states that overload resolution is performed upon the infinite set of // operators defined on enumerated types, pointers and delegates. Clearly we cannot // construct the infinite set; we have to pare it down. Previous implementations of C# // implement a much stricter rule; they only add the special operators to the candidate // set if one of the operands is of the relevant type. This means that operands // involving user-defined implicit conversions from class or struct types to enum, // pointer and delegate types do not cause the right candidates to participate in // overload resolution. It also presents numerous problems involving delegate variance // and conversions from lambdas to delegate types. // // It is onerous to require the actually specified behavior. We should change the // specification to match the previous implementation. var operators = ArrayBuilder <UnaryOperatorSignature> .GetInstance(); this.Compilation.builtInOperators.GetSimpleBuiltInOperators(kind, operators, skipNativeIntegerOperators: !operand.Type.IsNativeIntegerOrNullableNativeIntegerType()); GetEnumOperations(kind, operand, operators); var pointerOperator = GetPointerOperation(kind, operand); if (pointerOperator != null) { operators.Add(pointerOperator.Value); } CandidateOperators(operators, operand, results, ref useSiteInfo); operators.Free(); }
private bool IsEncompassedBy(BoundExpression aExpr, TypeSymbol a, TypeSymbol b, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert((object)a != null); Debug.Assert((object)b != null); // SPEC: If a standard implicit conversion exists from a type A to a type B // SPEC: and if neither A nor B is an interface type then A is said to be // SPEC: encompassed by B, and B is said to encompass A. return(EncompassingImplicitConversion(aExpr, a, b, ref useSiteInfo).Exists); }
private bool GetUserDefinedOperators(UnaryOperatorKind kind, BoundExpression operand, ArrayBuilder <UnaryOperatorAnalysisResult> results, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(operand != null); if ((object)operand.Type == null) { // If the operand has no type -- because it is a null reference or a lambda or a method group -- // there is no way we can determine what type to search for user-defined operators. return(false); } // Spec 7.3.5 Candidate user-defined operators // SPEC: Given a type T and an operation op(A) ... the set of candidate user-defined // SPEC: operators provided by T for op(A) is determined as follows: // SPEC: If T is a nullable type then T0 is its underlying type; otherwise T0 is T. // SPEC: For all operator declarations in T0 and all lifted forms of such operators, if // SPEC: at least one operator is applicable with respect to A then the set of candidate // SPEC: operators consists of all such applicable operators. Otherwise, if T0 is object // SPEC: then the set of candidate operators is empty. Otherwise, the set of candidate // SPEC: operators is the set provided by the direct base class of T0, or the effective // SPEC: base class of T0 if T0 is a type parameter. // https://github.com/dotnet/roslyn/issues/34451: The spec quote should be adjusted to cover operators from interfaces as well. // From https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-06-27.md: // - We only even look for operator implementations in interfaces if one of the operands has a type that is an interface or // a type parameter with a non-empty effective base interface list. // - The applicable operators from classes / structs shadow those in interfaces.This matters for constrained type parameters: // the effective base class can shadow operators from effective base interfaces. // - If we find an applicable candidate in an interface, that candidate shadows all applicable operators in base interfaces: // we stop looking. TypeSymbol type0 = operand.Type.StrippedType(); TypeSymbol constrainedToTypeOpt = type0 as TypeParameterSymbol; // Searching for user-defined operators is expensive; let's take an early out if we can. if (OperatorFacts.DefinitelyHasNoUserDefinedOperators(type0)) { return(false); } string name = OperatorFacts.UnaryOperatorNameFromOperatorKind(kind); var operators = ArrayBuilder <UnaryOperatorSignature> .GetInstance(); bool hadApplicableCandidates = false; NamedTypeSymbol current = type0 as NamedTypeSymbol; if ((object)current == null) { current = type0.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } if ((object)current == null && type0.IsTypeParameter()) { current = ((TypeParameterSymbol)type0).EffectiveBaseClass(ref useSiteInfo); } for (; (object)current != null; current = current.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo)) { operators.Clear(); GetUserDefinedUnaryOperatorsFromType(constrainedToTypeOpt, current, kind, name, operators); results.Clear(); if (CandidateOperators(operators, operand, results, ref useSiteInfo)) { hadApplicableCandidates = true; break; } } // Look in base interfaces, or effective interfaces for type parameters if (!hadApplicableCandidates) { ImmutableArray <NamedTypeSymbol> interfaces = default; if (type0.IsInterfaceType()) { interfaces = type0.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } else if (type0.IsTypeParameter()) { interfaces = ((TypeParameterSymbol)type0).AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); } if (!interfaces.IsDefaultOrEmpty) { var shadowedInterfaces = PooledHashSet <NamedTypeSymbol> .GetInstance(); var resultsFromInterface = ArrayBuilder <UnaryOperatorAnalysisResult> .GetInstance(); results.Clear(); foreach (NamedTypeSymbol @interface in interfaces) { if ([email protected]) { // this code could be reachable in error situations continue; } if (shadowedInterfaces.Contains(@interface)) { // this interface is "shadowed" by a derived interface continue; } operators.Clear(); resultsFromInterface.Clear(); GetUserDefinedUnaryOperatorsFromType(constrainedToTypeOpt, @interface, kind, name, operators); if (CandidateOperators(operators, operand, resultsFromInterface, ref useSiteInfo)) { hadApplicableCandidates = true; results.AddRange(resultsFromInterface); // this interface "shadows" all its base interfaces shadowedInterfaces.AddAll(@interface.AllInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo)); } } shadowedInterfaces.Free(); resultsFromInterface.Free(); } } operators.Free(); return(hadApplicableCandidates); }
private Conversion EncompassingImplicitConversion(BoundExpression aExpr, TypeSymbol a, TypeSymbol b, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(aExpr != null || (object)a != null); Debug.Assert((object)b != null); // DELIBERATE SPEC VIOLATION: // We ought to be saying that an encompassing conversion never exists when one of // the types is an interface type, but due to a desire to be compatible with a // dev10 bug, we allow it. See the comment regarding bug 17021 above for more details. var result = ClassifyStandardImplicitConversion(aExpr, a, b, ref useSiteInfo); return(IsEncompassingImplicitConversionKind(result.Kind) ? result : Conversion.NoConversion); }
private ImmutableArray <Symbol> ComputeSortedCrefMembers(NamespaceOrTypeSymbol?containerOpt, string memberName, int arity, bool hasParameterList, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Since we may find symbols without going through the lookup API, // expose the symbols via an ArrayBuilder. ArrayBuilder <Symbol> builder; { LookupResult result = LookupResult.GetInstance(); this.LookupSymbolsOrMembersInternal( result, containerOpt, name: memberName, arity: arity, basesBeingResolved: null, options: LookupOptions.AllMethodsOnArityZero, diagnose: false, useSiteInfo: ref useSiteInfo); // CONSIDER: Dev11 also checks for a constructor in the event of an ambiguous result. if (result.IsMultiViable) { // Dev11 doesn't consider members from System.Object when the container is an interface. // Lookup should already have dropped such members. builder = ArrayBuilder <Symbol> .GetInstance(); builder.AddRange(result.Symbols); result.Free(); } else { result.Free(); // Won't be using this. // Dev11 has a complicated two-stage process for determining when a cref is really referring to a constructor. // Under two sets of conditions, XmlDocCommentBinder::bindXMLReferenceName will decide that a name refers // to a constructor and under one set of conditions, the calling method, XmlDocCommentBinder::bindXMLReference, // will roll back that decision and return null. // In XmlDocCommentBinder::bindXMLReferenceName: // 1) If an unqualified, non-generic name didn't bind to anything and the name matches the name of the type // to which the doc comment is applied, then bind to a constructor. // 2) If a qualified, non-generic name didn't bind to anything and the LHS of the qualified name is a type // with the same name, then bind to a constructor. // Quoted from XmlDocCommentBinder::bindXMLReference: // Filtering out the case where specifying the name of a generic type without specifying // any arity returns a constructor. This case shouldn't return anything. Note that // returning the constructors was a fix for the wonky constructor behavior, but in order // to not introduce a regression and breaking change we return NULL in this case. // e.g. // // /// <see cref="Goo"/> // class Goo<T> { } // // This cref used not to bind to anything, because before it was looking for a type and // since there was no arity, it didn't find Goo<T>. Now however, it finds Goo<T>.ctor, // which is arguably correct, but would be a breaking change (albeit with minimal impact) // so we catch this case and chuck out the symbol found. // In Roslyn, we're doing everything in one pass, rather than guessing and rolling back. // As in the native compiler, we treat this as a fallback case - something that actually has the // specified name is preferred. NamedTypeSymbol?constructorType = null; if (arity == 0) // Member arity { NamedTypeSymbol?containerType = containerOpt as NamedTypeSymbol; if ((object?)containerType != null) { // Case 1: If the name is qualified by a type with the same name, then we want a // constructor (unless the type is generic, the cref is on/in the type (but not // on/in a nested type), and there were no parens after the member name). if (containerType.Name == memberName && (hasParameterList || containerType.Arity == 0 || !TypeSymbol.Equals(this.ContainingType, containerType.OriginalDefinition, TypeCompareKind.ConsiderEverything2))) { constructorType = containerType; } } else if ((object?)containerOpt == null && hasParameterList) { // Case 2: If the name is not qualified by anything, but we're in the scope // of a type with the same name (regardless of arity), then we want a constructor, // as long as there were parens after the member name. NamedTypeSymbol?binderContainingType = this.ContainingType; if ((object?)binderContainingType != null && memberName == binderContainingType.Name) { constructorType = binderContainingType; } } } if ((object?)constructorType != null) { ImmutableArray <MethodSymbol> instanceConstructors = constructorType.InstanceConstructors; int numInstanceConstructors = instanceConstructors.Length; if (numInstanceConstructors == 0) { return(ImmutableArray <Symbol> .Empty); } builder = ArrayBuilder <Symbol> .GetInstance(numInstanceConstructors); builder.AddRange(instanceConstructors); } else { return(ImmutableArray <Symbol> .Empty); } } } Debug.Assert(builder != null); // Since we resolve ambiguities by just picking the first symbol we encounter, // the order of the symbols matters for repeatability. if (builder.Count > 1) { builder.Sort(ConsistentSymbolOrder.Instance); } return(builder.ToImmutableAndFree()); }
protected UserDefinedConversionResult AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType(TypeSymbol source, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // SPEC: The governing type of a switch statement is established by the switch expression. // SPEC: 1) If the type of the switch expression is sbyte, byte, short, ushort, int, uint, // SPEC: long, ulong, bool, char, string, or an enum-type, or if it is the nullable type // SPEC: corresponding to one of these types, then that is the governing type of the switch statement. // SPEC: 2) Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the // SPEC: type of the switch expression to one of the following possible governing types: // SPEC: sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or, a nullable type // SPEC: corresponding to one of those types // NOTE: This method implements part (2) above, it should be called only if (1) is false for source type. Debug.Assert((object)source != null); Debug.Assert(!source.IsValidV6SwitchGoverningType()); // NOTE: For (2) we use an approach similar to native compiler's approach, but call into the common code for analyzing user defined implicit conversions. // NOTE: (a) Compute the set of types D from which user-defined conversion operators should be considered by considering only the source type. // NOTE: (b) Instead of computing applicable user defined implicit conversions U from the source type to a specific target type, // NOTE: we compute these from the source type to ANY target type. // NOTE: (c) From the conversions in U, select the most specific of them that targets a valid switch governing type // SPEC VIOLATION: Because we use the same strategy for computing the most specific conversion, as the Dev10 compiler did (in fact // SPEC VIOLATION: we share the code), we inherit any spec deviances in that analysis. Specifically, the analysis only considers // SPEC VIOLATION: which conversion has the least amount of lifting, where a conversion may be considered to be in unlifted form, // SPEC VIOLATION: half-lifted form (only the argument type or return type is lifted) or fully lifted form. The most specific computation // SPEC VIOLATION: looks for a unique conversion that is least lifted. The spec, on the other hand, requires that the conversion // SPEC VIOLATION: be *unique*, not merely most use the least amount of lifting among the applicable conversions. // SPEC VIOLATION: This introduces a SPEC VIOLATION for the following tests in the native compiler: // NOTE: // See test SwitchTests.CS0166_AggregateTypeWithMultipleImplicitConversions_07 // NOTE: struct Conv // NOTE: { // NOTE: public static implicit operator int (Conv C) { return 1; } // NOTE: public static implicit operator int (Conv? C2) { return 0; } // NOTE: public static int Main() // NOTE: { // NOTE: Conv? D = new Conv(); // NOTE: switch(D) // NOTE: { ... // SPEC VIOLATION: Native compiler allows the above code to compile // SPEC VIOLATION: even though there are two user-defined implicit conversions: // SPEC VIOLATION: 1) To int type (applicable in normal form): public static implicit operator int (Conv? C2) // SPEC VIOLATION: 2) To int? type (applicable in lifted form): public static implicit operator int (Conv C) // NOTE: // See also test SwitchTests.TODO // NOTE: struct Conv // NOTE: { // NOTE: public static implicit operator int? (Conv C) { return 1; } // NOTE: public static implicit operator string (Conv? C2) { return 0; } // NOTE: public static int Main() // NOTE: { // NOTE: Conv? D = new Conv(); // NOTE: switch(D) // NOTE: { ... // SPEC VIOLATION: Native compiler allows the above code to compile too // SPEC VIOLATION: even though there are two user-defined implicit conversions: // SPEC VIOLATION: 1) To string type (applicable in normal form): public static implicit operator string (Conv? C2) // SPEC VIOLATION: 2) To int? type (applicable in half-lifted form): public static implicit operator int? (Conv C) // SPEC VIOLATION: This occurs because the native compiler compares the applicable conversions to find one with the least amount // SPEC VIOLATION: of lifting, ignoring whether the return types are the same or not. // SPEC VIOLATION: We do the same to maintain compatibility with the native compiler. // (a) Compute the set of types D from which user-defined conversion operators should be considered by considering only the source type. var d = ArrayBuilder <TypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, t: null, d: d, useSiteInfo: ref useSiteInfo); // (b) Instead of computing applicable user defined implicit conversions U from the source type to a specific target type, // we compute these from the source type to ANY target type. We will filter out those that are valid switch governing // types later. var ubuild = ArrayBuilder <UserDefinedConversionAnalysis> .GetInstance(); ComputeApplicableUserDefinedImplicitConversionSet(null, source, target: null, d: d, u: ubuild, useSiteInfo: ref useSiteInfo, allowAnyTarget: true); d.Free(); ImmutableArray <UserDefinedConversionAnalysis> u = ubuild.ToImmutableAndFree(); // (c) Find that conversion with the least amount of lifting int?best = MostSpecificConversionOperator(conv => conv.ToType.IsValidV6SwitchGoverningType(isTargetTypeOfUserDefinedOp: true), u); if (best != null) { return(UserDefinedConversionResult.Valid(u, best.Value)); } return(UserDefinedConversionResult.NoApplicableOperators(u)); }
public override Conversion GetStackAllocConversion(BoundStackAllocArrayCreation sourceExpression, TypeSymbol destination, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // Conversions involving stackalloc expressions require a Binder. throw ExceptionUtilities.Unreachable; }
internal void LookupSymbolInAliases( Binder originalBinder, LookupResult result, string name, int arity, ConsList <TypeSymbol> basesBeingResolved, LookupOptions options, bool diagnose, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo ) { bool callerIsSemanticModel = originalBinder.IsSemanticModelBinder; AliasAndUsingDirective alias; if (this.UsingAliases.TryGetValue(name, out alias)) { // Found a match in our list of normal aliases. Mark the alias as being seen so that // it won't be reported to the user as something that can be removed. var res = originalBinder.CheckViability( alias.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved ); if (res.Kind == LookupResultKind.Viable) { MarkImportDirective(alias.UsingDirective, callerIsSemanticModel); } result.MergeEqual(res); } foreach (var a in this.ExternAliases) { if (a.Alias.Name == name) { // Found a match in our list of extern aliases. Mark the extern alias as being // seen so that it won't be reported to the user as something that can be // removed. var res = originalBinder.CheckViability( a.Alias, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved ); if (res.Kind == LookupResultKind.Viable) { MarkImportDirective(a.ExternAliasDirective, callerIsSemanticModel); } result.MergeEqual(res); } } }
internal override bool IsAccessibleHelper(Symbol symbol, TypeSymbol accessThroughType, out bool failedThroughTypeCheck, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo, ConsList <TypeSymbol> basesBeingResolved) { var type = _container as NamedTypeSymbol; if ((object)type != null) { return(this.IsSymbolAccessibleConditional(symbol, type, accessThroughType, out failedThroughTypeCheck, ref useSiteInfo)); } else { return(Next.IsAccessibleHelper(symbol, accessThroughType, out failedThroughTypeCheck, ref useSiteInfo, basesBeingResolved)); // delegate to containing Binder, eventually checking assembly. } }
/// <summary> /// Recursively builds a Conversion object with Kind=Deconstruction including information about any necessary /// Deconstruct method and any element-wise conversion. /// /// Note that the variables may either be plain or nested variables. /// The variables may be updated with inferred types if they didn't have types initially. /// Returns false if there was an error. /// </summary> private bool MakeDeconstructionConversion( TypeSymbol type, SyntaxNode syntax, SyntaxNode rightSyntax, BindingDiagnosticBag diagnostics, ArrayBuilder <DeconstructionVariable> variables, out Conversion conversion) { Debug.Assert((object)type != null); ImmutableArray <TypeSymbol> tupleOrDeconstructedTypes; conversion = Conversion.Deconstruction; // Figure out the deconstruct method (if one is required) and determine the types we get from the RHS at this level var deconstructMethod = default(DeconstructMethodInfo); if (type.IsTupleType) { // tuple literal such as `(1, 2)`, `(null, null)`, `(x.P, y.M())` tupleOrDeconstructedTypes = type.TupleElementTypesWithAnnotations.SelectAsArray(TypeMap.AsTypeSymbol); SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics); if (variables.Count != tupleOrDeconstructedTypes.Length) { Error(diagnostics, ErrorCode.ERR_DeconstructWrongCardinality, syntax, tupleOrDeconstructedTypes.Length, variables.Count); return(false); } } else { if (variables.Count < 2) { Error(diagnostics, ErrorCode.ERR_DeconstructTooFewElements, syntax); return(false); } var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type); BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray <BoundDeconstructValuePlaceholder> outPlaceholders, out _); if (deconstructInvocation.HasAnyErrors) { return(false); } deconstructMethod = new DeconstructMethodInfo(deconstructInvocation, inputPlaceholder, outPlaceholders); tupleOrDeconstructedTypes = outPlaceholders.SelectAsArray(p => p.Type); SetInferredTypes(variables, tupleOrDeconstructedTypes, diagnostics); } // Figure out whether those types will need conversions, including further deconstructions bool hasErrors = false; int count = variables.Count; var nestedConversions = ArrayBuilder <Conversion> .GetInstance(count); for (int i = 0; i < count; i++) { var variable = variables[i]; Conversion nestedConversion; if (variable.NestedVariables is object) { var elementSyntax = syntax.Kind() == SyntaxKind.TupleExpression ? ((TupleExpressionSyntax)syntax).Arguments[i] : syntax; hasErrors |= !MakeDeconstructionConversion(tupleOrDeconstructedTypes[i], elementSyntax, rightSyntax, diagnostics, variable.NestedVariables, out nestedConversion); } else { var single = variable.Single; Debug.Assert(single is object); CompoundUseSiteInfo <AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); nestedConversion = this.Conversions.ClassifyConversionFromType(tupleOrDeconstructedTypes[i], single.Type, ref useSiteInfo); diagnostics.Add(single.Syntax, useSiteInfo); if (!nestedConversion.IsImplicit) { hasErrors = true; GenerateImplicitConversionError(diagnostics, Compilation, single.Syntax, nestedConversion, tupleOrDeconstructedTypes[i], single.Type); } } nestedConversions.Add(nestedConversion); } conversion = new Conversion(ConversionKind.Deconstruction, deconstructMethod, nestedConversions.ToImmutableAndFree()); return(!hasErrors); }
internal override void LookupSymbolsInSingleBinder( LookupResult result, string name, int arity, ConsList <TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(result.IsClear); LookupSymbolInAliases( this.GetUsingAliasesMap(basesBeingResolved), this.ExternAliases, originalBinder, result, name, arity, basesBeingResolved, options, diagnose, ref useSiteInfo); }
private BetterResult BetterOperator(UnaryOperatorSignature op1, UnaryOperatorSignature op2, BoundExpression operand, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { // First we see if the conversion from the operand to one operand type is better than // the conversion to the other. BetterResult better = BetterConversionFromExpression(operand, op1.OperandType, op2.OperandType, ref useSiteInfo); if (better == BetterResult.Left || better == BetterResult.Right) { return(better); } // There was no better member on the basis of conversions. Go to the tiebreaking round. // SPEC: In case the parameter type sequences P1, P2 and Q1, Q2 are equivalent -- that is, every Pi // SPEC: has an identity conversion to the corresponding Qi -- the following tie-breaking rules // SPEC: are applied: if (Conversions.HasIdentityConversion(op1.OperandType, op2.OperandType)) { // SPEC: If Mp has more specific parameter types than Mq then Mp is better than Mq. // Under what circumstances can two unary operators with identical signatures be "more specific" // than another? With a binary operator you could have C<T>.op+(C<T>, T) and C<T>.op+(C<T>, int). // When doing overload resolution on C<int> + int, the latter is more specific. But with a unary // operator, the sole operand *must* be the containing type or its nullable type. Therefore // if there is an identity conversion, then the parameters really were identical. We therefore // skip checking for specificity. // SPEC: If one member is a non-lifted operator and the other is a lifted operator, // SPEC: the non-lifted one is better. bool lifted1 = op1.Kind.IsLifted(); bool lifted2 = op2.Kind.IsLifted(); if (lifted1 && !lifted2) { return(BetterResult.Right); } else if (!lifted1 && lifted2) { return(BetterResult.Left); } } // Always prefer operators with val parameters over operators with in parameters: if (op1.RefKind == RefKind.None && op2.RefKind == RefKind.In) { return(BetterResult.Left); } else if (op2.RefKind == RefKind.None && op1.RefKind == RefKind.In) { return(BetterResult.Right); } return(BetterResult.Neither); }
private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(method.IsExtensionMethod); Debug.Assert((object)thisType != null); if (!method.IsGenericMethod || method != method.ConstructedFrom) { return(method); } // We never resolve extension methods on a dynamic receiver. if (thisType.IsDynamic()) { return(null); } var containingAssembly = method.ContainingAssembly; var errorNamespace = containingAssembly.GlobalNamespace; var conversions = new TypeConversions(containingAssembly.CorLibrary); // There is absolutely no plausible syntax/tree that we could use for these // synthesized literals. We could be speculatively binding a call to a PE method. var syntaxTree = CSharpSyntaxTree.Dummy; var syntax = (CSharpSyntaxNode)syntaxTree.GetRoot(); // Create an argument value for the "this" argument of specific type, // and pass the same bad argument value for all other arguments. var thisArgumentValue = new BoundLiteral(syntax, ConstantValue.Bad, thisType) { WasCompilerGenerated = true }; var otherArgumentType = new ExtendedErrorTypeSymbol(errorNamespace, name: string.Empty, arity: 0, errorInfo: null, unreported: false); var otherArgumentValue = new BoundLiteral(syntax, ConstantValue.Bad, otherArgumentType) { WasCompilerGenerated = true }; var paramCount = method.ParameterCount; var arguments = new BoundExpression[paramCount]; for (int i = 0; i < paramCount; i++) { var argument = (i == 0) ? thisArgumentValue : otherArgumentValue; arguments[i] = argument; } var typeArgs = MethodTypeInferrer.InferTypeArgumentsFromFirstArgument( conversions, method, arguments.AsImmutable(), useSiteInfo: ref useSiteInfo); if (typeArgs.IsDefault) { return(null); } // For the purpose of constraint checks we use error type symbol in place of type arguments that we couldn't infer from the first argument. // This prevents constraint checking from failing for corresponding type parameters. int firstNullInTypeArgs = -1; var notInferredTypeParameters = PooledHashSet <TypeParameterSymbol> .GetInstance(); var typeParams = method.TypeParameters; var typeArgsForConstraintsCheck = typeArgs; for (int i = 0; i < typeArgsForConstraintsCheck.Length; i++) { if (!typeArgsForConstraintsCheck[i].HasType) { firstNullInTypeArgs = i; var builder = ArrayBuilder <TypeWithAnnotations> .GetInstance(); builder.AddRange(typeArgsForConstraintsCheck, firstNullInTypeArgs); for (; i < typeArgsForConstraintsCheck.Length; i++) { var typeArg = typeArgsForConstraintsCheck[i]; if (!typeArg.HasType) { notInferredTypeParameters.Add(typeParams[i]); builder.Add(TypeWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType)); } else { builder.Add(typeArg); } } typeArgsForConstraintsCheck = builder.ToImmutableAndFree(); break; } } // Check constraints. var diagnosticsBuilder = ArrayBuilder <TypeParameterDiagnosticInfo> .GetInstance(); var substitution = new TypeMap(typeParams, typeArgsForConstraintsCheck); ArrayBuilder <TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; var success = method.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, NoLocation.Singleton, diagnostics: null, template: new CompoundUseSiteInfo <AssemblySymbol>(useSiteInfo)), substitution, typeParams, typeArgsForConstraintsCheck, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: null, ref useSiteDiagnosticsBuilder, ignoreTypeConstraintsDependentOnTypeParametersOpt: notInferredTypeParameters.Count > 0 ? notInferredTypeParameters : null); diagnosticsBuilder.Free(); notInferredTypeParameters.Free(); if (useSiteDiagnosticsBuilder != null && useSiteDiagnosticsBuilder.Count > 0) { foreach (var diag in useSiteDiagnosticsBuilder) { useSiteInfo.Add(diag.UseSiteInfo); } } if (!success) { return(null); } // For the purpose of construction we use original type parameters in place of type arguments that we couldn't infer from the first argument. ImmutableArray <TypeWithAnnotations> typeArgsForConstruct = typeArgs; if (typeArgs.Any(t => !t.HasType)) { typeArgsForConstruct = typeArgs.ZipAsArray( method.TypeParameters, (t, tp) => t.HasType ? t : TypeWithAnnotations.Create(tp)); } return(method.Construct(typeArgsForConstruct)); }
protected BoundExpression LowerEvaluation(BoundDagEvaluation evaluation) { BoundExpression input = _tempAllocator.GetTemp(evaluation.Input); switch (evaluation) { case BoundDagFieldEvaluation f: { FieldSymbol field = f.Field; var outputTemp = new BoundDagTemp(f.Syntax, field.Type, f); BoundExpression output = _tempAllocator.GetTemp(outputTemp); BoundExpression access = _localRewriter.MakeFieldAccess(f.Syntax, input, field, null, LookupResultKind.Viable, field.Type); access.WasCompilerGenerated = true; return(_factory.AssignmentExpression(output, access)); } case BoundDagPropertyEvaluation p: { PropertySymbol property = p.Property; var outputTemp = new BoundDagTemp(p.Syntax, property.Type, p); BoundExpression output = _tempAllocator.GetTemp(outputTemp); return(_factory.AssignmentExpression(output, _localRewriter.MakePropertyAccess(_factory.Syntax, input, property, LookupResultKind.Viable, property.Type, isLeftOfAssignment: false))); } case BoundDagDeconstructEvaluation d: { MethodSymbol method = d.DeconstructMethod; var refKindBuilder = ArrayBuilder <RefKind> .GetInstance(); var argBuilder = ArrayBuilder <BoundExpression> .GetInstance(); BoundExpression receiver; void addArg(RefKind refKind, BoundExpression expression) { refKindBuilder.Add(refKind); argBuilder.Add(expression); } Debug.Assert(method.Name == WellKnownMemberNames.DeconstructMethodName); int extensionExtra; if (method.IsStatic) { Debug.Assert(method.IsExtensionMethod); receiver = _factory.Type(method.ContainingType); addArg(method.ParameterRefKinds[0], input); extensionExtra = 1; } else { receiver = input; extensionExtra = 0; } for (int i = extensionExtra; i < method.ParameterCount; i++) { ParameterSymbol parameter = method.Parameters[i]; Debug.Assert(parameter.RefKind == RefKind.Out); var outputTemp = new BoundDagTemp(d.Syntax, parameter.Type, d, i - extensionExtra); addArg(RefKind.Out, _tempAllocator.GetTemp(outputTemp)); } return(_factory.Call(receiver, method, refKindBuilder.ToImmutableAndFree(), argBuilder.ToImmutableAndFree())); } case BoundDagTypeEvaluation t: { TypeSymbol inputType = input.Type; Debug.Assert(inputType is { }); if (inputType.IsDynamic()) { // Avoid using dynamic conversions for pattern-matching. inputType = _factory.SpecialType(SpecialType.System_Object); input = _factory.Convert(inputType, input); } TypeSymbol type = t.Type; var outputTemp = new BoundDagTemp(t.Syntax, type, t); BoundExpression output = _tempAllocator.GetTemp(outputTemp); CompoundUseSiteInfo <AssemblySymbol> useSiteInfo = _localRewriter.GetNewCompoundUseSiteInfo(); Conversion conversion = _factory.Compilation.Conversions.ClassifyBuiltInConversion(inputType, output.Type, ref useSiteInfo); _localRewriter._diagnostics.Add(t.Syntax, useSiteInfo); BoundExpression evaluated; if (conversion.Exists) { if (conversion.Kind == ConversionKind.ExplicitNullable && inputType.GetNullableUnderlyingType().Equals(output.Type, TypeCompareKind.AllIgnoreOptions) && _localRewriter.TryGetNullableMethod(t.Syntax, inputType, SpecialMember.System_Nullable_T_GetValueOrDefault, out MethodSymbol getValueOrDefault)) { // As a special case, since the null test has already been done we can use Nullable<T>.GetValueOrDefault evaluated = _factory.Call(input, getValueOrDefault); } else { evaluated = _factory.Convert(type, input, conversion); } } else { evaluated = _factory.As(input, type); } return(_factory.AssignmentExpression(output, evaluated)); }