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 AnalyzeImplicitUserDefinedConversion. /// </remarks> protected UserDefinedConversionResult AnalyzeImplicitUserDefinedConversionForV6SwitchGoverningType(TypeSymbol source, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { // 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 <NamedTypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, t: null, d: d, useSiteDiagnostics: ref useSiteDiagnostics); // (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, useSiteDiagnostics: ref useSiteDiagnostics, 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)); }
/// <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)); }
/// <remarks> /// NOTE: Keep this method in sync with AnalyzeImplicitUserDefinedConversion. /// </remarks> protected UserDefinedConversionResult AnalyzeImplicitUserDefinedConversionForSwitchGoverningType(TypeSymbol source, ref HashSet <DiagnosticInfo> useSiteDiagnostics) { // 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.IsValidSwitchGoverningType()); // NOTE: There are multiple possible approaches for implementing (2): // NOTE: 1) AnalyzeImplicitUserDefinedConversion from source type to each of the possible governing types // NOTE: mentioned in (2): Though this approach exactly matches the specification, it is highly inefficient. // NOTE: We need to consider 20 possible target types (ten primitive non-nullable types // NOTE: and ten primitive nullable types) to determine if there is exactly one valid user defined conversion to // NOTE: any one of these types, requiring 20 calls to AnalyzeImplicitUserDefinedConversion. // NOTE: 2) Native compiler's approach: Native compiler implements this by walking through all the implicit user defined operators // NOTE: from the source type to a valid switch governing type, as per (2), and determining if there is a unique best. // NOTE: This part is that piece of code doesn't call into the code for analyzing user defined implicit conversion, but does the // NOTE: analysis of applicable lifted/normal forms itself. This makes it very difficult to maintain and is bug prone. // NOTE: See the SPEC VIOLATION comment later in this method for one of the cases where it gets the analysis wrong and violates the // NOTE: language specification. // NOTE: 3) Use an approach similar to native compiler's approach, but call into the common code for analyzing user defined implicit conversion. // NOTE: We choose approach (3) and implement it as a slight variation of AnalyzeImplicitUserDefinedConversion as follows: // 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) If U is empty, the conversion is undefined and a compile-time error occurs. // NOTE: (d) Find the most specific source type SX of the operators in U... // NOTE: (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. // NOTE: Let this set of unique target types be called Y. // NOTE: (f) Check if there is exactly one target type TX in Y such that it satisfied both conditions below: // NOTE: (i) TX is a valid switch governing type as per condition (2) of the SPEC for establishing the switch governing type and // NOTE: (ii) There is a valid most specific user defined implicit conversion operator from SX to TX. // NOTE: 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. // NOTE: Otherwise we either have ambiguity or no applicable operators. // (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 <NamedTypeSymbol> .GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, t: null, d: d, useSiteDiagnostics: ref useSiteDiagnostics); // (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. var ubuild = ArrayBuilder <UserDefinedConversionAnalysis> .GetInstance(); ComputeApplicableUserDefinedImplicitConversionSet(null, source, target: null, d: d, u: ubuild, useSiteDiagnostics: ref useSiteDiagnostics, allowAnyTarget: true); d.Free(); ImmutableArray <UserDefinedConversionAnalysis> u = ubuild.ToImmutableAndFree(); // (c) If U is empty, the conversion is undefined and a compile-time error occurs. if (u.Length == 0) { return(UserDefinedConversionResult.NoApplicableOperators(u)); } // (d) Find the most specific source type SX of the operators in U... TypeSymbol sx = MostSpecificSourceTypeForImplicitUserDefinedConversion(u, source, ref useSiteDiagnostics); if ((object)sx == null) { return(UserDefinedConversionResult.NoBestSourceType(u)); } return(MostSpecificConversionOperatorForSwitchGoverningType(sx, u)); }