-
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
Don't wrap fields in ValueTuple definitions #44231
Conversation
b06ad5e
to
881e92d
Compare
@@ -541,8 +541,7 @@ private void CompileNamedType(NamedTypeSymbol containingType) | |||
|
|||
case SymbolKind.Field: | |||
{ | |||
var field = (FieldSymbol)member; | |||
var fieldSymbol = (field.TupleUnderlyingField ?? field) as SourceMemberFieldSymbol; | |||
FieldSymbol fieldSymbol = member as FieldSymbol; | |||
if ((object)fieldSymbol != null) |
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.
Can use explicit cast and avoid the null
check. #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.
Could also do
if (member is FieldSymbol fieldSymbol)
``` #Resolved
@@ -520,7 +520,8 @@ private void AddTupleTypeName(INamedTypeSymbol symbol) | |||
} | |||
|
|||
VisitFieldType(element); | |||
if (!element.IsImplicitlyDeclared) | |||
if (!element.IsImplicitlyDeclared && | |||
!(symbol.IsGenericType && symbol.IsDefinition)) // C# tuples are generic |
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 we checking if this is a default field rather than a named field? If so, is there a more explicit way to do that check that is language-independent? Perhaps element.CorrespondingTupleField != null
. #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.
I've tried to think of different approaches, and tried a few, but this is really tricky.
We don't want to display the Item1
in VT'2
(which is !IsImplicitlyDeclared
, but has a CorrespondingTupleField
).
Also checks are whether fields are default are not don't work, as (int Item1, int).Item1
is a default field even though explicitly named.
So ended up sticking with this check. #ByDesign
@@ -399,7 +400,7 @@ public virtual bool IsDefaultTupleElement | |||
{ | |||
get | |||
{ | |||
return false; | |||
return ContainingType.IsDefinition && TupleElementIndex >= 0; |
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.
ContainingType.IsDefinition [](start = 23, length = 27)
TupleElementIndex
includes this check. #Resolved
@@ -424,7 +425,7 @@ public virtual FieldSymbol CorrespondingTupleField | |||
{ | |||
get | |||
{ | |||
return null; | |||
return (ContainingType.IsDefinition && TupleElementIndex >= 0) ? this : null; |
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.
ContainingType.IsDefinition [](start = 24, length = 27)
TupleElementIndex
includes this check. #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.
That property is virtual
though. Do we feel comfortable depending on all the overrides also enforcing this behavior? #Resolved
@@ -445,6 +446,25 @@ public virtual int TupleElementIndex | |||
{ | |||
get | |||
{ | |||
if (ContainingType.IsTupleType && ContainingType.IsDefinition) |
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.
ContainingType.IsDefinition [](start = 50, length = 27)
Why is it important that the containing type is the definition? #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.
In thinking about this question, I realized it's not. This implementation should handle the underlying fields in a constructed tuple. Will add test and fix
In reply to: 425344464 [](ancestors = 425344464)
if (ContainingType.IsTupleType && ContainingType.IsDefinition) | ||
{ | ||
var i = NamedTypeSymbol.MatchesCanonicalElementName(Name); | ||
int itemsPerType = Math.Min(ContainingType.Arity, NamedTypeSymbol.ValueTupleRestPosition - 1); |
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.
itemsPerType [](start = 24, length = 12)
Consider inlining this to only calculate when i > 0
. #Resolved
|
||
RuntimeMembers.MemberDescriptor relativeDescriptor = WellKnownMembers.GetDescriptor(wellKnownMember); | ||
var found = CSharpCompilation.GetRuntimeMember(ImmutableArray.Create<Symbol>(this), relativeDescriptor, CSharpCompilation.SpecialMembersSignatureComparer.Instance, | ||
accessWithinOpt: null); // force lookup of public members only |
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.
This looks expensive. Can we calculate TupleElementIndex
at most once? (Perhaps this property should be implemented in derived types where it might make more sense to cache the value.) #ByDesign
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 want to add state to every source or PE symbol just because it might be a tuple element.
By limiting this to definitions of ValueTuple and doing a cheap name check first, I think this becomes reasonable.
In reply to: 425346752 [](ancestors = 425346752)
@@ -1279,14 +1279,18 @@ internal override IEnumerable<FieldSymbol> GetFieldsToEmit() | |||
switch (m.Kind) | |||
{ | |||
case SymbolKind.Field: | |||
var field = (FieldSymbol)m; | |||
yield return field.TupleUnderlyingField ?? field; | |||
if (m is TupleErrorFieldSymbol) |
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 (m is TupleErrorFieldSymbol) [](start = 24, length = 31)
When do we try to emit with error fields? #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.
struct ValueTuple<T1, T2> { }
This is recognized as a 2-tuple, and thus has Item1
and Item2
In reply to: 425348604 [](ancestors = 425348604)
if (m is TupleErrorFieldSymbol) | ||
{ | ||
break; | ||
} |
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.
Is this needed? #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.
For empty structs, we emit some layout information ([StructLayout(LayoutKind.Sequential, Size = 1)]
).
Without this change, we don't emit this layout information correctly, and so PEVerification fails (struct must have a type or a size).
In reply to: 425349126 [](ancestors = 425349126)
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.
Why do we have an error field in a valid struct?
In reply to: 425410620 [](ancestors = 425410620,425349126)
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.
Because a type System.ValueTuple'2
is recognized as a tuple, and therefore has two fields Item1
and Item2
that can be accessed regardless of what the source says. We make them error fields because they lack real/backing fields.
Similarly, on an 8-tuple, we have an Item8
(an error field) if ValueTuple'1
lacks an Item1
.
In reply to: 425411648 [](ancestors = 425411648,425410620,425349126)
{ | ||
// No need to wrap fields on System.ValueTuple definitions | ||
members.Add(field); | ||
int tupleFieldIndex2 = currentFieldsForElements.IndexOf(field, ReferenceEqualityComparer.Instance); |
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.
currentFieldsForElements [](start = 55, length = 24)
Consider using a Dictionary<Symbol, int>
for currentFieldsForElements
. #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.
Never mind. This is the same lookup we were performing previously, and there are at most 8 items in the collection.
In reply to: 425355278 [](ancestors = 425355278)
@@ -26463,6 +26464,7 @@ void verifyTupleTypeWithErrorUnderlyingType(CSharpCompilation compilation, bool | |||
[WorkItem(41207, "https://github.com/dotnet/roslyn/issues/41207")] | |||
[WorkItem(1056281, "https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1056281")] | |||
[WorkItem(43549, "https://github.com/dotnet/roslyn/issues/43549")] |
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.
[WorkItem(43549, "https://github.com//issues/43549")] [](start = 8, length = 66)
Duplicate. Same comment for other tests below. #Resolved
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.CSharp.Test.Utilities; | ||
using Microsoft.CodeAnalysis.Test.Utilities; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Roslyn.Test.Utilities; | ||
using Roslyn.Utilities; | ||
using TestResources.NetFX; |
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.
TestResources.NetFX [](start = 6, length = 19)
Is this used? #Resolved
Done with review pass (commit 31) #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 32)
@dotnet/roslyn-compiler for a second review. I'm happy to jump on a call and walk you through if needed. Thanks |
1 similar comment
@dotnet/roslyn-compiler for a second review. I'm happy to jump on a call and walk you through if needed. Thanks |
@333fred can u take a look at this? |
Gentle ping for second review. Thanks |
@333fred can u take a look |
@333fred Can you review this week? Thanks |
Debug.Assert(!fieldSymbol.IsVirtualTupleField && (object)(fieldSymbol.TupleUnderlyingField ?? fieldSymbol) == fieldSymbol, "tuple fields should be rewritten to underlying by now"); | ||
Debug.Assert(!fieldSymbol.IsVirtualTupleField && | ||
(object)(fieldSymbol.TupleUnderlyingField ?? fieldSymbol) == fieldSymbol && | ||
!(fieldSymbol is TupleErrorFieldSymbol), "tuple fields should be rewritten to underlying by now"); |
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.
!(fieldSymbol is TupleErrorFieldSymbol) [](start = 16, length = 39)
Nit: consider is not
. #Resolved
@@ -405,6 +405,16 @@ public virtual bool IsDefaultTupleElement | |||
{ | |||
get | |||
{ | |||
Debug.Assert(!(this is TupleElementFieldSymbol) && !(this is TupleErrorFieldSymbol)); |
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.
!(this is TupleElementFieldSymbol) && !(this is TupleErrorFieldSymbol) [](start = 29, length = 70)
Consider this is not (TupleElementFieldSymbol or TupleErrorFieldSymbol)
#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.
// wrapped tuple fields already have this information and override this property | ||
Debug.Assert(!(this is TupleElementFieldSymbol) && !(this is TupleErrorFieldSymbol)); | ||
|
||
if (ContainingType.IsTupleType) |
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 (ContainingType.IsTupleType) [](start = 16, length = 31)
This check seems unnecessary. #Closed
{ | ||
return null; | ||
} | ||
|
||
return _containingTuple.GetTupleMemberSymbolForUnderlyingMember(_underlyingField.AssociatedSymbol); |
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.
AssociatedSymbol [](start = 97, length = 16)
When is there an associated symbol for a tuple field?
In short, are fields of tuple types that aren't tuple elements also represented as TupleFieldElementSymbol
rather than PEFieldSymbol
? (I realize this hasn't changed with this PR but it seems surprising that a tuple element could have an associated field.) #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 think you're right. This can be simplified (always null). Will add test explicitly for this scenario.
In reply to: 597362368 [](ancestors = 597362368)
_ = this.GetMembers(); | ||
} | ||
|
||
public TMember? GetTupleMemberSymbolForUnderlyingMember<TMember>(TMember underlyingMemberOpt) where TMember : Symbol? |
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.
? [](start = 124, length = 1)
Why where TMember : Symbol?
rather than where TMember : Symbol
and a nullable parameter TMember? underlyingMemberOpt
? #Closed
@@ -858,6 +889,17 @@ static ImmutableArray<Symbol> getOriginalFields(ImmutableArray<Symbol> members) | |||
Debug.Assert(fields.All(f => f is object)); | |||
return fields.ToImmutableAndFree(); | |||
} | |||
|
|||
static ImmutableArray<Location> getElementLocations(ref ImmutableArray<Location?> elementLocations, int tupleFieldIndex) |
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.
ref [](start = 64, length = 3)
Should this be in
or passed by value? #Closed
if (IsDefinition) | ||
{ | ||
defaultTupleField = field; | ||
fieldDefinitionsToIndexMap!.Add(field.OriginalDefinition, tupleFieldIndex); |
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.
.OriginalDefinition [](start = 77, length = 19)
Isn't field.OriginalDefinition == field
? #Closed
@@ -798,9 +824,14 @@ protected ArrayBuilder<Symbol> AddOrWrapTupleMembers(ImmutableArray<Symbol> curr | |||
} | |||
|
|||
elementsMatchedByFields.Free(); | |||
members.AddRange(nonFieldMembers); |
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.
members.AddRange(nonFieldMembers); [](start = 12, length = 34)
Why is it necessary for fields to precede non-fields?
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 had a vague recollection that we may have some code depending on that, but that doesn't seem to be the case.
I tested the change again just now and only a few tests that verify the members in order are affected.
@AlekseyTs Maybe you remember about this? Is it important to have the fields come first? If not, I'll update the test baseline.
In reply to: 597388310 [](ancestors = 597388310)
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.
Maybe you remember about this? Is it important to have the fields come first? If not, I'll update the test baseline.
I think PENamedTypeSymbol assumes some grouping and ordering of members.
In reply to: 601706851 [](ancestors = 601706851,597388310)
The first commit removes the wrapping of fields on ValueTuple definitions. It fixes #43597 and fixes #43549
The second commit removes some
field.TupleUnderlyingField ?? field
patterns that were added as mitigations.The third commit fixes #43595 (don't emit tuple error fields)