-
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
Records: allow positional members to be implemented with fields #52480
Changes from 3 commits
fb4765c
54271fa
5a9bec3
1f026b2
cddf8ab
cda3204
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 |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Collections.Immutable; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.Symbols | ||
{ | ||
/// <summary> | ||
/// A representation of a field symbol that is intended only to be used for comparison purposes | ||
/// (esp in MemberSignatureComparer). | ||
/// </summary> | ||
internal sealed class SignatureOnlyFieldSymbol : FieldSymbol | ||
{ | ||
private readonly string _name; | ||
private readonly TypeSymbol _containingType; | ||
private readonly TypeWithAnnotations _type; | ||
|
||
public SignatureOnlyFieldSymbol( | ||
string name, | ||
TypeSymbol containingType, | ||
TypeWithAnnotations type) | ||
{ | ||
_type = type; | ||
_containingType = containingType; | ||
_name = name; | ||
} | ||
|
||
public override string Name => _name; | ||
|
||
internal override TypeWithAnnotations GetFieldType(ConsList<FieldSymbol> fieldsBeingBound) => _type; | ||
|
||
public override Symbol ContainingSymbol => _containingType; | ||
|
||
#region Not used by MemberSignatureComparer | ||
public override bool IsReadOnly => throw ExceptionUtilities.Unreachable; | ||
|
||
public override bool IsStatic => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override bool HasSpecialName => throw ExceptionUtilities.Unreachable; | ||
|
||
public override ImmutableArray<Location> Locations => throw ExceptionUtilities.Unreachable; | ||
|
||
public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences => throw ExceptionUtilities.Unreachable; | ||
|
||
public override Accessibility DeclaredAccessibility => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override ObsoleteAttributeData ObsoleteAttributeData => throw ExceptionUtilities.Unreachable; | ||
|
||
public override AssemblySymbol ContainingAssembly => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override ModuleSymbol ContainingModule => throw ExceptionUtilities.Unreachable; | ||
|
||
public override FlowAnalysisAnnotations FlowAnalysisAnnotations => throw ExceptionUtilities.Unreachable; | ||
|
||
public override Symbol AssociatedSymbol => throw ExceptionUtilities.Unreachable; | ||
|
||
public override bool IsVolatile => throw ExceptionUtilities.Unreachable; | ||
|
||
public override bool IsConst => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override bool HasRuntimeSpecialName => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override bool IsNotSerialized => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override MarshalPseudoCustomAttributeData MarshallingInformation => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override int? TypeLayoutOffset => throw ExceptionUtilities.Unreachable; | ||
|
||
internal override ConstantValue GetConstantValue(ConstantFieldsInProgress inProgress, bool earlyDecodingWellKnownAttributes) => throw new System.NotImplementedException(); | ||
|
||
#endregion Not used by MemberSignatureComparer | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3466,7 +3466,6 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde | |
{ | ||
switch (member) | ||
{ | ||
case FieldSymbol: | ||
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.
It looks like for fields the only thing we are going to compare is the Name. I think we should consider simply using a separate dictionary for that and avoiding an introduction of SignatureOnlyFieldSymbol with the only purpose to lookup a field by name. #Closed |
||
case EventSymbol: | ||
case MethodSymbol { MethodKind: not (MethodKind.Ordinary or MethodKind.Constructor) }: | ||
continue; | ||
|
@@ -3542,8 +3541,10 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde | |
|
||
return; | ||
|
||
void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray<PropertySymbol> properties) | ||
void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray<Symbol> positionalMembers) | ||
{ | ||
Debug.Assert(positionalMembers.All(p => p is PropertySymbol or FieldSymbol)); | ||
|
||
var targetMethod = new SignatureOnlyMethodSymbol( | ||
WellKnownMemberNames.DeconstructMethodName, | ||
this, | ||
|
@@ -3563,7 +3564,7 @@ void addDeconstruct(SynthesizedRecordConstructor ctor, ImmutableArray<PropertySy | |
|
||
if (!memberSignatures.TryGetValue(targetMethod, out Symbol? existingDeconstructMethod)) | ||
{ | ||
members.Add(new SynthesizedRecordDeconstruct(this, ctor, properties, memberOffset: members.Count, diagnostics)); | ||
members.Add(new SynthesizedRecordDeconstruct(this, ctor, positionalMembers, memberOffset: members.Count, diagnostics)); | ||
} | ||
else | ||
{ | ||
|
@@ -3722,9 +3723,9 @@ void addToStringMethod(MethodSymbol printMethod) | |
} | ||
} | ||
|
||
ImmutableArray<PropertySymbol> addProperties(ImmutableArray<ParameterSymbol> recordParameters) | ||
ImmutableArray<Symbol> addProperties(ImmutableArray<ParameterSymbol> recordParameters) | ||
{ | ||
var existingOrAddedMembers = ArrayBuilder<PropertySymbol>.GetInstance(recordParameters.Length); | ||
var existingOrAddedMembers = ArrayBuilder<Symbol>.GetInstance(recordParameters.Length); | ||
int addedCount = 0; | ||
foreach (ParameterSymbol param in recordParameters) | ||
{ | ||
|
@@ -3743,12 +3744,42 @@ ImmutableArray<PropertySymbol> addProperties(ImmutableArray<ParameterSymbol> rec | |
if (!memberSignatures.TryGetValue(targetProperty, out var existingMember)) | ||
RikkiGibson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
existingMember = OverriddenOrHiddenMembersHelpers.FindFirstHiddenMemberIfAny(targetProperty, memberIsFromSomeCompilation: true); | ||
isInherited = true; | ||
if (existingMember is FieldSymbol) | ||
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.
The logic feels too convoluted and I think it can be significantly simplified as follows:
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. We shouldn't look at the base last though. A property from the base should win over a field from the current type, to avoid breaking existing scenario (below).
In reply to: 612815137 [](ancestors = 612815137) 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.
I don't think this behavior is sound, even though it avoids a breaking change. In reply to: 612935164 [](ancestors = 612935164,612815137) |
||
{ | ||
// field from base type should not be considered before we look at fields from current type | ||
existingMember = null; | ||
} | ||
else | ||
{ | ||
isInherited = true; | ||
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. I didn't understand why we avoid setting this for fields. In this case, the field is also inherited, right? We don't use it, but at the same time it seems simpler if we set it unconditionally. #Resolved 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. We shouldn't set |
||
} | ||
} | ||
|
||
if (existingMember is null) | ||
{ | ||
var targetField = new SignatureOnlyFieldSymbol(param.Name, | ||
this, | ||
param.TypeWithAnnotations); | ||
|
||
if (!memberSignatures.TryGetValue(targetField, out existingMember)) | ||
{ | ||
existingMember = OverriddenOrHiddenMembersHelpers.FindFirstHiddenMemberIfAny(targetField, memberIsFromSomeCompilation: true); | ||
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. Does this method return a different value when called with this targetField than when it is called with the targetProperty above? |
||
if (existingMember is not FieldSymbol) | ||
{ | ||
existingMember = null; | ||
} | ||
} | ||
} | ||
|
||
if (existingMember is null) | ||
{ | ||
addProperty(new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: false, diagnostics)); | ||
} | ||
else if (existingMember is FieldSymbol { IsStatic: false } field) | ||
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.
Are we making sure the type matches? Where? #Closed 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. |
||
{ | ||
Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeaturePositionalFieldsInRecords, diagnostics); | ||
existingOrAddedMembers.Add(field); | ||
} | ||
else if (existingMember is PropertySymbol { IsStatic: false, GetMethod: { } } prop | ||
&& prop.TypeWithAnnotations.Equals(param.TypeWithAnnotations, TypeCompareKind.AllIgnoreOptions)) | ||
{ | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
It feels like we can achieve the goal without this type. #Closed