-
Notifications
You must be signed in to change notification settings - Fork 207
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
Spec changes related to specific types. #3302
base: main
Are you sure you want to change the base?
Changes from all commits
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 | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1461,3 +1461,319 @@ wrapper class is so huge that it is likely to be a major use case for | |||||||||||||||
extension types that they can allow us to use a built-in class as the | ||||||||||||||||
representation, and still have a specialized interface—that is, an | ||||||||||||||||
extension type. | ||||||||||||||||
|
||||||||||||||||
## Specification changes related to specific types | ||||||||||||||||
|
||||||||||||||||
Extension types can implement class types, even class types and their members, | ||||||||||||||||
which are mentioned in the language specification. | ||||||||||||||||
Comment on lines
+1467
to
+1468
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is somewhat easy to misunderstand or get confused about. Perhaps:
Suggested change
|
||||||||||||||||
|
||||||||||||||||
By disallowing extension members with the same names as the members of `Object`, | ||||||||||||||||
we have reduced the problem to where the specification refers to members of | ||||||||||||||||
specific interfaces. | ||||||||||||||||
The specification needs to be updated to account for the existence of extension | ||||||||||||||||
types that are subtypes of those types, and which possibly shadow members from | ||||||||||||||||
the interface with extension members. | ||||||||||||||||
Comment on lines
+1470
to
+1475
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Still commentary. |
||||||||||||||||
|
||||||||||||||||
The specification, and feature specifications not yet included in the | ||||||||||||||||
specification, may uses phrases like (taken from the original collection | ||||||||||||||||
elements feature, so no longer direttly relevant): | ||||||||||||||||
|
||||||||||||||||
> 1. Evaluate the iterator expression to a value `sequence`. | ||||||||||||||||
> 2. If `sequence` is not an instance of a class that implements `Iterable`, | ||||||||||||||||
> throw a dynamic exception. | ||||||||||||||||
> 3. Evaluate `sequence.iterator` to a value `iterator`. | ||||||||||||||||
|
||||||||||||||||
Here the “Evaluate `sequence.iterator`” is imprecise, and really means “Invoke | ||||||||||||||||
the instance member `iterator` on the object `sequence` (which must exist | ||||||||||||||||
because of the second step). However, as *written*, it’s now ambiguous *which* | ||||||||||||||||
`iterator` member to invoke, in case the static type of `sequence` is an | ||||||||||||||||
extension type which implements `Iterable` and shadows `iterator`. | ||||||||||||||||
Comment on lines
+1477
to
+1490
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All commentary. |
||||||||||||||||
|
||||||||||||||||
**General Rule:** Where the existing language specification can invoke or access | ||||||||||||||||
an extension member, it can also invoke an extension type member, and where it | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Presumably, 'access' is needed in order to include tear-offs?:
Suggested change
|
||||||||||||||||
cannot invoke an extension member, it also will not invoke an extension type | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'or access' again, twice? |
||||||||||||||||
member. | ||||||||||||||||
|
||||||||||||||||
An example of the former is implicit `call` invocation: If `e1` in `e1(e2)` a | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
static type which is not a function type, but which exposes a `call` method, the | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here I actually wanted to include an extension |
||||||||||||||||
semantics is equivalent to that of `e1.call(e2)`. We apply this rule even the | ||||||||||||||||
static type of `e1` itself has no `call` method, but an extension `call` method | ||||||||||||||||
applies. If `e1` has an extension-type type, we check whether that has a `call` | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In many locations we have already used 'has an extension type' to mean 'has a type which is introduced by an
Suggested change
|
||||||||||||||||
method, and not its representation type. There is no *type* check, only a | ||||||||||||||||
*member* check. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commentary, whole paragraph (lines 1497-1503). |
||||||||||||||||
|
||||||||||||||||
The latter case is usually signaled by the specification *requiring* that | ||||||||||||||||
something implements a specific interface, and then invoking members of that | ||||||||||||||||
interface on the value. We can generally treat those cases as if they first | ||||||||||||||||
*up-cast* to the type they tested for, before invoking members, which ensure | ||||||||||||||||
that they invoke instance members. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For constant expressions where an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should probably spell out what I mean by that: We shouldn't try to "magically" find a suitable member when the standard static analysis and standard dynamic semantics of a given expression will invoke something that we don't want. Just like the constant expression, it's simply a compile-time error if the program uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. However, I'm not 100% convinced that we always want the class instance member: If the whole point around a given extension type is that it provides a better way to iterate over some elements (say, we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We generally need to distinguish between places where you must invoke an instance member, and where you may invoke an extension/extension type member. In the former cases, we can distinguish between always invoking the instance member (which a type check has ensured is there), or refusing to run if the instance member is shadowed by an extension member. In the latter cases, we may have rules that apply only if the invocation is an instance member invocation.
If we want to move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A possible attempt to specify a more structural Static semantics
This is not the only, or necessarily the best, design. (And it still hides the complexity of choosing whether to invoke an extension/extension type member or an instance member by just saying "invoke We could, possibly should, say that we give precedence to extension type overrides of Also, the above does not allow iterating something that has an instance member named
and runtime behavior would be just like desugaring {
S $tmp1 = e;
R $tmp2 = $tmp.iterator;
labels: while ($tmp2.moveNext()) {
decl = $tmp2.current;
{ body }
}
} Here we don't try to do any kind of downcast from But you can iterate anything, whether it's an |
||||||||||||||||
|
||||||||||||||||
An example of that is something like `yield*`, which checks statically that the | ||||||||||||||||
operand’s type is assignable to `Iterable<T>` for some `T`, at runtime checks | ||||||||||||||||
that the value’s runtime type implements `Iterable<T>` (only necessary if the | ||||||||||||||||
static type is `dynamic`), and then invokes `iterator` and `moveNext`/`current` | ||||||||||||||||
on the result. Those invocations *must* be on the instance members. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 100% sure? ;-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is intended as prescriptive, not predictive. It will invoke the instance members. |
||||||||||||||||
|
||||||||||||||||
We still need to check everywhere that the phrasing doesn’t make assumptions | ||||||||||||||||
about *the static type of the instance member*, which might not be visible when | ||||||||||||||||
shadowed by and extension type. See “Iterable and `for`/`in` below” for an | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
example where the current specification no longer works, mainly because it’s | ||||||||||||||||
defined by a rewrite, without being explicit about the static types of the | ||||||||||||||||
rewritten code, and assuming that invoking `.iterator` on a subtype of | ||||||||||||||||
`Iterable` has a predictable result. | ||||||||||||||||
|
||||||||||||||||
The types and places where specific types and members are mentions in the | ||||||||||||||||
specification include the following. | ||||||||||||||||
|
||||||||||||||||
#### Object methods | ||||||||||||||||
|
||||||||||||||||
Any reference to invoking `==`, `hashCode`, `runtimeType`, `toString` or | ||||||||||||||||
`noSuchMethod` are safe, since they cannot be shadowed by extension types. No | ||||||||||||||||
changes needed. | ||||||||||||||||
|
||||||||||||||||
#### The types `dynamic`, `void`, `FutureOr`, `Function`, `Record`, `Null` and `Never` | ||||||||||||||||
|
||||||||||||||||
An extension type cannot implement any of these types. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not have any rules about It is not obvious that this will create any issues (other than being useless): extension type NotEver(Never _) implements StatelessWidget {} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should not be allowed to implement it. Using it as representation type is fine. Implementing it is introducing a new nominative bottom type, which is too weird. An extension type can only implement interface types and extension types, and among the types that could potentially be counted as interfaces types (depending on perspective), an extension type cannot implement any of the types mentioned here. Which are precisely the types that we sometimes argue back and forth over whether they are normal class types with special rules, or special types where some have a fake declaration that looks like a class declaration. It doesn't matter what they really are, as long as the spec explicitly puts them in the correct categories for everything where we make a distinction. |
||||||||||||||||
|
||||||||||||||||
The specification can still safely assume that a subtype of any of `Function`, | ||||||||||||||||
`Record`, `Null` and `Never` does not have any extension type methods to worry | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, we might constrain There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also not sure it creates any issues to allow implementing |
||||||||||||||||
about. | ||||||||||||||||
|
||||||||||||||||
The `dynamic` and `void` types are just top-types with special semantics, but | ||||||||||||||||
that only applies to that type, it’s not inherited by subtypes, so the | ||||||||||||||||
specification only checks whether a type *is* `dynamic` or `void`, not whether | ||||||||||||||||
it’s a subtype. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, just a comment on this: Extension types are subtypes of these top types just like all other types, and there is no need to add new rules about the treatment of expressions of type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Precisely, this is an "nothing new to worry about here" comment. |
||||||||||||||||
|
||||||||||||||||
It’s not possible to implement `FutureOr<T>` (or the other union type, `T?`), | ||||||||||||||||
only “interface types”. This ensures that an extension type doesn’t have | ||||||||||||||||
union-type like subtyping behavior. | ||||||||||||||||
|
||||||||||||||||
No changes are needed related to the treatment of these types in the | ||||||||||||||||
specification. | ||||||||||||||||
|
||||||||||||||||
#### Conditions and `bool` | ||||||||||||||||
|
||||||||||||||||
Expressions in condition position (`if (condition) …`, (`do {…}`)`while | ||||||||||||||||
(condition)`, `condition ? … : …`, `condition || condition`, `condition && | ||||||||||||||||
condition`, `!condition`, `… when condition` ) *must* have a type assignable to | ||||||||||||||||
`bool`. | ||||||||||||||||
|
||||||||||||||||
There is no change to that. An extension type occurring in such a position is a | ||||||||||||||||
compile-time error if it does not implement `bool`., and not if it does | ||||||||||||||||
implement `bool`. All runtime behavior on `bool` values amount to checking | ||||||||||||||||
whether it’s the `true` or the `false` value, which is still sound. _I do not | ||||||||||||||||
believe we make any assumptions about members of `bool`, or invoke any | ||||||||||||||||
explicitly._ | ||||||||||||||||
|
||||||||||||||||
#### Numbers and arithmetic operators | ||||||||||||||||
|
||||||||||||||||
We have special rules in type inference for number operations on `int`, `num` | ||||||||||||||||
and `double`, to ensure that the static type of `1 + 1` is `int`. We effectively | ||||||||||||||||
pretend to have overloaded operators for typing only. | ||||||||||||||||
|
||||||||||||||||
The [newest | ||||||||||||||||
version](https://github.com/dart-lang/language/blob/main/accepted/2.12/nnbd/number-operation-typing.md) | ||||||||||||||||
of those rules came with null safety. The way the rules are written, they apply, | ||||||||||||||||
e.g., to `e1 + e2` when the static type of `e1` is a non-`Never` subtype of | ||||||||||||||||
`int` (the receiver is an `int`, maybe typed as a type variable `X` with bound | ||||||||||||||||
`int`), and the static type of `e2` is a subtype of `int`. That needs to also | ||||||||||||||||
say that the `+` must denote an instance member, to avoid applying to an | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We've used the phrase 'extension instance member' for a few years in order to be able to say 'extension static member' as well, and I expect that we'll have to say 'extension type instance member' and 'extension type static member', too. So it's better to say 'class instance member' here:
Suggested change
|
||||||||||||||||
extension type implementing `int` and declaring an extension type `+` operator. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (whitespace)
Suggested change
|
||||||||||||||||
|
||||||||||||||||
That amounts to two sections being changed by inserting something to the effect | ||||||||||||||||
of “where it’s an instance member”, additions italicized here: | ||||||||||||||||
|
||||||||||||||||
>Let `e` be an expression of one of the forms `e1 + e2`, `e1 - e2`, `e1 * e2`, | ||||||||||||||||
>`e1 % e2` or `e1.remainder(e2)`, where the static type of `e1` is a | ||||||||||||||||
>non-~~`Never`~~_bottom_ type *T* and *T* <: `num`, ~~and~~ where the static | ||||||||||||||||
>type of `e2` is *S* and *S* is assignable to `num`_, and where *T* has an | ||||||||||||||||
>implementation of the operator, or `remainder` method, that is an instance | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
>member_. Then: | ||||||||||||||||
|
||||||||||||||||
and | ||||||||||||||||
|
||||||||||||||||
>Let `e` be a normal invocation of the form `e1.clamp(e2, e3)`, where the static | ||||||||||||||||
>types of `e1`, `e2` and `e3` are *T*<sub>1</sub>, *T*<sub>2</sub> and | ||||||||||||||||
>*T*<sub>3</sub> respectively, ~~and~~ where *T*<sub>1</sub>, *T*<sub>2</sub>, | ||||||||||||||||
>and *T*<sub>3</sub> are all non-~~`Never`~~_bottom_ subtypes of `num`_, and | ||||||||||||||||
>where *T*<sub>1</sub> has an instance member named `clamp`_ . Then: | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||
|
||||||||||||||||
This will prevent the rules from applying to invocations of extension type | ||||||||||||||||
operators or methods, which shadow the platform methods that we know and trust. | ||||||||||||||||
|
||||||||||||||||
No other changes should be needed. The remainder of the rules are about context | ||||||||||||||||
types, and require the types involved to be *supertypes* of a number interface | ||||||||||||||||
type, which extension types never are. | ||||||||||||||||
|
||||||||||||||||
#### Patterns | ||||||||||||||||
|
||||||||||||||||
All map pattern entry patterns and list pattern element patterns are define in | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the pattern feature specification, we have no occurrences of 'entry pattern' or 'element pattern' denoting the entire construct (such as
Suggested change
|
||||||||||||||||
terms of calling `length,`, `operator[]` and/or `containsKey` on the object. All | ||||||||||||||||
these invocations *must* be instance member invocations, whether the static type | ||||||||||||||||
of the matched value type is an extension type implementing `Map` or `List` or | ||||||||||||||||
not, and whether it defines its own `length`, `operator[]` or `containsKey` | ||||||||||||||||
extension type members. For all practical purposes, the value is cast to `Map<K, | ||||||||||||||||
V>`/`List<E>` for some `K` and `V`, or `E`, *before* accessing elements, and it | ||||||||||||||||
uses the caching behavior of instance elements. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer if we avoid the "magic" and simply make it a compile-time error for a collection pattern to invoke Next, I'm not 100% convinced that it should be an error. It could be a useful hook. Just talked about this IRL: We might consider allowing extension type instance members and/or extension instance members to provide the required members to support iteration: So the requirement for being usable in a We might want to treat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That could apply to collection patterns in declaration patterns, but not in refutable patterns. That is, it's a compile-time error if the matched value type of an irrefutable list pattern (in a declaration/assignment pattern) has an extension type instance member named Or we can say that the list/map patterns always perform class instance member invocations for those methods, which is possibly what it says today. (But most likely it says nothing conclusive, because there used to be nothing to say.) |
||||||||||||||||
|
||||||||||||||||
Object patterns, on the other hand, base their member accesses on the *static | ||||||||||||||||
type* of the object pattern type. A `case ExtensionType(foo: p1, bar: p2)` use | ||||||||||||||||
the `foo` and `bar` getters of `ExtensionType`, whether they are extension type | ||||||||||||||||
members, instance members, or even extension members. _It follows the general | ||||||||||||||||
rule in being a place where extension members are allowed today._ | ||||||||||||||||
|
||||||||||||||||
Relational patterns can use extension and extension type implementations of `<`, | ||||||||||||||||
`<=`, `>` and `>=`, and the `==` operator *cannot* be shadowed by extension | ||||||||||||||||
methods. | ||||||||||||||||
|
||||||||||||||||
#### Iterable and `for`/`in` | ||||||||||||||||
|
||||||||||||||||
A `for (D x in e) body` loop, whether statement or element, with value | ||||||||||||||||
declaration `D x` and `e` having static type, `T`, we require that `T` is | ||||||||||||||||
assignable to `Iterable<dynamic>`, which we today knows is equivalent to | ||||||||||||||||
implementing `Iterable<S>` for some type `S`, or being `dynamic` or a bottom | ||||||||||||||||
type. | ||||||||||||||||
|
||||||||||||||||
The behavior of the for-in statement is currently specified by a *rewrite* to | ||||||||||||||||
the following code: | ||||||||||||||||
|
||||||||||||||||
```dart | ||||||||||||||||
T _$id1 = e; | ||||||||||||||||
var _$id2 = _$id1.iterator; | ||||||||||||||||
while (_$id2.moveNext()) { | ||||||||||||||||
D x = _$id2.current; | ||||||||||||||||
{ | ||||||||||||||||
body | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
That rewrite is no longer valid with extension types. The type `T` may be an | ||||||||||||||||
extension type which *does* implement `Iterable<S>` for some `S`, but which then | ||||||||||||||||
*shadows* the default `iterator`, and returns something entirely unrelated to | ||||||||||||||||
`Iterator<S>`. _We do not intend to invoke that extension type member._ | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned, we could do it anyway. |
||||||||||||||||
|
||||||||||||||||
The behavior of a `for`/`in` element is defined in terms of a `for`/`in` | ||||||||||||||||
statement. | ||||||||||||||||
|
||||||||||||||||
##### New specification: | ||||||||||||||||
|
||||||||||||||||
It’s (still) a compile-time error if `T` is not assignable to | ||||||||||||||||
`Iterable<dynamic>`. _This, also still, means that `T` is a bottom type, | ||||||||||||||||
`dynamic` or a type which implements `Iterable<S>` for some `S`. That type may | ||||||||||||||||
just also be an extension type._ | ||||||||||||||||
|
||||||||||||||||
If `T` implements `Iterable<S>`, let `E` be `S`, otherwise let `E` be `Never` if | ||||||||||||||||
`T` is a bottom type, or `dynamic` if `T` is `dynamic`. | ||||||||||||||||
|
||||||||||||||||
Now rewrite the `for`-`in` loop to: | ||||||||||||||||
|
||||||||||||||||
```dart | ||||||||||||||||
Iterable<E> _$id1 = e; | ||||||||||||||||
Iterator<E> _$id2 = _$id1.iterator; | ||||||||||||||||
while (_$id2.moveNext()) { | ||||||||||||||||
D x = _$id2.current; | ||||||||||||||||
{ | ||||||||||||||||
body | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
Like before, any errors that would occur in that program are reported as errors | ||||||||||||||||
in the original `for`-`in` statement. | ||||||||||||||||
|
||||||||||||||||
_**TODO**: Specify this without using a “desugaring” rewrite. Problems like | ||||||||||||||||
these are exactly why rewrites are problematic, they tend to introduce *more* | ||||||||||||||||
information, through the extra syntax or inferred types, than what the original | ||||||||||||||||
program warranted. Also, the rewrite supposedly happens after type inference, so | ||||||||||||||||
we need to either perform type inference on the desugared code, or assign a | ||||||||||||||||
context type and static type to each new expression, because those are | ||||||||||||||||
referenced by the dynamic semantics._ | ||||||||||||||||
|
||||||||||||||||
##### Consequences to existing code | ||||||||||||||||
|
||||||||||||||||
The only real change is that the type of `_$id1` is not the static type of `e`. | ||||||||||||||||
That means that if there is a class which overrides its iterator to be more | ||||||||||||||||
specific than required, that information is lost. | ||||||||||||||||
|
||||||||||||||||
Example: | ||||||||||||||||
|
||||||||||||||||
```dart | ||||||||||||||||
class VeryInts extends Iterable<num> { | ||||||||||||||||
Iterator<int> get iterator => <int>[1].iterator; | ||||||||||||||||
} | ||||||||||||||||
void main() { | ||||||||||||||||
for (int i in VeryInts()) print(i); | ||||||||||||||||
} | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
With the previous *specification*, the type that `.iterator` should be accessed | ||||||||||||||||
on was `VeryInts`, which means that `_$id2` will get static type | ||||||||||||||||
`Iterator<int>`, and its `.current` is then assignable to `int i`. With the new | ||||||||||||||||
specification, we don’t look at the members of the actual type, instead we | ||||||||||||||||
immediately up-cast to `Iterable<E>` and work with that. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd definitely prefer that the developer must write that upcast and the treatment after that point is completely standard, rather than getting into a situation where some members are called based on the type |
||||||||||||||||
|
||||||||||||||||
*Luckily*, our implementations already do that: CFE (Dart2js on DartPad): | ||||||||||||||||
|
||||||||||||||||
> ``` | ||||||||||||||||
> lib/main.dart:5:12: | ||||||||||||||||
> Error: A value of type 'num' can't be assigned to a variable of type 'int'. | ||||||||||||||||
> for (int i in VeryInts()) print(i); | ||||||||||||||||
> ^ | ||||||||||||||||
> ``` | ||||||||||||||||
|
||||||||||||||||
and Analyzer: | ||||||||||||||||
|
||||||||||||||||
> ``` | ||||||||||||||||
> error: line 5 • The type 'VeryInts' used in the 'for' loop must implement 'Iterable' with a type argument that can be assigned to 'int'. | ||||||||||||||||
> ``` | ||||||||||||||||
|
||||||||||||||||
So, this looks like it might be a spec-only change for the static semantics, to | ||||||||||||||||
make it match the actual implementation, and it’s *likely* that the | ||||||||||||||||
implementations will also do the right thing when faced with extension types. | ||||||||||||||||
They *must* only invoke instance `iterator`, `moveNext` and `current` members. | ||||||||||||||||
|
||||||||||||||||
#### Iterables and synchronous `yield*` | ||||||||||||||||
|
||||||||||||||||
The synchronous `yield*` operator takes an expression which, like `for`/`in`, | ||||||||||||||||
must be assignable to `Iterable<T>`, and it too must use only instance members to | ||||||||||||||||
iterate the operand where it state that it invokes `iterator`, `moveNext` and | ||||||||||||||||
`current`. | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably have the same level of support for a structural extension-or-extension-type based iteration in a |
||||||||||||||||
|
||||||||||||||||
#### Stream and `await for`, asynchronous `yield*` | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should use rules that are as similar as possible with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If so, then the rule should be to not invoke extension type members. I really do not want to get into defining which part of the
Basically, you have to return something which implements the entire So I want to enforce that whatever Also, the compilation of async operations is much more complicated than a simple The story of "An |
||||||||||||||||
|
||||||||||||||||
The `await for` specification is deliberately vague in how it’s implemented. | ||||||||||||||||
Like synchronous `for`/`in`, it checks that the stream expression’s static type | ||||||||||||||||
is assignable to `Stream<Object?>`, which again means implementing `Stream<S>` | ||||||||||||||||
for some `S` or being `dynamic` or `Never`. Then it “listens on the stream”, and | ||||||||||||||||
“when an event is emitted”, it executes the loop body. | ||||||||||||||||
|
||||||||||||||||
It does talk about a “stream subscription”, and calling `pause`, `resume` and | ||||||||||||||||
`cancel` on that. Every call, including the alluded `listen` call on the | ||||||||||||||||
original stream, must be an instance method call on a `Stream` or | ||||||||||||||||
`StreamSubscription`-typed object. Any assumptions about the static types | ||||||||||||||||
of such a call is based on the element type, which is `S` if the stream | ||||||||||||||||
expression's static type implements `Stream<S>`, and `Never` or `dynamic` | ||||||||||||||||
if it is a bottom type or `dynamic`. | ||||||||||||||||
|
||||||||||||||||
The behavior of an `async for` element is defined in terms of an `async for` | ||||||||||||||||
statement, anhd should just work if the other one does. | ||||||||||||||||
|
||||||||||||||||
The same vagueness applies to `yield*`, and it too must use only | ||||||||||||||||
instance methods to process the stream elements. | ||||||||||||||||
|
||||||||||||||||
#### Futures and `await`, asynchronous `return` | ||||||||||||||||
|
||||||||||||||||
An `await`, and the implicit optional await built into an `async` function’s | ||||||||||||||||
`return`, checks whether its operand is a `Future<T>`, where `T` is | ||||||||||||||||
*flatten*(`S`) and `S` is the static type of the operand. | ||||||||||||||||
|
||||||||||||||||
When it comes to *extension types*, the *flatten* function works without | ||||||||||||||||
changes. If an extension type `E` implements `Future<S>`, then *flatten*(`E`) is | ||||||||||||||||
`S`, just as for an instance type, and if not, *flatten*(`E`) is `E`. _This is | ||||||||||||||||
safe and sound. (We can prove that, but won’t do so here. The rule “`T` \<: | ||||||||||||||||
<code>FutureOr\<*flatten*(T)\></code> for all `T`" still applies, both before | ||||||||||||||||
and after extension type erasure.)_ | ||||||||||||||||
|
||||||||||||||||
Implementations must ensure that they don’t call any extension type `then` | ||||||||||||||||
methods or similar, but should not otherwise be affected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be located before the 'Discussion' section, because it contains normative elements.
We might also separate this into normative material (probably rather short) and a background part.
For instance, the paragraph in lines 1467-1468 serves as an introduction to the overall topic ("this is about classes mentioned in the language specification"), but it starts by noting a fact which is already specified (in terms of rules about
implements
on extension types).