From 450fae49e3e8813bff80266cbd0e6c61cc06256e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 12 Jul 2020 01:28:28 +0200 Subject: [PATCH] Introduce nint/nuint types and their conversions in the type system --- .../Semantics/ConversionTests.cs | 129 ++++++++++++++++++ .../CSharp/Resolver/CSharpConversions.cs | 50 ++++++- .../TypeSystem/NormalizeTypeVisitor.cs | 21 ++- .../TypeSystem/ReflectionHelper.cs | 16 ++- .../TypeSystem/SpecialType.cs | 12 +- ICSharpCode.Decompiler/TypeSystem/TypeKind.cs | 9 ++ 6 files changed, 222 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs index e278c01d3..9c7a43a4b 100644 --- a/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs +++ b/ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs @@ -32,6 +32,8 @@ namespace ICSharpCode.Decompiler.Tests.Semantics // assign short names to the fake reflection types using Null = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.Null; using dynamic = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.Dynamic; + using nuint = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.NUInt; + using nint = ICSharpCode.Decompiler.TypeSystem.ReflectionHelper.NInt; using C = Conversion; [TestFixture, Parallelizable(ParallelScope.All)] @@ -56,6 +58,13 @@ namespace ICSharpCode.Decompiler.Tests.Semantics return conversions.ImplicitConversion(from2, to2); } + Conversion ExplicitConversion(Type from, Type to) + { + IType from2 = compilation.FindType(from); + IType to2 = compilation.FindType(to); + return conversions.ExplicitConversion(from2, to2); + } + [Test] public void IdentityConversions() { @@ -285,6 +294,126 @@ namespace ICSharpCode.Decompiler.Tests.Semantics Assert.AreEqual(C.None, ImplicitConversion(typeof(int*), typeof(dynamic))); } + [Test] + public void ConversionToNInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.UnboxingConversion, ExplicitConversion(typeof(object), typeof(nint))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(void*), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(sbyte), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(byte), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(short), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(ushort), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(int), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(uint), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(long), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(ulong), typeof(nint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(char), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(float), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(double), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(decimal), typeof(nint))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(IntPtr), typeof(nint))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(UIntPtr), typeof(nint))); + } + + [Test] + public void ConversionToNUInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.UnboxingConversion, ExplicitConversion(typeof(object), typeof(nuint))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(void*), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(sbyte), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(byte), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(short), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(ushort), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(int), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(uint), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(long), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(ulong), typeof(nuint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(char), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(float), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(double), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(decimal), typeof(nuint))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(IntPtr), typeof(nuint))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(UIntPtr), typeof(nuint))); + } + + [Test] + public void ConversionFromNInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.BoxingConversion, ExplicitConversion(typeof(nint), typeof(object))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(nint), typeof(void*))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(nuint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(sbyte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(byte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(short))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(ushort))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(int))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(uint))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(long))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(ulong))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(char))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(float))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(double))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nint), typeof(decimal))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(nint), typeof(IntPtr))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(nint), typeof(UIntPtr))); + } + + [Test] + public void ConversionFromNUInt() + { + // Test based on the table in https://github.com/dotnet/csharplang/blob/master/proposals/native-integers.md + Assert.AreEqual(C.BoxingConversion, ExplicitConversion(typeof(nuint), typeof(object))); + Assert.AreEqual(C.ExplicitPointerConversion, ExplicitConversion(typeof(nuint), typeof(void*))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(nint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(sbyte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(byte))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(short))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(ushort))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(int))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(uint))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(long))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(ulong))); + Assert.AreEqual(C.ExplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(char))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(float))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(double))); + Assert.AreEqual(C.ImplicitNumericConversion, ExplicitConversion(typeof(nuint), typeof(decimal))); + Assert.AreEqual(C.None, ExplicitConversion(typeof(nuint), typeof(IntPtr))); + Assert.AreEqual(C.IdentityConversion, ExplicitConversion(typeof(nuint), typeof(UIntPtr))); + } + + + [Test] + public void NIntEnumConversion() + { + var explicitEnumConversion = C.EnumerationConversion(isImplicit: false, isLifted: false); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(nint), typeof(StringComparison))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(nuint), typeof(StringComparison))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(StringComparison), typeof(nint))); + Assert.AreEqual(explicitEnumConversion, ExplicitConversion(typeof(StringComparison), typeof(nuint))); + } + + [Test] + public void IntegerLiteralToNIntConversions() + { + Assert.IsTrue(IntegerLiteralConversion(0, typeof(nint))); + Assert.IsTrue(IntegerLiteralConversion(-1, typeof(nint))); + Assert.IsFalse(IntegerLiteralConversion(uint.MaxValue, typeof(nint))); + Assert.IsFalse(IntegerLiteralConversion(long.MaxValue, typeof(nint))); + } + + + [Test] + public void IntegerLiteralToNUIntConversions() + { + Assert.IsTrue(IntegerLiteralConversion(0, typeof(nuint))); + Assert.IsFalse(IntegerLiteralConversion(-1, typeof(nuint))); + Assert.IsTrue(IntegerLiteralConversion(uint.MaxValue, typeof(nuint))); + Assert.IsFalse(IntegerLiteralConversion(long.MaxValue, typeof(nuint))); + } + [Test] public void UnconstrainedTypeParameter() { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 8701e1dd9..eae329895 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -326,10 +326,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /* char */ { false, true , true , true , true , true }, /* sbyte */ { true , false, true , false, true , false }, /* byte */ { true , true , true , true , true , true }, - /* short */ { false, false, true , false, true , false }, - /* ushort */ { false, false, true , true , true , true }, - /* int */ { false, false, false, false, true , false }, - /* uint */ { false, false, false, false, true , true }, + /* short */ { true , false, true , false, true , false }, + /* ushort */ { false, true , true , true , true , true }, + /* int */ { false, false, true , false, true , false }, + /* uint */ { false, false, false, true , true , true }, + /* long */ { false, false, false, false, true , false }, + /* ulong */ { false, false, false, false, false, true }, }; bool ImplicitNumericConversion(IType fromType, IType toType) @@ -337,7 +339,29 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // C# 4.0 spec: §6.1.2 TypeCode from = ReflectionHelper.GetTypeCode(fromType); + if (from == TypeCode.Empty) { + // When converting from a native-sized integer, treat it as 64-bits + switch (fromType.Kind) { + case TypeKind.NInt: + from = TypeCode.Int64; + break; + case TypeKind.NUInt: + from = TypeCode.UInt64; + break; + } + } TypeCode to = ReflectionHelper.GetTypeCode(toType); + if (to == TypeCode.Empty) { + // When converting to a native-sized integer, only 32-bits can be stored safely + switch (toType.Kind) { + case TypeKind.NInt: + to = TypeCode.Int32; + break; + case TypeKind.NUInt: + to = TypeCode.UInt32; + break; + } + } if (to >= TypeCode.Single && to <= TypeCode.Decimal) { // Conversions to float/double/decimal exist from all integral types, // and there's a conversion from float to double. @@ -345,7 +369,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver || from == TypeCode.Single && to == TypeCode.Double; } else { // Conversions to integral types: look at the table - return from >= TypeCode.Char && from <= TypeCode.UInt32 + return from >= TypeCode.Char && from <= TypeCode.UInt64 && to >= TypeCode.Int16 && to <= TypeCode.UInt64 && implicitNumericConversionLookup[from - TypeCode.Char, to - TypeCode.Int16]; } @@ -353,6 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool IsNumericType(IType type) { + switch (type.Kind) { + case TypeKind.NInt: + case TypeKind.NUInt: + return true; + } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.Char && c <= TypeCode.Decimal; } @@ -693,7 +722,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return false; // C# 4.0 spec: §6.1.9 TypeCode fromTypeCode = ReflectionHelper.GetTypeCode(rr.Type); - TypeCode toTypeCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(toType)); + toType = NullableType.GetUnderlyingType(toType); + TypeCode toTypeCode = ReflectionHelper.GetTypeCode(toType); + if (toType.Kind == TypeKind.NUInt) { + toTypeCode = TypeCode.UInt32; + } if (fromTypeCode == TypeCode.Int64) { long val = (long)rr.ConstantValue; return val >= 0 && toTypeCode == TypeCode.UInt64; @@ -772,6 +805,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool IsIntegerType(IType type) { + switch (type.Kind) { + case TypeKind.NInt: + case TypeKind.NUInt: + return true; + } TypeCode c = ReflectionHelper.GetTypeCode(type); return c >= TypeCode.SByte && c <= TypeCode.UInt64; } diff --git a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs index 0f8d8a2a4..d7bce85a2 100644 --- a/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs +++ b/ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs @@ -15,6 +15,7 @@ namespace ICSharpCode.Decompiler.TypeSystem ReplaceClassTypeParametersWithDummy = false, ReplaceMethodTypeParametersWithDummy = false, DynamicAndObject = true, + IntPtrToNInt = true, TupleToUnderlyingType = true, RemoveModOpt = true, RemoveModReq = true, @@ -33,6 +34,7 @@ namespace ICSharpCode.Decompiler.TypeSystem public bool ReplaceClassTypeParametersWithDummy = true; public bool ReplaceMethodTypeParametersWithDummy = true; public bool DynamicAndObject = true; + public bool IntPtrToNInt = true; public bool TupleToUnderlyingType = true; public bool RemoveNullability = true; @@ -51,13 +53,18 @@ namespace ICSharpCode.Decompiler.TypeSystem public override IType VisitTypeDefinition(ITypeDefinition type) { - if (DynamicAndObject && type.KnownTypeCode == KnownTypeCode.Object) { - // Instead of normalizing dynamic->object, - // we do this the opposite direction, so that we don't need a compilation to find the object type. - if (RemoveNullability) - return SpecialType.Dynamic; - else - return SpecialType.Dynamic.ChangeNullability(type.Nullability); + switch (type.KnownTypeCode) { + case KnownTypeCode.Object when DynamicAndObject: + // Instead of normalizing dynamic->object, + // we do this the opposite direction, so that we don't need a compilation to find the object type. + if (RemoveNullability) + return SpecialType.Dynamic; + else + return SpecialType.Dynamic.ChangeNullability(type.Nullability); + case KnownTypeCode.IntPtr when IntPtrToNInt: + return SpecialType.NInt; + case KnownTypeCode.UIntPtr when IntPtrToNInt: + return SpecialType.NUInt; } return base.VisitTypeDefinition(type); } diff --git a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs index 4eb6677ce..dda869426 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs @@ -36,7 +36,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// A reflection class used to represent dynamic. /// public sealed class Dynamic {} - + + /// + /// A reflection class used to represent nint. + /// + public sealed class NInt { } + + /// + /// A reflection class used to represent nuint. + /// + public sealed class NUInt { } + /// /// A reflection class used to represent an unbound type argument. /// @@ -101,6 +111,10 @@ namespace ICSharpCode.Decompiler.TypeSystem } else if (type.DeclaringType != null) { if (type == typeof(Dynamic)) return SpecialType.Dynamic; + else if (type == typeof(NInt)) + return SpecialType.NInt; + else if (type == typeof(NUInt)) + return SpecialType.NUInt; else if (type == typeof(Null)) return SpecialType.NullType; else if (type == typeof(UnboundTypeArgument)) diff --git a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs index 082376efa..1cd54683b 100644 --- a/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs +++ b/ICSharpCode.Decompiler/TypeSystem/SpecialType.cs @@ -46,7 +46,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Type representing the C# 'dynamic' type. /// public readonly static SpecialType Dynamic = new SpecialType(TypeKind.Dynamic, "dynamic", isReferenceType: true); - + + /// + /// Type representing the C# 9 'nint' type. + /// + public readonly static SpecialType NInt = new SpecialType(TypeKind.NInt, "nint", isReferenceType: false); + + /// + /// Type representing the C# 9 'nuint' type. + /// + public readonly static SpecialType NUInt = new SpecialType(TypeKind.NUInt, "nuint", isReferenceType: false); + /// /// Type representing the result of the C# '__arglist()' expression. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs index 5f107d6fa..8bc26d43c 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeKind.cs @@ -94,5 +94,14 @@ namespace ICSharpCode.Decompiler.TypeSystem /// Modified type, with required modifier. /// ModReq, + + /// + /// C# 9 nint + /// + NInt, + /// + /// C# 9 nuint + /// + NUInt, } }