Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 87 additions & 14 deletions src/System.Management.Automation/engine/parser/PSType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Management.Automation.Internal;
using System.Reflection;
using System.Reflection.Emit;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

using Microsoft.PowerShell;
Expand Down Expand Up @@ -277,7 +279,9 @@ private sealed class DefineTypeHelper
internal readonly TypeBuilder _staticHelpersTypeBuilder;
private readonly Dictionary<string, PropertyMemberAst> _definedProperties;
private readonly Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>> _definedMethods;
private HashSet<Type> _interfaces;
private Dictionary<Tuple<string, Type>, PropertyInfo> _abstractProperties;
private Dictionary<Tuple<string, Type, string>, MethodInfo> _interfaceMethods;
internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions;
private bool _baseClassHasDefaultCtor;

Expand Down Expand Up @@ -449,20 +453,9 @@ private bool ShouldImplementProperty(string name, Type type, [NotNullWhen(true)]
{
if (_abstractProperties == null)
{
_abstractProperties = new Dictionary<Tuple<string, Type>, PropertyInfo>();
var allInterfaces = new HashSet<Type>();

// TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor.
// During compilation the interface hierarchy is flattened, so we only need to resolve one level of ancestral interfaces.
foreach (var interfaceType in _typeBuilder.GetInterfaces())
{
foreach (var parentInterface in interfaceType.GetInterfaces())
{
allInterfaces.Add(parentInterface);
}

allInterfaces.Add(interfaceType);
}
_abstractProperties = new Dictionary<Tuple<string, Type>, PropertyInfo>();
var allInterfaces = GetImplementingInterfaces();

foreach (var interfaceType in allInterfaces)
{
Expand All @@ -487,6 +480,74 @@ private bool ShouldImplementProperty(string name, Type type, [NotNullWhen(true)]
return _abstractProperties.TryGetValue(Tuple.Create(name, type), out interfaceProperty);
}

private bool ShouldImplementMethod(
string name,
Type returnType,
Type[] parameterTypes,
[NotNullWhen(true)] out MethodInfo interfaceMethod)
{
if (_interfaceMethods == null)
{
_interfaceMethods = new Dictionary<Tuple<string, Type, string>, MethodInfo>();
var allInterfaces = GetImplementingInterfaces();

// We include NonPublic so we can also get protected interface methods.
BindingFlags methodFlags = BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static;

foreach (var interfaceType in allInterfaces)
{
foreach (var method in interfaceType.GetMethods(methodFlags))
{
if (!(method.IsFamily || method.IsFamilyOrAssembly || method.IsPublic))
{
// We want public and protected only, no internal or private.
continue;
}

Type[] methodParameters = method.GetParameters().Select(p => p.ParameterType).ToArray();
string methodParametersId = GetTypeArrayId(methodParameters);
_interfaceMethods.Add(Tuple.Create(method.Name, method.ReturnType, methodParametersId), method);
}
}
}

string parameterTypeId = GetTypeArrayId(parameterTypes);
return _interfaceMethods.TryGetValue(Tuple.Create(name, returnType, parameterTypeId), out interfaceMethod);
}

private HashSet<Type> GetImplementingInterfaces()
{
if (_interfaces == null)
{
_interfaces = new HashSet<Type>();

// TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor.
// During compilation the interface hierarchy is flattened, so we only need to resolve one level of ancestral interfaces.
foreach (var interfaceType in _typeBuilder.GetInterfaces())
{
foreach (var parentInterface in interfaceType.GetInterfaces())
{
_interfaces.Add(parentInterface);
}

_interfaces.Add(interfaceType);
}
}

return _interfaces;
}

private static string GetTypeArrayId(Type[] types)
{
string typeId = string.Join(string.Empty, types.Select(t => t.AssemblyQualifiedName));
byte[] typeHash = SHA256.HashData(Encoding.UTF8.GetBytes(typeId));

return Convert.ToHexString(typeHash);
}

public void DefineMembers()
{
// If user didn't provide any instance ctors or static ctor we will generate default ctor or static ctor respectively.
Expand Down Expand Up @@ -884,12 +945,20 @@ private void DefineMethod(FunctionMemberAst functionMemberAst)
return;
}

var returnType = functionMemberAst.GetReturnType();
var attributes = functionMemberAst.IsPublic
? Reflection.MethodAttributes.Public
: Reflection.MethodAttributes.Private;
MethodInfo interfaceBaseMethod = null;
if (functionMemberAst.IsStatic)
{
attributes |= Reflection.MethodAttributes.Static;

ShouldImplementMethod(
functionMemberAst.Name,
returnType,
parameterTypes,
out interfaceBaseMethod);
}
else
{
Expand All @@ -902,7 +971,6 @@ private void DefineMethod(FunctionMemberAst functionMemberAst)
attributes |= Reflection.MethodAttributes.Virtual;
}

var returnType = functionMemberAst.GetReturnType();
if (returnType == null)
{
_parser.ReportError(functionMemberAst.ReturnType.Extent,
Expand All @@ -922,6 +990,11 @@ private void DefineMethod(FunctionMemberAst functionMemberAst)
var ilGenerator = method.GetILGenerator();
DefineMethodBody(functionMemberAst, ilGenerator, GetMetaDataName(method.Name, parameterTypes.Length), functionMemberAst.IsStatic, parameterTypes, returnType,
(i, n) => method.DefineParameter(i, ParameterAttributes.None, n));

if (interfaceBaseMethod != null)
{
_typeBuilder.DefineMethodOverride(method, interfaceBaseMethod);
}
}

private void DefineConstructor(IParameterMetadataProvider ipmp, ReadOnlyCollection<AttributeAst> attributeAsts, bool isHidden, Reflection.MethodAttributes methodAttributes, Type[] parameterTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,103 @@ class ClassWithStaticAbstractInterface : IInterfaceWithStaticAbstractProperty {
[InterfaceStaticAbstractPropertyTest]::GetSetter[ClassWithStaticAbstractInterface]() | Should -Be 4
}

It 'can implement .NET interface static methods' {
Add-Type -TypeDefinition @'
public interface IInterfaceWithStaticAbstractMethod
{
static abstract int GetNoParameters();
static abstract int GetOneParameter(string value);
static abstract int GetTwoParameters(string value, int multiplier);
}

public static class InterfaceStaticAbstractMethodTest
{
public static int GetNoParameters<T>() where T : IInterfaceWithStaticAbstractMethod
=> T.GetNoParameters();

public static int GetOneParameter<T>(string value) where T : IInterfaceWithStaticAbstractMethod
=> T.GetOneParameter(value);

public static int GetTwoParameters<T>(string value, int multiplier) where T : IInterfaceWithStaticAbstractMethod
=> T.GetTwoParameters(value, multiplier);
}
'@

$C1 = Invoke-Expression @'
class ClassWithStaticAbstractMethodInterface : IInterfaceWithStaticAbstractMethod {
static [int] GetNoParameters() { return 1 }
static [int] GetOneParameter([string]$Value) { return [int]$Value }
static [int] GetTwoParameters([string]$Value, [int]$Multiplier) { return ([int]$Value) * $Multiplier }
# Tests that the override uses the parameters when matching against the interface
static [int] GetTwoParameters([string]$Value, [string]$Multiplier) { return -1 }
}
[ClassWithStaticAbstractMethodInterface]
'@

$v = $C1::GetNoParameters()
$v | Should -Be 1
$v | Should -BeOfType ([int])

$v = $C1::GetOneParameter("2")
$v | Should -Be 2
$v | Should -BeOfType ([int])

$v = $C1::GetTwoParameters("3", 3)
$v | Should -Be 9
$v | Should -BeOfType ([int])

$v = $C1::GetTwoParameters("3", "3")
$v | Should -Be -1
$v | Should -BeOfType ([int])

[InterfaceStaticAbstractMethodTest]::GetNoParameters[ClassWithStaticAbstractMethodInterface]() | Should -Be 1
[InterfaceStaticAbstractMethodTest]::GetOneParameter[ClassWithStaticAbstractMethodInterface]("2") | Should -Be 2
[InterfaceStaticAbstractMethodTest]::GetTwoParameters[ClassWithStaticAbstractMethodInterface]("2", 2) | Should -Be 4
}

It 'can implement .NET interface static protected methods' {
Add-Type -TypeDefinition @'
public interface IInterfaceWithStaticAbstractProtectedMethod
{
protected static abstract int ProtectedMeth();
protected internal static abstract int ProtectedInternalMeth(string value);
}
'@

$C1 = Invoke-Expression @'
class ClassWithStaticAbstractProtectedMethodInterface : IInterfaceWithStaticAbstractProtectedMethod {
static [int] ProtectedMeth() { return 1 }
static [int] ProtectedInternalMeth([string]$Value) { return [int]$Value }
}
[ClassWithStaticAbstractProtectedMethodInterface]
'@

$v = $C1::ProtectedMeth()
$v | Should -Be 1
$v | Should -BeOfType ([int])

$v = $C1::ProtectedInternalMeth("2")
$v | Should -Be 2
$v | Should -BeOfType ([int])
}

It 'fails to implement .NET interface static internal methods' {
Add-Type -TypeDefinition @'
public interface IInterfaceWithStaticAbstractInternalMethod
{
internal static abstract int Test();
}
'@

{
Invoke-Expression @'
class ClassWithStaticAbstractInternalMethodInterface : IInterfaceWithStaticAbstractInternalMethod {
static [int] Test() { return 1 }
}
'@
} | Should -Throw -ExceptionType ([System.Management.Automation.ParseException])
}

It 'allows use of defined later type as a property type' {
class A { static [B]$b }
class B : A {}
Expand Down
Loading