-
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
Records: allow positional members to be implemented with fields #52480
Conversation
src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs
Outdated
Show resolved
Hide resolved
Was this test judged to no longer be useful? I expected the behavior to be unchanged here since we consider
src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs (Line 1911) edit: This seems to be how CodeStream allows commenting on unchanged lines. Seeing it for the first time. Interesting. #Resolved |
src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs
Outdated
Show resolved
Hide resolved
I'd removed it because I thought it was duplicate and wrong name. But In reply to: 818381854 [](ancestors = 818381854) |
|
||
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 comment
The 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?
#Resolved
} | ||
else | ||
{ | ||
isInherited = true; |
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 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 comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't set isInherited
to true
when a field was found from base types here, because we may find a field on the current type (which would be preferred and is not inherited). #Resolved
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
case FieldSymbol: [](start = 19, length = 18)
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
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 comment
The reason will be displayed to describe this comment to others. Learn more.
existingMember is FieldSymbol { IsStatic: false } field [](start = 29, length = 55)
Are we making sure the type matches? Where? #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.
@@ -3743,12 +3744,42 @@ ImmutableArray<PropertySymbol> addProperties(ImmutableArray<ParameterSymbol> rec | |||
if (!memberSignatures.TryGetValue(targetProperty, out var existingMember)) | |||
{ | |||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
if (existingMember is FieldSymbol) [](start = 24, length = 34)
The logic feels too convoluted and I think it can be significantly simplified as follows:
- First, we look for a matching property in the current record;
- Then we look for a matching field in the current record;
- Then we look for a first hidden member in base #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.
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).
That said, I think we can achieve that by calling FindFirstHiddenMemberIfAny
only once and keeping the result.
public record Base
{
public int X { get; set; }
}
public record C(int X) : Base
{
public new int X;
}
In reply to: 612815137 [](ancestors = 612815137)
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.
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).
I don't think this behavior is sound, even though it avoids a breaking change.
In reply to: 612935164 [](ancestors = 612935164,612815137)
/// 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 |
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.
SignatureOnlyFieldSymbol [](start = 26, length = 24)
It feels like we can achieve the goal without this type. #Closed
comp.VerifyDiagnostics( | ||
// (6,9): error CS0102: The type 'C' already contains a definition for 'X' | ||
comp.VerifyEmitDiagnostics( | ||
// (4,10): error CS8652: The feature 'positional fields in records' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. |
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.
// (4,10): error CS8652: The feature 'positional fields in records' is currently in Preview and unsupported. To use Preview features, use the 'preview' language version. [](start = 16, length = 171)
Should we use explicit language version for this compilation so that the test doesn't break once default version is changed? It looks like this comment is applicable to other tests too. #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.
I checked a few such tests and they all use the CreateCompilation
defined in this file with explicit language version 9.
In reply to: 612822814 [](ancestors = 612822814)
.maxstack 2 | ||
IL_0000: ldarg.1 | ||
IL_0001: ldarg.0 | ||
IL_0002: call ""int Base.I.get"" |
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.
IL_0002: call ""int Base.I.get"" [](start = 2, length = 39)
This looks unexpected. I think C.I
should be used here. #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.
And that is probably going to be a breaking change.
In reply to: 612824515 [](ancestors = 612824515)
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 looks like you intentionally tried to preserve previous behavior, but I am not sure if the proposed behavior is the right one to have and whether we explicitly approved it at LDM.
In reply to: 612824890 [](ancestors = 612824890,612824515)
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.
Added an open LDM issue to test plan.
Untill this is settled, I do not think we should merge this sub-feature.
In reply to: 612941755 [](ancestors = 612941755,612828289,612824890,612824515)
|
||
public record Base | ||
{ | ||
public int I = 42; |
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.
int [](start = 11, length = 3)
Are we testing scenario with a type mismatch? #Closed
|
||
public record Base | ||
{ | ||
public int I { get; set; } = 42; |
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.
public int I { get; set; } = 42; [](start = 4, length = 32)
Are we testing scenario when base has a field and derived has a property? #Closed
} | ||
record C3(int P) | ||
{ | ||
internal int P = P; |
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.
int [](start = 13, length = 3)
Are we testing type mismatch scenario? #Closed
|
||
record struct A(int X) | ||
{ | ||
public int X = X; |
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.
int [](start = 11, length = 3)
Are we testing type mismatch case? #Closed
Done with review pass (commit 4) #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.
LGTM (commit 6)
existingMember = inheritedMember; | ||
} | ||
// There should be an error if we picked a member that is hidden | ||
// This will be fixed in C# 9 as part of 16.10. Tracked by https://github.com/dotnet/roslyn/issues/52630 |
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 plan to do this after feature merge?
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.
Yes. I'll make the breaking change in 16.10 and both changes will meet in features/compiler
.
if (!fieldsByName.ContainsKey(fieldName)) | ||
{ | ||
fieldsByName.Add(fieldName, member); | ||
} |
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: it feels like this could be replaced with fieldsByName[fieldName] = member;
(in error scenarios with multiple identically named fields, which one we pick feels unimportant).
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.
If it's okay, I'll leave as-is. Thanks
Test plan: #51199
Opened issue #52630 for disallowing hidden members to be picked for positional usage.
Spec change: dotnet/csharplang#4673