From 6260fb02f46fb6b6a54f03acf48633eac83b550f Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 4 May 2023 12:07:18 +0200 Subject: [PATCH 1/7] Add draft version of feature-specification.md --- .../feature-specification.md | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 working/0107 - implicit-constructors/feature-specification.md diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md new file mode 100644 index 0000000000..16fecfd376 --- /dev/null +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -0,0 +1,362 @@ +# Implicit Constructors + +Author: Erik Ernst + +Status: Draft + +Version: 1.0 + +Experiment flag: implicit-constructors + +This document specifies implicit constructors as a kind of member that +static extensions can declare, and it specifies static extensions. + +_Implicit constructors_ are factory constructors that take exactly one +argument and have the modifier `implicit`. They allow user-written +conversions to take place implicitly, based on a mismatch between the +actual type of an expression and the type expected by the context. + +_Static extensions_ is a mechanism that adds static members and/or factory +constructors to a specified class, the _on-class_ of the static +extension. It could be viewed as a static variant of the existing mechanism +known as extension methods. + +The fact that implicit constructors are provided by a static extension +ensures that implicit conversions from a type `S` to a type `T` can be +declared even in the case where neither the declaration of `S` nor the +declaration of `T` can be edited. + +## Introduction + +Implicit constructors were proposed already several years ago in +[language issue #108][kevmoo proposal], and elsewhere. + +[kevmoo proposal]: https://github.com/dart-lang/language/issues/108 + +The main motivating situation was, and is, that an object of some type `S` +is available, but an object of some other type `T` is required, and the +conversion from `S` to `T` is considered to be a tedious detail that should +occur implicitly. + +For example, in a situation where a function `walk` accepts an argument of +type `Distance`, and the static type of `e` is `int`, the invocation +`walk(e)` could be implicitly transformed into `walk(Distance(e))` (or +`walk(const Distance(e))`, if `e` is a constant expression). + +The assumption is that the code is more convenient to write _and_ just as +easy or even easier to read when this conversion occurs implicitly. +See the 'Discussion' section for a broader discussion about this topic. + +Here is an example: + +```dart +class Distance { + final int value; + const Distance(this.value); +} + +static extension E1 on Distance { + implicit const factory Distance.fromInt(int i) = Distance; +} + +void walk(Distance d) {...} + +void main() { + walk(Distance(1)); // OK, normal invocation. + walk(1); // Also OK, invokes the constructor in E1 implicitly. +} +``` + +A static extension declaration is associated with an on-class (as opposed +to a plain extension declaration which is associated with an on-type). In +the example above, the on-class of `E1` is `Distance`. + +When the on-class of a static extension declaration is a generic class `G`, +the on-class may be denoted by a raw type `G`, or by a parameterized type +`G`. + +When the on-class is denoted by a raw type, the static extension cannot +declare any constructors. In this case the type arguments of the on-class +are ignored, which is what the `static` members must do anyway. + +When the on-class is denoted by a parameterized type `T`, constructors in the +static extension must return an object whose type is `T`. + +For example: + +```dart +// Omit extension type parameters when the static extension only has static +// members. Type parameters are just noise for static members, anyway. +static extension E2 on Map { + static Map castFromKey(Map source) => + Map.castFrom(source); +} + +// Declare extension type parameters, to be used by constructors. The +// type parameters can have stronger bounds than the on-class. +static extension E3 on Map { + factory Map.fromJson(Map source) => Map.from(source); +} + +var jsonMap = {"key": 42}; +var typedMap = Map.fromJson(jsonMap); +// `Map.fromJson(...)` is an error: Violates the bound of `K`. +``` + +A static extension with type parameters can be specialized for specific +values of specific type arguments by specifying actual type arguments of +the on-class to be types that depend on each other, or types with no type +variables: + +```dart +static extension E5 on Map> { + factory Map.listValue(X x) => {x: [x]}; +} + +var int2intList = Map.listValue(1); // Inferred as `Map>`. +// `Map.listValue(...)` is an error. + +static extension E6 on Map { + factory Map.fromString(Y y) => {y.toString(): y}; +} + +var string2int = Map.fromString(true); // Inferred as `Map`. +``` + +The support for implicit construction is managed via the import +mechanism. A particular static extension `E` containing an implicit +constructor _k_ is accessible if the library that declares `E` is imported, +and `E` is not hidden. Implicit construction only occurs with an accessible +implicit constructor. If several such constructors are accessible, the most +specific one is selected (detailed rules below). If there is no most +specific implicit constructor then a compile-time error occurs. + +## Specification + +### Syntax + +The grammar is modified as follows: + +```ebnf + ::= // Add a new alternative. + | + | + | + | // New alternative. + | + ...; + + ::= // New rule. + 'static' 'extension' ? ? 'on' + '{' ( )* '}'; + + ::= // New rule. + 'static' | + 'implicit'? | + 'static' ';'; + + ::= // New rule. + | + | + ; + + ::= // New rule. + | + ';'; + + ::= // New rule. + 'factory' ; + + ::= // New rule. + 'const'? 'factory' '=' + ; + + ::= // New rule. + ('final' | 'const') ? | + 'late' 'final' ? | + 'late'? ; +``` + +The identifier `implicit` is now mentioned in the grammar, but it is not a +built-in identifier nor a reserved word. *The parser does not need that.* + +In a static extension of the form `static extension E on C {...}` where `C` +is an identifier or an identifier with an import prefix, we say that the +on-class of `E` is `C`. + +In a static extension of the form `static extension E on C {...}` +where `C` is an identifier or prefixed identifier, we say that the on-class +of `E` is `C`, and the _constructor return type_ of `E` is `C`. + +In both cases, `E` is an identifer which is optionally followed by a term +derived from ``. + +### Static Analysis + +At first, we establish some sanity requirements for a static extension +declaration by specifying several errors. + +A compile-time error occurs if the on-class of a static extension does not +resolve to a declaration of a class, a mixin, a mixin class, or an inline +class. + +A compile-time error occurs if a static extension has a generic on-class +which is specified as a raw type, and the static extension contains one or +more constructor declarations. + +*In other words, if the static extension ignores the type parameters of the +on-class then it can only contain `static` members. Note that if the +on-class is non-generic then it is not a raw type, and the static extension +can contain constructors.* + +A compile-time error occurs if a static extension has an on-class which is +specified as a parameterized type `C`, and the actual type +parameters passed to `C` do not satisfy the bounds declared by `C`. +The static extension may declare one or more type parameters +`X1 extends B1 .. Xs extends Bs`, and these type variables may occur in the +types `T1 .. Tk`. During the bounds check, the bounds `B1 .. Bs` are +assumed for the type variables `X1 .. Xs`. + +A compile-time error occurs if a static extension _D_ declares a +constructor with the same name as a constructor in the on-class of _D_. + +Static extensions introduce several scopes: + +The current scope for the on-class of a static extension declaration _D_ +that does not declare any type parameters is the enclosing scope of _D_, +that is the library scope of the current library. + +A static extension _D_ that declares type variables introduces a type +variable scope whose enclosing scope is the library scope. The current +scope for the on-class of _D_ is the type variable scope. + +A static extension _D_ introduces a body scope, which is the current scope +for the member declarations. The enclosing scope for the body scope is the +type parameter scope, if any, and otherwise the library scope. + +Static members in a static extension are subject to the same static +analysis as static members in other declarations. There is one extra rule: +It is a compile-time error if a static member of a static extension has a +name which is also the name of a static member of the on-class. + +A factory constructor in a static extension introduces scopes in the same +way as other factory constructor declarations. The return type of the +factory constructor is the constructor return type of the static +extension *(that is, the type in the `on` clause)*. + +A static extension _D_ is _accessible_ if _D_ is declared in the current +library, or if _D_ is imported and not hidden. + +A static member invocation on a class `C`, of the form `C.m()` or similar, +is resolved by looking up static members in `C` named `m` and looking up +static members of every accessible static extension with on-class `C` and a +member named `m`. An error occurs if fewer than one or more than one +declaration named `m` was found. + +Once the invocation has been resolved to a static member declaration, the +static analysis of the invocation proceeds identically for a static member +in a static extension and a static member in a class, mixin, or other +declaration. + +Explicit constructor invocations are similar: + +A constructor invocation of the form `C.name(args)` is resolved +by looking up a constructor named `C.name` in the class `C` and in every +accessible static extension with on-class `C`. A compile-time error occurs +if fewer than one or more than one such constructor is found. + +If the invocation `C.name(args)` has been resolved to a +declaration in a static extension _D_ then the type parameters `X1 .. Xs` +of _D_ are found such that the constructor return type of _D_ is +`C`. A compile time error occurs if this is not possible. + +Otherwise we have a binding of each type variable `Xj`, `j` in `1 .. s` +to a type `Sj`. We say that `S1 .. Ss` is the list of actual type arguments +to _D_ for this invocation of `C.name`. + +Implicit invocations of static extension constructors are handled as +follows during static analysis: + +An expression can occur in an _assignment position_. This includes being +the right hand side of an assignment, an initializing expression in a +variable declaration, an actual argument in a function or method +invocation, and more. + +*This concept is already used to determine whether a coercion like generic +function instantiation is applicable. In this document we rely on this +concept being defined already. Currently it has been implemented, but not +specified.* + +If an expression `e` with static type `S` occurs in an assignment position +with context type schema `P`, and `S` is not assignable to the greatest +closure `T` of `P`, and `e` is not subject to any built-in coercion *(at +this time that means generic function instantiation and call method +tear-offs)* then `e` is _potentially subject to implicit construction_ +with _source type_ `S` and _target type schema_ `P`. + +If an expression `e` is potentially subject to implicit construction with +source type `S` and target type schema `P` then the following steps are +performed: + +- Gather every accessible static extension whose constructor return type + can be instantiated such that it is assignable to the greatest closure of + `P`. Call this set _M0_ +- The above-mentioned instantiation determines a value for all type + parameters of each static extension, and this determines a signature for + each constructor in the static extension. Each of these signatures has + one parameter type *(because they must accept exactly one argument)*. +- Select the constructor with the most special parameter type. + A compile-time error occurs if no such constructor exists. + +Otherwise, let `C.name` be the selected constructor. Then transform `e` to +`C.name(e)` and perform type analysis and inference with context type +schema `P`. + +### Dynamic Semantics + +The dynamic semantics of static members of a static extension is the same +as the dynamic semantics of other static functions. + +The dynamic semantics of an explicit invocation of a constructor in a +static extension is determined by the normal semantics of function +invocation, except that the actual value of the type parameters of the +static extension is as determined by the static analysis. + +## Discussion + +The language C++ has had a [similar mechanism][C++ implicit conversions] +for many years. It includes the notion of [converting constructors][] and +another notion of user-defined [conversion functions][]. + +[C++ implicit conversions]: https://en.cppreference.com/w/cpp/language/implicit_conversion +[converting constructors]: https://en.cppreference.com/w/cpp/language/converting_constructor +[conversion functions]: https://en.cppreference.com/w/cpp/language/cast_operator + +Scala is another language where implicit conversions have been supported +for a long time, as described [here][Scala implicit conversions]. + +[Scala implicit conversions]: https://docs.scala-lang.org/tour/implicit-conversions.html + +The experience from both C++ and Scala is that implicit conversions need to +be kept simple and comprehensible: It is simply not helpful if arbitrary +typing mistakes anywhere in the code can be implicitly masked by the +introduction of one or more unintended user-defined type conversions. + +With that in mind, the implicit conversions proposed here are subject to +some rather strict rules: + +- They can only be declared as `implicit` constructors in static + extensions. +- A static extension needs to be imported directly in order to have any + effect, and it can be hidden in imports. + +Scala even requires that the entity that provides implicit conversions is +explicitly imported (using something similar to `show` in an import). We +could consider doing that, but the current proposal is a little more +permissive. + +### Changelog + +1.0 - May 3, 2023 + +* First version of this document released. From ebe4afa97b4f444dd0107e1e8279cfc30a393c32 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 11 May 2023 16:27:38 +0200 Subject: [PATCH 2/7] WIP --- .../feature-specification.md | 243 ++++++++++++++---- 1 file changed, 200 insertions(+), 43 deletions(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index 16fecfd376..c67a570e0e 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -109,18 +109,19 @@ the on-class to be types that depend on each other, or types with no type variables: ```dart -static extension E5 on Map> { +static extension E4 on Map> { factory Map.listValue(X x) => {x: [x]}; } var int2intList = Map.listValue(1); // Inferred as `Map>`. -// `Map.listValue(...)` is an error. +// `Map.listValue(...)` is an error. static extension E6 on Map { factory Map.fromString(Y y) => {y.toString(): y}; } -var string2int = Map.fromString(true); // Inferred as `Map`. +var string2bool = Map.fromString(true); // Inferred as `Map`. +Map> string2listOfBool = Map.fromString([]); ``` The support for implicit construction is managed via the import @@ -161,15 +162,8 @@ The grammar is modified as follows: ; ::= // New rule. - | - ';'; - - ::= // New rule. - 'factory' ; - - ::= // New rule. - 'const'? 'factory' '=' - ; + | + ';'; ::= // New rule. ('final' | 'const') ? | @@ -182,14 +176,21 @@ built-in identifier nor a reserved word. *The parser does not need that.* In a static extension of the form `static extension E on C {...}` where `C` is an identifier or an identifier with an import prefix, we say that the -on-class of `E` is `C`. +on-class of the static extension is `C`. If `C` resolves to a non-generic +class then we say that the _constructor return type_ of the static +extension is `C`. + +*If `C` resolves to a generic class then the static extension does not have +a constructor return type.* In a static extension of the form `static extension E on C {...}` where `C` is an identifier or prefixed identifier, we say that the on-class of `E` is `C`, and the _constructor return type_ of `E` is `C`. -In both cases, `E` is an identifer which is optionally followed by a term -derived from ``. +In both cases, `E` is an identifer `id` which is optionally followed by a +term derived from ``. We say that the identifier `id` is +the _name_ of the static extension. *Note that `T1 .. Tk` above may contain +occurrences of those type variables.* ### Static Analysis @@ -219,6 +220,10 @@ assumed for the type variables `X1 .. Xs`. A compile-time error occurs if a static extension _D_ declares a constructor with the same name as a constructor in the on-class of _D_. +*In other words, a constructor in a static extension can never have a name +clash with a constructor declared by its on-class.* + +#### Static extension scopes Static extensions introduce several scopes: @@ -244,35 +249,187 @@ way as other factory constructor declarations. The return type of the factory constructor is the constructor return type of the static extension *(that is, the type in the `on` clause)*. -A static extension _D_ is _accessible_ if _D_ is declared in the current -library, or if _D_ is imported and not hidden. +Type variables of a static extension `E` are in scope in static member +declarations in `E`, but any reference to such type variables in a static +member declaration is a compile-time error. *The same rule applies for +static members of classes, mixins, etc.* -A static member invocation on a class `C`, of the form `C.m()` or similar, -is resolved by looking up static members in `C` named `m` and looking up -static members of every accessible static extension with on-class `C` and a -member named `m`. An error occurs if fewer than one or more than one -declaration named `m` was found. +#### Static extension accessibility -Once the invocation has been resolved to a static member declaration, the -static analysis of the invocation proceeds identically for a static member -in a static extension and a static member in a class, mixin, or other -declaration. - -Explicit constructor invocations are similar: - -A constructor invocation of the form `C.name(args)` is resolved -by looking up a constructor named `C.name` in the class `C` and in every -accessible static extension with on-class `C`. A compile-time error occurs -if fewer than one or more than one such constructor is found. - -If the invocation `C.name(args)` has been resolved to a -declaration in a static extension _D_ then the type parameters `X1 .. Xs` -of _D_ are found such that the constructor return type of _D_ is -`C`. A compile time error occurs if this is not possible. +A static extension _D_ is _accessible_ if _D_ is declared in the current +library, or if _D_ is imported and not hidden. -Otherwise we have a binding of each type variable `Xj`, `j` in `1 .. s` -to a type `Sj`. We say that `S1 .. Ss` is the list of actual type arguments -to _D_ for this invocation of `C.name`. +#### Invocation of a static member of a static extension + +An explicitly resolved invocation of a static member of a static extension +`E` *(with zero or more type parameters, which are ignored in this +context)*, is an expression of the form `E.m()` (or any other member +access), where `m` is a static member declared by `E`. + +*This can be used to invoke a static member of a specific static extension +in order to manually resolve a name clash.* + +A static member invocation on a class `C`, of the form `C.m()` (or any +other member access), is resolved by looking up static members in `C` named +`m` and looking up static members of every accessible static extension with +on-class `C` and a member named `m`. An error occurs if fewer than one or +more than one declaration named `m` was found. + +Once the invocation has been resolved to a static member declaration in a +specific static extension `E`, the invocation is treated as `E.m()`, which +is an invocation of a specific function, analyzed and executed as usual. + +#### The instantiated constructor return type of a static extension + +We associate a static extension `E` with formal type parameters +`X1 extends B1 .. Xs extends Bs` and an actual type argument list +`T1 .. Ts` with a type known as the _instantiated constructor return type_ +of `E` with type arguments `T1 .. Ts`. + +When a static extension has an on-class which is a non-generic class `C`, +the instantiated constructor return type is `C`. *There may be some +formal type parameters and actual type arguments, but they are ignored.* + +When a static extension has no formal type parameters, and it has an +on-type `C`, the instantiated constructor return type is +`C`. *In this case the on-type is a fixed type, e.g., +`List`, and there are no type arguments.* + +Consider a static extension `E` with formal type parameters +`X1 extends B1 .. Xs extends Bs` and a constructor return type +`C`. With actual type arguments `T1 .. Ts`, the instantiated +constructor return type of `E` with type arguments `T1 .. Ts` is +`[T1/X1 .. Ts/Xs]C`. + +#### Static extension specificity + +Let `E1` be a static extension with `k` type parameters, and +`E2` a static extension with `m` type parameters, +We say that `E1` with actual type arguments `T1 .. Tk` is +_more specific_ than `E2` with actual type arguments `S1 .. Sm` +iff the instantiated constructor return type of the former is a subtype of +the instantiated constructor return type of the latter. + +We say that they are _equally specific_ if said types are subtypes of each +other in both directions. + +We say that the former is _strictly more specific_ than the latter if +the former is more specific than the latter, and they are not equally +specific. + +With a set of extensions `E1 .. En` and corresponding actual type arguments +`T11 .. T1k1`, `T21 .. T2k2` .. `Tn1 .. Tnkn`, the _most specific_ one is +the one which is strictly more specific than each of the others, if it exists. + +#### Explicit invocation of a constructor in a static extension + +Explicit constructor invocations are similar to static member invocations, +but they need more detailed rules because they can use the formal type +parameters declared by the static extension. + +An _explicitly resolved invocation_ of a constructor named `C.name` in a +static extension `E` with `s` type parameters and on-class `C` can be +expressed as `E.C.name(args)`, `E.C.name(args)`, or +`E.C.name(args)`, (and similarly for a constructor +named `C` using `E.C(args)` etc). + +A compile-time error occurs if the type arguments passed to `E` violate the +declared bounds. A compile-time error occurs if type arguments `U1 .. Uk` +are passed to `C`, but no list of actual type arguments for the type +variables of `E` can be found such that the instantiated constructor return +type of `E` with said type arguments is `C`. + +A compile-time error occurs if the invocation passes actual type arguments +to both `E` and `C`, `S1 .. Ss` and `U1 .. Uk`, respectively, unless the +instantiated constructor return type of `E` with actual type arguments +`S1 .. Ss` is `C`. + +*Explicitly resolved invocations of constructors declared in static +extensions are an exception, usable in the case where a name clash prevents +an implicitly resolved invocation, which are specified in the rest of this +section.* + +A constructor invocation of the form `C.name(args)` is partially +resolved by looking up a constructor named `C.name` in the class `C` and in +every accessible static extension with on-class `C`. A compile-time error +occurs if no such constructor is found. Similarly, an invocation of the +form `C(args)` uses a lookup for constructors named `C`. + +*It is not possible to find a mixture consisting of one constructor in the +on-class and one or more constructors in static extensions: That's already +a compile-time error at the declarations in the static extension. Hence, +the invocation is resolved to a single constructor in `C`, or it is +partially resolved to a set of constructors in static extensions.* + +If the invocation resolves to a constructor in `C`, the pre-feature static +analysis and dynamic semantics apply. + +Otherwise, when the invocation is partially resolved to a set of candidate +constructors found in static extensions, we find the associated type +arguments for each of those constructors _kj_ as follows: + +Assume that _kj_ is declared by a static extension `E` with type parameters +`X1 extends B1 .. Xs extends Bs` and on-type `C`. +Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds +`B1 .. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. +If this fails then remove _kj_ from the set of candidate constructors. +Otherwise note that _kj_ uses actual type arguments `U1 .. Us`. + +If all candidate constructors have been removed, a compile-time error +occurs. + +Otherwise, resolve the invocation to the constructor with the most specific +instantiated constructor return type, using said actual type arguments. A +compile-time error occurs if none of the candidates is most specific. + +When there is no error, we have a static extension `E` with type parameters +`X1 extends B1 .. Xs extends Bs`, on-type `C`, actual type +arguments `U1 .. Us`, and instantiated constructor return type +`C`, which is also the static type of the invocation. The +invocation is henceforth treated as `E.C.name(args)`. + +A constructor invocation of the form `C.name(args)` (respectively +`C(args)`) where `C` resolves to a non-generic class is resolved in the +same manner, with `m == 0`. + +*Note that it is necessarily a compile-time error if this invocation is +partially resolved to a set of two or more constructors in static +extensions, because they are all equally specific.* + +Consider a constructor invocation of the form `C.name(args)` (and similarly +for `C(args)`) where `C` resolves to a generic class. As usual, the +invocation is treated as in the pre-feature language when it resolves to a +constructor declared by the class `C`. + +In the case where the invocation resolves to exactly one constructor +`C.name` (or `C`) declared by a static extension `E`, the invocation is +treated as `E.C.name(args)` (respectively `E.C(args)`). + +Otherwise, assume that the invocation has context type schema `P` and that +it partially resolves to a set of constructors declared by static +extensions with on-class `C`. + +For each argument `aj` in `args`, perform type inference with the empty +context type `_`, yielding `aaj` of type `Tj`. + +For each of these extensions, we find the associated type arguments for +each candidate constructor _kj_ declared by `E` as follows: + +Assume that _kj_ is declared by a static extension `E` with type parameters +`X1 extends B1 .. Xs extends Bs` and on-type `C`. +Obtain actual type arguments `U1 .. Us` for `X1 .. Xs` by inference of +`C` in context `P`. +If the inference fails then _kj_ is removed from the set of candidates. +Otherwise, _kj_ is a candidate with actual type arguments `U1 .. Us`. + +From the set of candidates with actual type arguments, the one which is +most specific is selected. Call it `E`. A compile-time error occurs if no +such candidate exists. + +If no error occurred, the invocation is henceforth treated as +`E.C.name(aa1, aa2, ..)`. + +#### Implicit invocation of a constructor in a static extension Implicit invocations of static extension constructors are handled as follows during static analysis: @@ -308,8 +465,8 @@ performed: - Select the constructor with the most special parameter type. A compile-time error occurs if no such constructor exists. -Otherwise, let `C.name` be the selected constructor. Then transform `e` to -`C.name(e)` and perform type analysis and inference with context type +Otherwise, let `E.C.name` be the selected constructor. Then transform `e` to +`E.C.name(e)` and perform type analysis and inference with context type schema `P`. ### Dynamic Semantics From 16a1eae15415e3459f79765969a5f1085ba3b3c4 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 12 May 2023 20:26:21 +0200 Subject: [PATCH 3/7] Revised to introduce ambiguity errors rather than trying to find the most specific constructor --- .../feature-specification.md | 291 +++++++++++------- 1 file changed, 185 insertions(+), 106 deletions(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index c67a570e0e..543072347c 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -223,6 +223,35 @@ constructor with the same name as a constructor in the on-class of _D_. *In other words, a constructor in a static extension can never have a name clash with a constructor declared by its on-class.* +A compile-time error occurs if a static extension declares an +`implicit` constructor whose formal parameter list accepts a number of +positional paremeters which is different from one, or if it accepts any +named parameters. + +*A `static extension` declaration gets a fresh name in addition to the +declared name, just like `extension` declarations.* + +For each static extension declaration _D_ named `E` which is in the library +scope of the current library *(that is, _D_ is declared in the current +library, or it is imported and not hidden)*, a fresh name `FreshE` is +created, and _D_ is entered into the library scope with the fresh name. + +*This makes it possible to resolve an implicit reference to a static +extension, e.g., an invocation of a static member or constructor declared +by the static extension, even in the case where there is a different +declaration in scope with the same name. For example:* + +```dart +static extension E on C { // `FreshE` is an extra name for `E`. + static void foo() {} +} + +void f() { + // Name clash: type parameter shadows static extension. + C.foo(); // Resolved as `FreshE.foo()`. +} +``` + #### Static extension scopes Static extensions introduce several scopes: @@ -275,25 +304,35 @@ other member access), is resolved by looking up static members in `C` named on-class `C` and a member named `m`. An error occurs if fewer than one or more than one declaration named `m` was found. -Once the invocation has been resolved to a static member declaration in a -specific static extension `E`, the invocation is treated as `E.m()`, which -is an invocation of a specific function, analyzed and executed as usual. +If the invocation has been resolved to a static member declared by the +class `C` then the static analysis and dynamic semantics are the same as in +the pre-feature language. + +Otherwise, when the invocation has been resolved to a static member +declaration in a static extension `E`, the invocation is treated as +`E.m()`, which is an invocation of a specific static function, analyzed and +executed as usual. #### The instantiated constructor return type of a static extension We associate a static extension `E` with formal type parameters `X1 extends B1 .. Xs extends Bs` and an actual type argument list -`T1 .. Ts` with a type known as the _instantiated constructor return type_ -of `E` with type arguments `T1 .. Ts`. +`T1 .. Ts` with a type known as the _instantiated constructor return type +of_ `E` _with type arguments_ `T1 .. Ts`. -When a static extension has an on-class which is a non-generic class `C`, -the instantiated constructor return type is `C`. *There may be some -formal type parameters and actual type arguments, but they are ignored.* +When a static extension `E` has an on-class which is a non-generic class +`C`, the instantiated constructor return type is `C`, for any list of +actual type arguments. *It is not very useful to declare a type parameter +of a static extension which isn't used in the constructor return type, +because it can only be passed in an explicitly resolved constructor +invocation, e.g., `E.C(42)`. In all other invocations, the value of +such type variables is determined by instantiation to bound.* When a static extension has no formal type parameters, and it has an on-type `C`, the instantiated constructor return type is -`C`. *In this case the on-type is a fixed type, e.g., -`List`, and there are no type arguments.* +`C`. *In this case the on-type is a fixed type (also known as a +ground type), e.g., `List`. The point is that there are no type +variables in the type, and hence it is the same for every call site.* Consider a static extension `E` with formal type parameters `X1 extends B1 .. Xs extends Bs` and a constructor return type @@ -301,26 +340,6 @@ Consider a static extension `E` with formal type parameters constructor return type of `E` with type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]C`. -#### Static extension specificity - -Let `E1` be a static extension with `k` type parameters, and -`E2` a static extension with `m` type parameters, -We say that `E1` with actual type arguments `T1 .. Tk` is -_more specific_ than `E2` with actual type arguments `S1 .. Sm` -iff the instantiated constructor return type of the former is a subtype of -the instantiated constructor return type of the latter. - -We say that they are _equally specific_ if said types are subtypes of each -other in both directions. - -We say that the former is _strictly more specific_ than the latter if -the former is more specific than the latter, and they are not equally -specific. - -With a set of extensions `E1 .. En` and corresponding actual type arguments -`T11 .. T1k1`, `T21 .. T2k2` .. `Tn1 .. Tnkn`, the _most specific_ one is -the one which is strictly more specific than each of the others, if it exists. - #### Explicit invocation of a constructor in a static extension Explicit constructor invocations are similar to static member invocations, @@ -331,23 +350,30 @@ An _explicitly resolved invocation_ of a constructor named `C.name` in a static extension `E` with `s` type parameters and on-class `C` can be expressed as `E.C.name(args)`, `E.C.name(args)`, or `E.C.name(args)`, (and similarly for a constructor -named `C` using `E.C(args)` etc). +named `C` using `E.C(args)` etc). A compile-time error occurs if the type arguments passed to `E` violate the -declared bounds. A compile-time error occurs if type arguments `U1 .. Uk` -are passed to `C`, but no list of actual type arguments for the type -variables of `E` can be found such that the instantiated constructor return -type of `E` with said type arguments is `C`. +declared bounds. A compile-time error occurs if no type arguments are +passed to `E`, and type arguments `U1 .. Uk` are passed to `C`, but no list +of actual type arguments for the type variables of `E` can be found such +that the instantiated constructor return type of `E` with said type +arguments is `C`. A compile-time error occurs if the invocation passes actual type arguments to both `E` and `C`, `S1 .. Ss` and `U1 .. Uk`, respectively, unless the -instantiated constructor return type of `E` with actual type arguments -`S1 .. Ss` is `C`. - -*Explicitly resolved invocations of constructors declared in static -extensions are an exception, usable in the case where a name clash prevents -an implicitly resolved invocation, which are specified in the rest of this -section.* +instantiated constructor return type of `E` with actual type arguments +`S1 .. Ss` is `C`. In this type comparison, top types like +`dynamic` and `Object?` are considered different, and no type normalization +occurs. *In other words, the types must be identical, not just mutual +subtypes.* + +*Note that explicitly resolved invocations of constructors declared in +static extensions are an exception in real code, usable in the case where a +name clash prevents an implicitly resolved invocation. Implicitly resolved +invocations are specified in the rest of this section by reducing them to +explicitly resolved ones. Also note that implicitly resolved invocations is +not the same thing as implicit invocations (which are specified in a later +section).* A constructor invocation of the form `C.name(args)` is partially resolved by looking up a constructor named `C.name` in the class `C` and in @@ -364,37 +390,25 @@ partially resolved to a set of constructors in static extensions.* If the invocation resolves to a constructor in `C`, the pre-feature static analysis and dynamic semantics apply. -Otherwise, when the invocation is partially resolved to a set of candidate -constructors found in static extensions, we find the associated type -arguments for each of those constructors _kj_ as follows: +Otherwise, the invocation is partially resolved to a set of candidate +constructors found in static extensions. Each of the candidates _kj_ is +vetted as follows: -Assume that _kj_ is declared by a static extension `E` with type parameters -`X1 extends B1 .. Xs extends Bs` and on-type `C`. -Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds -`B1 .. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. -If this fails then remove _kj_ from the set of candidate constructors. -Otherwise note that _kj_ uses actual type arguments `U1 .. Us`. +Assume that _kj_ is a constructor declared by a static extension `E` with +type parameters `X1 extends B1 .. Xs extends Bs` and on-type `C`. +Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds `B1 +.. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. If this +fails then remove _kj_ from the set of candidate constructors. Otherwise +note that _kj_ uses actual type arguments `U1 .. Us`. -If all candidate constructors have been removed, a compile-time error -occurs. - -Otherwise, resolve the invocation to the constructor with the most specific -instantiated constructor return type, using said actual type arguments. A -compile-time error occurs if none of the candidates is most specific. - -When there is no error, we have a static extension `E` with type parameters -`X1 extends B1 .. Xs extends Bs`, on-type `C`, actual type -arguments `U1 .. Us`, and instantiated constructor return type -`C`, which is also the static type of the invocation. The -invocation is henceforth treated as `E.C.name(args)`. +If all candidate constructors have been removed, or more than one candidate +remains, a compile-time error occurs. Otherwise, the invocation is +henceforth treated as `E.C.name(args)`. A constructor invocation of the form `C.name(args)` (respectively `C(args)`) where `C` resolves to a non-generic class is resolved in the -same manner, with `m == 0`. - -*Note that it is necessarily a compile-time error if this invocation is -partially resolved to a set of two or more constructors in static -extensions, because they are all equally specific.* +same manner, with `m == 0`. *In this case, type parameters declared by `E` +will be bound to values selected by instantiation to bound.* Consider a constructor invocation of the form `C.name(args)` (and similarly for `C(args)`) where `C` resolves to a generic class. As usual, the @@ -405,36 +419,78 @@ In the case where the invocation resolves to exactly one constructor `C.name` (or `C`) declared by a static extension `E`, the invocation is treated as `E.C.name(args)` (respectively `E.C(args)`). -Otherwise, assume that the invocation has context type schema `P` and that -it partially resolves to a set of constructors declared by static -extensions with on-class `C`. +Otherwise, when there are two or more candidates from static extensions, +an error occurs. *We do not wish to specify an approach whereby `args` is +subject to type inference multiple times, and hence we do not support type +inference for `C.name(args)` in the case where there are multiple distinct +declarations whose signature could be used during the static analysis of +that expression.* + +#### Implicit constructor specificity + +Let `E` be a static extension with type parameters +`X1 extends B1 .. Xs extends Bs` +that declares an `implicit` constructor _k_ whose formal parameter has type +`U` *(which may contain some of `X1 .. Xs`)*. + +*An `implicit` constructor must always have exactly one formal parameter.* + +Let `T1 .. Ts` be types satisfying the bounds `B1 .. Bs`. We then say that +the _instantiated parameter type_ of the implicit constructor _k_ with +actual type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]U`. + + +Let `E1` be a static extension with `s` type parameters, let `T1 .. Ts` +be types that satisfy the bounds of `E1`, and assume that _k1_ is an +`implicit` constructor declared by `E1`. +Let `E2` be a static extension with `t` type parameters, let `S1 .. St` +be types that satisfy the bounds of `E2`, and assume that _k2_ is an +`implicit` constructor declared by `E2`. -For each argument `aj` in `args`, perform type inference with the empty -context type `_`, yielding `aaj` of type `Tj`. +We then say that _k1_ is _more specific_ than _k2_ with said actual type +arguments iff the instantiated parameter type of _k1_ with actual type +arguments `T1 .. Ts` is a proper subtype of the instantiated parameter type +of _k2_ with the actual type arguments `S1 .. St`. -For each of these extensions, we find the associated type arguments for -each candidate constructor _kj_ declared by `E` as follows: +If the two instantiated parameter types are mutual subtypes then we say +that the two constructors are equally specific. -Assume that _kj_ is declared by a static extension `E` with type parameters -`X1 extends B1 .. Xs extends Bs` and on-type `C`. -Obtain actual type arguments `U1 .. Us` for `X1 .. Xs` by inference of -`C` in context `P`. -If the inference fails then _kj_ is removed from the set of candidates. -Otherwise, _kj_ is a candidate with actual type arguments `U1 .. Us`. +With a list of extensions `E1 .. En` and corresponding actual type +arguments and implicit constructors _k1 .. kn_, we say that the most +specific one is _kj_ iff that constructor with the given type arguments is +more specific than each of the others. -From the set of candidates with actual type arguments, the one which is -most specific is selected. Call it `E`. A compile-time error occurs if no -such candidate exists. +#### Static extension applicability -If no error occurred, the invocation is henceforth treated as -`E.C.name(aa1, aa2, ..)`. +Let `E` be a static extension with type parameters +`X1 extends B1 .. Xs extends Bs` and constructor return type +`C`. Let `P` be a context type schema. + +Let `f` be a function declared as follows, also known as the applicability +function of `E`: + +```dart +C f() => f(); +``` + +We say that `E` is applicable with context type schema `P` yielding actual +type arguments `S1 .. Ss` iff type inference of the invocation `f()` with +context type schema `P` yields a list of actual type arguments `S1 .. Ss` +to `f` such that `[S1/X1 .. Ss/Xs]C` is assignable to the +greatest closure of `P`. #### Implicit invocation of a constructor in a static extension -Implicit invocations of static extension constructors are handled as -follows during static analysis: +A static extension constructor marked with the modifier `implicit` can be +invoked implicitly. + +*For example, we can have a declaration `Distance d = 1;`, and it may be +transformed into `Distance d = Distance.fromInt(1);` where +`Distance.fromInt` is an implicit constructor declared in an accessible +static extension with on-class `Distance` whose parameter type is `int`.* -An expression can occur in an _assignment position_. This includes being +First, we need to introduce the notion of an _assignment position_. +An expression can occur in an assignment position. This includes being the right hand side of an assignment, an initializing expression in a variable declaration, an actual argument in a function or method invocation, and more. @@ -455,29 +511,52 @@ If an expression `e` is potentially subject to implicit construction with source type `S` and target type schema `P` then the following steps are performed: -- Gather every accessible static extension whose constructor return type - can be instantiated such that it is assignable to the greatest closure of - `P`. Call this set _M0_ -- The above-mentioned instantiation determines a value for all type - parameters of each static extension, and this determines a signature for - each constructor in the static extension. Each of these signatures has - one parameter type *(because they must accept exactly one argument)*. -- Select the constructor with the most special parameter type. - A compile-time error occurs if no such constructor exists. - -Otherwise, let `E.C.name` be the selected constructor. Then transform `e` to -`E.C.name(e)` and perform type analysis and inference with context type -schema `P`. +- Perform type inference on `e` with context type schema `P`. *The type of + `e` may well not be assignable to the greatest closure of `P`, but + performing type inference using the given context type is standard, + and we do not wish to change that.* Assume that the resulting expression + is `e0`, with static type `T0`. +- Gather every accessible static extension that declares one or more + `implicit` constructors, and that is applicable with context type schema + `P`. Assume that the result is the set `E1 .. En` of static extensions, + each with an actual type argument list `A1 .. An` *(each `Aj` is a list + of types, whose length is the same as the type parameter list of `Ej`)*. + The candidate constructors are then all constructors in `E1 .. En` which + are marked `implicit`. +- For each candidate constructor _k_, eliminate _k_ from the set of + candidates if `T0` is not assignable to the instantiated parameter type + of _k_ with the actual type arguments `Aj` of the static extension `Ej` + that declares _k_. +- If the set of candidate constructors is empty, a compile-time error + occurs. +- Otherwise, if none of the candidate constructors is most specific with + the given actual type arguments of the enclosing static extension, an + error occurs. +- Otherwise, one specific static extension `Ej` with actual type arguments + `Aj`, and one constructor _k_ declared by `Ej` is most specific. Let + `C.name` be the name of _k_. +- The expression `e` is then transformed to `Ej.C(e0)`. + +*Note that no further type inference is applied to this expression: The +static extension `Ej` has received actual type arguments `Aj`, and this +fully determines the type arguments passed to `C`. Finally, `e0` has been +subject to type inference in the first step.* ### Dynamic Semantics The dynamic semantics of static members of a static extension is the same as the dynamic semantics of other static functions. -The dynamic semantics of an explicit invocation of a constructor in a -static extension is determined by the normal semantics of function -invocation, except that the actual value of the type parameters of the -static extension is as determined by the static analysis. +The dynamic semantics of an explicitly resolved invocation of a constructor +in a static extension is determined by the normal semantics of function +invocation, except that the type parameters of the static extension are +bound to the actual type arguments passed to the static extension in the +invocation. + +An implicitly resolved invocation of a constructor declared by a static +extension is reduced to an explicitly resolved one during static analysis. +The same is true for implicit invocations of `implicit` constructors. +This fully determines the dynamic semantics of this feature. ## Discussion From 68e5dd402fb786c3f4c1cd52238a0d16ee273edd Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 17 May 2023 18:25:58 +0200 Subject: [PATCH 4/7] Many small improvements --- .../feature-specification.md | 109 +++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index 543072347c..753b52cd8c 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -190,7 +190,7 @@ of `E` is `C`, and the _constructor return type_ of `E` is `C`. In both cases, `E` is an identifer `id` which is optionally followed by a term derived from ``. We say that the identifier `id` is the _name_ of the static extension. *Note that `T1 .. Tk` above may contain -occurrences of those type variables.* +occurrences of those type parameters.* ### Static Analysis @@ -198,25 +198,25 @@ At first, we establish some sanity requirements for a static extension declaration by specifying several errors. A compile-time error occurs if the on-class of a static extension does not -resolve to a declaration of a class, a mixin, a mixin class, or an inline -class. +resolve to an enum declaration or a declaration of a class, a mixin, a mixin +class, or an inline class. -A compile-time error occurs if a static extension has a generic on-class -which is specified as a raw type, and the static extension contains one or -more constructor declarations. +A compile-time error occurs if a static extension has an on-clause of the form +`on C` where `C` denotes a generic class and no type arguments are passed to `C` +*(i.e., it is a raw type)*, and the static extension contains one or more +constructor declarations. *In other words, if the static extension ignores the type parameters of the on-class then it can only contain `static` members. Note that if the on-class is non-generic then it is not a raw type, and the static extension can contain constructors.* -A compile-time error occurs if a static extension has an on-class which is -specified as a parameterized type `C`, and the actual type -parameters passed to `C` do not satisfy the bounds declared by `C`. -The static extension may declare one or more type parameters -`X1 extends B1 .. Xs extends Bs`, and these type variables may occur in the -types `T1 .. Tk`. During the bounds check, the bounds `B1 .. Bs` are -assumed for the type variables `X1 .. Xs`. +A compile-time error occurs if a static extension has an on-clause of the +form `on C`, and the actual type parameters passed to `C` do not +satisfy the bounds declared by `C`. The static extension may declare one +or more type parameters `X1 extends B1 .. Xs extends Bs`, and these type +variables may occur in the types `T1 .. Tk`. During the bounds check, the +bounds `B1 .. Bs` are assumed for the type variables `X1 .. Xs`. A compile-time error occurs if a static extension _D_ declares a constructor with the same name as a constructor in the on-class of _D_. @@ -228,13 +228,15 @@ A compile-time error occurs if a static extension declares an positional paremeters which is different from one, or if it accepts any named parameters. -*A `static extension` declaration gets a fresh name in addition to the -declared name, just like `extension` declarations.* +Consider a static extension declaration _D_ named `E` which is declared in +the current library or present in any exported namespace of an import +*(that is, _D_ is declared in the current library or it is imported and +not hidden, but it could be imported and have a name clash with some other +imported declaration)*. A fresh name `FreshE` is created, and _D_ is +entered into the library scope with the fresh name. -For each static extension declaration _D_ named `E` which is in the library -scope of the current library *(that is, _D_ is declared in the current -library, or it is imported and not hidden)*, a fresh name `FreshE` is -created, and _D_ is entered into the library scope with the fresh name. +*This means that a `static extension` declaration gets a fresh name in +addition to the declared name, just like `extension` declarations.* *This makes it possible to resolve an implicit reference to a static extension, e.g., an invocation of a static member or constructor declared @@ -256,13 +258,13 @@ void f() { Static extensions introduce several scopes: -The current scope for the on-class of a static extension declaration _D_ +The current scope for the on-clause of a static extension declaration _D_ that does not declare any type parameters is the enclosing scope of _D_, that is the library scope of the current library. A static extension _D_ that declares type variables introduces a type -variable scope whose enclosing scope is the library scope. The current -scope for the on-class of _D_ is the type variable scope. +parameter scope whose enclosing scope is the library scope. The current +scope for the on-clause of _D_ is the type parameter scope. A static extension _D_ introduces a body scope, which is the current scope for the member declarations. The enclosing scope for the body scope is the @@ -290,9 +292,9 @@ library, or if _D_ is imported and not hidden. #### Invocation of a static member of a static extension -An explicitly resolved invocation of a static member of a static extension -`E` *(with zero or more type parameters, which are ignored in this -context)*, is an expression of the form `E.m()` (or any other member +An _explicitly resolved invocation_ of a static member of a static +extension `E` *(with zero or more type parameters, which are ignored in +this context)*, is an expression of the form `E.m()` (or any other member access), where `m` is a static member declared by `E`. *This can be used to invoke a static member of a specific static extension @@ -320,7 +322,7 @@ We associate a static extension `E` with formal type parameters `T1 .. Ts` with a type known as the _instantiated constructor return type of_ `E` _with type arguments_ `T1 .. Ts`. -When a static extension `E` has an on-class which is a non-generic class +When a static extension `E` has an on-clause which is a non-generic class `C`, the instantiated constructor return type is `C`, for any list of actual type arguments. *It is not very useful to declare a type parameter of a static extension which isn't used in the constructor return type, @@ -349,7 +351,7 @@ parameters declared by the static extension. An _explicitly resolved invocation_ of a constructor named `C.name` in a static extension `E` with `s` type parameters and on-class `C` can be expressed as `E.C.name(args)`, `E.C.name(args)`, or -`E.C.name(args)`, (and similarly for a constructor +`E.C.name(args)` (and similarly for a constructor named `C` using `E.C(args)` etc). A compile-time error occurs if the type arguments passed to `E` violate the @@ -415,6 +417,11 @@ for `C(args)`) where `C` resolves to a generic class. As usual, the invocation is treated as in the pre-feature language when it resolves to a constructor declared by the class `C`. +In the case where the context type schema for this invocation fully +determines the actual type arguments of `C`, the expression is changed to +receive said actual type arguments, `C.name(args)`, and treated +as described above. + In the case where the invocation resolves to exactly one constructor `C.name` (or `C`) declared by a static extension `E`, the invocation is treated as `E.C.name(args)` (respectively `E.C(args)`). @@ -433,7 +440,8 @@ Let `E` be a static extension with type parameters that declares an `implicit` constructor _k_ whose formal parameter has type `U` *(which may contain some of `X1 .. Xs`)*. -*An `implicit` constructor must always have exactly one formal parameter.* +*Note that an `implicit` constructor must always have exactly one formal +parameter.* Let `T1 .. Ts` be types satisfying the bounds `B1 .. Bs`. We then say that the _instantiated parameter type_ of the implicit constructor _k_ with @@ -456,7 +464,7 @@ If the two instantiated parameter types are mutual subtypes then we say that the two constructors are equally specific. With a list of extensions `E1 .. En` and corresponding actual type -arguments and implicit constructors _k1 .. kn_, we say that the most +arguments and implicit constructors _k1 .. km_, we say that the most specific one is _kj_ iff that constructor with the given type arguments is more specific than each of the others. @@ -466,18 +474,18 @@ Let `E` be a static extension with type parameters `X1 extends B1 .. Xs extends Bs` and constructor return type `C`. Let `P` be a context type schema. -Let `f` be a function declared as follows, also known as the applicability -function of `E`: +Let `f` be a function declared as follows, also known as the +_applicability function_ of `E`: ```dart C f() => f(); ``` -We say that `E` is applicable with context type schema `P` yielding actual -type arguments `S1 .. Ss` iff type inference of the invocation `f()` with -context type schema `P` yields a list of actual type arguments `S1 .. Ss` -to `f` such that `[S1/X1 .. Ss/Xs]C` is assignable to the -greatest closure of `P`. +We say that `E` is _applicable with context type schema_ `P` +_yielding actual type arguments_ `S1 .. Ss` iff type inference of the +invocation `f()` with context type schema `P` yields a list of actual type +arguments `S1 .. Ss` to `f` such that `[S1/X1 .. Ss/Xs]C` is +assignable to the greatest closure of `P`. #### Implicit invocation of a constructor in a static extension @@ -500,22 +508,18 @@ function instantiation is applicable. In this document we rely on this concept being defined already. Currently it has been implemented, but not specified.* -If an expression `e` with static type `S` occurs in an assignment position -with context type schema `P`, and `S` is not assignable to the greatest -closure `T` of `P`, and `e` is not subject to any built-in coercion *(at -this time that means generic function instantiation and call method -tear-offs)* then `e` is _potentially subject to implicit construction_ -with _source type_ `S` and _target type schema_ `P`. +Assume that an expression `e` occurs in an assignment position with context +type schema `P`. In this situation, type inference is performed on `e` with +context type schema `P`, and the resulting expression `e1` has some type +`T0`. Assume that `e1` is not subject to any built-in coercions *(at this +time this means generic function instantiation or call method tear-off)*, +and `T0` is not assignable to the greatest closure of `P`. In this case we +say that `e` is _potentially subject to implicit construction with source +type_ `T0`. If an expression `e` is potentially subject to implicit construction with -source type `S` and target type schema `P` then the following steps are -performed: - -- Perform type inference on `e` with context type schema `P`. *The type of - `e` may well not be assignable to the greatest closure of `P`, but - performing type inference using the given context type is standard, - and we do not wish to change that.* Assume that the resulting expression - is `e0`, with static type `T0`. +source type `T0`, the following steps are performed: + - Gather every accessible static extension that declares one or more `implicit` constructors, and that is applicable with context type schema `P`. Assume that the result is the set `E1 .. En` of static extensions, @@ -534,8 +538,9 @@ performed: error occurs. - Otherwise, one specific static extension `Ej` with actual type arguments `Aj`, and one constructor _k_ declared by `Ej` is most specific. Let - `C.name` be the name of _k_. -- The expression `e` is then transformed to `Ej.C(e0)`. + `C.name` (respectively `C`) be the name of _k_. +- The expression `e` is then replaced by `Ej.C.name(e0)` + (respectively `Ej.C(e0)`). *Note that no further type inference is applied to this expression: The static extension `Ej` has received actual type arguments `Aj`, and this From b1687938dbb7e6835a9616828b7ec6c202fa06f8 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 24 May 2023 21:22:51 +0200 Subject: [PATCH 5/7] Adjust rules to use shadowing semantics ("the class always wins"), and recommend a lint --- .../feature-specification.md | 211 ++++++++++-------- 1 file changed, 114 insertions(+), 97 deletions(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index 753b52cd8c..a7a3bb369c 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -169,9 +169,18 @@ The grammar is modified as follows: ('final' | 'const') ? | 'late' 'final' ? | 'late'? ; + + ::= + 'show' | + 'hide' | + 'enable' ; + + ::= + (',' )*; ``` The identifier `implicit` is now mentioned in the grammar, but it is not a +built-in identifier nor a reserved word. Similarly, `enable` is not a built-in identifier nor a reserved word. *The parser does not need that.* In a static extension of the form `static extension E on C {...}` where `C` @@ -201,15 +210,15 @@ A compile-time error occurs if the on-class of a static extension does not resolve to an enum declaration or a declaration of a class, a mixin, a mixin class, or an inline class. -A compile-time error occurs if a static extension has an on-clause of the form -`on C` where `C` denotes a generic class and no type arguments are passed to `C` -*(i.e., it is a raw type)*, and the static extension contains one or more -constructor declarations. +A compile-time error occurs if a static extension has an on-clause of the +form `on C` where `C` denotes a generic class and no type arguments are +passed to `C` *(i.e., it is a raw type)*, and the static extension contains +one or more constructor declarations. *In other words, if the static extension ignores the type parameters of the on-class then it can only contain `static` members. Note that if the -on-class is non-generic then it is not a raw type, and the static extension -can contain constructors.* +on-class is non-generic then `C` is not a raw type, and the static +extension can contain constructors.* A compile-time error occurs if a static extension has an on-clause of the form `on C`, and the actual type parameters passed to `C` do not @@ -218,14 +227,9 @@ or more type parameters `X1 extends B1 .. Xs extends Bs`, and these type variables may occur in the types `T1 .. Tk`. During the bounds check, the bounds `B1 .. Bs` are assumed for the type variables `X1 .. Xs`. -A compile-time error occurs if a static extension _D_ declares a -constructor with the same name as a constructor in the on-class of _D_. -*In other words, a constructor in a static extension can never have a name -clash with a constructor declared by its on-class.* - A compile-time error occurs if a static extension declares an `implicit` constructor whose formal parameter list accepts a number of -positional paremeters which is different from one, or if it accepts any +positional parameters which is different from one, or if it accepts any named parameters. Consider a static extension declaration _D_ named `E` which is declared in @@ -254,6 +258,17 @@ void f() { } ``` +Tools may report diagnostic messages like warnings or lints in certain +situations. This is not part of the specification, but here is one +recommended message: + +A compile-time message is emitted if a static extension _D_ declares a +constructor or a static member with the same name as a constructor or a +static member in the on-class of _D_. + +*In other words, a static extension should not have name clashes with its +on-class.* + #### Static extension scopes Static extensions introduce several scopes: @@ -271,9 +286,7 @@ for the member declarations. The enclosing scope for the body scope is the type parameter scope, if any, and otherwise the library scope. Static members in a static extension are subject to the same static -analysis as static members in other declarations. There is one extra rule: -It is a compile-time error if a static member of a static extension has a -name which is also the name of a static member of the on-class. +analysis as static members in other declarations. A factory constructor in a static extension introduces scopes in the same way as other factory constructor declarations. The return type of the @@ -287,15 +300,20 @@ static members of classes, mixins, etc.* #### Static extension accessibility -A static extension _D_ is _accessible_ if _D_ is declared in the current -library, or if _D_ is imported and not hidden. +A static extension declaration _D_ is _accessible_ if _D_ is declared in +the current library, or if _D_ is imported and not hidden. + +An implicit constructor declaration named `C.name` (respectively `C`) in a +static extension declaration _D_ is _enabled_ if _D_ is declared in the +current library, or if _D_ is imported and the import directive that +imports _D_ includes `C.name` (`C`) in an `enable` combinator. #### Invocation of a static member of a static extension An _explicitly resolved invocation_ of a static member of a static -extension `E` *(with zero or more type parameters, which are ignored in -this context)*, is an expression of the form `E.m()` (or any other member -access), where `m` is a static member declared by `E`. +extension named `E` is an expression of the form `E.m()` (or any other +member access, *e.g., `E.m`, `E.m = e`, etc*), where `m` is a static member +declared by `E`. *This can be used to invoke a static member of a specific static extension in order to manually resolve a name clash.* @@ -303,43 +321,47 @@ in order to manually resolve a name clash.* A static member invocation on a class `C`, of the form `C.m()` (or any other member access), is resolved by looking up static members in `C` named `m` and looking up static members of every accessible static extension with -on-class `C` and a member named `m`. An error occurs if fewer than one or -more than one declaration named `m` was found. +on-class `C` and a member named `m`. + +If `C` contains such a declaration then the expression is an invocation of +that static member of `C`, with the same static analysis and dynamic +behavior as before the introduction of this feature. -If the invocation has been resolved to a static member declared by the -class `C` then the static analysis and dynamic semantics are the same as in -the pre-feature language. +Otherwise, an error occurs if fewer than one or more than one declaration +named `m` was found. -Otherwise, when the invocation has been resolved to a static member -declaration in a static extension `E`, the invocation is treated as -`E.m()`, which is an invocation of a specific static function, analyzed and -executed as usual. +Otherwise, the invocation is resolved to the given static member +declaration in a static extension named `E`, and the invocation is treated +as `E.m()` *(this is an explicitly resolved invocation, which is specified +above)*. #### The instantiated constructor return type of a static extension -We associate a static extension `E` with formal type parameters -`X1 extends B1 .. Xs extends Bs` and an actual type argument list -`T1 .. Ts` with a type known as the _instantiated constructor return type -of_ `E` _with type arguments_ `T1 .. Ts`. - -When a static extension `E` has an on-clause which is a non-generic class -`C`, the instantiated constructor return type is `C`, for any list of -actual type arguments. *It is not very useful to declare a type parameter -of a static extension which isn't used in the constructor return type, -because it can only be passed in an explicitly resolved constructor -invocation, e.g., `E.C(42)`. In all other invocations, the value of -such type variables is determined by instantiation to bound.* - -When a static extension has no formal type parameters, and it has an -on-type `C`, the instantiated constructor return type is -`C`. *In this case the on-type is a fixed type (also known as a -ground type), e.g., `List`. The point is that there are no type -variables in the type, and hence it is the same for every call site.* - -Consider a static extension `E` with formal type parameters -`X1 extends B1 .. Xs extends Bs` and a constructor return type +We associate a static extension declaration _D_ named `E` with formal type +parameters `X1 extends B1 .. Xs extends Bs` and an actual type argument +list `T1 .. Ts` with a type known as the _instantiated constructor return +type of_ _D_ _with type arguments_ `T1 .. Ts`. + +When a static extension declaration _D_ named `E` has an on-clause which is +a non-generic class `C`, the instantiated constructor return type is `C`, +for any list of actual type arguments. + +*It is not very useful to declare a type parameter of a static extension +which isn't used in the constructor return type, because it can only be +passed in an explicitly resolved constructor invocation, e.g., +`E.C(42)`. In all other invocations, the value of such type variables +is determined by instantiation to bound.* + +When a static extension declaration _D_ has no formal type parameters, and +it has an on-type `C`, the instantiated constructor return type +of _D_ is `C`. *In this case the on-type is a fixed type (also +known as a ground type), e.g., `List`. This implies that the +constructor return type of D is the same for every call site.* + +Consider a static extension declaration _D_ named `E` with formal type +parameters `X1 extends B1 .. Xs extends Bs` and a constructor return type `C`. With actual type arguments `T1 .. Ts`, the instantiated -constructor return type of `E` with type arguments `T1 .. Ts` is +constructor return type of _D_ with type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]C`. #### Explicit invocation of a constructor in a static extension @@ -349,10 +371,10 @@ but they need more detailed rules because they can use the formal type parameters declared by the static extension. An _explicitly resolved invocation_ of a constructor named `C.name` in a -static extension `E` with `s` type parameters and on-class `C` can be -expressed as `E.C.name(args)`, `E.C.name(args)`, or -`E.C.name(args)` (and similarly for a constructor -named `C` using `E.C(args)` etc). +static extension declaration _D_ named `E` with `s` type parameters and +on-class `C` can be expressed as `E.C.name(args)`, `E.C.name(args)`, or `E.C.name(args)` (and similarly +for a constructor named `C` using `E.C(args)` etc). A compile-time error occurs if the type arguments passed to `E` violate the declared bounds. A compile-time error occurs if no type arguments are @@ -362,12 +384,12 @@ that the instantiated constructor return type of `E` with said type arguments is `C`. A compile-time error occurs if the invocation passes actual type arguments -to both `E` and `C`, `S1 .. Ss` and `U1 .. Uk`, respectively, unless the -instantiated constructor return type of `E` with actual type arguments -`S1 .. Ss` is `C`. In this type comparison, top types like -`dynamic` and `Object?` are considered different, and no type normalization -occurs. *In other words, the types must be identical, not just mutual -subtypes.* +to both `E` and `C`, call them `S1 .. Ss` and `U1 .. Uk`, respectively, +unless the instantiated constructor return type of _D_ with actual type +arguments `S1 .. Ss` is `C`. In this type comparison, top types +like `dynamic` and `Object?` are considered different, and no type +normalization occurs. *In other words, the types must be identical, not +just mutual subtypes.* *Note that explicitly resolved invocations of constructors declared in static extensions are an exception in real code, usable in the case where a @@ -383,25 +405,19 @@ every accessible static extension with on-class `C`. A compile-time error occurs if no such constructor is found. Similarly, an invocation of the form `C(args)` uses a lookup for constructors named `C`. -*It is not possible to find a mixture consisting of one constructor in the -on-class and one or more constructors in static extensions: That's already -a compile-time error at the declarations in the static extension. Hence, -the invocation is resolved to a single constructor in `C`, or it is -partially resolved to a set of constructors in static extensions.* - -If the invocation resolves to a constructor in `C`, the pre-feature static -analysis and dynamic semantics apply. +If a constructor in `C` with the requested name was found, the pre-feature +static analysis and dynamic semantics apply. Otherwise, the invocation is partially resolved to a set of candidate constructors found in static extensions. Each of the candidates _kj_ is vetted as follows: -Assume that _kj_ is a constructor declared by a static extension `E` with -type parameters `X1 extends B1 .. Xs extends Bs` and on-type `C`. -Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds `B1 -.. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. If this -fails then remove _kj_ from the set of candidate constructors. Otherwise -note that _kj_ uses actual type arguments `U1 .. Us`. +Assume that _kj_ is a constructor declared by a static extension _D_ named +`E` with type parameters `X1 extends B1 .. Xs extends Bs` and on-type `C`. Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the +bounds `B1 .. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. +If this fails then remove _kj_ from the set of candidate constructors. +Otherwise note that _kj_ uses actual type arguments `U1 .. Us`. If all candidate constructors have been removed, or more than one candidate remains, a compile-time error occurs. Otherwise, the invocation is @@ -423,8 +439,8 @@ receive said actual type arguments, `C.name(args)`, and treated as described above. In the case where the invocation resolves to exactly one constructor -`C.name` (or `C`) declared by a static extension `E`, the invocation is -treated as `E.C.name(args)` (respectively `E.C(args)`). +`C.name` (or `C`) declared by a static extension named `E`, the invocation +is treated as `E.C.name(args)` (respectively `E.C(args)`). Otherwise, when there are two or more candidates from static extensions, an error occurs. *We do not wish to specify an approach whereby `args` is @@ -441,16 +457,16 @@ that declares an `implicit` constructor _k_ whose formal parameter has type `U` *(which may contain some of `X1 .. Xs`)*. *Note that an `implicit` constructor must always have exactly one formal -parameter.* +parameter. It must be positional and it can be optional.* Let `T1 .. Ts` be types satisfying the bounds `B1 .. Bs`. We then say that the _instantiated parameter type_ of the implicit constructor _k_ with actual type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]U`. - Let `E1` be a static extension with `s` type parameters, let `T1 .. Ts` be types that satisfy the bounds of `E1`, and assume that _k1_ is an `implicit` constructor declared by `E1`. + Let `E2` be a static extension with `t` type parameters, let `S1 .. St` be types that satisfy the bounds of `E2`, and assume that _k2_ is an `implicit` constructor declared by `E2`. @@ -464,24 +480,24 @@ If the two instantiated parameter types are mutual subtypes then we say that the two constructors are equally specific. With a list of extensions `E1 .. En` and corresponding actual type -arguments and implicit constructors _k1 .. km_, we say that the most -specific one is _kj_ iff that constructor with the given type arguments is +arguments and implicit constructors _k1 .. km_, we say that the _most +specific_ one is _kj_ iff that constructor with the given type arguments is more specific than each of the others. #### Static extension applicability -Let `E` be a static extension with type parameters -`X1 extends B1 .. Xs extends Bs` and constructor return type +Let _D_ be a static extension declaration named `E` with type parameters +`X1 extends B1 .. Xs extends Bs` and constructor return type `C`. Let `P` be a context type schema. -Let `f` be a function declared as follows, also known as the +Let `f` be a function declared as follows, also known as the _applicability function_ of `E`: ```dart C f() => f(); ``` -We say that `E` is _applicable with context type schema_ `P` +We say that _D_ is _applicable with context type schema_ `P` _yielding actual type arguments_ `S1 .. Ss` iff type inference of the invocation `f()` with context type schema `P` yields a list of actual type arguments `S1 .. Ss` to `f` such that `[S1/X1 .. Ss/Xs]C` is @@ -494,14 +510,16 @@ invoked implicitly. *For example, we can have a declaration `Distance d = 1;`, and it may be transformed into `Distance d = Distance.fromInt(1);` where -`Distance.fromInt` is an implicit constructor declared in an accessible -static extension with on-class `Distance` whose parameter type is `int`.* +`Distance.fromInt` is an enabled implicit constructor declared in an +accessible static extension with on-class `Distance` whose parameter type +is `int`.* + +First, we need to introduce the notion of an assignment position. -First, we need to introduce the notion of an _assignment position_. -An expression can occur in an assignment position. This includes being +An expression can occur in an _assignment position_. This includes being the right hand side of an assignment, an initializing expression in a variable declaration, an actual argument in a function or method -invocation, and more. +invocation, the right operand of a binary operator, and more. *This concept is already used to determine whether a coercion like generic function instantiation is applicable. In this document we rely on this @@ -510,8 +528,8 @@ specified.* Assume that an expression `e` occurs in an assignment position with context type schema `P`. In this situation, type inference is performed on `e` with -context type schema `P`, and the resulting expression `e1` has some type -`T0`. Assume that `e1` is not subject to any built-in coercions *(at this +context type schema `P`, and the resulting expression `e0` has some type +`T0`. Assume that `e0` is not subject to any built-in coercions *(at this time this means generic function instantiation or call method tear-off)*, and `T0` is not assignable to the greatest closure of `P`. In this case we say that `e` is _potentially subject to implicit construction with source @@ -526,7 +544,7 @@ source type `T0`, the following steps are performed: each with an actual type argument list `A1 .. An` *(each `Aj` is a list of types, whose length is the same as the type parameter list of `Ej`)*. The candidate constructors are then all constructors in `E1 .. En` which - are marked `implicit`. + are marked `implicit`, and which are enabled. - For each candidate constructor _k_, eliminate _k_ from the set of candidates if `T0` is not assignable to the instantiated parameter type of _k_ with the actual type arguments `Aj` of the static extension `Ej` @@ -537,8 +555,8 @@ source type `T0`, the following steps are performed: the given actual type arguments of the enclosing static extension, an error occurs. - Otherwise, one specific static extension `Ej` with actual type arguments - `Aj`, and one constructor _k_ declared by `Ej` is most specific. Let - `C.name` (respectively `C`) be the name of _k_. + `Aj`, and one enabled constructor _k_ declared by `Ej` is most + specific. Let `C.name` (respectively `C`) be the name of _k_. - The expression `e` is then replaced by `Ej.C.name(e0)` (respectively `Ej.C(e0)`). @@ -593,8 +611,7 @@ some rather strict rules: Scala even requires that the entity that provides implicit conversions is explicitly imported (using something similar to `show` in an import). We -could consider doing that, but the current proposal is a little more -permissive. +require that each constructor must be `enabled` in the impert. ### Changelog From 8d4fb95c53d5745152d34d626f40f39a0ebe2bae Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 22 Aug 2023 15:05:01 +0200 Subject: [PATCH 6/7] Update "inline class" to "extension type" --- working/0107 - implicit-constructors/feature-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index a7a3bb369c..61c02b9bec 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -208,7 +208,7 @@ declaration by specifying several errors. A compile-time error occurs if the on-class of a static extension does not resolve to an enum declaration or a declaration of a class, a mixin, a mixin -class, or an inline class. +class, or an extension type. A compile-time error occurs if a static extension has an on-clause of the form `on C` where `C` denotes a generic class and no type arguments are From a571b4b13eb007479e6dd2f44501182e324949cf Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 22 Aug 2023 15:58:27 +0200 Subject: [PATCH 7/7] Typos --- working/0107 - implicit-constructors/feature-specification.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index 61c02b9bec..cba5efbce6 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -611,7 +611,7 @@ some rather strict rules: Scala even requires that the entity that provides implicit conversions is explicitly imported (using something similar to `show` in an import). We -require that each constructor must be `enabled` in the impert. +require that each constructor must be `enabled` in the import. ### Changelog