-
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
Changes from 4 commits
72dc304
881e92d
21f72af
348daea
4154f07
758a8be
f4b0b9e
df14519
f491231
6400146
2054720
fc85a89
74cfed2
c422d49
c57a02b
c18e709
49199b8
5575125
52dba4d
92cf81d
e40f8c3
ae512eb
612364d
a3706e7
6b52689
9eeeecb
d260464
59933df
465438c
77e86db
ac45f85
1b2eb98
2484dc0
f02ee32
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 |
---|---|---|
|
@@ -407,7 +407,6 @@ internal virtual IEnumerable<EventSymbol> GetEventsToEmit() | |
|
||
foreach (var f in GetFieldsToEmit()) | ||
{ | ||
Debug.Assert((object)(f.TupleUnderlyingField ?? f) == f); | ||
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 think we should keep the assert. #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. In fact, I think we should make it even stronger and make sure we don't run into a In reply to: 426203595 [](ancestors = 426203595) 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 doesn't look like the thread is actually addressed and no other response provided. In reply to: 426264743 [](ancestors = 426264743,426203595) |
||
if (isStruct || f.ShouldInclude(context)) | ||
{ | ||
yield return f; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -275,10 +275,7 @@ private void ValidateReferencedAssembly(AssemblySymbol assembly, AssemblyReferen | |
case SymbolKind.Field: | ||
// NOTE: Dev11 does not add synthesized backing fields for properties, | ||
// but adds backing fields for events, Roslyn adds both | ||
{ | ||
var field = (FieldSymbol)member; | ||
AddSymbolLocation(result, field.TupleUnderlyingField ?? field); | ||
} | ||
AddSymbolLocation(result, member); | ||
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 feels like we should skip |
||
break; | ||
|
||
case SymbolKind.Event: | ||
|
@@ -288,7 +285,7 @@ private void ValidateReferencedAssembly(AssemblySymbol assembly, AssemblyReferen | |
FieldSymbol field = ((EventSymbol)member).AssociatedField; | ||
if ((object)field != null) | ||
{ | ||
AddSymbolLocation(result, field.TupleUnderlyingField ?? field); | ||
AddSymbolLocation(result, field); | ||
} | ||
} | ||
break; | ||
|
@@ -1009,7 +1006,7 @@ internal Cci.IFieldReference Translate( | |
bool needDeclaration = false) | ||
{ | ||
Debug.Assert(fieldSymbol.IsDefinitionOrDistinct()); | ||
Debug.Assert(!fieldSymbol.IsVirtualTupleField && (object)(fieldSymbol.TupleUnderlyingField ?? fieldSymbol) == fieldSymbol, "tuple fields should be rewritten to underlying by now"); | ||
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 think we should keep this condition, it is still bad to violate it. #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. In fact, I think we should make the assert even stronger and make sure we don't run into a In reply to: 426203778 [](ancestors = 426203778) 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 doesn't look like the thread is actually addressed completely and no other response provided. In reply to: 426264817 [](ancestors = 426264817,426203778) |
||
Debug.Assert(!fieldSymbol.IsVirtualTupleField, "virtual tuple fields should be rewritten to underlying by now"); | ||
|
||
if (!fieldSymbol.IsDefinition) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe 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 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've tried to think of different approaches, and tried a few, but this is really tricky. So ended up sticking with this check. #ByDesign 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.
This logic looks suspicious. Is this going to do the right thing for VB symbols? Is this PR affecting behavior of 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. In this PR, we're not changing the behavior of I'll look for VB tests to make sure the scenario you raised is covered. In reply to: 426205983 [](ancestors = 426205983) 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 feels to me that relying on IsImplicitlyDeclared to decide whether to include names is fragile and probably a wrong thing to do. It feels like that should be decided on whether explicit names were specified in the tuple type syntax used to "create" the tuple type. That is not the same as IsImplicitlyDeclared, or at least it is no longer the same. We probably should base the logic on a more specific API/information. In reply to: 426687365 [](ancestors = 426687365,426205983) 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 might be worth discussing this issue in more depth offline before we decide the course of action In reply to: 426774418 [](ancestors = 426774418,426687365,426205983) |
||
{ | ||
AddSpace(); | ||
builder.Add(CreatePart(SymbolDisplayPartKind.FieldName, symbol, element.Name)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// 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; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} | ||
|
||
|
@@ -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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. That property is |
||
} | ||
} | ||
|
||
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more.
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 commentThe 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) |
||
{ | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more.
Consider inlining this to only calculate when |
||
if (i > 0 && i <= itemsPerType) | ||
{ | ||
WellKnownMember wellKnownMember = NamedTypeSymbol.GetTupleTypeMember(ContainingType.Arity, i); | ||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. This looks expensive. Can we calculate 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 want to add state to every source or PE symbol just because it might be a tuple element. In reply to: 425346752 [](ancestors = 425346752) |
||
|
||
if (found is object) | ||
{ | ||
return i - 1; | ||
} | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -878,7 +878,7 @@ internal override IEnumerable<FieldSymbol> GetFieldsToEmit() | |
else | ||
{ | ||
// If there are any non-event fields, they are at the very beginning. | ||
IEnumerable<FieldSymbol> nonEventFields = GetMembers<FieldSymbol>(this.GetMembers(), SymbolKind.Field, offset: 0).Select(f => f.TupleUnderlyingField ?? f); | ||
IEnumerable<FieldSymbol> nonEventFields = GetMembers<FieldSymbol>(this.GetMembers(), SymbolKind.Field, offset: 0); | ||
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.
Is it possible to run into a 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 turns out it's not possible, but the reason seems suspicious. The tuple error fields are not at the very begining... They come after the constructor, so they are filtered out by In reply to: 426204181 [](ancestors = 426204181) 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.
This doesn't sound right. I think the members should be grouped as we expect and that consumers of In reply to: 426681748 [](ancestors = 426681748,426204181) |
||
|
||
// Event backing fields are not part of the set returned by GetMembers. Let's add them manually. | ||
ArrayBuilder<FieldSymbol> eventFields = null; | ||
|
@@ -889,9 +889,6 @@ internal override IEnumerable<FieldSymbol> GetFieldsToEmit() | |
if ((object)associatedField != null) | ||
{ | ||
Debug.Assert((object)associatedField.AssociatedSymbol != null); | ||
|
||
associatedField = associatedField.TupleUnderlyingField ?? associatedField; | ||
|
||
Debug.Assert(!nonEventFields.Contains(associatedField)); | ||
|
||
if (eventFields == null) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more.
When do we try to emit with error fields? #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.
This is recognized as a 2-tuple, and thus has In reply to: 425348604 [](ancestors = 425348604) |
||
{ | ||
break; | ||
} | ||
|
||
yield return (FieldSymbol)m; | ||
break; | ||
case SymbolKind.Event: | ||
FieldSymbol associatedField = ((EventSymbol)m).AssociatedField; | ||
if ((object)associatedField != null) | ||
{ | ||
yield return associatedField.TupleUnderlyingField ?? associatedField; | ||
yield return associatedField; | ||
} | ||
break; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1126,6 +1126,11 @@ private bool HasInstanceFields() | |
switch (m.Kind) | ||
{ | ||
case SymbolKind.Field: | ||
if (m is TupleErrorFieldSymbol) | ||
{ | ||
break; | ||
} | ||
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. Is this needed? #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. For empty structs, we emit some layout information ( In reply to: 425349126 [](ancestors = 425349126) 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. 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 commentThe reason will be displayed to describe this comment to others. Learn more. Because a type Similarly, on an 8-tuple, we have an In reply to: 425411648 [](ancestors = 425411648,425410620,425349126) |
||
|
||
return true; | ||
|
||
case SymbolKind.Event: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -445,7 +445,7 @@ internal static int IsTupleElementNameReserved(string name) | |
return 0; | ||
} | ||
|
||
return matchesCanonicalElementName(name); | ||
return MatchesCanonicalElementName(name); | ||
|
||
static bool isElementNameForbidden(string name) | ||
{ | ||
|
@@ -463,26 +463,27 @@ static bool isElementNameForbidden(string name) | |
return false; | ||
} | ||
} | ||
} | ||
|
||
// Returns 3 for "Item3". | ||
// Returns -1 otherwise. | ||
static int matchesCanonicalElementName(string name) | ||
/// <summary> | ||
/// Returns 3 for "Item3". | ||
/// Returns -1 otherwise. | ||
/// </summary> | ||
internal static int MatchesCanonicalElementName(string name) | ||
{ | ||
if (name.StartsWith("Item", StringComparison.Ordinal)) | ||
{ | ||
if (name.StartsWith("Item", StringComparison.Ordinal)) | ||
string tail = name.Substring(4); | ||
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
Unfortunately there are no overloads of #WontFix 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. Looked around but didn't find something better. Not sure I want to add one for this PR, especially since it's a pre-existing usage of In reply to: 426860653 [](ancestors = 426860653) |
||
if (int.TryParse(tail, out int number)) | ||
{ | ||
string tail = name.Substring(4); | ||
int number; | ||
if (int.TryParse(tail, out number)) | ||
if (number > 0 && string.Equals(name, TupleMemberName(number), StringComparison.Ordinal)) | ||
{ | ||
if (number > 0 && String.Equals(name, TupleMemberName(number), StringComparison.Ordinal)) | ||
{ | ||
return number; | ||
} | ||
return number; | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
/// <summary> | ||
|
@@ -601,6 +602,19 @@ public sealed override ImmutableArray<FieldSymbol> TupleElements | |
continue; | ||
} | ||
|
||
if (this.IsDefinition) | ||
{ | ||
// 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 commentThe reason will be displayed to describe this comment to others. Learn more.
Consider using a 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. 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) |
||
if (tupleFieldIndex2 >= 0) | ||
{ | ||
elementsMatchedByFields[tupleFieldIndex2] = true; // mark as handled | ||
} | ||
|
||
continue; | ||
} | ||
|
||
var underlyingField = field is TupleElementFieldSymbol tupleElement ? tupleElement.UnderlyingField.OriginalDefinition : field.OriginalDefinition; | ||
int tupleFieldIndex = currentFieldsForElements.IndexOf(underlyingField, ReferenceEqualityComparer.Instance); | ||
if (underlyingField is TupleErrorFieldSymbol) | ||
|
@@ -621,11 +635,7 @@ public sealed override ImmutableArray<FieldSymbol> TupleElements | |
var providedName = elementNames.IsDefault ? null : elementNames[tupleFieldIndex]; | ||
|
||
ImmutableArray<Location> locations; | ||
if (this.IsDefinition) | ||
{ | ||
locations = member.Locations; | ||
} | ||
else if (elementLocations.IsDefault) | ||
if (elementLocations.IsDefault) | ||
{ | ||
locations = ImmutableArray<Location>.Empty; | ||
} | ||
|
@@ -1051,57 +1061,47 @@ internal SmallDictionary<Symbol, Symbol> UnderlyingDefinitionToMemberMap | |
{ | ||
get | ||
{ | ||
return _lazyUnderlyingDefinitionToMemberMap ?? | ||
(_lazyUnderlyingDefinitionToMemberMap = ComputeDefinitionToMemberMap()); | ||
} | ||
} | ||
return _lazyUnderlyingDefinitionToMemberMap ??= computeDefinitionToMemberMap(); | ||
|
||
private SmallDictionary<Symbol, Symbol> ComputeDefinitionToMemberMap() | ||
{ | ||
var map = new SmallDictionary<Symbol, Symbol>(ReferenceEqualityComparer.Instance); | ||
var members = TupleUnderlyingType.GetMembers(); | ||
|
||
// Go in reverse because we want members with default name, which precede the ones with | ||
// friendly names, to be in the map. | ||
for (int i = members.Length - 1; i >= 0; i--) | ||
{ | ||
var member = members[i]; | ||
switch (member.Kind) | ||
SmallDictionary<Symbol, Symbol> computeDefinitionToMemberMap() | ||
{ | ||
case SymbolKind.Method: | ||
case SymbolKind.Property: | ||
case SymbolKind.NamedType: | ||
map.Add(member.OriginalDefinition, member); | ||
break; | ||
|
||
case SymbolKind.Field: | ||
var tupleUnderlyingField = ((FieldSymbol)member).TupleUnderlyingField; | ||
if (tupleUnderlyingField is object) | ||
{ | ||
map[tupleUnderlyingField.OriginalDefinition] = member; | ||
} | ||
break; | ||
var map = new SmallDictionary<Symbol, Symbol>(ReferenceEqualityComparer.Instance); | ||
var members = TupleUnderlyingType.GetMembers(); | ||
|
||
case SymbolKind.Event: | ||
var underlyingEvent = (EventSymbol)member; | ||
var underlyingAssociatedField = underlyingEvent.AssociatedField; | ||
// The field is not part of the members list | ||
if (underlyingAssociatedField is object) | ||
// Go in reverse because we want members with default name, which precede the ones with | ||
// friendly names, to be in the map. | ||
for (int i = members.Length - 1; i >= 0; i--) | ||
{ | ||
var member = members[i]; | ||
switch (member.Kind) | ||
{ | ||
Debug.Assert((object)underlyingAssociatedField.ContainingSymbol == TupleUnderlyingType); | ||
Debug.Assert(TupleUnderlyingType.GetMembers(underlyingAssociatedField.Name).IndexOf(underlyingAssociatedField) < 0); | ||
map.Add(underlyingAssociatedField.OriginalDefinition, new TupleFieldSymbol(TupleUnderlyingType, underlyingAssociatedField, -i - 1)); | ||
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 is not clear why this is no longer needed. We don't need/want to create an instance of a TupleFieldSymbol, but it feel like we still want an entry in the map if we wanted to have it in the map before this PR. #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. I'll put the logic back for completeness, but the only place we use this is in In reply to: 426810809 [](ancestors = 426810809) |
||
case SymbolKind.Method: | ||
case SymbolKind.Property: | ||
case SymbolKind.NamedType: | ||
map.Add(member.OriginalDefinition, member); | ||
break; | ||
|
||
case SymbolKind.Field: | ||
var tupleUnderlyingField = ((FieldSymbol)member).TupleUnderlyingField; | ||
if (tupleUnderlyingField is object) | ||
{ | ||
map[tupleUnderlyingField.OriginalDefinition] = member; | ||
} | ||
break; | ||
|
||
case SymbolKind.Event: | ||
var underlyingEvent = (EventSymbol)member; | ||
map.Add(underlyingEvent.OriginalDefinition, member); | ||
break; | ||
|
||
default: | ||
throw ExceptionUtilities.UnexpectedValue(member.Kind); | ||
} | ||
} | ||
|
||
map.Add(underlyingEvent.OriginalDefinition, member); | ||
break; | ||
|
||
default: | ||
throw ExceptionUtilities.UnexpectedValue(member.Kind); | ||
return map; | ||
} | ||
} | ||
|
||
return map; | ||
} | ||
|
||
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.
Can use explicit cast and avoid the
null
check. #ResolvedThere 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