Browse Source

Add support for arithmetic using C# 9 native integer types.

pull/2063/head
Daniel Grunwald 6 years ago
parent
commit
3a4db502bc
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs
  2. 25
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs
  3. 104
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  4. 69
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  5. 21
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  6. 2
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  7. 16
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

4
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs

@ -186,7 +186,7 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void* Add<T>(void* source, int elementOffset) public unsafe static void* Add<T>(void* source, int elementOffset)
{ {
return (byte*)source + (long)elementOffset * (long)sizeof(T); return (byte*)source + (nint)elementOffset * (nint)sizeof(T);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -210,7 +210,7 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void* Subtract<T>(void* source, int elementOffset) public unsafe static void* Subtract<T>(void* source, int elementOffset)
{ {
return (byte*)source - (long)elementOffset * (long)sizeof(T); return (byte*)source - (nint)elementOffset * (nint)sizeof(T);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]

25
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs

@ -73,5 +73,30 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
u64 = (uint)i; u64 = (uint)i;
i = (nint)u64; i = (nint)u64;
} }
public void Arithmetic()
{
Console.WriteLine((nint)intptr * 2);
Console.WriteLine(i * 2);
Console.WriteLine(i + (nint)u);
Console.WriteLine((nuint)i + u);
}
public object[] Boxing()
{
return new object[10] {
1,
(nint)2,
3L,
4u,
(nuint)5u,
6uL,
int.MaxValue,
(nint)int.MaxValue,
i64,
(nint)i64
};
}
} }
} }

104
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -109,15 +109,10 @@ namespace ICSharpCode.Decompiler.CSharp
if (!allowImplicitConversion) { if (!allowImplicitConversion) {
if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) { if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
expr = new CastExpression(ConvertType(rr.Type), expr); expr = new CastExpression(ConvertType(rr.Type), expr);
} else { } else if (rr.Type.IsCSharpSmallIntegerType()) {
switch (rr.Type.GetDefinition()?.KnownTypeCode) {
case KnownTypeCode.SByte:
case KnownTypeCode.Byte:
case KnownTypeCode.Int16:
case KnownTypeCode.UInt16:
expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr); expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr);
break; } else if (rr.Type.IsCSharpNativeIntegerType()) {
} expr = new CastExpression(new PrimitiveType(rr.Type.Name), expr);
} }
} }
var exprRR = expr.Annotation<ResolveResult>(); var exprRR = expr.Annotation<ResolveResult>();
@ -900,24 +895,14 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
if (inst.InputType.IsIntegerType()) {
// Ensure the inputs have the correct sign: // Ensure the inputs have the correct sign:
KnownTypeCode inputType = KnownTypeCode.None; IType inputType = FindArithmeticType(inst.InputType, inst.Sign);
switch (inst.InputType) {
case StackType.I: // In order to generate valid C# we need to treat (U)IntPtr as (U)Int64 in comparisons.
case StackType.I8:
inputType = inst.Sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64;
break;
case StackType.I4:
inputType = inst.Sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32;
break;
}
if (inputType != KnownTypeCode.None) {
IType targetType = compilation.FindType(inputType);
if (inst.IsLifted) { if (inst.IsLifted) {
targetType = NullableType.Create(compilation, targetType); inputType = NullableType.Create(compilation, inputType);
} }
left = left.ConvertTo(targetType, this); left = left.ConvertTo(inputType, this);
right = right.ConvertTo(targetType, this); right = right.ConvertTo(inputType, this);
} }
return new BinaryOperatorExpression(left.Expression, op, right.Expression) return new BinaryOperatorExpression(left.Expression, op, right.Expression)
.WithILInstruction(inst) .WithILInstruction(inst)
@ -993,22 +978,22 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
switch (inst.Operator) { switch (inst.Operator) {
case BinaryNumericOperator.Add: case BinaryNumericOperator.Add:
return HandleBinaryNumeric(inst, BinaryOperatorType.Add); return HandleBinaryNumeric(inst, BinaryOperatorType.Add, context);
case BinaryNumericOperator.Sub: case BinaryNumericOperator.Sub:
return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract); return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract, context);
case BinaryNumericOperator.Mul: case BinaryNumericOperator.Mul:
return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply); return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply, context);
case BinaryNumericOperator.Div: case BinaryNumericOperator.Div:
return HandlePointerSubtraction(inst) return HandlePointerSubtraction(inst)
?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide); ?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide, context);
case BinaryNumericOperator.Rem: case BinaryNumericOperator.Rem:
return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus); return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus, context);
case BinaryNumericOperator.BitAnd: case BinaryNumericOperator.BitAnd:
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd); return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd, context);
case BinaryNumericOperator.BitOr: case BinaryNumericOperator.BitOr:
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr); return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr, context);
case BinaryNumericOperator.BitXor: case BinaryNumericOperator.BitXor:
return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr); return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr, context);
case BinaryNumericOperator.ShiftLeft: case BinaryNumericOperator.ShiftLeft:
return HandleShift(inst, BinaryOperatorType.ShiftLeft); return HandleShift(inst, BinaryOperatorType.ShiftLeft);
case BinaryNumericOperator.ShiftRight: case BinaryNumericOperator.ShiftRight:
@ -1158,7 +1143,7 @@ namespace ICSharpCode.Decompiler.CSharp
TranslatedExpression EnsureIntegerType(TranslatedExpression expr) TranslatedExpression EnsureIntegerType(TranslatedExpression expr)
{ {
if (!expr.Type.IsCSharpPrimitiveIntegerType()) { if (!expr.Type.IsCSharpPrimitiveIntegerType() && !expr.Type.IsCSharpNativeIntegerType()) {
// pointer arithmetic accepts all primitive integer types, but no enums etc. // pointer arithmetic accepts all primitive integer types, but no enums etc.
StackType targetType = expr.Type.GetStackType() == StackType.I4 ? StackType.I4 : StackType.I8; StackType targetType = expr.Type.GetStackType() == StackType.I4 ? StackType.I4 : StackType.I8;
expr = expr.ConvertTo( expr = expr.ConvertTo(
@ -1254,7 +1239,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOperatorType op) TranslatedExpression HandleBinaryNumeric(BinaryNumericInstruction inst, BinaryOperatorType op, TranslationContext context)
{ {
var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow); var resolverWithOverflowCheck = resolver.WithCheckForOverflow(inst.CheckForOverflow);
var left = Translate(inst.Left); var left = Translate(inst.Left);
@ -1312,8 +1297,12 @@ namespace ICSharpCode.Decompiler.CSharp
|| !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign)) || !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign))
{ {
// Left and right operands are incompatible, so convert them to a common type // Left and right operands are incompatible, so convert them to a common type
StackType targetStackType = inst.UnderlyingResultType == StackType.I ? StackType.I8 : inst.UnderlyingResultType; Sign sign = inst.Sign;
IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign)); if (sign == Sign.None) {
// If the sign doesn't matter, try to use the same sign as expected by the context
sign = context.TypeHint.GetSign();
}
IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign);
left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this); left = left.ConvertTo(NullableType.IsNullable(left.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this); right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult); rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult);
@ -1327,6 +1316,28 @@ namespace ICSharpCode.Decompiler.CSharp
return resultExpr; return resultExpr;
} }
IType FindType(StackType stackType, Sign sign)
{
if (stackType == StackType.I && settings.NativeIntegers) {
return sign == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt;
} else {
return compilation.FindType(stackType.ToKnownTypeCode(sign));
}
}
IType FindArithmeticType(StackType stackType, Sign sign)
{
if (stackType == StackType.I) {
if (settings.NativeIntegers) {
return sign == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt;
} else {
// If native integers are not available, use 64-bit arithmetic instead
stackType = StackType.I8;
}
}
return compilation.FindType(stackType.ToKnownTypeCode(sign));
}
/// <summary> /// <summary>
/// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments. /// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments.
/// </summary> /// </summary>
@ -1339,17 +1350,17 @@ namespace ICSharpCode.Decompiler.CSharp
if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) { if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) {
// If the argument is oversized (needs truncation to match stack size of its ILInstruction), // If the argument is oversized (needs truncation to match stack size of its ILInstruction),
// perform the truncation now. // perform the truncation now.
IType targetType = compilation.FindType(argStackType.ToKnownTypeCode(sign)); IType targetType = FindType(argStackType, sign);
argUType = targetType; argUType = targetType;
if (isLifted) if (isLifted)
targetType = NullableType.Create(compilation, targetType); targetType = NullableType.Create(compilation, targetType);
arg = arg.ConvertTo(targetType, this); arg = arg.ConvertTo(targetType, this);
} }
if (argUType.GetStackType() == StackType.I) { if (argUType.IsKnownType(KnownTypeCode.IntPtr) || argUType.IsKnownType(KnownTypeCode.UIntPtr)) {
// None of the operators we might want to apply are supported by IntPtr/UIntPtr. // None of the operators we might want to apply are supported by IntPtr/UIntPtr.
// Also, pointer arithmetic has different semantics (works in number of elements, not bytes). // Also, pointer arithmetic has different semantics (works in number of elements, not bytes).
// So any inputs of size StackType.I must be converted to long/ulong. // So any inputs of size StackType.I must be converted to long/ulong.
IType targetType = compilation.FindType(StackType.I8.ToKnownTypeCode(sign)); IType targetType = FindArithmeticType(StackType.I, sign);
if (isLifted) if (isLifted)
targetType = NullableType.Create(compilation, targetType); targetType = NullableType.Create(compilation, targetType);
arg = arg.ConvertTo(targetType, this); arg = arg.ConvertTo(targetType, this);
@ -1631,7 +1642,11 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitConv(Conv inst, TranslationContext context) protected internal override TranslatedExpression VisitConv(Conv inst, TranslationContext context)
{ {
var arg = Translate(inst.Argument); Sign hintSign = inst.InputSign;
if (hintSign == Sign.None) {
hintSign = context.TypeHint.GetSign();
}
var arg = Translate(inst.Argument, typeHint: FindArithmeticType(inst.InputType, hintSign));
IType inputType = NullableType.GetUnderlyingType(arg.Type); IType inputType = NullableType.GetUnderlyingType(arg.Type);
StackType inputStackType = inst.InputType; StackType inputStackType = inst.InputType;
// Note: we're dealing with two conversions here: // Note: we're dealing with two conversions here:
@ -2344,8 +2359,17 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitBox(Box inst, TranslationContext context) protected internal override TranslatedExpression VisitBox(Box inst, TranslationContext context)
{ {
IType targetType = inst.Type;
var arg = Translate(inst.Argument, typeHint: targetType);
if (settings.NativeIntegers && !arg.Type.Equals(targetType)) {
if (targetType.IsKnownType(KnownTypeCode.IntPtr)) {
targetType = SpecialType.NInt;
} else if (targetType.IsKnownType(KnownTypeCode.UIntPtr)) {
targetType = SpecialType.NUInt;
}
}
arg = arg.ConvertTo(targetType, this);
var obj = compilation.FindType(KnownTypeCode.Object); var obj = compilation.FindType(KnownTypeCode.Object);
var arg = Translate(inst.Argument, typeHint: inst.Type).ConvertTo(inst.Type, this);
return new CastExpression(ConvertType(obj), arg.Expression) return new CastExpression(ConvertType(obj), arg.Expression)
.WithILInstruction(inst) .WithILInstruction(inst)
.WithRR(new ConversionResolveResult(obj, arg.ResolveResult, Conversion.BoxingConversion)); .WithRR(new ConversionResolveResult(obj, arg.ResolveResult, Conversion.BoxingConversion));

69
ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs

@ -774,6 +774,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs); return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs);
} else if (lhsType is PointerType && rhsType is PointerType) { } else if (lhsType is PointerType && rhsType is PointerType) {
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs); return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs);
} else if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType()) {
if (lhsType.Equals(rhsType))
return BinaryOperatorResolveResult(compilation.FindType(KnownTypeCode.Boolean), lhs, op, rhs, isLifted: isNullable);
else
return new ErrorResolveResult(compilation.FindType(KnownTypeCode.Boolean));
} }
if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) { if (op == BinaryOperatorType.Equality || op == BinaryOperatorType.InEquality) {
if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) { if (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) {
@ -855,6 +860,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
default: default:
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
if (lhsType.IsCSharpNativeIntegerType() || rhsType.IsCSharpNativeIntegerType()) {
if (lhsType.Equals(rhsType)) {
return BinaryOperatorResolveResult(
isNullable ? NullableType.Create(compilation, lhsType) : lhsType,
lhs, op, rhs, isLifted: isNullable);
}
// mixing nint/nuint is not allowed
return new ErrorResolveResult(lhsType);
}
OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { lhs, rhs }); OverloadResolution builtinOperatorOR = CreateOverloadResolution(new[] { lhs, rhs });
foreach (var candidate in methodGroup) { foreach (var candidate in methodGroup) {
builtinOperatorOR.AddCandidate(candidate); builtinOperatorOR.AddCandidate(candidate);
@ -1000,8 +1014,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants) bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants)
{ {
// C# 4.0 spec: §7.3.6.2 // C# 4.0 spec: §7.3.6.2
TypeCode lhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(lhs.Type)); var lhsUType = NullableType.GetUnderlyingType(lhs.Type);
TypeCode rhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rhs.Type)); var rhsUType = NullableType.GetUnderlyingType(rhs.Type);
TypeCode lhsCode = ReflectionHelper.GetTypeCode(lhsUType);
TypeCode rhsCode = ReflectionHelper.GetTypeCode(rhsUType);
// Treat C# 9 native integers as falling between int and long.
// However they don't have a TypeCode, so we hack around that here:
if (lhsUType.Kind == TypeKind.NInt) {
lhsCode = TypeCode.Int32;
} else if (lhsUType.Kind == TypeKind.NUInt) {
lhsCode = TypeCode.UInt32;
}
if (rhsUType.Kind == TypeKind.NInt) {
rhsCode = TypeCode.Int32;
} else if (rhsUType.Kind == TypeKind.NUInt) {
rhsCode = TypeCode.UInt32;
}
// if one of the inputs is the null literal, promote that to the type of the other operand // if one of the inputs is the null literal, promote that to the type of the other operand
if (isNullable && lhs.Type.Kind == TypeKind.Null && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) { if (isNullable && lhs.Type.Kind == TypeKind.Null && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) {
lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants); lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants);
@ -1026,10 +1054,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
} else if (lhsCode == TypeCode.UInt64 || rhsCode == TypeCode.UInt64) { } else if (lhsCode == TypeCode.UInt64 || rhsCode == TypeCode.UInt64) {
targetType = TypeCode.UInt64; targetType = TypeCode.UInt64;
bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs); bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs);
} else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) { } else if (lhsUType.Kind == TypeKind.NUInt || rhsUType.Kind == TypeKind.NUInt) {
targetType = TypeCode.Int64; bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs);
lhs = CastTo(SpecialType.NUInt, isNullable, lhs, allowNullableConstants);
rhs = CastTo(SpecialType.NUInt, isNullable, rhs, allowNullableConstants);
return !bindingError;
} else if (lhsCode == TypeCode.UInt32 || rhsCode == TypeCode.UInt32) { } else if (lhsCode == TypeCode.UInt32 || rhsCode == TypeCode.UInt32) {
targetType = (IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs)) ? TypeCode.Int64 : TypeCode.UInt32; targetType = (IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs)) ? TypeCode.Int64 : TypeCode.UInt32;
} else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) {
targetType = TypeCode.Int64;
} else if (lhsUType.Kind == TypeKind.NInt || rhsUType.Kind == TypeKind.NInt) {
lhs = CastTo(SpecialType.NInt, isNullable, lhs, allowNullableConstants);
rhs = CastTo(SpecialType.NInt, isNullable, rhs, allowNullableConstants);
return !bindingError;
} else { } else {
targetType = TypeCode.Int32; targetType = TypeCode.Int32;
} }
@ -1066,14 +1103,18 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
ResolveResult CastTo(TypeCode targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants) ResolveResult CastTo(TypeCode targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants)
{ {
IType elementType = compilation.FindType(targetType); return CastTo(compilation.FindType(targetType), isNullable, expression, allowNullableConstants);
IType nullableType = MakeNullable(elementType, isNullable); }
ResolveResult CastTo(IType targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants)
{
IType nullableType = MakeNullable(targetType, isNullable);
if (nullableType.Equals(expression.Type)) if (nullableType.Equals(expression.Type))
return expression; return expression;
if (allowNullableConstants && expression.IsCompileTimeConstant) { if (allowNullableConstants && expression.IsCompileTimeConstant) {
if (expression.ConstantValue == null) if (expression.ConstantValue == null)
return new ConstantResolveResult(nullableType, null); return new ConstantResolveResult(nullableType, null);
ResolveResult rr = ResolveCast(elementType, expression); ResolveResult rr = ResolveCast(targetType, expression);
if (rr.IsError) if (rr.IsError)
return rr; return rr;
Debug.Assert(rr.IsCompileTimeConstant); Debug.Assert(rr.IsCompileTimeConstant);
@ -1262,7 +1303,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// C# 4.0 spec: §7.7.6 Cast expressions // C# 4.0 spec: §7.7.6 Cast expressions
Conversion c = conversions.ExplicitConversion(expression, targetType); Conversion c = conversions.ExplicitConversion(expression, targetType);
if (expression.IsCompileTimeConstant && !c.IsUserDefined) { if (expression.IsCompileTimeConstant && !c.IsUserDefined) {
TypeCode code = ReflectionHelper.GetTypeCode(targetType); IType underlyingType = targetType.GetEnumUnderlyingType();
TypeCode code = ReflectionHelper.GetTypeCode(underlyingType);
if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null) { if (code >= TypeCode.Boolean && code <= TypeCode.Decimal && expression.ConstantValue != null) {
try { try {
return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue));
@ -1276,19 +1318,18 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return new ConstantResolveResult(targetType, expression.ConstantValue); return new ConstantResolveResult(targetType, expression.ConstantValue);
else else
return new ErrorResolveResult(targetType); return new ErrorResolveResult(targetType);
} else if (targetType.Kind == TypeKind.Enum) { } else if ((underlyingType.Kind == TypeKind.NInt || underlyingType.Kind == TypeKind.NUInt) && expression.ConstantValue != null) {
code = ReflectionHelper.GetTypeCode(GetEnumUnderlyingType(targetType)); code = (underlyingType.Kind == TypeKind.NInt ? TypeCode.Int32 : TypeCode.UInt32);
if (code >= TypeCode.SByte && code <= TypeCode.UInt64 && expression.ConstantValue != null) {
try { try {
return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue)); return new ConstantResolveResult(targetType, Util.CSharpPrimitiveCast.Cast(code, expression.ConstantValue, checkForOverflow: true));
} catch (OverflowException) { } catch (OverflowException) {
return new ErrorResolveResult(targetType); // If constant value doesn't fit into 32-bits, the conversion is not a compile-time constant
return new ConversionResolveResult(targetType, expression, c, checkForOverflow);
} catch (InvalidCastException) { } catch (InvalidCastException) {
return new ErrorResolveResult(targetType); return new ErrorResolveResult(targetType);
} }
} }
} }
}
return new ConversionResolveResult(targetType, expression, c, checkForOverflow); return new ConversionResolveResult(targetType, expression, c, checkForOverflow);
} }

21
ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs

@ -722,7 +722,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ace; return ace;
} else if (rr.IsCompileTimeConstant) { } else if (rr.IsCompileTimeConstant) {
var expr = ConvertConstantValue(rr.Type, rr.ConstantValue); var expr = ConvertConstantValue(rr.Type, rr.ConstantValue);
if (isBoxing && rr.Type.IsCSharpSmallIntegerType()) { if (isBoxing && (rr.Type.IsCSharpSmallIntegerType() || rr.Type.IsCSharpNativeIntegerType())) {
// C# does not have small integer literal types. // C# does not have small integer literal types.
// We need to add a cast so that the integer literal gets boxed as the correct type. // We need to add a cast so that the integer literal gets boxed as the correct type.
expr = new CastExpression(ConvertType(rr.Type), expr); expr = new CastExpression(ConvertType(rr.Type), expr);
@ -793,21 +793,24 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
if (type.IsKnownType(KnownTypeCode.Double) || type.IsKnownType(KnownTypeCode.Single)) if (type.IsKnownType(KnownTypeCode.Double) || type.IsKnownType(KnownTypeCode.Single))
return ConvertFloatingPointLiteral(type, constantValue); return ConvertFloatingPointLiteral(type, constantValue);
IType literalType = type; IType literalType = type;
bool smallInteger = type.IsCSharpSmallIntegerType(); bool integerTypeMismatch = type.IsCSharpSmallIntegerType() || type.IsCSharpNativeIntegerType();
if (smallInteger) { if (integerTypeMismatch) {
// C# does not have integer literals of small integer types, // C# does not have integer literals of small integer types,
// use `int` literal instead. // use `int` literal instead.
constantValue = CSharpPrimitiveCast.Cast(TypeCode.Int32, constantValue, false); // It also doesn't have native integer literals, those also use `int` (or `uint` for `nuint`).
literalType = type.GetDefinition().Compilation.FindType(KnownTypeCode.Int32); bool unsigned = type.Kind == TypeKind.NUInt;
constantValue = CSharpPrimitiveCast.Cast(unsigned ? TypeCode.UInt32 : TypeCode.Int32, constantValue, false);
var compilation = resolver?.Compilation ?? expectedType.GetDefinition()?.Compilation;
literalType = compilation?.FindType(unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32);
} }
LiteralFormat format = LiteralFormat.None; LiteralFormat format = LiteralFormat.None;
if (PrintIntegralValuesAsHex) { if (PrintIntegralValuesAsHex) {
format = LiteralFormat.HexadecimalNumber; format = LiteralFormat.HexadecimalNumber;
} }
expr = new PrimitiveExpression(constantValue, format); expr = new PrimitiveExpression(constantValue, format);
if (AddResolveResultAnnotations) if (AddResolveResultAnnotations && literalType != null)
expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue)); expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue));
if (smallInteger && !type.Equals(expectedType)) { if (integerTypeMismatch && !type.Equals(expectedType)) {
expr = new CastExpression(ConvertType(type), expr); expr = new CastExpression(ConvertType(type), expr);
} }
return expr; return expr;
@ -822,7 +825,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
// find IType of constant in compilation. // find IType of constant in compilation.
var constantType = expectedType; var constantType = expectedType;
if (!expectedType.IsKnownType(info.Type)) { if (!expectedType.IsKnownType(info.Type)) {
var compilation = expectedType.GetDefinition().Compilation; var compilation = resolver?.Compilation ?? expectedType.GetDefinition()?.Compilation;
if (compilation == null)
return false;
constantType = compilation.FindType(info.Type); constantType = compilation.FindType(info.Type);
} }
// if the field definition cannot be found, do not generate a reference to the field. // if the field definition cannot be found, do not generate a reference to the field.

2
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -202,7 +202,7 @@ namespace ICSharpCode.Decompiler.IL
case BinaryNumericOperator.BitXor: case BinaryNumericOperator.BitXor:
var left = bni.Left.InferType(compilation); var left = bni.Left.InferType(compilation);
var right = bni.Right.InferType(compilation); var right = bni.Right.InferType(compilation);
if (left.Equals(right) && (left.IsCSharpPrimitiveIntegerType() || left.IsKnownType(KnownTypeCode.Boolean))) if (left.Equals(right) && (left.IsCSharpPrimitiveIntegerType() || left.IsCSharpNativeIntegerType() || left.IsKnownType(KnownTypeCode.Boolean)))
return left; return left;
else else
return SpecialType.UnknownType; return SpecialType.UnknownType;

16
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -133,6 +133,22 @@ namespace ICSharpCode.Decompiler.TypeSystem
} }
} }
/// <summary>
/// Gets whether the type is a C# 9 native integer type: nint or nuint.
///
/// Returns false for (U)IntPtr.
/// </summary>
public static bool IsCSharpNativeIntegerType(this IType type)
{
switch (type.Kind) {
case TypeKind.NInt:
case TypeKind.NUInt:
return true;
default:
return false;
}
}
/// <summary> /// <summary>
/// Gets whether the type is a C# primitive integer type: byte, sbyte, short, ushort, int, uint, long and ulong. /// Gets whether the type is a C# primitive integer type: byte, sbyte, short, ushort, int, uint, long and ulong.
/// ///

Loading…
Cancel
Save