diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index 36242b30704c0..b3b5048999726 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -705,12 +705,14 @@ protected int GetOrCreateSlot(Symbol symbol, int containingSlot = 0) return slot; } - // Descends through Rest fields of a tuple is "symbol" is an extended field - // As a result the "symbol" will be adjusted to be the field of the innermost tuple - // and a corresponding containingSlot is returned. - // Return value -1 indicates a failure which could happen for the following reasons - // a) Rest field does not exist, which could happen in rare error scenarios involving broken ValueTuple types - // b) Rest is not tracked already and forceSlotsToExist is false (otherwise we create slots on demand) + /// + /// Descends through Rest fields of a tuple if "symbol" is an extended field + /// As a result the "symbol" will be adjusted to be the field of the innermost tuple + /// and a corresponding containingSlot is returned. + /// Return value -1 indicates a failure which could happen for the following reasons + /// a) Rest field does not exist, which could happen in rare error scenarios involving broken ValueTuple types + /// b) Rest is not tracked already and forceSlotsToExist is false (otherwise we create slots on demand) + /// private int DescendThroughTupleRestFields(ref Symbol symbol, int containingSlot, bool forceContainingSlotsToExist) { var fieldSymbol = symbol as TupleFieldSymbol; @@ -718,7 +720,7 @@ private int DescendThroughTupleRestFields(ref Symbol symbol, int containingSlot, { TypeSymbol containingType = ((TupleTypeSymbol)symbol.ContainingType).UnderlyingNamedType; - // for tuple fields the varible indentifier represents the underlying field + // for tuple fields the variable identifier represents the underlying field symbol = fieldSymbol.TupleUnderlyingField; // descend through Rest fields diff --git a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs index 18039e0cd514e..ad45f65443a25 100644 --- a/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs @@ -385,7 +385,7 @@ public virtual bool IsTupleField } /// - /// Returns Ture when field symbol is not mapped directly to a field in the underlying tuple struct. + /// Returns True when field symbol is not mapped directly to a field in the underlying tuple struct. /// internal virtual bool IsVirtualTupleField { diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs index 018b501a5deda..f59606a387f44 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleFieldSymbol.cs @@ -15,7 +15,7 @@ internal class TupleFieldSymbol : WrappedFieldSymbol protected readonly TupleTypeSymbol _containingTuple; /// - /// If this field represents a tuple element (including the name match), + /// If this field represents a tuple element, /// id is an index of the element (zero-based). /// Otherwise, (-1 - [index in members array]); /// @@ -40,7 +40,7 @@ public override bool IsTupleField } /// - /// If this field represents a tuple element (including the name match), + /// If this field represents a tuple element, /// id is an index of the element (zero-based). /// Otherwise, (-1 - [index in members array]); /// i @@ -103,22 +103,27 @@ internal override DiagnosticInfo GetUseSiteDiagnostic() public override sealed int GetHashCode() { - return Hash.Combine(_containingTuple.GetHashCode(), _tupleFieldId.GetHashCode()); + return Hash.Combine( + Hash.Combine(_containingTuple.GetHashCode(), _tupleFieldId.GetHashCode()), + this.Name.GetHashCode()); } public override sealed bool Equals(object obj) { - return Equals(obj as TupleFieldSymbol); - } + var other = obj as TupleFieldSymbol; - public bool Equals(TupleFieldSymbol other) - { if ((object)other == this) { return true; } - return (object)other != null && _tupleFieldId == other._tupleFieldId && _containingTuple == other._containingTuple; + // note we have to compare both index and name because + // in nameless tuple there could be fields that differ only by index + // and in named tupoles there could be fields that differ only by name + return (object)other != null && + _tupleFieldId == other.TupleElementIndex && + _containingTuple == other._containingTuple && + this.Name == other.Name; } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs index 5565f6496e01b..43af58b1f7fce 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Tuples/TupleTypeSymbol.cs @@ -556,6 +556,15 @@ internal static int IsElementNameReserved(string name) return 0; } + return MatchesCanonicalElementName(name); + } + + /// + /// Returns 3 for "Item3". + /// Returns -1 otherwise. + /// + private static int MatchesCanonicalElementName(string name) + { if (name.StartsWith("Item", StringComparison.Ordinal)) { string tail = name.Substring(4); @@ -724,13 +733,13 @@ private ImmutableArray CollectTupleElementFields() continue; } - int index = (member as TupleFieldSymbol)?.TupleElementIndex ?? - ((TupleErrorFieldSymbol)member).TupleElementIndex; + var field = (FieldSymbol)member; + var index = field.TupleElementIndex; - if (index >= 0) + if (index >= 0 && index == MatchesCanonicalElementName(field.Name) - 1) { Debug.Assert((object)builder[index] == null); - builder[index] = (FieldSymbol)member; + builder[index] = field; } } @@ -815,7 +824,7 @@ private ImmutableArray CreateMembers() { // The name given doesn't match default name Item8, etc. // Add a field with default name and make it virtual since we are not at the top level - members.Add(new TupleVirtualElementFieldSymbol(this, fieldSymbol, defaultName, -members.Count - 1, null)); + members.Add(new TupleVirtualElementFieldSymbol(this, fieldSymbol, defaultName, tupleFieldIndex, null)); } // Add a field with the given name @@ -832,7 +841,7 @@ private ImmutableArray CreateMembers() else { // Add a field with default name - members.Add(new TupleFieldSymbol(this, fieldSymbol, -members.Count - 1)); + members.Add(new TupleFieldSymbol(this, fieldSymbol, tupleFieldIndex)); // Add a field with the given name if (namesOfVirtualFields[tupleFieldIndex] != null) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs index 1559605585590..02ed530302f8f 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleTest.cs @@ -8630,9 +8630,9 @@ static void Main() var m2Item1 = (FieldSymbol)m2Tuple.GetMembers()[0]; var m2a2 = (FieldSymbol)m2Tuple.GetMembers()[1]; - Assert.IsType(m1Item1); - Assert.IsType(m2Item1); - Assert.IsType(m2a2); + AssertNonvirtualTupleElementField(m1Item1); + AssertNonvirtualTupleElementField(m2Item1); + AssertVirtualTupleElementField(m2a2); Assert.True(m1Item1.IsTupleField); Assert.Same(m1Item1, m1Item1.OriginalDefinition); @@ -8879,7 +8879,7 @@ static void Main() var m3Item8 = (FieldSymbol)m3Tuple.GetMembers("Item8").Single(); - Assert.IsType(m3Item8); + AssertVirtualTupleElementField(m3Item8); Assert.True(m3Item8.IsTupleField); Assert.Same(m3Item8, m3Item8.OriginalDefinition); @@ -9073,7 +9073,7 @@ static void Main() var m4Item8 = (FieldSymbol)m4Tuple.GetMembers("Item8").Single(); - Assert.IsType(m4Item8); + AssertVirtualTupleElementField(m4Item8); Assert.True(m4Item8.IsTupleField); Assert.Same(m4Item8, m4Item8.OriginalDefinition); @@ -9092,7 +9092,7 @@ static void Main() var m4h4 = (FieldSymbol)m4Tuple.GetMembers("h4").Single(); - Assert.IsType(m4h4); + AssertVirtualTupleElementField(m4h4); Assert.True(m4h4.IsTupleField); Assert.Same(m4h4, m4h4.OriginalDefinition); @@ -9321,7 +9321,7 @@ static void Main() var m5Item8 = (FieldSymbol)m5Tuple.GetMembers("Item8").Single(); - Assert.IsType(m5Item8); + AssertVirtualTupleElementField(m5Item8); Assert.True(m5Item8.IsTupleField); Assert.Same(m5Item8, m5Item8.OriginalDefinition); @@ -9691,7 +9691,7 @@ static void Main() var m8Item8 = (FieldSymbol)m8Tuple.GetMembers("Item8").Single(); - Assert.IsType(m8Item8); + AssertVirtualTupleElementField(m8Item8); Assert.True(m8Item8.IsTupleField); Assert.Same(m8Item8, m8Item8.OriginalDefinition); @@ -9711,7 +9711,7 @@ static void Main() var m8Item1 = (FieldSymbol)m8Tuple.GetMembers("Item1").Last(); - Assert.IsType(m8Item1); + AssertVirtualTupleElementField(m8Item1); Assert.True(m8Item1.IsTupleField); Assert.Same(m8Item1, m8Item1.OriginalDefinition); @@ -9890,9 +9890,9 @@ static void Main() var m2Item1 = (FieldSymbol)m2Tuple.GetMembers()[0]; var m2a2 = (FieldSymbol)m2Tuple.GetMembers()[1]; - Assert.IsType(m1Item1); - Assert.IsType(m2Item1); - Assert.IsType(m2a2); + AssertNonvirtualTupleElementField(m1Item1); + AssertNonvirtualTupleElementField(m2Item1); + AssertVirtualTupleElementField(m2a2); Assert.True(m1Item1.IsTupleField); Assert.Same(m1Item1, m1Item1.OriginalDefinition); @@ -10226,9 +10226,9 @@ static void Test03() var m102Item20 = (FieldSymbol)m102Tuple.GetMembers("Item20").Single(); var m102a = (FieldSymbol)m102Tuple.GetMembers("a").Single(); - Assert.IsType(m10Item1); - Assert.IsType(m102Item20); - Assert.IsType(m102a); + AssertNonvirtualTupleElementField(m10Item1); + AssertTupleNonElementField(m102Item20); + AssertVirtualTupleElementField(m102a); Assert.Equal("System.ObsoleteAttribute", m10Item1.GetAttributes().Single().ToString()); Assert.Equal("System.ObsoleteAttribute", m102Item20.GetAttributes().Single().ToString()); @@ -10239,10 +10239,10 @@ static void Test03() var m102Item2 = (FieldSymbol)m102Tuple.GetMembers("Item2").Single(); var m102b = (FieldSymbol)m102Tuple.GetMembers("b").Single(); - Assert.IsType(m10Item2); - Assert.IsType(m102Item2); - Assert.IsType(m102Item21); - Assert.IsType(m102b); + AssertNonvirtualTupleElementField(m10Item2); + AssertNonvirtualTupleElementField(m102Item2); + AssertTupleNonElementField(m102Item21); + AssertVirtualTupleElementField(m102b); Assert.Equal(20, m10Item2.TypeLayoutOffset); Assert.Equal(20, m102Item2.TypeLayoutOffset); @@ -10256,8 +10256,8 @@ static void Test03() var m103Item2 = (FieldSymbol)m103Tuple.GetMembers("Item2").Last(); var m103Item9 = (FieldSymbol)m103Tuple.GetMembers("Item9").Single(); - Assert.IsType(m103Item2); - Assert.IsType(m103Item9); + AssertVirtualTupleElementField(m103Item2); + AssertVirtualTupleElementField(m103Item9); Assert.Null(m103Item2.TypeLayoutOffset); Assert.Equal(20, m103Item2.TupleUnderlyingField.TypeLayoutOffset); Assert.Null(m103Item9.TypeLayoutOffset); @@ -10296,6 +10296,36 @@ static void Test03() Assert.Equal("System.ObsoleteAttribute", m10E2.GetAttributes().Single().ToString()); } + private void AssertTupleNonElementField(FieldSymbol sym) + { + Assert.True(sym.IsTupleField); + Assert.False(sym.IsVirtualTupleField); + + //it is not an element so index must be negative + Assert.True(sym.TupleElementIndex < 0); + } + + private void AssertVirtualTupleElementField(FieldSymbol sym) + { + Assert.True(sym.IsTupleField); + Assert.True(sym.IsVirtualTupleField); + + //it is an element so must have nonnegative index + Assert.True(sym.TupleElementIndex >= 0); + } + + private void AssertNonvirtualTupleElementField(FieldSymbol sym) + { + Assert.True(sym.IsTupleField); + Assert.False(sym.IsVirtualTupleField); + + //it is an element so must have nonnegative index + Assert.True(sym.TupleElementIndex >= 0); + + //if it was 8th or after, it would be virtual + Assert.True(sym.TupleElementIndex < TupleTypeSymbol.RestPosition - 1); + } + [Fact] public void CustomValueTupleWithGenericMethod() {