From 3f8b7b23e96b2fbeedc8fe595ff7ea99dc27841e Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Fri, 15 Jul 2022 11:55:01 -0700 Subject: [PATCH] Support UnscopedRefAttribute --- .../Compiler Breaking Changes - DotNet 7.md | 25 +- .../Portable/Binder/Binder.ValueChecks.cs | 12 + .../MethodWellKnownAttributeData.cs | 16 + .../ParameterEarlyWellKnownAttributeData.cs | 15 + .../PropertyWellKnownAttributeData.cs | 16 + .../TypeWellKnownAttributeData.cs | 18 + .../Source/SourceComplexParameterSymbol.cs | 17 + .../SourceMethodSymbolWithAttributes.cs | 7 + .../Symbols/Source/SourceNamedTypeSymbol.cs | 7 + .../Symbols/Source/SourceParameterSymbol.cs | 18 +- .../Source/SourcePropertySymbolBase.cs | 7 + .../Symbols/Source/ThisParameterSymbol.cs | 37 +- .../AttributeTests_CallerInfoAttributes.cs | 22 + .../Test/Semantic/Semantics/RefFieldTests.cs | 385 ++++++++++++++++++ .../Symbol/Symbols/MissingSpecialMember.cs | 2 + .../Attributes/AttributeDescription.cs | 1 + .../Core/Portable/WellKnownMember.cs | 1 + .../Core/Portable/WellKnownMembers.cs | 8 + src/Compilers/Core/Portable/WellKnownTypes.cs | 2 + .../WellKnownTypeValidationTests.vb | 10 +- 20 files changed, 613 insertions(+), 13 deletions(-) diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md index 8df4598162ef7..52e464cce567d 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md @@ -128,15 +128,24 @@ static ref T ReturnOutParamByRef(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([UnscopedRef] out T t) + { + t = default; + return ref t; // ok + } + ``` -```csharp -static ref T ReturnRefParamByRef(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(ref T t) + { + t = default; + return ref t; // ok + } + ``` ## Method ref struct return escape analysis depends on ref escape of ref arguments diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 49db2a7a7cb27..cad05975bdb0f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -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. @@ -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; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs index 5a4c0ad0ea706..e95f295b21e87 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/MethodWellKnownAttributeData.cs @@ -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 _memberNotNullAttributeData = ImmutableArray.Empty; public void AddNotNullMember(string memberName) diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/ParameterEarlyWellKnownAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/ParameterEarlyWellKnownAttributeData.cs index 3bcfe343f2c40..317fd4b0754d1 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/ParameterEarlyWellKnownAttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/ParameterEarlyWellKnownAttributeData.cs @@ -11,5 +11,20 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class ParameterEarlyWellKnownAttributeData : CommonParameterEarlyWellKnownAttributeData { + private bool _hasUnscopedRefAttribute; + public bool HasUnscopedRefAttribute + { + get + { + VerifySealed(expected: true); + return _hasUnscopedRefAttribute; + } + set + { + VerifySealed(expected: false); + _hasUnscopedRefAttribute = value; + SetDataStored(); + } + } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/PropertyWellKnownAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/PropertyWellKnownAttributeData.cs index 99d6a9fdab3bd..8993a1d8ff8f2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/PropertyWellKnownAttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/PropertyWellKnownAttributeData.cs @@ -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 _memberNotNullAttributeData = ImmutableArray.Empty; public void AddNotNullMember(string memberName) diff --git a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/TypeWellKnownAttributeData.cs b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/TypeWellKnownAttributeData.cs index 25c1530166692..cd6ade55fd875 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/TypeWellKnownAttributeData.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Attributes/WellKnownAttributeData/TypeWellKnownAttributeData.cs @@ -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 } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 3c121938a6918..36be68f45dc96 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -196,6 +196,8 @@ internal bool HasEnumeratorCancellationAttribute } } + internal bool HasUnscopedRefAttribute => GetEarlyDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true; + #nullable enable internal static SyntaxNode? GetDefaultValueSyntaxForIsNullableAnalysisEnabled(ParameterSyntax? parameterSyntax) => @@ -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)) @@ -663,6 +669,17 @@ internal override (CSharpAttributeData?, BoundAttribute?) EarlyDecodeWellKnownAt return !hasAnyDiagnostics ? (attributeData, boundAttribute) : (null, null); } + + private (CSharpAttributeData?, BoundAttribute?) EarlyDecodeUnscopedRefAttribute(ref EarlyDecodeWellKnownAttributeArguments 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().HasUnscopedRefAttribute = true; + return result; + } #nullable disable protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttributeArguments arguments) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs index 020f765ada186..f4914a037d053 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs @@ -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().HasUnscopedRefAttribute = true; + } else { var compilation = this.DeclaringCompilation; @@ -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 arguments, AttributeDescription description) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index aa0025e273baa..5f7819b13061a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -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().HasUnscopedRefAttribute = true; + } else { var compilation = this.DeclaringCompilation; @@ -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); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs index 18e250286f61b..0c5d940a10c22 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs @@ -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 { diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index f597228d91dec..5b1282e213800 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -1313,6 +1313,11 @@ protected override void DecodeWellKnownAttributeImpl(ref DecodeWellKnownAttribut MessageID.IDS_FeatureMemberNotNull.CheckFeatureAvailability(diagnostics, arguments.AttributeSyntaxOpt); CSharpAttributeData.DecodeMemberNotNullWhenAttribute(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().HasUnscopedRefAttribute = true; + } } internal bool HasDisallowNull @@ -1369,6 +1374,8 @@ internal ImmutableArray MemberNotNullAttributeIfExists internal ImmutableArray MemberNotNullWhenAttributeIfExists => FindAttributes(AttributeDescription.MemberNotNullWhenAttribute); + internal bool HasUnscopedRefAttribute => GetDecodedWellKnownAttributeData()?.HasUnscopedRefAttribute == true; + private SourceAttributeData FindAttribute(AttributeDescription attributeDescription) => (SourceAttributeData)GetAttributes().First(a => a.IsTargetAttribute(this, attributeDescription)); diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs index f2daba205129c..f02442840ab34 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/ThisParameterSymbol.cs @@ -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; + } } } diff --git a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_CallerInfoAttributes.cs b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_CallerInfoAttributes.cs index f9f3b86e81005..e1b0cf7967028 100644 --- a/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_CallerInfoAttributes.cs +++ b/src/Compilers/CSharp/Test/Emit2/Attributes/AttributeTests_CallerInfoAttributes.cs @@ -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)); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index be630e7afe859..a04179bab2832 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -12509,5 +12509,390 @@ void M() Diagnostic(ErrorCode.ERR_BadParameterModifiersOrder, "scoped").WithArguments("scoped", "ref").WithLocation(6, 23) ); } + + private const string UnscopedRefAttributeDefinition = +@"namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + public sealed class UnscopedRefAttribute : Attribute + { + } +}"; + + [Fact] + public void UnscopedRefAttribute_Method_01() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +struct S1 +{ + private int _field; + public ref int GetField1() => ref _field; // error + [UnscopedRef] public ref int GetField2() => ref _field; // ok +}"; + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (5,39): error CS8170: Struct members cannot return 'this' or other instance members by reference + // public ref int GetField1() => ref _field; // error + Diagnostic(ErrorCode.ERR_RefReturnStructThis, "_field").WithLocation(5, 39)); + } + + [WorkItem(62791, "https://github.com/dotnet/roslyn/issues/62791")] + [CombinatorialData] + [Theory] + public void UnscopedRefAttribute_Method_02(bool useCompilationReference) + { + var sourceA = +@"using System.Diagnostics.CodeAnalysis; +public struct S +{ + private T _t; + public ref T F1() => throw null; + [UnscopedRef] public ref T F2() => ref _t; +}"; + var comp = CreateCompilation(new[] { sourceA, UnscopedRefAttributeDefinition }); + comp.VerifyEmitDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"class Program +{ + static ref int F1() + { + var s = new S(); + return ref s.F1(); // ok + } + static ref int F2() + { + var s = new S(); + return ref s.F2(); // error + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }); + // https://github.com/dotnet/roslyn/issues/62791: Should report an error for the call to F2(). + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void UnscopedRefAttribute_Property_01() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +struct S1 +{ + private int _field; + public ref int Property1 => ref _field; // error + [UnscopedRef] public ref int Property2 => ref _field; // ok +}"; + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (5,37): error CS8170: Struct members cannot return 'this' or other instance members by reference + // public ref int Property1 => ref _field; // error + Diagnostic(ErrorCode.ERR_RefReturnStructThis, "_field").WithLocation(5, 37)); + } + + [WorkItem(62791, "https://github.com/dotnet/roslyn/issues/62791")] + [CombinatorialData] + [Theory] + public void UnscopedRefAttribute_Property_02(bool useCompilationReference) + { + var sourceA = +@"using System.Diagnostics.CodeAnalysis; +public struct S +{ + private T _t; + public ref T P1 => throw null; + [UnscopedRef] public ref T P2 => ref _t; +}"; + var comp = CreateCompilation(new[] { sourceA, UnscopedRefAttributeDefinition }); + comp.VerifyEmitDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"class Program +{ + static ref int F1() + { + var s = new S(); + return ref s.P1; // ok + } + static ref int F2() + { + var s = new S(); + return ref s.P2; // error + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }); + // https://github.com/dotnet/roslyn/issues/62791: Should report an error for the call to P2. + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void UnscopedRefAttribute_Struct_01() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +[UnscopedRef] +struct S2 +{ + private int _field; + public ref int Property => ref _field; // ok + public ref int GetField() => ref _field; // ok +} +struct S3A +{ + private S2 _child; + public ref int ValueA => ref _child.Property; // ok +} +[UnscopedRef] +struct S3B +{ + private S2 _child; + public ref int ValueB => ref _child.Property; // ok +}"; + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics(); + } + + [WorkItem(62791, "https://github.com/dotnet/roslyn/issues/62791")] + [CombinatorialData] + [Theory] + public void UnscopedRefAttribute_Struct_02(bool useCompilationReference) + { + var sourceA = +@"using System.Diagnostics.CodeAnalysis; +[UnscopedRef] +public struct S +{ + private T _t; + public ref T F() => ref _t; + public ref T P => ref _t; +}"; + var comp = CreateCompilation(new[] { sourceA, UnscopedRefAttributeDefinition }); + comp.VerifyEmitDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"class Program +{ + static ref int F1() + { + var s = new S(); + return ref s.F(); // error + } + static ref int F2() + { + var s = new S(); + return ref s.P; // error + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }); + // https://github.com/dotnet/roslyn/issues/62791: Should report errors for s.F() and s.P. + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void UnscopedRefAttribute_Struct_03() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +[UnscopedRef] +struct S2 +{ + private int _field; + scoped public ref int Property => ref _field; // error + scoped public ref int GetField() => ref _field; // error +}"; + // PROTOTYPE: Should report errors that _field escapes in each case, + // and should not report ERR_BadMemberFlag. + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (6,27): error CS0106: The modifier 'scoped' is not valid for this item + // scoped public ref int Property => ref _field; // error + Diagnostic(ErrorCode.ERR_BadMemberFlag, "Property").WithArguments("scoped").WithLocation(6, 27), + // (7,27): error CS0106: The modifier 'scoped' is not valid for this item + // scoped public ref int GetField() => ref _field; // error + Diagnostic(ErrorCode.ERR_BadMemberFlag, "GetField").WithArguments("scoped").WithLocation(7, 27)); + } + + [WorkItem(62691, "https://github.com/dotnet/roslyn/issues/62691")] + [Fact] + public void UnscopedRefAttribute_RefRefStructParameter_01() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +ref struct R { } +class Program +{ + static ref R ReturnRefStructRef(bool b, ref R x, [UnscopedRef] ref R y) + { + if (b) + return ref x; // error: Cannot return parameter by reference + else + return ref y; // ok + } +}"; + // https://github.com/dotnet/roslyn/issues/62691: An error should be reported for 'return ref x;'. + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics(); + + var parameters = comp.GetMember("Program.ReturnRefStructRef").Parameters; + VerifyParameterSymbol(parameters[1], "ref R x", RefKind.Ref, DeclarationScope.Unscoped); // https://github.com/dotnet/roslyn/issues/62691: Should be DeclarationScope.RefScoped. + VerifyParameterSymbol(parameters[2], "ref R y", RefKind.Ref, DeclarationScope.Unscoped); + } + + [WorkItem(62791, "https://github.com/dotnet/roslyn/issues/62791")] + [CombinatorialData] + [Theory] + public void UnscopedRefAttribute_RefRefStructParameter_02(bool useCompilationReference) + { + var sourceA = +@"using System.Diagnostics.CodeAnalysis; +public ref struct R +{ + public ref T F; + public R(ref T t) { F = ref t; } +} +public class A +{ + public static ref T F1(ref R r) + { + throw null; + } + public static ref T F2([UnscopedRef] ref R r) + { + return ref r.F; + } +}"; + var comp = CreateCompilation(new[] { sourceA, UnscopedRefAttributeDefinition }); + comp.VerifyEmitDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"class B +{ + static ref int F1() + { + int i = 1; + var r = new R(ref i); + return ref A.F1(ref r); // ok + } + static ref int F2() + { + int i = 2; + var r = new R(ref i); + return ref A.F2(ref r); // error + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }); + // https://github.com/dotnet/roslyn/issues/62791: Should not report errors for A.F1(). + comp.VerifyEmitDiagnostics( + // (7,20): error CS8347: Cannot use a result of 'A.F1(ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope + // return ref A.F1(ref r); // ok + Diagnostic(ErrorCode.ERR_EscapeCall, "A.F1(ref r)").WithArguments("A.F1(ref R)", "r").WithLocation(7, 20), + // (7,29): error CS8168: Cannot return local 'r' by reference because it is not a ref local + // return ref A.F1(ref r); // ok + Diagnostic(ErrorCode.ERR_RefReturnLocal, "r").WithArguments("r").WithLocation(7, 29), + // (13,20): error CS8347: Cannot use a result of 'A.F2(ref R)' in this context because it may expose variables referenced by parameter 'r' outside of their declaration scope + // return ref A.F2(ref r); // error + Diagnostic(ErrorCode.ERR_EscapeCall, "A.F2(ref r)").WithArguments("A.F2(ref R)", "r").WithLocation(13, 20), + // (13,29): error CS8168: Cannot return local 'r' by reference because it is not a ref local + // return ref A.F2(ref r); // error + Diagnostic(ErrorCode.ERR_RefReturnLocal, "r").WithArguments("r").WithLocation(13, 29)); + } + + [Fact] + public void UnscopedRefAttribute_OutParameter_01() + { + var source = +@"using System.Diagnostics.CodeAnalysis; +class Program +{ + static ref int ReturnOut(bool b, out int x, [UnscopedRef] out int y) + { + x = 1; + y = 2; + if (b) + return ref x; // error: Cannot return parameter by reference + else + return ref y; // ok + } +}"; + var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition }); + comp.VerifyDiagnostics( + // (9,24): error CS8166: Cannot return a parameter by reference 'x' because it is not a ref parameter + // return ref x; // error: Cannot return parameter by reference + Diagnostic(ErrorCode.ERR_RefReturnParameter, "x").WithArguments("x").WithLocation(9, 24)); + + var parameters = comp.GetMember("Program.ReturnOut").Parameters; + VerifyParameterSymbol(parameters[1], "out System.Int32 x", RefKind.Out, DeclarationScope.RefScoped); + VerifyParameterSymbol(parameters[2], "out System.Int32 y", RefKind.Out, DeclarationScope.Unscoped); + } + + [WorkItem(62791, "https://github.com/dotnet/roslyn/issues/62791")] + [CombinatorialData] + [Theory] + public void UnscopedRefAttribute_OutParameter_02(bool useCompilationReference) + { + var sourceA = +@"using System.Diagnostics.CodeAnalysis; +public class A +{ + public static ref T F1(out T t) + { + throw null; + } + public static ref T F2([UnscopedRef] out T t) + { + t = default; + return ref t; + } +}"; + var comp = CreateCompilation(new[] { sourceA, UnscopedRefAttributeDefinition }); + comp.VerifyEmitDiagnostics(); + var refA = AsReference(comp, useCompilationReference); + + var sourceB = +@"class B +{ + static ref int F1() + { + int i = 1; + return ref A.F1(out i); // ok + } + static ref int F2() + { + int i = 2; + return ref A.F2(out i); // error + } +}"; + comp = CreateCompilation(sourceB, references: new[] { refA }); + // https://github.com/dotnet/roslyn/issues/62791: Should report an error for A.F2(). + comp.VerifyEmitDiagnostics(); + } + + [Fact] + public void UnscopedRefAttribute_Cycle() + { + var source = +@"namespace System.Diagnostics.CodeAnalysis +{ + public sealed class UnscopedRefAttribute : Attribute + { + public UnscopedRefAttribute([UnscopedRef] out int i) { i = 0; } + } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + } + + // PROTOTYPE: Test on instance method of nested type when attribute is applied to top-level type. + + // PROTOTYPE: Test [UnscopedRef] with -langversion:10. Should report an error in the same way we'd report an error for 'scoped'. Test with all possible locations above. + + // PROTOTYPE: [UnscopedRef] must apply consistently across overrides, implementations, delegate conversions. + + // PROTOTYPE Test SymbolDisplay with [UnscopedRef] on out parameter. + // PROTOTYPE Test SymbolDisplay with [UnscopedRef] on ref parameter to ref struct. } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index e61298854268a..2663942e3fed6 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -617,6 +617,7 @@ public void AllWellKnownTypes() case WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute: case WellKnownType.System_MemoryExtensions: case WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute: + case WellKnownType.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute: // Not yet in the platform. continue; case WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation: @@ -981,6 +982,7 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T: case WellKnownMember.System_MemoryExtensions__AsSpan_String: case WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor: + case WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor: // Not yet in the platform. continue; case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile: diff --git a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs index abe6c5d039722..981b05de4cb83 100644 --- a/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs +++ b/src/Compilers/Core/Portable/Symbols/Attributes/AttributeDescription.cs @@ -478,5 +478,6 @@ static AttributeDescription() internal static readonly AttributeDescription RequiredMemberAttribute = new AttributeDescription("System.Runtime.CompilerServices", "RequiredMemberAttribute", s_signatures_HasThis_Void_Only); internal static readonly AttributeDescription SetsRequiredMembersAttribute = new AttributeDescription("System.Diagnostics.CodeAnalysis", "SetsRequiredMembersAttribute", s_signatures_HasThis_Void_Only); internal static readonly AttributeDescription CompilerFeatureRequiredAttribute = new AttributeDescription("System.Runtime.CompilerServices", "CompilerFeatureRequiredAttribute", s_signatures_HasThis_Void_String_Only); + internal static readonly AttributeDescription UnscopedRefAttribute = new AttributeDescription("System.Diagnostics.CodeAnalysis", "UnscopedRefAttribute", s_signatures_HasThis_Void_Only); } } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index e32700fdbd918..838912eb28468 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -529,6 +529,7 @@ internal enum WellKnownMember System_MemoryExtensions__AsSpan_String, System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor, System_MissingMethodException__ctor, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 66117afc0bbae..8704273e8ce7a 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -3640,6 +3640,13 @@ static WellKnownMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor + (byte)MemberFlags.Constructor, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type // System_MissingMethodException__ctor (byte)MemberFlags.Constructor, // Flags @@ -4103,6 +4110,7 @@ static WellKnownMembers() "SequenceEqual", // System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T "AsSpan", // System_MemoryExtensions__AsSpan_String ".ctor", // System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute_ctor + ".ctor", // System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor ".ctor", // System_MissingMethodException__ctor }; diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 41bf3dc7e746d..32fd6e2934d34 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -323,6 +323,7 @@ internal enum WellKnownType System_MemoryExtensions, System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, + System_Diagnostics_CodeAnalysis_UnscopedRefAttribute, System_MissingMethodException, @@ -640,6 +641,7 @@ internal static class WellKnownTypes "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute", "System.MemoryExtensions", "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute", + "System.Diagnostics.CodeAnalysis.UnscopedRefAttribute", "System.MissingMethodException", }; diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 200cfb8e6ecf0..ed202ed8d29b1 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -552,6 +552,7 @@ End Namespace WellKnownType.System_MemoryExtensions, WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute, + WellKnownType.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute, WellKnownType.System_MemoryExtensions ' Not available on all platforms. Continue For @@ -623,7 +624,8 @@ End Namespace WellKnownType.System_Diagnostics_CodeAnalysis_SetsRequiredMembersAttribute, WellKnownType.System_MemoryExtensions, WellKnownType.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute, - WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute + WellKnownType.System_Runtime_CompilerServices_LifetimeAnnotationAttribute, + WellKnownType.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute ' Not available on all platforms. Continue For Case WellKnownType.ExtSentinel @@ -719,7 +721,8 @@ End Namespace WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T, WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T, WellKnownMember.System_MemoryExtensions__AsSpan_String, - WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor + WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor ' Not available yet, but will be in upcoming release. Continue For Case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, @@ -871,7 +874,8 @@ End Namespace WellKnownMember.System_MemoryExtensions__SequenceEqual_Span_T, WellKnownMember.System_MemoryExtensions__SequenceEqual_ReadOnlySpan_T, WellKnownMember.System_MemoryExtensions__AsSpan_String, - WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor + WellKnownMember.System_Runtime_CompilerServices_CompilerFeatureRequiredAttribute__ctor, + WellKnownMember.System_Diagnostics_CodeAnalysis_UnscopedRefAttribute__ctor ' Not available yet, but will be in upcoming release. Continue For Case WellKnownMember.Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile,