提交 b26fc0dc 编写于 作者: V vsadov

Ternary ref branches must have matching ref-escapes

上级 3c7e7ac9
...@@ -1518,15 +1518,14 @@ internal static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint ...@@ -1518,15 +1518,14 @@ internal static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint
case BoundKind.ConditionalOperator: case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr; var conditional = (BoundConditionalOperator)expr;
// byref conditional defers to its operands // byref conditional defers to one operand. Since are here without errors, both operands must match.
if (conditional.IsByRef && if (conditional.IsByRef &&
(CheckRefEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) && CheckRefEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
CheckRefEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics)))
{ {
return true; return true;
} }
// reprot standard lvalue error // report standard lvalue error
break; break;
case BoundKind.FieldAccess: case BoundKind.FieldAccess:
...@@ -1691,14 +1690,13 @@ internal static uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainin ...@@ -1691,14 +1690,13 @@ internal static uint GetRefEscape(BoundExpression expr, uint scopeOfTheContainin
case BoundKind.ConditionalOperator: case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr; var conditional = (BoundConditionalOperator)expr;
// byref conditional defers to its operands // byref conditional defers to one operand. Since are here without errors, both operands must match.
if (conditional.IsByRef) if (conditional.IsByRef)
{ {
return Math.Max(GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression), return GetRefEscape(conditional.Consequence, scopeOfTheContainingExpression);
GetRefEscape(conditional.Alternative, scopeOfTheContainingExpression));
} }
// report standard lvalue error // otherwise it is an RValue
break; break;
case BoundKind.FieldAccess: case BoundKind.FieldAccess:
......
...@@ -3524,7 +3524,7 @@ private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node ...@@ -3524,7 +3524,7 @@ private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node
{ {
diagnostics.Add(ErrorCode.ERR_RefConditionalNeedsTwoRefs, whenTrue.GetFirstToken().GetLocation()); diagnostics.Add(ErrorCode.ERR_RefConditionalNeedsTwoRefs, whenTrue.GetFirstToken().GetLocation());
} }
} }
BoundExpression condition = BindBooleanExpression(node.Condition, diagnostics); BoundExpression condition = BindBooleanExpression(node.Condition, diagnostics);
...@@ -3636,6 +3636,32 @@ private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node ...@@ -3636,6 +3636,32 @@ private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node
} }
} }
if (!hasErrors && isRef)
{
var currentScope = this.LocalScopeDepth;
// ref-escape must agree on both branches.
var whenTrueEscape = GetRefEscape(trueExpr, currentScope);
var whenFalseEscape = GetRefEscape(falseExpr, currentScope);
if (whenTrueEscape != whenFalseEscape)
{
// ask the one with narrower escape, for the wider - hopefully the errors will make the violation easier to fix.
if (whenTrueEscape < whenFalseEscape)
{
CheckRefEscape(falseExpr.Syntax, falseExpr, currentScope, whenTrueEscape, checkingReceiver: false, diagnostics: diagnostics);
}
else
{
CheckRefEscape(trueExpr.Syntax, trueExpr, currentScope, whenFalseEscape, checkingReceiver: false, diagnostics: diagnostics);
}
diagnostics.Add(ErrorCode.ERR_MismatchedRefEscapeInTernary, node.Location);
hasErrors = true;
}
}
ConstantValue constantValue = null; ConstantValue constantValue = null;
if (!hasErrors) if (!hasErrors)
......
...@@ -6307,6 +6307,15 @@ internal class CSharpResources { ...@@ -6307,6 +6307,15 @@ internal class CSharpResources {
} }
} }
/// <summary>
/// Looks up a localized string similar to Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes..
/// </summary>
internal static string ERR_MismatchedRefEscapeInTernary {
get {
return ResourceManager.GetString("ERR_MismatchedRefEscapeInTernary", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Argument missing. /// Looks up a localized string similar to Argument missing.
/// </summary> /// </summary>
......
...@@ -4867,7 +4867,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ ...@@ -4867,7 +4867,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
</data> </data>
<data name="ERR_CallArgMixing" xml:space="preserve"> <data name="ERR_CallArgMixing" xml:space="preserve">
<value>This combination of arguments to '{0}' is disallowed because it may expose variables referenced by parameter '{1}' outside of their declaration scope</value> <value>This combination of arguments to '{0}' is disallowed because it may expose variables referenced by parameter '{1}' outside of their declaration scope</value>
</data> </data>
<data name="ERR_MismatchedRefEscapeInTernary" xml:space="preserve">
<value>Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes.</value>
</data>
<data name="ERR_InitializeByValueVariableWithReference" xml:space="preserve"> <data name="ERR_InitializeByValueVariableWithReference" xml:space="preserve">
<value>Cannot initialize a by-value variable with a reference</value> <value>Cannot initialize a by-value variable with a reference</value>
</data> </data>
......
...@@ -1531,6 +1531,7 @@ internal enum ErrorCode ...@@ -1531,6 +1531,7 @@ internal enum ErrorCode
ERR_EscapeCall2 = 8522, ERR_EscapeCall2 = 8522,
ERR_EscapeOther = 8523, ERR_EscapeOther = 8523,
ERR_CallArgMixing = 8524, ERR_CallArgMixing = 8524,
ERR_EscapeLocal = 8525, ERR_MismatchedRefEscapeInTernary = 8525,
ERR_EscapeLocal = 8526,
} }
} }
...@@ -941,5 +941,101 @@ static S1 MayWrap(in int arg) ...@@ -941,5 +941,101 @@ static S1 MayWrap(in int arg)
Diagnostic(ErrorCode.ERR_CallArgMixing, "MayAssign(ref rOuter)").WithArguments("Program.MayAssign(ref Program.S1, in int)", "arg2").WithLocation(16, 13) Diagnostic(ErrorCode.ERR_CallArgMixing, "MayAssign(ref rOuter)").WithArguments("Program.MayAssign(ref Program.S1, in int)", "arg2").WithLocation(16, 13)
); );
} }
[Fact()]
public void MismatchedRefTernaryEscape()
{
var text = @"
class Program
{
static void Main()
{
}
public static int field = 1;
void Test1()
{
var local = 42;
ref var ternary1 = ref true ? ref field : ref local;
ref var lr = ref local;
ref var fr = ref field;
ref var ternary2 = ref true ? ref field : ref local;
}
}
";
CreateStandardCompilation(text).VerifyDiagnostics(
// (13,55): error CS8168: Cannot return local 'local' by reference because it is not a ref local
// ref var ternary1 = ref true ? ref field : ref local;
Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(13, 55),
// (13,32): error CS8525: Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes.
// ref var ternary1 = ref true ? ref field : ref local;
Diagnostic(ErrorCode.ERR_MismatchedRefEscapeInTernary, "true ? ref field : ref local").WithLocation(13, 32),
// (18,55): error CS8168: Cannot return local 'local' by reference because it is not a ref local
// ref var ternary2 = ref true ? ref field : ref local;
Diagnostic(ErrorCode.ERR_RefReturnLocal, "local").WithArguments("local").WithLocation(18, 55),
// (18,32): error CS8525: Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes.
// ref var ternary2 = ref true ? ref field : ref local;
Diagnostic(ErrorCode.ERR_MismatchedRefEscapeInTernary, "true ? ref field : ref local").WithLocation(18, 32));
}
[Fact()]
public void MismatchedRefTernaryEscapeBlock()
{
var text = @"
class Program
{
static void Main()
{
}
public static int field = 1;
void Test1()
{
var outer = 42;
var sOuter = MayWrap(ref outer);
{
var inner = 42;
var sInner = MayWrap(ref inner);
ref var ternary1 = ref true ? ref sOuter[1] : ref sInner[1];
ref var ir = ref sInner[1];
ref var or = ref sOuter[1];
ref var ternary2 = ref true ? ref ir : ref or;
}
}
static S1 MayWrap(ref int arg)
{
return default;
}
ref struct S1
{
public ref int this[int i] => throw null;
}
}
";
CreateStandardCompilation(text).VerifyDiagnostics(
// (19,63): error CS8520: Cannot use local 'sInner' in this context because it may expose referenced variables outside of their declaration scope
// ref var ternary1 = ref true ? ref sOuter[1] : ref sInner[1];
Diagnostic(ErrorCode.ERR_EscapeLocal, "sInner").WithArguments("sInner").WithLocation(19, 63),
// (19,36): error CS8525: Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes.
// ref var ternary1 = ref true ? ref sOuter[1] : ref sInner[1];
Diagnostic(ErrorCode.ERR_MismatchedRefEscapeInTernary, "true ? ref sOuter[1] : ref sInner[1]").WithLocation(19, 36),
// (24,47): error CS8520: Cannot use local 'ir' in this context because it may expose referenced variables outside of their declaration scope
// ref var ternary2 = ref true ? ref ir : ref or;
Diagnostic(ErrorCode.ERR_EscapeLocal, "ir").WithArguments("ir").WithLocation(24, 47),
// (24,36): error CS8525: Branches of a ref ternary operator cannot refer to variables with incompatible declaration scopes.
// ref var ternary2 = ref true ? ref ir : ref or;
Diagnostic(ErrorCode.ERR_MismatchedRefEscapeInTernary, "true ? ref ir : ref or").WithLocation(24, 36)
);
}
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册