// 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; } /// /// Gets the underlying System.ValueType type. /// public ParameterizedType UnderlyingType { get; } /// /// Gets the tuple elements. /// public ImmutableArray ElementTypes { get; } /// /// Gets the cardinality of the tuple. /// public int Cardinality => ElementTypes.Length; /// /// Gets the names of the tuple elements. /// public ImmutableArray ElementNames { get; } public TupleType(ICompilation compilation, ImmutableArray elementTypes, ImmutableArray elementNames = default(ImmutableArray), IModule valueTupleAssembly = null) { this.Compilation = compilation; this.UnderlyingType = CreateUnderlyingType(compilation, elementTypes, valueTupleAssembly); this.ElementTypes = elementTypes; if (elementNames.IsDefault) { this.ElementNames = Enumerable.Repeat(null, elementTypes.Length).ToImmutableArray(); } else { Debug.Assert(elementNames.Length == elementTypes.Length); this.ElementNames = elementNames; } } static ParameterizedType CreateUnderlyingType(ICompilation compilation, ImmutableArray 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); } /// /// Gets whether the specified type is a valid underlying type for a tuple. /// Also returns true for tuple types themselves. /// public static bool IsTupleCompatible(IType type, out int tupleCardinality) { switch (type.Kind) { case TypeKind.Tuple: tupleCardinality = ((TupleType)type).ElementTypes.Length; return true; case TypeKind.Class: case TypeKind.Struct: 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; } /// /// Construct a tuple type (without element names) from the given underlying type. /// Returns null if the input is not a valid underlying type. /// public static TupleType FromUnderlyingType(ICompilation compilation, IType type) { var elementTypes = GetTupleElementTypes(type); if (elementTypes.Length > 0) { return new TupleType( compilation, elementTypes, valueTupleAssembly: type.GetDefinition()?.ParentModule ); } else { return null; } } /// /// Gets the tuple element types from a tuple type or tuple underlying type. /// public static ImmutableArray GetTupleElementTypes(IType tupleType) { List output = null; if (Collect(tupleType)) { return output.ToImmutableArray(); } else { return default(ImmutableArray); } bool Collect(IType type) { switch (type.Kind) { case TypeKind.Tuple: if (output == null) output = new List(); 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(); 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 TypeParameters => EmptyList.Instance; public override IReadOnlyList TypeArguments => EmptyList.Instance; public override IEnumerable 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 GetAccessors(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetAccessors(filter, options); } public override IEnumerable GetConstructors(Predicate filter = null, GetMemberOptions options = GetMemberOptions.IgnoreInheritedMembers) { // CS8181 'new' cannot be used with tuple type. Use a tuple literal expression instead. return EmptyList.Instance; } public override ITypeDefinition GetDefinition() { return UnderlyingType.GetDefinition(); } public override ITypeDefinitionOrUnknown GetDefinitionOrUnknown() { return UnderlyingType.GetDefinitionOrUnknown(); } public override IEnumerable GetEvents(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetEvents(filter, options); } public override IEnumerable GetFields(Predicate 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 GetMethods(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetMethods(filter, options); } public override IEnumerable GetMethods(IReadOnlyList typeArguments, Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetMethods(typeArguments, filter, options); } public override IEnumerable GetNestedTypes(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetNestedTypes(filter, options); } public override IEnumerable GetNestedTypes(IReadOnlyList typeArguments, Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetNestedTypes(typeArguments, filter, options); } public override IEnumerable GetProperties(Predicate filter = null, GetMemberOptions options = GetMemberOptions.None) { return UnderlyingType.GetProperties(filter, options); } } public class TupleTypeReference : ITypeReference { /// /// Gets the types of the tuple elements. /// public ImmutableArray ElementTypes { get; } /// /// Gets the names of the tuple elements. /// public ImmutableArray ElementNames { get; } public IModuleReference ValueTupleAssembly { get; } public TupleTypeReference(ImmutableArray elementTypes) { this.ElementTypes = elementTypes; } public TupleTypeReference(ImmutableArray elementTypes, ImmutableArray elementNames = default(ImmutableArray), IModuleReference valueTupleAssembly = null) { this.ValueTupleAssembly = valueTupleAssembly; this.ElementTypes = elementTypes; this.ElementNames = elementNames; } public IType Resolve(ITypeResolveContext context) { return new TupleType(context.Compilation, ElementTypes.Select(t => t.Resolve(context)).ToImmutableArray(), ElementNames, ValueTupleAssembly?.Resolve(context) ); } } public static class TupleTypeExtensions { public static IType TupleUnderlyingTypeOrSelf(this IType type) { var t = (type as TupleType)?.UnderlyingType ?? type; return t.WithoutNullability(); } } }