diff --git a/resources/type-system/inference.md b/resources/type-system/inference.md index f33b439e78..2f6b93180e 100644 --- a/resources/type-system/inference.md +++ b/resources/type-system/inference.md @@ -6,6 +6,13 @@ Status: Draft ## CHANGELOG +2023.06.24 + - Adjust function literal return type inference to avoid spurious application + of `flatten`, and make sure return statements do not affect generator + functions. + - Specify the value context to use for generator functions for more + non-trivial return types (not just `Iterable` for some `X`). + 2022.05.12 - Define the notions of "constraint solution for a set of type variables" and "Grounded constraint solution for a set of type variables". These @@ -260,11 +267,11 @@ unlike normal fields, the initializer for a `late` field may reference `this`. Function literals which are inferred in an empty typing context (see below) are inferred using the declared type for all of their parameters. If a parameter has no declared type, it is treated as if it was declared with type `dynamic`. -Inference for each returned expression in the body of the function literal is -done in an empty typing context (see below). +Inference for each returned or yielded expression in the body of the function +literal is done with an empty _value context_ scheme (see below). Function literals which are inferred in an non-empty typing context where the -context type is a function type are inferred as described below. +context type is a function type are inferred as described here. Each parameter is assumed to have its declared type if present. If no type is declared for a parameter and there is a corresponding parameter in the context @@ -276,19 +283,21 @@ corresponding parameter in the context type schema, the variable is treated as having type `dynamic`. The return type of the context function type is used at several points during -inference. We refer to this type as the **imposed return type -schema**. Inference for each returned or yielded expression in the body of the -function literal is done using a context type derived from the imposed return -type schema `S` as follows: +inference. We refer to this type as the **imposed return type schema**. + +Inference for each returned or yielded expression in the body of the +function literal is done using a **value context** type scheme derived from the +imposed return type schema `S` as follows: + - If function is declared `async`, then without null safety, + the value context scheme is **flatten**(`S`); + with null safety the value context scheme is + **futureValueTypeSchema**(`S`), as defined below. + - If the function expression is declared `async*` then the value context + scheme is **streamElementTypeSchema**(`S`), as defined below. + - If the function expression is declared `sync*` then the value context + scheme is **iterableElementTypeSchema**(`S`), as defined below. - If the function expression is neither `async` nor a generator, then the - context type is `S`. - - If the function expression is declared `async*` and `S` is of the form - `Stream` for some `S1`, then the context type is `S1`. - - If the function expression is declared `sync*` and `S` is of the form - `Iterable` for some `S1`, then the context type is `S1`. - - Otherwise, without null safety, the context type is `FutureOr` - where `T` is the imposed return type schema; with null safety, the context - type is `FutureOr`. + value context scheme is `S`. The function **futureValueTypeSchema** is defined as follows: @@ -299,66 +308,143 @@ The function **futureValueTypeSchema** is defined as follows: - **futureValueTypeSchema**(`void`) = `void`. - **futureValueTypeSchema**(`dynamic`) = `dynamic`. - **futureValueTypeSchema**(`_`) = `_`. -- Otherwise, for all `S`, **futureValueTypeSchema**(`S`) = `Object?`. - -_Note that it is a compile-time error unless the return type of an asynchronous -non-generator function is a supertype of `Future`, which means that -the last case will only be applied when `S` is `Object` or a top type._ +- Otherwise, for all other `S`, **futureValueTypeSchema**(`S`) = `Object?`. + +The function **streamElementTypeSchema** is defined as follow: + +- **streamElementTypeSchema**(`S?`) = **streamElementTypeSchema**(`S`), + for all `S`. +- **streamElementTypeSchema**(`S*`) = **streamElementTypeSchema**(`S`), + for all `S`. +- **streamElementTypeSchema**(`FutureOr`) = **streamElementTypeSchema**(`S`), + for all `S`. +- **streamElementTypeSchema**(`Stream`) = `S`, for all `S`. +- **streamElementTypeSchema**(`void`) = `void` +- **streamElementTypeSchema**(`dynamic`) = `dynamic` +- **streamElementTypeSchema**(`_`) = `_` +- Otherwise, for all other `S`, **streamElementTypeSchema**(`S`) = `Object?`. + +The function **iterableElementTypeSchema** is defined as follow: + +- **iterableElementTypeSchema**(`S?`) = **iterableElementTypeSchema**(`S`), + for all `S`. +- **iterableElementTypeSchema**(`S*`) = **iterableElementTypeSchema**(`S`), + for all `S`. +- **iterableElementTypeSchema**(`FutureOr`) = + **iterableElementTypeSchema**(`S`), for all `S`. +- **iterableElementTypeSchema**(`Iterable`) = `S`, for all `S`. +- **iterableElementTypeSchema**(`void`) = `void` +- **iterableElementTypeSchema**(`dynamic`) = `dynamic` +- **iterableElementTypeSchema**(`_`) = `_` +- Otherwise, for all other `S`, **iterableElementTypeSchema**(`S`) = `Object?`. + +_Note that it is a compile-time error unless the return type of an +asynchronous non-generator function is a supertype of `Future`, +which means that the last case of **futureValueTypeScheme** will only +be applied when `S` is `Object`. +Similarly for `async*` and `sync*` functions whose return types +must be a supertypes of `Stream` and `Iterable`. +These requirements also prevent the return type from being a type variable._ In order to infer the return type of a function literal, we first infer the -**actual returned type** of the function literal. - -The actual returned type of a function literal with an expression body is the -inferred type of the expression body, using the local type inference algorithm -described below with a typing context as computed above. - -The actual returned type of a function literal with a block body is computed as -follows. Let `T` be `Never` if every control path through the block exits the -block without reaching the end of the block, as computed by the **definite -completion** analysis specified elsewhere. Let `T` be `Null` if any control -path reaches the end of the block without exiting the block, as computed by the -**definite completion** analysis specified elsewhere. Let `K` be the typing -context for the function body as computed above from the imposed return type -schema. - - For each `return e;` statement in the block, let `S` be the inferred type of - `e`, using the local type inference algorithm described below with typing - context `K`, and update `T` to be `UP(flatten(S), T)` if the enclosing - function is `async`, or `UP(S, T)` otherwise. - - For each `return;` statement in the block, update `T` to be `UP(Null, T)`. - - For each `yield e;` statement in the block, let `S` be the inferred type of - `e`, using the local type inference algorithm described below with typing - context `K`, and update `T` to be `UP(S, T)`. - - If the enclosing function is marked `sync*`, then for each `yield* e;` - statement in the block, let `S` be the inferred type of `e`, using the - local type inference algorithm described below with a typing context of - `Iterable`; let `E` be the type such that `Iterable` is a - super-interface of `S`; and update `T` to be `UP(E, T)`. - - If the enclosing function is marked `async*`, then for each `yield* e;` - statement in the block, let `S` be the inferred type of `e`, using the - local type inference algorithm described below with a typing context of - `Stream`; let `E` be the type such that `Stream` is a super-interface - of `S`; and update `T` to be `UP(E, T)`. - -The **actual returned type** of the function literal is the value of `T` after -all `return` and `yield` statements in the block body have been considered. - -Let `T` be the **actual returned type** of a function literal as computed above. +**actual value type** of the function literal. _The actual value type +represents the types of the actual values returned by non-generator functions, +and the values emitted by generator functions._ + +Let `K` be the value context schema for the function body as computed above +from the imposed return type schema. +_When we refer to the inferred type of an expression with a typing context, +it is the static type of the expression inferred using the local type inference +algorithm described below._ + +The actual value type of a function literal with an expression body, `=> e`, +_(which cannot be a generator function)_ is computed as follows: + - If the function literal is marked `async`, + then let `T` be the inferred type of the returned expression with + `FutureOr` as typing context. + The actual value type is **flatten**(`T`). + - If the function literal is not marked `async`, let `T` be the inferred + type of the returned expression with typing context `K`. + The actually value type is `T`. + +The actual value type of a function literal with a block body is computed as +follows. +Let `T` be `Never` if the function is a generator function, +or if every control path through the block exits the block without +reaching the end of the block, as computed by the **definite completion** +analysis specified elsewhere. +Otherwise _(the function is a non-generator function and at least +one control path reaches the end of the block)_ let `T` be `null`. + +Then process relevant statements of the block, one by one in source order, +to find a value type `V` for that statement. + + - If the function literal is a non-`async` non-generator function, + the relevant statements are `return;` or `return e;` statements. + * For a `return;` statement, let `V` be `Null`. + * For a `return e;` statement, let `V` be the inferred type of `e` with + typing context `K`. + + - If the function literal is marked `async`, the relevant statements + are `return;` and `return e;` statements. + * For a `return;` statement, let `V` be `Null`. + * For a `return e;` statement, + then let `S` be the inferred type of `e` with typing context + `FutureOr`. + Let `V` be **flatten**(`S`). + + - If the function literal is marked `sync*`, the relevant statements + are `yield e;` or `yield* e;` statement. + * For a `yield e;` statement, let `V` be the inferred type of `e` with + typing context `K`. + * For a `yield* e;` statement, let `S` be the inferred type of `e` with + typing context `Iterable`. + If `S` implements `Iterable` for some `R`, let `V` be `R`. + Otherwise let `V` be `S`. + _It is a compile-time error if `S` is not a assignable to + `Iterable`, so either `S` implements `Iterable`, + or it is one of `dynamic` or `Never`._ + + - If the function literal is marked `async*`, the relevant statements are + `yield e;` or `yield* e;` statements. + * For a `yield e;` statement, let `V` be the inferred type of `e` with + typing context `K`. + * For a `yield* e;` statement, let `S` be the inferred type of `e` with + typing context `Stream`. + If `S` implements `Stream` for some `R`, let `V` be `R`. + Otherwise let `V` be `S`. + _It is a compile-time error if `S` is not a assignable to + `Stream`, so either `S` implements `Stream`, + or it is one of `dynamic` or `Never`._ + +After processing each relevant statement, update `T` to be **UP**(`V`, `T`). + +The **actual value type** of the function literal is the value of `T` after +all relevant statements in the block body have been processed. + +Let `T` be the **actual value type** of a function literal as computed above. Let `R` be the greatest closure of the typing context `K` as computed above. -With null safety: if `R` is `void`, or the function literal is marked `async` -and `R` is `FutureOr`, let `S` be `void` (without null-safety: no special -treatment is applicable to `void`). +_Then compute the actual value/element type, `S`, to use in the inferred return +type of the function literal, based on the actual value type, but bounded +by the typing context, if any._ + +With null safety, if `R` is `void`, let `S` be `void` +_(without null-safety: no special treatment is applicable to `void`)_. + +Otherwise (_if `R` is not `void`, or without null safety_), +if `T <: R` then let `S` be `T`, else let `S` be `R`. -Otherwise, if `T <: R` then let `S` be `T`. Otherwise, let `S` be `R`. The -inferred return type of the function literal is then defined as follows: +The inferred return type of the function literal is then defined as follows: - If the function literal is marked `async` then the inferred return type is - `Future`. + `Future`. - If the function literal is marked `async*` then the inferred return type is `Stream`. - If the function literal is marked `sync*` then the inferred return type is `Iterable`. - - Otherwise, the inferred return type is `S`. + - Otherwise, _a synchronous non-generator function_, the inferred return + type is `S`. ## Local return type inference.