From a599aae54d19557e388971b4a56ff0444aeefed0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 4 Oct 2019 10:35:35 +0200 Subject: [PATCH 1/6] #1572: Do not generate variable names that match C# keywords. --- .../TestCases/Pretty/CompoundAssignmentTest.cs | 4 ++-- .../TestCases/Pretty/RefLocalsAndReturns.cs | 16 ++++++++-------- .../CSharp/OutputVisitor/CSharpOutputVisitor.cs | 9 ++++++--- .../IL/Transforms/AssignVariableNames.cs | 6 ++++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs index b0cb624b6..b133ac078 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/CompoundAssignmentTest.cs @@ -4943,8 +4943,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void Issue1779(int value) { - CustomStruct2 @struct = GetStruct(); - @struct.IntProp += value; + CustomStruct2 customStruct = GetStruct(); + customStruct.IntProp += value; } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs index 7a65ff10a..192503f48 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/RefLocalsAndReturns.cs @@ -182,11 +182,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty GetRef().Method(); // call on a copy, not the original ref: - NormalStruct @ref = GetRef(); - @ref.Method(); + NormalStruct normalStruct = GetRef(); + normalStruct.Method(); - ReadOnlyStruct ref2 = GetRef(); - ref2.Method(); + ReadOnlyStruct readOnlyStruct = GetRef(); + readOnlyStruct.Method(); } public void CallOnReadOnlyRefReturn() @@ -293,13 +293,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void RefReassignment(ref NormalStruct s) { - ref NormalStruct @ref = ref GetRef(); - RefReassignment(ref @ref); + ref NormalStruct reference = ref GetRef(); + RefReassignment(ref reference); if (s.GetHashCode() == 0) { - @ref = ref GetRef(); + reference = ref GetRef(); } - RefReassignment(ref @ref.GetHashCode() == 4 ? ref @ref : ref s); + RefReassignment(ref reference.GetHashCode() == 4 ? ref reference : ref s); } public static void Main(string[] args) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index a462ccb9f..a4f448d2d 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team +// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team // // 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 @@ -426,8 +426,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor /// /// Determines whether the specified identifier is a keyword in the given context. + /// If is all keywords are treated as unconditional. /// - public static bool IsKeyword(string identifier, AstNode context) + public static bool IsKeyword(string identifier, AstNode context = null) { // only 2-10 char lower-case identifiers can be keywords if (identifier.Length > maxKeywordLength || identifier.Length < 2 || identifier[0] < 'a') @@ -440,10 +441,12 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor } if (queryKeywords.Contains(identifier)) { - return context.Ancestors.Any(ancestor => ancestor is QueryExpression); + return context == null || context.Ancestors.Any(ancestor => ancestor is QueryExpression); } if (identifier == "await") { + if (context == null) + return true; foreach (AstNode ancestor in context.Ancestors) { // with lambdas/anonymous methods, diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index bee806143..c3ce39109 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -657,8 +657,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (name.Length == 0) return "obj"; - else - return char.ToLower(name[0]) + name.Substring(1); + string lowerCaseName = char.ToLower(name[0]) + name.Substring(1); + if (CSharp.OutputVisitor.CSharpOutputVisitor.IsKeyword(lowerCaseName)) + return null; + return lowerCaseName; } internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) From 03aecf047dcdb55ab7a8009ad37f0c1c77b6a8d0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Mar 2025 11:30:57 +0100 Subject: [PATCH 2/6] Add VariableScope and rework AssignVariableNames step to support renaming parameters of nested ILFunctions in the future. --- .../TestCases/Pretty/DelegateConstruction.cs | 6 +- .../CSharp/ExpressionBuilder.cs | 7 +- .../CSharp/StatementBuilder.cs | 10 + .../IL/ControlFlow/AsyncAwaitDecompiler.cs | 1 + .../IL/ControlFlow/YieldReturnDecompiler.cs | 1 + ICSharpCode.Decompiler/IL/ILReader.cs | 4 + .../IL/Instructions/ILFunction.cs | 5 + .../IL/Transforms/AssignVariableNames.cs | 684 +++++++++++------- .../Util/CollectionExtensions.cs | 2 +- 9 files changed, 438 insertions(+), 282 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index c87ca9a7d..fd76c9e23 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -377,10 +377,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction public static void NameConflict2(int j) { List> list = new List>(); - for (int k = 0; k < 10; k++) + for (int i = 0; i < 10; i++) { - list.Add(delegate (int i) { - Console.WriteLine(i); + list.Add(delegate (int k) { + Console.WriteLine(k); }); } } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 63737a2d4..e95134ed4 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2547,6 +2547,11 @@ namespace ICSharpCode.Decompiler.CSharp foreach (var parameter in parameters) { var pd = astBuilder.ConvertParameter(parameter); + if (variables.TryGetValue(i, out var v)) + { + pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type)); + pd.Name = v.Name; + } if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) @@ -2554,8 +2559,6 @@ namespace ICSharpCode.Decompiler.CSharp } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) pd.Type = null; - if (variables.TryGetValue(i, out var v)) - pd.AddAnnotation(new ILVariableResolveResult(v, parameters[i].Type)); yield return pd; i++; } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 094a51355..d3f2f5069 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -1389,6 +1389,16 @@ namespace ICSharpCode.Decompiler.CSharp var astBuilder = exprBuilder.astBuilder; var method = (MethodDeclaration)astBuilder.ConvertEntity(function.ReducedMethod); + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); + + foreach (var (i, p) in method.Parameters.WithIndex()) + { + if (variables.TryGetValue(i, out var v)) + { + p.Name = v.Name; + } + } + if (function.Method.HasBody) { var nestedBuilder = new StatementBuilder( diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs index 259367b2d..ff37d9a1e 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs @@ -1165,6 +1165,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow function.MoveNextMethod = moveNextFunction.Method; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.CodeSize = moveNextFunction.CodeSize; + function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength; function.IsIterator = IsAsyncEnumerator; moveNextFunction.Variables.Clear(); moveNextFunction.ReleaseRef(); diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index dbea7d4cf..c5ef712bc 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -656,6 +656,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow function.MoveNextMethod = moveNextFunction.Method; function.SequencePointCandidates = moveNextFunction.SequencePointCandidates; function.CodeSize = moveNextFunction.CodeSize; + function.LocalVariableSignatureLength = moveNextFunction.LocalVariableSignatureLength; // Copy-propagate temporaries holding a copy of 'this'. // This is necessary because the old (pre-Roslyn) C# compiler likes to store 'this' in temporary variables. diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index 2ec9da40a..226218e12 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -342,7 +342,10 @@ namespace ICSharpCode.Decompiler.IL if (index < 0) ilVar.Name = "this"; else if (string.IsNullOrWhiteSpace(name)) + { ilVar.Name = "P_" + index; + ilVar.HasGeneratedName = true; + } else ilVar.Name = name; return ilVar; @@ -706,6 +709,7 @@ namespace ICSharpCode.Decompiler.IL var function = new ILFunction(this.method, body.GetCodeSize(), this.genericContext, mainContainer, kind); function.Variables.AddRange(parameterVariables); function.Variables.AddRange(localVariables); + function.LocalVariableSignatureLength = localVariables.Length; Debug.Assert(stackVariables != null); function.Variables.AddRange(stackVariables); function.Variables.AddRange(variableByExceptionHandler.Values); diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index bbf0e6441..eb81ccbce 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -67,6 +67,11 @@ namespace ICSharpCode.Decompiler.IL /// public readonly ILVariableCollection Variables; + /// + /// Gets + /// + public int LocalVariableSignatureLength; + /// /// Gets the scope in which the local function is declared. /// Returns null, if this is not a local function. diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index c3ce39109..0d1acd261 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -25,6 +26,7 @@ using System.Reflection.Metadata; using Humanizer.Inflections; +using ICSharpCode.Decompiler.CSharp; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -32,7 +34,7 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { - public class AssignVariableNames : IILTransform + public class AssignVariableNames : ILVisitor, IILTransform { static readonly Dictionary typeNameToVariableNameDict = new Dictionary { { "System.Boolean", "flag" }, @@ -53,52 +55,83 @@ namespace ICSharpCode.Decompiler.IL.Transforms }; ILTransformContext context; - List currentLowerCaseTypeOrMemberNames; - Dictionary reservedVariableNames; - HashSet loopCounters; const char maxLoopVariableName = 'n'; - int numDisplayClassLocals; - public void Run(ILFunction function, ILTransformContext context) + public class VariableScope { - this.context = context; + readonly ILTransformContext context; + readonly VariableScope parentScope; + readonly ILFunction function; + readonly Dictionary localFunctions = new(); + readonly Dictionary variableMapping = new(ILVariableEqualityComparer.Instance); + readonly string[] assignedLocalSignatureIndices; + + IImmutableSet currentLowerCaseTypeOrMemberNames; + Dictionary reservedVariableNames; + HashSet loopCounters; + int numDisplayClassLocals; + + public VariableScope(ILFunction function, ILTransformContext context, VariableScope parentScope = null) + { + this.function = function; + this.context = context; + this.parentScope = parentScope; + + numDisplayClassLocals = 0; + assignedLocalSignatureIndices = new string[function.LocalVariableSignatureLength]; + reservedVariableNames = new Dictionary(); + + // find all loop counters in the current function + loopCounters = new HashSet(); + foreach (var inst in TreeTraversal.PreOrder((ILInstruction)function, i => i.Children)) + { + if (inst is ILFunction && inst != function) + break; + if (inst is BlockContainer { Kind: ContainerKind.For, Blocks: [.., var incrementBlock] }) + { + foreach (var i in incrementBlock.Instructions) + { + if (HighLevelLoopTransform.MatchIncrement(i, out var variable)) + loopCounters.Add(variable); + } + } + } - reservedVariableNames = new Dictionary(); - currentLowerCaseTypeOrMemberNames = new List(); - var currentLowerCaseMemberNames = CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition); - foreach (var name in currentLowerCaseMemberNames) - currentLowerCaseTypeOrMemberNames.Add(name); - var currentLowerCaseTypeNames = CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition); - foreach (var name in currentLowerCaseTypeNames) - { - currentLowerCaseTypeOrMemberNames.Add(name); - AddExistingName(reservedVariableNames, name); - } - loopCounters = CollectLoopCounters(function); - foreach (var f in function.Descendants.OfType()) - { - if (f.Method != null) + // if this is the root scope, we also collect all lower-case type and member names + // and fixed parameter names to avoid conflicts when naming local variables. + if (parentScope == null) { - if (IsSetOrEventAccessor(f.Method) && f.Method.Parameters.Count > 0) + var currentLowerCaseTypeOrMemberNames = new HashSet(StringComparer.Ordinal); + foreach (var name in CollectAllLowerCaseMemberNames(function.Method.DeclaringTypeDefinition)) + currentLowerCaseTypeOrMemberNames.Add(name); + foreach (var name in CollectAllLowerCaseTypeNames(function.Method.DeclaringTypeDefinition)) { - for (int i = 0; i < f.Method.Parameters.Count - 1; i++) + currentLowerCaseTypeOrMemberNames.Add(name); + AddExistingName(reservedVariableNames, name); + } + this.currentLowerCaseTypeOrMemberNames = currentLowerCaseTypeOrMemberNames.ToImmutableHashSet(); + + // handle implicit parameters of set or event accessors + if (function.Method != null && IsSetOrEventAccessor(function.Method) && function.Parameters.Count > 0) + { + for (int i = 0; i < function.Method.Parameters.Count - 1; i++) { - AddExistingName(reservedVariableNames, f.Method.Parameters[i].Name); + AddExistingName(reservedVariableNames, function.Method.Parameters[i].Name); } - var lastParameter = f.Method.Parameters.Last(); - switch (f.Method.AccessorOwner) + var lastParameter = function.Method.Parameters.Last(); + switch (function.Method.AccessorOwner) { case IProperty prop: - if (f.Method.AccessorKind == MethodSemanticsAttributes.Setter) + if (function.Method.AccessorKind == MethodSemanticsAttributes.Setter) { if (prop.Parameters.Any(p => p.Name == "value")) { - f.Warnings.Add("Parameter named \"value\" already present in property signature!"); + function.Warnings.Add("Parameter named \"value\" already present in property signature!"); break; } - var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f + var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function && v.Kind == VariableKind.Parameter - && v.Index == f.Method.Parameters.Count - 1); + && v.Index == function.Method.Parameters.Count - 1); if (variableForLastParameter == null) { AddExistingName(reservedVariableNames, lastParameter.Name); @@ -114,11 +147,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms } break; case IEvent ev: - if (f.Method.AccessorKind != MethodSemanticsAttributes.Raiser) + if (function.Method.AccessorKind != MethodSemanticsAttributes.Raiser) { - var variableForLastParameter = f.Variables.FirstOrDefault(v => v.Function == f + var variableForLastParameter = function.Variables.FirstOrDefault(v => v.Function == function && v.Kind == VariableKind.Parameter - && v.Index == f.Method.Parameters.Count - 1); + && v.Index == function.Method.Parameters.Count - 1); if (variableForLastParameter == null) { AddExistingName(reservedVariableNames, lastParameter.Name); @@ -140,183 +173,394 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else { - foreach (var p in f.Method.Parameters) - AddExistingName(reservedVariableNames, p.Name); + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0).ToDictionary(v => v.Index); + foreach (var (i, p) in function.Parameters.WithIndex()) + { + string name = p.Name; + if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList) + { + // needs to be consistent with logic in ILReader.CreateILVarable + name = "P_" + i; + } + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = name; + AddExistingName(reservedVariableNames, name); + } + } + + static bool IsSetOrEventAccessor(IMethod method) + { + switch (method.AccessorKind) + { + case MethodSemanticsAttributes.Setter: + case MethodSemanticsAttributes.Adder: + case MethodSemanticsAttributes.Remover: + return true; + default: + return false; + } } } else { - foreach (var p in f.Variables.Where(v => v.Kind == VariableKind.Parameter)) - AddExistingName(reservedVariableNames, p.Name); + this.currentLowerCaseTypeOrMemberNames = parentScope.currentLowerCaseTypeOrMemberNames; + var variables = function.Variables.Where(v => v.Kind == VariableKind.Parameter).ToDictionary(v => v.Index); + + foreach (var (i, p) in function.Parameters.WithIndex()) + { + if (function.Kind is ILFunctionKind.Delegate or ILFunctionKind.ExpressionTree + && CSharpDecompiler.IsTransparentIdentifier(p.Name)) + { + AddExistingName(reservedVariableNames, p.Name); + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = p.Name; + } + if (!parentScope.IsReservedVariableName(p.Name, out _)) + { + AddExistingName(reservedVariableNames, p.Name); + if (variables.TryGetValue(i, out var v)) + variableMapping[v] = p.Name; + } + } } } - numDisplayClassLocals = 0; - PerformAssignment(function); - } - static IEnumerable CollectAllLowerCaseMemberNames(ITypeDefinition type) - { - foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) - yield return item.Name; - } + public void Add(MethodDefinitionHandle localFunction, string name) + { + this.localFunctions[localFunction] = name; + } - static IEnumerable CollectAllLowerCaseTypeNames(ITypeDefinition type) - { - var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace); - foreach (var item in ns.Types) + public string TryGetExistingName(MethodDefinitionHandle localFunction) { - if (IsLowerCase(item.Name)) - yield return item.Name; + if (localFunctions.TryGetValue(localFunction, out var name)) + return name; + return parentScope?.TryGetExistingName(localFunction); } - } - static bool IsLowerCase(string name) - { - return name.Length > 0 && char.ToLower(name[0]) == name[0]; - } + public string TryGetExistingName(ILVariable v) + { + if (variableMapping.TryGetValue(v, out var name)) + return name; + return parentScope?.TryGetExistingName(v); + } - bool IsSetOrEventAccessor(IMethod method) - { - switch (method.AccessorKind) + public string TryGetExistingName(ILFunction function, int index) { - case MethodSemanticsAttributes.Setter: - case MethodSemanticsAttributes.Adder: - case MethodSemanticsAttributes.Remover: + if (this.function == function) + { + return this.assignedLocalSignatureIndices[index]; + } + else + { + return parentScope?.TryGetExistingName(function, index); + } + } + + public void AssignNameToLocalSignatureIndex(ILFunction function, int index, string name) + { + var scope = this; + while (scope != null && scope.function != function) + scope = scope.parentScope; + Debug.Assert(scope != null); + scope.assignedLocalSignatureIndices[index] = name; + } + + public bool IsReservedVariableName(string name, out int index) + { + if (reservedVariableNames.TryGetValue(name, out index)) return true; - default: - return false; + return parentScope?.IsReservedVariableName(name, out index) ?? false; } - } - void PerformAssignment(ILFunction function) - { - var localFunctionMapping = new Dictionary(); - var variableMapping = new Dictionary(ILVariableEqualityComparer.Instance); - var assignedLocalSignatureIndices = new Dictionary<(ILFunction, int), string>(); + public void ReserveVariableName(string name, int index = 1) + { + reservedVariableNames[name] = index; + } - foreach (var inst in function.Descendants) + public string NextDisplayClassLocal() { - if (inst is ILFunction { Kind: ILFunctionKind.LocalFunction } localFunction) + return parentScope?.NextDisplayClassLocal() ?? "CS$<>8__locals" + (numDisplayClassLocals++); + } + + public bool IsLoopCounter(ILVariable v) + { + return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true); + } + + public string AssignName(ILVariable v) + { + // variable has no valid name + string newName = v.Name; + if (v.HasGeneratedName || !IsValidName(newName)) + { + // don't use the name from the debug symbols if it looks like a generated name + // generate a new one based on how the variable is used + newName = GenerateNameForVariable(v); + } + // use the existing name and update index appended to future conflicts + string nameWithoutNumber = SplitName(newName, out int newIndex); + if (IsReservedVariableName(nameWithoutNumber, out int lastUsedIndex)) { - // assign names to local functions - if (!LocalFunctionDecompiler.ParseLocalFunctionName(localFunction.Name, out _, out var newName) || !IsValidName(newName)) - newName = null; - if (newName == null) + if (v.Type.IsKnownType(KnownTypeCode.Int32) && IsLoopCounter(v)) { - string nameWithoutNumber = "f"; - if (!reservedVariableNames.TryGetValue(nameWithoutNumber, out int currentIndex)) + // special case for loop counters, + // we don't want them to be named i, i2, ..., but i, j, ... + newName = GenerateNameForVariable(v); + nameWithoutNumber = newName; + newIndex = 1; + } + } + if (IsReservedVariableName(nameWithoutNumber, out lastUsedIndex)) + { + // name without number was already used + if (newIndex > lastUsedIndex) + { + // new index is larger than last, so we can use it + } + else + { + // new index is smaller or equal, so we use the next value + newIndex = lastUsedIndex + 1; + } + // resolve conflicts by appending the index to the new name: + newName = nameWithoutNumber + newIndex.ToString(); + } + // update the last used index + ReserveVariableName(nameWithoutNumber, newIndex); + variableMapping.Add(v, newName); + return newName; + } + + string GenerateNameForVariable(ILVariable variable) + { + string proposedName = null; + if (variable.Type.IsKnownType(KnownTypeCode.Int32)) + { + // test whether the variable might be a loop counter + if (loopCounters.Contains(variable)) + { + // For loop variables, use i,j,k,l,m,n + for (char c = 'i'; c <= maxLoopVariableName; c++) + { + if (!IsReservedVariableName(c.ToString(), out _)) + { + proposedName = c.ToString(); + break; + } + } + } + } + // The ComponentResourceManager inside InitializeComponent must be named "resources", + // otherwise the WinForms designer won't load the Form. + if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager") + { + proposedName = "resources"; + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForAddress = variable.AddressInstructions.OfType() + .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null) + .Where(arg => !string.IsNullOrWhiteSpace(arg)) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForAddress.Count > 0) + { + proposedName = proposedNameForAddress[0]; + } + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForStores = new HashSet(); + foreach (var store in variable.StoreInstructions) + { + if (store is StLoc stloc) { - currentIndex = 1; + var name = GetNameFromInstruction(stloc.Value); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } - int count = Math.Max(1, currentIndex) + 1; - reservedVariableNames[nameWithoutNumber] = count; - if (count > 1) + else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot) { - newName = nameWithoutNumber + count.ToString(); + var name = GetNameFromInstruction(match.TestedOperand); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } - else + else if (store is PinnedRegion pinnedRegion) { - newName = nameWithoutNumber; + var name = GetNameFromInstruction(pinnedRegion.Init); + if (!currentLowerCaseTypeOrMemberNames.Contains(name)) + proposedNameForStores.Add(name); } } - localFunction.Name = newName; - localFunction.ReducedMethod.Name = newName; - localFunctionMapping[(MethodDefinitionHandle)localFunction.ReducedMethod.MetadataToken] = newName; - } - else if (inst is IInstructionWithVariableOperand i) - { - var v = i.Variable; - // if there is already a valid name for the variable slot, just use it - if (variableMapping.TryGetValue(v, out string name)) + if (proposedNameForStores.Count == 1) { - v.Name = name; - continue; + proposedName = proposedNameForStores.Single(); } - switch (v.Kind) + } + if (string.IsNullOrEmpty(proposedName)) + { + var proposedNameForLoads = variable.LoadInstructions + .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex)) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForLoads.Count == 1) { - case VariableKind.Parameter: - // Parameter names are handled in ILReader.CreateILVariable - // and CSharpDecompiler.FixParameterNames - break; - case VariableKind.InitializerTarget: // keep generated names - AddExistingName(reservedVariableNames, v.Name); - break; - case VariableKind.DisplayClassLocal: - v.Name = "CS$<>8__locals" + (numDisplayClassLocals++); - break; - case VariableKind.Local when v.Index != null: - if (assignedLocalSignatureIndices.TryGetValue((v.Function, v.Index.Value), out name)) - { - // make sure all local ILVariables that refer to the same slot in the locals signature - // are assigned the same name. - v.Name = name; - } - else - { - v.Name = AssignName(v, variableMapping); - // Remember the newly assigned name: - assignedLocalSignatureIndices.Add((v.Function, v.Index.Value), v.Name); - } - break; - default: - v.Name = AssignName(v, variableMapping); - break; + proposedName = proposedNameForLoads[0]; } } - else if (inst is (Call or LdFtn) and IInstructionWithMethodOperand m) + if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) { - // update references to local functions - if (m.Method is LocalFunctionMethod lf - && localFunctionMapping.TryGetValue((MethodDefinitionHandle)lf.MetadataToken, out var name)) + var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType() + .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context))) + .Except(currentLowerCaseTypeOrMemberNames).ToList(); + if (proposedNameForStoresFromNewObj.Count == 1) { - lf.Name = name; + proposedName = proposedNameForStoresFromNewObj[0]; } } + if (string.IsNullOrEmpty(proposedName)) + { + proposedName = GetNameByType(variable.Type); + } + + // for generated names remove number-suffixes + return SplitName(proposedName, out _); } } - string AssignName(ILVariable v, Dictionary variableMapping) + public void Run(ILFunction function, ILTransformContext context) + { + this.context = context; + function.AcceptVisitor(this, null); + } + + protected override Unit Default(ILInstruction inst, VariableScope context) { - // variable has no valid name - string newName = v.Name; - if (v.HasGeneratedName || !IsValidName(newName)) + foreach (var child in inst.Children) { - // don't use the name from the debug symbols if it looks like a generated name - // generate a new one based on how the variable is used - newName = GenerateNameForVariable(v); + child.AcceptVisitor(this, context); } - // use the existing name and update index appended to future conflicts - string nameWithoutNumber = SplitName(newName, out int newIndex); - if (reservedVariableNames.TryGetValue(nameWithoutNumber, out int lastUsedIndex)) + + if (inst is not IInstructionWithVariableOperand { Variable: var v }) + return default; + + // if there is already a valid name for the variable slot, just use it + string name = context.TryGetExistingName(v); + if (!string.IsNullOrEmpty(name)) { - if (v.Type.IsKnownType(KnownTypeCode.Int32) && loopCounters.Contains(v)) - { - // special case for loop counters, - // we don't want them to be named i, i2, ..., but i, j, ... - newName = GenerateNameForVariable(v); - nameWithoutNumber = newName; - newIndex = 1; - } + v.Name = name; + return default; } - if (reservedVariableNames.TryGetValue(nameWithoutNumber, out lastUsedIndex)) + + switch (v.Kind) { - // name without number was already used - if (newIndex > lastUsedIndex) - { - // new index is larger than last, so we can use it - } - else + case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: + // Parameter names of top-level functions are handled in ILReader.CreateILVariable + // and CSharpDecompiler.FixParameterNames + break; + case VariableKind.InitializerTarget: // keep generated names + case VariableKind.NamedArgument: + context.ReserveVariableName(v.Name); + break; + case VariableKind.DisplayClassLocal: + v.Name = context.NextDisplayClassLocal(); + break; + case VariableKind.Local when v.Index != null: + name = context.TryGetExistingName(v.Function, v.Index.Value); + if (name != null) + { + // make sure all local ILVariables that refer to the same slot in the locals signature + // are assigned the same name. + v.Name = name; + } + else + { + v.Name = context.AssignName(v); + context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); + } + break; + default: + v.Name = context.AssignName(v); + break; + } + + return default; + } + + protected internal override Unit VisitILFunction(ILFunction function, VariableScope context) + { + if (function.Kind == ILFunctionKind.LocalFunction) + { + // assign names to local functions + if (!LocalFunctionDecompiler.ParseLocalFunctionName(function.Name, out _, out var newName) || !IsValidName(newName)) + newName = null; + if (newName == null) { - // new index is smaller or equal, so we use the next value - newIndex = lastUsedIndex + 1; + string nameWithoutNumber = "f"; + if (!context.IsReservedVariableName(nameWithoutNumber, out int currentIndex)) + { + currentIndex = 1; + } + int count = Math.Max(1, currentIndex) + 1; + context.ReserveVariableName(nameWithoutNumber, count); + if (count > 1) + { + newName = nameWithoutNumber + count.ToString(); + } + else + { + newName = nameWithoutNumber; + } } - // resolve conflicts by appending the index to the new name: - newName = nameWithoutNumber + newIndex.ToString(); + function.Name = newName; + function.ReducedMethod.Name = newName; + context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName); + } + + return base.VisitILFunction(function, new VariableScope(function, this.context, context)); + } + + protected internal override Unit VisitCall(Call inst, VariableScope context) + { + if (inst.Method is LocalFunctionMethod m) + { + string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken); + if (!string.IsNullOrEmpty(name)) + m.Name = name; } - // update the last used index - reservedVariableNames[nameWithoutNumber] = newIndex; - variableMapping.Add(v, newName); - return newName; + + return base.VisitCall(inst, context); + } + + protected internal override Unit VisitLdFtn(LdFtn inst, VariableScope context) + { + if (inst.Method is LocalFunctionMethod m) + { + string name = context.TryGetExistingName((MethodDefinitionHandle)m.MetadataToken); + if (!string.IsNullOrEmpty(name)) + m.Name = name; + } + + return base.VisitLdFtn(inst, context); + } + + static IEnumerable CollectAllLowerCaseMemberNames(ITypeDefinition type) + { + foreach (var item in type.GetMembers(m => IsLowerCase(m.Name))) + yield return item.Name; + } + + static IEnumerable CollectAllLowerCaseTypeNames(ITypeDefinition type) + { + var ns = type.ParentModule.Compilation.GetNamespaceByFullName(type.Namespace); + foreach (var item in ns.Types) + { + if (IsLowerCase(item.Name)) + yield return item.Name; + } + } + + static bool IsLowerCase(string name) + { + return name.Length > 0 && char.ToLower(name[0]) == name[0]; } /// @@ -351,118 +595,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - HashSet CollectLoopCounters(ILFunction function) - { - var loopCounters = new HashSet(); - - foreach (BlockContainer possibleLoop in function.Descendants.OfType()) - { - if (possibleLoop.Kind != ContainerKind.For) - continue; - foreach (var inst in possibleLoop.Blocks.Last().Instructions) - { - if (HighLevelLoopTransform.MatchIncrement(inst, out var variable)) - loopCounters.Add(variable); - } - } - - return loopCounters; - } - - string GenerateNameForVariable(ILVariable variable) - { - string proposedName = null; - if (variable.Type.IsKnownType(KnownTypeCode.Int32)) - { - // test whether the variable might be a loop counter - if (loopCounters.Contains(variable)) - { - // For loop variables, use i,j,k,l,m,n - for (char c = 'i'; c <= maxLoopVariableName; c++) - { - if (!reservedVariableNames.ContainsKey(c.ToString())) - { - proposedName = c.ToString(); - break; - } - } - } - } - // The ComponentResourceManager inside InitializeComponent must be named "resources", - // otherwise the WinForms designer won't load the Form. - if (CSharp.CSharpDecompiler.IsWindowsFormsInitializeComponentMethod(context.Function.Method) && variable.Type.FullName == "System.ComponentModel.ComponentResourceManager") - { - proposedName = "resources"; - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForAddress = variable.AddressInstructions.OfType() - .Select(arg => arg.Parent is CallInstruction c ? c.GetParameter(arg.ChildIndex)?.Name : null) - .Where(arg => !string.IsNullOrWhiteSpace(arg)) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForAddress.Count > 0) - { - proposedName = proposedNameForAddress[0]; - } - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForStores = new HashSet(); - foreach (var store in variable.StoreInstructions) - { - if (store is StLoc stloc) - { - var name = GetNameFromInstruction(stloc.Value); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - else if (store is MatchInstruction match && match.SlotInfo == MatchInstruction.SubPatternsSlot) - { - var name = GetNameFromInstruction(match.TestedOperand); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - else if (store is PinnedRegion pinnedRegion) - { - var name = GetNameFromInstruction(pinnedRegion.Init); - if (!currentLowerCaseTypeOrMemberNames.Contains(name)) - proposedNameForStores.Add(name); - } - } - if (proposedNameForStores.Count == 1) - { - proposedName = proposedNameForStores.Single(); - } - } - if (string.IsNullOrEmpty(proposedName)) - { - var proposedNameForLoads = variable.LoadInstructions - .Select(arg => GetNameForArgument(arg.Parent, arg.ChildIndex)) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForLoads.Count == 1) - { - proposedName = proposedNameForLoads[0]; - } - } - if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) - { - var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType() - .Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context))) - .Except(currentLowerCaseTypeOrMemberNames).ToList(); - if (proposedNameForStoresFromNewObj.Count == 1) - { - proposedName = proposedNameForStoresFromNewObj[0]; - } - } - if (string.IsNullOrEmpty(proposedName)) - { - proposedName = GetNameByType(variable.Type); - } - - // for generated names remove number-suffixes - return SplitName(proposedName, out _); - } - static string GetNameFromInstruction(ILInstruction inst) { switch (inst) @@ -663,7 +795,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return lowerCaseName; } - internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) + static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context) { if (!variableType.IsKnownType(KnownTypeCode.Object)) return variableType; diff --git a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs index 0d5febe9c..a88ac4376 100644 --- a/ICSharpCode.Decompiler/Util/CollectionExtensions.cs +++ b/ICSharpCode.Decompiler/Util/CollectionExtensions.cs @@ -225,7 +225,7 @@ namespace ICSharpCode.Decompiler.Util yield return func(index++, element); } - public static IEnumerable<(int, T)> WithIndex(this ICollection source) + public static IEnumerable<(int, T)> WithIndex(this IEnumerable source) { int index = 0; foreach (var item in source) From 0481c7d1ee05f7b6008cc00564cc603c099ce3de Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Mar 2025 11:33:28 +0100 Subject: [PATCH 3/6] Improve LINQ decompiler to support combining lambda parameter names if they syntactically refer to the same range variable --- .../Transforms/CombineQueryExpressions.cs | 32 ++++++++----------- .../Transforms/IntroduceQueryExpressions.cs | 21 ++++++++++-- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs index ad92d1a27..55d6dec35 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/CombineQueryExpressions.cs @@ -16,12 +16,12 @@ // 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 ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -54,12 +54,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms next = child.NextSibling; CombineQueries(child, fromOrLetIdentifiers); } - QueryExpression query = node as QueryExpression; - if (query != null) + if (node is QueryExpression query) { QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); - QueryExpression innerQuery = fromClause.Expression as QueryExpression; - if (innerQuery != null) + if (fromClause.Expression is QueryExpression innerQuery) { if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery, fromOrLetIdentifiers)) { @@ -165,21 +163,17 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { RemoveTransparentIdentifierReferences(child, fromOrLetIdentifiers); } - MemberReferenceExpression mre = node as MemberReferenceExpression; - if (mre != null) + if (node is MemberReferenceExpression mre && mre.Target is IdentifierExpression ident + && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier)) { - IdentifierExpression ident = mre.Target as IdentifierExpression; - if (ident != null && CSharpDecompiler.IsTransparentIdentifier(ident.Identifier)) - { - IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); - mre.TypeArguments.MoveTo(newIdent.TypeArguments); - newIdent.CopyAnnotationsFrom(mre); - newIdent.RemoveAnnotations(); // remove the reference to the property of the anonymous type - if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation)) - newIdent.AddAnnotation(annotation); - mre.ReplaceWith(newIdent); - return; - } + IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); + mre.TypeArguments.MoveTo(newIdent.TypeArguments); + newIdent.CopyAnnotationsFrom(mre); + newIdent.RemoveAnnotations(); // remove the reference to the property of the anonymous type + if (fromOrLetIdentifiers.TryGetValue(mre.MemberName, out var annotation)) + newIdent.AddAnnotation(annotation); + mre.ReplaceWith(newIdent); + return; } } } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs index 8e5e55368..1590b2ab3 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceQueryExpressions.cs @@ -17,10 +17,10 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; +using ICSharpCode.Decompiler.IL; namespace ICSharpCode.Decompiler.CSharp.Transforms { @@ -54,13 +54,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms while (IsDegenerateQuery(innerQuery)) { QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); - if (fromClause.Identifier != innerFromClause.Identifier) - break; + ILVariable innerVariable = innerFromClause.Annotation()?.Variable; + ILVariable rangeVariable = fromClause.Annotation()?.Variable; // Replace the fromClause with all clauses from the inner query fromClause.Remove(); QueryClause insertionPos = null; foreach (var clause in innerQuery.Clauses) { + CombineRangeVariables(clause, innerVariable, rangeVariable); query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); } fromClause = innerFromClause; @@ -69,6 +70,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } + private void CombineRangeVariables(QueryClause clause, ILVariable oldVariable, ILVariable newVariable) + { + foreach (var identifier in clause.DescendantNodes().OfType()) + { + var variable = identifier.Parent.Annotation()?.Variable; + if (variable == oldVariable) + { + identifier.Parent.RemoveAnnotations(); + identifier.Parent.AddAnnotation(new ILVariableResolveResult(newVariable)); + identifier.ReplaceWith(Identifier.Create(newVariable.Name)); + } + } + } + bool IsDegenerateQuery(QueryExpression query) { if (query == null) From ffcd468d22f492bbdcc4c4045a2a74df4a7bf308 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Mar 2025 21:41:20 +0100 Subject: [PATCH 4/6] Fix #1572: parameters of lambdas and local functions are renamed, if there are with names from outer scopes collisions. --- .../PrettyTestRunner.cs | 56 ++++----- .../TestCases/ILPretty/GuessAccessors.cs | 4 +- .../TestCases/Pretty/DelegateConstruction.cs | 33 ++++++ .../TestCases/VBPretty/Async.cs | 12 +- .../CSharp/CSharpDecompiler.cs | 2 +- .../CSharp/ExpressionBuilder.cs | 2 +- .../IL/Transforms/AssignVariableNames.cs | 112 +++++++++++------- 7 files changed, 143 insertions(+), 78 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index f4ff7c914..cfe0c420b 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -233,22 +233,21 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { - NullPropagation = false, + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.NullPropagation = false; // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - FileScopedNamespaces = false, + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.FileScopedNamespaces = false; }); } [Test] public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { // legacy csc generates a dead store in debug builds - RemoveDeadStores = (cscOptions == CompilerOptions.None), - SwitchExpressions = false, - FileScopedNamespaces = false, + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.SwitchExpressions = false; }); } @@ -267,7 +266,10 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.QueryExpressions = false; + settings.NullableReferenceTypes = false; + }); } [Test] @@ -293,9 +295,9 @@ namespace ICSharpCode.Decompiler.Tests { await RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { - UseEnhancedUsing = false, - FileScopedNamespaces = false, + configureDecompiler: settings => { + settings.UseEnhancedUsing = false; + settings.FileScopedNamespaces = false; } ); } @@ -327,11 +329,11 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) { - DecompilerSettings settings = Tester.GetSettings(cscOptions); - // legacy csc generates a dead store in debug builds - settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); - settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; + }); } [Test] @@ -440,9 +442,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { - var settings = Tester.GetSettings(cscOptions); - settings.UseDebugSymbols = false; - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: settings); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false); } [Test] @@ -474,7 +474,7 @@ namespace ICSharpCode.Decompiler.Tests { await RunForLibrary( cscOptions: cscOptions, - decompilerSettings: new DecompilerSettings { UseEnhancedUsing = false, FileScopedNamespaces = false } + configureDecompiler: settings => { settings.UseEnhancedUsing = false; } ); } @@ -499,7 +499,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings()); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true); } [Test] @@ -601,7 +601,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6)); + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6)); } [Test] @@ -712,12 +712,12 @@ namespace ICSharpCode.Decompiler.Tests await RunForLibrary(cscOptions: cscOptions); } - async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) + async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action configureDecompiler = null) { - await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); + await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler); } - async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) + async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action configureDecompiler = null) { var csFile = Path.Combine(TestCasePath, testName + ".cs"); var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe"); @@ -739,7 +739,9 @@ namespace ICSharpCode.Decompiler.Tests } // 2. Decompile - var decompiled = await Tester.DecompileCSharp(exeFile, decompilerSettings ?? Tester.GetSettings(cscOptions)).ConfigureAwait(false); + var settings = Tester.GetSettings(cscOptions); + configureDecompiler?.Invoke(settings); + var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false); // 3. Compile CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray()); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs index c9e0f56e0..423ad4119 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs @@ -15,8 +15,8 @@ namespace ClassLibrary1 //IL_0007: Expected O, but got Unknown UnknownClass val = new UnknownClass(); int? unknownProperty = val.UnknownProperty; - int? num2 = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); - int? num3 = num2; + int? num = (val.UnknownProperty = unknownProperty.GetValueOrDefault()); + int? num3 = num; List list = new List { val[unknownProperty.Value] ?? "", val.NotProperty, diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs index fd76c9e23..d7566dced 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/DelegateConstruction.cs @@ -19,15 +19,33 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; #if CS100 using System.Threading.Tasks; + #endif namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { public static class DelegateConstruction { + internal class Dummy + { + public int baz; + + public List more; + } + + [CompilerGenerated] + internal class Helper + { + internal bool HelpMe(Dummy dum) + { + return true; + } + } + private class InstanceTests { public struct SomeData @@ -643,6 +661,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.DelegateConstruction { del(x); } + + public void Issue1572(DelegateConstruction.Dummy dum) + { +#if EXPECTED_OUTPUT + DelegateConstruction.Helper CS_0024_003C_003E8__locals0 = new DelegateConstruction.Helper(); + DelegateConstruction.Dummy dummy = dum.more.Where((DelegateConstruction.Dummy dummy2) => true).Where((DelegateConstruction.Dummy dummy2) => true).FirstOrDefault(); + Console.WriteLine(); + dummy.baz++; +#else + DelegateConstruction.Helper h = new DelegateConstruction.Helper(); + DelegateConstruction.Dummy localDummy = dum.more.Where(h.HelpMe).Where(h.HelpMe).FirstOrDefault(); + Console.WriteLine(); + localDummy.baz++; +#endif + } } [AttributeUsage(AttributeTargets.All)] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs index 6cb502dd0..7c2c76025 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Async.cs @@ -40,9 +40,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty public async void AwaitDefaultYieldAwaitable() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - YieldAwaitable yieldAwaitable = default(YieldAwaitable); - YieldAwaitable yieldAwaitable2 = yieldAwaitable; - await yieldAwaitable2; + YieldAwaitable yieldAwaitable2 = default(YieldAwaitable); + YieldAwaitable yieldAwaitable = yieldAwaitable2; + await yieldAwaitable; #else await default(YieldAwaitable); #endif @@ -51,9 +51,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty public async void AwaitDefaultHopToThreadPool() { #if LEGACY_VBC || (OPTIMIZE && !ROSLYN4) - HopToThreadPoolAwaitable hopToThreadPoolAwaitable = default(HopToThreadPoolAwaitable); - HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = hopToThreadPoolAwaitable; - await hopToThreadPoolAwaitable2; + HopToThreadPoolAwaitable hopToThreadPoolAwaitable2 = default(HopToThreadPoolAwaitable); + HopToThreadPoolAwaitable hopToThreadPoolAwaitable = hopToThreadPoolAwaitable2; + await hopToThreadPoolAwaitable; #else await default(HopToThreadPoolAwaitable); #endif diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 86f6ed66e..b9559dba1 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1282,7 +1282,7 @@ namespace ICSharpCode.Decompiler.CSharp { if (string.IsNullOrWhiteSpace(parameter.Name) && !parameter.Type.IsArgList()) { - // needs to be consistent with logic in ILReader.CreateILVarable + // needs to be consistent with logic in ILReader.CreateILVariable parameter.Name = "P_" + i; } i++; diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e95134ed4..90df1890a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2554,7 +2554,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (string.IsNullOrEmpty(pd.Name) && !pd.Type.IsArgList()) { - // needs to be consistent with logic in ILReader.CreateILVarable(ParameterDefinition) + // needs to be consistent with logic in ILReader.CreateILVariable pd.Name = "P_" + i; } if (settings.AnonymousTypes && parameter.Type.ContainsAnonymousType()) diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 0d1acd261..24f7079fc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -179,7 +179,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms string name = p.Name; if (string.IsNullOrWhiteSpace(name) && p.Type != SpecialType.ArgList) { - // needs to be consistent with logic in ILReader.CreateILVarable + // needs to be consistent with logic in ILReader.CreateILVariable name = "P_" + i; } if (variables.TryGetValue(i, out var v)) @@ -221,6 +221,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variables.TryGetValue(i, out var v)) variableMapping[v] = p.Name; } + else if (variables.TryGetValue(i, out var v)) + { + v.HasGeneratedName = true; + } } } } @@ -287,6 +291,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms return loopCounters.Contains(v) || (parentScope?.IsLoopCounter(v) == true); } + public string AssignNameIfUnassigned(ILVariable v) + { + if (variableMapping.TryGetValue(v, out var name)) + return name; + return AssignName(v); + } + public string AssignName(ILVariable v) { // variable has no valid name @@ -432,57 +443,65 @@ namespace ICSharpCode.Decompiler.IL.Transforms function.AcceptVisitor(this, null); } - protected override Unit Default(ILInstruction inst, VariableScope context) + Unit VisitChildren(ILInstruction inst, VariableScope context) { foreach (var child in inst.Children) { child.AcceptVisitor(this, context); } - if (inst is not IInstructionWithVariableOperand { Variable: var v }) - return default; + return default; + } - // if there is already a valid name for the variable slot, just use it - string name = context.TryGetExistingName(v); - if (!string.IsNullOrEmpty(name)) + protected override Unit Default(ILInstruction inst, VariableScope context) + { + if (inst is IInstructionWithVariableOperand { Variable: var v }) { - v.Name = name; - return default; - } + // if there is already a valid name for the variable slot, just use it + string name = context.TryGetExistingName(v); + if (!string.IsNullOrEmpty(name)) + { + v.Name = name; + return VisitChildren(inst, context); + } - switch (v.Kind) - { - case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: - // Parameter names of top-level functions are handled in ILReader.CreateILVariable - // and CSharpDecompiler.FixParameterNames - break; - case VariableKind.InitializerTarget: // keep generated names - case VariableKind.NamedArgument: - context.ReserveVariableName(v.Name); - break; - case VariableKind.DisplayClassLocal: - v.Name = context.NextDisplayClassLocal(); - break; - case VariableKind.Local when v.Index != null: - name = context.TryGetExistingName(v.Function, v.Index.Value); - if (name != null) - { - // make sure all local ILVariables that refer to the same slot in the locals signature - // are assigned the same name. - v.Name = name; - } - else - { + switch (v.Kind) + { + case VariableKind.Parameter when !v.HasGeneratedName && v.Function.Kind == ILFunctionKind.TopLevelFunction: + // Parameter names of top-level functions are handled in ILReader.CreateILVariable + // and CSharpDecompiler.FixParameterNames + break; + case VariableKind.InitializerTarget: // keep generated names + case VariableKind.NamedArgument: + context.ReserveVariableName(v.Name); + break; + case VariableKind.UsingLocal when v.AddressCount == 0 && v.LoadCount == 0: + // using variables that are not read, will not be declared in source source + break; + case VariableKind.DisplayClassLocal: + v.Name = context.NextDisplayClassLocal(); + break; + case VariableKind.Local when v.Index != null: + name = context.TryGetExistingName(v.Function, v.Index.Value); + if (name != null) + { + // make sure all local ILVariables that refer to the same slot in the locals signature + // are assigned the same name. + v.Name = name; + } + else + { + v.Name = context.AssignName(v); + context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); + } + break; + default: v.Name = context.AssignName(v); - context.AssignNameToLocalSignatureIndex(v.Function, v.Index.Value, v.Name); - } - break; - default: - v.Name = context.AssignName(v); - break; + break; + } } - return default; + return VisitChildren(inst, context); } protected internal override Unit VisitILFunction(ILFunction function, VariableScope context) @@ -515,7 +534,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms context.Add((MethodDefinitionHandle)function.ReducedMethod.MetadataToken, newName); } - return base.VisitILFunction(function, new VariableScope(function, this.context, context)); + var nestedContext = new VariableScope(function, this.context, context); + base.VisitILFunction(function, nestedContext); + + if (function.Kind != ILFunctionKind.TopLevelFunction) + { + foreach (var p in function.Variables.Where(v => v.Kind == VariableKind.Parameter && v.Index >= 0)) + { + p.Name = nestedContext.AssignNameIfUnassigned(p); + } + } + + return default; } protected internal override Unit VisitCall(Call inst, VariableScope context) From 8a67f48e4e8d7320ea5b71e5c52f2b154a57ae14 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Mar 2025 22:18:24 +0100 Subject: [PATCH 5/6] Fix #1956: Adapt previous fix for variable names that have a number as suffix. --- ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index 24f7079fc..e0029d00e 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -215,7 +215,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (variables.TryGetValue(i, out var v)) variableMapping[v] = p.Name; } - if (!parentScope.IsReservedVariableName(p.Name, out _)) + string nameWithoutNumber = SplitName(p.Name, out int newIndex); + if (!parentScope.IsReservedVariableName(nameWithoutNumber, out _)) { AddExistingName(reservedVariableNames, p.Name); if (variables.TryGetValue(i, out var v)) From 355a039b59f137abf1bb7ca1fcf2a068c3a5de08 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Mar 2025 22:47:30 +0100 Subject: [PATCH 6/6] Sightly improve variable naming of known types such as EventArgs and Exceptions --- .../TestCases/ILPretty/GuessAccessors.cs | 12 +++---- .../TestCases/Pretty/ExceptionHandling.cs | 12 +++---- .../IL/Transforms/AssignVariableNames.cs | 34 +++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs index 423ad4119..69ebf0db9 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/GuessAccessors.cs @@ -50,9 +50,9 @@ namespace ClassLibrary1 //IL_00e1: Expected O, but got Unknown //IL_00e1: Expected O, but got Unknown UnknownGenericClass val = new UnknownGenericClass(); - UnknownEventArgs val2 = (val.UnknownProperty = val.UnknownProperty); + UnknownEventArgs e = (val.UnknownProperty = val.UnknownProperty); List list = new List { - val[((object)val2).GetHashCode()] ?? "", + val[((object)e).GetHashCode()] ?? "", val.NotProperty, val.get_NotPropertyWithGeneric(42), val[42], @@ -61,10 +61,10 @@ namespace ClassLibrary1 }; val.OnEvent += Instance_OnEvent; val.OnEvent -= Instance_OnEvent; - UnknownEventArgs val3 = val[(UnknownEventArgs)null]; - val[new UnknownEventArgs()] = val3; - UnknownEventArgs val4 = val[new UnknownEventArgs(), new UnknownEventArgs()]; - val[new UnknownEventArgs(), new UnknownEventArgs()] = val4; + UnknownEventArgs e2 = val[(UnknownEventArgs)null]; + val[new UnknownEventArgs()] = e2; + UnknownEventArgs e3 = val[new UnknownEventArgs(), new UnknownEventArgs()]; + val[new UnknownEventArgs(), new UnknownEventArgs()] = e3; } public void MethodUnknownStatic() diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs index 82aba9ab6..2e47643f0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs @@ -418,9 +418,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) + catch (TException ex) { - Console.WriteLine(val.Message); + Console.WriteLine(ex.Message); throw; } } @@ -452,9 +452,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) when (val.Message.Contains("Test")) + catch (TException ex) when (ex.Message.Contains("Test")) { - Console.WriteLine(val.Message); + Console.WriteLine(ex.Message); throw; } } @@ -465,9 +465,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Console.WriteLine(input); } - catch (TException val) when (val.Message.Contains("Test")) + catch (TException ex) when (ex.Message.Contains("Test")) { - Console.WriteLine("{0} {1}", val, val.ToString()); + Console.WriteLine("{0} {1}", ex, ex.ToString()); } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs index e0029d00e..dbe8c5dd4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs @@ -729,22 +729,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms type = NullableType.GetUnderlyingType(((TypeWithElementType)type).ElementType); } - 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; - } + string name; if (type.IsAnonymousType()) { name = "anon"; @@ -753,13 +738,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms { name = "ex"; } + else if (type.Name.EndsWith("EventArgs", StringComparison.Ordinal)) + { + name = "e"; + } else if (type.IsCSharpNativeIntegerType()) { name = "num"; } else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) { - name = type.Name; + 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", + _ => type.Name + }; // remove the 'I' for interfaces if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) name = name.Substring(1);