-
Notifications
You must be signed in to change notification settings - Fork 5.3k
ECMA spec addendum dealing with static interface methods #49558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7d95be3
99d118f
9096eed
d6110be
af2643b
9108d76
aa789da
aafce99
c51084c
b122c42
677f6e1
01e5ded
48b020c
d596870
9027cd6
e959fb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -420,3 +420,393 @@ The algorithm is amended as follows: | |
| **Section** "III.4.2 callvirt" is extended to allow throwing `AmbiguousImplementationException` if the implementation of the interface method resolves at runtime to more than one default interface method. It's also extended to specify throwing `EntryPointNotFoundException` if the default interface implementation is abstract. | ||
|
|
||
| **Section** "III.4.18 ldvirtftn" is extended to allow throwing `AmbiguousImplementationException` if the implementation of the interface method resolves at runtime to more than one default interface method. It's also extended to specify throwing `EntryPointNotFoundException` if the default interface implementation is abstract. | ||
|
|
||
| ## Static Interface Methods | ||
|
|
||
| Follow proposed changes to the ECMA standard pertaining to static interface methods. The quotations and page numbers refer to | ||
| version 6 from June 2012 available at: | ||
|
|
||
| https://www.ecma-international.org/publications-and-standards/standards/ecma-335/ | ||
|
|
||
| ### I.8.4.4, Virtual Methods | ||
|
|
||
| (Add second paragraph) | ||
|
|
||
| Static interface methods may be marked as virtual. Valid object types implementing such interfaces shall provide implementations | ||
| for these methods by means of Method Implementations (II.15.1.4). Polymorphic behavior of calls to these methods is facilitated | ||
|
||
| by the constrained. call IL instruction where the constrained. prefix specifies the type to use for lookup of the static interface | ||
| method. | ||
|
|
||
| ### II.9.7, Validity of member signatures | ||
|
|
||
| (Edit bulleted list under **Generic type definition** at the top of page 134) | ||
|
|
||
| * Every instance method and virtual method declaration is valid with respect to S | ||
| * Every inherited interface declaration is valid with respect to S | ||
| * There are no restrictions on *non-virtual* static members, instance constructors or on the type's | ||
| own generic parameter constraints. | ||
|
|
||
| ### II.9.9, Inheritance and Overriding | ||
|
|
||
| (Edit first paragraph by adding the word *virtual* to the parenthesized formulation *for virtual **instance** methods*) | ||
|
|
||
| Member inheritance is defined in Partition I, in “Member Inheritance”. (Overriding and hiding are also | ||
| defined in that partition, in “Hiding, overriding, and layout”.) This definition is extended, in an obvious | ||
| manner, in the presence of generics. Specifically, in order to determine whether a member hides (for | ||
| static or instance members) or overrides (for virtual instance methods) a member from a base class or interface, | ||
| simply substitute each generic parameter with its generic argument, and compare the resulting member | ||
| signatures. [*Example*: The following illustrates this point: | ||
|
|
||
| ### II.9.11, Constrains on Generic Parameters | ||
trylek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| (Change first paragraph) | ||
|
|
||
| A generic parameter declared on a generic class or generic method can be *constrained* by one or more | ||
| types (for encoding, see *GenericParamConstraint* table in paragraph II.22.21) and by one or more special | ||
| constraints (paragraph II.10.1.7). Generic parameters can be instantiated only with generic arguments that are | ||
| *assignable-to* (paragraph I.8.7.3) (when boxed) and *implements-all-static-interface-methods-of* (**paragraph | ||
|
||
| reference needed**) each of the declared constraints and that satisfy all specified special constraints. | ||
|
|
||
| (Change the last paragraph on page 137) | ||
|
|
||
| [*Note*: Constraints on a generic parameter only restrict the types that the generic parameter may | ||
| be instantiated with. Verification (see Partition III) requires that a field, property or method that a | ||
| generic parameter is known to provide through meeting a constraint, cannot be directly | ||
| accessed/called via the generic parameter unless it is first boxed (see Partition III) or the **callvirt**, | ||
| **call** or **ldftn** instruction is prefixed with the **constrained.** prefix instruction (see Partition III). *end note*] | ||
|
|
||
| ### II.10.3 Introducing and overriding virtual methods | ||
|
|
||
| (Change first paragraph) | ||
|
|
||
| A virtual method of a base type is overridden by providing a direct implementation of the method | ||
| (using a method definition, see paragraph II.15.4) and not specifying it to be newslot (paragraph II.15.4.2.3). An existing | ||
| method body can also be used to implement a given instance or static virtual declaration using the .override directive | ||
| (paragraph II.10.3.2). | ||
|
|
||
| ### II.10.3.2 The .override directive | ||
|
|
||
| (Change first paragraph) | ||
|
|
||
| The .override directive specifies that a virtual method shall be implemented (overridden), in this type, | ||
| by a virtual instance method with a different name or a non-virtual static method, but with the same signature. | ||
| This directive can be used to provide an implementation for a virtual method inherited from a base class, or | ||
| a virtual method specified in an interface implemented by this type. The .override directive specifies a Method | ||
| Implementation (MethodImpl) in the metadata (§II.15.1.4). | ||
|
|
||
| (Change the third and fourth paragraph on page 148, the second and third one below the table) | ||
|
|
||
| The first *TypeSpec::MethodName* pair specifies the virtual method that is being overridden, and shall | ||
| be either an inherited virtual method or a virtual method on an interface that the current type | ||
| implements. The remaining information specifies the virtual instance or non-virtual static method that | ||
AlekseyTs marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| provides the implementation. | ||
|
|
||
| While the syntax specified here (as well as the actual metadata format (paragraph II.22.27) allows any virtual | ||
| method to be used to provide an implementation, a conforming program shall provide a virtual instance | ||
| or static method actually implemented directly on the type. | ||
trylek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### II.12 Semantics of Interfaces | ||
|
|
||
| (Add to the end of the 1st paragraph) | ||
|
|
||
| Interfaces may define static virtual methods that get resolved at runtime based on actual types involved. | ||
trylek marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| These static virtual methods must be marked as abstract in the defining interfaces. | ||
|
|
||
| ### II.12.2 Implementing virtual methods on interfaces | ||
|
|
||
| (Edit 8th paragraph at page 158, the first unindented one below the bullet list, by | ||
| basically clarifying that "public virtual methods" only refer to "public virtual instance methods"): | ||
|
|
||
| The VES shall use the following algorithm to determine the appropriate implementation of an | ||
| interface's virtual abstract methods on the open form of the class: | ||
|
|
||
| * Create an interface table that has an empty list for each virtual method defined by | ||
| the interface. | ||
|
|
||
| * If the interface is an explicit interface of this class: | ||
|
|
||
| * If the class defines any public virtual instance methods whose name and signature | ||
| match a virtual method on the interface, then add these to the list for that | ||
| method, in type declaration order (see above). [*Note*: For an example where | ||
| the order is relevant, see Case 6 in paragraph 12.2.1. *end Note*] | ||
|
|
||
| * If there are any public virtual instance methods available on this class (directly or inherited) | ||
| having the same name and signature as the interface method, and whose generic type | ||
| parameters do not exactly match any methods in the existing list for that interface | ||
| method for this class or any class in its inheritance chain, then add them (in **type | ||
| declaration order**) to the list for the corresponding methods on the interface. | ||
|
|
||
| * If there are multiple methods with the same name, signature and generic type | ||
| parameters, only the last such method in **method declaration order** is added to the | ||
| list. [Note: For an example of duplicate methods, see Case 4 in paragraph 12.2.1. *end Note*] | ||
|
|
||
| * Apply all MethodImpls that are specified for this class, placing explicitly specified | ||
| virtual methods into the interface list for this method, in place of those inherited or | ||
| chosen by name matching that have identical generic type parameters. If there are | ||
| multiple methods for the same interface method (i.e. with different generic type | ||
| parameters), place them in the list in **type declaration order** of the associated | ||
| interfaces. | ||
|
|
||
| * If the current class is not abstract and there are any interface methods that still have | ||
| empty slots (i.e. slots with empty lists) for this class and all classes in its inheritance | ||
| chain, then the program is invalid. | ||
|
|
||
| ### II.12.2.1, Interface implementation examples (page 159) | ||
|
|
||
| For now I'm inclined to add a completely separate section describing static interface | ||
| methods to this paragraph. The existing example is already quite sophisticated and | ||
| I think that expanding it even further with static interface methods would make it really | ||
| confusing. | ||
|
|
||
| (Add at the end of the section before the closing title "End informative text" at the bottom of page 161) | ||
|
|
||
| **Static interface method examples** | ||
|
|
||
| We use the following interfaces to demonstrate static interface method resolution: | ||
|
|
||
| ``` | ||
| interface IFancyTypeName | ||
| { | ||
| static string GetFancyTypeName(); | ||
| } | ||
|
|
||
| interface IAddition<T> | ||
| { | ||
| static T Zero(); // Neutral element | ||
| static T Add(T a, T b); | ||
| } | ||
|
|
||
| interface IMultiplicationBy<T, TMultiplier> | ||
| { | ||
| static T One(); // Neutral element | ||
| static T Multiply(T a, TMultiplier b); | ||
| } | ||
|
|
||
| interface IMultiplication<T> : IMultiplicationBy<T, T> | ||
| { | ||
| } | ||
|
|
||
| interface IArithmetic<T> : IAddition<T>, IMultiplication<T> | ||
| { | ||
| } | ||
| ``` | ||
|
|
||
| We demonstrate the basic rules of static interface method resolution on several simple classes | ||
| implementing these interfaces: | ||
|
|
||
| ``` | ||
| class FancyClass : IFancyTypeName | ||
| { | ||
| public static string IFancyTypeName.GetFancyTypeName() { return "I am the fancy class"; } | ||
| } | ||
|
|
||
| class DerivedFancyClass : FancyClass | ||
| { | ||
| } | ||
|
|
||
| class GenericPair<T> : IArithmetic<GenericPair<T>>, IMultiplicationBy<GenericPair<T>, T> | ||
| { | ||
| public T Component1; | ||
| public T Component2; | ||
|
|
||
| public GenericPair(T component1, T component2) | ||
| { | ||
| Component1 = component1; | ||
| Component2 = component2; | ||
| } | ||
|
|
||
| static GenericPair<T> IAddition<GenericPair<T>>.Zero() | ||
| { | ||
| return new GenericPair<T>(0, 0); | ||
| } | ||
|
|
||
| static GenericPair<T> IAddition<GenericPair<T>>.Add(GenericPair<T> a, GenericPair<T> b) | ||
| { | ||
| return new GenericPair<T>(a.Component1 + b.Component1, a.Component2 + b.Component2); | ||
| } | ||
|
|
||
| static GenericPair<T> IMultiplicationBy<GenericPair<T>, GenericPair<T>>.One() | ||
| { | ||
| return new GenericPair<T>(1, 1); | ||
| } | ||
|
|
||
| static GenericPair<T> IMultiplicationBy<GenericPair<T>, GenericPair<T>>.Multiply(GenericPair<T> a, GenericPair<T> b) | ||
| { | ||
| return new GenericPair<T>(a.Component1 * b.Component1, a.Component2 * b.Component2); | ||
| } | ||
|
|
||
| static GenericPair<T> IMultiplicationBy<GenericPair<T>, T>.Multiply(GenericPair<T> a, T b) | ||
| { | ||
| return new GenericPair<T>(a.Component1 * b, a.Component2 * b); | ||
| } | ||
| } | ||
|
|
||
| class FancyFloatPair : GenericPair<float>, IFancyTypeName | ||
| { | ||
| public static string IFancyTypeName.GetFancyTypeName() { return "I am the fancy float pair"; } | ||
| } | ||
| ``` | ||
|
|
||
| Given these types and their content we can now demonstrate the resolution and behavior of | ||
| static interface methods on several simple algorithms: | ||
|
|
||
| ``` | ||
| void PrintFancyTypeName<T>() | ||
| where T : IFancyTypeName | ||
| { | ||
| Console.WriteLine("My fancy name is: {0}", T.GetFancyTypeName()); | ||
| } | ||
| ``` | ||
|
|
||
| Calling `PrintFancyTypeName<DerivedFancyClass>()` should then output `I am the fancy class` | ||
| to the console. Likewise, `PrintFancyTypeName<FancyFloatPair>()` should output `I am the fancy | ||
| float pair`. In both cases the actual type parameter of the `PrintFancyTypeName` generic method | ||
| implements the `IFancyTypeName` interface and its virtual static method `GetFancyTypeName`. | ||
|
|
||
| **Note**: Please note that `DerivedFancyClass` implements the `IFancyTypeName.GetFancyTypeName` | ||
| method via its base class `FancyClass`. While implementing the static interface method in a | ||
| base class is fine, this design proposal doesn't address implementing static interface methods | ||
| in the interfaces themselves or in derived interfaces akin to default interface support. | ||
|
|
||
| ``` | ||
| T Power<T>(T t, uint power) | ||
| where T : IMultiplication<T> | ||
| { | ||
| T result = T.One(); | ||
| T powerOfT = t; | ||
|
|
||
| while (power != 0) | ||
| { | ||
| if ((power & 1) != 0) | ||
| { | ||
| result = T.Multiply(result, powerOfT); | ||
| } | ||
| powerOfT = T.Multiply(powerOfT, powerOfT); | ||
| power >>= 1; | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| ``` | ||
|
|
||
| This is an example of polymorphic math where the underlying operators can take arbitrary | ||
| form based on the types involved - you can calculate an integral power of a byte or an int, | ||
| of a float, a double, a complex number, a quaternion or a matrix without much distinction | ||
| with regard to the underlying type, you just need to be able to carry out basic arithmetic | ||
| operations. | ||
|
|
||
| ``` | ||
| T Exponential<T, TFloat>(T exponent) where | ||
| T : IArithmetic<T>, | ||
| T : IMultiplicationBy<T, TFloat> | ||
|
|
||
| { | ||
| T result = T.One(); | ||
| T powerOfValue = exponent; | ||
| TFloat inverseFactorial = (TFloat)1.0; | ||
| const int NumberOfTermsInMacLaurinSeries = 6; | ||
| for (int term = 1; term <= NumberOfTermsInMacLaurinSeries; term++) | ||
| { | ||
| result = T.Add(result, (IMultiplicationBy<T, TFloat>)T.Multiply(T.powerOfValue, inverseFactorial)); | ||
| inverseFactorial /= term; | ||
| powerOfValue = T.Multiply(powerOfValue, exponent); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| ``` | ||
|
|
||
| Another example of polymorphic maths calculating the exponential using the Taylor series, | ||
| usable for calculating the exponential of a matrix. | ||
|
|
||
| ### II.15.2 Static, Instance and Virtual Methods (page 177) | ||
|
|
||
| (Clarify first paragraph) | ||
|
|
||
| Static methods are methods that are associated with a type, not with its instances. For | ||
| static virtual methods, the particular method to call is determined via a type lookup | ||
| based on the `constrained.` IL instruction prefix or on generic type constraints but | ||
| the call itself doesn't involve any instance or `this` pointer. | ||
|
|
||
| ### II.22.26, MethodDef: 0x06 | ||
|
|
||
| (Edit bulleted section "This contains informative text only" starting at the bottom of page | ||
| 233): | ||
|
|
||
| Edit section *7.b*: Static | Virtual | !Abstract | ||
|
|
||
| (Add new section 41 after the last section 40:) | ||
|
|
||
| * 41. If the owner of this method is not an interface, then if Flags.Static is 1 then Flags.Virtual must be 0. | ||
|
|
||
| ### II.22.27, MethodImpl: 0x19 | ||
|
|
||
| (Edit bulleted section "This contains informative text only" at the top of the page 237) | ||
|
|
||
| Edit section 7: The method indexed by *MethodBody* shall be non-virtual if the method indexed | ||
| by MethodDeclaration is static. Otherwise it shall be virtual. | ||
|
|
||
davidwrighton marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| (Add new section 14 after section 13:) | ||
|
|
||
| * 14. If the method indexed by *MethodBody* has the static flag set, the method indexed by *MethodBody* must be indexed via a MethodDef and not a MemberRef. [ERROR] | ||
|
|
||
| ### III.2.1, constrained. - (prefix) invoke a member on a value of a variable type (page 316) | ||
|
|
||
| (Change the section title to:) | ||
|
|
||
| III.2.1, constrained. - (prefix) invoke an instance or static method or load method pointer to a variable type | ||
|
|
||
| (Change the "Stack transition" section below the initial assembly format table to a table as follows:) | ||
|
|
||
| | Prefix and instruction pair | Stack Transition | ||
| |:----------------------------------------------|:---------------- | ||
| | constrained. *thisType* callvirt *method* | ..., ptr, arg1, ... argN -> ..., ptr, arg1, ... argN | ||
| | constrained. *implementorType* call *method* | ..., arg1, ... argN -> ..., arg1, ... argN | ||
| | constrained. *implementorType* ldftn *method* | ..., ftn -> ..., ftn | ||
|
|
||
| (Replace the first "Description" paragraph below the "Stack Transition" section as follows:) | ||
|
|
||
| The `constrained.` prefix is permitted only on a `callvirt`, `call` | ||
| or `ldftn` instruction. When followed by the `callvirt` instruction, | ||
| the type of *ptr* must be a managed pointer (&) to *thisType*. The constrained prefix is designed | ||
| to allow `callvirt` instructions to be made in a uniform way independent of whether | ||
| *thisType* is a value type or a reference type. | ||
|
|
||
| When followed by the `call` instruction or the `ldftn` instruction, | ||
| the method must refer to a virtual static method defined on an interface. The behavior of the | ||
| `constrained.` prefix is to change the method that the `call` or `ldftn` | ||
| instruction refers to to be the method on `implementorType` which implements the | ||
| virtual static method (paragraph *II.10.3*). | ||
|
|
||
| (Edit the paragraph "Correctness:" second from the bottom of page 316:) | ||
|
|
||
| The `constrained.` prefix will be immediately followed by a `ldftn`, `call` or `callvirt` | ||
| instruction. *thisType* shall be a valid `typedef`, `typeref`, or `typespec` metadata token. | ||
|
|
||
| (Edit the paragraph "Verifiability" at the bottom of page 316:) | ||
|
|
||
| For the `callvirt` instruction, the `ptr` argument will be a managed pointer (`&`) to `thisType`. | ||
| In addition all the normal verification rules of the `callvirt` instruction apply after the `ptr` | ||
| transformation as described above. This is equivalent to requiring that a boxed `thisType` must be | ||
| a subclass of the class `method` belongs to. | ||
|
|
||
| The `implementorType` must be constrained to implement the interface that is the owning type of | ||
| the method. If the `constrained.` prefix is applied to a `call` or `ldftn` instruction, | ||
| `method` must be a virtual static method. | ||
|
|
||
| ### III.3.19, call - call a method (page 342) | ||
|
|
||
| (Edit 2nd Description paragraph:) | ||
|
|
||
| The metadata token carries sufficient information to determine whether the call is to a static | ||
| (non-virtual or virtual) method, an instance method, a virtual instance method, or a global function. In all of | ||
| these cases the destination address is determined entirely from the metadata token. (Contrast this with the | ||
| `callvirt` instruction for calling virtual instance methods, where the destination address also depends upon | ||
| the exact type of the instance reference pushed before the `callvirt`; see below.) | ||
|
|
||
| (Edit numbered list in the middle of the page 342): | ||
|
|
||
| Bullet 2: It is valid to call a virtual method using `call` (rather than `callvirt`); this indicates that | ||
| the method is to be resolved using the class specified by method rather than as | ||
| specified dynamically from the object being invoked. This is used, for example, to | ||
| compile calls to “methods on `base`” (i.e., the statically known parent class) or to virtual static methods. | ||
Uh oh!
There was an error while loading. Please reload this page.