diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index 4a6727a42..317b487f3 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -19,6 +19,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.Tests.TypeSystem; @@ -88,6 +89,20 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.AreEqual(C.None, ImplicitConversion(typeof(List[,]>), typeof(List[]>))); } + [Test] + public void TupleIdentityConversions() + { + var intType = compilation.FindType(typeof(int)); + var stringType = compilation.FindType(typeof(string)); + Assert.AreEqual(C.IdentityConversion, conversions.ImplicitConversion( + new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "b")), + new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "c")))); + + Assert.AreEqual(C.None, conversions.ImplicitConversion( + new TupleType(compilation, ImmutableArray.Create(intType, stringType), ImmutableArray.Create("a", "b")), + new TupleType(compilation, ImmutableArray.Create(stringType, intType), ImmutableArray.Create("a", "b")))); + } + [Test] public void PrimitiveConversions() { diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 0613a0030..4b9263b7c 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -24,6 +24,7 @@ using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; using Attribute = ICSharpCode.Decompiler.CSharp.Syntax.Attribute; namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1061,7 +1062,17 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor throwExpression.Expression.AcceptVisitor(this); EndNode(throwExpression); } - + + public virtual void VisitTupleExpression(TupleExpression tupleExpression) + { + Debug.Assert(tupleExpression.Elements.Count >= 2); + StartNode(tupleExpression); + LPar(); + WriteCommaSeparatedList(tupleExpression.Elements); + RPar(); + EndNode(tupleExpression); + } + public virtual void VisitTypeOfExpression(TypeOfExpression typeOfExpression) { StartNode(typeOfExpression); @@ -2269,7 +2280,31 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor WriteTypeArguments(memberType.TypeArguments); EndNode(memberType); } - + + public virtual void VisitTupleType(TupleAstType tupleType) + { + Debug.Assert(tupleType.ElementTypes.Count >= 2); + StartNode(tupleType); + LPar(); + if (tupleType.ElementNames.Any()) { + bool isFirst = true; + foreach (var (type, name) in tupleType.ElementTypes.Zip(tupleType.ElementNames)) { + if (isFirst) { + isFirst = false; + } else { + Comma(type); + } + type.AcceptVisitor(this); + Space(); + name.AcceptVisitor(this); + } + } else { + WriteCommaSeparatedList(tupleType.ElementTypes); + } + RPar(); + EndNode(tupleType); + } + public virtual void VisitComposedType(ComposedType composedType) { StartNode(composedType); diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 35cbdc6c1..4bca855ee 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -310,6 +310,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver else return base.VisitOtherType(type); } + + public override IType VisitTupleType(TupleType type) + { + return type.UnderlyingType; + } } #endregion diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index 304713e9b..a21abb861 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -1055,8 +1055,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver : base((IMethod)nonLiftedMethod.MemberDefinition, nonLiftedMethod.Substitution) { this.nonLiftedOperator = nonLiftedMethod; - var substitution = new MakeNullableVisitor(nonLiftedMethod.Compilation, nonLiftedMethod.Substitution); - this.Parameters = base.CreateParameters(substitution); + var compilation = nonLiftedMethod.Compilation; + var substitution = nonLiftedMethod.Substitution; + this.Parameters = base.CreateParameters( + type => NullableType.Create(compilation, type.AcceptVisitor(substitution))); // Comparison operators keep the 'bool' return type even when lifted. if (IsComparisonOperator(nonLiftedMethod)) this.ReturnType = nonLiftedMethod.ReturnType; @@ -1078,38 +1080,6 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return nonLiftedOperator.GetHashCode() ^ 0x7191254; } } - - sealed class MakeNullableVisitor : TypeVisitor - { - readonly ICompilation compilation; - readonly TypeParameterSubstitution typeParameterSubstitution; - - public MakeNullableVisitor(ICompilation compilation, TypeParameterSubstitution typeParameterSubstitution) - { - this.compilation = compilation; - this.typeParameterSubstitution = typeParameterSubstitution; - } - - public override IType VisitTypeDefinition(ITypeDefinition type) - { - return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); - } - - public override IType VisitTypeParameter(ITypeParameter type) - { - return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); - } - - public override IType VisitParameterizedType(ParameterizedType type) - { - return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); - } - - public override IType VisitOtherType(IType type) - { - return NullableType.Create(compilation, type.AcceptVisitor(typeParameterSubstitution)); - } - } #endregion } diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs index e3f1dcac2..32a35723b 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs @@ -593,8 +593,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return; } // Handle parameterized type: - ParameterizedType pU = U as ParameterizedType; - ParameterizedType pV = V as ParameterizedType; + ParameterizedType pU = U.TupleUnderlyingTypeOrSelf() as ParameterizedType; + ParameterizedType pV = V.TupleUnderlyingTypeOrSelf() as ParameterizedType; if (pU != null && pV != null && object.Equals(pU.GenericType, pV.GenericType) && pU.TypeParameterCount == pV.TypeParameterCount) @@ -644,7 +644,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // Handle array types: ArrayType arrU = U as ArrayType; ArrayType arrV = V as ArrayType; - ParameterizedType pV = V as ParameterizedType; + ParameterizedType pV = V.TupleUnderlyingTypeOrSelf() as ParameterizedType; if (arrU != null && arrV != null && arrU.Dimensions == arrV.Dimensions) { MakeLowerBoundInference(arrU.ElementType, arrV.ElementType); return; @@ -656,7 +656,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (pV != null) { ParameterizedType uniqueBaseType = null; foreach (IType baseU in U.GetAllBaseTypes()) { - ParameterizedType pU = baseU as ParameterizedType; + ParameterizedType pU = baseU.TupleUnderlyingTypeOrSelf() as ParameterizedType; if (pU != null && object.Equals(pU.GenericType, pV.GenericType) && pU.TypeParameterCount == pV.TypeParameterCount) { if (uniqueBaseType == null) uniqueBaseType = pU; @@ -730,7 +730,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // Handle array types: ArrayType arrU = U as ArrayType; ArrayType arrV = V as ArrayType; - ParameterizedType pU = U as ParameterizedType; + ParameterizedType pU = U.TupleUnderlyingTypeOrSelf() as ParameterizedType; if (arrV != null && arrU != null && arrU.Dimensions == arrV.Dimensions) { MakeUpperBoundInference(arrU.ElementType, arrV.ElementType); return; @@ -742,7 +742,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (pU != null) { ParameterizedType uniqueBaseType = null; foreach (IType baseV in V.GetAllBaseTypes()) { - ParameterizedType pV = baseV as ParameterizedType; + ParameterizedType pV = baseV.TupleUnderlyingTypeOrSelf() as ParameterizedType; if (pV != null && object.Equals(pU.GenericType, pV.GenericType) && pU.TypeParameterCount == pV.TypeParameterCount) { if (uniqueBaseType == null) uniqueBaseType = pV; diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs index 043105b6f..a281563c2 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs @@ -115,7 +115,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { VisitChildren (memberType); } - + + public virtual void VisitTupleType(TupleAstType tupleType) + { + VisitChildren(tupleType); + } + public virtual void VisitAttribute (Attribute attribute) { VisitChildren (attribute); @@ -536,6 +541,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax VisitChildren (throwExpression); } + public virtual void VisitTupleExpression(TupleExpression tupleExpression) + { + VisitChildren (tupleExpression); + } + public virtual void VisitTypeOfExpression (TypeOfExpression typeOfExpression) { VisitChildren (typeOfExpression); @@ -747,6 +757,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (memberType); } + + public virtual T VisitTupleType(TupleAstType tupleType) + { + return VisitChildren (tupleType); + } public virtual T VisitAttribute (Attribute attribute) { @@ -1168,6 +1183,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (throwExpression); } + public virtual T VisitTupleExpression (TupleExpression tupleExpression) + { + return VisitChildren (tupleExpression); + } + public virtual T VisitTypeOfExpression (TypeOfExpression typeOfExpression) { return VisitChildren (typeOfExpression); @@ -1379,7 +1399,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { return VisitChildren (memberType, data); } - + + public virtual S VisitTupleType(TupleAstType tupleType, T data) + { + return VisitChildren (tupleType, data); + } + public virtual S VisitAttribute (Attribute attribute, T data) { return VisitChildren (attribute, data); @@ -1800,6 +1825,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return VisitChildren (throwExpression, data); } + public virtual S VisitTupleExpression (TupleExpression tupleExpression, T data) + { + return VisitChildren (tupleExpression, data); + } + public virtual S VisitTypeOfExpression (TypeOfExpression typeOfExpression, T data) { return VisitChildren (typeOfExpression, data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/TupleExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/TupleExpression.cs new file mode 100644 index 000000000..d6f75b93a --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/TupleExpression.cs @@ -0,0 +1,51 @@ +// 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 ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + public class TupleExpression : Expression + { + public AstNodeCollection Elements { + get { return GetChildrenByRole(Roles.Expression); } + } + + public override void AcceptVisitor(IAstVisitor visitor) + { + visitor.VisitTupleExpression(this); + } + + public override T AcceptVisitor(IAstVisitor visitor) + { + return visitor.VisitTupleExpression(this); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return visitor.VisitTupleExpression(this, data); + } + + protected internal override bool DoMatch(AstNode other, Match match) + { + return other is TupleExpression tuple + && Elements.DoMatch(tuple.Elements, match); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs index 24b8857a6..514213e7d 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs @@ -56,6 +56,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitStackAllocExpression(StackAllocExpression stackAllocExpression); void VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression); void VisitThrowExpression(ThrowExpression throwExpression); + void VisitTupleExpression(TupleExpression tupleExpression); void VisitTypeOfExpression(TypeOfExpression typeOfExpression); void VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression); void VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression); @@ -133,6 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax void VisitSyntaxTree(SyntaxTree syntaxTree); void VisitSimpleType(SimpleType simpleType); void VisitMemberType(MemberType memberType); + void VisitTupleType(TupleAstType tupleType); void VisitComposedType(ComposedType composedType); void VisitArraySpecifier(ArraySpecifier arraySpecifier); void VisitPrimitiveType(PrimitiveType primitiveType); @@ -194,6 +196,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitStackAllocExpression(StackAllocExpression stackAllocExpression); S VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression); S VisitThrowExpression(ThrowExpression throwExpression); + S VisitTupleExpression(TupleExpression tupleExpression); S VisitTypeOfExpression(TypeOfExpression typeOfExpression); S VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression); S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression); @@ -271,6 +274,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitSyntaxTree(SyntaxTree syntaxTree); S VisitSimpleType(SimpleType simpleType); S VisitMemberType(MemberType memberType); + S VisitTupleType(TupleAstType tupleType); S VisitComposedType(ComposedType composedType); S VisitArraySpecifier(ArraySpecifier arraySpecifier); S VisitPrimitiveType(PrimitiveType primitiveType); @@ -332,6 +336,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitStackAllocExpression(StackAllocExpression stackAllocExpression, T data); S VisitThisReferenceExpression(ThisReferenceExpression thisReferenceExpression, T data); S VisitThrowExpression(ThrowExpression throwExpression, T data); + S VisitTupleExpression(TupleExpression tupleExpression, T data); S VisitTypeOfExpression(TypeOfExpression typeOfExpression, T data); S VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, T data); S VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, T data); @@ -409,6 +414,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax S VisitSyntaxTree(SyntaxTree syntaxTree, T data); S VisitSimpleType(SimpleType simpleType, T data); S VisitMemberType(MemberType memberType, T data); + S VisitTupleType(TupleAstType tupleType, T data); S VisitComposedType(ComposedType composedType, T data); S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data); S VisitPrimitiveType(PrimitiveType primitiveType, T data); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TupleAstType.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TupleAstType.cs new file mode 100644 index 000000000..b8e0d08b5 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TupleAstType.cs @@ -0,0 +1,63 @@ +// 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 ICSharpCode.Decompiler.CSharp.Resolver; +using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.CSharp.Syntax +{ + public class TupleAstType : AstType + { + public AstNodeCollection ElementTypes { + get { return GetChildrenByRole(Roles.TypeArgument); } + } + + public AstNodeCollection ElementNames { + get { return GetChildrenByRole(Roles.Identifier); } + } + + public override void AcceptVisitor(IAstVisitor visitor) + { + visitor.VisitTupleType(this); + } + + public override T AcceptVisitor(IAstVisitor visitor) + { + return visitor.VisitTupleType(this); + } + + public override S AcceptVisitor(IAstVisitor visitor, T data) + { + return visitor.VisitTupleType(this, data); + } + + public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null) + { + throw new NotSupportedException(); + } + + protected internal override bool DoMatch(AstNode other, Match match) + { + return other is TupleAstType o + && ElementTypes.DoMatch(o.ElementTypes, match) + && ElementNames.DoMatch(o.ElementNames, match); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 49c6372cf..9f51a9f07 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -231,15 +231,25 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ConvertType(typeWithElementType.ElementType); } } - ParameterizedType pt = type as ParameterizedType; - if (pt != null) { + if (type is ParameterizedType pt) { if (pt.IsKnownType(KnownTypeCode.NullableOfT)) { return ConvertType(pt.TypeArguments[0]).MakeNullableType(); } return ConvertTypeHelper(pt.GenericType, pt.TypeArguments); } - ITypeDefinition typeDef = type as ITypeDefinition; - if (typeDef != null) { + if (type is TupleType tuple) { + var astType = new TupleAstType(); + if (tuple.HasCustomElementNames) { + foreach (var (etype, ename) in tuple.TupleElementTypes.Zip(tuple.TupleElementNames)) { + astType.ElementTypes.Add(ConvertType(etype)); + astType.ElementNames.Add(Identifier.Create(ename)); + } + } else { + astType.ElementTypes.AddRange(tuple.TupleElementTypes.Select(ConvertType)); + } + return astType; + } + if (type is ITypeDefinition typeDef) { if (typeDef.TypeParameterCount > 0) { // Unbound type IType[] typeArguments = new IType[typeDef.TypeParameterCount]; diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index ba040fdaa..6e16fc3ae 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -107,6 +107,7 @@ + @@ -178,6 +179,7 @@ + @@ -323,6 +325,7 @@ + diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs index 86ed53812..523446cc0 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SpecializedMember.cs @@ -303,7 +303,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation if (result != null) return result; else - return LazyInit.GetOrSet(ref this.parameters, CreateParameters(this.Substitution)); + return LazyInit.GetOrSet(ref this.parameters, CreateParameters(t => t.AcceptVisitor(this.Substitution))); } protected set { // This setter is used for LiftedUserDefinedOperator, a special case of specialized member @@ -315,7 +315,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation } } - protected IParameter[] CreateParameters(TypeVisitor substitution) + protected IParameter[] CreateParameters(Func substitution) { var paramDefs = ((IParameterizedMember)this.baseMember).Parameters; if (paramDefs.Count == 0) { @@ -324,7 +324,7 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation var parameters = new IParameter[paramDefs.Count]; for (int i = 0; i < parameters.Length; i++) { var p = paramDefs[i]; - IType newType = p.Type.AcceptVisitor(substitution); + IType newType = substitution(p.Type); parameters[i] = new DefaultParameter( newType, p.Name, this, p.Attributes, p.IsRef, p.IsOut, diff --git a/ICSharpCode.Decompiler/TypeSystem/ParameterListComparer.cs b/ICSharpCode.Decompiler/TypeSystem/ParameterListComparer.cs index 3ceee3306..ef7947113 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ParameterListComparer.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ParameterListComparer.cs @@ -52,6 +52,11 @@ namespace ICSharpCode.Decompiler.TypeSystem return SpecialType.Dynamic; return base.VisitTypeDefinition(type); } + + public override IType VisitTupleType(TupleType type) + { + return type.UnderlyingType.AcceptVisitor(this); + } } static readonly NormalizeTypeVisitor normalizationVisitor = new NormalizeTypeVisitor(); diff --git a/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs b/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs index edae53397..675a30c9e 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ParameterizedType.cs @@ -271,6 +271,8 @@ namespace ICSharpCode.Decompiler.TypeSystem public bool Equals(IType other) { + if (this == other) + return true; ParameterizedType c = other as ParameterizedType; if (c == null || !genericType.Equals(c.genericType) || typeArguments.Length != c.typeArguments.Length) return false; diff --git a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs index 80f343b4f..fa423cfb8 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TupleType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TupleType.cs @@ -1,17 +1,278 @@ -using System; +// 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.Text; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.TypeSystem { - public sealed class TupleType + 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; } - - public ImmutableArray TupleElements { get; } + + /// + /// Gets the tuple elements. + /// + public ImmutableArray TupleElementTypes { get; } + + /// + /// Gets the names of the tuple elements. + /// + public ImmutableArray TupleElementNames { get; } + + public bool HasCustomElementNames { get; } + + public TupleType(ICompilation compilation, ImmutableArray elementTypes) + { + this.Compilation = compilation; + this.UnderlyingType = CreateUnderlyingType(compilation, elementTypes); + this.TupleElementTypes = elementTypes; + this.TupleElementNames = Enumerable.Range(1, elementTypes.Length).Select(p => "Item" + p).ToImmutableArray(); + this.HasCustomElementNames = false; + } + + public TupleType(ICompilation compilation, ImmutableArray elementTypes, ImmutableArray elementNames) + { + this.Compilation = compilation; + this.UnderlyingType = CreateUnderlyingType(compilation, elementTypes); + this.TupleElementTypes = elementTypes; + this.TupleElementNames = elementNames; + this.HasCustomElementNames = true; + } + + static ParameterizedType CreateUnderlyingType(ICompilation compilation, ImmutableArray elementTypes) + { + int remainder = (elementTypes.Length - 1) % (RestPosition - 1) + 1; + Debug.Assert(remainder >= 1 && remainder < RestPosition); + int pos = elementTypes.Length - remainder; + var type = new ParameterizedType( + compilation.FindType(new TopLevelTypeName("System", "ValueTuple", remainder)), + elementTypes.Slice(pos)); + while (pos > 0) { + pos -= (RestPosition - 1); + type = new ParameterizedType( + compilation.FindType(new TopLevelTypeName("System", "ValueTuple", RestPosition)), + elementTypes.Slice(pos, RestPosition - 1).Concat(new[] { type })); + } + Debug.Assert(pos == 0); + return type; + } + + /// + /// Gets whether the specified type is a valid underlying type for a tuple. + /// Also returns type for tuple types themselves. + /// + public static bool IsTupleCompatible(IType type, out int tupleCardinality) + { + switch (type.Kind) { + case TypeKind.Tuple: + tupleCardinality = ((TupleType)type).TupleElementNames.Length; + return true; + case TypeKind.Class: + case TypeKind.Struct: + if (type.Namespace == "System" && type.Name == "ValueType") { + 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; + } + + static bool CollectTupleElementTypes(IType type, List output) + { + switch (type.Kind) { + case TypeKind.Tuple: + output.AddRange(((TupleType)type).TupleElementTypes); + return true; + case TypeKind.Class: + case TypeKind.Struct: + if (type.Namespace == "System" && type.Name == "ValueType") { + 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 CollectTupleElementTypes(type.TypeArguments[RestIndex], output); + } + } + 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) + && TupleElementNames.SequenceEqual(o.TupleElementNames); + } + + public override int GetHashCode() + { + unchecked { + int hash = UnderlyingType.GetHashCode(); + foreach (string name in TupleElementNames) { + hash *= 31; + hash += name.GetHashCode(); + } + return hash; + } + } + + public override IType AcceptVisitor(TypeVisitor visitor) + { + return visitor.VisitTupleType(this); + } + + public override IType VisitChildren(TypeVisitor visitor) + { + IType[] newElementTypes = null; + for (int i = 0; i < TupleElementTypes.Length; i++) { + IType type = TupleElementTypes[i]; + var newType = type.AcceptVisitor(visitor); + if (newType != type) { + if (newElementTypes == null) { + newElementTypes = TupleElementTypes.ToArray(); + } + newElementTypes[i] = newType; + } + } + if (newElementTypes != null) { + return new TupleType(this.Compilation, newElementTypes.ToImmutableArray(), this.TupleElementNames); + } 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 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 <= TupleElementTypes.Length; i++) { + var type = TupleElementTypes[i]; + var name = TupleElementNames[i]; + int pos = i + 1; + string itemName = "Item" + pos; + if (name != itemName) + yield return MakeField(type, name); + if (pos >= RestPosition) + yield return MakeField(type, itemName); + } + } + + private IField MakeField(IType type, string name) + { + throw new NotImplementedException(); + } + + 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 static class TupleTypeExtensions + { + public static IType TupleUnderlyingTypeOrSelf(this IType type) + { + return (type as TupleType)?.UnderlyingType ?? type; + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs index b7796d129..2dd306c50 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs @@ -81,5 +81,11 @@ namespace ICSharpCode.Decompiler.TypeSystem Intersection, /// ArgList, + /// A C# 7 tuple type. + /// E.g. (string, int) + /// Note: System.ValueTuple<string, int> is not considered a tuple type. + /// + /// + Tuple, } } diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs index eae8aceed..152d397e0 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeVisitor.cs @@ -52,7 +52,12 @@ namespace ICSharpCode.Decompiler.TypeSystem { return type.VisitChildren(this); } - + + public virtual IType VisitTupleType(TupleType type) + { + return type.VisitChildren(this); + } + public virtual IType VisitOtherType(IType type) { return type.VisitChildren(this); diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 911e4561a..7fc86acd0 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -12,6 +12,26 @@ namespace ICSharpCode.Decompiler.Util value = pair.Value; } + public static IEnumerable<(A, B)> Zip(this IEnumerable input1, IEnumerable input2) + { + return input1.Zip(input2, (a, b) => (a, b)); + } + + public static IEnumerable Slice(this IReadOnlyList input, int offset, int length) + { + for (int i = offset; i < offset + length; i++) { + yield return input[i]; + } + } + + public static IEnumerable Slice(this IReadOnlyList input, int offset) + { + int length = input.Count; + for (int i = offset; i < length; i++) { + yield return input[i]; + } + } + public static HashSet ToHashSet(this IEnumerable input) { return new HashSet(input);