(Taken from dotnet/roslyn#20648)
At one time the implementation of tuples recognized that tuples and their underlying types are distinct types. Later, the LDM decided that these should not be distinct types. We (mostly) modified the compiler’s language behavior to respect the LDM decision, but we have not modified the APIs accordingly.
Our compiler guidelines include this design point:
As a rule, the public APIs exposed by Roslyn should reflect the language view of the program, not the lowered or implementation view. For example, a
Symbol
'sName
property should reflect the name from the language point of view if there is one; we have a separateMetadataName
(internal) property to represent the implementation view.
In order to align our APIs with the language view for tuples, I propose to make the following changes:
- The
INamedTypeSymbol
APIs are changed as follows:IsTupleType
: Any type that is “tuple-compatible” shall be considered a tuple type (including original types such asValueTuple<T1, T2>
), in which case this API returns true.Construct
: A constructed type that is a tuple type (e.g., the construction ofValueTuple<T1,T2>
with the types{Int32, Int32}
) shall be considered a tuple type. This currently works in the language, but not in the APIs. The APIs will be fixed so thatINamedTypeSymbol.Construct
returns tuple types when appropriate.TupleUnderlyingType
: for a tuple type, it shall return a corresponding tuple type with no user-added names (at the top level). For example, given the type(int a, int b)
, its underlying type is(int, int)
, also known asSystem.ValueTuple<int, int>
. For non-tuple types, it behaves as before.Arity
: Tuple types with fewer than 8 elements have the same arity as the number of elements. For example,(int, int)
as arity 2. Tuple types with 8 or more elements have arity 8. This is a straightforward consequence of a tuple type without named elements having itself as its underlying type.TypeArguments
: This is now the same as the type arguments of the tuple’s underlying type (i.e., its own type arguments for unnamed tuple types).ConstructedFrom
: taken from the underlying typeOriginalDefinition
: taken from the underlying typeLocations
: taken from the underlying type.TypeSubstitution
and other internal APIs: taken from the underlying type
- Instances of
ValueTuple<T>
(length one tuples) are considered a tuple type (even though there is no surface syntax for them in the language) FieldSymbol
APIs are changed as follows:OriginalDefinition
: For fields originating in the underlying type, that underlying field. For compiler-synthesized fields of long tuples, itself. For user-named elements, the same as today.Locations
: For fields originating in the underlying type, that underlying field’s location. For compiler-synthesized fields of long tuples, empty. For user-named elements, the location where a name was provided.IsTupleField
: true if the field is an instance field of a type (ContainingType
) for whichIsTupleType
is true.IsVirtualTupleField
,IsDefaultTupleElement
,TupleUnderlyingField
(ie.VT'1.Item1
for(...).Item8
),CorrespondingTupleField
(ie.(...).Item1
for(...).Alice
),TupleElementIndex
: as today.
Some interesting effects are on long tuples.
ValueTuple<T1, T2, T3, T4, T5, T6, T7, TRest>
is not a tuple type. Its fieldItem1
andRest
have propertyIsTupleField
false. This is existing behavior.- When it is instantiated or substituted with a tuple type as
TRest
, the result is a tuple type that has additional synthetic fields forItem8
and beyond. - The
TRest
field has the propertyIsTupleField
that returns true when accessed from a long tuple type. This is existing behavior.
In C# 6 and prior, a constructed type always had the same number of members as the type it was constructed from. This is no longer the case in C# 7, as long tuple types have additional members that are not members of the type from which they are constructed (e.g. Item8
). This change is reflected in the compiler APIs, which will no longer support the obsolete invariant. I haven’t yet found any code that depends on this invariant.
ToDisplayString
andToDisplayFormat
and diagnostics use tuple format- Missing
ValueTuple
type results in an error type (rather than a struct type) - If explicitly specifying default tuple names, as in
(int Item1, int Item2)
, those fields are just the default tuple fields. This is equivalent to(int, int)
and has no element names. - All members that are added are definitions, implicitly declared,
- Tuple types have the name
ValueTuple
and an arity up to 8 (ValueTuple'8
). - Both
(int, int)
and(int a, int b)
are constructed from(T1, T2)
(ie.ValueTuple<T1, T2>
). - Both
(int, ..., int)
and(int a, ..., int g)
are constructed fromValueTuple<T1, ..., TRest>
. - Tests use ValueTuple format in VerifyIL
- Still some details to figure out for DeclaringSyntaxReferences and Locations.