Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,6 @@ private Conversion ClassifyConversionFromTypeForCast(TypeSymbol source, TypeSymb
//
// fail.

// PROTOTYPE: Note, an explicit user-defined conversion may come before a union conversion in this case.
// Confirm that this is acceptable.
var conversion = GetExplicitUserDefinedConversion(source, destination, isChecked: isChecked, ref useSiteInfo);
if (conversion.Exists)
{
Expand Down Expand Up @@ -791,7 +789,6 @@ private Conversion GetImplicitUserDefinedOrUnionConversion(BoundExpression sourc
return result;
}

// PROTOTYPE: Confirm that union conversions are considered after user-defined conversions.
Conversion unionConversion = AnalyzeImplicitUnionConversions(sourceExpression, source, destination, ref useSiteInfo);

if (unionConversion.Exists)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -991,8 +991,6 @@ private Conversion AnalyzeImplicitUnionConversions(
Debug.Assert(sourceExpression != null || (object)source != null);
Debug.Assert((object)target != null);

// PROTOTYPE: It might make sense to block conversions from a base class or any interface. See comments in tests.

if (target.StrippedType() is not NamedTypeSymbol namedTarget || !namedTarget.IsUnionTypeWithUseSiteDiagnostics(ref useSiteInfo))
{
return Conversion.NoConversion;
Expand Down
8 changes: 5 additions & 3 deletions src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,12 @@ protected BoundExpression ConvertCaseExpression(CSharpSyntaxNode node, BoundExpr
hasErrors = true;
}

caseExpression = CreateConversion(caseExpression, conversion, SwitchGoverningType, diagnostics);
if (!conversion.IsUnion)
{
caseExpression = CreateConversion(caseExpression, conversion, SwitchGoverningType, diagnostics);
}
}

// PROTOTYPE: Test that consumer actually does the right thing when a union matching is involved. Cover error scenarios as well.
var inputType = SwitchGoverningType;
NamedTypeSymbol unionType = PrepareForUnionMatchingIfAppropriateAndReturnUnionType(node, ref inputType, diagnostics);
return ConvertPatternExpression(unionType, inputType, node, caseExpression, out constantValueOpt, hasErrors, diagnostics, out _);
Expand Down Expand Up @@ -433,7 +435,7 @@ private BoundExpression BindSwitchGoverningExpression(BindingDiagnosticBag diagn
if (conversion.IsValid)
{
// Condition (2) satisfied
Debug.Assert(conversion.Kind == ConversionKind.ImplicitUserDefined); // PROTOTYPE: Follow up
Debug.Assert(conversion.Kind == ConversionKind.ImplicitUserDefined);
Debug.Assert(conversion.Method.IsUserDefinedConversion());
Debug.Assert(conversion.UserDefinedToConversion.IsIdentity);
Debug.Assert(resultantGoverningType.IsValidV6SwitchGoverningType(isTargetTypeOfUserDefinedOp: true));
Expand Down
2 changes: 0 additions & 2 deletions src/Compilers/CSharp/Portable/Symbols/NamedTypeSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1843,8 +1843,6 @@ internal ImmutableArray<TypeSymbol> UnionCaseTypes
internal static bool IsSuitableUnionConstructor(MethodSymbol ctor)
{
Debug.Assert(ctor.MethodKind is MethodKind.Constructor);
// PROTOTYPE: Confirm RefKind restriction. Conversion operators allow only RefKind.None or RefKind.In.
// It feels like it makes sense to use the same restriction here.
return ctor is { DeclaredAccessibility: Accessibility.Public, ParameterCount: 1, Parameters: [{ RefKind: RefKind.In or RefKind.None }] };
}

Expand Down
178 changes: 156 additions & 22 deletions src/Compilers/CSharp/Test/CSharp15/UnionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,160 @@ .maxstack 2
");
}

[Fact]
public void UnionMatching_36_SwitchStatement()
{
var src = @"
struct S1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}

class Program
{
static void Main()
{
System.Console.Write(Test1(new S1(10)));
System.Console.Write(Test1(new S1(""10"")));
System.Console.Write(Test1(default));
System.Console.Write(Test1(new S1(11)));
System.Console.Write(Test1(new S1(""11"")));
System.Console.Write(Test1(new S1(0)));
}

static int Test1(S1 u)
{
switch (u)
{
case 10: return 1;
case ""11"": return 2;
}

return -1;
}
}
";
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "1-1-1-12-1").VerifyDiagnostics();
}

[Fact]
public void UnionMatching_37_SwitchStatement()
{
var src = @"
struct S1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}

class Program
{
static void Main()
{
System.Console.Write(Test1(new S1(10)));
System.Console.Write(Test1(new S1(""10"")));
System.Console.Write(Test1(default));
System.Console.Write(Test1(new S1(11)));
System.Console.Write(Test1(new S1(""11"")));
System.Console.Write(Test1(new S1(0)));
}

static int Test1(S1 u)
{
switch (u)
{
case 10: goto case 44;
case ""11"": goto case ""55"";
case 44: return 44;
case ""55"": return 55;
}

return -1;
}
}
";
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
CompileAndVerify(comp, expectedOutput: "44-1-1-155-1").VerifyDiagnostics();
}

[Fact]
public void UnionMatching_38_SwitchStatement()
{
var src = @"
struct S1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}

class Program
{
static int Test1(S1 u)
{
switch (u)
{
case 10: return 1;
case ""11"": return 2;
case true: return 3;
}

return -1;
}
}
";
var comp = CreateCompilation([src, IUnionSource]);
comp.VerifyDiagnostics(
// (18,18): error CS8121: An expression of type 'S1' cannot be handled by a pattern of type 'bool'.
// case true: return 3;
Diagnostic(ErrorCode.ERR_PatternWrongType, "true").WithArguments("S1", "bool").WithLocation(18, 18)
);
}

[Fact]
public void UnionMatching_39_SwitchStatement()
{
var src = @"
struct S1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}

class Program
{
static int Test1(S1 u)
{
switch (u)
{
case 10: goto case true;
case ""11"": return 2;
}

return -1;
}
}
";
var comp = CreateCompilation([src, IUnionSource]);
comp.VerifyDiagnostics(
// (16,13): error CS0163: Control cannot fall through from one case label ('case 10:') to another
// case 10: goto case true;
Diagnostic(ErrorCode.ERR_SwitchFallThrough, "case 10:").WithArguments("case 10:").WithLocation(16, 13),
// (16,22): error CS0029: Cannot implicitly convert type 'bool' to 'S1'
// case 10: goto case true;
Diagnostic(ErrorCode.ERR_NoImplicitConv, "goto case true;").WithArguments("bool", "S1").WithLocation(16, 22)
);
}

[Fact]
public void PatternWrongType_TypePattern_01_BindConstantPatternWithFallbackToTypePattern_UnionType_Out_UnionType_In()
{
Expand Down Expand Up @@ -3148,7 +3302,8 @@ static void Test1(S1 u)
// case 1:
Diagnostic(ErrorCode.ERR_SwitchFallOut, "case 1:").WithArguments("case 1:").WithLocation(102, 13),

// PROTOTYPE: This doesn't look like a union matching error. Something is likely missing in implementation.
// The following error is expected per language specification (https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/statements.md#13104-the-goto-statement):
// "if the constant_expression is not implicitly convertible (§10.2) to the governing type of the nearest enclosing switch statement, a compile-time error occurs."

// (103,17): error CS0029: Cannot implicitly convert type 'string' to 'S1'
// goto case empty;
Expand Down Expand Up @@ -5222,8 +5377,6 @@ static S1 Test1(int? x)
";
var comp = CreateCompilation([src, IUnionSource]);

// PROTOTYPE: Confirm that there are no lifted forms.

comp.VerifyDiagnostics(
// (20,16): error CS0029: Cannot implicitly convert type 'int?' to 'S1'
// return x;
Expand Down Expand Up @@ -5781,11 +5934,6 @@ static S1 Test2(System.ValueType x)
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "System.ValueType S1").VerifyDiagnostics();

// PROTOTYPE: Confirm that we are fine with this conversion behavior. See previous test as well.
// Cast performs unboxing conversion, but implicit conversion performs union conversion.
// Might be too confusing.
// Note, language disallows user-defined conversions like that, see errors below.

verifier.VerifyIL("Program.Test2", @"
{
// Code size 7 (0x7)
Expand Down Expand Up @@ -5889,11 +6037,6 @@ static S1 Test2(I1 x)
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "I1 S1").VerifyDiagnostics();

// PROTOTYPE: Confirm that we are fine with this conversion behavior. See previous test as well.
// Cast performs unboxing conversion, but implicit conversion performs union conversion.
// Might be too confusing.
// Note, language disallows user-defined conversions like that, see errors below.

verifier.VerifyIL("Program.Test2", @"
{
// Code size 7 (0x7)
Expand Down Expand Up @@ -5968,10 +6111,6 @@ static S1 Test2(I1 x)
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "I1 S1 I1 S1").VerifyDiagnostics();

// PROTOTYPE: Confirm that we are fine with this conversion behavior.
// Might be too confusing.
// Note, language disallows user-defined conversions like that, see errors below.

verifier.VerifyIL("Program.Test1", @"
{
// Code size 7 (0x7)
Expand Down Expand Up @@ -6054,11 +6193,6 @@ static S1 Test2(I1 x)
var comp = CreateCompilation([src, IUnionSource], options: TestOptions.ReleaseExe);
var verifier = CompileAndVerify(comp, expectedOutput: "I1 S1").VerifyDiagnostics();

// PROTOTYPE: Confirm that we are fine with this conversion behavior.
// Cast performs castclass conversion, but implicit conversion performs union conversion.
// Might be too confusing.
// Note, language disallows user-defined conversions like that, see errors below.

verifier.VerifyIL("Program.Test1", @"
{
// Code size 7 (0x7)
Expand Down
Loading