From 4909f56f201945d8367fe41126c7de440fa60929 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 28 Jul 2021 14:59:42 -0700 Subject: [PATCH 1/4] Covariant return updates to ecma augments --- docs/design/specs/Ecma-335-Augments.md | 93 ++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/design/specs/Ecma-335-Augments.md b/docs/design/specs/Ecma-335-Augments.md index 9b5d045c4c793e..747fce5da9aa1c 100644 --- a/docs/design/specs/Ecma-335-Augments.md +++ b/docs/design/specs/Ecma-335-Augments.md @@ -2,6 +2,16 @@ This is a list of additions and edits to be made in ECMA-335 specifications. It includes both documentation of new runtime features and issues encountered during development. Some of the issues are definite spec errors while others could be reasoned as Microsoft implementation quirks. +## Changes by feature area + +- [Signatures](#signatures) +- [Heap sizes](#heap-sizes) +- [Metadata merging](#metadata-merging) +- [Module Initializer](#module-initializer) +- [Default Interface Methods](#default-interface-methods) +- [Static Interface Methods](#static-interface-methods) +- [Covariant Return Types](#covariant-return-types) + ## Signatures There is a general philosophical issue whereby the spec defines the @@ -810,3 +820,86 @@ Bullet 2: It is valid to call a virtual method using `call` (rather than `callvi 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. + +## Covariant Return Types + +Covariant return methods is a runtime feature designed to support the [covariant return types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/covariant-returns.md) and [records](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md) C# language features implemented in C# 9.0. + +This feature allows an overriding method to have a return type that is different than the one on the method it overrides, but compatible with it. The type compability rules are defined in ECMA I.8.7.1. + +Example: +``` +class Base +{ + public virtual object VirtualMethod() { return null; } +} + +class Derived : Base +{ + // Note that the VirtualMethod here overrides the VirtualMethod defined on Base even though the + // return types are not the same. However, the return types are compatible in a covariant fashion. + public override string VirtualMethod() { return null;} +} +``` + +Covariant return methods can only be described through MethodImpl records, and as are only be applicable to methods on reference types. Methods on interfaces and value types are not supported. In addition the covariant override is only applicable for overriding methods also defined on a reference type. + +See [Implementation Design](../features/covariant-return-methods.md) for notes from the implementation in CoreCLR. + +Changes to the spec. These changes are relative to the 6th edition (June 2012) of the ECMA-335 specification published by ECMA available at: + +https://www.ecma-international.org/publications-and-standards/standards/ecma-335/ + +### I.8.7.1 Assignment compatibility for signature types + +Add a new signature type relation. + +"A method signature type T is *covariant-return-compatible-with* with a method signature type U if and only if: +1. The calling conventions of T and U shall match exactly, ignoring the distinction +between static and instance methods (i.e., the this parameter, if any, is not treated +specially). +2. For each parameter type of P of T, and corresponding type Q of U, P is identical Q. +3. For the return type P of T, and return type Q of U, Q is *compatible-with* P and is a *reference type* OR Q is identical to P. + +### II.10.3.2 The .override directive (page 147) + +Edit the first paragraph "The .override directive specifies that a virtual method shall be implemented (overridden), in this type, by a virtual method with a different name, but with a *covariant-return-compatible-with* signature (§I.8.7.1). 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 and all other. If not used to provide an implementation for a virtual method inherited from a base class the signature must be the identical." + +### II.10.3.4 Impact of overrides on derived classes +Add a third bullet +"- If a virtual method is overridden via a .override directive and virtual method in parent class was overriden with a .override directive where the method body of that second .override directive is decorated with `System.Runtime.CompilerServices.PreserveBaseOverridesAttribute` then the virtual method override shall apply to the method body of the second .override directive as well. + +``` +.class A { + .method newslot virtual RetType VirtualFunction() { ... } +} +.class B extends A { + .method virtual DerivedRetType VirtualFunction() { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = ( 01 00 00 00 ) + .override A.VirtualFuncion + ... + } +} +.class C extends B { + .method virtual MoreDerivedRetType VirtualFunction() + { + .override A.VirtualFunction + ... + } +} +``` +For this example, the behavior of calls on objects of various types is presented in the following table: +| Type of object | Method Invocation | Method Called | +| --- | --- | --- | +| A | A::VirtualFunction() | A::VirtualFunction | +| B | A::VirtualFunction() | B::VirtualFunction | +| B | B::VirtualFunction() | B::VirtualFunction | +| C | A::VirtualFunction() | C::VirtualFunction | +| C | B::VirtualFunction() | C::VirtualFunction | +| C | C::VirtualFunction() | C::VirtualFunction | +" + +Add a new paragraph "When a rules above result in the implementation of virtual method not satisfying the requirement that the signature of the implementor of a virtual method is not *covariant-return-compatible-with* (§I.8.7.1) the program is not considered well formed. As an example, this can happen if a virtual method is overriden with a .override directive(A) paired with a `PreserveBaseOverridesAttribute` with a *covariant-return-compatible-with* signature. and then the initial virtual method is subsequently overridden with a method which is *covariant-return-compatible-with* the initial virtual method, but not with the method body associated with .override directive(A)." + +### II.22.27 +Edit rule 12 to specify that "The method isgnature defined by *MethodBody* shall match those defined by *MethodDeclaration* exactly if *MethodDeclaration* defines a method on an interface or be covariant-return-compatible-with* (§I.8.7.1) if *MethodDeclaration* represents a method on a class." \ No newline at end of file From 4ccdd739d4fb5464eda5619392f41f15750b7bea Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 28 Jul 2021 15:06:22 -0700 Subject: [PATCH 2/4] Fix linter issues. --- docs/design/specs/Ecma-335-Augments.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/design/specs/Ecma-335-Augments.md b/docs/design/specs/Ecma-335-Augments.md index 747fce5da9aa1c..7838430e2e1e85 100644 --- a/docs/design/specs/Ecma-335-Augments.md +++ b/docs/design/specs/Ecma-335-Augments.md @@ -855,9 +855,7 @@ https://www.ecma-international.org/publications-and-standards/standards/ecma-335 Add a new signature type relation. "A method signature type T is *covariant-return-compatible-with* with a method signature type U if and only if: -1. The calling conventions of T and U shall match exactly, ignoring the distinction -between static and instance methods (i.e., the this parameter, if any, is not treated -specially). +1. The calling conventions of T and U shall match exactly, ignoring the distinction between static and instance methods (i.e., the this parameter, if any, is not treated specially). 2. For each parameter type of P of T, and corresponding type Q of U, P is identical Q. 3. For the return type P of T, and return type Q of U, Q is *compatible-with* P and is a *reference type* OR Q is identical to P. From e3ec2292fe68f7c39b01f50a5689c47030aae066 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 28 Jul 2021 15:42:51 -0700 Subject: [PATCH 3/4] Feedback --- docs/design/specs/Ecma-335-Augments.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/design/specs/Ecma-335-Augments.md b/docs/design/specs/Ecma-335-Augments.md index 7838430e2e1e85..f6c8105334ea66 100644 --- a/docs/design/specs/Ecma-335-Augments.md +++ b/docs/design/specs/Ecma-335-Augments.md @@ -842,7 +842,7 @@ class Derived : Base } ``` -Covariant return methods can only be described through MethodImpl records, and as are only be applicable to methods on reference types. Methods on interfaces and value types are not supported. In addition the covariant override is only applicable for overriding methods also defined on a reference type. +Covariant return methods can only be described through MethodImpl records, and are only be applicable to methods on non-interface reference types. In addition the covariant override is only applicable for overriding methods also defined on a non-interface reference type. See [Implementation Design](../features/covariant-return-methods.md) for notes from the implementation in CoreCLR. @@ -897,7 +897,7 @@ For this example, the behavior of calls on objects of various types is presented | C | C::VirtualFunction() | C::VirtualFunction | " -Add a new paragraph "When a rules above result in the implementation of virtual method not satisfying the requirement that the signature of the implementor of a virtual method is not *covariant-return-compatible-with* (§I.8.7.1) the program is not considered well formed. As an example, this can happen if a virtual method is overriden with a .override directive(A) paired with a `PreserveBaseOverridesAttribute` with a *covariant-return-compatible-with* signature. and then the initial virtual method is subsequently overridden with a method which is *covariant-return-compatible-with* the initial virtual method, but not with the method body associated with .override directive(A)." +Add a new paragraph "When rules above result in the implementation of virtual method not satisfying the requirement that the signature of the implementor of a virtual method is not *covariant-return-compatible-with* (§I.8.7.1) the program is not considered well formed. As an example, this can happen if a virtual method is overriden with a .override directive(A) paired with a `PreserveBaseOverridesAttribute` with a *covariant-return-compatible-with* signature. and then the initial virtual method is subsequently overridden with a method which is *covariant-return-compatible-with* the initial virtual method, but not with the method body associated with .override directive(A)." ### II.22.27 -Edit rule 12 to specify that "The method isgnature defined by *MethodBody* shall match those defined by *MethodDeclaration* exactly if *MethodDeclaration* defines a method on an interface or be covariant-return-compatible-with* (§I.8.7.1) if *MethodDeclaration* represents a method on a class." \ No newline at end of file +Edit rule 12 to specify that "The method signature defined by *MethodBody* shall match those defined by *MethodDeclaration* exactly if *MethodDeclaration* defines a method on an interface or be *covariant-return-compatible-with* (§I.8.7.1) if *MethodDeclaration* represents a method on a class." \ No newline at end of file From b7074a297e0dd33fc38d01f3f043b06340ac09ff Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 28 Jul 2021 16:24:34 -0700 Subject: [PATCH 4/4] Make error scenario clearer, and fix more typos --- docs/design/specs/Ecma-335-Augments.md | 35 +++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/design/specs/Ecma-335-Augments.md b/docs/design/specs/Ecma-335-Augments.md index f6c8105334ea66..546a6dbc375552 100644 --- a/docs/design/specs/Ecma-335-Augments.md +++ b/docs/design/specs/Ecma-335-Augments.md @@ -842,7 +842,7 @@ class Derived : Base } ``` -Covariant return methods can only be described through MethodImpl records, and are only be applicable to methods on non-interface reference types. In addition the covariant override is only applicable for overriding methods also defined on a non-interface reference type. +Covariant return methods can only be described through MethodImpl records, and are only applicable to methods on non-interface reference types. In addition the covariant override is only applicable for overriding methods also defined on a non-interface reference type. See [Implementation Design](../features/covariant-return-methods.md) for notes from the implementation in CoreCLR. @@ -861,11 +861,12 @@ Add a new signature type relation. ### II.10.3.2 The .override directive (page 147) -Edit the first paragraph "The .override directive specifies that a virtual method shall be implemented (overridden), in this type, by a virtual method with a different name, but with a *covariant-return-compatible-with* signature (§I.8.7.1). 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 and all other. If not used to provide an implementation for a virtual method inherited from a base class the signature must be the identical." +Edit the first paragraph "The .override directive specifies that a virtual method shall be implemented (overridden), in this type, by a virtual method with a different name, but with a *covariant-return-compatible-with* signature (§I.8.7.1). 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 and all other. If not used to provide an implementation for a virtual method inherited from a base class the signature must be identical." ### II.10.3.4 Impact of overrides on derived classes Add a third bullet -"- If a virtual method is overridden via a .override directive and virtual method in parent class was overriden with a .override directive where the method body of that second .override directive is decorated with `System.Runtime.CompilerServices.PreserveBaseOverridesAttribute` then the virtual method override shall apply to the method body of the second .override directive as well. +"- If a virtual method is overridden via an .override directive or if a derived class provides an implentation and that virtual method in parent class was overriden with an .override directive where the method body of that second .override directive is decorated with `System.Runtime.CompilerServices.PreserveBaseOverridesAttribute` then the implementation of the virtual method shall be the implementation of the method body of the second .override directive as well. If this results in the implementor of the virtual method in the parent class not having a signature which is *covariant-return-compatible-with* the virtual method in the parent class, the program is not valid. + ``` .class A { @@ -885,19 +886,25 @@ Add a third bullet ... } } +.class D extends C +{ + .method virtual DerivedRetTypeNotDerivedFromMoreDerivedRetType VirtualFunction() { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = ( 01 00 00 00 ) + .override A.VirtualFuncion + ... + } +} ``` For this example, the behavior of calls on objects of various types is presented in the following table: -| Type of object | Method Invocation | Method Called | -| --- | --- | --- | -| A | A::VirtualFunction() | A::VirtualFunction | -| B | A::VirtualFunction() | B::VirtualFunction | -| B | B::VirtualFunction() | B::VirtualFunction | -| C | A::VirtualFunction() | C::VirtualFunction | -| C | B::VirtualFunction() | C::VirtualFunction | -| C | C::VirtualFunction() | C::VirtualFunction | +| Type of object | Method Invocation | Method Called | Notes | +| --- | --- | --- | --- | +| A | A::VirtualFunction() | A::VirtualFunction | | +| B | A::VirtualFunction() | B::VirtualFunction | | +| B | B::VirtualFunction() | B::VirtualFunction | | +| C | A::VirtualFunction() | C::VirtualFunction | | +| C | B::VirtualFunction() | C::VirtualFunction | | +| C | C::VirtualFunction() | C::VirtualFunction | | +| D | B::VirtualFunction() | ERROR | A program containing type D is not valid, as B::VirtualFunction would be implemented by D::VirtualFunction which is not *covariant-return-compatible-with* (§I.8.7.1) B::VirtualFunction | " - -Add a new paragraph "When rules above result in the implementation of virtual method not satisfying the requirement that the signature of the implementor of a virtual method is not *covariant-return-compatible-with* (§I.8.7.1) the program is not considered well formed. As an example, this can happen if a virtual method is overriden with a .override directive(A) paired with a `PreserveBaseOverridesAttribute` with a *covariant-return-compatible-with* signature. and then the initial virtual method is subsequently overridden with a method which is *covariant-return-compatible-with* the initial virtual method, but not with the method body associated with .override directive(A)." - ### II.22.27 Edit rule 12 to specify that "The method signature defined by *MethodBody* shall match those defined by *MethodDeclaration* exactly if *MethodDeclaration* defines a method on an interface or be *covariant-return-compatible-with* (§I.8.7.1) if *MethodDeclaration* represents a method on a class." \ No newline at end of file