diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs index 8905736a8..06d88df04 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1325.cs @@ -35,13 +35,15 @@ namespace Issue1325 internal class Test { - public string Parameterized { - get { - throw new NotImplementedException(); - } - set { - throw new NotImplementedException(); - } + // C# has no syntax for parameterized property 'Parameterized'. + public string get_Parameterized(int i) + { + throw new NotImplementedException(); + } + + public void set_Parameterized(int i, string value) + { + throw new NotImplementedException(); } public string Unparameterized { get; set; } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs index 8f034867a..40caf9e62 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/ParameterizedProperties.cs @@ -3,8 +3,7 @@ using System; public interface IParameterized { - // C# has no syntax for parameterized properties; the accessors of - // property 'IndexedValue' are emitted as ordinary methods. + // C# has no syntax for parameterized property 'IndexedValue'. int get_IndexedValue(int index); void set_IndexedValue(int index, int Value); } @@ -13,8 +12,7 @@ public class ParameterizedProperties : IParameterized { private int _field; - // C# has no syntax for parameterized properties; the accessors of - // property 'SharedProp' are emitted as ordinary methods. + // C# has no syntax for parameterized property 'SharedProp'. public static int get_SharedProp(int index) { return index; @@ -24,8 +22,7 @@ public class ParameterizedProperties : IParameterized { } - // C# has no syntax for parameterized properties; the accessors of - // property 'IndexedValue' are emitted as ordinary methods. + // C# has no syntax for parameterized property 'IndexedValue'. public int get_IndexedValue(int index) { return _field; @@ -36,16 +33,14 @@ public class ParameterizedProperties : IParameterized _field = value; } - // C# has no syntax for parameterized properties; the accessors of - // property 'ReadOnlyProp' are emitted as ordinary methods. + // C# has no syntax for parameterized property 'ReadOnlyProp'. public int get_ReadOnlyProp(int index) { return index; } - // C# has no syntax for parameterized properties; the accessors of - // property 'Attributed' are emitted as ordinary methods. The property's - // attributes are kept below under the inert 'property:' target (CS0657). + // C# has no syntax for parameterized property 'Attributed'. + // Its 'property:' attributes below are ignored by the compiler (CS0657). [property: Obsolete("read-write parameterized property")] public int get_Attributed(int index) { diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 3c5c61d41..9f16135a7 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1338,7 +1338,14 @@ namespace ICSharpCode.Decompiler.CSharp case HandleKind.PropertyDefinition: IProperty property = module.GetDefinition((PropertyDefinitionHandle)entity); parentExtensionInfo = property.ResolveExtensionInfo(); - syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo)); + if (property.IsParameterizedProperty()) + { + syntaxTree.Members.AddRange(DecompileParameterizedProperty(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo)); + } + else + { + syntaxTree.Members.Add(DoDecompile(property, decompileRun, new SimpleTypeResolveContext(property), parentExtensionInfo)); + } if (first) { parentTypeDef = property.DeclaringTypeDefinition; @@ -1869,6 +1876,15 @@ namespace ICSharpCode.Decompiler.CSharp { return; } + if (property.IsParameterizedProperty()) + { + foreach (var accessorDecl in DecompileParameterizedProperty(property, decompileRun, decompilationContext, null)) + { + entityMap.Add(property, accessorDecl); + EnqueueReferencedMembers(accessorDecl); + } + return; + } entityDecl = DoDecompile(property, decompileRun, decompilationContext.WithCurrentMember(property), null); entityMap.Add(property, entityDecl); break; @@ -1897,19 +1913,24 @@ namespace ICSharpCode.Decompiler.CSharp throw new ArgumentOutOfRangeException("Unexpected member type"); } - foreach (var node in entityDecl.Descendants) + EnqueueReferencedMembers(entityDecl); + + void EnqueueReferencedMembers(EntityDeclaration decl) { - var rr = node.GetResolveResult(); - if (rr is MemberResolveResult mrr - && mrr.Member.DeclaringTypeDefinition == typeDef - && !(mrr.Member is IMethod { IsLocalFunction: true })) + foreach (var node in decl.Descendants) { - workList.Enqueue(mrr.Member); - } - else if (rr is TypeResolveResult trr - && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) - { - workList.Enqueue(trr.Type.GetDefinition()!); + var rr = node.GetResolveResult(); + if (rr is MemberResolveResult mrr + && mrr.Member.DeclaringTypeDefinition == typeDef + && !(mrr.Member is IMethod { IsLocalFunction: true })) + { + workList.Enqueue(mrr.Member); + } + else if (rr is TypeResolveResult trr + && trr.Type.GetDefinition()?.DeclaringTypeDefinition == typeDef) + { + workList.Enqueue(trr.Type.GetDefinition()!); + } } } } @@ -2047,7 +2068,10 @@ namespace ICSharpCode.Decompiler.CSharp { methodDecl.Modifiers |= Modifiers.Extern; } - if (method.SymbolKind == SymbolKind.Method && !method.IsExplicitInterfaceImplementation + // Accessors qualify only when they are emitted as ordinary methods (parameterized + // properties); an Accessor node cannot carry the 'new' modifier. + if ((method.SymbolKind == SymbolKind.Method || (method.SymbolKind == SymbolKind.Accessor && methodDecl is MethodDeclaration)) + && !method.IsExplicitInterfaceImplementation && methodDefinition.HasFlag(System.Reflection.MethodAttributes.Virtual) == methodDefinition.HasFlag(System.Reflection.MethodAttributes.NewSlot)) { SetNewModifier(methodDecl); @@ -2456,6 +2480,40 @@ namespace ICSharpCode.Decompiler.CSharp return false; } + /// + /// Decompiles a parameterized property (a named property with parameters, e.g. from + /// VB.NET) into declarations of its accessor methods, because C# has no syntax for + /// such properties. The property-level attributes are placed on the first accessor + /// under the 'property:' attribute target, which is not valid for methods and is + /// therefore ignored by the C# compiler (CS0657): recompilation neither loses the + /// attributes from the source nor misapplies them to the accessor method. + /// + List DecompileParameterizedProperty(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo) + { + var result = new List(2); + var typeSystemAstBuilder = CreateAstBuilder(decompileRun.Settings); + foreach (var accessor in new[] { property.Getter, property.Setter }) + { + if (accessor == null) + continue; + var accessorDecl = DoDecompile(accessor, decompileRun, decompilationContext.WithCurrentMember(accessor), extensionInfo); + if (result.Count == 0) + { + accessorDecl.AddLeadingTrivia(new Comment($" C# has no syntax for parameterized property '{property.Name}'.")); + var attributes = property.GetAttributes().Select(typeSystemAstBuilder.ConvertAttribute).ToList(); + if (attributes.Count > 0) + { + var attrSection = new AttributeSection { AttributeTarget = "property" }; + attrSection.Attributes.AddRange(attributes); + accessorDecl.Attributes.InsertAfter(null, attrSection); + accessorDecl.AddLeadingTrivia(new Comment(" Its 'property:' attributes below are ignored by the compiler (CS0657).")); + } + } + result.Add(accessorDecl); + } + return result; + } + EntityDeclaration DoDecompile(IProperty property, DecompileRun decompileRun, ITypeResolveContext decompilationContext, ExtensionInfo? extensionInfo) { Debug.Assert(decompilationContext.CurrentMember == property); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 897e157a1..7f8a7259c 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -1871,6 +1871,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax return ConvertDestructor((IMethod)entity); case SymbolKind.Accessor: IMethod accessor = (IMethod)entity; + if (accessor.AccessorOwner is IProperty owner && owner.IsParameterizedProperty()) + { + // C# cannot represent the parameterized property itself; its accessors + // are declared as ordinary methods. + return ConvertMethod(accessor); + } Accessibility ownerAccessibility = accessor.AccessorOwner?.Accessibility ?? Accessibility.None; return ConvertAccessor(accessor, accessor.AccessorKind, ownerAccessibility, false)!; default: diff --git a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs index 1b624c739..8d958ac73 100644 --- a/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs +++ b/ICSharpCode.Decompiler/TypeSystem/TypeSystemExtensions.cs @@ -171,6 +171,17 @@ namespace ICSharpCode.Decompiler.TypeSystem } } + /// + /// Gets whether the property is a parameterized property that is not an indexer, + /// i.e. a named property with parameters (a VB.NET parameterized property or a + /// C++/CLI indexed property). C# has no syntax for declaring or using such a + /// property; only its accessor methods can be represented. + /// + public static bool IsParameterizedProperty(this IProperty property) + { + return property.SymbolKind == SymbolKind.Property && property.Parameters.Count > 0; + } + /// /// Gets whether the type is an open type (contains type parameters). ///