Don't wrap fields in ValueTuple definitions#44231
Conversation
b06ad5e to
881e92d
Compare
| 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.
Can use explicit cast and avoid the null check. #Resolved
There was a problem hiding this comment.
Could also do
if (member is FieldSymbol fieldSymbol)
``` #Resolved| VisitFieldType(element); | ||
| if (!element.IsImplicitlyDeclared) | ||
| if (!element.IsImplicitlyDeclared && | ||
| !(symbol.IsGenericType && symbol.IsDefinition)) // C# tuples are generic |
There was a problem hiding this comment.
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.
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
| get | ||
| { | ||
| return false; | ||
| return ContainingType.IsDefinition && TupleElementIndex >= 0; |
There was a problem hiding this comment.
ContainingType.IsDefinition [](start = 23, length = 27)
TupleElementIndex includes this check. #Resolved
| get | ||
| { | ||
| return null; | ||
| return (ContainingType.IsDefinition && TupleElementIndex >= 0) ? this : null; |
There was a problem hiding this comment.
ContainingType.IsDefinition [](start = 24, length = 27)
TupleElementIndex includes this check. #Resolved
There was a problem hiding this comment.
That property is virtual though. Do we feel comfortable depending on all the overrides also enforcing this behavior? #Resolved
| { | ||
| get | ||
| { | ||
| if (ContainingType.IsTupleType && ContainingType.IsDefinition) |
There was a problem hiding this comment.
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.
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.
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.
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.
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)
| case SymbolKind.Field: | ||
| var field = (FieldSymbol)m; | ||
| yield return field.TupleUnderlyingField ?? field; | ||
| if (m is TupleErrorFieldSymbol) |
There was a problem hiding this comment.
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.
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.
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.
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.
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.
currentFieldsForElements [](start = 55, length = 24)
Consider using a Dictionary<Symbol, int> for currentFieldsForElements. #Closed
There was a problem hiding this comment.
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.
[WorkItem(43549, "https://github.com//issues/43549")] [](start = 8, length = 66)
Duplicate. Same comment for other tests below. #Resolved
| using Microsoft.CodeAnalysis.Text; | ||
| using Roslyn.Test.Utilities; | ||
| using Roslyn.Utilities; | ||
| using TestResources.NetFX; |
There was a problem hiding this comment.
TestResources.NetFX [](start = 6, length = 19)
Is this used? #Resolved
|
Done with review pass (commit 31) #Closed |
|
@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.
!(fieldSymbol is TupleErrorFieldSymbol) [](start = 16, length = 39)
Nit: consider is not. #Resolved
| { | ||
| get | ||
| { | ||
| Debug.Assert(!(this is TupleElementFieldSymbol) && !(this is TupleErrorFieldSymbol)); |
There was a problem hiding this comment.
!(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.
| // 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.
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.
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.
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.
? [](start = 124, length = 1)
Why where TMember : Symbol? rather than where TMember : Symbol and a nullable parameter TMember? underlyingMemberOpt ? #Closed
| return fields.ToImmutableAndFree(); | ||
| } | ||
|
|
||
| static ImmutableArray<Location> getElementLocations(ref ImmutableArray<Location?> elementLocations, int tupleFieldIndex) |
There was a problem hiding this comment.
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.
.OriginalDefinition [](start = 77, length = 19)
Isn't field.OriginalDefinition == field? #Closed
| } | ||
|
|
||
| elementsMatchedByFields.Free(); | ||
| members.AddRange(nonFieldMembers); |
There was a problem hiding this comment.
members.AddRange(nonFieldMembers); [](start = 12, length = 34)
Why is it necessary for fields to precede non-fields?
There was a problem hiding this comment.
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.
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 ?? fieldpatterns that were added as mitigations.The third commit fixes #43595 (don't emit tuple error fields)