private UserDefinedConversionResult AnalyzeExplicitUserDefinedConversions( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, bool isChecked, ref CompoundUseSiteInfo <AssemblySymbol> useSiteInfo) { Debug.Assert(sourceExpression is null || Compilation is not null); Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)target != null); // SPEC: A user-defined explicit conversion from type S to type T is processed // SPEC: as follows: // SPEC: Find the set of types D from which user-defined conversion operators // SPEC: will be considered... var d = ArrayBuilder <(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> .GetInstance(); ComputeUserDefinedExplicitConversionTypeSet(source, target, d, ref useSiteInfo); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U... var ubuild = ArrayBuilder <UserDefinedConversionAnalysis> .GetInstance(); ComputeApplicableUserDefinedExplicitConversionSet(sourceExpression, source, target, isChecked: isChecked, 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 = MostSpecificSourceTypeForExplicitUserDefinedConversion(u, sourceExpression, 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 = MostSpecificTargetTypeForExplicitUserDefinedConversion(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)); }
/// <remarks> /// NOTE: Keep this method in sync with <see cref="AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType"/>. /// </remarks> private UserDefinedConversionResult AnalyzeImplicitUserDefinedConversions( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { 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 <NamedTypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, target, d, ref useSiteDiagnostics); // 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 useSiteDiagnostics); 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 useSiteDiagnostics); #if XSHARP if ((object)sx == null) { // When converting to USUAL and no valid operator is found then choose the operator with an Object Parameter // Except when the source is a pointer type. if (this is Conversions) { Conversions conv = this as Conversions; bool usePointer = source.IsPointerType(); if (conv.Compilation.Options.HasRuntime && target == conv.Compilation.UsualType()) { for (int i = 0; i < u.Length; i++) { var x = u[i]; if (usePointer && x.ToType == target && x.FromType.IsVoidPointer()) { return(UserDefinedConversionResult.Valid(u, i)); } if (!usePointer && x.ToType == target && x.FromType == conv.Compilation.GetSpecialType(SpecialType.System_Object)) { return(UserDefinedConversionResult.Valid(u, i)); } } } } } #endif 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 useSiteDiagnostics); 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 static UserDefinedConversionResult MostSpecificConversionOperatorForSwitchGoverningType(TypeSymbol sx, ImmutableArray <UserDefinedConversionAnalysis> u) { // This method finds the most specific user-defined implicit conversion operator from the best source type SX to a valid switch governing type. // It implements steps (e) and (f) for AnalyzeImplicitUserDefinedConversionForSwitchGoverningType, see comments in that method for details. // (e) Instead of finding the most specific target type TX of the operators in U, we consider all unique target types TX for all operators in U. // Let this set of unique target types be called Y. var y = new HashSet <TypeSymbol>(); UserDefinedConversionResult?exactConversionResult = null; // (f) Check if there is exactly one target type TX in Y such that it satisfied both conditions below: // (i) TX is a valid switch governing type as per condition (2) of the SPEC for establishing the switch governing type and // (ii) There is a valid most specific user defined implicit conversion operator from SX to TX. foreach (UserDefinedConversionAnalysis analysis in u) { TypeSymbol tx = analysis.ToType; if (y.Add(tx) && tx.IsValidSwitchGoverningType(isTargetTypeOfUserDefinedOp: true)) { if (!exactConversionResult.HasValue) { // NOTE: As mentioned in the comments at the start of this function, native compiler doesn't call into // NOTE: the code for analyzing user defined implicit conversion, i.e. MostSpecificConversionOperator, // NOTE: but does the analysis of applicable lifted/normal forms itself. // NOTE: This introduces a SPEC VIOLATION for the following test 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) // SPEC VIOLATION: We maintain compability with the native compiler. int?best = MostSpecificConversionOperator(sx, tx, u); if (best != null) { exactConversionResult = UserDefinedConversionResult.Valid(u, best.Value); continue; } } return(UserDefinedConversionResult.Ambiguous(u)); } } // If there exists such unique TX in Y, then that operator is the resultant user defined conversion and TX is the resultant switch governing type. // Otherwise we either have ambiguity or no applicable operators. return(exactConversionResult.HasValue ? exactConversionResult.Value : UserDefinedConversionResult.NoApplicableOperators(u)); }