Skip to content

Commit

Permalink
Handle deconstruct pattern in ref safety analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
jjonescz committed Oct 15, 2024
1 parent 09012a1 commit cd7baee
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,65 @@ static uint getDeclarationValEscape(BoundTypeExpression typeExpression, uint val

public override BoundNode? VisitRecursivePattern(BoundRecursivePattern node)
{
if (node.DeconstructMethod is { } deconstructMethod)
{
Debug.Assert(node.Deconstruction.Length == deconstructMethod.ParameterCount - (deconstructMethod.RequiresInstanceReceiver ? 0 : 1));

ImmutableArray<BoundExpression> arguments = node.Deconstruction.SelectAsArray(static x =>
{
if (x.Pattern is BoundObjectPattern { VariableAccess: { } variableAccess })
{
return variableAccess;
}
return new BoundDiscardExpression(
x.Syntax,
NullableAnnotation.NotAnnotated,
isInferred: false,
x.Pattern.NarrowedType)
{
WasCompilerGenerated = true,
};
});

var receiver = new BoundValuePlaceholder(
node.Syntax,
node.InputType)
{
WasCompilerGenerated = true,
};

var placeholders = ArrayBuilder<(BoundValuePlaceholderBase, uint)>.GetInstance();
placeholders.Add((receiver, _localScopeDepth));

if (!deconstructMethod.RequiresInstanceReceiver)
{
arguments = arguments.Insert(0, receiver);
receiver = null;
}

using (var _ = new PlaceholderRegion(this, placeholders))
{
CheckInvocationArgMixing(
node.Syntax,
MethodInfo.Create(deconstructMethod),
receiver,
receiverIsSubjectToCloning: ThreeState.False,
deconstructMethod.Parameters,
arguments,
deconstructMethod.ParameterRefKinds,
argsToParamsOpt: default,
_localScopeDepth,
_diagnostics);
}

using (var _ = new PatternInput(this, _localScopeDepth))
{
SetPatternLocalScopes(node);
return base.VisitRecursivePattern(node);
}
}

SetPatternLocalScopes(node);
return base.VisitRecursivePattern(node);
}
Expand Down
217 changes: 217 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5982,6 +5982,223 @@ public static class Extensions
Diagnostic(ErrorCode.ERR_EscapeVariable, "local2").WithArguments("local2").WithLocation(11, 18));
}

[Fact]
public void Deconstruction_UnscopedRef()
{
var source = """
using System.Diagnostics.CodeAnalysis;
class C
{
R M()
{
new S().Deconstruct(out var x, out _);
return x;
}
}
struct S
{
[UnscopedRef] public void Deconstruct(out R x, out int y) => throw null;
}
ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (7,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 16));
}

[Fact]
public void Deconstruction_UnscopedRef_ExtensionMethod()
{
var source = """
class C
{
R M()
{
new S().Deconstruct(out var x, out _);
return x;
}
}
struct S;
ref struct R;
static class E
{
public static void Deconstruct(this in S s, out R x, out int y) => throw null;
}
""";
CreateCompilation(source).VerifyDiagnostics(
// (6,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(6, 16));
}

[Fact]
public void Deconstruction_UnscopedRef_Assignment()
{
var source = """
using System.Diagnostics.CodeAnalysis;
class C
{
R M()
{
(var x, _) = new S();
return x;
}
}
struct S
{
[UnscopedRef] public void Deconstruct(out R x, out int y) => throw null;
}
ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (7,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 16));
}

[Fact]
public void Deconstruction_UnscopedRef_Assignment_ExtensionMethod()
{
var source = """
class C
{
R M()
{
(var x, _) = new S();
return x;
}
}
struct S;
ref struct R;
static class E
{
public static void Deconstruct(this in S s, out R x, out int y) => throw null;
}
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (6,16): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(6, 16));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75484")]
public void Deconstruction_UnscopedRef_PatternMatching()
{
var source = """
using System.Diagnostics.CodeAnalysis;
class C
{
R M()
{
if (new S() is (var x, _))
return x;
return default;
}
}
struct S
{
[UnscopedRef] public void Deconstruct(out R x, out int y) => throw null;
}
ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (7,20): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 20));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75484")]
public void Deconstruction_UnscopedRef_PatternMatching_ExtensionMethod()
{
var source = """
class C
{
R M()
{
if (new S() is (var x, _))
return x;
return default;
}
}
struct S;
ref struct R;
static class E
{
public static void Deconstruct(this in S s, out R x, out int y) => throw null;
}
""";
CreateCompilation(source).VerifyDiagnostics(
// (6,20): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(6, 20));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75484")]
public void Deconstruction_UnscopedRef_PatternMatching_NestedProp()
{
var source = """
using System.Diagnostics.CodeAnalysis;
class C
{
S Prop { get; set; }
R M()
{
if (new C() is { Prop: (var x, _) })
return x;
return default;
}
}
struct S
{
[UnscopedRef] public void Deconstruct(out R x, out int y) => throw null;
}
ref struct R;
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (8,20): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(8, 20));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75484")]
public void Deconstruction_UnscopedRef_PatternMatching_NestedPattern()
{
var source = """
using System.Diagnostics.CodeAnalysis;
class C
{
R M1()
{
if (new S() is ({ Prop: (var x2, _) } x, _))
return x;
return default;
}
R M2()
{
if (new S() is ({ Prop: (var x2, _) } x, _))
return x2;
return default;
}
}
struct S
{
[UnscopedRef] public void Deconstruct(out R x, out int y) => throw null;
}
ref struct R
{
public S Prop { get; set; }
}
""";
CreateCompilation([source, UnscopedRefAttributeDefinition]).VerifyDiagnostics(
// (7,20): error CS8352: Cannot use variable 'x' in this context because it may expose referenced variables outside of their declaration scope
// return x;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x").WithArguments("x").WithLocation(7, 20),
// (13,20): error CS8352: Cannot use variable 'x2' in this context because it may expose referenced variables outside of their declaration scope
// return x2;
Diagnostic(ErrorCode.ERR_EscapeVariable, "x2").WithArguments("x2").WithLocation(13, 20));
}

[Theory]
[InlineData(LanguageVersion.CSharp10)]
[InlineData(LanguageVersion.CSharp11)]
Expand Down

0 comments on commit cd7baee

Please sign in to comment.