Browse Source

Merge branch 'master' of https://github.com/icsharpcode/ILSpy

pull/2066/head
MysticBoy 5 years ago
parent
commit
c95e9faa76
  1. 1
      ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs
  2. 9
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  3. 5
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  5. 54
      ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
  6. 129
      ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs
  7. 28
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs
  8. 4
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Unsafe.cs
  9. 198
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NativeInts.cs
  10. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs
  11. 5
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  12. 1
      ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs
  13. 200
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  14. 50
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  15. 93
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs
  16. 32
      ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
  17. 46
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  18. 22
      ICSharpCode.Decompiler/DecompilerSettings.cs
  19. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  20. 2
      ICSharpCode.Decompiler/IL/ILTypeExtensions.cs
  21. 21
      ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs
  22. 2
      ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs
  23. 29
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  24. 135
      ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
  25. 78
      ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs
  26. 10
      ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs
  27. 15
      ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs
  28. 35
      ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs
  29. 7
      ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs
  30. 2
      ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs
  31. 7
      ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs
  32. 32
      ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs
  33. 16
      ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs
  34. 12
      ICSharpCode.Decompiler/TypeSystem/SpecialType.cs
  35. 9
      ICSharpCode.Decompiler/TypeSystem/TypeKind.cs
  36. 37
      ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs
  37. 4
      ILSpy.Tests/ILSpy.Tests.csproj
  38. 4
      ILSpy/AssemblyListManager.cs
  39. 1
      ILSpy/ILSpy.csproj
  40. 5
      ILSpy/Languages/CSharpHighlightingTokenWriter.cs
  41. 1
      ILSpy/Languages/CSharpLanguage.cs
  42. 22
      ILSpy/Properties/Resources.Designer.cs
  43. 6
      ILSpy/Properties/Resources.resx
  44. 2
      ILSpy/SessionSettings.cs
  45. 61
      ILSpy/TreeNodes/BaseTypesEntryNode.cs
  46. 25
      ILSpy/TreeNodes/BaseTypesTreeNode.cs
  47. 41
      ILSpy/ViewModels/ManageAssemblyListsViewModel.cs
  48. 15
      ILSpy/Views/ManageAssemblyListsDialog.xaml

1
ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs

@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -39,6 +39,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
"System.Runtime.CompilerServices.IsUnmanagedAttribute",
"System.Runtime.CompilerServices.NullableAttribute",
"System.Runtime.CompilerServices.NullableContextAttribute",
"System.Runtime.CompilerServices.NativeIntegerAttribute",
"Microsoft.CodeAnalysis.EmbeddedAttribute",
};

9
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -57,6 +57,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
ReferenceVisualBasic = 0x40,
ReferenceCore = 0x80,
GeneratePdb = 0x100,
Preview = 0x200
}
[Flags]
@ -271,7 +272,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -271,7 +272,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
if (flags.HasFlag(CompilerOptions.UseRoslyn)) {
var parseOptions = new CSharpParseOptions(
preprocessorSymbols: preprocessorSymbols.ToArray(),
languageVersion: Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8
languageVersion: flags.HasFlag(CompilerOptions.Preview) ? Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview : Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8
);
var syntaxTrees = sourceFileNames.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, path: f, encoding: Encoding.UTF8));
if (flags.HasFlag(CompilerOptions.ReferenceCore)) {
@ -399,7 +400,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -399,7 +400,11 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
internal static DecompilerSettings GetSettings(CompilerOptions cscOptions)
{
if (cscOptions.HasFlag(CompilerOptions.UseRoslyn)) {
return new DecompilerSettings(CSharp.LanguageVersion.Latest);
if (cscOptions.HasFlag(CompilerOptions.Preview)) {
return new DecompilerSettings(CSharp.LanguageVersion.Latest);
} else {
return new DecompilerSettings(CSharp.LanguageVersion.CSharp8_0);
}
} else {
return new DecompilerSettings(CSharp.LanguageVersion.CSharp5);
}

5
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -42,8 +42,8 @@ @@ -42,8 +42,8 @@
<ItemGroup>
<PackageReference Include="DiffLib" Version="2017.7.26.1241" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.2.2" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" Version="3.7.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0-4.final" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" Version="3.7.0-4.final" />
<PackageReference Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta1-63314-01" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
@ -92,6 +92,7 @@ @@ -92,6 +92,7 @@
<Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<None Include="TestCases\Pretty\NativeInts.cs" />
<None Include="TestCases\ILPretty\CallIndirect.cs" />
<Compile Include="TestCases\ILPretty\Issue1681.cs" />
<Compile Include="TestCases\Ugly\AggressiveScalarReplacementOfAggregates.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -337,6 +337,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -337,6 +337,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions);
}
[Test]
public void NativeInts([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
}
[Test]
public void NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{

54
ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs

@ -88,39 +88,65 @@ namespace ICSharpCode.Decompiler.Tests @@ -88,39 +88,65 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public void ExplicitConversions()
{
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe");
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe", LanguageVersion.CSharp8_0);
}
[Test]
public void ExplicitConversions_32()
{
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe");
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe", LanguageVersion.CSharp8_0);
}
[Test]
[Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")]
public void ExplicitConversions_With_NativeInts()
{
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions.exe", LanguageVersion.Preview);
}
[Test]
[Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")]
public void ExplicitConversions_32_With_NativeInts()
{
RunWithOutput("Random Tests\\TestCases", "ExplicitConversions_32.exe", LanguageVersion.Preview);
}
[Test]
public void Random_TestCase_1()
{
RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe");
RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe", LanguageVersion.CSharp8_0);
}
[Test]
[Ignore("Waiting for https://github.com/dotnet/roslyn/issues/45929")]
public void Random_TestCase_1_With_NativeInts()
{
RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe", LanguageVersion.Preview);
}
void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null, bool useOldProjectFormat = false)
// Let's limit the roundtrip tests to C# 8.0 for now; because 9.0 is still in preview
// and the generated project doesn't build as-is.
const LanguageVersion defaultLanguageVersion = LanguageVersion.CSharp8_0;
void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, LanguageVersion languageVersion = defaultLanguageVersion, string keyFile = null, bool useOldProjectFormat = false)
{
RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile, useOldProjectFormat);
RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), languageVersion, snkFilePath: keyFile, useOldProjectFormat: useOldProjectFormat);
}
void RunWithOutput(string dir, string fileToRoundtrip)
void RunWithOutput(string dir, string fileToRoundtrip, LanguageVersion languageVersion = defaultLanguageVersion)
{
string inputDir = Path.Combine(TestDir, dir);
RunInternal(dir, fileToRoundtrip,
outputDir => Tester.RunAndCompareOutput(fileToRoundtrip, Path.Combine(inputDir, fileToRoundtrip), Path.Combine(outputDir, fileToRoundtrip)));
outputDir => Tester.RunAndCompareOutput(fileToRoundtrip, Path.Combine(inputDir, fileToRoundtrip), Path.Combine(outputDir, fileToRoundtrip)),
languageVersion);
}
void RunOnly(string dir, string fileToRoundtrip)
void RunOnly(string dir, string fileToRoundtrip, LanguageVersion languageVersion = defaultLanguageVersion)
{
RunInternal(dir, fileToRoundtrip, outputDir => { });
RunInternal(dir, fileToRoundtrip, outputDir => { }, languageVersion);
}
void RunInternal(string dir, string fileToRoundtrip, Action<string> testAction, string snkFilePath = null, bool useOldProjectFormat = false)
void RunInternal(string dir, string fileToRoundtrip, Action<string> testAction, LanguageVersion languageVersion, string snkFilePath = null, bool useOldProjectFormat = false)
{
if (!Directory.Exists(TestDir)) {
Assert.Ignore($"Assembly-roundtrip test ignored: test directory '{TestDir}' needs to be checked out separately." + Environment.NewLine +
@ -131,8 +157,8 @@ namespace ICSharpCode.Decompiler.Tests @@ -131,8 +157,8 @@ namespace ICSharpCode.Decompiler.Tests
string outputDir = inputDir + "-output";
if (inputDir.EndsWith("TestCases")) {
// make sure output dir names are unique so that we don't get trouble due to parallel test execution
decompiledDir += Path.GetFileNameWithoutExtension(fileToRoundtrip);
outputDir += Path.GetFileNameWithoutExtension(fileToRoundtrip);
decompiledDir += Path.GetFileNameWithoutExtension(fileToRoundtrip) + "_" + languageVersion.ToString();
outputDir += Path.GetFileNameWithoutExtension(fileToRoundtrip) + "_" + languageVersion.ToString();
}
ClearDirectory(decompiledDir);
ClearDirectory(outputDir);
@ -155,9 +181,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -155,9 +181,7 @@ namespace ICSharpCode.Decompiler.Tests
// use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes
var projectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}");
// Let's limit the roundtrip tests to C# 7.3 for now; because 8.0 is still in preview
// and the generated project doesn't build as-is.
var settings = new DecompilerSettings(LanguageVersion.CSharp7_3);
var settings = new DecompilerSettings(languageVersion);
if (useOldProjectFormat) {
settings.UseSdkStyleProjectFormat = false;
}

129
ICSharpCode.Decompiler.Tests/Semantics/ConversionTests.cs

@ -32,6 +32,8 @@ namespace ICSharpCode.Decompiler.Tests.Semantics @@ -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 @@ -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 @@ -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()
{

28
ICSharpCode.Decompiler.Tests/TestCases/Correctness/CompoundAssignment.cs

@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -32,6 +32,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
UnsignedShiftRightStaticProperty();
DivideByBigValue();
Overflow();
IntPtr_CompoundAssign();
}
static void Test(int a, int b)
@ -104,6 +105,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -104,6 +105,19 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
}
}
IntPtr intPtrField = new IntPtr(IntPtr.Size == 8 ? long.MaxValue : int.MaxValue);
public IntPtr IntPtrProperty {
get {
Console.WriteLine("In {0}.get_IntPtrProperty", instanceNumber);
return intPtrField;
}
set {
Console.WriteLine("In {0}.set_IntPtrProperty, value={1}", instanceNumber, value);
intPtrField = value;
}
}
public static Dictionary<string, int> GetDict()
{
Console.WriteLine("In GetDict()");
@ -112,8 +126,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -112,8 +126,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
static CompoundAssignment GetObject()
{
Console.WriteLine("In GetObject() (instance #)");
return new CompoundAssignment();
var obj = new CompoundAssignment();
Console.WriteLine("In GetObject() (instance #{0})", obj.instanceNumber);
return obj;
}
static string GetString()
@ -208,5 +223,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -208,5 +223,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
{
return val;
}
static void IntPtr_CompoundAssign()
{
Console.WriteLine("IntPtr_CompoundAssign:");
#if !MCS
GetObject().IntPtrProperty -= 2;
GetObject().IntPtrProperty += 2;
#endif
}
}
}

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

@ -186,7 +186,7 @@ namespace System.Runtime.CompilerServices @@ -186,7 +186,7 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)]
@ -210,7 +210,7 @@ namespace System.Runtime.CompilerServices @@ -210,7 +210,7 @@ namespace System.Runtime.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)]
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)]

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

@ -0,0 +1,198 @@ @@ -0,0 +1,198 @@
// Copyright (c) 2020 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;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
internal class NativeInts
{
private const nint nint_const = 42;
private const nuint nuint_const = 99u;
private IntPtr intptr;
private UIntPtr uintptr;
private nint i;
private nuint u;
private int i32;
private uint u32;
private long i64;
private ulong u64;
private (IntPtr, nint, UIntPtr, nuint) tuple_field;
private Dictionary<nint, IntPtr> dict1;
private Dictionary<IntPtr, nint> dict2;
public void Convert()
{
intptr = i;
intptr = (nint)u;
intptr = (nint)(nuint)uintptr;
uintptr = (nuint)i;
uintptr = u;
uintptr = (nuint)(nint)intptr;
i = intptr;
i = (nint)u;
i = (nint)(nuint)uintptr;
u = (nuint)i;
u = uintptr;
u = (nuint)(nint)intptr;
}
public void Convert2()
{
i32 = (int)i;
i = i32;
intptr = (IntPtr)i32;
i64 = (long)intptr;
i64 = i;
i = (nint)i64;
u32 = (uint)i;
i = (nint)u32;
u64 = (uint)i;
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 void Shifts()
{
Console.WriteLine(i << i32);
Console.WriteLine(i >> i32);
Console.WriteLine(u >> i32);
Console.WriteLine(u << i32);
}
public void Comparisons()
{
Console.WriteLine(i < i32);
Console.WriteLine(i <= i32);
Console.WriteLine(i > i32);
Console.WriteLine(i >= i32);
Console.WriteLine(i == (nint)u);
Console.WriteLine(i < (nint)u);
Console.WriteLine((nuint)i < u);
}
public void Unary()
{
Console.WriteLine(~i);
Console.WriteLine(~u);
Console.WriteLine(-i);
}
public unsafe int* PtrArithmetic(int* ptr)
{
return ptr + i;
}
public unsafe nint* PtrArithmetic(nint* ptr)
{
return ptr + u;
}
public object[] Boxing()
{
return new object[10] {
1,
(nint)2,
3L,
4u,
(nuint)5u,
6uL,
int.MaxValue,
(nint)int.MaxValue,
i64,
(nint)i64
};
}
public NativeInts GetInstance(int i)
{
return this;
}
public void CompoundAssign()
{
GetInstance(0).i += i32;
checked {
GetInstance(1).i += i32;
}
GetInstance(2).u *= 2u;
checked {
GetInstance(3).u *= 2u;
}
GetInstance(4).intptr += (nint)i32;
checked {
// Note: the cast is necessary here, without it we'd call IntPtr.op_Addition
// but that is always unchecked.
GetInstance(5).intptr += (nint)i32;
}
// multiplication results in compiler-error without the cast
GetInstance(6).intptr *= (nint)2;
GetInstance(7).i <<= i32;
}
public void LocalTypeFromStore()
{
nint num = 42;
IntPtr zero = IntPtr.Zero;
nint zero2 = IntPtr.Zero;
nuint num2 = 43u;
nint num3 = i;
IntPtr intPtr = intptr;
Console.WriteLine();
zero2 = 1;
Console.WriteLine();
intptr = num;
intptr = zero;
intptr = zero2;
uintptr = num2;
intptr = num3;
intptr = intPtr;
}
public void LocalTypeFromUse()
{
IntPtr intPtr = intptr;
nint num = intptr;
Console.WriteLine();
intptr = intPtr;
i = num + 1;
}
}
}

6
ICSharpCode.Decompiler.Tests/TestCases/Pretty/TupleTests.cs

@ -146,9 +146,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -146,9 +146,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void LocalVariables((int, int) a)
{
(int, int) valueTuple = (a.Item1 + a.Item2, a.Item1 * a.Item2);
Console.WriteLine(valueTuple.ToString());
Console.WriteLine(valueTuple.GetType().FullName);
(int, int) tuple = (a.Item1 + a.Item2, a.Item1 * a.Item2);
Console.WriteLine(tuple.ToString());
Console.WriteLine(tuple.GetType().FullName);
}
public void Foreach(IEnumerable<(int, string)> input)

5
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -149,9 +149,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -149,9 +149,9 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new IndexRangeTransform(),
new NamedArgumentTransform(),
new UserDefinedLogicTransform(),
new IndexRangeTransform()
new UserDefinedLogicTransform()
),
}
},
@ -165,6 +165,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -165,6 +165,7 @@ namespace ICSharpCode.Decompiler.CSharp
new HighLevelLoopTransform(),
new ReduceNestingTransform(),
new IntroduceDynamicTypeOnLocals(),
new IntroduceNativeIntTypeOnLocals(),
new AssignVariableNames(),
};
}

1
ICSharpCode.Decompiler/CSharp/CSharpLanguageVersion.cs

@ -17,6 +17,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -17,6 +17,7 @@ namespace ICSharpCode.Decompiler.CSharp
CSharp7_2 = 702,
CSharp7_3 = 703,
CSharp8_0 = 800,
Preview = 900,
Latest = 0x7FFFFFFF
}
}

200
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -109,15 +109,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -109,15 +109,10 @@ namespace ICSharpCode.Decompiler.CSharp
if (!allowImplicitConversion) {
if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
expr = new CastExpression(ConvertType(rr.Type), expr);
} else {
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);
break;
}
} else if (rr.Type.IsCSharpSmallIntegerType()) {
expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr);
} else if (rr.Type.IsCSharpNativeIntegerType()) {
expr = new CastExpression(new PrimitiveType(rr.Type.Name), expr);
}
}
var exprRR = expr.Annotation<ResolveResult>();
@ -572,7 +567,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -572,7 +567,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (argUType.GetStackType().GetSize() < inst.UnderlyingResultType.GetSize()
|| argUType.Kind == TypeKind.Enum && argUType.IsSmallIntegerType()
|| argUType.GetStackType() == StackType.I
|| (argUType.GetStackType() == StackType.I && !argUType.IsCSharpNativeIntegerType())
|| argUType.IsKnownType(KnownTypeCode.Boolean)
|| argUType.IsKnownType(KnownTypeCode.Char))
{
@ -581,13 +576,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -581,13 +576,11 @@ namespace ICSharpCode.Decompiler.CSharp
// Same if the argument is an enum based on a small integer type
// (those don't undergo numeric promotion in C# the way non-enum small integer types do).
// Same if the type is one that does not support ~ (IntPtr, bool and char).
StackType targetStackType = inst.UnderlyingResultType;
if (targetStackType == StackType.I) {
// IntPtr doesn't support operator ~.
// Note that it's OK to use a type that's larger than necessary.
targetStackType = StackType.I8;
Sign sign = context.TypeHint.GetSign();
if (sign == Sign.None) {
sign = argUType.GetSign();
}
IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(argUType.GetSign()));
IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign);
if (inst.IsLifted) {
targetType = NullableType.Create(compilation, targetType);
}
@ -800,7 +793,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -800,7 +793,7 @@ namespace ICSharpCode.Decompiler.CSharp
} else if (rightUType.GetStackType() == inst.InputType && !rightUType.IsSmallIntegerType()) {
targetType = rightUType;
} else {
targetType = compilation.FindType(inst.InputType.ToKnownTypeCode(leftUType.GetSign()));
targetType = FindType(inst.InputType, leftUType.GetSign());
}
}
if (inst.IsLifted) {
@ -900,24 +893,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -900,24 +893,14 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
// Ensure the inputs have the correct sign:
KnownTypeCode inputType = KnownTypeCode.None;
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.InputType.IsIntegerType()) {
// Ensure the inputs have the correct sign:
IType inputType = FindArithmeticType(inst.InputType, inst.Sign);
if (inst.IsLifted) {
targetType = NullableType.Create(compilation, targetType);
inputType = NullableType.Create(compilation, inputType);
}
left = left.ConvertTo(targetType, this);
right = right.ConvertTo(targetType, this);
left = left.ConvertTo(inputType, this);
right = right.ConvertTo(inputType, this);
}
return new BinaryOperatorExpression(left.Expression, op, right.Expression)
.WithILInstruction(inst)
@ -993,22 +976,22 @@ namespace ICSharpCode.Decompiler.CSharp @@ -993,22 +976,22 @@ namespace ICSharpCode.Decompiler.CSharp
{
switch (inst.Operator) {
case BinaryNumericOperator.Add:
return HandleBinaryNumeric(inst, BinaryOperatorType.Add);
return HandleBinaryNumeric(inst, BinaryOperatorType.Add, context);
case BinaryNumericOperator.Sub:
return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract);
return HandleBinaryNumeric(inst, BinaryOperatorType.Subtract, context);
case BinaryNumericOperator.Mul:
return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply);
return HandleBinaryNumeric(inst, BinaryOperatorType.Multiply, context);
case BinaryNumericOperator.Div:
return HandlePointerSubtraction(inst)
?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide);
?? HandleBinaryNumeric(inst, BinaryOperatorType.Divide, context);
case BinaryNumericOperator.Rem:
return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus);
return HandleBinaryNumeric(inst, BinaryOperatorType.Modulus, context);
case BinaryNumericOperator.BitAnd:
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd);
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseAnd, context);
case BinaryNumericOperator.BitOr:
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr);
return HandleBinaryNumeric(inst, BinaryOperatorType.BitwiseOr, context);
case BinaryNumericOperator.BitXor:
return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr);
return HandleBinaryNumeric(inst, BinaryOperatorType.ExclusiveOr, context);
case BinaryNumericOperator.ShiftLeft:
return HandleShift(inst, BinaryOperatorType.ShiftLeft);
case BinaryNumericOperator.ShiftRight:
@ -1158,12 +1141,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1158,12 +1141,9 @@ namespace ICSharpCode.Decompiler.CSharp
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.
StackType targetType = expr.Type.GetStackType() == StackType.I4 ? StackType.I4 : StackType.I8;
expr = expr.ConvertTo(
compilation.FindType(targetType.ToKnownTypeCode(expr.Type.GetSign())),
this);
expr = expr.ConvertTo(FindArithmeticType(expr.Type.GetStackType(), expr.Type.GetSign()), this);
}
return expr;
}
@ -1254,7 +1234,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1254,7 +1234,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 left = Translate(inst.Left);
@ -1276,11 +1256,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1276,11 +1256,12 @@ namespace ICSharpCode.Decompiler.CSharp
if (op == BinaryOperatorType.Subtract && inst.Left.MatchLdcI(0)) {
IType rightUType = NullableType.GetUnderlyingType(right.Type);
if (rightUType.IsKnownType(KnownTypeCode.Int32) || rightUType.IsKnownType(KnownTypeCode.Int64) || rightUType.IsCSharpSmallIntegerType()) {
if (rightUType.IsKnownType(KnownTypeCode.Int32) || rightUType.IsKnownType(KnownTypeCode.Int64)
|| rightUType.IsCSharpSmallIntegerType() || rightUType.IsCSharpNativeIntegerType()) {
// unary minus is supported on signed int and long, and on the small integer types (since they promote to int)
var uoe = new UnaryOperatorExpression(UnaryOperatorType.Minus, right.Expression);
uoe.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation);
var resultType = rightUType.IsKnownType(KnownTypeCode.Int64) ? rightUType : compilation.FindType(KnownTypeCode.Int32);
var resultType = FindArithmeticType(inst.RightInputType, Sign.Signed);
if (inst.IsLifted)
resultType = NullableType.Create(compilation, resultType);
return uoe.WithILInstruction(inst).WithRR(new OperatorResolveResult(
@ -1312,8 +1293,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1312,8 +1293,12 @@ namespace ICSharpCode.Decompiler.CSharp
|| !IsCompatibleWithSign(left.Type, inst.Sign) || !IsCompatibleWithSign(right.Type, inst.Sign))
{
// Left and right operands are incompatible, so convert them to a common type
StackType targetStackType = inst.UnderlyingResultType == StackType.I ? StackType.I8 : inst.UnderlyingResultType;
IType targetType = compilation.FindType(targetStackType.ToKnownTypeCode(inst.Sign));
Sign sign = 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);
right = right.ConvertTo(NullableType.IsNullable(right.Type) ? NullableType.Create(compilation, targetType) : targetType, this);
rr = resolverWithOverflowCheck.ResolveBinaryOperator(op, left.ResolveResult, right.ResolveResult);
@ -1327,6 +1312,39 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1327,6 +1312,39 @@ namespace ICSharpCode.Decompiler.CSharp
return resultExpr;
}
/// <summary>
/// Gets a type matching the stack type and sign.
/// </summary>
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));
}
}
/// <summary>
/// Gets a type used for performing arithmetic with the stack type and sign.
///
/// This may result in a larger type than requested when the selected C# version
/// doesn't support native integers.
/// Should only be used after a call to PrepareArithmeticArgument()
/// to ensure that we're not preserving extra bits from an oversized TranslatedExpression.
/// </summary>
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>
/// Handle oversized arguments needing truncation; and avoid IntPtr/pointers in arguments.
/// </summary>
@ -1339,17 +1357,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1339,17 +1357,17 @@ namespace ICSharpCode.Decompiler.CSharp
if (argStackType.IsIntegerType() && argStackType.GetSize() < argUType.GetSize()) {
// If the argument is oversized (needs truncation to match stack size of its ILInstruction),
// perform the truncation now.
IType targetType = compilation.FindType(argStackType.ToKnownTypeCode(sign));
IType targetType = FindType(argStackType, sign);
argUType = targetType;
if (isLifted)
targetType = NullableType.Create(compilation, targetType);
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.
// 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.
IType targetType = compilation.FindType(StackType.I8.ToKnownTypeCode(sign));
IType targetType = FindArithmeticType(StackType.I, sign);
if (isLifted)
targetType = NullableType.Create(compilation, targetType);
arg = arg.ConvertTo(targetType, this);
@ -1385,6 +1403,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1385,6 +1403,8 @@ namespace ICSharpCode.Decompiler.CSharp
var left = Translate(inst.Left);
var right = Translate(inst.Right);
left = PrepareArithmeticArgument(left, inst.LeftInputType, inst.Sign, inst.IsLifted);
Sign sign = inst.Sign;
var leftUType = NullableType.GetUnderlyingType(left.Type);
if (leftUType.IsCSharpSmallIntegerType() && sign != Sign.Unsigned && inst.UnderlyingResultType == StackType.I4) {
@ -1396,12 +1416,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1396,12 +1416,7 @@ namespace ICSharpCode.Decompiler.CSharp
// if we don't need a specific sign, prefer keeping that of the input:
sign = leftUType.GetSign();
}
IType targetType;
if (inst.UnderlyingResultType == StackType.I4) {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32);
} else {
targetType = compilation.FindType(sign == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64);
}
IType targetType = FindArithmeticType(inst.UnderlyingResultType, sign);
if (NullableType.IsNullable(left.Type)) {
targetType = NullableType.Create(compilation, targetType);
}
@ -1562,10 +1577,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1562,10 +1577,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
} else {
IType targetType = NullableType.GetUnderlyingType(target.Type).GetEnumUnderlyingType();
if (NullableType.IsNullable(value.Type)) {
targetType = NullableType.Create(compilation, targetType);
}
value = value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion: true);
value = ConvertValue(value, targetType);
}
break;
case AssignmentOperatorType.Multiply:
@ -1575,10 +1587,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1575,10 +1587,7 @@ namespace ICSharpCode.Decompiler.CSharp
case AssignmentOperatorType.BitwiseOr:
case AssignmentOperatorType.ExclusiveOr: {
IType targetType = NullableType.GetUnderlyingType(target.Type);
if (NullableType.IsNullable(value.Type)) {
targetType = NullableType.Create(compilation, targetType);
}
value = value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion: true);
value = ConvertValue(value, targetType);
break;
}
}
@ -1590,6 +1599,20 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1590,6 +1599,20 @@ namespace ICSharpCode.Decompiler.CSharp
resultExpr.Expression.AddAnnotation(inst.CheckForOverflow ? AddCheckedBlocks.CheckedAnnotation : AddCheckedBlocks.UncheckedAnnotation);
}
return resultExpr;
TranslatedExpression ConvertValue(TranslatedExpression value, IType targetType)
{
bool allowImplicitConversion = true;
if (targetType.GetStackType() == StackType.I) {
// Force explicit cast for (U)IntPtr, keep allowing implicit conversion only for n(u)int
allowImplicitConversion = targetType.IsCSharpNativeIntegerType();
targetType = targetType.GetSign() == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt;
}
if (NullableType.IsNullable(value.Type)) {
targetType = NullableType.Create(compilation, targetType);
}
return value.ConvertTo(targetType, this, inst.CheckForOverflow, allowImplicitConversion);
}
}
TranslatedExpression HandleCompoundShift(NumericCompoundAssign inst, AssignmentOperatorType op)
@ -1631,7 +1654,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1631,7 +1654,11 @@ namespace ICSharpCode.Decompiler.CSharp
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);
StackType inputStackType = inst.InputType;
// Note: we're dealing with two conversions here:
@ -1650,8 +1677,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1650,8 +1677,15 @@ namespace ICSharpCode.Decompiler.CSharp
IType GetType(KnownTypeCode typeCode)
{
IType type = compilation.FindType(typeCode);
if (inst.IsLifted)
// Prefer n(u)int over (U)IntPtr
if (typeCode == KnownTypeCode.IntPtr && settings.NativeIntegers && !type.Equals(context.TypeHint)) {
type = SpecialType.NInt;
} else if (typeCode == KnownTypeCode.UIntPtr && settings.NativeIntegers && !type.Equals(context.TypeHint)) {
type = SpecialType.NUInt;
}
if (inst.IsLifted) {
type = NullableType.Create(compilation, type);
}
return type;
}
@ -2272,19 +2306,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2272,19 +2306,14 @@ namespace ICSharpCode.Decompiler.CSharp
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref));
}
TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false)
TranslatedExpression TranslateArrayIndex(ILInstruction i)
{
var input = Translate(i);
KnownTypeCode targetType;
if (i.ResultType == StackType.I4) {
if (input.Type.IsSmallIntegerType() && input.Type.Kind != TypeKind.Enum) {
return input; // we don't need a cast, just let small integers be promoted to int
}
targetType = input.Type.GetSign() == Sign.Unsigned ? KnownTypeCode.UInt32 : KnownTypeCode.Int32;
} else {
targetType = input.Type.GetSign() == Sign.Unsigned ? KnownTypeCode.UInt64 : KnownTypeCode.Int64;
if (i.ResultType == StackType.I4 && input.Type.IsSmallIntegerType() && input.Type.Kind != TypeKind.Enum) {
return input; // we don't need a cast, just let small integers be promoted to int
}
return input.ConvertTo(compilation.FindType(targetType), this);
IType targetType = FindArithmeticType(i.ResultType, input.Type.GetSign());
return input.ConvertTo(targetType, this);
}
internal static bool IsUnboxAnyWithIsInst(UnboxAny unboxAny, IsInst isInst)
@ -2337,8 +2366,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2337,8 +2366,17 @@ namespace ICSharpCode.Decompiler.CSharp
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 arg = Translate(inst.Argument, typeHint: inst.Type).ConvertTo(inst.Type, this);
return new CastExpression(ConvertType(obj), arg.Expression)
.WithILInstruction(inst)
.WithRR(new ConversionResolveResult(obj, arg.ResolveResult, Conversion.BoxingConversion));

50
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -326,10 +326,12 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}

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

@ -449,14 +449,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -449,14 +449,20 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// C# 4.0 spec: §7.6.9 Postfix increment and decrement operators
// C# 4.0 spec: §7.7.5 Prefix increment and decrement operators
TypeCode code = ReflectionHelper.GetTypeCode(type);
if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer)
if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer || type.IsCSharpNativeIntegerType())
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
else
return new ErrorResolveResult(expression.Type);
case UnaryOperatorType.Plus:
if (type.IsCSharpNativeIntegerType()) {
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
}
methodGroup = operators.UnaryPlusOperators;
break;
case UnaryOperatorType.Minus:
if (type.IsCSharpNativeIntegerType()) {
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
}
methodGroup = CheckForOverflow ? operators.CheckedUnaryMinusOperators : operators.UncheckedUnaryMinusOperators;
break;
case UnaryOperatorType.Not:
@ -472,7 +478,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -472,7 +478,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
rr = WithCheckForOverflow(false).ResolveCast(type, rr);
if (rr.IsCompileTimeConstant)
return rr;
}
}
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
} else if (type.IsCSharpNativeIntegerType()) {
return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable);
} else {
methodGroup = operators.BitwiseComplementOperators;
@ -774,6 +782,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -774,6 +782,11 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return HandleEnumComparison(op, rhsType, isNullable, lhs, rhs);
} else if (lhsType is PointerType && rhsType is PointerType) {
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 (lhsType.IsReferenceType == true && rhsType.IsReferenceType == true) {
@ -855,6 +868,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -855,6 +868,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
default:
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 });
foreach (var candidate in methodGroup) {
builtinOperatorOR.AddCandidate(candidate);
@ -1000,8 +1022,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1000,8 +1022,22 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants)
{
// C# 4.0 spec: §7.3.6.2
TypeCode lhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(lhs.Type));
TypeCode rhsCode = ReflectionHelper.GetTypeCode(NullableType.GetUnderlyingType(rhs.Type));
var lhsUType = NullableType.GetUnderlyingType(lhs.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 (isNullable && lhs.Type.Kind == TypeKind.Null && rhsCode >= TypeCode.Boolean && rhsCode <= TypeCode.Decimal) {
lhs = CastTo(rhsCode, isNullable, lhs, allowNullableConstants);
@ -1018,7 +1054,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1018,7 +1054,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
if (lhsCode == TypeCode.Decimal || rhsCode == TypeCode.Decimal) {
targetType = TypeCode.Decimal;
bindingError = (lhsCode == TypeCode.Single || lhsCode == TypeCode.Double
|| rhsCode == TypeCode.Single || rhsCode == TypeCode.Double);
|| rhsCode == TypeCode.Single || rhsCode == TypeCode.Double);
} else if (lhsCode == TypeCode.Double || rhsCode == TypeCode.Double) {
targetType = TypeCode.Double;
} else if (lhsCode == TypeCode.Single || rhsCode == TypeCode.Single) {
@ -1026,10 +1062,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1026,10 +1062,19 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
} else if (lhsCode == TypeCode.UInt64 || rhsCode == TypeCode.UInt64) {
targetType = TypeCode.UInt64;
bindingError = IsSigned(lhsCode, lhs) || IsSigned(rhsCode, rhs);
} else if (lhsCode == TypeCode.Int64 || rhsCode == TypeCode.Int64) {
targetType = TypeCode.Int64;
} else if (lhsUType.Kind == TypeKind.NUInt || rhsUType.Kind == TypeKind.NUInt) {
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) {
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 {
targetType = TypeCode.Int32;
}
@ -1063,17 +1108,21 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1063,17 +1108,21 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return false;
}
}
ResolveResult CastTo(TypeCode targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants)
{
IType elementType = compilation.FindType(targetType);
IType nullableType = MakeNullable(elementType, isNullable);
return CastTo(compilation.FindType(targetType), isNullable, expression, allowNullableConstants);
}
ResolveResult CastTo(IType targetType, bool isNullable, ResolveResult expression, bool allowNullableConstants)
{
IType nullableType = MakeNullable(targetType, isNullable);
if (nullableType.Equals(expression.Type))
return expression;
if (allowNullableConstants && expression.IsCompileTimeConstant) {
if (expression.ConstantValue == null)
return new ConstantResolveResult(nullableType, null);
ResolveResult rr = ResolveCast(elementType, expression);
ResolveResult rr = ResolveCast(targetType, expression);
if (rr.IsError)
return rr;
Debug.Assert(rr.IsCompileTimeConstant);
@ -1262,7 +1311,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1262,7 +1311,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
// C# 4.0 spec: §7.7.6 Cast expressions
Conversion c = conversions.ExplicitConversion(expression, targetType);
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) {
try {
return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue));
@ -1276,16 +1326,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -1276,16 +1326,15 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver
return new ConstantResolveResult(targetType, expression.ConstantValue);
else
return new ErrorResolveResult(targetType);
} else if (targetType.Kind == TypeKind.Enum) {
code = ReflectionHelper.GetTypeCode(GetEnumUnderlyingType(targetType));
if (code >= TypeCode.SByte && code <= TypeCode.UInt64 && expression.ConstantValue != null) {
try {
return new ConstantResolveResult(targetType, CSharpPrimitiveCast(code, expression.ConstantValue));
} catch (OverflowException) {
return new ErrorResolveResult(targetType);
} catch (InvalidCastException) {
return new ErrorResolveResult(targetType);
}
} else if ((underlyingType.Kind == TypeKind.NInt || underlyingType.Kind == TypeKind.NUInt) && expression.ConstantValue != null) {
code = (underlyingType.Kind == TypeKind.NInt ? TypeCode.Int32 : TypeCode.UInt32);
try {
return new ConstantResolveResult(targetType, Util.CSharpPrimitiveCast.Cast(code, expression.ConstantValue, checkForOverflow: true));
} catch (OverflowException) {
// 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) {
return new ErrorResolveResult(targetType);
}
}
}

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

@ -303,7 +303,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -303,7 +303,16 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
astType = ConvertTypeHelper(pt.GenericType, pt.TypeArguments);
break;
default:
astType = MakeSimpleType(type.Name);
switch (type.Kind) {
case TypeKind.Dynamic:
case TypeKind.NInt:
case TypeKind.NUInt:
astType = new PrimitiveType(type.Name);
break;
default:
astType = MakeSimpleType(type.Name);
break;
}
break;
}
if (type.Nullability == Nullability.Nullable) {
@ -713,7 +722,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -713,7 +722,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ace;
} else if (rr.IsCompileTimeConstant) {
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.
// We need to add a cast so that the integer literal gets boxed as the correct type.
expr = new CastExpression(ConvertType(rr.Type), expr);
@ -784,21 +793,24 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -784,21 +793,24 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
if (type.IsKnownType(KnownTypeCode.Double) || type.IsKnownType(KnownTypeCode.Single))
return ConvertFloatingPointLiteral(type, constantValue);
IType literalType = type;
bool smallInteger = type.IsCSharpSmallIntegerType();
if (smallInteger) {
bool integerTypeMismatch = type.IsCSharpSmallIntegerType() || type.IsCSharpNativeIntegerType();
if (integerTypeMismatch) {
// C# does not have integer literals of small integer types,
// use `int` literal instead.
constantValue = CSharpPrimitiveCast.Cast(TypeCode.Int32, constantValue, false);
literalType = type.GetDefinition().Compilation.FindType(KnownTypeCode.Int32);
// It also doesn't have native integer literals, those also use `int` (or `uint` for `nuint`).
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;
if (PrintIntegralValuesAsHex) {
format = LiteralFormat.HexadecimalNumber;
}
expr = new PrimitiveExpression(constantValue, format);
if (AddResolveResultAnnotations)
if (AddResolveResultAnnotations && literalType != null)
expr.AddAnnotation(new ConstantResolveResult(literalType, constantValue));
if (smallInteger && !type.Equals(expectedType)) {
if (integerTypeMismatch && !type.Equals(expectedType)) {
expr = new CastExpression(ConvertType(type), expr);
}
return expr;
@ -813,7 +825,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -813,7 +825,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
// find IType of constant in compilation.
var constantType = expectedType;
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);
}
// if the field definition cannot be found, do not generate a reference to the field.

46
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -305,22 +305,29 @@ namespace ICSharpCode.Decompiler.CSharp @@ -305,22 +305,29 @@ namespace ICSharpCode.Decompiler.CSharp
// Direct cast only works correctly for IntPtr -> long.
// IntPtr -> int works correctly only in checked context.
// Everything else can be worked around by casting via long.
if (!(targetType.IsKnownType(KnownTypeCode.Int64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) {
return this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
if (!(targetType.IsKnownType(KnownTypeCode.Int64) || targetType.Kind == TypeKind.NInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.Int32))) {
var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NInt : compilation.FindType(KnownTypeCode.Int64);
return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
}
} else if (type.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion from UIntPtr
// Direct cast only works correctly for UIntPtr -> ulong.
// UIntPtr -> uint works correctly only in checked context.
// Everything else can be worked around by casting via ulong.
if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) {
return this.ConvertTo(compilation.FindType(KnownTypeCode.UInt64), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
if (!(targetType.IsKnownType(KnownTypeCode.UInt64) || targetType.Kind == TypeKind.NUInt || checkForOverflow && targetType.IsKnownType(KnownTypeCode.UInt32))) {
var convertVia = expressionBuilder.settings.NativeIntegers ? SpecialType.NUInt : compilation.FindType(KnownTypeCode.UInt64);
return this.ConvertTo(convertVia, expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
}
}
if (targetUType.IsKnownType(KnownTypeCode.IntPtr)) { // Conversion to IntPtr
if (type.IsKnownType(KnownTypeCode.Int32)) {
// normal casts work for int (both in checked and unchecked context)
if (type.IsKnownType(KnownTypeCode.Int32) || type.Kind == TypeKind.NInt) {
// normal casts work for int/nint (both in checked and unchecked context)
// note that pointers only allow normal casts in unchecked contexts
} else if (expressionBuilder.settings.NativeIntegers) {
// if native integer types are available, prefer using those
return this.ConvertTo(SpecialType.NInt, expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
} else if (checkForOverflow) {
// if overflow-checking is enabled, we can simply cast via long:
// (and long itself works directly in checked context)
@ -328,17 +335,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -328,17 +335,19 @@ namespace ICSharpCode.Decompiler.CSharp
return this.ConvertTo(compilation.FindType(KnownTypeCode.Int64), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
} else {
} else if (type.Kind != TypeKind.Pointer) {
// If overflow-checking is disabled, the only way to truncate to native size
// without throwing an exception in 32-bit mode is to use a pointer type.
if (type.Kind != TypeKind.Pointer) {
return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
return this.ConvertTo(new PointerType(compilation.FindType(KnownTypeCode.Void)), expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow);
}
} else if (targetUType.IsKnownType(KnownTypeCode.UIntPtr)) { // Conversion to UIntPtr
if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer) {
// normal casts work for uint and pointers (both in checked and unchecked context)
if (type.IsKnownType(KnownTypeCode.UInt32) || type.Kind == TypeKind.Pointer || type.Kind == TypeKind.NUInt) {
// normal casts work for uint/nuint and pointers (both in checked and unchecked context)
} else if (expressionBuilder.settings.NativeIntegers) {
// if native integer types are available, prefer using those
return this.ConvertTo(SpecialType.NUInt, expressionBuilder, checkForOverflow)
.ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion);
} else if (checkForOverflow) {
// if overflow-checking is enabled, we can simply cast via ulong:
// (and ulong itself works directly in checked context)
@ -471,7 +480,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -471,7 +480,7 @@ namespace ICSharpCode.Decompiler.CSharp
return this;
}
} else {
if (targetType.Kind != TypeKind.Dynamic && type.Kind != TypeKind.Dynamic && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(type, targetType)) {
if (NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(type, targetType)) {
// avoid an explicit cast when types differ only in nullability of reference types
return this;
}
@ -505,6 +514,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -505,6 +514,11 @@ namespace ICSharpCode.Decompiler.CSharp
// If the cast was required for the old conversion, avoid making it implicit.
return false;
}
if (oldTargetType.Kind == TypeKind.NInt || oldTargetType.Kind == TypeKind.NUInt
|| newTargetType.Kind == TypeKind.NInt || newTargetType.Kind == TypeKind.NUInt) {
// nint has identity conversion with IntPtr, but the two have different implicit conversions
return false;
}
if (conversion.IsBoxingConversion) {
return conversions.IsBoxingConversionOrInvolvingTypeParameter(inputType, newTargetType);
}

22
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -114,10 +114,15 @@ namespace ICSharpCode.Decompiler @@ -114,10 +114,15 @@ namespace ICSharpCode.Decompiler
staticLocalFunctions = false;
ranges = false;
}
if (languageVersion < CSharp.LanguageVersion.Preview) {
nativeIntegers = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (nativeIntegers)
return CSharp.LanguageVersion.Preview;
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
@ -141,6 +146,23 @@ namespace ICSharpCode.Decompiler @@ -141,6 +146,23 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp1;
}
bool nativeIntegers = true;
/// <summary>
/// Use C# 9 <c>nint</c>/<c>nuint</c> types.
/// </summary>
[Category("C# 9.0 (experimental)")]
[Description("DecompilerSettings.NativeIntegers")]
public bool NativeIntegers {
get { return nativeIntegers; }
set {
if (nativeIntegers != value) {
nativeIntegers = value;
OnPropertyChanged();
}
}
}
bool anonymousMethods = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -78,6 +78,7 @@ @@ -78,6 +78,7 @@
<Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" />
<Compile Include="Documentation\XmlDocumentationElement.cs" />
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="Solution\ProjectId.cs" />
<Compile Include="Solution\ProjectItem.cs" />
<Compile Include="Solution\SolutionCreator.cs" />

2
ICSharpCode.Decompiler/IL/ILTypeExtensions.cs

@ -202,7 +202,7 @@ namespace ICSharpCode.Decompiler.IL @@ -202,7 +202,7 @@ namespace ICSharpCode.Decompiler.IL
case BinaryNumericOperator.BitXor:
var left = bni.Left.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;
else
return SpecialType.UnknownType;

21
ICSharpCode.Decompiler/IL/Instructions/CompoundAssignmentInstruction.cs

@ -128,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL @@ -128,7 +128,7 @@ namespace ICSharpCode.Decompiler.IL
/// Gets whether the instruction checks for overflow.
/// </summary>
public readonly bool CheckForOverflow;
/// <summary>
/// For integer operations that depend on the sign, specifies whether the operation
/// is signed or unsigned.
@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL @@ -151,7 +151,7 @@ namespace ICSharpCode.Decompiler.IL
CompoundTargetKind targetKind, ILInstruction value, IType type, CompoundEvalMode evalMode)
: base(OpCode.NumericCompoundAssign, evalMode, target, targetKind, value)
{
Debug.Assert(IsBinaryCompatibleWithType(binary, type));
Debug.Assert(IsBinaryCompatibleWithType(binary, type, null));
this.CheckForOverflow = binary.CheckForOverflow;
this.Sign = binary.Sign;
this.LeftInputType = binary.LeftInputType;
@ -164,11 +164,11 @@ namespace ICSharpCode.Decompiler.IL @@ -164,11 +164,11 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(evalMode == CompoundEvalMode.EvaluatesToNewValue || (Operator == BinaryNumericOperator.Add || Operator == BinaryNumericOperator.Sub));
Debug.Assert(this.ResultType == (IsLifted ? StackType.O : UnderlyingResultType));
}
/// <summary>
/// Gets whether the specific binary instruction is compatible with a compound operation on the specified type.
/// </summary>
internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, IType type)
internal static bool IsBinaryCompatibleWithType(BinaryNumericInstruction binary, IType type, DecompilerSettings settings)
{
if (binary.IsLifted) {
if (!NullableType.IsNullable(type))
@ -201,6 +201,19 @@ namespace ICSharpCode.Decompiler.IL @@ -201,6 +201,19 @@ namespace ICSharpCode.Decompiler.IL
default:
return false; // operator not supported on pointer types
}
} else if (type.IsKnownType(KnownTypeCode.IntPtr) || type.IsKnownType(KnownTypeCode.UIntPtr)) {
// "target.intptr *= 2;" is compiler error, but
// "target.intptr *= (nint)2;" works
if (settings != null && !settings.NativeIntegers) {
// But if native integers are not available, we cannot use compound assignment.
return false;
}
// The trick with casting the RHS to n(u)int doesn't work for shifts:
switch (binary.Operator) {
case BinaryNumericOperator.ShiftLeft:
case BinaryNumericOperator.ShiftRight:
return false;
}
}
if (binary.Sign != Sign.None) {
if (type.IsCSharpSmallIntegerType()) {

2
ICSharpCode.Decompiler/IL/PointerArithmeticOffset.cs

@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL @@ -36,7 +36,7 @@ namespace ICSharpCode.Decompiler.IL
if (mul.CheckForOverflow != checkForOverflow)
return null;
if (elementSize > 0 && mul.Right.MatchLdcI(elementSize.Value)
|| mul.Right.UnwrapConv(ConversionKind.SignExtend) is SizeOf sizeOf && sizeOf.Type.Equals(pointerElementType)) {
|| mul.Right.UnwrapConv(ConversionKind.SignExtend) is SizeOf sizeOf && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(sizeOf.Type, pointerElementType)) {
var countOffsetInst = mul.Left;
if (unwrapZeroExtension) {
countOffsetInst = countOffsetInst.UnwrapConv(ConversionKind.ZeroExtend);

29
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -417,19 +417,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -417,19 +417,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType);
}
string name;
if (type is ArrayType) {
name = "array";
} else if (type is PointerType) {
name = "ptr";
} else if (type.Kind == TypeKind.TypeParameter || type.Kind == TypeKind.Unknown || type.Kind == TypeKind.Dynamic) {
name = "val";
} else if (type.Kind == TypeKind.ByReference) {
name = "reference";
} else if (type.IsAnonymousType()) {
string name = type.Kind switch
{
TypeKind.Array => "array",
TypeKind.Pointer => "ptr",
TypeKind.TypeParameter => "val",
TypeKind.Unknown => "val",
TypeKind.Dynamic => "val",
TypeKind.ByReference => "reference",
TypeKind.Tuple => "tuple",
TypeKind.NInt => "num",
TypeKind.NUInt => "num",
_ => null
};
if (name != null) {
return name;
}
if (type.IsAnonymousType()) {
name = "anon";
} else if (type.Name.EndsWith("Exception", StringComparison.Ordinal)) {
name = "ex";
} else if (type.IsCSharpNativeIntegerType()) {
name = "num";
} else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) {
name = type.Name;
// remove the 'I' for interfaces

135
ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs

@ -123,6 +123,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -123,6 +123,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
this.RangeGetAll = p.Getter;
}
}
public static bool IsRangeCtor(IMethod method)
{
return method.SymbolKind == SymbolKind.Constructor
&& method.Parameters.Count == 2
&& method.DeclaringType.IsKnownType(KnownTypeCode.Range)
&& method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Index));
}
}
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
@ -147,9 +155,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -147,9 +155,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
rangeVar = null;
rangeVarInit = null;
}
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
if (!block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit))
if (!block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit)) {
// Not our primary indexing/slicing pattern.
// However, we might be dealing with a partially-transformed pattern that needs to be extended.
ExtendSlicing();
return;
}
if (!(startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4))
return;
var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
@ -283,25 +296,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -283,25 +296,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return;
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true))
return;
if (startIndexKind == IndexKind.FromStart && endIndexKind == IndexKind.FromStart) {
// It's possible we actually have a startIndex/endIndex that involves the container length,
// but we couldn't detect it yet because the statement initializing the containerLengthVar is
// not yet part of the region to be transformed.
// If we transform now, we'd end up with:
// int length = span.Length;
// Console.WriteLine(span[(length - GetInt(1))..(length - GetInt(2))].ToString());
// which is correct but unnecessarily complex.
// So we peek ahead at the next instruction to be transformed:
if (startPos > 0 && MatchContainerLengthStore(block.Instructions[startPos - 1], out _, ref containerVar)) {
// Looks like the transform would be able do to a better job including that previous instruction,
// so let's avoid transforming just yet.
return;
}
// Something similar happens with the rangeVar:
if (startPos > 0 && block.Instructions[startPos - 1] is StLoc stloc && stloc.Variable.Type.IsKnownType(KnownTypeCode.Range)) {
return;
}
}
var specialMethods = new IndexMethods(context.TypeSystem);
if (!specialMethods.IsValid)
return;
@ -312,33 +306,116 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -312,33 +306,116 @@ namespace ICSharpCode.Decompiler.IL.Transforms
newCall.ConstrainedTo = call.ConstrainedTo;
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
newCall.Arguments.Add(call.Arguments[0]);
newCall.Arguments.Add(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
newCall.AddILRange(call);
for (int i = startPos; i < pos; i++) {
newCall.AddILRange(block.Instructions[i]);
}
call.ReplaceWith(newCall);
block.Instructions.RemoveRange(startPos, pos - startPos);
}
ILInstruction MakeRange(IndexKind startIndexKind, ILInstruction startIndexLoad, IndexKind endIndexKind, ILInstruction endIndexLoad, IndexMethods specialMethods)
{
if (rangeVar != null) {
newCall.Arguments.Add(rangeVarInit);
return rangeVarInit;
} else if (startIndexKind == IndexKind.TheStart && endIndexKind == IndexKind.TheEnd && specialMethods.RangeGetAll != null) {
newCall.Arguments.Add(new Call(specialMethods.RangeGetAll));
return new Call(specialMethods.RangeGetAll);
} else if (startIndexKind == IndexKind.TheStart && specialMethods.RangeEndAt != null) {
var rangeCtorCall = new Call(specialMethods.RangeEndAt);
rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
newCall.Arguments.Add(rangeCtorCall);
return rangeCtorCall;
} else if (endIndexKind == IndexKind.TheEnd && specialMethods.RangeStartAt != null) {
var rangeCtorCall = new Call(specialMethods.RangeStartAt);
rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
newCall.Arguments.Add(rangeCtorCall);
return rangeCtorCall;
} else {
var rangeCtorCall = new NewObj(specialMethods.RangeCtor);
rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
newCall.Arguments.Add(rangeCtorCall);
return rangeCtorCall;
}
newCall.AddILRange(call);
}
void ExtendSlicing()
{
// We might be dealing with a situation where we executed TransformSlicing() in a previous run of this transform
// that only looked at a part of the instructions making up the slicing pattern.
// The first run would have mis-detected slicing from end as slicing from start.
// This results in code like:
// int length = span.Length;
// Console.WriteLine(span[GetIndex(1).GetOffset(length)..GetIndex(2).GetOffset(length)].ToString());
// or:
// int length = span.Length;
// Range range = GetRange();
// Console.WriteLine(span[range.Start.GetOffset(length)..range.End.GetOffset(length)].ToString());
if (containerLengthVar == null) {
return; // need a container length to extend with
}
Debug.Assert(containerLengthVar.IsSingleDefinition);
Debug.Assert(containerLengthVar.LoadCount == 1 || containerLengthVar.LoadCount == 2);
NewObj rangeCtorCall = null;
foreach (var inst in containerLengthVar.LoadInstructions[0].Ancestors) {
if (inst is NewObj newobj && IndexMethods.IsRangeCtor(newobj.Method)) {
rangeCtorCall = newobj;
break;
}
if (inst == block)
break;
}
if (rangeCtorCall == null)
return;
// Now match the pattern that TransformSlicing() generated in the IndexKind.FromStart case
if (!(rangeCtorCall.Parent is CallInstruction { Method: SyntheticRangeIndexAccessor _ } slicingCall))
return;
if (!MatchContainerVar(slicingCall.Arguments[0], ref containerVar))
return;
if (!slicingCall.IsDescendantOf(block.Instructions[pos]))
return;
Debug.Assert(rangeCtorCall.Arguments.Count == 2);
if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[0], out var startOffsetInst))
return;
if (!MatchIndexImplicitConv(rangeCtorCall.Arguments[1], out var endOffsetInst))
return;
var startIndexKind = MatchGetOffset(startOffsetInst, out var startIndexLoad, containerLengthVar, ref containerVar);
var endIndexKind = MatchGetOffset(endOffsetInst, out var endIndexLoad, containerLengthVar, ref containerVar);
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind)) {
return;
}
// holds because we've used containerLengthVar at least once
Debug.Assert(startIndexKind != IndexKind.FromStart || endIndexKind != IndexKind.FromStart);
if (rangeVar != null) {
if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start"))
return;
if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End"))
return;
}
context.Step("Merge containerLengthVar into slicing", slicingCall);
var specialMethods = new IndexMethods(context.TypeSystem);
rangeCtorCall.ReplaceWith(MakeRange(startIndexKind, startIndexLoad, endIndexKind, endIndexLoad, specialMethods));
for (int i = startPos; i < pos; i++) {
newCall.AddILRange(block.Instructions[i]);
slicingCall.AddILRange(block.Instructions[i]);
}
call.ReplaceWith(newCall);
block.Instructions.RemoveRange(startPos, pos - startPos);
}
}
private bool MatchIndexImplicitConv(ILInstruction inst, out ILInstruction offsetInst)
{
offsetInst = null;
if (!(inst is CallInstruction call))
return false;
if (!(call.Method.IsOperator && call.Method.Name == "op_Implicit"))
return false;
var op = call.Method;
if (!(op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)))
return false;
if (!op.DeclaringType.IsKnownType(KnownTypeCode.Index))
return false;
offsetInst = call.Arguments.Single();
return true;
}
static bool IsSlicingMethod(IMethod method)
{
if (method.IsExtensionMethod)
@ -451,6 +528,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -451,6 +528,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
return false;
if (lengthVar.LoadCount == 0 || lengthVar.LoadCount > 2)
return false;
return MatchContainerLength(init, null, ref containerVar);
}

78
ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
// Copyright (c) 2020 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.Linq;
using System.Text;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL
{
class IntroduceNativeIntTypeOnLocals : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.NativeIntegers)
return;
foreach (var variable in function.Variables) {
if (variable.Kind != VariableKind.Local &&
variable.Kind != VariableKind.StackSlot &&
variable.Kind != VariableKind.ForeachLocal &&
variable.Kind != VariableKind.UsingLocal) {
continue;
}
if (!(variable.Type.IsKnownType(KnownTypeCode.IntPtr) || variable.Type.IsKnownType(KnownTypeCode.UIntPtr)))
continue;
bool isUsedAsNativeInt = variable.LoadInstructions.Any(IsUsedAsNativeInt);
bool isAssignedNativeInt = variable.StoreInstructions.Any(store => IsNativeIntStore(store, context.TypeSystem));
if (isUsedAsNativeInt || isAssignedNativeInt) {
variable.Type = variable.Type.GetSign() == Sign.Unsigned ? SpecialType.NUInt : SpecialType.NInt;
}
}
}
static bool IsUsedAsNativeInt(LdLoc load)
{
return load.Parent switch
{
BinaryNumericInstruction { UnderlyingResultType: StackType.I } => true,
BitNot { UnderlyingResultType: StackType.I } => true,
CallInstruction call => call.GetParameter(load.ChildIndex)?.Type.IsCSharpNativeIntegerType() ?? false,
_ => false,
};
}
static bool IsNativeIntStore(IStoreInstruction store, ICompilation compilation)
{
if (store is StLoc stloc) {
switch (stloc.Value) {
case BinaryNumericInstruction { UnderlyingResultType: StackType.I }:
return true;
case Conv { ResultType: StackType.I }:
return true;
default:
var inferredType = stloc.Value.InferType(compilation);
return inferredType.IsCSharpNativeIntegerType();
}
}
return false;
}
}
}

10
ICSharpCode.Decompiler/IL/Transforms/TransformAssignment.cs

@ -198,9 +198,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -198,9 +198,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType)
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType, DecompilerSettings settings)
{
if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType))
if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType, settings))
return false;
if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow))
return false; // conv does not match binary operation
@ -305,7 +305,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -305,7 +305,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable))
return false;
if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
if (!ValidateCompoundAssign(binary, smallIntConv, targetType, context.Settings))
return false;
context.Step($"Compound assignment (binary.numeric)", compoundStore);
finalizeMatch?.Invoke(context);
@ -656,7 +656,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -656,7 +656,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (binary != null && (binary.Right.MatchLdcI(1) || binary.Right.MatchLdcF4(1) || binary.Right.MatchLdcF8(1))) {
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings))
return false;
stloc = binary.Left as StLoc;
} else if (value is Call operatorCall && operatorCall.Method.IsOperator && operatorCall.Arguments.Count == 1) {
@ -731,7 +731,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -731,7 +731,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (!(binary.Operator == BinaryNumericOperator.Add || binary.Operator == BinaryNumericOperator.Sub))
return false;
if (!ValidateCompoundAssign(binary, conv, targetType))
if (!ValidateCompoundAssign(binary, conv, targetType, context.Settings))
return false;
context.Step("TransformPostIncDecOperator (builtin)", inst);
finalizeMatch?.Invoke(context);

15
ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs

@ -399,8 +399,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -399,8 +399,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// IL_0000: ldarg.0
// IL_0001: call instance void [mscorlib]System.Object::.ctor()
// IL_0006: ret
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Ldarg_0)
return false;
var opCode = DecodeOpCodeSkipNop(ref reader);
switch (opCode) {
case ILOpCode.Ldarg:
case ILOpCode.Ldarg_s:
if (reader.DecodeIndex(opCode) != 0)
return false;
break;
case ILOpCode.Ldarg_0:
// OK
break;
default:
return false;
}
if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Call)
return false;
var baseCtorHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32());

35
ICSharpCode.Decompiler/TypeSystem/ApplyAttributeTypeVisitor.cs

@ -42,6 +42,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -42,6 +42,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
{
bool hasDynamicAttribute = false;
bool[] dynamicAttributeData = null;
bool hasNativeIntegersAttribute = false;
bool[] nativeIntegersAttributeData = null;
string[] tupleElementNames = null;
Nullability nullability;
Nullability[] nullableAttributeData = null;
@ -50,7 +52,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -50,7 +52,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
} else {
nullability = Nullability.Oblivious;
}
const TypeSystemOptions relevantOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations;
const TypeSystemOptions relevantOptions = TypeSystemOptions.Dynamic | TypeSystemOptions.Tuple | TypeSystemOptions.NullabilityAnnotations | TypeSystemOptions.NativeIntegers;
if (attributes != null && (options & relevantOptions) != 0) {
foreach (var attrHandle in attributes.Value) {
var attr = metadata.GetCustomAttribute(attrHandle);
@ -65,6 +67,16 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -65,6 +67,16 @@ namespace ICSharpCode.Decompiler.TypeSystem
dynamicAttributeData = values.SelectArray(v => (bool)v.Value);
}
}
} else if ((options & TypeSystemOptions.NativeIntegers) != 0 && attrType.IsKnownType(metadata, KnownAttribute.NativeInteger)) {
hasNativeIntegersAttribute = true;
var ctor = attr.DecodeValue(Metadata.MetadataExtensions.minimalCorlibTypeProvider);
if (ctor.FixedArguments.Length == 1) {
var arg = ctor.FixedArguments[0];
if (arg.Value is ImmutableArray<SRM.CustomAttributeTypedArgument<IType>> values
&& values.All(v => v.Value is bool)) {
nativeIntegersAttributeData = values.SelectArray(v => (bool)v.Value);
}
}
} else if ((options & TypeSystemOptions.Tuple) != 0 && attrType.IsKnownType(metadata, KnownAttribute.TupleElementNames)) {
var ctor = attr.DecodeValue(Metadata.MetadataExtensions.minimalCorlibTypeProvider);
if (ctor.FixedArguments.Length == 1) {
@ -88,11 +100,12 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -88,11 +100,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
}
}
}
if (hasDynamicAttribute || nullability != Nullability.Oblivious || nullableAttributeData != null
if (hasDynamicAttribute || hasNativeIntegersAttribute || nullability != Nullability.Oblivious || nullableAttributeData != null
|| (options & (TypeSystemOptions.Tuple | TypeSystemOptions.KeepModifiers)) != TypeSystemOptions.KeepModifiers)
{
var visitor = new ApplyAttributeTypeVisitor(
compilation, hasDynamicAttribute, dynamicAttributeData,
hasNativeIntegersAttribute, nativeIntegersAttributeData,
options, tupleElementNames,
nullability, nullableAttributeData
);
@ -109,6 +122,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -109,6 +122,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
readonly ICompilation compilation;
readonly bool hasDynamicAttribute;
readonly bool[] dynamicAttributeData;
readonly bool hasNativeIntegersAttribute;
readonly bool[] nativeIntegersAttributeData;
readonly TypeSystemOptions options;
readonly string[] tupleElementNames;
readonly Nullability defaultNullability;
@ -117,12 +132,17 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -117,12 +132,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
int tupleTypeIndex = 0;
int nullabilityTypeIndex = 0;
private ApplyAttributeTypeVisitor(ICompilation compilation, bool hasDynamicAttribute, bool[] dynamicAttributeData, TypeSystemOptions options, string[] tupleElementNames,
private ApplyAttributeTypeVisitor(ICompilation compilation,
bool hasDynamicAttribute, bool[] dynamicAttributeData,
bool hasNativeIntegersAttribute, bool[] nativeIntegersAttributeData,
TypeSystemOptions options, string[] tupleElementNames,
Nullability defaultNullability, Nullability[] nullableAttributeData)
{
this.compilation = compilation ?? throw new ArgumentNullException(nameof(compilation));
this.hasDynamicAttribute = hasDynamicAttribute;
this.dynamicAttributeData = dynamicAttributeData;
this.hasNativeIntegersAttribute = hasNativeIntegersAttribute;
this.nativeIntegersAttributeData = nativeIntegersAttributeData;
this.options = options;
this.tupleElementNames = tupleElementNames;
this.defaultNullability = defaultNullability;
@ -247,11 +267,18 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -247,11 +267,18 @@ namespace ICSharpCode.Decompiler.TypeSystem
public override IType VisitTypeDefinition(ITypeDefinition type)
{
IType newType = type;
if (type.KnownTypeCode == KnownTypeCode.Object && hasDynamicAttribute) {
var ktc = type.KnownTypeCode;
if (ktc == KnownTypeCode.Object && hasDynamicAttribute) {
if (dynamicAttributeData == null || dynamicTypeIndex >= dynamicAttributeData.Length)
newType = SpecialType.Dynamic;
else if (dynamicAttributeData[dynamicTypeIndex])
newType = SpecialType.Dynamic;
} else if ((ktc == KnownTypeCode.IntPtr || ktc == KnownTypeCode.UIntPtr) && hasNativeIntegersAttribute) {
// native integers use the same indexing logic as 'dynamic'
if (nativeIntegersAttributeData == null || dynamicTypeIndex > nativeIntegersAttributeData.Length)
newType = (ktc == KnownTypeCode.IntPtr ? SpecialType.NInt : SpecialType.NUInt);
else if (nativeIntegersAttributeData[dynamicTypeIndex])
newType = (ktc == KnownTypeCode.IntPtr ? SpecialType.NInt : SpecialType.NUInt);
}
if (type.IsReferenceType == true) {
Nullability nullability = GetNullability();

7
ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs

@ -110,10 +110,15 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -110,10 +110,15 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// </summary>
ReadOnlyMethods = 0x800,
/// <summary>
/// [NativeIntegerAttribute] is used to replace 'IntPtr' types with the 'nint' type.
/// </summary>
NativeIntegers = 0x1000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# languages features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers
}
/// <summary>
@ -145,6 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -145,6 +150,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
typeSystemOptions |= TypeSystemOptions.NullabilityAnnotations;
if (settings.ReadOnlyMethods)
typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods;
if (settings.NativeIntegers)
typeSystemOptions |= TypeSystemOptions.NativeIntegers;
return typeSystemOptions;
}

2
ICSharpCode.Decompiler/TypeSystem/Implementation/AttributeListBuilder.cs

@ -194,6 +194,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -194,6 +194,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
switch (attributeType.Name) {
case "DynamicAttribute":
return (options & TypeSystemOptions.Dynamic) != 0;
case "NativeIntegerAttribute":
return (options & TypeSystemOptions.NativeIntegers) != 0;
case "TupleElementNamesAttribute":
return (options & TypeSystemOptions.Tuple) != 0;
case "ExtensionAttribute":

7
ICSharpCode.Decompiler/TypeSystem/Implementation/KnownAttributes.cs

@ -98,11 +98,14 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -98,11 +98,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
// Security attributes:
PermissionSet,
// C# 9 attributes:
NativeInteger,
}
static class KnownAttributes
{
internal const int Count = (int)KnownAttribute.PermissionSet + 1;
internal const int Count = (int)KnownAttribute.NativeInteger + 1;
static readonly TopLevelTypeName[] typeNames = new TopLevelTypeName[Count]{
default,
@ -160,6 +163,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -160,6 +163,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new TopLevelTypeName("System.Runtime.InteropServices", nameof(MarshalAsAttribute)),
// Security attributes:
new TopLevelTypeName("System.Security.Permissions", "PermissionSetAttribute"),
// C# 9 attributes:
new TopLevelTypeName("System.Runtime.CompilerServices", "NativeIntegerAttribute"),
};
public static ref readonly TopLevelTypeName GetTypeName(this KnownAttribute attr)

32
ICSharpCode.Decompiler/TypeSystem/NormalizeTypeVisitor.cs

@ -15,6 +15,18 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -15,6 +15,18 @@ namespace ICSharpCode.Decompiler.TypeSystem
ReplaceClassTypeParametersWithDummy = false,
ReplaceMethodTypeParametersWithDummy = false,
DynamicAndObject = true,
IntPtrToNInt = true,
TupleToUnderlyingType = true,
RemoveModOpt = true,
RemoveModReq = true,
RemoveNullability = true,
};
internal static readonly NormalizeTypeVisitor IgnoreNullabilityAndTuples = new NormalizeTypeVisitor {
ReplaceClassTypeParametersWithDummy = false,
ReplaceMethodTypeParametersWithDummy = false,
DynamicAndObject = false,
IntPtrToNInt = false,
TupleToUnderlyingType = true,
RemoveModOpt = true,
RemoveModReq = true,
@ -33,6 +45,7 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -33,6 +45,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 +64,18 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -51,13 +64,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);
}

16
ICSharpCode.Decompiler/TypeSystem/ReflectionHelper.cs

@ -36,7 +36,17 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -36,7 +36,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// A reflection class used to represent <c>dynamic</c>.
/// </summary>
public sealed class Dynamic {}
/// <summary>
/// A reflection class used to represent <c>nint</c>.
/// </summary>
public sealed class NInt { }
/// <summary>
/// A reflection class used to represent <c>nuint</c>.
/// </summary>
public sealed class NUInt { }
/// <summary>
/// A reflection class used to represent an unbound type argument.
/// </summary>
@ -101,6 +111,10 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -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))

12
ICSharpCode.Decompiler/TypeSystem/SpecialType.cs

@ -46,7 +46,17 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -46,7 +46,17 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// Type representing the C# 'dynamic' type.
/// </summary>
public readonly static SpecialType Dynamic = new SpecialType(TypeKind.Dynamic, "dynamic", isReferenceType: true);
/// <summary>
/// Type representing the C# 9 'nint' type.
/// </summary>
public readonly static SpecialType NInt = new SpecialType(TypeKind.NInt, "nint", isReferenceType: false);
/// <summary>
/// Type representing the C# 9 'nuint' type.
/// </summary>
public readonly static SpecialType NUInt = new SpecialType(TypeKind.NUInt, "nuint", isReferenceType: false);
/// <summary>
/// Type representing the result of the C# '__arglist()' expression.
/// </summary>

9
ICSharpCode.Decompiler/TypeSystem/TypeKind.cs

@ -94,5 +94,14 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -94,5 +94,14 @@ namespace ICSharpCode.Decompiler.TypeSystem
/// Modified type, with required modifier.
/// </summary>
ModReq,
/// <summary>
/// C# 9 <c>nint</c>
/// </summary>
NInt,
/// <summary>
/// C# 9 <c>nuint</c>
/// </summary>
NUInt,
}
}

37
ICSharpCode.Decompiler/TypeSystem/TypeUtils.cs

@ -36,6 +36,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -36,6 +36,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
case TypeKind.Pointer:
case TypeKind.ByReference:
case TypeKind.Class:
case TypeKind.NInt:
case TypeKind.NUInt:
return NativeIntSize;
case TypeKind.Enum:
type = type.GetEnumUnderlyingType();
@ -131,6 +133,22 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -131,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>
/// Gets whether the type is a C# primitive integer type: byte, sbyte, short, ushort, int, uint, long and ulong.
///
@ -243,6 +261,8 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -243,6 +261,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
case TypeKind.ByReference:
return StackType.Ref;
case TypeKind.Pointer:
case TypeKind.NInt:
case TypeKind.NUInt:
return StackType.I;
case TypeKind.TypeParameter:
// Type parameters are always considered StackType.O, even
@ -305,8 +325,13 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -305,8 +325,13 @@ namespace ICSharpCode.Decompiler.TypeSystem
public static Sign GetSign(this IType type)
{
type = type.SkipModifiers();
if (type.Kind == TypeKind.Pointer)
return Sign.Unsigned;
switch (type.Kind) {
case TypeKind.Pointer:
case TypeKind.NUInt:
return Sign.Unsigned;
case TypeKind.NInt:
return Sign.Signed;
}
var typeDef = type.GetEnumUnderlyingType().GetDefinition();
if (typeDef == null)
return Sign.None;
@ -375,8 +400,12 @@ namespace ICSharpCode.Decompiler.TypeSystem @@ -375,8 +400,12 @@ namespace ICSharpCode.Decompiler.TypeSystem
public static PrimitiveType ToPrimitiveType(this IType type)
{
type = type.SkipModifiers();
if (type.Kind == TypeKind.Unknown) return PrimitiveType.Unknown;
if (type.Kind == TypeKind.ByReference) return PrimitiveType.Ref;
switch (type.Kind) {
case TypeKind.Unknown: return PrimitiveType.Unknown;
case TypeKind.ByReference: return PrimitiveType.Ref;
case TypeKind.NInt: return PrimitiveType.I;
case TypeKind.NUInt: return PrimitiveType.U;
}
var def = type.GetEnumUnderlyingType().GetDefinition();
return def != null ? def.KnownTypeCode.ToPrimitiveType() : PrimitiveType.None;
}

4
ILSpy.Tests/ILSpy.Tests.csproj

@ -46,8 +46,8 @@ @@ -46,8 +46,8 @@
<ItemGroup>
<PackageReference Include="DiffLib" Version="2017.7.26.1241" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" Version="3.7.0-2.final" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.7.0-4.final" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" Version="3.7.0-4.final" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="System.Collections.Immutable" Version="1.7.1" />
<PackageReference Include="NUnit" Version="3.11.0" />

4
ILSpy/AssemblyListManager.cs

@ -119,10 +119,8 @@ namespace ICSharpCode.ILSpy @@ -119,10 +119,8 @@ namespace ICSharpCode.ILSpy
public bool DeleteList(string Name)
{
if (AssemblyLists.Contains(Name))
if (AssemblyLists.Remove(Name))
{
AssemblyLists.Remove(Name);
ILSpySettings.Update(
delegate(XElement root)
{

1
ILSpy/ILSpy.csproj

@ -58,6 +58,7 @@ @@ -58,6 +58,7 @@
<PackageReference Include="DataGridExtensions" Version="2.1.1" />
<PackageReference Include="System.Reflection.Metadata" Version="1.8.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.19" />
</ItemGroup>
<ItemGroup>

5
ILSpy/Languages/CSharpHighlightingTokenWriter.cs

@ -298,12 +298,15 @@ namespace ICSharpCode.ILSpy @@ -298,12 +298,15 @@ namespace ICSharpCode.ILSpy
case "ushort":
case "ulong":
case "unmanaged":
case "nint":
case "nuint":
color = valueTypeKeywordsColor;
break;
case "class":
case "object":
case "string":
case "void":
case "dynamic":
color = referenceTypeKeywordsColor;
break;
}
@ -327,7 +330,7 @@ namespace ICSharpCode.ILSpy @@ -327,7 +330,7 @@ namespace ICSharpCode.ILSpy
{
color = valueKeywordColor;
}
if ((identifier.Name == "dynamic" || identifier.Name == "var") && identifier.Parent is AstType)
if (identifier.Name == "var" && identifier.Parent is AstType)
color = queryKeywordsColor;
switch (GetCurrentDefinition()) {
case ITypeDefinition t:

1
ILSpy/Languages/CSharpLanguage.cs

@ -107,6 +107,7 @@ namespace ICSharpCode.ILSpy @@ -107,6 +107,7 @@ namespace ICSharpCode.ILSpy
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp7_2.ToString(), "C# 7.2 / VS 2017.4"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp7_3.ToString(), "C# 7.3 / VS 2017.7"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.CSharp8_0.ToString(), "C# 8.0 / VS 2019"),
new LanguageVersion(Decompiler.CSharp.LanguageVersion.Preview.ToString(), "C# 9.0 (experimental)"),
};
}
return versions;

22
ILSpy/Properties/Resources.Designer.cs generated

@ -406,6 +406,16 @@ namespace ICSharpCode.ILSpy.Properties { @@ -406,6 +406,16 @@ namespace ICSharpCode.ILSpy.Properties {
}
/// <summary>
/// Looks up a localized string similar to Base Types.
/// </summary>
public static string BaseTypes {
get {
return ResourceManager.GetString("BaseTypes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to C_lone.
/// 查找类似 C_lone 的本地化字符串。
/// </summary>
public static string C_lone {
@ -586,7 +596,7 @@ namespace ICSharpCode.ILSpy.Properties { @@ -586,7 +596,7 @@ namespace ICSharpCode.ILSpy.Properties {
}
/// <summary>
/// 查找类似 Allow extension &apos;Add&apos; methods in collection initializer expressions 的本地化字符串。
/// Looks up a localized string similar to Allow extension &apos;Add&apos; methods in collection initializer expressions.
/// </summary>
public static string DecompilerSettings_AllowExtensionAddMethodsInCollectionInitializerExpressions {
get {
@ -921,6 +931,16 @@ namespace ICSharpCode.ILSpy.Properties { @@ -921,6 +931,16 @@ namespace ICSharpCode.ILSpy.Properties {
}
/// <summary>
/// Looks up a localized string similar to Use nint/nuint types.
/// </summary>
public static string DecompilerSettings_NativeIntegers {
get {
return ResourceManager.GetString("DecompilerSettings.NativeIntegers", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Nullable reference types.
/// 查找类似 Nullable reference types 的本地化字符串。
/// </summary>
public static string DecompilerSettings_NullableReferenceTypes {

6
ILSpy/Properties/Resources.resx

@ -873,6 +873,12 @@ Do you want to continue?</value> @@ -873,6 +873,12 @@ Do you want to continue?</value>
<data name="DecompilerSettings.UseSdkStyleProjectFormat" xml:space="preserve">
<value>Use new SDK style format for generated project files (*.csproj)</value>
</data>
<data name="DecompilerSettings.NativeIntegers" xml:space="preserve">
<value>Use nint/nuint types</value>
</data>
<data name="BaseTypes" xml:space="preserve">
<value>Base Types</value>
</data>
<data name="DEBUGDumpPDBAsXML" xml:space="preserve">
<value>DEBUG -- Dump PDB as XML</value>
</data>

2
ILSpy/SessionSettings.cs

@ -78,7 +78,7 @@ namespace ICSharpCode.ILSpy @@ -78,7 +78,7 @@ namespace ICSharpCode.ILSpy
public string ActiveAssemblyList {
get => activeAssemblyList;
set {
if (value != activeAssemblyList) {
if (value != null && value != activeAssemblyList) {
activeAssemblyList = value;
OnPropertyChanged();
}

61
ILSpy/TreeNodes/BaseTypesEntryNode.cs

@ -16,12 +16,8 @@ @@ -16,12 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.TreeView;
@ -29,61 +25,20 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -29,61 +25,20 @@ namespace ICSharpCode.ILSpy.TreeNodes
{
sealed class BaseTypesEntryNode : ILSpyTreeNode, IMemberTreeNode
{
readonly PEFile module;
readonly EntityHandle handle;
readonly IType type;
readonly bool isInterface;
bool showExpander;
readonly ITypeDefinition type;
public BaseTypesEntryNode(PEFile module, EntityHandle handle, IType type, bool isInterface)
public BaseTypesEntryNode(ITypeDefinition type)
{
if (handle.IsNil)
throw new ArgumentNullException(nameof(handle));
this.module = module ?? throw new ArgumentNullException(nameof(module));
this.handle = handle;
this.type = type;
this.isInterface = isInterface;
this.LazyLoading = true;
TryResolve(module, handle, type);
}
ITypeDefinition TryResolve(PEFile module, EntityHandle handle, IType type, bool mayRetry = true)
{
DecompilerTypeSystem typeSystem = new DecompilerTypeSystem(module, module.GetAssemblyResolver(),
TypeSystemOptions.Default | TypeSystemOptions.Uncached);
var t = typeSystem.MainModule.ResolveEntity(handle) as ITypeDefinition;
if (t != null) {
showExpander = t.DirectBaseTypes.Any();
var other = t.ParentModule.PEFile.GetTypeSystemWithCurrentOptionsOrNull();
Debug.Assert(other != null);
t = other.FindType(t.FullTypeName).GetDefinition();
} else {
showExpander = mayRetry;
}
RaisePropertyChanged(nameof(Text));
RaisePropertyChanged(nameof(ShowExpander));
return t;
}
public override object Text => this.Language.TypeToString(type, includeNamespace: true);
public override bool ShowExpander => showExpander && base.ShowExpander;
public override object Text => this.Language.TypeToString(type, includeNamespace: true) + handle.ToSuffixString();
public override object Icon =>
isInterface ? Images.Interface : Images.Class;
protected override void LoadChildren()
{
var t = TryResolve(module, handle, type, false);
if (t != null) {
BaseTypesTreeNode.AddBaseTypes(this.Children, t.ParentModule.PEFile, t);
}
}
public override object Icon => type.Kind == TypeKind.Interface ? Images.Interface : Images.Class;
public override void ActivateItem(System.Windows.RoutedEventArgs e)
{
var t = TryResolve(module, handle, type, false);
e.Handled = ActivateItem(this, t);
e.Handled = ActivateItem(this, type);
}
internal static bool ActivateItem(SharpTreeNode node, ITypeDefinition def)
@ -103,10 +58,6 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -103,10 +58,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
language.WriteCommentLine(output, language.TypeToString(type, includeNamespace: true));
}
IEntity IMemberTreeNode.Member {
get {
return TryResolve(module, handle, type, false);
}
}
IEntity IMemberTreeNode.Member => type;
}
}

25
ILSpy/TreeNodes/BaseTypesTreeNode.cs

@ -20,6 +20,9 @@ using System; @@ -20,6 +20,9 @@ using System;
using System.Linq;
using System.Reflection.Metadata;
using System.Windows.Threading;
using Humanizer.Localisation;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
@ -42,7 +45,7 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -42,7 +45,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
this.LazyLoading = true;
}
public override object Text => "Base Types";
public override object Text => Properties.Resources.BaseTypes;
public override object Icon => Images.SuperTypes;
@ -53,19 +56,13 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -53,19 +56,13 @@ namespace ICSharpCode.ILSpy.TreeNodes
internal static void AddBaseTypes(SharpTreeNodeCollection children, PEFile module, ITypeDefinition typeDefinition)
{
var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeDefinition.MetadataToken);
var baseTypes = typeDefinition.DirectBaseTypes.ToArray();
int i = 0;
if (typeDefinition.Kind == TypeKind.Interface) {
i++;
} else if (!typeDef.BaseType.IsNil) {
children.Add(new BaseTypesEntryNode(module, typeDef.BaseType, baseTypes[i], false));
i++;
}
foreach (var h in typeDef.GetInterfaceImplementations()) {
var impl = module.Metadata.GetInterfaceImplementation(h);
children.Add(new BaseTypesEntryNode(module, impl.Interface, baseTypes[i], true));
i++;
TypeDefinitionHandle handle = (TypeDefinitionHandle)typeDefinition.MetadataToken;
DecompilerTypeSystem typeSystem = new DecompilerTypeSystem(module, module.GetAssemblyResolver(),
TypeSystemOptions.Default | TypeSystemOptions.Uncached);
var t = typeSystem.MainModule.ResolveEntity(handle) as ITypeDefinition;
foreach (var td in t.GetAllBaseTypeDefinitions().Reverse().Skip(1)) {
if (t.Kind != TypeKind.Interface || t.Kind == td.Kind)
children.Add(new BaseTypesEntryNode(td));
}
}

41
ILSpy/ViewModels/ManageAssemblyListsViewModel.cs

@ -49,6 +49,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -49,6 +49,7 @@ namespace ICSharpCode.ILSpy.ViewModels
ResetCommand = new DelegateCommand(ExecuteReset);
DeleteCommand = new DelegateCommand(ExecuteDelete, CanExecuteDelete);
CreatePreconfiguredAssemblyListCommand = new DelegateCommand<PreconfiguredAssemblyList>(ExecuteCreatePreconfiguredAssemblyList);
SelectAssemblyListCommand = new DelegateCommand(ExecuteSelectAssemblyList, CanExecuteSelectAssemblyList);
PreconfiguredAssemblyLists = new List<PreconfiguredAssemblyList>(ResolvePreconfiguredAssemblyLists());
}
@ -115,6 +116,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -115,6 +116,7 @@ namespace ICSharpCode.ILSpy.ViewModels
public ICommand RenameCommand { get; }
public ICommand DeleteCommand { get; }
public ICommand CreatePreconfiguredAssemblyListCommand { get; }
public ICommand SelectAssemblyListCommand { get; }
private void ExecuteNew()
{
@ -124,7 +126,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -124,7 +126,7 @@ namespace ICSharpCode.ILSpy.ViewModels
if (dlg.DialogResult == true) {
if (manager.AssemblyLists.Contains(dlg.ListName)) {
args.Cancel = true;
MessageBox.Show(Properties.Resources.ListExistsAlready, null, MessageBoxButton.OK);
MessageBox.Show(Resources.ListExistsAlready, null, MessageBoxButton.OK);
}
}
};
@ -146,7 +148,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -146,7 +148,7 @@ namespace ICSharpCode.ILSpy.ViewModels
if (dlg.DialogResult == true) {
if (manager.AssemblyLists.Contains(dlg.ListName)) {
args.Cancel = true;
MessageBox.Show(Properties.Resources.ListExistsAlready, null, MessageBoxButton.OK);
MessageBox.Show(Resources.ListExistsAlready, null, MessageBoxButton.OK);
}
}
};
@ -157,7 +159,7 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -157,7 +159,7 @@ namespace ICSharpCode.ILSpy.ViewModels
private void ExecuteReset()
{
if (MessageBox.Show(parent, Properties.Resources.ListsResetConfirmation,
if (MessageBox.Show(parent, Resources.ListsResetConfirmation,
"ILSpy", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No, MessageBoxOptions.None) != MessageBoxResult.Yes)
return;
manager.ClearAll();
@ -167,10 +169,19 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -167,10 +169,19 @@ namespace ICSharpCode.ILSpy.ViewModels
private void ExecuteDelete()
{
if (MessageBox.Show(parent, Properties.Resources.ListDeleteConfirmation,
if (MessageBox.Show(parent, Resources.ListDeleteConfirmation,
"ILSpy", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No, MessageBoxOptions.None) != MessageBoxResult.Yes)
return;
manager.DeleteList(SelectedAssemblyList);
string assemblyList = SelectedAssemblyList;
SelectedAssemblyList = null;
int index = manager.AssemblyLists.IndexOf(assemblyList);
manager.DeleteList(assemblyList);
if (manager.AssemblyLists.Count > 0) {
SelectedAssemblyList = manager.AssemblyLists[Math.Max(0, index - 1)];
if (MainWindow.Instance.sessionSettings.ActiveAssemblyList == assemblyList) {
MainWindow.Instance.sessionSettings.ActiveAssemblyList = SelectedAssemblyList;
}
}
}
private bool CanExecuteDelete()
@ -197,12 +208,17 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -197,12 +208,17 @@ namespace ICSharpCode.ILSpy.ViewModels
}
if (manager.AssemblyLists.Contains(dlg.ListName)) {
args.Cancel = true;
MessageBox.Show(Properties.Resources.ListExistsAlready, null, MessageBoxButton.OK);
MessageBox.Show(Resources.ListExistsAlready, null, MessageBoxButton.OK);
}
}
};
if (dlg.ShowDialog() == true) {
manager.RenameList(selectedAssemblyList, dlg.ListName);
string assemblyList = SelectedAssemblyList;
SelectedAssemblyList = dlg.ListName;
manager.RenameList(assemblyList, dlg.ListName);
if (MainWindow.Instance.sessionSettings.ActiveAssemblyList == assemblyList) {
MainWindow.Instance.sessionSettings.ActiveAssemblyList = manager.AssemblyLists[manager.AssemblyLists.Count - 1];
}
}
}
@ -323,6 +339,17 @@ namespace ICSharpCode.ILSpy.ViewModels @@ -323,6 +339,17 @@ namespace ICSharpCode.ILSpy.ViewModels
}
}
}
private bool CanExecuteSelectAssemblyList()
{
return SelectedAssemblyList != null;
}
private void ExecuteSelectAssemblyList()
{
MainWindow.Instance.sessionSettings.ActiveAssemblyList = SelectedAssemblyList;
this.parent.Close();
}
}
public class PreconfiguredAssemblyList

15
ILSpy/Views/ManageAssemblyListsDialog.xaml

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
Title="{x:Static properties:Resources.ManageAssemblyLists}"
Style="{DynamicResource DialogWindow}"
WindowStartupLocation="CenterOwner"
@ -35,7 +36,19 @@ @@ -35,7 +36,19 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListBox Name="listView" Margin="0 8" Grid.ColumnSpan="4" SelectedItem="{Binding SelectedAssemblyList}"
SelectionMode="Single" ItemsSource="{Binding AssemblyLists}" />
SelectionMode="Single" ItemsSource="{Binding AssemblyLists}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseDoubleClick">
<b:InvokeCommandAction Command="{Binding SelectAssemblyListCommand}" />
</b:EventTrigger>
<b:KeyTrigger Key="Return" FiredOn="KeyDown">
<b:InvokeCommandAction Command="{Binding SelectAssemblyListCommand}" />
</b:KeyTrigger>
<b:KeyTrigger Key="Delete" FiredOn="KeyDown">
<b:InvokeCommandAction Command="{Binding DeleteCommand}" CommandParameter="{Binding ., RelativeSource={RelativeSource AncestorType=Window}}" />
</b:KeyTrigger>
</b:Interaction.Triggers>
</ListBox>
<StackPanel Grid.Column="5" Grid.RowSpan="2" Margin="4, 8">
<Button Margin="2 2 2 10" Command="{Binding NewCommand}" CommandParameter="{Binding ., RelativeSource={RelativeSource AncestorType=Window}}" Content="{x:Static properties:Resources._New}"/>

Loading…
Cancel
Save