Files
ILSpy/ICSharpCode.Decompiler/TypeSystem/TupleType.cs
T
Siegfried Pammer a216586236 Require System.ValueTuple to be a struct to be tuple compatible
C# only accepts System.ValueTuple as a tuple when it is a struct, so a class of
that name is an unrelated type and rendering it with tuple syntax describes it
as something it is not. It also made a tuple appear to contain itself, which no
struct can, and the deconstruction transform then registered the same variable
as a node of its tuple tree twice and threw ArgumentException, failing the whole
method instead of leaving the statements alone.

The check has accepted classes since tuples were added to the type system,
alongside a name comparison against "ValueType" that was corrected later.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
2026-08-07 16:19:00 +02:00

388 lines
12 KiB
C#

// Copyright (c) 2018 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.TypeSystem
{
public sealed class TupleType : AbstractType, ICompilationProvider
{
public const int RestPosition = 8;
const int RestIndex = RestPosition - 1;
public ICompilation Compilation { get; }
/// <summary>
/// Gets the underlying <c>System.ValueType</c> type.
/// </summary>
public ParameterizedType UnderlyingType { get; }
/// <summary>
/// Gets the tuple elements.
/// </summary>
public ImmutableArray<IType> ElementTypes { get; }
/// <summary>
/// Gets the cardinality of the tuple.
/// </summary>
public int Cardinality => ElementTypes.Length;
/// <summary>
/// Gets the names of the tuple elements.
/// </summary>
public ImmutableArray<string> ElementNames { get; }
public TupleType(ICompilation compilation, ImmutableArray<IType> elementTypes,
ImmutableArray<string> elementNames = default(ImmutableArray<string>),
IModule valueTupleAssembly = null)
{
this.Compilation = compilation;
this.UnderlyingType = CreateUnderlyingType(compilation, elementTypes, valueTupleAssembly);
this.ElementTypes = elementTypes;
if (elementNames.IsDefault)
{
this.ElementNames = Enumerable.Repeat<string>(null, elementTypes.Length).ToImmutableArray();
}
else
{
Debug.Assert(elementNames.Length == elementTypes.Length);
this.ElementNames = elementNames;
}
}
static ParameterizedType CreateUnderlyingType(ICompilation compilation, ImmutableArray<IType> elementTypes, IModule valueTupleAssembly)
{
int remainder = (elementTypes.Length - 1) % (RestPosition - 1) + 1;
Debug.Assert(remainder >= 1 && remainder < RestPosition);
int pos = elementTypes.Length - remainder;
var type = new ParameterizedType(
FindValueTupleType(compilation, valueTupleAssembly, remainder),
elementTypes.Slice(pos));
while (pos > 0)
{
pos -= (RestPosition - 1);
type = new ParameterizedType(
FindValueTupleType(compilation, valueTupleAssembly, RestPosition),
elementTypes.Slice(pos, RestPosition - 1).Concat(new[] { type }));
}
Debug.Assert(pos == 0);
return type;
}
private static IType FindValueTupleType(ICompilation compilation, IModule valueTupleAssembly, int tpc)
{
var typeName = new TopLevelTypeName("System", "ValueTuple", tpc);
if (valueTupleAssembly != null)
{
var typeDef = valueTupleAssembly.GetTypeDefinition(typeName);
if (typeDef != null)
return typeDef;
}
return compilation.FindType(typeName);
}
/// <summary>
/// Gets whether the specified type is a valid underlying type for a tuple.
/// Also returns true for tuple types themselves.
/// </summary>
public static bool IsTupleCompatible(IType type, out int tupleCardinality)
{
switch (type.Kind)
{
case TypeKind.Tuple:
tupleCardinality = ((TupleType)type).ElementTypes.Length;
return true;
case TypeKind.Struct:
// C# requires System.ValueTuple to be a struct, so a class of that name is
// some other type that happens to share it and must not become tuple syntax.
if (type.Namespace == "System" && type.Name == "ValueTuple")
{
int tpc = type.TypeParameterCount;
if (tpc > 0 && tpc < RestPosition)
{
tupleCardinality = tpc;
return true;
}
else if (tpc == RestPosition && type is ParameterizedType pt)
{
if (IsTupleCompatible(pt.TypeArguments[RestIndex], out tupleCardinality))
{
tupleCardinality += RestPosition - 1;
return true;
}
}
}
break;
}
tupleCardinality = 0;
return false;
}
/// <summary>
/// Construct a tuple type (without element names) from the given underlying type.
/// Returns null if the input is not a valid underlying type.
/// </summary>
public static TupleType FromUnderlyingType(ICompilation compilation, IType type)
{
var elementTypes = GetTupleElementTypes(type);
if (!elementTypes.IsDefaultOrEmpty)
{
return new TupleType(
compilation,
elementTypes,
valueTupleAssembly: type.GetDefinition()?.ParentModule
);
}
else
{
return null;
}
}
/// <summary>
/// Gets the tuple element types from a tuple type or tuple underlying type.
/// </summary>
public static ImmutableArray<IType> GetTupleElementTypes(IType tupleType)
{
List<IType> output = null;
if (Collect(tupleType))
{
return output.ToImmutableArray();
}
else
{
return default(ImmutableArray<IType>);
}
bool Collect(IType type)
{
switch (type.Kind)
{
case TypeKind.Tuple:
if (output == null)
output = new List<IType>();
output.AddRange(((TupleType)type).ElementTypes);
return true;
case TypeKind.Class:
case TypeKind.Struct:
if (type.Namespace == "System" && type.Name == "ValueTuple")
{
if (output == null)
output = new List<IType>();
int tpc = type.TypeParameterCount;
if (tpc > 0 && tpc < RestPosition)
{
output.AddRange(type.TypeArguments);
return true;
}
else if (tpc == RestPosition)
{
output.AddRange(type.TypeArguments.Take(RestPosition - 1));
return Collect(type.TypeArguments[RestIndex]);
}
}
break;
}
return false;
}
}
public override TypeKind Kind => TypeKind.Tuple;
public override bool? IsReferenceType => UnderlyingType.IsReferenceType;
public override int TypeParameterCount => 0;
public override IReadOnlyList<ITypeParameter> TypeParameters => EmptyList<ITypeParameter>.Instance;
public override IReadOnlyList<IType> TypeArguments => EmptyList<IType>.Instance;
public override IEnumerable<IType> DirectBaseTypes => UnderlyingType.DirectBaseTypes;
public override string FullName => UnderlyingType.FullName;
public override string Name => UnderlyingType.Name;
public override string ReflectionName => UnderlyingType.ReflectionName;
public override string Namespace => UnderlyingType.Namespace;
public override bool Equals(IType other)
{
var o = other as TupleType;
if (o == null)
return false;
if (!UnderlyingType.Equals(o.UnderlyingType))
return false;
return UnderlyingType.Equals(o.UnderlyingType)
&& ElementNames.SequenceEqual(o.ElementNames);
}
public override int GetHashCode()
{
unchecked
{
int hash = UnderlyingType.GetHashCode();
foreach (string name in ElementNames)
{
hash *= 31;
hash += name != null ? name.GetHashCode() : 0;
}
return hash;
}
}
public override string ToString()
{
StringBuilder b = new StringBuilder();
b.Append('(');
for (int i = 0; i < ElementTypes.Length; i++)
{
if (i > 0)
b.Append(", ");
b.Append(ElementTypes[i]);
if (ElementNames[i] != null)
{
b.Append(' ');
b.Append(ElementNames[i]);
}
}
b.Append(')');
return b.ToString();
}
public override IType AcceptVisitor(TypeVisitor visitor)
{
return visitor.VisitTupleType(this);
}
public override IType VisitChildren(TypeVisitor visitor)
{
IType[] newElementTypes = null;
for (int i = 0; i < ElementTypes.Length; i++)
{
IType type = ElementTypes[i];
var newType = type.AcceptVisitor(visitor);
if (newType != type)
{
if (newElementTypes == null)
{
newElementTypes = ElementTypes.ToArray();
}
newElementTypes[i] = newType;
}
}
if (newElementTypes != null)
{
return new TupleType(this.Compilation, newElementTypes.ToImmutableArray(), this.ElementNames,
this.GetDefinition()?.ParentModule);
}
else
{
return this;
}
}
public override IEnumerable<IMethod> GetAccessors(Predicate<IMethod> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetAccessors(filter, options);
}
public override IEnumerable<IMethod> GetConstructors(Predicate<IMethod> filter = null, GetMemberOptions options = GetMemberOptions.IgnoreInheritedMembers)
{
// CS8181 'new' cannot be used with tuple type. Use a tuple literal expression instead.
return EmptyList<IMethod>.Instance;
}
public override ITypeDefinition GetDefinition()
{
return UnderlyingType.GetDefinition();
}
public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown()
{
return UnderlyingType.GetDefinitionOrUnknown();
}
public override IEnumerable<IEvent> GetEvents(Predicate<IEvent> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetEvents(filter, options);
}
public override IEnumerable<IField> GetFields(Predicate<IField> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
// The fields from the underlying type (Item1..Item7 and Rest)
foreach (var field in UnderlyingType.GetFields(filter, options))
{
yield return field;
}
/*for (int i = 0; i < ElementTypes.Length; i++) {
var type = ElementTypes[i];
var name = ElementNames[i];
int pos = i + 1;
string itemName = "Item" + pos;
if (name != itemName && name != null)
yield return MakeField(type, name);
if (pos >= RestPosition)
yield return MakeField(type, itemName);
}*/
}
/*private IField MakeField(IType type, string name)
{
var f = new DefaultUnresolvedField();
f.ReturnType = SpecialType.UnknownType;
f.Name = name;
return new TupleElementField(f, Compilation.TypeResolveContext);
}*/
public override IEnumerable<IMethod> GetMethods(Predicate<IMethod> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetMethods(filter, options);
}
public override IEnumerable<IMethod> GetMethods(IReadOnlyList<IType> typeArguments, Predicate<IMethod> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetMethods(typeArguments, filter, options);
}
public override IEnumerable<IType> GetNestedTypes(Predicate<ITypeDefinition> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetNestedTypes(filter, options);
}
public override IEnumerable<IType> GetNestedTypes(IReadOnlyList<IType> typeArguments, Predicate<ITypeDefinition> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetNestedTypes(typeArguments, filter, options);
}
public override IEnumerable<IProperty> GetProperties(Predicate<IProperty> filter = null, GetMemberOptions options = GetMemberOptions.None)
{
return UnderlyingType.GetProperties(filter, options);
}
}
public static class TupleTypeExtensions
{
public static IType TupleUnderlyingTypeOrSelf(this IType type)
{
var t = (type as TupleType)?.UnderlyingType ?? type;
return t.WithoutNullability();
}
}
}