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

Add CompilerGeneratedAttribute to record members #58542

Merged
merged 13 commits into from
Feb 8, 2022

Conversation

Youssef1313
Copy link
Member

@Youssef1313 Youssef1313 commented Dec 31, 2021

Fixes #46439
Fixes #47613

@ghost ghost added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Dec 31, 2021
@Youssef1313 Youssef1313 force-pushed the compiler-generated-attribute-records branch 2 times, most recently from 6ac551e to 7d5955a Compare December 31, 2021 17:14
@Youssef1313 Youssef1313 force-pushed the compiler-generated-attribute-records branch from 7d5955a to a9193c6 Compare December 31, 2021 19:29
@Youssef1313 Youssef1313 marked this pull request as ready for review December 31, 2021 19:36
@Youssef1313 Youssef1313 requested a review from a team as a code owner December 31, 2021 19:36
Comment on lines +60 to +67
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));
}
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 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.

Comment on lines 702 to 703
var equalityContract = record.GetMember("EqualityContract");
validateCompilerGeneratedAttribute(equalityContract);
Copy link
Member Author

@Youssef1313 Youssef1313 Jan 2, 2022

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

Copy link
Member Author

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?

Copy link
Member

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).

Copy link
Member

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

@jcouv jcouv added the Feature - Records Records label Jan 2, 2022
@jcouv jcouv self-assigned this Jan 18, 2022
@@ -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()
Copy link
Member

@jcouv jcouv Jan 18, 2022

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

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 6)

@Youssef1313 Youssef1313 requested a review from jcouv January 18, 2022 20:09
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));
}
Copy link
Member

@jcouv jcouv Jan 18, 2022

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

Copy link
Member Author

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?

Copy link
Member

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 :).

Copy link
Member

@jcouv jcouv Jan 20, 2022

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

Copy link
Member Author

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.

https://github.com/dotnet/runtime/blob/1d751ba8563fc099c6a75c41687d2f282a05f916/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerGeneratedAttribute.cs#L6

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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

@Youssef1313 Youssef1313 Jan 21, 2022

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.

Copy link
Member

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.

@Youssef1313 Youssef1313 requested review from jcouv and 333fred February 5, 2022 15:49
@Youssef1313
Copy link
Member Author

@333fred @jcouv This should hopefully be ready to merge.


var ctor = record.GetMembers(WellKnownMemberNames.InstanceConstructorName);
Assert.Equal(2, ctor.Length);
Assert.Equal(1, ctor[0].GetParameters().Length); // copy constructor
Copy link
Member

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

Copy link
Member Author

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.

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 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()
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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

Copy link
Member Author

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?

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.

Still LGTM (iteration 11)

@jcouv
Copy link
Member

jcouv commented Feb 8, 2022

@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);
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

image

Copy link
Member

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

Copy link
Member

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.

Copy link
Member

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());
            }
        }

Copy link
Member Author

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!

@jcouv jcouv enabled auto-merge (squash) February 8, 2022 19:33
@jcouv jcouv merged commit a3e3dc6 into dotnet:main Feb 8, 2022
@ghost ghost added this to the Next milestone Feb 8, 2022
@Youssef1313 Youssef1313 deleted the compiler-generated-attribute-records branch February 8, 2022 21:11
@RikkiGibson RikkiGibson modified the milestones: Next, 17.2.P2 Mar 1, 2022
@kofifus
Copy link

kofifus commented May 18, 2022

Looking in SharpLab this does not fixes the mentioned #46439 and #47613 as the following synthesized methods are still not decorated with [CompilerGenerated]:
The synthesized constructors, ToString, PrintMembers, operator !=, operator ==, GetHashCode, Equals, <Clone>$, Deconstruct

@Youssef1313
Copy link
Member Author

@kofifus Do you have a specific repro? I see the attribute in SharpLab:

image

@kofifus
Copy link

kofifus commented May 18, 2022

Oh I had to switch the branch from Default to Main !
Is that included in latest VS2022 preview ? doesn't seem to but I may have things wrong on my side ..

@Youssef1313
Copy link
Member Author

PR is set to 17.2 Preview 2

@kofifus
Copy link

kofifus commented May 18, 2022

Great (I think you mean 17.3)

@kofifus
Copy link

kofifus commented Jun 15, 2022

As far as I can tell this didn't make it to today's release of Version 17.3.0 Preview 2.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee. Feature - Records Records
Projects
None yet
6 participants