-
Notifications
You must be signed in to change notification settings - Fork 215
Feature specification of #3090, allowing an inline class to implement its representation type #3138
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
Closed
Closed
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b3708e0
First draft feature specification of #3090
eernstg 59c1f50
Add missing section lines
eernstg de9ad29
Cleaned up TODO, added the missing commentary that was intended for that
eernstg 3d9d894
Review response
eernstg 03b2cb9
Review response
eernstg a8ceb4c
review response
eernstg 6da38a6
Review response
eernstg e4250bb
Add commentary about implementation specific behavior
eernstg cd1b43c
Review response: Improved description of member name clash conflict r…
eernstg 53a2b9e
Review response: Improved the conflict spec a bit moer
eernstg 23ede5a
Typo
eernstg 15f37e4
Added error for `implements X`; we may enable that later, but for now…
eernstg f45f63a
Add rule about non-error for `implements B` where `B` is a non-inline…
eernstg b9f4c5f
Add errors for multiple occurrences of the same class in the superint…
eernstg 613287c
Removed special case for non-inline superinterfaces (where we allowed…
eernstg 483efad
Review response
eernstg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
268 changes: 268 additions & 0 deletions
268
working/1426-extension-types/feature-specification-implements-rep.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
# Inline Classes can Implement the Representation Type | ||
|
||
Author: [email protected] | ||
|
||
Status: Draft. | ||
|
||
This is an addendum to the [inline class feature specification][], | ||
proposing an additional feature: An inline class `V` may have its | ||
representation type `R` as a superinterface. This causes `V` to be a | ||
subtype of `R`, and it enables invocations of members of `R` on a receiver | ||
of type `V`. | ||
|
||
[inline class feature specification]: https://github.com/dart-lang/language/blob/main/accepted/future-releases/inline-classes/feature-specification.md | ||
|
||
## Change Log | ||
|
||
## Summary | ||
|
||
This is a proposal to add a new feature to inline classes: An inline class `V` | ||
may include one or more superinterfaces in its `implements` clause which are | ||
supertypes `R1 .. Rk` of the ultimate representation type `R` of `V` (this | ||
allows `implements R` as a special case). This causes `V` to be a subtype | ||
of each of `Rj`, `j` in `1 .. k`, and it enables invocations of members of | ||
`Rj` on a receiver of type `V`. | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
As it is currently | ||
[specified](https://github.com/dart-lang/language/blob/main/accepted/future-releases/inline-classes/feature-specification.md), | ||
the `implements` clause of an inline class declaration is used to establish | ||
a subtype relationship to other inline classes. This allows the given | ||
inline class to "inherit" member implementations from those other inline | ||
classes, and it establishes a subtype relationship. For example: | ||
|
||
```dart | ||
inline class A { | ||
final int i; | ||
A(this.i); | ||
int get next => i + 1; | ||
} | ||
|
||
inline class B implements A { | ||
final int i; | ||
B(this.i); | ||
} | ||
|
||
var b = B(1).next; // OK. | ||
A a = b; // OK. | ||
int i = b; // Error: `B` is not assignable to `int`. | ||
``` | ||
|
||
Another subtype relationship which can safely be adopted is that the inline | ||
class is a subtype of its representation type, or any supertype of the | ||
representation type. This proposal broadens the applicability of the | ||
`implements` clause to establish that kind of subtype relationship as well: | ||
|
||
```dart | ||
inline class A implements int { // `A` is a subtype of `int`. | ||
final int i; | ||
A(this.i); | ||
} | ||
|
||
var a = A(2); | ||
int i = a; // OK. | ||
List<int> list = <A>[a]; // OK. | ||
``` | ||
|
||
This subtype relationship is sound because the run-time value of an | ||
expression of type `A` is an instance of `int`. | ||
|
||
This subtype relationship may be desirable in the case where there is no | ||
need to protect an object accessed as an `A` against being accessed as an | ||
`int`, and it is even considered to be useful that it will readily "become | ||
an `int` whenever needed". | ||
|
||
## Specification | ||
|
||
### Static analysis | ||
|
||
We need to introduce the _ultimate representation type_ of an inline type | ||
`V<T1 .. Tk>`: This is the instantiated representation type `R` of `V` if | ||
that is a type that is not and does not contain any inline | ||
types. Otherwise, assume that `R` is `V1<U1 .. Us>`, then the ultimate | ||
representation type of `V` is the ultimate representation type of | ||
`V1<W1 .. Ws>` where `Wj` is the ultimate representation type of `Uj`, for | ||
`j` in `1 .. s`. Similarly for function types and other composite types. | ||
|
||
*In this document it is assumed that the ultimate representation type | ||
exists. This is ensured by means of a static check on each inline class | ||
declaration, as specified in the inline class feature specification.* | ||
|
||
The permission for an inline class declaration to have a non-inline | ||
superinterface is expressed by changing the feature specification text | ||
in the section [Composing inline | ||
classes](https://github.com/dart-lang/language/blob/main/accepted/future-releases/inline-classes/feature-specification.md#composing-inline-classes) | ||
which is currently as follows: | ||
|
||
> A compile-time error occurs if _V1_ is a type name or a parameterized type which occurs as a superinterface in an inline class declaration _DV_, but _V1_ does not denote an inline type. | ||
|
||
It is adjusted to end as follows: | ||
|
||
> ... declaration _DV_, unless _V1_ denotes an inline type, or _V1_ is a supertype of the ultimate representation type of _DV_. | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
*The fact that the non-inline superinterface must be a supertype of the | ||
ultimate representation type rather than just the representation type is | ||
helpful in the case where the representation type is itself an inline | ||
type:* | ||
|
||
```dart | ||
inline class A { | ||
final num n; | ||
A(this.n); | ||
} | ||
|
||
inline class B implements num { // OK. | ||
final A a; | ||
A(this.a); | ||
} | ||
``` | ||
|
||
*This is allowed because the representation object for an expression of | ||
type `B` is an object of type `num`, because it is the representation | ||
object of an expression of type `A`. Note that there is no need for (or any | ||
problem with) a subtype relationship between `A` and `B`. The relationship | ||
between an inline type and its instantiated representation type and the | ||
subtype relationship for an inline type are independent concepts.* | ||
|
||
Let _DV_ be an inline class declaration named `V` with representation type | ||
`R` and assume that the `implements` clause of _DV_ includes the non-inline | ||
types `R1 .. Rk`. *We then have `R <: Rj` for each `j`, because anything | ||
else is an error.* | ||
|
||
Assume that `m` is a member which not declared by _DV_, and none of _DV_'s | ||
inline superinterfaces have a member named `m`, but one or more of the | ||
interfaces of `R1 .. Rk` has a member named `m`. A compile-time error | ||
occurs if there exist `j1` and `j2` in `1 .. k` and a member name `m` such | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
that `m` does not have a combined member signature for `R1 | ||
.. Rk`. Otherwise the member signature of `m` is that combined member | ||
signature. | ||
|
||
Invocations of members declared by _DV_ or declared by an inline | ||
superinterface of _DV_ and not declared by any of `Rj`, `j` in `1..k` are | ||
unaffected by the fact that _DV_ implements `R1 .. Rk`. | ||
|
||
*This could be an invocation of a member declared by _DV_, or by any of its | ||
non-inline superinterfaces, or both, but the rules are unchanged.* | ||
|
||
Let `m` be a member name which is not declared by _DV_. Assume that the | ||
interface of `Rj` has a member named `m`. A compile-time error occurs if an | ||
inline superinterface of _DV_ also declares a member named `m`. | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Let `m` be a member name which is not declared by _DV_ and not declared by | ||
an inline superinterface of _DV_. Assume that the interface of `Rj` has a | ||
member named `m` with signature `s` *(this is the combined member signature | ||
that may depend on other types in `R1 .. Rk`)*. An invocation of `m` on a | ||
receiver of type `V` (or `V<T1 .. Ts>` if _DV_ is generic) is then treated | ||
as the same invocation, but with signature `s`. | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
*In other words: Conflicts among superinterfaces are treated the same, | ||
whether it is an inline or a non-inline superinterface. In both cases, _DV_ | ||
can resolve the conflict by redeclaring the given member. No override check | ||
is applied, any signature with the given name will resolve the conflict. If | ||
there is no conflict then _DV_ will "forward to" the members of `R0`.* | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
*An implementation may choose to implicitly induce forwarding members into | ||
_DV_ in order to enable invocation of members of `R0`. However, such | ||
forwarding members must preserve the semantics of a direct invocation. In | ||
particular, if an invocation omits some optional parameters then the | ||
invocation of a member of `R0` must use the default value for that | ||
parameter of the actually invoked instance method, not a statically known | ||
value.* | ||
|
||
```dart | ||
class C { | ||
int m([int i = 0]) => i; | ||
} | ||
|
||
class D extends C { | ||
int m([int i = 42, j = -1]) => i; | ||
} | ||
|
||
inline class V implements C { | ||
final C it; | ||
V(this.it); | ||
} | ||
|
||
void main() { | ||
V v = V(D()); | ||
// v.m(3, 4); // Compile-time error: `m` signature is from `C`. | ||
v.m(); // Returns 42, not 0. | ||
} | ||
``` | ||
|
||
It is an implementation specific behavior whether a closurization | ||
*(also known as a tear-off)* of an inline class instance member which is | ||
obtained from the interface of `R0` is a tear-off of the member of the | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
representation object, or it is a tear-off of an implicitly induced | ||
forwarding method. | ||
|
||
*This makes no difference for the behavior of an invocation of the | ||
tear-off, but it does change the results returned by `==`.* | ||
eernstg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
A member of the interface of _DV_ which is obtained from `R0` is available | ||
for subtypes in the same manner as members obtained from other | ||
superinterfaces of _DV_ and members declared by _DV_. *For example:* | ||
|
||
```dart | ||
inline class A implements int { | ||
final int i; | ||
A(this.i); | ||
} | ||
|
||
inline class B implements A { | ||
final int i; | ||
B(this.i); | ||
} | ||
|
||
void main() { | ||
B b = B(42); | ||
b.isEven; // OK. | ||
} | ||
``` | ||
|
||
## Dynamic Semantics | ||
|
||
When an expression of the form `e.m(args)` (or any other member access, | ||
e.g., `e.m` or `e.m = e2`) has a receiver `e` whose static type is an | ||
inline type `V`, and `m` is a member of one or more non-inline | ||
superinterfaces of `V`, it is performed as a member access of `m` as an | ||
instance member of the ultimate representation type of `e`. | ||
|
||
*In other words, invocations of members of non-inline superinterfaces of an | ||
inline type receiver are forwarded to the representation object.* | ||
|
||
*It is an implementation specific choice whether this invocation of an | ||
instance member of the ultimate representation type is performed directly | ||
or as an invocation of a forwarding function.* | ||
|
||
## Discussion | ||
|
||
We have discussed how to provide access to members of the representation | ||
type of an inline class in a more flexible manner. | ||
|
||
One approach would be to say that every abstract declaration in an inline | ||
class is a request for a forwarding method (or an inlined forwarding | ||
semantics) with respect to the interface of the representation type. | ||
|
||
```dart | ||
inline class V { | ||
final int i; | ||
V(this.i); | ||
bool get isEven; // Abstract, requests forwarder to `i.isEven`. | ||
} | ||
``` | ||
|
||
Another approach would be to use an `export` directive of sorts, | ||
cf. https://github.com/dart-lang/language/issues/2506. | ||
|
||
```dart | ||
inline class V { | ||
final int i; | ||
V(this.i); | ||
export i show isEven; | ||
} | ||
``` | ||
|
||
There are many trade-offs. For example, the abstract method may seem more | ||
familiar, but the export mechanism avoids redeclaring the | ||
signature. Further discussions about this topic can be seen in github | ||
issues, in particular the one mentioned above. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.