Skip to content
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

Merged
merged 34 commits into from
Mar 26, 2021
Merged

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented May 13, 2020

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)

@jcouv jcouv added this to the 16.7 milestone May 13, 2020
@jcouv jcouv self-assigned this May 13, 2020
@jcouv jcouv force-pushed the tuple-fields branch 2 times, most recently from b06ad5e to 881e92d Compare May 14, 2020 15:11
@jcouv jcouv marked this pull request as ready for review May 14, 2020 18:07
@jcouv jcouv requested a review from a team as a code owner May 14, 2020 18:07
@jcouv jcouv requested a review from AlekseyTs May 14, 2020 18:07
@@ -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)
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member

@jaredpar jaredpar May 14, 2020

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
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member Author

@jcouv jcouv May 15, 2020

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;
Copy link
Member

@cston cston May 14, 2020

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;
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member

@jaredpar jaredpar May 14, 2020

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)
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member Author

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);
Copy link
Member

@cston cston May 14, 2020

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
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member Author

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)
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member Author

@jcouv jcouv May 15, 2020

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;
}
Copy link
Member

@cston cston May 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed? #Resolved

Copy link
Member Author

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)

Copy link
Member

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)

Copy link
Member Author

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);
Copy link
Member

@cston cston May 14, 2020

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

Copy link
Member

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")]
Copy link
Member

@cston cston May 14, 2020

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;
Copy link
Member

@cston cston May 14, 2020

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

@jcouv jcouv marked this pull request as draft May 15, 2020 15:48
@AlekseyTs
Copy link
Contributor

AlekseyTs commented Feb 26, 2021

Done with review pass (commit 31) #Closed

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (commit 32)

@jcouv
Copy link
Member Author

jcouv commented Mar 1, 2021

@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
@jcouv
Copy link
Member Author

jcouv commented Mar 2, 2021

@dotnet/roslyn-compiler for a second review. I'm happy to jump on a call and walk you through if needed. Thanks

@jaredpar
Copy link
Member

jaredpar commented Mar 2, 2021

@333fred can u take a look at this?

Base automatically changed from master to main March 3, 2021 23:52
@jcouv
Copy link
Member Author

jcouv commented Mar 9, 2021

Gentle ping for second review. Thanks

@jaredpar
Copy link
Member

jaredpar commented Mar 9, 2021

@333fred can u take a look

@jcouv
Copy link
Member Author

jcouv commented Mar 15, 2021

@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");
Copy link
Member

@333fred 333fred Mar 15, 2021

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));
Copy link
Member

@333fred 333fred Mar 15, 2021

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And below


In reply to: 594693244 [](ancestors = 594693244)

// wrapped tuple fields already have this information and override this property
Debug.Assert(!(this is TupleElementFieldSymbol) && !(this is TupleErrorFieldSymbol));

if (ContainingType.IsTupleType)
Copy link
Member

@cston cston Mar 19, 2021

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);
Copy link
Member

@cston cston Mar 19, 2021

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

Copy link
Member Author

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?
Copy link
Member

@cston cston Mar 19, 2021

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)
Copy link
Member

@cston cston Mar 19, 2021

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);
Copy link
Member

@cston cston Mar 19, 2021

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);
Copy link
Member

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?

Copy link
Member Author

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)

Copy link
Contributor

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)

@cston
Copy link
Member

cston commented Mar 19, 2021

        Assert.Equal("RetargetingMethodSymbol", methodM.GetType().Name);

Consider using Assert.IsType<...>(...) for each of these cases. #Closed


Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs:21842 in 1b2eb98. [](commit_id = 1b2eb98, deletion_comment = False)

@cston
Copy link
Member

cston commented Mar 25, 2021

    public override Symbol? AssociatedSymbol

sealed?


Refers to: src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs:95 in 2484dc0. [](commit_id = 2484dc0, deletion_comment = False)

@jcouv jcouv merged commit f23a180 into dotnet:main Mar 26, 2021
@ghost ghost modified the milestones: 16.10, Next Mar 26, 2021
@jcouv jcouv deleted the tuple-fields branch March 26, 2021 00:03
@allisonchou allisonchou modified the milestones: Next, 16.10.P2 Mar 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants