PowerShell / PowerShell

PowerShell for every system!
https://microsoft.com/PowerShell
MIT License
44.57k stars 7.2k forks source link

Class static abstract methods on interfaces don't work #21060

Open jborean93 opened 8 months ago

jborean93 commented 8 months ago

Prerequisites

Steps to reproduce

Add-Type -TypeDefinition @'
public interface IMethod
{
    static abstract int GetId();
}

public class CSharpMethod : IMethod
{
    public static int GetId() => 1;
}
'@

class PwshMethod : IMethod {
    static [int] GetId() { return 1 }
}

Expected behavior

Class is created and you can do `[PwshMethod]::GetId()` to return `1`.

Actual behavior

ParserError:
Line |
   1 |  class PwshMethod : IMethod {
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Error during creation of type "PwshMethod". Error message: Virtual static method 'GetId' is not implemented on type 'PwshMethod' from assembly 'PowerShell Class Assembly,
     | Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.

Error details

Type        : System.Management.Automation.ParseException
Errors      :
    Extent  : class PwshMethod : IMethod {
              static [int] GetId() { return 1 }
              }
    ErrorId : TypeCreationError
    Message : Error during creation of type "PwshMethod". Error message:
              Virtual static method 'GetId' is not implemented on type 'PwshMethod' from assembly 'PowerShell Class Assembly, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.
Message     : At line:1 char:1
              + class PwshMethod : IMethod {
              + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
              Error during creation of type "PwshMethod". Error message:
              Virtual static method 'GetId' is not implemented on type 'PwshMethod' from assembly 'PowerShell Class Assembly, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.
ErrorRecord :
    Exception             :
        Type    : System.Management.Automation.ParentContainsErrorRecordException
        Message : At line:1 char:1
                  + class PwshMethod : IMethod {
                  + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  Error during creation of type "PwshMethod". Error message:
                  Virtual static method 'GetId' is not implemented on type 'PwshMethod' from assembly 'PowerShell Class Assembly, Version=1.0.0.1, Culture=neutral,
PublicKeyToken=null'.
        HResult : -2146233087
    CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    FullyQualifiedErrorId : TypeCreationError
    InvocationInfo        :
        ScriptLineNumber : 1
        OffsetInLine     : 1
        HistoryId        : -1
        Line             : class PwshMethod : IMethod {

        Statement        : class PwshMethod : IMethod {
                           static [int] GetId() { return 1 }
                           }
        PositionMessage  : At line:1 char:1
                           + class PwshMethod : IMethod {
                           + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        CommandOrigin    : Internal
TargetSite  :
    Name          : Invoke
    DeclaringType : System.Management.Automation.Runspaces.PipelineBase, System.Management.Automation, Version=7.4.0.500, Culture=neutral, PublicKeyToken=31bf3856ad364e35
    MemberType    : Method
    Module        : System.Management.Automation.dll
Source      : System.Management.Automation
HResult     : -2146233087
StackTrace  :
   at System.Management.Automation.Runspaces.PipelineBase.Invoke(IEnumerable input)
   at Microsoft.PowerShell.Executor.ExecuteCommandHelper(Pipeline tempPipeline, Exception& exceptionThrown, ExecutionOptions options)

Environment data

Name                           Value
----                           -----
PSVersion                      7.4.0
PSEdition                      Core
GitCommitId                    7.4.0
OS                             Arch Linux
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Visuals

No response

jborean93 commented 8 months ago

Just for future reference here is how you can define a class that implements such an interface with TypeBuilder

using namespace System.Reflection
using namespace System.Reflection.Emit

$ErrorActionPreference = 'Stop'

Add-Type -TypeDefinition @'
public interface IMethod
{
    static abstract int GetId();
}
'@

$ab = [AssemblyBuilder]::DefineDynamicAssembly(
    [AssemblyName]::new("MyAssembly"),
    [AssemblyBuilderAccess]::Run)
$mb = $ab.DefineDynamicModule("MyModule")
$tb = $mb.DefineType(
    "MyClass",
    [TypeAttributes]::Public,
    $null,
    [type[]]@([IMethod]))

$getAttr = $tb.DefineMethod(
    "GetId",
    [MethodAttributes]"Public, Static",
    [int],
    [type]::EmptyTypes)
$getAttrIl = $getAttr.GetILGenerator()
$getAttrIl.Emit([OpCodes]::Ldc_I4_1)
$getAttrIL.Emit([OpCodes]::Ret)
$tb.DefineMethodOverride($getAttr, [IMethod].GetMethod("GetId"))

$null = $tb.CreateType()

[MyClass]::GetId()
rhubarb-geek-nz commented 8 months ago

How can you have static properties or methods on an interface when an interface depends on having a 'this'?

I would have expected the only static things an interface could have would be constants.

How do you invoke a static method on an interface?

jborean93 commented 8 months ago

It's a new feature added in C# 11 https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members. Just like any other static property/method, the implementation can only reference other static properties/methods on that same interface/type.

How do you invoke a static method on an interface?

You don't invoke it on the interface you still invoke it on the class that implements the abstract property/method.

Add-Type @'
public interface IMethod
{
    static abstract int GetId();
}

public class MyClass : IMethod
{
    public static int GetId() => 1;
}

public class OtherClass
{
    // Demonstrates how it can be used in C#
    public static int Test<T>() where T : IMethod => T.GetId();
}
'@

# Can be called directly on the class like any other static member
[MyClass]::GetId()

[OtherClass]::Test[MyClass]()
Explore static virtual members in interfaces - C#
This advanced tutorial demonstrates scenarios for operators and other static members in interfaces.
daxian-dbw commented 7 months ago

The Engine WG reviewed this issue and happy to see this enhancement to PowerShell class.