From 616036e68736d1b031c9c7cce5b24b01950ad38f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Wed, 18 Sep 2024 14:11:03 -0700 Subject: [PATCH] Standard conversion scenarios --- .../Portable/Binder/Binder_Conversions.cs | 4 +- .../Semantics/Conversions/ConversionsBase.cs | 28 +-- .../CSharp/Test/Emit3/ExtensionTypeTests.cs | 192 +++++++++++++++++- 3 files changed, 196 insertions(+), 28 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs index 4a13ab0fc93be..186dd2d3d1030 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs @@ -363,11 +363,11 @@ void ensureAllUnderlyingConversionsChecked(SyntaxNode syntax, BoundExpression so conversion.UnderlyingConversions[0].AssertUnderlyingConversionsChecked(); conversion.MarkUnderlyingConversionsChecked(); } - else if (source.Type?.IsNullableType() == true) + else if (source.Type?.IsNullableType(includeExtensions: true) == true) { _ = CreateConversion( syntax, - new BoundValuePlaceholder(source.Syntax, source.Type.GetNullableUnderlyingType()), + new BoundValuePlaceholder(source.Syntax, source.Type.GetNullableUnderlyingType(includeExtensions: true)), conversion.UnderlyingConversions[0], isCast: false, conversionGroupOpt: null, diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs index 5be210a1eab33..40efbf889130e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs @@ -548,7 +548,6 @@ public Conversion ClassifyStandardConversion(TypeSymbol source, TypeSymbol desti /// public Conversion ClassifyStandardConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { - // TODO2 Debug.Assert(sourceExpression is null || Compilation is not null); Debug.Assert(sourceExpression != null || (object)source != null); Debug.Assert((object)destination != null); @@ -728,13 +727,6 @@ Conversion classifyConversion(TypeSymbol source, TypeSymbol destination, ref Com return nullableConversion; } - // TODO2 - var extensionConversion = ClassifyImplicitExtensionConversion(source, destination, ref useSiteInfo); - if (extensionConversion.Exists) - { - return extensionConversion; - } - if (source is FunctionTypeSymbol) { Debug.Assert(false); @@ -771,12 +763,6 @@ Conversion classifyConversion(TypeSymbol source, TypeSymbol destination, ref Com } } - private Conversion ClassifyImplicitExtensionConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) - { - // TODO2 - return Conversion.NoConversion; - } - private Conversion ClassifyImplicitBuiltInConversionSlow(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo useSiteInfo) { Debug.Assert((object)source != null); @@ -836,7 +822,6 @@ private Conversion ClassifyExplicitBuiltInOnlyConversion(TypeSymbol source, Type return Conversion.ExplicitEnumeration; } - // TODO2 var nullableConversion = ClassifyExplicitNullableConversion(source, destination, isChecked: isChecked, ref useSiteInfo, forCast); if (nullableConversion.Exists) { @@ -926,11 +911,11 @@ private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion( break; case ConversionKind.ImplicitNullable: - var strippedSource = source.StrippedType(); - var strippedDestination = destination.StrippedType(); + var strippedSource = source.StrippedType(includeExtensions: true); + var strippedDestination = destination.StrippedType(includeExtensions: true); var underlyingConversion = DeriveStandardExplicitFromOppositeStandardImplicitConversion(strippedSource, strippedDestination, ref useSiteInfo); - // the opposite underlying conversion may not exist + // the opposite underlying conversion may not exist // for example if underlying conversion is implicit tuple impliedExplicitConversion = underlyingConversion.Exists ? Conversion.MakeNullableConversion(ConversionKind.ExplicitNullable, underlyingConversion) : @@ -944,6 +929,7 @@ private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion( return impliedExplicitConversion; } + // TODO2 resume here #nullable enable /// @@ -2458,13 +2444,13 @@ private Conversion ClassifyExplicitNullableConversion(TypeSymbol source, TypeSym // SPEC: An explicit conversion from S to T?. // SPEC: An explicit conversion from S? to T. - if (!source.IsNullableType() && !destination.IsNullableType()) + if (!source.IsNullableType(includeExtensions: true) && !destination.IsNullableType(includeExtensions: true)) { return Conversion.NoConversion; } - TypeSymbol unwrappedSource = source.StrippedType(); - TypeSymbol unwrappedDestination = destination.StrippedType(); + TypeSymbol unwrappedSource = source.StrippedType(includeExtensions: true); + TypeSymbol unwrappedDestination = destination.StrippedType(includeExtensions: true); if (HasIdentityConversionInternal(unwrappedSource, unwrappedDestination)) { diff --git a/src/Compilers/CSharp/Test/Emit3/ExtensionTypeTests.cs b/src/Compilers/CSharp/Test/Emit3/ExtensionTypeTests.cs index 7a27265e9830d..6bffcb3ba7460 100644 --- a/src/Compilers/CSharp/Test/Emit3/ExtensionTypeTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/ExtensionTypeTests.cs @@ -51984,7 +51984,7 @@ public void Conversion_ImplicitTuple_NestedImplicitConversions() } [Fact] - public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_ToExtension() + public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_ToNullableOfExtension() { // Spec: For each of the predefined implicit or explicit conversions that convert // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: @@ -52041,12 +52041,11 @@ public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_ToE } [Fact] - public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_FromExtension() + public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_FromNullableOfExtension() { // Spec: For each of the predefined implicit or explicit conversions that convert // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: - // TODO2 broken, resume here // TODO2 There may be a spec issue (implicit tuple conversion should also cover expressions of tuple type) // - An implicit or explicit conversion from S? to T? var src = """ @@ -52104,7 +52103,6 @@ public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_Fro // Spec: For each of the predefined implicit or explicit conversions that convert // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: - // TODO2 resume here // TODO2 this is weird // - An implicit or explicit conversion from S? to T? var src = """ @@ -52130,7 +52128,7 @@ public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_Fro } [Fact] - public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_FromExtensionOfNullable_ToExtension() + public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_FromExtensionOfNullable_ToNullableOfExtension() { // Spec: For each of the predefined implicit or explicit conversions that convert // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: @@ -52159,6 +52157,96 @@ public void Conversion_ImplicitNullable_ImplicitTuple_FromNullableToNullable_Fro Assert.Equal(ConversionKind.ImplicitReference, conversion.UnderlyingConversions[0].UnderlyingConversions[1].Kind); } + [Fact] + public void Conversion_ExplicitNullable_ExplicitTuple_FromNullableToNullable_FromExtensionOfNullable_ToNullableOfExtension() + { + // Spec: For each of the predefined implicit or explicit conversions that convert + // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: + + // - An implicit or explicit conversion from S? to T? + var src = """ +E1 e1 = (1, "ran"); +E2? e2 = e1; +e2.Value.Print(); + +public explicit extension E1 for (long, object)? { } +public explicit extension E2 for (int, string) { public void Print() { System.Console.Write(this); } } +"""; + + var comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (2,10): error CS0266: Cannot implicitly convert type 'E1' to 'E2?'. An explicit conversion exists (are you missing a cast?) + // E2? e2 = e1; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "e1").WithArguments("E1", "E2?").WithLocation(2, 10)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var expr = GetSyntax(tree, "e1"); + var conversion = model.GetConversion(expr); + Assert.Equal(ConversionKind.ExplicitNullable, conversion.Kind); + Assert.Equal(ConversionKind.ExplicitTuple, conversion.UnderlyingConversions[0].Kind); + Assert.Equal(ConversionKind.ExplicitNumeric, conversion.UnderlyingConversions[0].UnderlyingConversions[0].Kind); + Assert.Equal(ConversionKind.ExplicitReference, conversion.UnderlyingConversions[0].UnderlyingConversions[1].Kind); + + src = """ +E1 e1 = (1, "ran"); +E2? e2 = (E2?)e1; +e2.Value.Print(); + +public explicit extension E1 for (long, object)? { } +public explicit extension E2 for (int, string) { public void Print() { System.Console.Write(this); } } +"""; + + comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """(1, ran)""").VerifyDiagnostics(); + } + + [Fact] + public void Conversion_ExplicitNullable_ExplicitTuple_FromNullableToNullable_FromNullableOfExtension_ToExtensionOfNullable() + { + // Spec: For each of the predefined implicit or explicit conversions that convert + // from a non-nullable value type S to a non-nullable value type T, the following nullable conversions exist: + + // - An implicit or explicit conversion from S? to T? + var src = """ +E1? e1 = (1, "ran"); +E2 e2 = e1; +e2.Print(); + +public explicit extension E1 for (long, object) { } +public explicit extension E2 for (int, string)? { public void Print() { System.Console.Write(this); } } +"""; + + var comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (2,9): error CS0266: Cannot implicitly convert type 'E1?' to 'E2'. An explicit conversion exists (are you missing a cast?) + // E2 e2 = e1; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "e1").WithArguments("E1?", "E2").WithLocation(2, 9)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var expr = GetSyntax(tree, "e1"); + var conversion = model.GetConversion(expr); + Assert.Equal(ConversionKind.ExplicitNullable, conversion.Kind); + Assert.Equal(ConversionKind.ExplicitTuple, conversion.UnderlyingConversions[0].Kind); + Assert.Equal(ConversionKind.ExplicitNumeric, conversion.UnderlyingConversions[0].UnderlyingConversions[0].Kind); + Assert.Equal(ConversionKind.ExplicitReference, conversion.UnderlyingConversions[0].UnderlyingConversions[1].Kind); + + src = """ +E1? e1 = (1, "ran"); +E2 e2 = (E2)e1; +e2.Print(); + +public explicit extension E1 for (long, object) { } +public explicit extension E2 for (int, string)? { public void Print() { System.Console.Write(this); } } +"""; + + comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """(1, ran)""").VerifyDiagnostics(); + } + [Fact] public void Conversion_ImplicitNullable_ImplicitTuple_ToNullable() { @@ -52229,6 +52317,100 @@ public void Conversion_ExplicitNullable_ImplicitTuple_FromNullable() CompileAndVerify(comp, expectedOutput: "(1, ran)"); } + [Fact] + public void Conversion_ExplicitStandard_FromNullableToNullable_FromExtensionOfNullable() + { + var src = """ +E e = 42; +C c = e; + +public explicit extension E for long? { } +public class C +{ + public int? field; + public static implicit operator C(int? x) => new C() { field = x }; +} +"""; + + var comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (2,7): error CS0266: Cannot implicitly convert type 'E' to 'C'. An explicit conversion exists (are you missing a cast?) + // C c = e; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "e").WithArguments("E", "C").WithLocation(2, 7)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var expr = GetSyntax(tree, "e"); + var conversion = model.GetConversion(expr); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.ExplicitNullable, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); + + src = """ +E e = 42; +C c = (C)e; +System.Console.Write(c.field); + +public explicit extension E for long? { } +public class C +{ + public int? field; + public static implicit operator C(int? x) => new C() { field = x }; +} +"""; + + comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """42""").VerifyDiagnostics(); + } + + [Fact] + public void Conversion_ExplicitStandard_FromNullableToNullable_ToExtensionOfNullable() + { + var src = """ +C c = new C() { field = 42 }; +E e = c; + +public explicit extension E for int? { } +public class C +{ + public long? field; + public static implicit operator long?(C x) => x.field; +} +"""; + + var comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics( + // (2,7): error CS0266: Cannot implicitly convert type 'C' to 'E'. An explicit conversion exists (are you missing a cast?) + // E e = c; + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "c").WithArguments("C", "E").WithLocation(2, 7)); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + var expr = GetSyntax(tree, "c"); + var conversion = model.GetConversion(expr); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.ExplicitNullable, conversion.UserDefinedToConversion.Kind); + + src = """ +C c = new C() { field = 42 }; +E e = (E)c; +System.Console.Write(e); + +public explicit extension E for int? { } +public class C +{ + public long? field; + public static implicit operator long?(C x) => x.field; +} +"""; + + comp = CreateCompilation([src, ExtensionErasureAttributeDefinition]); + comp.VerifyEmitDiagnostics(); + CompileAndVerify(comp, expectedOutput: """42""").VerifyDiagnostics(); + } + [Fact] public void Conversion_HasImplicitEnumerationConversion() {