-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Support struct records #43133
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -143,6 +143,9 @@ public bool SetNullableContext(byte? value) | |
} | ||
} | ||
|
||
private static readonly ObjectPool<PooledDictionary<Symbol, Symbol>> s_duplicateMemberSignatureDictionary = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why do you need a custom pool instead of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a custom comparer #Resolved |
||
PooledDictionary<Symbol, Symbol>.CreatePool(MemberSignatureComparer.DuplicateSourceComparer); | ||
|
||
protected SymbolCompletionState state; | ||
|
||
private Flags _flags; | ||
|
@@ -2922,20 +2925,26 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde | |
BinderFactory binderFactory = this.DeclaringCompilation.GetBinderFactory(paramList.SyntaxTree); | ||
var binder = binderFactory.GetBinder(paramList); | ||
|
||
var memberSignatures = new HashSet<Symbol>(members, MemberSignatureComparer.DuplicateSourceComparer); | ||
var memberSignatures = s_duplicateMemberSignatureDictionary.Allocate(); | ||
foreach (var member in members) | ||
{ | ||
memberSignatures.Add(member, member); | ||
} | ||
|
||
var ctor = addCtor(paramList); | ||
addProperties(ctor.Parameters); | ||
addObjectEquals(); | ||
var thisEquals = addThisEquals(); | ||
addObjectEquals(thisEquals); | ||
addHashCode(); | ||
addThisEquals(); | ||
|
||
memberSignatures.Free(); | ||
|
||
return; | ||
|
||
SynthesizedRecordConstructor addCtor(ParameterListSyntax paramList) | ||
{ | ||
var ctor = new SynthesizedRecordConstructor(this, binder, paramList, diagnostics); | ||
if (!memberSignatures.Contains(ctor)) | ||
if (!memberSignatures.ContainsKey(ctor)) | ||
{ | ||
members.Add(ctor); | ||
} | ||
|
@@ -2952,7 +2961,7 @@ void addProperties(ImmutableArray<ParameterSymbol> recordParameters) | |
foreach (ParameterSymbol param in ctor.Parameters) | ||
{ | ||
var property = new SynthesizedRecordPropertySymbol(this, param); | ||
if (!memberSignatures.Contains(property)) | ||
if (!memberSignatures.ContainsKey(property)) | ||
{ | ||
members.Add(property); | ||
members.Add(property.GetMethod); | ||
|
@@ -2961,10 +2970,10 @@ void addProperties(ImmutableArray<ParameterSymbol> recordParameters) | |
} | ||
} | ||
|
||
void addObjectEquals() | ||
void addObjectEquals(MethodSymbol thisEquals) | ||
{ | ||
var objEquals = new SynthesizedRecordObjEquals(this); | ||
if (!memberSignatures.Contains(objEquals)) | ||
var objEquals = new SynthesizedRecordObjEquals(this, thisEquals); | ||
if (!memberSignatures.ContainsKey(objEquals)) | ||
333fred marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
// PROTOTYPE: Don't add if the overridden method is sealed | ||
members.Add(objEquals); | ||
|
@@ -2974,32 +2983,27 @@ void addObjectEquals() | |
void addHashCode() | ||
{ | ||
var hashCode = new SynthesizedRecordGetHashCode(this); | ||
if (!memberSignatures.Contains(hashCode)) | ||
if (!memberSignatures.ContainsKey(hashCode)) | ||
{ | ||
// PROTOTYPE: Don't add if the overridden method is sealed | ||
members.Add(hashCode); | ||
} | ||
} | ||
|
||
void addThisEquals() | ||
MethodSymbol addThisEquals() | ||
{ | ||
var thisEquals = new SynthesizedRecordEquals(this); | ||
if (!memberSignatures.Contains(thisEquals)) | ||
if (!memberSignatures.TryGetValue(thisEquals, out var existing)) | ||
{ | ||
members.Add(thisEquals); | ||
return thisEquals; | ||
} | ||
return (MethodSymbol)existing; | ||
} | ||
} | ||
|
||
private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder<Symbol> members, ArrayBuilder<ImmutableArray<FieldOrPropertyInitializer>> staticInitializers, DiagnosticBag diagnostics) | ||
{ | ||
switch (declaration.Kind) | ||
{ | ||
case DeclarationKind.Class: | ||
case DeclarationKind.Struct: | ||
break; | ||
} | ||
|
||
//we're not calling the helpers on NamedTypeSymbol base, because those call | ||
//GetMembers and we're inside a GetMembers call ourselves (i.e. stack overflow) | ||
var hasInstanceConstructor = false; | ||
|
@@ -3036,7 +3040,8 @@ private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder<Symbol> members, | |
// NOTE: Per section 11.3.8 of the spec, "every struct implicitly has a parameterless instance constructor". | ||
// We won't insert a parameterless constructor for a struct if there already is one. | ||
// We don't expect anything to be emitted, but it should be in the symbol table. | ||
if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || (!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) | ||
if ((!hasParameterlessInstanceConstructor && this.IsStructType()) || | ||
(!hasInstanceConstructor && !this.IsStatic && !this.IsInterface)) | ||
{ | ||
members.Add((this.TypeKind == TypeKind.Submission) ? | ||
new SynthesizedSubmissionConstructor(this, diagnostics) : | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,16 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols | |
{ | ||
internal sealed class SynthesizedRecordObjEquals : SynthesizedInstanceMethodSymbol | ||
{ | ||
private readonly MethodSymbol _typedRecordEquals; | ||
|
||
public override NamedTypeSymbol ContainingType { get; } | ||
|
||
public override ImmutableArray<ParameterSymbol> Parameters { get; } | ||
|
||
public SynthesizedRecordObjEquals(NamedTypeSymbol containingType) | ||
|
||
public SynthesizedRecordObjEquals(NamedTypeSymbol containingType, MethodSymbol typedRecordEquals) | ||
{ | ||
_typedRecordEquals = typedRecordEquals; | ||
ContainingType = containingType; | ||
Parameters = ImmutableArray.Create<ParameterSymbol>(SynthesizedParameterSymbol.Create( | ||
this, | ||
|
@@ -120,13 +124,32 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, | |
{ | ||
var F = new SyntheticBoundNodeFactory(this, ContainingType.GetNonNullSyntaxNode(), compilationState, diagnostics); | ||
|
||
// return this.Equals(param as ContainingType); | ||
var block = F.Block(ImmutableArray.Create<BoundStatement>( | ||
F.Return( | ||
F.InstanceCall(F.This(), "Equals", | ||
F.As(F.Parameter(Parameters[0]), ContainingType))))); | ||
|
||
F.CloseMethod(block); | ||
var paramAccess = F.Parameter(Parameters[0]); | ||
|
||
BoundExpression expression; | ||
if (ContainingType.IsStructType()) | ||
{ | ||
// For structs: | ||
// | ||
// return param is ContainingType i ? this.Equals(in i) : false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
expression = F.Conditional( | ||
F.Is(paramAccess, ContainingType), | ||
F.Call( | ||
F.This(), | ||
_typedRecordEquals, | ||
ImmutableArray.Create<RefKind>(RefKind.In), | ||
333fred marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ImmutableArray.Create<BoundExpression>(F.Convert(ContainingType, paramAccess))), | ||
F.Literal(false), | ||
F.SpecialType(SpecialType.System_Boolean)); | ||
} | ||
else | ||
{ | ||
// For classes: | ||
// return this.Equals(param as ContainingType); | ||
expression = F.InstanceCall(F.This(), "Equals", F.As(paramAccess, ContainingType)); | ||
} | ||
|
||
F.CloseMethod(F.Block(ImmutableArray.Create<BoundStatement>(F.Return(expression)))); | ||
} | ||
} | ||
} |
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 the condition
IsImplicitInstanceConstructor
generally impliesmethodSymbol.ParameterCount == 0
, so you've weakened the assertion in case there is a mismatch there. #ResolvedThere 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.
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
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.
Are there any other implicit instance constructors though? AFAIK, there's only the default parameterless one.
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.
Record constructors!
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.
Fair enough.
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.
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.