|
// Licensed to the .NET Foundation under one or more agreements.
// 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;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RuntimeMembers;
using Roslyn.Utilities;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
internal abstract partial class NamedTypeSymbol
{
internal const int ValueTupleRestPosition = 8; // The Rest field is in 8th position
internal const int ValueTupleRestIndex = ValueTupleRestPosition - 1;
internal const string ValueTupleTypeName = "ValueTuple";
internal const string ValueTupleRestFieldName = "Rest";
private TupleExtraData? _lazyTupleData;
/// <summary>
/// Helps create a tuple type from source.
/// </summary>
internal static NamedTypeSymbol CreateTuple(
Location? locationOpt,
ImmutableArray<TypeWithAnnotations> elementTypesWithAnnotations,
ImmutableArray<Location?> elementLocations,
ImmutableArray<string?> elementNames,
CSharpCompilation compilation,
bool shouldCheckConstraints,
bool includeNullability,
ImmutableArray<bool> errorPositions,
CSharpSyntaxNode? syntax = null,
BindingDiagnosticBag? diagnostics = null)
{
Debug.Assert(!shouldCheckConstraints || syntax is object);
Debug.Assert(elementNames.IsDefault || elementTypesWithAnnotations.Length == elementNames.Length);
Debug.Assert(!includeNullability || shouldCheckConstraints);
int numElements = elementTypesWithAnnotations.Length;
if (numElements <= 1)
{
throw ExceptionUtilities.Unreachable();
}
NamedTypeSymbol underlyingType = getTupleUnderlyingType(elementTypesWithAnnotations, syntax, compilation, diagnostics);
if (diagnostics?.DiagnosticBag is object && ((SourceModuleSymbol)compilation.SourceModule).AnyReferencedAssembliesAreLinked)
{
// Complain about unembeddable types from linked assemblies.
Emit.NoPia.EmbeddedTypesManager.IsValidEmbeddableType(underlyingType, syntax, diagnostics.DiagnosticBag);
}
var locations = locationOpt is null ? ImmutableArray<Location>.Empty : ImmutableArray.Create(locationOpt);
var constructedType = CreateTuple(underlyingType, elementNames, errorPositions, elementLocations, locations);
if (shouldCheckConstraints && diagnostics != null)
{
Debug.Assert(syntax is object);
constructedType.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(compilation, compilation.Conversions, includeNullability, syntax.Location, diagnostics),
syntax, elementLocations, nullabilityDiagnosticsOpt: includeNullability ? diagnostics : null);
}
return constructedType;
// Produces the underlying ValueTuple corresponding to this list of element types.
// Pass a null diagnostic bag and syntax node if you don't care about diagnostics.
static NamedTypeSymbol getTupleUnderlyingType(ImmutableArray<TypeWithAnnotations> elementTypes, CSharpSyntaxNode? syntax, CSharpCompilation compilation, BindingDiagnosticBag? diagnostics)
{
int numElements = elementTypes.Length;
int remainder;
int chainLength = NumberOfValueTuples(numElements, out remainder);
NamedTypeSymbol firstTupleType = compilation.GetWellKnownType(GetTupleType(remainder));
if (diagnostics is object && syntax is object)
{
ReportUseSiteAndObsoleteDiagnostics(syntax, diagnostics, firstTupleType);
}
NamedTypeSymbol? chainedTupleType = null;
if (chainLength > 1)
{
chainedTupleType = compilation.GetWellKnownType(GetTupleType(ValueTupleRestPosition));
if (diagnostics is object && syntax is object)
{
ReportUseSiteAndObsoleteDiagnostics(syntax, diagnostics, chainedTupleType);
}
}
return ConstructTupleUnderlyingType(firstTupleType, chainedTupleType, elementTypes);
}
}
public static NamedTypeSymbol CreateTuple(
NamedTypeSymbol tupleCompatibleType,
ImmutableArray<string?> elementNames = default,
ImmutableArray<bool> errorPositions = default,
ImmutableArray<Location?> elementLocations = default,
ImmutableArray<Location> locations = default)
{
Debug.Assert(tupleCompatibleType.IsTupleType);
return tupleCompatibleType.WithElementNames(elementNames, elementLocations, errorPositions, locations);
}
internal NamedTypeSymbol WithTupleDataFrom(NamedTypeSymbol original)
{
if (!IsTupleType || (original._lazyTupleData == null && this._lazyTupleData == null) || TupleData!.EqualsIgnoringTupleUnderlyingType(original.TupleData))
{
return this;
}
return WithElementNames(original.TupleElementNames, original.TupleElementLocations, original.TupleErrorPositions, original.Locations);
}
internal NamedTypeSymbol? TupleUnderlyingType
=> this._lazyTupleData != null ? this.TupleData!.TupleUnderlyingType : (this.IsTupleType ? this : null);
/// <summary>
/// Copy this tuple, but modify it to use the new element types.
/// </summary>
internal NamedTypeSymbol WithElementTypes(ImmutableArray<TypeWithAnnotations> newElementTypes)
{
Debug.Assert(TupleElementTypesWithAnnotations.Length == newElementTypes.Length);
Debug.Assert(newElementTypes.All(t => t.HasType));
NamedTypeSymbol firstTupleType;
NamedTypeSymbol? chainedTupleType;
if (Arity < NamedTypeSymbol.ValueTupleRestPosition)
{
firstTupleType = OriginalDefinition;
chainedTupleType = null;
}
else
{
chainedTupleType = OriginalDefinition;
var underlyingType = this;
do
{
underlyingType = ((NamedTypeSymbol)underlyingType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[NamedTypeSymbol.ValueTupleRestIndex].Type);
}
while (underlyingType.Arity >= NamedTypeSymbol.ValueTupleRestPosition);
firstTupleType = underlyingType.OriginalDefinition;
}
return CreateTuple(
ConstructTupleUnderlyingType(firstTupleType, chainedTupleType, newElementTypes),
elementNames: TupleElementNames, elementLocations: TupleElementLocations, errorPositions: TupleErrorPositions, locations: Locations);
}
/// <summary>
/// Copy this tuple, but modify it to use the new element names.
/// Also applies new location of the whole tuple as well as each element.
/// Drops the inferred positions.
/// </summary>
internal NamedTypeSymbol WithElementNames(ImmutableArray<string?> newElementNames,
ImmutableArray<Location?> newElementLocations,
ImmutableArray<bool> errorPositions,
ImmutableArray<Location> locations)
{
Debug.Assert(IsTupleType);
Debug.Assert(newElementNames.IsDefault || this.TupleElementTypesWithAnnotations.Length == newElementNames.Length);
return WithTupleData(new TupleExtraData(this.TupleUnderlyingType!, newElementNames, newElementLocations, errorPositions, locations));
}
private NamedTypeSymbol WithTupleData(TupleExtraData newData)
{
Debug.Assert(IsTupleType);
if (newData.EqualsIgnoringTupleUnderlyingType(TupleData))
{
return this;
}
if (this.IsDefinition)
{
if (newData.ElementNames.IsDefault)
{
// We don't want to modify the definition unless we're adding names
return this;
}
// This is for the rare case of making a tuple with names inside the definition of a ValueTuple type
// We don't want to call Construct here, as it has a shortcut that would return `this`, thus causing a loop
return this.ConstructCore(this.GetTypeParametersAsTypeArguments(), unbound: false).WithTupleData(newData);
}
return WithTupleDataCore(newData);
}
protected abstract NamedTypeSymbol WithTupleDataCore(TupleExtraData newData);
/// <summary>
/// Decompose the underlying tuple type into its links and store them into the underlyingTupleTypeChain.
///
/// For instance, ValueTuple<..., ValueTuple< int >> (the underlying type for an 8-tuple)
/// will be decomposed into two links: the first one is the entire thing, and the second one is the ValueTuple< int >
/// </summary>
internal static void GetUnderlyingTypeChain(NamedTypeSymbol underlyingTupleType, ArrayBuilder<NamedTypeSymbol> underlyingTupleTypeChain)
{
NamedTypeSymbol currentType = underlyingTupleType;
while (true)
{
underlyingTupleTypeChain.Add(currentType);
if (currentType.Arity == NamedTypeSymbol.ValueTupleRestPosition)
{
currentType = (NamedTypeSymbol)currentType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[NamedTypeSymbol.ValueTupleRestPosition - 1].Type;
}
else
{
break;
}
}
}
/// <summary>
/// Returns the number of nestings required to represent numElements as nested ValueTuples.
/// For example, for 8 elements, you need 2 ValueTuples and the remainder (ie the size of the last nested ValueTuple) is 1.
/// </summary>
private static int NumberOfValueTuples(int numElements, out int remainder)
{
remainder = (numElements - 1) % (ValueTupleRestPosition - 1) + 1;
return (numElements - 1) / (ValueTupleRestPosition - 1) + 1;
}
private static NamedTypeSymbol ConstructTupleUnderlyingType(NamedTypeSymbol firstTupleType, NamedTypeSymbol? chainedTupleTypeOpt, ImmutableArray<TypeWithAnnotations> elementTypes)
{
Debug.Assert(chainedTupleTypeOpt is null == elementTypes.Length < ValueTupleRestPosition);
int numElements = elementTypes.Length;
int remainder;
int chainLength = NumberOfValueTuples(numElements, out remainder);
NamedTypeSymbol currentSymbol = firstTupleType.Construct(ImmutableArray.Create(elementTypes, (chainLength - 1) * (ValueTupleRestPosition - 1), remainder));
int loop = chainLength - 1;
while (loop > 0)
{
var chainedTypes = ImmutableArray.Create(elementTypes, (loop - 1) * (ValueTupleRestPosition - 1), ValueTupleRestPosition - 1).Add(TypeWithAnnotations.Create(currentSymbol));
currentSymbol = chainedTupleTypeOpt!.Construct(chainedTypes);
loop--;
}
return currentSymbol;
}
private static void ReportUseSiteAndObsoleteDiagnostics(CSharpSyntaxNode? syntax, BindingDiagnosticBag diagnostics, NamedTypeSymbol firstTupleType)
{
Binder.ReportUseSite(firstTupleType, diagnostics, syntax);
Binder.ReportDiagnosticsIfObsoleteInternal(diagnostics, firstTupleType, syntax, firstTupleType.ContainingType, BinderFlags.None);
}
/// <summary>
/// For tuples with no natural type, we still need to verify that an underlying type of proper arity exists, and report if otherwise.
/// </summary>
internal static void VerifyTupleTypePresent(int cardinality, CSharpSyntaxNode? syntax, CSharpCompilation compilation, BindingDiagnosticBag diagnostics)
{
RoslynDebug.Assert(diagnostics is object && syntax is object);
int remainder;
int chainLength = NumberOfValueTuples(cardinality, out remainder);
NamedTypeSymbol firstTupleType = compilation.GetWellKnownType(GetTupleType(remainder));
ReportUseSiteAndObsoleteDiagnostics(syntax, diagnostics, firstTupleType);
if (chainLength > 1)
{
NamedTypeSymbol chainedTupleType = compilation.GetWellKnownType(GetTupleType(ValueTupleRestPosition));
ReportUseSiteAndObsoleteDiagnostics(syntax, diagnostics, chainedTupleType);
}
}
internal static void ReportTupleNamesMismatchesIfAny(TypeSymbol destination, BoundTupleLiteral literal, BindingDiagnosticBag diagnostics)
{
var sourceNames = literal.ArgumentNamesOpt;
if (sourceNames.IsDefault)
{
return;
}
ImmutableArray<bool> inferredNames = literal.InferredNamesOpt;
bool noInferredNames = inferredNames.IsDefault;
ImmutableArray<string> destinationNames = destination.TupleElementNames;
int sourceLength = sourceNames.Length;
bool allMissing = destinationNames.IsDefault;
Debug.Assert(allMissing || destinationNames.Length == sourceLength);
for (int i = 0; i < sourceLength; i++)
{
var sourceName = sourceNames[i];
var wasInferred = noInferredNames ? false : inferredNames[i];
if (sourceName != null && !wasInferred && (allMissing || string.CompareOrdinal(destinationNames[i], sourceName) != 0))
{
diagnostics.Add(ErrorCode.WRN_TupleLiteralNameMismatch, literal.Arguments[i].Syntax.Parent!.Location, sourceName, destination);
}
}
}
/// <summary>
/// Find the well-known ValueTuple type of a given arity.
/// For example, for arity=2:
/// returns WellKnownType.System_ValueTuple_T2
/// </summary>
private static WellKnownType GetTupleType(int arity)
{
if (arity > ValueTupleRestPosition)
{
throw ExceptionUtilities.Unreachable();
}
return tupleTypes[arity - 1];
}
private static readonly WellKnownType[] tupleTypes = {
WellKnownType.System_ValueTuple_T1,
WellKnownType.System_ValueTuple_T2,
WellKnownType.System_ValueTuple_T3,
WellKnownType.System_ValueTuple_T4,
WellKnownType.System_ValueTuple_T5,
WellKnownType.System_ValueTuple_T6,
WellKnownType.System_ValueTuple_T7,
WellKnownType.System_ValueTuple_TRest };
/// <summary>
/// Find the constructor for a well-known ValueTuple type of a given arity.
///
/// For example, for arity=2:
/// returns WellKnownMember.System_ValueTuple_T2__ctor
///
/// For arity=12:
/// return System_ValueTuple_TRest__ctor
/// </summary>
internal static WellKnownMember GetTupleCtor(int arity)
{
if (arity > 8)
{
throw ExceptionUtilities.Unreachable();
}
return tupleCtors[arity - 1];
}
private static readonly WellKnownMember[] tupleCtors = {
WellKnownMember.System_ValueTuple_T1__ctor,
WellKnownMember.System_ValueTuple_T2__ctor,
WellKnownMember.System_ValueTuple_T3__ctor,
WellKnownMember.System_ValueTuple_T4__ctor,
WellKnownMember.System_ValueTuple_T5__ctor,
WellKnownMember.System_ValueTuple_T6__ctor,
WellKnownMember.System_ValueTuple_T7__ctor,
WellKnownMember.System_ValueTuple_TRest__ctor };
/// <summary>
/// Find the well-known members to the ValueTuple type of a given arity and position.
/// For example, for arity=3 and position=1:
/// returns WellKnownMember.System_ValueTuple_T3__Item1
/// </summary>
internal static WellKnownMember GetTupleTypeMember(int arity, int position)
{
return tupleMembers[arity - 1][position - 1];
}
private static readonly WellKnownMember[][] tupleMembers = new[]{
new[]{
WellKnownMember.System_ValueTuple_T1__Item1 },
new[]{
WellKnownMember.System_ValueTuple_T2__Item1,
WellKnownMember.System_ValueTuple_T2__Item2 },
new[]{
WellKnownMember.System_ValueTuple_T3__Item1,
WellKnownMember.System_ValueTuple_T3__Item2,
WellKnownMember.System_ValueTuple_T3__Item3 },
new[]{
WellKnownMember.System_ValueTuple_T4__Item1,
WellKnownMember.System_ValueTuple_T4__Item2,
WellKnownMember.System_ValueTuple_T4__Item3,
WellKnownMember.System_ValueTuple_T4__Item4 },
new[]{
WellKnownMember.System_ValueTuple_T5__Item1,
WellKnownMember.System_ValueTuple_T5__Item2,
WellKnownMember.System_ValueTuple_T5__Item3,
WellKnownMember.System_ValueTuple_T5__Item4,
WellKnownMember.System_ValueTuple_T5__Item5 },
new[]{
WellKnownMember.System_ValueTuple_T6__Item1,
WellKnownMember.System_ValueTuple_T6__Item2,
WellKnownMember.System_ValueTuple_T6__Item3,
WellKnownMember.System_ValueTuple_T6__Item4,
WellKnownMember.System_ValueTuple_T6__Item5,
WellKnownMember.System_ValueTuple_T6__Item6 },
new[]{
WellKnownMember.System_ValueTuple_T7__Item1,
WellKnownMember.System_ValueTuple_T7__Item2,
WellKnownMember.System_ValueTuple_T7__Item3,
WellKnownMember.System_ValueTuple_T7__Item4,
WellKnownMember.System_ValueTuple_T7__Item5,
WellKnownMember.System_ValueTuple_T7__Item6,
WellKnownMember.System_ValueTuple_T7__Item7 },
new[]{
WellKnownMember.System_ValueTuple_TRest__Item1,
WellKnownMember.System_ValueTuple_TRest__Item2,
WellKnownMember.System_ValueTuple_TRest__Item3,
WellKnownMember.System_ValueTuple_TRest__Item4,
WellKnownMember.System_ValueTuple_TRest__Item5,
WellKnownMember.System_ValueTuple_TRest__Item6,
WellKnownMember.System_ValueTuple_TRest__Item7,
WellKnownMember.System_ValueTuple_TRest__Rest }
};
/// <summary>
/// Returns "Item1" for position=1
/// Returns "Item12" for position=12
/// </summary>
internal static string TupleMemberName(int position)
{
return "Item" + position;
}
/// <summary>
/// Checks whether the field name is reserved and tells us which position it's reserved for.
///
/// For example:
/// Returns 3 for "Item3".
/// Returns 0 for "Rest", "ToString" and other members of System.ValueTuple.
/// Returns -1 for names that aren't reserved.
/// </summary>
internal static int IsTupleElementNameReserved(string name)
{
if (isElementNameForbidden(name))
{
return 0;
}
return MatchesCanonicalTupleElementName(name);
static bool isElementNameForbidden(string name)
{
switch (name)
{
case "CompareTo":
case WellKnownMemberNames.DeconstructMethodName:
case "Equals":
case "GetHashCode":
case "Rest":
case "ToString":
return true;
default:
return false;
}
}
}
// Returns 3 for "Item3".
// Returns -1 otherwise.
internal static int MatchesCanonicalTupleElementName(string name)
{
if (name.StartsWith("Item", StringComparison.Ordinal))
{
string tail = name.Substring("Item".Length);
if (int.TryParse(tail, out int number))
{
if (number > 0 && string.Equals(name, TupleMemberName(number), StringComparison.Ordinal))
{
return number;
}
}
}
return -1;
}
/// <summary>
/// Lookup well-known member declaration in provided type and reports diagnostics.
/// </summary>
internal static Symbol? GetWellKnownMemberInType(NamedTypeSymbol type, WellKnownMember relativeMember, BindingDiagnosticBag diagnostics, SyntaxNode? syntax)
{
Symbol? member = GetWellKnownMemberInType(type, relativeMember);
if (member is null)
{
MemberDescriptor relativeDescriptor = WellKnownMembers.GetDescriptor(relativeMember);
Binder.Error(diagnostics, ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, syntax, relativeDescriptor.Name, type, type.ContainingAssembly);
}
else
{
UseSiteInfo<AssemblySymbol> useSiteInfo = member.GetUseSiteInfo();
if (useSiteInfo.DiagnosticInfo?.Severity != DiagnosticSeverity.Error)
{
useSiteInfo = useSiteInfo.AdjustDiagnosticInfo(null);
}
diagnostics.Add(useSiteInfo, static syntax => syntax?.Location ?? Location.None, syntax);
}
return member;
// Lookup well-known member declaration in provided type.
//
// If a well-known member of a generic type instantiation is needed use this method to get the corresponding generic definition and
// <see cref="MethodSymbol.AsMember"/> to construct an instantiation.
//
// <param name="type">Type that we'll try to find member in.</param>
// <param name="relativeMember">A reference to a well-known member type descriptor. Note however that the type in that descriptor is ignored here.</param>
static Symbol? GetWellKnownMemberInType(NamedTypeSymbol type, WellKnownMember relativeMember)
{
Debug.Assert(relativeMember >= WellKnownMember.System_ValueTuple_T1__Item1 && relativeMember <= WellKnownMember.System_ValueTuple_TRest__ctor);
Debug.Assert(type.IsDefinition);
MemberDescriptor relativeDescriptor = WellKnownMembers.GetDescriptor(relativeMember);
var members = type.GetMembers(relativeDescriptor.Name);
return CSharpCompilation.GetRuntimeMember(members, relativeDescriptor, CSharpCompilation.SpecialMembersSignatureComparer.Instance,
accessWithinOpt: null); // force lookup of public members only
}
}
public sealed override bool IsTupleType
=> IsTupleTypeOfCardinality(tupleCardinality: out _);
internal TupleExtraData? TupleData
{
get
{
if (!IsTupleType)
{
return null;
}
if (_lazyTupleData is null)
{
Interlocked.CompareExchange(ref _lazyTupleData, new TupleExtraData(this), null);
}
return _lazyTupleData;
}
}
public sealed override ImmutableArray<string?> TupleElementNames
=> _lazyTupleData is null ? default : _lazyTupleData.ElementNames;
private ImmutableArray<bool> TupleErrorPositions
=> _lazyTupleData is null ? default : _lazyTupleData.ErrorPositions;
private ImmutableArray<Location?> TupleElementLocations
=> _lazyTupleData is null ? default : _lazyTupleData.ElementLocations;
public sealed override ImmutableArray<TypeWithAnnotations> TupleElementTypesWithAnnotations
=> IsTupleType ? TupleData!.TupleElementTypesWithAnnotations(this) : default;
public sealed override ImmutableArray<FieldSymbol> TupleElements
=> IsTupleType ? TupleData!.TupleElements(this) : default;
public TMember? GetTupleMemberSymbolForUnderlyingMember<TMember>(TMember? underlyingMemberOpt) where TMember : Symbol
{
return IsTupleType ? TupleData!.GetTupleMemberSymbolForUnderlyingMember(underlyingMemberOpt) : null;
}
protected ArrayBuilder<Symbol> MakeSynthesizedTupleMembers(ImmutableArray<Symbol> currentMembers, HashSet<Symbol>? replacedFields = null)
{
Debug.Assert(IsTupleType);
Debug.Assert(currentMembers.All(m => !(m is TupleVirtualElementFieldSymbol)));
var elementTypes = TupleElementTypesWithAnnotations;
var elementsMatchedByFields = ArrayBuilder<bool>.GetInstance(elementTypes.Length, fillWithValue: false);
var members = ArrayBuilder<Symbol>.GetInstance(currentMembers.Length);
NamedTypeSymbol currentValueTuple = this;
int currentNestingLevel = 0;
var currentFieldsForElements = ArrayBuilder<FieldSymbol?>.GetInstance(currentValueTuple.Arity);
// Lookup field definitions that we are interested in
collectTargetTupleFields(currentValueTuple.Arity, getOriginalFields(currentMembers), currentFieldsForElements);
var elementNames = TupleElementNames;
var elementLocations = TupleData!.ElementLocations;
while (true)
{
foreach (Symbol member in currentMembers)
{
switch (member.Kind)
{
case SymbolKind.Field:
var field = (FieldSymbol)member;
if (field is TupleVirtualElementFieldSymbol)
{
// In a long tuple situation where the nested tuple has names, we don't care about those names.
// We will re-add all necessary virtual element field symbols below.
replacedFields?.Add(field);
continue;
}
var underlyingField = field is TupleElementFieldSymbol tupleElement ? tupleElement.UnderlyingField.OriginalDefinition : field.OriginalDefinition;
int tupleFieldIndex = currentFieldsForElements.IndexOf(underlyingField, ReferenceEqualityComparer.Instance);
if (underlyingField is TupleErrorFieldSymbol)
{
// We will re-add all necessary error field symbols below.
replacedFields?.Add(field);
continue;
}
else if (tupleFieldIndex >= 0)
{
// adjust tuple index for nesting
if (currentNestingLevel != 0)
{
tupleFieldIndex += (ValueTupleRestPosition - 1) * currentNestingLevel;
}
else
{
replacedFields?.Add(field);
}
var providedName = elementNames.IsDefault ? null : elementNames[tupleFieldIndex];
ImmutableArray<Location> locations = getElementLocations(in elementLocations, tupleFieldIndex);
var defaultName = TupleMemberName(tupleFieldIndex + 1);
// if provided name does not match the default one,
// then default element is declared implicitly
var defaultImplicitlyDeclared = providedName != defaultName;
// Add a field with default name. It should be present regardless.
FieldSymbol defaultTupleField;
var fieldSymbol = underlyingField.AsMember(currentValueTuple);
if (currentNestingLevel != 0)
{
// This is a matching field, but it is in the extension tuple
// Make it virtual since we are not at the top level
defaultTupleField = new TupleVirtualElementFieldSymbol(this,
fieldSymbol,
defaultName,
tupleFieldIndex,
locations,
cannotUse: false,
isImplicitlyDeclared: defaultImplicitlyDeclared,
correspondingDefaultFieldOpt: null);
members.Add(defaultTupleField);
}
else
{
Debug.Assert(fieldSymbol.Name == defaultName, "top level underlying field must match default name");
if (IsDefinition)
{
defaultTupleField = field;
}
else
{
// Add the underlying/real field as an element (wrapping mainly to capture location). It should have the default name.
defaultTupleField = new TupleElementFieldSymbol(this,
fieldSymbol,
tupleFieldIndex,
locations,
isImplicitlyDeclared: defaultImplicitlyDeclared);
members.Add(defaultTupleField);
}
}
if (defaultImplicitlyDeclared && !string.IsNullOrEmpty(providedName))
{
var errorPositions = TupleErrorPositions;
var isError = errorPositions.IsDefault ? false : errorPositions[tupleFieldIndex];
// The name given doesn't match the default name Item8, etc.
// Add a virtual field with the given name
members.Add(new TupleVirtualElementFieldSymbol(this,
fieldSymbol,
providedName,
tupleFieldIndex,
locations,
cannotUse: isError,
isImplicitlyDeclared: false,
correspondingDefaultFieldOpt: defaultTupleField));
}
elementsMatchedByFields[tupleFieldIndex] = true; // mark as handled
}
// No need to wrap other real fields
break;
case SymbolKind.NamedType:
case SymbolKind.Method:
case SymbolKind.Property:
case SymbolKind.Event:
break;
default:
if (currentNestingLevel == 0)
{
throw ExceptionUtilities.UnexpectedValue(member.Kind);
}
break;
}
}
if (currentValueTuple.Arity != ValueTupleRestPosition)
{
break;
}
var oldUnderlying = currentValueTuple;
currentValueTuple = (NamedTypeSymbol)oldUnderlying.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[ValueTupleRestIndex].Type;
currentNestingLevel++;
if (currentValueTuple.Arity != ValueTupleRestPosition)
{
// refresh members and target fields
currentMembers = currentValueTuple.GetMembers();
currentFieldsForElements.Clear();
collectTargetTupleFields(currentValueTuple.Arity, getOriginalFields(currentMembers), currentFieldsForElements);
}
else
{
Debug.Assert((object)oldUnderlying.OriginalDefinition == currentValueTuple.OriginalDefinition);
}
}
currentFieldsForElements.Free();
// At the end, add unmatched fields as error symbols
for (int i = 0; i < elementsMatchedByFields.Count; i++)
{
if (!elementsMatchedByFields[i])
{
// We couldn't find a backing field for this element. It will be an error to access it.
int fieldRemainder; // one-based
int fieldChainLength = NumberOfValueTuples(i + 1, out fieldRemainder);
NamedTypeSymbol container = getNestedTupleUnderlyingType(this, fieldChainLength - 1).OriginalDefinition;
var diagnosticInfo = container.IsErrorType() ?
null :
new CSDiagnosticInfo(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly,
TupleMemberName(fieldRemainder),
container,
container.ContainingAssembly);
var providedName = elementNames.IsDefault ? null : elementNames[i];
var location = elementLocations.IsDefault ? null : elementLocations[i];
var defaultName = TupleMemberName(i + 1);
// if provided name does not match the default one,
// then default element is declared implicitly
var defaultImplicitlyDeclared = providedName != defaultName;
// Add a field with default name. It should be present regardless.
TupleErrorFieldSymbol defaultTupleField = new TupleErrorFieldSymbol(this,
defaultName,
i,
defaultImplicitlyDeclared ? null : location,
elementTypes[i],
diagnosticInfo,
defaultImplicitlyDeclared,
correspondingDefaultFieldOpt: null);
members.Add(defaultTupleField);
if (defaultImplicitlyDeclared && !String.IsNullOrEmpty(providedName))
{
// Add friendly named element field.
members.Add(new TupleErrorFieldSymbol(this,
providedName,
i,
location,
elementTypes[i],
diagnosticInfo,
isImplicitlyDeclared: false,
correspondingDefaultFieldOpt: defaultTupleField));
}
}
}
elementsMatchedByFields.Free();
return members;
// Returns the nested type at a certain depth.
//
// For depth=0, just return the tuple type as-is.
// For depth=1, returns the nested tuple type at position 8.
static NamedTypeSymbol getNestedTupleUnderlyingType(NamedTypeSymbol topLevelUnderlyingType, int depth)
{
NamedTypeSymbol found = topLevelUnderlyingType;
for (int i = 0; i < depth; i++)
{
found = (NamedTypeSymbol)found.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[ValueTupleRestPosition - 1].Type;
}
return found;
}
static void collectTargetTupleFields(int arity, ImmutableArray<Symbol> members, ArrayBuilder<FieldSymbol?> fieldsForElements)
{
int fieldsPerType = Math.Min(arity, ValueTupleRestPosition - 1);
for (int i = 0; i < fieldsPerType; i++)
{
WellKnownMember wellKnownTupleField = GetTupleTypeMember(arity, i + 1);
fieldsForElements.Add((FieldSymbol?)getWellKnownMemberInType(members, wellKnownTupleField));
}
}
static Symbol? getWellKnownMemberInType(ImmutableArray<Symbol> members, WellKnownMember relativeMember)
{
Debug.Assert(relativeMember >= WellKnownMember.System_ValueTuple_T1__Item1 && relativeMember <= WellKnownMember.System_ValueTuple_TRest__ctor);
MemberDescriptor relativeDescriptor = WellKnownMembers.GetDescriptor(relativeMember);
return CSharpCompilation.GetRuntimeMember(members, relativeDescriptor, CSharpCompilation.SpecialMembersSignatureComparer.Instance,
accessWithinOpt: null); // force lookup of public members only
}
static ImmutableArray<Symbol> getOriginalFields(ImmutableArray<Symbol> members)
{
var fields = ArrayBuilder<Symbol>.GetInstance();
foreach (var member in members)
{
if (member is TupleVirtualElementFieldSymbol)
{
continue;
}
else if (member is TupleElementFieldSymbol tupleField)
{
fields.Add(tupleField.UnderlyingField.OriginalDefinition);
}
else if (member is FieldSymbol field)
{
fields.Add(field.OriginalDefinition);
}
}
Debug.Assert(fields.All(f => f is object));
return fields.ToImmutableAndFree();
}
static ImmutableArray<Location> getElementLocations(in ImmutableArray<Location?> elementLocations, int tupleFieldIndex)
{
if (elementLocations.IsDefault)
{
return ImmutableArray<Location>.Empty;
}
var elementLocation = elementLocations[tupleFieldIndex];
return elementLocation == null ? ImmutableArray<Location>.Empty : ImmutableArray.Create(elementLocation);
}
}
private TypeSymbol MergeTupleNames(NamedTypeSymbol other, NamedTypeSymbol mergedType)
{
// Merge tuple element names, if any
ImmutableArray<string?> names1 = TupleElementNames;
ImmutableArray<string?> names2 = other.TupleElementNames;
ImmutableArray<string?> mergedNames;
if (names1.IsDefault || names2.IsDefault)
{
mergedNames = default;
}
else
{
Debug.Assert(names1.Length == names2.Length);
mergedNames = names1.ZipAsArray(names2, (n1, n2) => string.CompareOrdinal(n1, n2) == 0 ? n1 : null)!;
if (mergedNames.All(n => n is null))
{
mergedNames = default;
}
}
bool namesUnchanged = mergedNames.IsDefault ? TupleElementNames.IsDefault : mergedNames.SequenceEqual(TupleElementNames);
return (namesUnchanged && this.Equals(mergedType, TypeCompareKind.ConsiderEverything))
? this
: CreateTuple(mergedType, mergedNames, this.TupleErrorPositions, this.TupleElementLocations, this.Locations);
}
/// <summary>
/// The main purpose of this type is to store element names and also cache some information related to tuples.
/// </summary>
internal sealed class TupleExtraData
{
/// <summary>
/// Element names, if provided.
/// </summary>
internal ImmutableArray<string?> ElementNames { get; }
/// <summary>
/// Declaration locations for individual elements, if provided.
/// Declaration location for this tuple type symbol
/// </summary>
internal ImmutableArray<Location?> ElementLocations { get; }
/// <summary>
/// Which element names were inferred and therefore cannot be used.
/// If none of the element names were inferred, or inferred names can be used (no tracking necessary), leave as default.
/// This information is ignored in type equality and comparison.
/// </summary>
internal ImmutableArray<bool> ErrorPositions { get; }
internal ImmutableArray<Location> Locations { get; }
/// <summary>
/// Element types.
/// </summary>
private ImmutableArray<TypeWithAnnotations> _lazyElementTypes;
private ImmutableArray<FieldSymbol> _lazyDefaultElementFields;
private SmallDictionary<Symbol, Symbol>? _lazyUnderlyingDefinitionToMemberMap;
/// <summary>
/// The same named type, but without element names.
/// </summary>
internal NamedTypeSymbol TupleUnderlyingType { get; }
internal TupleExtraData(NamedTypeSymbol underlyingType)
{
RoslynDebug.Assert(underlyingType is object);
Debug.Assert(underlyingType.IsTupleType);
Debug.Assert(underlyingType.TupleElementNames.IsDefault);
TupleUnderlyingType = underlyingType;
Locations = ImmutableArray<Location>.Empty;
}
internal TupleExtraData(NamedTypeSymbol underlyingType, ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations, ImmutableArray<bool> errorPositions, ImmutableArray<Location> locations)
: this(underlyingType)
{
ElementNames = elementNames;
ElementLocations = elementLocations;
ErrorPositions = errorPositions;
Locations = locations.NullToEmpty();
}
internal bool EqualsIgnoringTupleUnderlyingType(TupleExtraData? other)
{
if (other is null && this.ElementNames.IsDefault && this.ElementLocations.IsDefault && this.ErrorPositions.IsDefault)
{
return true;
}
return other is object
&& areEqual(this.ElementNames, other.ElementNames)
&& areEqual(this.ElementLocations, other.ElementLocations)
&& areEqual(this.ErrorPositions, other.ErrorPositions);
static bool areEqual<T>(ImmutableArray<T> one, ImmutableArray<T> other)
{
if (one.IsDefault && other.IsDefault)
{
return true;
}
if (one.IsDefault != other.IsDefault)
{
return false;
}
return one.SequenceEqual(other);
}
}
public ImmutableArray<TypeWithAnnotations> TupleElementTypesWithAnnotations(NamedTypeSymbol tuple)
{
Debug.Assert(tuple.Equals(TupleUnderlyingType, TypeCompareKind.IgnoreTupleNames));
if (_lazyElementTypes.IsDefault)
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyElementTypes, collectTupleElementTypesWithAnnotations(tuple));
}
return _lazyElementTypes;
static ImmutableArray<TypeWithAnnotations> collectTupleElementTypesWithAnnotations(NamedTypeSymbol tuple)
{
ImmutableArray<TypeWithAnnotations> elementTypes;
if (tuple.Arity == ValueTupleRestPosition)
{
// Ensure all Rest extensions are tuples
var extensionTupleElementTypes = tuple.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[ValueTupleRestPosition - 1].Type.TupleElementTypesWithAnnotations;
var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(ValueTupleRestPosition - 1 + extensionTupleElementTypes.Length);
typesBuilder.AddRange(tuple.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics, ValueTupleRestPosition - 1);
typesBuilder.AddRange(extensionTupleElementTypes);
elementTypes = typesBuilder.ToImmutableAndFree();
}
else
{
elementTypes = tuple.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics;
}
return elementTypes;
}
}
public ImmutableArray<FieldSymbol> TupleElements(NamedTypeSymbol tuple)
{
Debug.Assert(tuple.Equals(TupleUnderlyingType, TypeCompareKind.IgnoreTupleNames));
if (_lazyDefaultElementFields.IsDefault)
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyDefaultElementFields, collectTupleElementFields(tuple));
}
return _lazyDefaultElementFields;
ImmutableArray<FieldSymbol> collectTupleElementFields(NamedTypeSymbol tuple)
{
var builder = ArrayBuilder<FieldSymbol>.GetInstance(TupleElementTypesWithAnnotations(tuple).Length, fillWithValue: null!);
foreach (var member in tuple.GetMembers())
{
if (member.Kind != SymbolKind.Field)
{
continue;
}
var candidate = (FieldSymbol)member;
var index = candidate.TupleElementIndex;
if (index >= 0)
{
if (builder[index]?.IsDefaultTupleElement != false)
{
builder[index] = candidate;
}
else
{
// there is a better field in the slot
// that can only happen if the candidate is default.
Debug.Assert(candidate.IsDefaultTupleElement);
}
}
}
Debug.Assert(builder.All(f => f is object));
return builder.ToImmutableAndFree();
}
}
internal SmallDictionary<Symbol, Symbol> UnderlyingDefinitionToMemberMap
{
get
{
return _lazyUnderlyingDefinitionToMemberMap ??= computeDefinitionToMemberMap();
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)
{
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;
var underlyingAssociatedField = underlyingEvent.AssociatedField;
// The field is not part of the members list
if (underlyingAssociatedField is object)
{
Debug.Assert((object)underlyingAssociatedField.ContainingSymbol == TupleUnderlyingType);
Debug.Assert(TupleUnderlyingType.GetMembers(underlyingAssociatedField.Name).IndexOf(underlyingAssociatedField) < 0);
map.Add(underlyingAssociatedField.OriginalDefinition, underlyingAssociatedField);
}
map.Add(underlyingEvent.OriginalDefinition, member);
break;
default:
throw ExceptionUtilities.UnexpectedValue(member.Kind);
}
}
return map;
}
}
}
public TMember? GetTupleMemberSymbolForUnderlyingMember<TMember>(TMember? underlyingMemberOpt) where TMember : Symbol
{
if (underlyingMemberOpt is null)
{
return null;
}
Symbol underlyingMemberDefinition = underlyingMemberOpt.OriginalDefinition;
if (underlyingMemberDefinition is TupleElementFieldSymbol tupleField)
{
underlyingMemberDefinition = tupleField.UnderlyingField;
}
if (TypeSymbol.Equals(underlyingMemberDefinition.ContainingType, TupleUnderlyingType.OriginalDefinition, TypeCompareKind.ConsiderEverything))
{
if (UnderlyingDefinitionToMemberMap.TryGetValue(underlyingMemberDefinition, out Symbol? result))
{
return (TMember)result;
}
}
return null;
}
}
}
}
|