Skip to content

Commit

Permalink
Standard conversion scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv committed Sep 18, 2024
1 parent faf1a4b commit 616036e
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 28 deletions.
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ public Conversion ClassifyStandardConversion(TypeSymbol source, TypeSymbol desti
/// </remarks>
public Conversion ClassifyStandardConversion(BoundExpression sourceExpression, TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// TODO2
Debug.Assert(sourceExpression is null || Compilation is not null);
Debug.Assert(sourceExpression != null || (object)source != null);
Debug.Assert((object)destination != null);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -771,12 +763,6 @@ Conversion classifyConversion(TypeSymbol source, TypeSymbol destination, ref Com
}
}

private Conversion ClassifyImplicitExtensionConversion(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
// TODO2
return Conversion.NoConversion;
}

private Conversion ClassifyImplicitBuiltInConversionSlow(TypeSymbol source, TypeSymbol destination, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
{
Debug.Assert((object)source != null);
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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) :
Expand All @@ -944,6 +929,7 @@ private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion(

return impliedExplicitConversion;
}
// TODO2 resume here

#nullable enable
/// <summary>
Expand Down Expand Up @@ -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))
{
Expand Down
192 changes: 187 additions & 5 deletions src/Compilers/CSharp/Test/Emit3/ExtensionTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 = """
Expand Down Expand Up @@ -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 = """
Expand All @@ -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:
Expand Down Expand Up @@ -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<IdentifierNameSyntax>(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<IdentifierNameSyntax>(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()
{
Expand Down Expand Up @@ -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<IdentifierNameSyntax>(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<IdentifierNameSyntax>(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()
{
Expand Down

0 comments on commit 616036e

Please sign in to comment.