diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index ac1dfdfb3ec48..2e70a14ceb381 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -1754,12 +1754,11 @@ public bool HasAnyNullabilityImplicitConversion(TypeWithAnnotations source, Type ClassifyImplicitConversionFromType(source.Type, destination.Type, ref discardedUseSiteInfo).Kind != ConversionKind.NoConversion; } - public static bool HasIdentityConversionToAny(T type, ArrayBuilder targetTypes) - where T : TypeSymbol + private static bool HasIdentityConversionToAny(NamedTypeSymbol type, ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> targetTypes) { foreach (var targetType in targetTypes) { - if (HasIdentityConversionInternal(type, targetType, includeNullability: false)) + if (HasIdentityConversionInternal(type, targetType.ParticipatingType, includeNullability: false)) { return true; } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedConversions.cs index 6c28f576c6119..607f3eaedd31a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedConversions.cs @@ -2,20 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using System.Collections.Generic; +using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.CSharp { internal partial class ConversionsBase { - public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder result, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo useSiteInfo) + public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol? ConstrainedToTypeOpt)> result, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo useSiteInfo) { // CONSIDER: These sets are usually small; if they are large then this is an O(n^2) // CONSIDER: algorithm. We could use a hash table instead to build up the set. @@ -52,23 +51,18 @@ public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder interfaces = includeBaseTypes ? + typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo) : + typeParameter.EffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); + + foreach (NamedTypeSymbol iface in interfaces) { - case SpecialType.System_ValueType: - case SpecialType.System_Enum: - case SpecialType.System_Array: - case SpecialType.System_Object: - if (!excludeExisting || !HasIdentityConversionToAny(typeParameter, result)) - { - // Add the type parameter to the set as well. This will be treated equivalent to adding its - // effective interfaces to the set. We are not doing that here because we still need to know - // the originating type parameter as "constrained to" type. - result.Add(typeParameter); - } - break; + if (!excludeExisting || !HasIdentityConversionToAny(iface, result)) + { + result.Add((iface, typeParameter)); + } } } else @@ -76,13 +70,14 @@ public static void AddTypesParticipatingInUserDefinedConversion(ArrayBuilder result, bool excludeExisting, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo useSiteInfo) + static void addFromClassOrStruct(ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol? ConstrainedToTypeOpt)> result, bool excludeExisting, TypeSymbol type, bool includeBaseTypes, ref CompoundUseSiteInfo useSiteInfo) { if (type.IsClassType() || type.IsStructType()) { - if (!excludeExisting || !HasIdentityConversionToAny(type, result)) + var namedType = (NamedTypeSymbol)type; + if (!excludeExisting || !HasIdentityConversionToAny(namedType, result)) { - result.Add(type); + result.Add((namedType, null)); } } @@ -96,7 +91,7 @@ static void addFromClassOrStruct(ArrayBuilder result, bool excludeEx { if (!excludeExisting || !HasIdentityConversionToAny(t, result)) { - result.Add(t); + result.Add((t, null)); } t = t.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteInfo); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs index 8a65d6ccb5aa9..0fca2d0c998ed 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedExplicitConversions.cs @@ -33,7 +33,7 @@ private UserDefinedConversionResult AnalyzeExplicitUserDefinedConversions( // SPEC: Find the set of types D from which user-defined conversion operators // SPEC: will be considered... - var d = ArrayBuilder.GetInstance(); + 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... @@ -71,7 +71,7 @@ private UserDefinedConversionResult AnalyzeExplicitUserDefinedConversions( return UserDefinedConversionResult.Valid(u, best.Value); } - private static void ComputeUserDefinedExplicitConversionTypeSet(TypeSymbol source, TypeSymbol target, ArrayBuilder d, ref CompoundUseSiteInfo useSiteInfo) + private static void ComputeUserDefinedExplicitConversionTypeSet(TypeSymbol source, TypeSymbol target, ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> d, ref CompoundUseSiteInfo useSiteInfo) { // Spec 6.4.5: User-defined explicit conversions // Find the set of types, D, from which user-defined conversion operators will be considered. @@ -87,7 +87,7 @@ private void ComputeApplicableUserDefinedExplicitConversionSet( TypeSymbol source, TypeSymbol target, bool isChecked, - ArrayBuilder d, + ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> d, ArrayBuilder u, ref CompoundUseSiteInfo useSiteInfo) { @@ -97,30 +97,29 @@ private void ComputeApplicableUserDefinedExplicitConversionSet( Debug.Assert(d != null); Debug.Assert(u != null); - HashSet lookedInInterfaces = null; + bool haveInterfaces = false; - foreach (TypeSymbol declaringType in d) + foreach ((NamedTypeSymbol declaringType, TypeParameterSymbol constrainedToTypeOpt) in d) { - if (declaringType is TypeParameterSymbol typeParameter) + if (declaringType.IsInterface) { - ImmutableArray interfaceTypes = typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); - - if (!interfaceTypes.IsEmpty) - { - lookedInInterfaces ??= new HashSet(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, isChecked: isChecked, u, ref useSiteInfo); - } - } - } + Debug.Assert(constrainedToTypeOpt is not null); + haveInterfaces = true; } else { - addCandidatesFromType(constrainedToTypeOpt: null, (NamedTypeSymbol)declaringType, sourceExpression, source, target, isChecked: isChecked, u, ref useSiteInfo); + addCandidatesFromType(constrainedToTypeOpt: null, declaringType, sourceExpression, source, target, isChecked: isChecked, u, ref useSiteInfo); + } + } + + if (u.Count == 0 && haveInterfaces) + { + foreach ((NamedTypeSymbol declaringType, TypeParameterSymbol constrainedToTypeOpt) in d) + { + if (declaringType.IsInterface) + { + addCandidatesFromType(constrainedToTypeOpt: constrainedToTypeOpt, declaringType, sourceExpression, source, target, isChecked: isChecked, u, ref useSiteInfo); + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs index 505af4088a16b..581f6d09e1493 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs @@ -77,7 +77,7 @@ private UserDefinedConversionResult AnalyzeImplicitUserDefinedConversions( // 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.GetInstance(); + var d = ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)>.GetInstance(); ComputeUserDefinedImplicitConversionTypeSet(source, target, d, ref useSiteInfo); // SPEC: Find the set of applicable user-defined and lifted conversion operators, U... @@ -115,7 +115,7 @@ private UserDefinedConversionResult AnalyzeImplicitUserDefinedConversions( return UserDefinedConversionResult.Valid(u, best.Value); } - private static void ComputeUserDefinedImplicitConversionTypeSet(TypeSymbol s, TypeSymbol t, ArrayBuilder d, ref CompoundUseSiteInfo useSiteInfo) + private static void ComputeUserDefinedImplicitConversionTypeSet(TypeSymbol s, TypeSymbol t, ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> d, ref CompoundUseSiteInfo useSiteInfo) { // Spec 6.4.4: User-defined implicit conversions // Find the set of types D from which user-defined conversion operators @@ -143,7 +143,7 @@ private void ComputeApplicableUserDefinedImplicitConversionSet( BoundExpression sourceExpression, TypeSymbol source, TypeSymbol target, - ArrayBuilder d, + ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)> d, ArrayBuilder u, ref CompoundUseSiteInfo useSiteInfo, bool allowAnyTarget = false) @@ -245,30 +245,29 @@ private void ComputeApplicableUserDefinedImplicitConversionSet( return; } - HashSet lookedInInterfaces = null; + bool haveInterfaces = false; - foreach (TypeSymbol declaringType in d) + foreach ((NamedTypeSymbol declaringType, TypeParameterSymbol constrainedToTypeOpt) in d) { - if (declaringType is TypeParameterSymbol typeParameter) + if (declaringType.IsInterface) { - ImmutableArray interfaceTypes = typeParameter.AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); - - if (!interfaceTypes.IsEmpty) - { - lookedInInterfaces ??= new HashSet(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); - } - } - } + Debug.Assert(constrainedToTypeOpt is not null); + haveInterfaces = true; } else { - addCandidatesFromType(constrainedToTypeOpt: null, (NamedTypeSymbol)declaringType, sourceExpression, source, target, u, ref useSiteInfo, allowAnyTarget); + addCandidatesFromType(constrainedToTypeOpt: null, declaringType, sourceExpression, source, target, u, ref useSiteInfo, allowAnyTarget); + } + } + + if (u.Count == 0 && haveInterfaces) + { + foreach ((NamedTypeSymbol declaringType, TypeParameterSymbol constrainedToTypeOpt) in d) + { + if (declaringType.IsInterface) + { + addCandidatesFromType(constrainedToTypeOpt: constrainedToTypeOpt, declaringType, sourceExpression, source, target, u, ref useSiteInfo, allowAnyTarget); + } } } @@ -944,7 +943,7 @@ protected UserDefinedConversionResult AnalyzeImplicitUserDefinedConversionForV6S // 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.GetInstance(); + var d = ArrayBuilder<(NamedTypeSymbol ParticipatingType, TypeParameterSymbol ConstrainedToTypeOpt)>.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, diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs index f2b2ea25a8e1d..72c230053444e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeParameterSymbol.cs @@ -290,6 +290,18 @@ internal ImmutableArray EffectiveInterfacesNoUseSiteDiagnostics } } + internal ImmutableArray EffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref CompoundUseSiteInfo useSiteInfo) + { + var result = EffectiveInterfacesNoUseSiteDiagnostics; + + foreach (var iface in result) + { + iface.OriginalDefinition.AddUseSiteInfo(ref useSiteInfo); + } + + return result; + } + /// /// The most encompassed type (spec 6.4.2) from the constraints. /// diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs index 3a8d4ed9bd809..bb1b865168afd 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs @@ -14137,7 +14137,7 @@ private static bool Execute(bool isVirtual) } #endif - return true; + return ExecutionConditionUtil.IsMonoOrCoreClr; } [ConditionalTheory(typeof(CoreClrOnly))] @@ -29905,7 +29905,7 @@ static C1 M03(int y) where T : I1> [CombinatorialData] public void ConsumeAbstractConversionOperator_10([CombinatorialValues("implicit", "explicit")] string op) { - // Look in derived interfaces + // Look in base interfaces for source string metadataName = ConversionOperatorName(op); bool needCast = op == "explicit"; @@ -29953,13 +29953,16 @@ .locals init (int V_0) } [Theory] - [CombinatorialData] - public void ConsumeAbstractConversionOperator_11([CombinatorialValues("implicit", "explicit")] string op) + [InlineData("implicit", false)] + [InlineData("implicit", true)] + [InlineData("explicit", false)] + [InlineData("explicit", true)] + public void ConsumeAbstractConversionOperator_11(string op, bool useCast) { // Same as ConsumeAbstractConversionOperator_10 only direction of conversion is flipped + // Look in base interfaces for destination for explicit cast in code string metadataName = ConversionOperatorName(op); - bool needCast = op == "explicit"; var source1 = @" @@ -29975,7 +29978,7 @@ class Test { static T M02(int x) where T : U where U : I2 { - return " + (needCast ? "(T)" : "") + @"x; + return " + (useCast ? "(T)" : "") + @"x; } } "; @@ -29983,10 +29986,12 @@ static T M02(int x) where T : U where U : I2 parseOptions: TestOptions.RegularPreview, targetFramework: _supportingFramework); - var verifier = CompileAndVerify(compilation1, verify: Verification.Skipped).VerifyDiagnostics(); + if (useCast) + { + var verifier = CompileAndVerify(compilation1, verify: Verification.Skipped).VerifyDiagnostics(); - verifier.VerifyIL("Test.M02(int)", -@" + verifier.VerifyIL("Test.M02(int)", + @" { // Code size 18 (0x12) .maxstack 1 @@ -30001,6 +30006,15 @@ .locals init (T V_0) IL_0011: ret } "); + } + else + { + compilation1.VerifyDiagnostics( + // (14,16): error CS0266: Cannot implicitly convert type 'int' to 'T'. An explicit conversion exists (are you missing a cast?) + // return x; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "x").WithArguments("int", "T").WithLocation(14, 16) + ); + } } [Theory] @@ -30146,7 +30160,7 @@ .locals init (T V_0) [CombinatorialData] public void ConsumeAbstractConversionOperator_15([CombinatorialValues("implicit", "explicit")] string op) { - // If there is a non-trivial class constraint, interfaces are not looked at. + // If there is an applicable candidate in effective base class, interfaces are not looked at. string metadataName = ConversionOperatorName(op); bool needCast = op == "explicit"; @@ -30248,7 +30262,7 @@ .locals init (T V_0) [CombinatorialData] public void ConsumeAbstractConversionOperator_17([CombinatorialValues("implicit", "explicit")] string op) { - // If there is a non-trivial class constraint, interfaces are not looked at. + // If there is no applicable candidate in effective base class, look in interfaces. bool needCast = op == "explicit"; @@ -30274,16 +30288,32 @@ static int M03(T y) where T : U where U : I1 { return " + (needCast ? "(int)" : "") + @"y; } + + static void Main() + { + var c2 = new C2(); + M02(c2); + M03(c2); + } +} + +public class C2 : C1, I1 +{ + public static " + op + @" operator int(C2 x) + { + System.Console.WriteLine(""C2 conversion""); + return default; + } } "; - var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview, targetFramework: _supportingFramework); - compilation1.VerifyDiagnostics( - // (15,16): error CS0030: Cannot convert type 'T' to 'int' - // return (int)x; - Diagnostic((op == "explicit" ? ErrorCode.ERR_NoExplicitConv : ErrorCode.ERR_NoImplicitConv), (needCast ? "(int)" : "") + "x").WithArguments("T", "int").WithLocation(15, 16) - ); + + CompileAndVerify(compilation1, verify: Verification.Skipped, expectedOutput: !Execute(isVirtual: false) ? null : @" +C2 conversion +C2 conversion +").VerifyDiagnostics(); } [Theory] @@ -30316,16 +30346,84 @@ static T M03(int y) where T : U where U : I1 { return " + (needCast ? "(T)" : "") + @"y; } + + static void Main() + { + M02(0); + M03(1); + } +} + +public class C2 : C1, I1 +{ + public static " + op + @" operator C2(int x) + { + System.Console.WriteLine(""C2 conversion""); + return default; + } } "; - var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview, targetFramework: _supportingFramework); - compilation1.VerifyDiagnostics( - // (15,16): error CS0030: Cannot convert type 'int' to 'T' - // return (T)x; - Diagnostic((op == "explicit" ? ErrorCode.ERR_NoExplicitConv : ErrorCode.ERR_NoImplicitConv), (needCast ? "(T)" : "") + "x").WithArguments("int", "T").WithLocation(15, 16) - ); + + CompileAndVerify(compilation1, verify: Verification.Skipped, expectedOutput: !Execute(isVirtual: false) ? null : @" +C2 conversion +C2 conversion +").VerifyDiagnostics(); + } + + [Fact] + [WorkItem(56753, "https://github.com/dotnet/roslyn/issues/56753")] + public void ConsumeAbstractConversionOperator_19() + { + var source1 = +@" +interface I1 { } + +abstract class Base : I1 {} + +interface I2 where T : class, I2 +{ + public static abstract implicit operator T(string value); +} + +class Derived : Base, I2 +{ + public static implicit operator Derived(string value) + { + System.Console.WriteLine(""Derived conversion""); + return default; + } +} + +static class Util +{ + static void Method1(string value) where T : Base, I2 + { + var newT = (T)value; + } + + static void Method2(string value) where T : class, I1, I2 + { + var newT = (T)value; + } + + static void Main() + { + Method1(""""); + Method2(""""); + } +} +"; + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugExe, + parseOptions: TestOptions.RegularPreview, + targetFramework: _supportingFramework); + + CompileAndVerify(compilation1, verify: Verification.Skipped, expectedOutput: !Execute(isVirtual: false) ? null : @" +Derived conversion +Derived conversion +").VerifyDiagnostics(); } [Theory]