Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support UnscopedRefAttribute #62783

Merged
merged 14 commits into from
Aug 3, 2022
Merged

Support UnscopedRefAttribute #62783

merged 14 commits into from
Aug 3, 2022

Conversation

cston
Copy link
Member

@cston cston commented Jul 20, 2022

See dotnet/runtime#72074.

See low-level-struct-improvements.md.

Corresponding spec update: dotnet/csharplang#6329

Fixes #62780.

Relates to test plan #59194

@cston cston force-pushed the RefEscapesAttribute branch 4 times, most recently from 2751f37 to 1574564 Compare July 21, 2022 14:10
@cston cston changed the title Support RefEscapesAttribute Support UnscopedRefAttribute Jul 21, 2022
@cston cston force-pushed the RefEscapesAttribute branch 3 times, most recently from 3f8b7b2 to 6442a41 Compare July 27, 2022 18:57
@cston cston changed the base branch from main to release/dev17.4 July 27, 2022 18:58
@cston cston force-pushed the RefEscapesAttribute branch 5 times, most recently from 85d875c to e1c0157 Compare July 28, 2022 05:52
@cston cston force-pushed the RefEscapesAttribute branch from e1c0157 to 7884b57 Compare July 28, 2022 14:16
@cston
Copy link
Member Author

cston commented Jul 28, 2022

cc @jaredpar, @AaronRobinsonMSFT

@cston cston marked this pull request as ready for review July 28, 2022 16:37
@cston cston requested a review from a team as a code owner July 28, 2022 16:37
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
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: other csharp blocks are left-indented. Not sure whether it's material (does that affect markup rendering on GitHub or docs.microsoft.com), but I'd stick with that to be safe. #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without indenting, subsequent bullets start at 1.

@jcouv jcouv self-assigned this Jul 28, 2022
if (_moduleSymbol.Module.HasLifetimeAnnotationAttribute(_handle, out var pair))
if (_moduleSymbol.Module.HasUnscopedRefAttribute(_handle))
{
scope = DeclarationScope.Unscoped;
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mark the parameter as bad if there's both attributes? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -978,7 +982,9 @@ public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
}
}

internal sealed override DeclarationScope Scope => _packedFlags.Scope;
internal sealed override DeclarationScope DeclaredScope => _packedFlags.Scope;
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is PEParameterSymbol.DeclaredScope reachable (aside from the use on next line)? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like PEParameterSymbol.DeclaredScope is only used below, so perhaps we could limit DeclaredScope to source symbols only. That change may be a little involved though, so perhaps that could be done later if necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -162,7 +162,7 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r

if (ParameterHelpers.RequiresLifetimeAnnotationAttribute(this))
{
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeLifetimeAnnotationAttribute(this, Scope));
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeLifetimeAnnotationAttribute(this, DeclaredScope));
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeclaredScope

nit: there's not really a difference but this feels more like EffectiveScope #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, there shouldn't be a difference here, but I think I prefer DeclaredScope since that is what was declared.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still nit but want to clarify my reasoning: Declared scope is only a step towards producing the real/effective scope. I think most places should use the real/effective scope unless there's a special reason. When it comes to emitting (like here), clearly we should use the real/effective scope, not its precedessor. If we added any more factors into the computations of real/effective scope, we'd want them to affect what we emit.

Copy link
Member Author

@cston cston Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we're emitting implicitly scoped parameters with no [ScopedRef] attribute. If we use EffectiveScope for emit, then emitting [UnscopedRef] out int i (where the EffectiveScope is Unscoped) will require ensuring an [UnscopedRef] attribute is emitted, perhaps by assuming an [UnscopedRef] attribute was specified in source. Instead, if we use DeclaredScope here (which is perhaps poorly named since DeclaredScope is (implicitly )RefScoped for out int i), then any [UnscopedRef] from source will provided the expected result in metadata.

A better alternative might be to emit implicitly scoped parameters with a [ScopedRef] attribute. Then EffectiveScope may be simpler here. #63078

@@ -101,7 +101,7 @@ internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, r

if (ParameterHelpers.RequiresLifetimeAnnotationAttribute(this))
{
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeLifetimeAnnotationAttribute(this, Scope));
AddSynthesizedAttribute(ref attributes, moduleBuilder.SynthesizeLifetimeAnnotationAttribute(this, DeclaredScope));
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeclaredScope

nit: Same here, feels like EffectiveScope #Closed

}
return scope;

static bool hasUnscopedRefAttribute(MethodSymbol? containingMethod, NamedTypeSymbol? containingType)
Copy link
Member

@jcouv jcouv Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedTypeSymbol? containingType

this parameter is unused #Resolved

}";
var comp = CreateCompilation(new[] { source, UnscopedRefAttributeDefinition });
comp.VerifyEmitDiagnostics(
// (6,23): error CS9064: UnscopedRefAttribute can only be applied to parameters that are passed by reference and implicitly scoped.
Copy link
Member

@jcouv jcouv Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnscopedRefAttribute can only be applied to parameters that are passed by reference and implicitly scoped.

nit: Consider "UnscopedRefAttribute can only be applied to implicitly-scoped 'out' parameters"? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnscopedRefAttribute can also be applied to ref parameters of ref struct types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the text to cover the two cases as we discussed.

t3 = default;
return ref t3;
}
public ref T F4A([UnscopedRef] scoped out T t4)
Copy link
Member

@jcouv jcouv Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect an error (this is not "implicitly-scoped") #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done with review pass (iteration 8)

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 8)

}
public class A<T>
{
public ref T F1A(ref R<T> r1)
Copy link
Member

@jcouv jcouv Jul 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the verification below (VerifyParameterSymbol(baseType.GetMethod("F1A").Parameters[0], "ref R<System.Int32> r1", RefKind.Ref, DeclarationScope.Unscoped);), it seems we're not treating this parameter as scoped. But shouldn't it be implicitly-scoped according to the rule "ref parameters which refer to a ref struct"? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change to treat ref parameters to ref struct types as implicitly scoped is handled in a subsequent PR: #62778.

@@ -218,6 +218,8 @@ public bool IsWriteOnly

internal abstract bool MustCallMethodsDirectly { get; }

internal abstract bool HasUnscopedRefAttribute { get; }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasUnscopedRefAttribute

Is PropertySymbol.HasUnscopedRefAttribute necessary? Should the attribute be supported on properties in general, just on properties from source, or only on accessors directly?

@cston cston changed the base branch from release/dev17.4 to main August 1, 2022 18:54
Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done review pass (commit 11). I did not look at tests yet.

@cston cston force-pushed the RefEscapesAttribute branch from 71f521a to df68917 Compare August 2, 2022 19:51
public void UnscopedRefAttribute_Method_01(string type)
{
var source =
$@"using System.Diagnostics.CodeAnalysis;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: raw interpolated strings would be more readable here :).

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 14).

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM Thanks (iteration 14)

@cston cston merged commit 7ab83bb into dotnet:main Aug 3, 2022
@cston cston deleted the RefEscapesAttribute branch August 3, 2022 04:25
@ghost ghost added this to the Next milestone Aug 3, 2022
@dibarbet dibarbet modified the milestones: Next, 17.4 P2 Sep 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support UnscopedRefAttribute to allow marking implicitly scoped ref parameters as unscoped
5 participants