Skip to content

Commit

Permalink
Handle val escape for the switch expression. (#35311)
Browse files Browse the repository at this point in the history
Fixes #35278
  • Loading branch information
Neal Gafter committed Apr 30, 2019
1 parent b23b1d0 commit 6aa22bd
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 2 deletions.
14 changes: 12 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Expand Up @@ -2584,7 +2584,7 @@ private static uint GetValEscape(ImmutableArray<BoundExpression> expressions, ui

/// <summary>
/// A counterpart to the GetValEscape, which validates if given escape demand can be met by the expression.
/// The result indicates whether the escape is possible.
/// The result indicates whether the escape is possible.
/// Additionally, the method emits diagnostics (possibly more than one, recursively) that would help identify the cause for the failure.
/// </summary>
internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint escapeFrom, uint escapeTo, bool checkingReceiver, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -2884,10 +2884,20 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// only possible in error cases (if possible at all)
return false;

case BoundKind.SwitchExpression:
foreach (var arm in ((BoundSwitchExpression)expr).SwitchArms)
{
var result = arm.Value;
if (!CheckValEscape(result.Syntax, result, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
return false;
}

return true;

default:
// in error situations some unexpected nodes could make here
// returning "false" seems safer than throwing.
// we will still assert to make sure that all nodes are accounted for.
// we will still assert to make sure that all nodes are accounted for.
Debug.Assert(false, $"{expr.Kind} expression of {expr.Type} type");
return false;

Expand Down
181 changes: 181 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests3.cs
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -425,5 +426,185 @@ public static void Main()
);
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact, WorkItem(35278, "https://github.com/dotnet/roslyn/issues/35278")]
public void ValEscapeForSwitchExpression_01()
{
var source = @"
class Program
{
public enum Rainbow
{
Red,
Orange,
}
public ref struct RGBColor
{
int _r, _g, _b;
public int R => _r;
public int G => _g;
public int B => _b;
public RGBColor(int r, int g, int b)
{
_r = r;
_g = g;
_b = b;
}
public new string ToString() => $""RGBColor(0x{_r:X2}, 0x{_g:X2}, 0x{_b:X2})"";
}
static void Main(string[] args)
{
System.Console.WriteLine(FromRainbow(Rainbow.Red).ToString());
}
public static RGBColor FromRainbow(Rainbow colorBand) =>
colorBand switch
{
Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00),
Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
_ => throw null!
};
}";
var expectedOutput = "RGBColor(0xFF, 0x00, 0x00)";
var compilation = CreateCompilation(source, options: TestOptions.DebugExe);
compilation.VerifyDiagnostics(
);
var comp = CompileAndVerify(compilation, expectedOutput: expectedOutput);
}

[Fact, WorkItem(35278, "https://github.com/dotnet/roslyn/issues/35278")]
public void ValEscapeForSwitchExpression_02()
{
var source = @"
using System;
class Program
{
public ref struct RGBColor
{
public RGBColor(Span<int> span)
{
}
}
public static RGBColor FromSpan(int r, int g, int b)
{
Span<int> span = stackalloc int[] { r, g, b };
return 1 switch
{
1 => new RGBColor(span),
_ => throw null!
};
}
}";
var compilation = CreateCompilationWithMscorlibAndSpan(source, options: TestOptions.DebugDll);
compilation.VerifyDiagnostics(
// (17,18): error CS8347: Cannot use a result of 'Program.RGBColor.RGBColor(Span<int>)' in this context because it may expose variables referenced by parameter 'span' outside of their declaration scope
// 1 => new RGBColor(span),
Diagnostic(ErrorCode.ERR_EscapeCall, "new RGBColor(span)").WithArguments("Program.RGBColor.RGBColor(System.Span<int>)", "span").WithLocation(17, 18),
// (17,31): error CS8352: Cannot use local 'span' in this context because it may expose referenced variables outside of their declaration scope
// 1 => new RGBColor(span),
Diagnostic(ErrorCode.ERR_EscapeLocal, "span").WithArguments("span").WithLocation(17, 31));
}

[Fact]
public void NoRefSwitch_01()
{
var source = @"
class Program
{
ref int M(bool b, ref int x, ref int y)
{
return ref (b switch { true => x, false => y });
}
}";
var compilation = CreateCompilationWithMscorlibAndSpan(source, options: TestOptions.DebugDll);
compilation.VerifyDiagnostics(
// (6,21): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference
// return ref (b switch { true => x, false => y });
Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "b switch { true => x, false => y }").WithLocation(6, 21));
}

[Fact]
public void NoRefSwitch_02()
{
var source = @"
class Program
{
ref int M(bool b, ref int x, ref int y)
{
return ref (b switch { true => ref x, false => ref y });
}
}";
var compilation = CreateCompilationWithMscorlibAndSpan(source, options: TestOptions.DebugDll);
compilation.VerifyDiagnostics(
// (6,23): warning CS8509: The switch expression does not handle all possible inputs (it is not exhaustive).
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithLocation(6, 23),
// (6,40): error CS1525: Invalid expression term 'ref'
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_InvalidExprTerm, "ref").WithArguments("ref").WithLocation(6, 40),
// (6,40): error CS1003: Syntax error, ',' expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_SyntaxError, "ref").WithArguments(",", "ref").WithLocation(6, 40),
// (6,40): error CS1513: } expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_RbraceExpected, "ref").WithLocation(6, 40),
// (6,40): error CS1026: ) expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_CloseParenExpected, "ref").WithLocation(6, 40),
// (6,40): error CS1002: ; expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_SemicolonExpected, "ref").WithLocation(6, 40),
// (6,40): warning CS0162: Unreachable code detected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.WRN_UnreachableCode, "ref").WithLocation(6, 40),
// (6,44): error CS0118: 'x' is a variable but is used like a type
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_BadSKknown, "x").WithArguments("x", "variable", "type").WithLocation(6, 44),
// (6,45): error CS1001: Identifier expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_IdentifierExpected, ",").WithLocation(6, 45),
// (6,45): error CS8174: A declaration of a by-reference variable must have an initializer
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_ByReferenceVariableMustBeInitialized, "").WithLocation(6, 45),
// (6,47): error CS1001: Identifier expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_IdentifierExpected, "false").WithLocation(6, 47),
// (6,47): error CS1002: ; expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_SemicolonExpected, "false").WithLocation(6, 47),
// (6,47): error CS8174: A declaration of a by-reference variable must have an initializer
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_ByReferenceVariableMustBeInitialized, "").WithLocation(6, 47),
// (6,53): error CS1002: ; expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_SemicolonExpected, "=>").WithLocation(6, 53),
// (6,53): error CS1513: } expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_RbraceExpected, "=>").WithLocation(6, 53),
// (6,60): error CS0118: 'y' is a variable but is used like a type
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_BadSKknown, "y").WithArguments("y", "variable", "type").WithLocation(6, 60),
// (6,62): error CS1001: Identifier expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_IdentifierExpected, "}").WithLocation(6, 62),
// (6,62): error CS1002: ; expected
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(6, 62),
// (6,62): error CS8174: A declaration of a by-reference variable must have an initializer
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_ByReferenceVariableMustBeInitialized, "").WithLocation(6, 62),
// (6,63): error CS1519: Invalid token ')' in class, struct, or interface member declaration
// return ref (b switch { true => ref x, false => ref y });
Diagnostic(ErrorCode.ERR_InvalidMemberDecl, ")").WithArguments(")").WithLocation(6, 63),
// (8,1): error CS1022: Type or namespace definition, or end-of-file expected
// }
Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(8, 1));
}
}
}

0 comments on commit 6aa22bd

Please sign in to comment.