-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Add CompilerGeneratedAttribute to record members #58542
Add CompilerGeneratedAttribute to record members #58542
Conversation
6ac551e
to
7d5955a
Compare
7d5955a
to
a9193c6
Compare
internal override void AddSynthesizedAttributes(PEModuleBuilder moduleBuilder, ref ArrayBuilder<SynthesizedAttributeData> attributes) | ||
{ | ||
base.AddSynthesizedAttributes(moduleBuilder, ref attributes); | ||
Debug.Assert(IsImplicitlyDeclared); | ||
var compilation = this.DeclaringCompilation; | ||
AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); | ||
Debug.Assert(WellKnownMembers.IsSynthesizedAttributeOptional(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see a room for refactoring, since this block is repeated in many places (not only in this PR - but over the codebase)
For example, we can have a virtual property in Symbol
:
protected virtual bool SynthesizeCompilerGeneratedAttribute => false;
and in the already-existing empty virtual AddSynthesizedAttributes
method:
if (SynthesizeCompilerGeneratedAttribute)
{
// Add this repeated code block here.
}
Let me know if the refactoring worth it, and (if yes) whether you want it in this PR or a separate one.
var equalityContract = record.GetMember("EqualityContract"); | ||
validateCompilerGeneratedAttribute(equalityContract); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should both get_EqualityContract
and EqualityContract
members have the attribute? or only EqualityContract
property? Or only get_EqualityContract
(unlikely to be correct - but it's the current behavior)?
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@AlekseyTs Can you point out to the expected behavior here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's okay to only have the CompilerGenerated attribute on the property and not the accessor. It's implied that the accessor must be compiler-generated too (it wouldn't make sense to have an explicit accessor in an implicitly declared property).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please capture the behavior (lack of attribute on accessor) in test
src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Synthesized.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Synthesized.cs
Outdated
Show resolved
Hide resolved
@@ -652,6 +652,255 @@ internal class C2<[Attr] T2> { } | |||
Assert.Equal(new[] { "Attr" }, GetAttributeNames(typeParam.GetAttributes())); | |||
} | |||
|
|||
[Fact] | |||
[WorkItem(46439, "https://github.com/dotnet/roslyn/issues/46439")] | |||
public void RecordSynthesizedMembers() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Consider also validating that methods provided explicitly by the user don't get the compiler-generated attribute. #Closed
src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Synthesized.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit/Attributes/AttributeTests_Synthesized.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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 6)
var compilation = this.DeclaringCompilation; | ||
AddSynthesizedAttribute(ref attributes, compilation.TrySynthesizeAttribute(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); | ||
Debug.Assert(WellKnownMembers.IsSynthesizedAttributeOptional(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing this code moved (from accessor to property) and looking at the PR this came from (#57917, reviewed by Aleksey and Fred), I think the previous decision was to put the attribute on the accessor. Let's keep it that way. Sorry about that. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#57917 was intended to be (mostly) refactoring only (no behavioral changes around the attribute waa introduced).
I'm more convinced with the attribute being on the property.
@AlekseyTs @333fred What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not certain what precedent we have for where we put CompilerGenerated
. For example, in top-level statements, if we generate a closure type we don't put a CompilerGenerated
on it because the containing type has CompilerGenerated
. If the algorithm is "put it on the highest possible point", then putting it on the property instead of the accessors makes sense. If that's not the algorithm, then I'd appreciate knowing the algorithm :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this more, if we put the attribute on the property, then what happens if someone derives from the record and overrides the accessor (or adds a setter)?
So I think the attribute should be left on the accessor.
Relevant spec
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcouv That makes sense since CompilerGeneratedAttribute
is inherited.
Should I include the attribute on the property if the property isn't virtual (ie, the containing record is sealed)? or just keep it simple and leave on the accessor only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always putting it on the accessor only seems fine/simpler with me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic makes sense to me as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic makes sense to me as well.
@333fred Are you referring to the "always-accessor" or the "depends property virtual-ness" logic?
I have the "depends on property virtual-ness" logic locally. Let me know if I should push the commit or revert to only-accessor approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant @jcouv's logic.
|
||
var ctor = record.GetMembers(WellKnownMemberNames.InstanceConstructorName); | ||
Assert.Equal(2, ctor.Length); | ||
Assert.Equal(1, ctor[0].GetParameters().Length); // copy constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: you could do Assert.Equal("....", ctor[0].ToTestDisplayString());
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcouv I addressed the comment in last commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 10)
@@ -652,6 +652,279 @@ internal class C2<[Attr] T2> { } | |||
Assert.Equal(new[] { "Attr" }, GetAttributeNames(typeParam.GetAttributes())); | |||
} | |||
|
|||
[Fact] | |||
[WorkItem(46439, "https://github.com/dotnet/roslyn/issues/46439")] | |||
public void RecordSynthesizedMembers() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have tests validating the behavior when the CompilerGenerated
attribute is missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jaredpar I added the test, but I'm not sure if there is an existing test already somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be a legitimate test failure with AttributeTests_Synthesized.AttributeIsMissing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jcouv I'm not familiar with PEVerify issues/failures. Should I skip verification or there is something else to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still LGTM (iteration 11)
@jaredpar for second approval. |
@@ -984,7 +984,7 @@ public StringBuilder Append(string s) | |||
|
|||
"; | |||
var comp = CreateEmptyCompilation(source); | |||
CompileAndVerify(comp, symbolValidator: validate); | |||
CompileAndVerify(comp, symbolValidator: validate, verify: Verification.Skipped); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the verification error that was skipped here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using an empty compilation, I'd recommend using CreateCompilation
then MakeTypeMissing
. Then I don't think we'll need to skip PE verification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that type of PE verification failure is generally a real failure. It makes me worried that we missed a case and are attempting to emit a reference to the attribute when it doesn't exist. Think @jcouv approach is correct here to use MakeTypeMissing
that will help rule out that problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This worked:
[Fact]
[WorkItem(46439, "https://github.com/dotnet/roslyn/issues/46439")]
public void AttributeIsMissing()
{
string source = @"
record struct R;
";
var comp = CreateCompilation(source);
comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_CompilerGeneratedAttribute);
var verifier = CompileAndVerify(comp, symbolValidator: validate);
verifier.VerifyDiagnostics();
void validate(ModuleSymbol module)
{
var record = module.GlobalNamespace.GetTypeMember("R");
Assert.Equal(7, record.GetMembers().Length); // If a new record member is added, extend the test with its behavior regarding CompilerGeneratedAttribute.
var ctor = record.GetMember(WellKnownMemberNames.InstanceConstructorName);
Assert.Empty(ctor.GetAttributes());
var toString = record.GetMember(WellKnownMemberNames.ObjectToString);
Assert.Empty(toString.GetAttributes());
var op_Equality = record.GetMember(WellKnownMemberNames.EqualityOperatorName);
Assert.Empty(op_Equality.GetAttributes());
var op_Inequality = record.GetMember(WellKnownMemberNames.InequalityOperatorName);
Assert.Empty(op_Inequality.GetAttributes());
var getHashCode = record.GetMember(WellKnownMemberNames.ObjectGetHashCode);
Assert.Empty(getHashCode.GetAttributes());
var equals = record.GetMembers(WellKnownMemberNames.ObjectEquals);
Assert.Equal(2, equals.Length);
Assert.Empty(equals[0].GetAttributes());
Assert.Empty(equals[1].GetAttributes());
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! I didn't know MakeTypeMissing
is a thing!
Oh I had to switch the branch from |
PR is set to 17.2 Preview 2 |
Great (I think you mean 17.3) |
As far as I can tell this didn't make it to today's release of Version 17.3.0 Preview 2.0 |
Fixes #46439
Fixes #47613