Skip to content

Commit d2fc014

Browse files
committed
Add support for interface static abstract props
Add support for writing a PowerShell class that can implement an interface that contains a static abstract property introduced in C# 11.
1 parent 74d8bdb commit d2fc014

2 files changed

Lines changed: 72 additions & 8 deletions

File tree

src/System.Management.Automation/engine/parser/PSType.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Collections.ObjectModel;
6+
using System.Diagnostics.CodeAnalysis;
67
using System.Globalization;
78
using System.Linq;
89
using System.Management.Automation.Internal;
@@ -276,7 +277,7 @@ private sealed class DefineTypeHelper
276277
internal readonly TypeBuilder _staticHelpersTypeBuilder;
277278
private readonly Dictionary<string, PropertyMemberAst> _definedProperties;
278279
private readonly Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>> _definedMethods;
279-
private HashSet<Tuple<string, Type>> _abstractProperties;
280+
private Dictionary<Tuple<string, Type>, PropertyInfo> _abstractProperties;
280281
internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions;
281282
private bool _baseClassHasDefaultCtor;
282283

@@ -444,11 +445,11 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou
444445
return baseClass ?? typeof(object);
445446
}
446447

447-
private bool ShouldImplementProperty(string name, Type type)
448+
private bool ShouldImplementProperty(string name, Type type, [NotNullWhen(true)] out PropertyInfo interfaceProperty)
448449
{
449450
if (_abstractProperties == null)
450451
{
451-
_abstractProperties = new HashSet<Tuple<string, Type>>();
452+
_abstractProperties = new Dictionary<Tuple<string, Type>, PropertyInfo>();
452453
var allInterfaces = new HashSet<Type>();
453454

454455
// TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor.
@@ -467,7 +468,7 @@ private bool ShouldImplementProperty(string name, Type type)
467468
{
468469
foreach (var property in interfaceType.GetProperties())
469470
{
470-
_abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType));
471+
_abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property);
471472
}
472473
}
473474

@@ -477,13 +478,13 @@ private bool ShouldImplementProperty(string name, Type type)
477478
{
478479
if (property.GetAccessors().Any(m => m.IsAbstract))
479480
{
480-
_abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType));
481+
_abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property);
481482
}
482483
}
483484
}
484485
}
485486

486-
return _abstractProperties.Contains(Tuple.Create(name, type));
487+
return _abstractProperties.TryGetValue(Tuple.Create(name, type), out interfaceProperty);
487488
}
488489

489490
public void DefineMembers()
@@ -629,9 +630,19 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type
629630
// The property set and property get methods require a special set of attributes.
630631
var getSetAttributes = Reflection.MethodAttributes.SpecialName | Reflection.MethodAttributes.HideBySig;
631632
getSetAttributes |= propertyMemberAst.IsPublic ? Reflection.MethodAttributes.Public : Reflection.MethodAttributes.Private;
632-
if (ShouldImplementProperty(propertyMemberAst.Name, type))
633+
MethodInfo implementingGetter = null;
634+
MethodInfo implementingSetter = null;
635+
if (ShouldImplementProperty(propertyMemberAst.Name, type, out PropertyInfo interfaceProperty))
633636
{
634-
getSetAttributes |= Reflection.MethodAttributes.Virtual;
637+
if (propertyMemberAst.IsStatic)
638+
{
639+
implementingGetter = interfaceProperty.GetGetMethod();
640+
implementingSetter = interfaceProperty.GetSetMethod();
641+
}
642+
else
643+
{
644+
getSetAttributes |= Reflection.MethodAttributes.Virtual;
645+
}
635646
}
636647

637648
if (propertyMemberAst.IsStatic)
@@ -677,6 +688,11 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type
677688
getIlGen.Emit(OpCodes.Ret);
678689
}
679690

691+
if (implementingGetter != null)
692+
{
693+
_typeBuilder.DefineMethodOverride(getMethod, implementingGetter);
694+
}
695+
680696
// Define the "set" accessor method.
681697
MethodBuilder setMethod = _typeBuilder.DefineMethod(string.Concat("set_", propertyMemberAst.Name), getSetAttributes, null, new Type[] { type });
682698
ILGenerator setIlGen = setMethod.GetILGenerator();
@@ -710,6 +726,11 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type
710726

711727
setIlGen.Emit(OpCodes.Ret);
712728

729+
if (implementingSetter != null)
730+
{
731+
_typeBuilder.DefineMethodOverride(setMethod, implementingSetter);
732+
}
733+
713734
// Map the two methods created above to our PropertyBuilder to
714735
// their corresponding behaviors, "get" and "set" respectively.
715736
property.SetGetMethod(getMethod);

test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,49 @@ Describe 'Classes inheritance syntax' -Tags "CI" {
8080
$getter.Attributes -band [System.Reflection.MethodAttributes]::Virtual | Should -Be ([System.Reflection.MethodAttributes]::Virtual)
8181
}
8282

83+
It 'can implement .NET interface static properties' {
84+
Add-Type -TypeDefinition @'
85+
public interface IInterfaceWithStaticAbstractProperty
86+
{
87+
static abstract int Getter { get; }
88+
static abstract int Setter { get; set; }
89+
}
90+
91+
public static class InterfaceStaticAbstractPropertyTest
92+
{
93+
public static int GetGetter<T>() where T : IInterfaceWithStaticAbstractProperty
94+
=> T.Getter;
95+
96+
public static int GetSetter<T>() where T : IInterfaceWithStaticAbstractProperty
97+
=> T.Setter;
98+
99+
public static int SetSetter<T>(int value) where T : IInterfaceWithStaticAbstractProperty
100+
=> T.Setter = value;
101+
}
102+
'@
103+
104+
$C1 = Invoke-Expression @'
105+
class ClassWithStaticAbstractInterface : IInterfaceWithStaticAbstractProperty {
106+
static [int]$Getter = 1
107+
static [int]$Setter = 2
108+
}
109+
110+
[ClassWithStaticAbstractInterface]
111+
'@
112+
113+
$C1::Getter | Should -Be 1
114+
$C1::Getter | Should -BeOfType ([int])
115+
$C1::Setter | Should -Be 2
116+
$C1::Setter | Should -BeOfType ([int])
117+
$C1::Setter = 3
118+
$C1::Setter | Should -Be 3
119+
120+
[InterfaceStaticAbstractPropertyTest]::GetGetter[ClassWithStaticAbstractInterface]() | Should -Be 1
121+
[InterfaceStaticAbstractPropertyTest]::GetSetter[ClassWithStaticAbstractInterface]() | Should -Be 3
122+
[InterfaceStaticAbstractPropertyTest]::SetSetter[ClassWithStaticAbstractInterface](4)
123+
[InterfaceStaticAbstractPropertyTest]::GetSetter[ClassWithStaticAbstractInterface]() | Should -Be 4
124+
}
125+
83126
It 'allows use of defined later type as a property type' {
84127
class A { static [B]$b }
85128
class B : A {}

0 commit comments

Comments
 (0)