mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
13 KiB
419 lines
13 KiB
// 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.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; |
|
} |
|
|
|
/// <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.Length > 0) |
|
{ |
|
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 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 class TupleTypeReference : ITypeReference |
|
{ |
|
/// <summary> |
|
/// Gets the types of the tuple elements. |
|
/// </summary> |
|
public ImmutableArray<ITypeReference> ElementTypes { get; } |
|
|
|
/// <summary> |
|
/// Gets the names of the tuple elements. |
|
/// </summary> |
|
public ImmutableArray<string> ElementNames { get; } |
|
|
|
public IModuleReference ValueTupleAssembly { get; } |
|
|
|
public TupleTypeReference(ImmutableArray<ITypeReference> elementTypes) |
|
{ |
|
this.ElementTypes = elementTypes; |
|
} |
|
|
|
public TupleTypeReference(ImmutableArray<ITypeReference> elementTypes, |
|
ImmutableArray<string> elementNames = default(ImmutableArray<string>), |
|
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(); |
|
} |
|
} |
|
}
|
|
|