Skip to content

Commit

Permalink
Support UnscopedRefAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
cston committed Jul 27, 2022
1 parent d81ce62 commit 3f8b7b2
Show file tree
Hide file tree
Showing 20 changed files with 613 additions and 13 deletions.
25 changes: 17 additions & 8 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,24 @@ static ref T ReturnOutParamByRef<T>(out T t)
}
```

A possible workaround is to change the method signature to pass the parameter by `ref` instead.
Possible workarounds are:
1. Use `System.Diagnostics.CodeAnalysis.UnscopedRefAttribute` to mark the reference as unscoped.
```csharp
static ref T ReturnOutParamByRef<T>([UnscopedRef] out T t)
{
t = default;
return ref t; // ok
}
```

```csharp
static ref T ReturnRefParamByRef<T>(ref T t)
{
t = default;
return ref t; // ok
}
```
1. Change the method signature to pass the parameter by `ref`.
```csharp
static ref T ReturnRefParamByRef<T>(ref T t)
{
t = default;
return ref t; // ok
}
```

## Method ref struct return escape analysis depends on ref escape of ref arguments

Expand Down
12 changes: 12 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2481,6 +2481,8 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
return CheckLocalRefEscape(node, local, escapeTo, checkingReceiver, diagnostics);

case BoundKind.ThisReference:
Debug.Assert(this.ContainingMember() is MethodSymbol { ThisParameter: not null });

var thisref = (BoundThisReference)expr;

// "this" is an RValue, unless in a struct.
Expand All @@ -2492,6 +2494,16 @@ internal bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint escapeF
//"this" is not returnable by reference in a struct.
if (escapeTo == Binder.ExternalScope)
{
// PROTOTYPE: Add this same case to GetRefEscape().
if (UseUpdatedEscapeRules)
{
if (this.ContainingMember() is MethodSymbol { ThisParameter: var thisParameter } &&
thisParameter.Scope == DeclarationScope.Unscoped)
{
// can ref escape to any other level
return true;
}
}
Error(diagnostics, ErrorCode.ERR_RefReturnStructThis, node);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ public bool HasSkipLocalsInitAttribute
}
}

private bool _hasUnscopedRefAttribute;
public bool HasUnscopedRefAttribute
{
get
{
VerifySealed(expected: true);
return _hasUnscopedRefAttribute;
}
set
{
VerifySealed(expected: false);
_hasUnscopedRefAttribute = value;
SetDataStored();
}
}

private ImmutableArray<string> _memberNotNullAttributeData = ImmutableArray<string>.Empty;

public void AddNotNullMember(string memberName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,20 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols
/// </summary>
internal sealed class ParameterEarlyWellKnownAttributeData : CommonParameterEarlyWellKnownAttributeData
{
private bool _hasUnscopedRefAttribute;
public bool HasUnscopedRefAttribute
{
get
{
VerifySealed(expected: true);
return _hasUnscopedRefAttribute;
}
set
{
VerifySealed(expected: false);
_hasUnscopedRefAttribute = value;
SetDataStored();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ public bool HasSkipLocalsInitAttribute
}
}

private bool _hasUnscopedRefAttribute;
public bool HasUnscopedRefAttribute
{
get
{
VerifySealed(expected: true);
return _hasUnscopedRefAttribute;
}
set
{
VerifySealed(expected: false);
_hasUnscopedRefAttribute = value;
SetDataStored();
}
}

private ImmutableArray<string> _memberNotNullAttributeData = ImmutableArray<string>.Empty;

public void AddNotNullMember(string memberName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,23 @@ public bool HasSkipLocalsInitAttribute
}
}
#endregion

#region UnscopedRefAttribute
private bool _hasUnscopedRefAttribute;
public bool HasUnscopedRefAttribute
{
get
{
VerifySealed(expected: true);
return _hasUnscopedRefAttribute;
}
set
{
VerifySealed(expected: false);
_hasUnscopedRefAttribute = value;
SetDataStored();
}
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ internal bool HasEnumeratorCancellationAttribute
}
}

internal bool HasUnscopedRefAttribute => GetEarlyDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true;

#nullable enable

internal static SyntaxNode? GetDefaultValueSyntaxForIsNullableAnalysisEnabled(ParameterSyntax? parameterSyntax) =>
Expand Down Expand Up @@ -593,6 +595,10 @@ internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAt
{
return EarlyDecodeAttributeForDefaultParameterValue(AttributeDescription.DateTimeConstantAttribute, ref arguments);
}
else if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.UnscopedRefAttribute))
{
return EarlyDecodeUnscopedRefAttribute(ref arguments);
}
else if (!IsOnPartialImplementation(arguments.AttributeSyntax))
{
if (CSharpAttributeData.IsTargetEarlyAttribute(arguments.AttributeType, arguments.AttributeSyntax, AttributeDescription.CallerLineNumberAttribute))
Expand Down Expand Up @@ -663,6 +669,17 @@ internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAt

return !hasAnyDiagnostics ? (attributeData, boundAttribute) : (null, null);
}

private (CSharpAttributeData?, BoundAttribute?) EarlyDecodeUnscopedRefAttribute(ref EarlyDecodeWellKnownAttributeArguments<EarlyWellKnownAttributeBinder, NamedTypeSymbol, AttributeSyntax, AttributeLocation> arguments)
{
var result = arguments.Binder.GetAttribute(arguments.AttributeSyntax, arguments.AttributeType, beforeAttributePartBound: null, afterAttributePartBound: null, out bool _);
if (result.Item1.HasErrors)
{
return (null, null);
}
arguments.GetOrCreateData<ParameterEarlyWellKnownAttributeData>().HasUnscopedRefAttribute = true;
return result;
}
#nullable disable

protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,11 @@ private void DecodeWellKnownAttributeAppliedToMethod(ref DecodeWellKnownAttribut
{
DecodeUnmanagedCallersOnlyAttribute(ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.UnscopedRefAttribute))
{
// PROTOTYPE: Should this be an early decoded attribute? Create a test that manifests a difference.
arguments.GetOrCreateData<MethodWellKnownAttributeData>().HasUnscopedRefAttribute = true;
}
else
{
var compilation = this.DeclaringCompilation;
Expand Down Expand Up @@ -600,6 +605,8 @@ public override FlowAnalysisAnnotations FlowAnalysisAnnotations
private static FlowAnalysisAnnotations DecodeFlowAnalysisAttributes(MethodWellKnownAttributeData attributeData)
=> attributeData?.HasDoesNotReturnAttribute == true ? FlowAnalysisAnnotations.DoesNotReturn : FlowAnalysisAnnotations.None;

internal bool HasUnscopedRefAttribute => GetDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true;

private bool VerifyObsoleteAttributeAppliedToMethod(
ref DecodeWellKnownAttributeArguments<AttributeSyntax, CSharpAttributeData, AttributeLocation> arguments,
AttributeDescription description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,11 @@ protected sealed override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownA
{
_lazyIsExplicitDefinitionOfNoPiaLocalType = ThreeState.True;
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.UnscopedRefAttribute))
{
// PROTOTYPE: Should this be an early decoded attribute? Create a test that manifests a difference.
arguments.GetOrCreateData<TypeWellKnownAttributeData>().HasUnscopedRefAttribute = true;
}
else
{
var compilation = this.DeclaringCompilation;
Expand Down Expand Up @@ -1274,6 +1279,8 @@ internal override NamedTypeSymbol ComImportCoClass
}
}

internal bool HasUnscopedRefAttribute => GetDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true;

private void ValidateConditionalAttribute(CSharpAttributeData attribute, AttributeSyntax node, BindingDiagnosticBag diagnostics)
{
Debug.Assert(this.IsConditional);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,23 @@ public sealed override RefKind RefKind
}
}

internal sealed override DeclarationScope Scope => _scope;
internal sealed override DeclarationScope Scope
{
get
{
// PROTOTYPE: Clean up this implementation (and share
// across all ParameterSymbols?) if we keep this.
if (_refKind == RefKind.Out)
{
// PROTOTYPE: Shouldn't require casting to SourceComplexParameterSymbol.
if (this is SourceComplexParameterSymbol { HasUnscopedRefAttribute: true })
{
return DeclarationScope.Unscoped;
}
}
return _scope;
}
}

public sealed override string Name
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,11 @@ protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttribut
MessageID.IDS_FeatureMemberNotNull.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt);
CSharpAttributeData.DecodeMemberNotNullWhenAttribute<PropertyWellKnownAttributeData>(ContainingType, ref arguments);
}
else if (attribute.IsTargetAttribute(this, AttributeDescription.UnscopedRefAttribute))
{
// PROTOTYPE: Should this be an early decoded attribute? Create a test that manifests a difference.
arguments.GetOrCreateData<PropertyWellKnownAttributeData>().HasUnscopedRefAttribute = true;
}
}

internal bool HasDisallowNull
Expand Down Expand Up @@ -1369,6 +1374,8 @@ internal ImmutableArray<SourceAttributeData> MemberNotNullAttributeIfExists
internal ImmutableArray<SourceAttributeData> MemberNotNullWhenAttributeIfExists
=> FindAttributes(AttributeDescription.MemberNotNullWhenAttribute);

internal bool HasUnscopedRefAttribute => GetDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true;

private SourceAttributeData FindAttribute(AttributeDescription attributeDescription)
=> (SourceAttributeData)GetAttributes().First(a => a.IsTargetAttribute(this, attributeDescription));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,41 @@ internal override MarshalPseudoCustomAttributeData? MarshallingInformation

internal override bool HasInterpolatedStringHandlerArgumentError => false;

internal override DeclarationScope Scope => ContainingType?.TypeKind != TypeKind.Struct ? DeclarationScope.Unscoped : DeclarationScope.RefScoped;
internal override DeclarationScope Scope
{
get
{
if (_containingType.TypeKind != TypeKind.Struct)
{
return DeclarationScope.Unscoped;
}
if (HasUnscopedRefAttribute(_containingMethod, _containingType as NamedTypeSymbol))
{
return DeclarationScope.Unscoped;
}
return DeclarationScope.RefScoped;
}
}

private static bool HasUnscopedRefAttribute(MethodSymbol? containingMethod, NamedTypeSymbol? containingType)
{
if (containingMethod is SourceMethodSymbolWithAttributes { HasUnscopedRefAttribute: true })
{
return true;
}
if (containingMethod?.AssociatedSymbol is SourcePropertySymbol { HasUnscopedRefAttribute: true })
{
return true;
}
while (containingType is { })
{
if (containingType is SourceNamedTypeSymbol { HasUnscopedRefAttribute: true })
{
return true;
}
containingType = containingType.ContainingSymbol as NamedTypeSymbol;
}
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5818,5 +5818,27 @@ void M(int i, [CallerArgumentExpression(""i"")] in string s = ""default value"")

CompileAndVerify(comp, expectedOutput: "1 + 1").VerifyDiagnostics();
}

[Fact]
public void CallerArgumentExpression_Cycle()
{
string source =
@"namespace System.Runtime.CompilerServices
{
public sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute([CallerArgumentExpression(nameof(parameterName))] string parameterName)
{
ParameterName = parameterName;
}
public string ParameterName { get; }
}
}";
var comp = CreateCompilation(source);
comp.VerifyDiagnostics(
// (5,51): error CS8964: The CallerArgumentExpressionAttribute may only be applied to parameters with default values
// public CallerArgumentExpressionAttribute([CallerArgumentExpression(nameof(parameterName))] string parameterName)
Diagnostic(ErrorCode.ERR_BadCallerArgumentExpressionParamWithoutDefaultValue, "CallerArgumentExpression").WithLocation(5, 51));
}
}
}
Loading

0 comments on commit 3f8b7b2

Please sign in to comment.