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 struct records #43133

Merged
merged 5 commits into from
Apr 23, 2020
Merged

Support struct records #43133

merged 5 commits into from
Apr 23, 2020

Conversation

agocke
Copy link
Member

@agocke agocke commented Apr 7, 2020

I believe this should work according to the spec and am just cleaning
up some implementation gaps.

Relates to #40726 (test plan for records)

I believe this should work according to the spec and am just cleaning
up some implementation gaps.
@agocke agocke changed the title Record struct Support struct records Apr 7, 2020
@agocke agocke marked this pull request as ready for review April 7, 2020 18:39
@agocke agocke requested a review from a team as a code owner April 7, 2020 18:39
@gafter
Copy link
Member

gafter commented Apr 7, 2020

I see there were some recent changes to the spec which have issues (i.e. don't make sense). I cannot figure out how to add comments on those changes as there was never a PR that I could view. I don't think it is implementable as written (e.g. there is no way to "suppress" the struct default constructor on the CLR). So consider this a comment that the implementation here doesn't do that as specified. #Resolved

@agocke
Copy link
Member Author

agocke commented Apr 7, 2020

@gafter I don't follow. There's no way to suppress struct default construction but we can certainly prevent surfacing a default constructor. The spec change is that the struct parameterless constructor for a record is no longer visible. #Resolved

@gafter
Copy link
Member

gafter commented Apr 7, 2020

The C# compiler currently never produces a default constructor for a struct, so I don't know what there is to suppress (or make not visible). #Resolved

@agocke
Copy link
Member Author

agocke commented Apr 7, 2020

Ah, I see, on metadata import there's no way to tell that the struct is a record, and therefore no way to avoid creating the symbol. Good point. I'll add a test for this and adjust the spec. #Resolved

@gafter
Copy link
Member

gafter commented Apr 7, 2020

As far as I know we don't create a symbol for a struct's default constructor, but yes, you have the idea. But the problem occurs purely in source too. A record struct would satisfy a type parameter's struct constraint, producing a type parameter that would satisfy another type parameter's new() constraint. #Resolved

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Apr 7, 2020

As far as I know we don't create a symbol for a struct's default constructor

I think we do:

                        // Structs have an implicit parameterless constructor, even if it
                        // does not appear in metadata (11.3.8)
                        if (!haveParameterlessConstructor)
                        {
                            nonFieldMembers.Insert(0, new SynthesizedInstanceConstructor(this));
                        }
``` #Resolved

@gafter gafter self-requested a review April 8, 2020 01:15
@agocke
Copy link
Member Author

agocke commented Apr 9, 2020

I've adjusted the spec and implementation to preserve default struct parameterless constructors #Resolved

@@ -1153,7 +1153,8 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
// don't emit if the resulting method would contain initializers with errors
if (!hasErrors && (hasBody || includeInitializersInBody))
{
Debug.Assert(!methodSymbol.IsImplicitInstanceConstructor || !methodSymbol.ContainingType.IsStructType());
Debug.Assert(!(methodSymbol.IsImplicitInstanceConstructor && methodSymbol.ParameterCount == 0) ||
Copy link
Member

@gafter gafter Apr 10, 2020

Choose a reason for hiding this comment

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

IsImplicitInstanceConstructor [](start = 52, length = 29)

I think the condition IsImplicitInstanceConstructor generally implies methodSymbol.ParameterCount == 0, so you've weakened the assertion in case there is a mismatch there. #Resolved

Copy link
Member Author

@agocke agocke Apr 10, 2020

Choose a reason for hiding this comment

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

Not as far as I can tell: http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Symbols/MethodSymbol.cs,fb6aebe573e152e2,references

That is satisfied for every record constructor as well, so all record constructors are now implicit instance constructors #Resolved

Copy link
Member

Choose a reason for hiding this comment

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

Are there any other implicit instance constructors though? AFAIK, there's only the default parameterless one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Record constructors!

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough.

Copy link
Member

Choose a reason for hiding this comment

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

Thankfully there's only one use of this property (in this assert), so we don't have to worry about that contract being violated elsewhere.

@@ -143,6 +143,9 @@ public bool SetNullableContext(byte? value)
}
}

private static readonly ObjectPool<PooledDictionary<Symbol, Symbol>> s_duplicateMemberSignatureDictionary =
Copy link
Member

@gafter gafter Apr 10, 2020

Choose a reason for hiding this comment

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

s_duplicateMemberSignatureDictionary [](start = 77, length = 36)

Why do you need a custom pool instead of using PooledDictionary<Symbol, Symbol>.GetInstance()? #Resolved

Copy link
Member Author

@agocke agocke Apr 10, 2020

Choose a reason for hiding this comment

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

Need a custom comparer #Resolved

public void StructRecord1()
{
var src = @"
data struct Point(int X, int Y);";
Copy link
Member

Choose a reason for hiding this comment

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

data struct [](start = 0, length = 11)

I took a "data structures" class once. 😉

Copy link
Member

@gafter gafter left a comment

Choose a reason for hiding this comment

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

:shipit:

{
// For structs:
//
// return param is ContainingType i ? this.Equals(in i) : false;
Copy link
Member

@alrz alrz Apr 11, 2020

Choose a reason for hiding this comment

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

param is ContainingType i && this.Equals(in i)?

@333fred
Copy link
Member

333fred commented Apr 22, 2020

Done review pass (commit 3). Just a couple of small points.

@agocke agocke merged commit e232f50 into dotnet:features/records Apr 23, 2020
@agocke agocke deleted the record-struct branch April 23, 2020 00:39
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.

5 participants