From 5eb675d321528776303740dc95e0657f7d23548d Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 17 Dec 2024 09:21:08 +0100 Subject: [PATCH 1/8] New file, 3rd variant of static extensions --- .../feature-specification-variant3.md | 566 ++++++++++++++++++ 1 file changed, 566 insertions(+) create mode 100644 working/0723-static-extensions/feature-specification-variant3.md diff --git a/working/0723-static-extensions/feature-specification-variant3.md b/working/0723-static-extensions/feature-specification-variant3.md new file mode 100644 index 000000000..6fe9c2fed --- /dev/null +++ b/working/0723-static-extensions/feature-specification-variant3.md @@ -0,0 +1,566 @@ +# Static Extensions + +Author: Erik Ernst + +Status: Draft + +Version: 1.1 + +Experiment flag: static-extensions + +This document specifies static extensions. This is a feature that supports +the addition of static members and/or constructors to an existing +declaration that can have such members, in a way which is somewhat similar +to the addition of instance members using a plain `extension` declaration. + +## Introduction + +A feature like static extensions was requested already several years ago in +[language issue #723][issue 723], and elsewhere. + +[issue 723]: https://github.com/dart-lang/language/issues/723 + +The main motivation for this feature is that developers wish to add +constructors or static members to an existing class, mixin, enum, or +extension type declaration, but they do not have the ability to directly +edit the source code of said declaration. + +This feature allows static members to be declared in static extensions of a +given class/mixin/etc. declaration, and they can be invoked as if they had +been declared in said declaration. + +Here is an example: + +```dart +class Distance { + final int value; + const Distance(this.value); +} + +static extension E1 on Distance { + factory Distance.fromHalf(int half) => Distance(2 * half); +} + +void walk(Distance d) {...} + +void main() { + walk(Distance.fromHalf(10)); +} +``` + +A static extension declaration is associated with an _on-declaration_ (as +opposed to a plain extension declaration which is associated with an +on-type). In the example above, the on-declaration of `E1` is `Distance`. + +When the on-declaration of a static extension declaration is a generic +class `G`, it is denoted by the corresponding raw type `G`. + +For example: + +```dart +static extension E2 on Map { + static Map castFromKey(Map source) => + Map.castFrom(source); +} + +static extension E3 on Map { + factory Map.fromJson( + Map source + ) => + Map.from(source); +} + +var jsonMap = {"key": 42}; +var typedMap = Map.fromJson(jsonMap); + +// But `Map.fromJson(...)` is an error: It violates the +// bound of `K`. +``` + +Another motivation for this mechanism is that it supports constructors of +generic classes whose invocation is only allowed when the given actual type +arguments satisfy some constraints that are stronger than the ones required +by the class itself. + +For example, we might have a class `SortedList` where the regular +constructors (in the class itself) require an argument of type +`Comparator`, but a static extension provides an extra constructor that +does not require the `Comparator` argument. This extra constructor would +have a constraint on the actual type argument, namely that it is an `X` such +that `X extends Comparable`. + +```dart +class SortedList { + final Comparator _comparator; + SortedList(Comparator this._comparator); + // ... lots of stuff that doesn't matter here ... +} + +static extension on SortedList { + SortedList.ofComparable>() + : this((X a, X b) => a.compareTo(b)); +} +``` + +A static extension with type parameters can be specialized for specific +values of specific type arguments by specifying actual type arguments of +the on-declaration to be types that depend on each other, or types with no +type variables: + +```dart +static extension E4 on Map { + factory Map>.listValue(X x) => {x: [x]}; +} + +var int2intList = Map.listValue(1); // `Map>`. +// `Map.listValue(...)` is an error. + +static extension E6 on Map { + factory Map.fromString(Y y) => {y.toString(): y}; +} + +var string2bool = Map.fromString(true); // `Map`. +Map> string2listOfBool = Map.fromString([]); +``` + +## Specification + +### Syntax + +The grammar is modified as follows: + +```ebnf + ::= // Add a new alternative. + | + | + | + | // New alternative. + | + ... + + ::= // New rule. + 'static' 'extension' ? 'on' + '{' ( )* '}' + + ::= // New rule. + 'static' | + | + 'static' ';' + + ::= // New rule. + | + | + + + ::= // New rule. + | + ';' | + | + ';' + + ::= // New rule. + 'const'? 'factory' + + ::= + '.' + + ::= + + + + ::= // New rule. + ('final' | 'const') ? | + 'late' 'final' ? | + 'late'? +``` + +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-declaration of the static extension is `C`. + +If `C` denotes a non-generic class, mixin, mixin class, or extension +type then we say that the _constructor return type_ of the static extension +is `C`. + +If `C` denotes a generic declaration then `E` is treated as +`static extension E on C {...}` +where `T1 .. Tk` are obtained by instantiation to bound. + +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-declaration 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 parameters.* + +### 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-declaration of a static extension +does not resolve to an enum declaration or a declaration of a class, a +mixin, a mixin 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 +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-declaration then it can only contain `static` members. Note that if the +on-declaration 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 +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 to hold for the type variables `X1 .. Xs`. + +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. + +*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 +by the static extension, even in the case where there is a different +declaration in scope with the same name. For example:* + +```dart +// Assume that `FreshE` is the implicitly induced extra name for `E`. +static extension E on C { + static void foo() {} +} + +void f() { + // Name clash: type parameter shadows static extension. + C.foo(); // Resolved as `FreshE.foo()`. +} +``` + +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 diagnostic is emitted if a static extension _D_ declares a +constructor or a static member with the same basename as a constructor or a +static member in the on-declaration of _D_. + +*In other words, a static extension should not have name clashes with its +on-declaration. The warning above is aimed at static members and +constructors, but a similar warning would probably be useful for name +clashes with instance members as well.* + +#### Static extension scopes + +Static extensions introduce several scopes: + +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 +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 +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. + +A constructor in a static extension introduces scopes in the same way as +other constructor declarations. The return type of the constructor is the +constructor return type of the static extension *(that is, the type in the +`on` clause)*. + +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.* + +#### Static extension accessibility + +A static extension declaration _D_ is _accessible_ if _D_ is declared in +the current library, or if _D_ is imported and not hidden. + +*In particular, it is accessible even in the case where there is a name +clash with another locally declared or imported declaration with the same +name. This is also true if _D_ is imported with a prefix. Similarly, it is +accessible even in the case where _D_ does not have a name, if it is +declared in the current library.* + +#### Invocation of a static member + +*The language specification defines the notion of a _member invocation_ in +the section [Member Invocations][], which is used below. This concept +includes method invocations like `e.aMethod(24)`, property extractions +like `e.aGetter` or `e.aMethod` (tear-offs), operator invocations like +`e1 + e2` or `aListOrNull?[1] = e`, function invocations like `f()`. Each +of these expressions has a _syntactic receiver_ and an _associated member +name_. With `e.aMethod(24)`, the receiver is `e` and the associated +member name is `aMethod`, with `e1 + e2` the receiver is `e1` and the +member name is `+`, and with `f()` the receiver is `f` and the member name +is `call`. Note that the syntactic receiver is a type literal in the case +where the member invocation invokes a static member. In the following we +will specify invocations of static members using this concept.* + +[Member Invocations]: https://github.com/dart-lang/language/blob/94194cee07d7deadf098b1f1e0475cb424f3d4be/specification/dartLangSpec.tex#L13903 + +Consider an expression `e` which is a member invocation with syntactic +receiver `E` and associated member name `m`, where `E` denotes a static +extension and `m` is a static member declared by `E`. We say that `e` is an +_explicitly resolved invocation_ of said static member of `E`. + +*This can be used to invoke a static member of a specific static extension +in order to manually resolve a name clash. + +Consider an expression `e` which is a member invocation with syntactic +receiver `C` and an associated member name `m`, where `C` denotes a class +and `m` is a static member declared by `C`. The static analysis and dynamic +semantics of this expression is the same as in Dart before the introduction +of this feature. + +When `C` declares a static member whose basename is the basename of `m`, +but `C` does not declare a static member named `m` or a constructor named +`C.m`, a compile-time error occurs. *This is the same behavior as in +pre-feature Dart. It's about "near name clashes" involving a setter.* + +In the case where `C` does not declare any static members whose basename is +the basename of `m`, and `C` does not declare any constructors named `C.m2` +where `m2` is the basename of `m`, let _M_ be the set containing each +accessible extension whose on-declaration is `C`, and whose static members +include one with the name `m`, or which declares a constructor named `C.m`. + +*If `C` does declare a constructor with such a name `C.m2` then the given +expression is not a static member invocation. This case is described in a +section below.* + +Otherwise *(when `C` does not declare such a constructor)*, an error occurs +if _M_ is empty or _M_ contains more than one member. + +Otherwise *(when no error occurred)*, assume that _M_ contains exactly one +element which is an extension `E` that declares a static member named +`m`. The invocation is then treated as `E.m()` *(this is an explicitly +resolved invocation, which is specified above)*. + +Otherwise *(when `E` does not declare such a static member)*, _M_ will +contain exactly one element which is a constructor named `C.m`. This is not +a static member invocation, and it is specified in a section below. + +In addition to these rules for invocations of static members of a static +extension or a class, a corresponding set of rules exist for a static +extension and the following: An enumerated declaration *(`enum ...`)*, a +mixin class, a mixin, and an extension type. They only differ by being +concerned with a different kind of on-declaration. + +In addition to the member invocations specified above, it is also possible +to invoke a static member of the enclosing declaration based on lexical +lookup. This case is applicable when an expression in a class, enum, mixin +or extension type resolves to an invocation of a static member of the +enclosing declaration. + +*This invocation will never invoke a static member of a static extension +which is not the enclosing declaration. In other words, there is nothing +new in this case.* + +#### The instantiated constructor return type of a static extension + +Assume that _D_ is a generic static extension declaration named `E` with +formal type parameters `X1 extends B1, ..., Xs extends Bs` and constructor +return type `C`. Let `T1, ..., Ts` be a list of types. The +_instantiated constructor return type_ of _D_ _with actual type arguments_ +`T1 .. Ts` is then the type `[T1/X1 .. Ts/Xs]C`. + +*As a special case, assume that _D_ has an on-type which denotes a +non-generic class `C`. In this case, the instantiated constructor return +type is `C`, for any list of actual type arguments.* + +*Note that such type arguments can be useful, in spite of the fact that +they do not occur in the type of the newly created object. For example:* + +```dart +class A { + final int i; + A(this.i); +} + +static extension E on A { + A.computed(X x, int Function(X) fun): this(fun(x)); +} + +void main() { + // We can create an `A` "directly". + A a = A(42); + + // We can also use a function to compute the `int`. + a = A.computed('Hello!', (s) => s.length); +} +``` + +*As another special case, assume that _D_ has no formal type parameters, +and it has a constructor return type of the form `C`. In this +case the instantiated constructor return type of _D_ is `C`, +which is a ground type, and it is the same for all call sites.* + +#### 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 a static extension. + +An _explicitly resolved invocation_ of a constructor named `C.name` in a +static extension declaration _D_ named `E` with `s` type parameters and +on-declaration `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). + +*The point is that an explicitly resolved invocation has a static analysis +and dynamic semantics which is very easy to understand, based on the +information. In particular, the actual type arguments passed to the +extension determines the actual type arguments passed to the class, which +means that the explicitly resolved invocation typically has quite some +redundancy (but it is very easy to check whether it is consistent, and it +is an error if it is inconsistent). Every other form is reduced to this +explicitly resolved form.* + +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 +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`. + +*Note that we must be able to choose the values of the type parameters +`X1 .. Xs` such that the instantiated constructor return type is +exactly `C`, it is not sufficient that it is a subtype thereof, +or that it differs in any other way.* + +A compile-time error occurs if the invocation passes actual type arguments +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 equal, not +just mutual subtypes.* + +*Note that explicitly resolved invocations of constructors declared in +static extensions are a rare exception in real code, usable in the case +where a name clash prevents an implicitly resolved invocation. However, +implicitly resolved invocations are specified in the rest of this section +by reducing them to explicitly resolved ones.* + +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-declaration `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`. + +*Note that, as always, a constructor named `C` can also be denoted by +`C.new` (and it must be denoted as such in a constructor tear-off).* + +If a constructor in `C` with the requested name was found, the pre-feature +static analysis and dynamic semantics apply. *That is, the class always +wins.* + +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: + +If `m` is zero and `E` is an accessible extension with on-declaration `C` +that declares a static member whose basename is `name` then the invocation +is a static member invocation *(which is specified in an earlier section)*. + +Otherwise, assume that _kj_ is a constructor declared by a static extension +_D_ named `E` with type parameters `X1 extends B1 .. Xs extends Bs`, +on-declaration `C`, 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`. This may determine the +value of some of the actual type arguments `U1 .. Us`, and others may be +unconstrained (because they do not occur in `C`). Actual type +arguments corresponding to unconstrained type parameters are given as `_` +(and they are subject to inference later on, where the types of the actual +arguments `args` may influence their value). If this inference 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 +henceforth treated as `E.C.name(args)` (respectively +`E.C(args)`). *This is an explicitly resolved static +extension constructor invocation, which is specified above.* + +A constructor invocation of the form `C.name(args)` (respectively +`C(args)`) where `C` denotes a non-generic class is resolved in the +same manner, with `m == 0`. + +Consider a constructor invocation of the form `C.name(args)` (and similarly +for `C(args)`) where `C` denotes a generic class. As usual, the +invocation is treated as in the pre-feature language when it denotes a +constructor declared by the class `C`. + +In the case where the context type schema for this invocation +determines some actual type arguments of `C`, the expression is changed to +receive said actual type arguments, `C.name(args)` (where the +unconstrained actual type arguments are given as `_` and inferred later). +The expression is then treated as described above. + +Next, we construct a set _M_ containing all accessible static extensions +with on-declaration `C` that declare a constructor named `C.name` +(respectively `C`). + +In the case where _M_ contains exactly one extension `E` that declares a +constructor named `C.name` (respectively `C`), 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 +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. The workaround is to specify the actual type arguments +explicitly.* + +In addition to these rules for invocations of constructors of a static +extension or a class, a corresponding set of rules exist for a static +extension and the following: An enumerated declaration *(`enum ...`)*, a +mixin class, a mixin, and an extension type. They only differ by being +concerned with a different kind of declaration. + +### 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 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. +This fully determines the dynamic semantics of this feature. + +### Changelog + +1.1 - August 30, 2024 + +* Clarify many parts. + +1.0 - May 31, 2024 + +* First version of this document released. From 5a57208e91565ebf01e3291026823d11f9f9632a Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 19 Dec 2024 16:26:12 +0100 Subject: [PATCH 2/8] WIP --- .../feature-specification-variant3.md | 141 ++++++++---------- 1 file changed, 59 insertions(+), 82 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant3.md b/working/0723-static-extensions/feature-specification-variant3.md index 6fe9c2fed..cc8e598bf 100644 --- a/working/0723-static-extensions/feature-specification-variant3.md +++ b/working/0723-static-extensions/feature-specification-variant3.md @@ -143,58 +143,54 @@ The grammar is modified as follows: '{' ( )* '}' ::= // New rule. - 'static' | - | - 'static' ';' + 'static'? ';' | + 'static'? | + - ::= // New rule. + ::= // New rule. + ('final' | 'const') ? | + 'late' 'final' ? | + 'late'? + + ::= // New rule. | | ::= // New rule. | - ';' | - | - ';' + | + ';' - ::= // New rule. - 'const'? 'factory' - - ::= - '.' + ::= // New rule. + 'external' | + 'external' | + | + | + - ::= - + ::= // New rule. + + ::= // New rule. + 'const'? 'factory' - ::= // New rule. - ('final' | 'const') ? | - 'late' 'final' ? | - 'late'? + ::= // New rule. + ? '.' ``` 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-declaration of the static extension is `C`. - -If `C` denotes a non-generic class, mixin, mixin class, or extension -type then we say that the _constructor return type_ of the static extension -is `C`. - -If `C` denotes a generic declaration then `E` is treated as -`static extension E on C {...}` -where `T1 .. Tk` are obtained by instantiation to bound. - -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-declaration of `E` is `C`, and the _constructor return type_ of `E` is -`C`. +_on-declaration_ of the static extension 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 parameters.* +*In contrast, an `extension` declaration has an on-type. The difference +arises because an `extension` declaration enables the invocation of certain +extra, statically resolved methods, and they are enabled based on the type +of the syntactic receiver at the call site. A static extension is not +enabled or disabled based on the type of an expression, they are directly +looked up by reference to a declaration. This implies that a +`static extension` declaration must be unambiguously tied to a specific +declaration, which is then the one whose static interface is expanded.* ### Static Analysis @@ -203,24 +199,7 @@ declaration by specifying several errors. A compile-time error occurs if the on-declaration of a static extension does not resolve to an enum declaration or a declaration of a class, a -mixin, a mixin 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 -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-declaration then it can only contain `static` members. Note that if the -on-declaration 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 -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 to hold for the type variables `X1 .. Xs`. +mixin, a mixin class, an extension type, or an extension. Consider a static extension declaration _D_ named `E` which is declared in the current library or present in any exported namespace of an import @@ -257,8 +236,8 @@ A compile-time diagnostic is emitted if a static extension _D_ declares a constructor or a static member with the same basename as a constructor or a static member in the on-declaration of _D_. -*In other words, a static extension should not have name clashes with its -on-declaration. The warning above is aimed at static members and +*In other words, a static extension can, but should not, have name clashes +with its on-declaration. The warning above is aimed at static members and constructors, but a similar warning would probably be useful for name clashes with instance members as well.* @@ -267,29 +246,23 @@ clashes with instance members as well.* Static extensions introduce several scopes: 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 -parameter scope whose enclosing scope is the library scope. The current -scope for the on-clause of _D_ is the type parameter scope. +is the enclosing scope of _D_, that is, the library scope of the current +library. 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. +the library scope. Static members in a static extension are subject to the same static analysis as static members in other declarations. -A constructor in a static extension introduces scopes in the same way as -other constructor declarations. The return type of the constructor is the -constructor return type of the static extension *(that is, the type in the -`on` clause)*. +A non-generic constructor in a static extension introduces scopes in the +same way as other constructor declarations. The return type of the +constructor is the type corresponding on-declaration of the static +extension. -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.* +It is a compile-time error if a non-generic constructor is declared in a +static extension whose on-declaration is generic. #### Static extension accessibility @@ -308,14 +281,15 @@ declared in the current library.* the section [Member Invocations][], which is used below. This concept includes method invocations like `e.aMethod(24)`, property extractions like `e.aGetter` or `e.aMethod` (tear-offs), operator invocations like -`e1 + e2` or `aListOrNull?[1] = e`, function invocations like `f()`. Each -of these expressions has a _syntactic receiver_ and an _associated member -name_. With `e.aMethod(24)`, the receiver is `e` and the associated -member name is `aMethod`, with `e1 + e2` the receiver is `e1` and the -member name is `+`, and with `f()` the receiver is `f` and the member name -is `call`. Note that the syntactic receiver is a type literal in the case -where the member invocation invokes a static member. In the following we -will specify invocations of static members using this concept.* +`e1 + e2` or `aListOrNull?[1] = e`, and function invocations like `f()`. +Each of these expressions has a _syntactic receiver_ and an _associated +member name_. With `e.aMethod(24)`, the receiver is `e` and the +associated member name is `aMethod`, with `e1 + e2` the receiver is `e1` +and the member name is `+`, and with `f()` the receiver is `f` and the +member name is `call`. Note that the syntactic receiver is a type literal +in the case where the member invocation invokes a static member. In the +following we will specify invocations of static members using this +concept.* [Member Invocations]: https://github.com/dart-lang/language/blob/94194cee07d7deadf098b1f1e0475cb424f3d4be/specification/dartLangSpec.tex#L13903 @@ -325,7 +299,7 @@ extension and `m` is a static member declared by `E`. We say that `e` is an _explicitly resolved invocation_ of said static member of `E`. *This can be used to invoke a static member of a specific static extension -in order to manually resolve a name clash. +in order to manually resolve a name clash.* Consider an expression `e` which is a member invocation with syntactic receiver `C` and an associated member name `m`, where `C` denotes a class @@ -341,8 +315,9 @@ pre-feature Dart. It's about "near name clashes" involving a setter.* In the case where `C` does not declare any static members whose basename is the basename of `m`, and `C` does not declare any constructors named `C.m2` where `m2` is the basename of `m`, let _M_ be the set containing each -accessible extension whose on-declaration is `C`, and whose static members -include one with the name `m`, or which declares a constructor named `C.m`. +accessible static extension whose on-declaration is `C`, and whose static +members include one with the name `m`, or which declares a constructor +named `C.m`. *If `C` does declare a constructor with such a name `C.m2` then the given expression is not a static member invocation. This case is described in a @@ -376,6 +351,8 @@ enclosing declaration. which is not the enclosing declaration. In other words, there is nothing new in this case.* +# TODO -- major revisions will be made anywhere beyond this point + #### The instantiated constructor return type of a static extension Assume that _D_ is a generic static extension declaration named `E` with From 1a3ffbdb643b6d3cffc4db1503dd71095a1690c0 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Mon, 6 Jan 2025 17:55:45 +0100 Subject: [PATCH 3/8] WIP --- .../0723-static-extensions/feature-specification-variant3.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant3.md b/working/0723-static-extensions/feature-specification-variant3.md index cc8e598bf..fb67d1c60 100644 --- a/working/0723-static-extensions/feature-specification-variant3.md +++ b/working/0723-static-extensions/feature-specification-variant3.md @@ -351,8 +351,6 @@ enclosing declaration. which is not the enclosing declaration. In other words, there is nothing new in this case.* -# TODO -- major revisions will be made anywhere beyond this point - #### The instantiated constructor return type of a static extension Assume that _D_ is a generic static extension declaration named `E` with @@ -392,6 +390,8 @@ and it has a constructor return type of the form `C`. In this case the instantiated constructor return type of _D_ is `C`, which is a ground type, and it is the same for all call sites.* +# TODO -- major revisions will be made anywhere beyond this point + #### Invocation of a constructor in a static extension Explicit constructor invocations are similar to static member invocations, From 714a6631dff04fbf48dac498af84e1ad8c4b34ba Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 13 Feb 2025 11:03:26 +0100 Subject: [PATCH 4/8] WIP --- .../feature-specification-variant2.md | 207 ++++++++---------- 1 file changed, 95 insertions(+), 112 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant2.md b/working/0723-static-extensions/feature-specification-variant2.md index 7f62ca96e..244817577 100644 --- a/working/0723-static-extensions/feature-specification-variant2.md +++ b/working/0723-static-extensions/feature-specification-variant2.md @@ -4,7 +4,7 @@ Author: Erik Ernst Status: Draft -Version: 1.1 +Version: 1.2 Experiment flag: static-extensions @@ -57,18 +57,24 @@ are only applicable to extensions that have an on-declaration, all other extensions will continue to work exactly as they do today. In the example above, the on-declaration of `E1` is `Distance`. -For example: +Here is an example where a static member is added to a class: ```dart // Static members must ignore the type parameters. It may be useful -// to omit the type parameters in the case where every member is -// static. +// to omit the type parameters from the extension in the case where +// every member is static. extension E2 on Map { static Map castFromKey(Map source) => Map.castFrom(source); } +``` + +An extension with a generic on-declaration _D_ which is a class or an +enumerated type can implicitly inject certain kinds of constructors into +_D_, and they are able to use the type parameters of the extension. For +example: -// Type parameters are used by constructors. +```dart extension E3 on Map { factory Map.fromJson(Map source) => Map.from(source); @@ -80,6 +86,22 @@ var typedMap = Map.fromJson(jsonMap); // bound of `K`. ``` +This situation is just an abbreviated notation for a declaration where the +constructor declares its own handling of genericity: + +```dart +// We could keep the type parameters of the extension, but they are now +// unused because the constructor declares its own type parameters. +extension SameAsE3 on Map { + factory Map.fromJson( + Map source + ) => Map.from(source); +} +``` + +An extension can declare factories and redirecting generative constructors, +but it cannot declare a non-redirecting generative constructor. + Another motivation for this mechanism is that it supports constructors of generic classes whose invocation is only allowed when the given actual type arguments satisfy some constraints that are stronger than the ones required @@ -125,6 +147,9 @@ Map> map3 = Map.fromString([]); ## Specification +This specification assumes that generic constructors have already been +added to Dart. + ### Syntax The grammar remains unchanged. @@ -138,11 +163,9 @@ redirecting factory constructor redirects to a constructor that does not exist, or there is a redirection cycle.* In an extension declaration of the form `extension E on C {...}` where `C` -is an identifier or an identifier with an import prefix that denotes a +is an identifier (or an identifier with an import prefix) that denotes a class, mixin, enum, or extension type declaration, we say that the -_on-declaration_ of the extension is `C`. If `C` denotes a non-generic -class, mixin, mixin class, or extension type then we say that the -_constructor return type_ of the extension is `C`. +_on-declaration_ of the extension is `C`. If `C` denotes a generic class then `E` is treated as `extension E on C {...}` where `T1 .. Tk` are obtained by @@ -151,20 +174,20 @@ instantiation to bound. In an extension of the form `extension E on C {...}` where `C` is an identifier or prefixed identifier that denotes a class, mixin, enum, or extension type declaration, we say that the _on-declaration_ of `E` is -`C`, and the _constructor return type_ of `E` is `C`. +`C`. In an extension of the form `extension E on F {...}` where `F` is a type alias whose transitive alias expansion denotes a class, mixin, enum, or extension type `C`, we say that the _on-declaration_ of `E` is `C`, and -the _constructor return type_ of `E` is the transitive alias expansion of -`F`. +the declaration is treated as if `F` were replaced by its +transitive alias expansion. In all other cases, an extension declaration does not have an -on-declaration nor a constructor return type. +on-declaration. -For the purpose of identifying the on-declaration and constructor return -type of a given extension, the types `void`, `dynamic`, and `Never` are not -considered to be classes, and neither are record types or function types. +For the purpose of identifying the on-declaration of a given extension, the +types `void`, `dynamic`, and `Never` are not considered to be classes, and +neither are record types or function types. *Also note that none of the following types are classes:* @@ -174,7 +197,9 @@ considered to be classes, and neither are record types or function types. *It may well be possible to allow record types and function types to be extended with constructors that are declared in an extension, but this is -left as a potential future enhancement.* +left as a potential future enhancement. It could be useful to be able to +denote a set of specific functions of a given type by declaring them as +static members of an extension on that function type.* ### Static Analysis @@ -183,9 +208,13 @@ by specifying several errors. It is a compile-time error to declare a constructor in an extension whose on-type is not regular-bounded, assuming that the type parameters declared -by the extension satisfy their bounds. It is a compile-time error to invoke -a constructor of an extension whose instantiated on-type is not -regular-bounded. +by the extension satisfy their bounds. + +*This is just a restatement of a compile-time error which is reported for +such invocations of the corresponding generic constructor.* + +It is a compile-time error to invoke a constructor of an extension whose +instantiated on-type is not regular-bounded. Tools may report diagnostic messages like warnings or lints in certain situations. This is not part of the specification, but here is one @@ -206,14 +235,15 @@ clashes with instance members as well.* the section [Member Invocations][], which is used below. This concept includes method invocations like `e.aMethod(24)`, property extractions like `e.aGetter` or `e.aMethod` (tear-offs), operator invocations like -`e1 + e2` or `aListOrNull?[1] = e`, function invocations like `f()`. Each -of these expressions has a _syntactic receiver_ and an _associated member -name_. With `e.aMethod(24)`, the receiver is `e` and the associated -member name is `aMethod`, with `e1 + e2` the receiver is `e1` and the -member name is `+`, and with `f()` the receiver is `f` and the member name -is `call`. Note that the syntactic receiver is a type literal in the case -where the member invocation invokes a static member. In the following we -will specify invocations of static members using this concept.* +`e1 + e2` or `aListOrNull?[1] = e`, and function invocations like `f()`. +Each of these expressions has a _syntactic receiver_ and an _associated +member name_. With `e.aMethod(24)`, the receiver is `e` and the +associated member name is `aMethod`, with `e1 + e2` the receiver is `e1` +and the member name is `+`, and with `f()` the receiver is `f` and the +member name is `call`. Note that the syntactic receiver is a type literal +in the case where the member invocation invokes a static member. In the +following we will specify invocations of static members using this +concept.* [Member Invocations]: https://github.com/dart-lang/language/blob/94194cee07d7deadf098b1f1e0475cb424f3d4be/specification/dartLangSpec.tex#L13903 @@ -226,7 +256,7 @@ _explicitly resolved invocation_ of said static member of `E`. order to manually resolve a name clash. At the same time, in Dart without the feature which is specified in this document, this is the only way we can invoke a static member of an extension (except when it is in scope, see -below), so it may be useful for backward compatibility.* +below), so it can also be useful because it avoids breaking existing code.* Consider an expression `e` which is a member invocation with syntactic receiver `C` and an associated member name `m`, where `C` denotes a class @@ -277,45 +307,6 @@ enclosing declaration. not the enclosing declaration. In other words, there is nothing new in this case.* -#### The instantiated constructor return type of an extension - -Assume that _D_ is a generic extension declaration named `E` with formal -type parameters `X1 extends B1, ..., Xs extends Bs` and constructor return -type `C`. Let `T1, ..., Ts` be a list of types. The -_instantiated constructor return type_ of _D_ _with actual type arguments_ -`T1 .. Ts` is then the type `[T1/X1 .. Ts/Xs]C`. - -*As a special case, assume that _D_ has an on-type which denotes a -non-generic class `C`. In this case, the instantiated constructor return -type is `C`, for any list of actual type arguments.* - -*Note that such type arguments can be useful, in spite of the fact that -they do not occur in the type of the newly created object. For example:* - -```dart -class A { - final int i; - A(this.i); -} - -extension E on A { - A.computed(X x, int Function(X) fun): this(fun(x)); -} - -void main() { - // We can create an `A` "directly". - A a = A(42); - - // We can also use a function to compute the `int`. - a = A.computed('Hello!', (s) => s.length); -} -``` - -*As another special case, assume that _D_ has no formal type parameters, -and it has a constructor return type of the form `C`. In this -case the instantiated constructor return type of _D_ is `C`, -which is a ground type, and it is the same for all call sites.* - #### Invocation of a constructor in an extension Explicit constructor invocations are similar to explicitly resolved static @@ -324,38 +315,18 @@ the formal type parameters declared by an extension. An _explicitly resolved invocation_ of a constructor named `C.name` in an extension declaration _D_ named `E` with `s` type parameters and -on-declaration `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). - -*The point is that an explicitly resolved invocation has a static analysis -and dynamic semantics which is very easy to understand, based on the -information. In particular, the actual type arguments passed to the -extension determines the actual type arguments passed to the class, which -means that the explicitly resolved invocation typically has quite some -redundancy (but it is very easy to check whether it is consistent, and it -is an error if it is inconsistent). Every other form is reduced to this -explicitly resolved form.* - -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 -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`. - -*Note that we must be able to choose the values of the type parameters -`X1 .. Xs` such that the instantiated constructor return type is -exactly `C`, it is not sufficient that it is a subtype thereof, -or that it differs in any other way.* - -A compile-time error occurs if the invocation passes actual type arguments -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 equal, not -just mutual subtypes.* +on-declaration `C` can be expressed as `E.name(args)`, or (if it is a +generic constructor) `E.name(args)`. + +An explicitly resolved invocation of a constructor named `C` in an +extension declaration _D_ named `E` with `s` type parameters and +on-declaration `C` can be expressed as `E.new(args)`, or (if it is a +generic constructor) `E.new(args)`. + +*We might be able to allow invocations of the form `E(args)`, but they are +probably too confusing for a reader who does not know that it is a +constructor invocation which could have been written as `C(args)` where `C` +is the on-declaration of `E`.* *Note that explicitly resolved invocations of constructors declared in extensions are a rare exception in real code, usable in the case where a @@ -384,18 +355,24 @@ If `m` is zero and `E` is an accessible extension with on-declaration `C` that declares a static member whose basename is `name` then the invocation is a static member invocation *(which is specified in an earlier section)*. -Otherwise, assume that _kj_ is a constructor declared by an extension _D_ -named `E` with type parameters `X1 extends B1 .. Xs extends Bs`, -on-declaration `C`, 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`. This may determine the -value of some of the actual type arguments `U1 .. Us`, and others may be -unconstrained (because they do not occur in `C`). Actual type -arguments corresponding to unconstrained type parameters are given as `_` -(and they are subject to inference later on, where the types of the actual -arguments `args` may influence their value). If this inference fails -then remove _kj_ from the set of candidate constructors. Otherwise note -that _kj_ uses actual type arguments `U1 .. Us`. +Otherwise, assume that _kj_ is a generic constructor declared by an +extension _D_ named `E` with type parameters +`X1 extends B1 .. Xs extends Bs`, on-declaration `C`, and return type +`C` (or _kj_ is a non-generic constructor that gets the same type +parameters and type arguments from the enclosing extension). + +!!!TODO!!! + +Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds +`B1 .. Bs`, such that `([U1/X1 .. Us/Xs]C) == C`. This +may determine the value of some of the actual type arguments `U1 .. Us`, +and others may be unconstrained (because they do not occur in +`C`). Actual type arguments corresponding to unconstrained type +parameters are given as `_` (and they are subject to inference later on, +where the types of the actual arguments `args` may influence their +value). If this inference 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 @@ -457,6 +434,12 @@ This fully determines the dynamic semantics of this feature. ### Changelog +1.2 - Feb 6, 2025 + +* Change the text to rely on generic constructor declarations, rather + than introducing a new mechanism which is only used with extensions. +* !!!TODO!!! + 1.1 - Aug 21, 2024 * Extensive revision of this document based on thorough review. From 87e93d04ed6a6c129755239eb986c06049dd3aa4 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Thu, 27 Feb 2025 15:33:48 +0100 Subject: [PATCH 5/8] Remove variant3, I will not work on `static extensions` at this time --- .../feature-specification-variant3.md | 543 ------------------ 1 file changed, 543 deletions(-) delete mode 100644 working/0723-static-extensions/feature-specification-variant3.md diff --git a/working/0723-static-extensions/feature-specification-variant3.md b/working/0723-static-extensions/feature-specification-variant3.md deleted file mode 100644 index fb67d1c60..000000000 --- a/working/0723-static-extensions/feature-specification-variant3.md +++ /dev/null @@ -1,543 +0,0 @@ -# Static Extensions - -Author: Erik Ernst - -Status: Draft - -Version: 1.1 - -Experiment flag: static-extensions - -This document specifies static extensions. This is a feature that supports -the addition of static members and/or constructors to an existing -declaration that can have such members, in a way which is somewhat similar -to the addition of instance members using a plain `extension` declaration. - -## Introduction - -A feature like static extensions was requested already several years ago in -[language issue #723][issue 723], and elsewhere. - -[issue 723]: https://github.com/dart-lang/language/issues/723 - -The main motivation for this feature is that developers wish to add -constructors or static members to an existing class, mixin, enum, or -extension type declaration, but they do not have the ability to directly -edit the source code of said declaration. - -This feature allows static members to be declared in static extensions of a -given class/mixin/etc. declaration, and they can be invoked as if they had -been declared in said declaration. - -Here is an example: - -```dart -class Distance { - final int value; - const Distance(this.value); -} - -static extension E1 on Distance { - factory Distance.fromHalf(int half) => Distance(2 * half); -} - -void walk(Distance d) {...} - -void main() { - walk(Distance.fromHalf(10)); -} -``` - -A static extension declaration is associated with an _on-declaration_ (as -opposed to a plain extension declaration which is associated with an -on-type). In the example above, the on-declaration of `E1` is `Distance`. - -When the on-declaration of a static extension declaration is a generic -class `G`, it is denoted by the corresponding raw type `G`. - -For example: - -```dart -static extension E2 on Map { - static Map castFromKey(Map source) => - Map.castFrom(source); -} - -static extension E3 on Map { - factory Map.fromJson( - Map source - ) => - Map.from(source); -} - -var jsonMap = {"key": 42}; -var typedMap = Map.fromJson(jsonMap); - -// But `Map.fromJson(...)` is an error: It violates the -// bound of `K`. -``` - -Another motivation for this mechanism is that it supports constructors of -generic classes whose invocation is only allowed when the given actual type -arguments satisfy some constraints that are stronger than the ones required -by the class itself. - -For example, we might have a class `SortedList` where the regular -constructors (in the class itself) require an argument of type -`Comparator`, but a static extension provides an extra constructor that -does not require the `Comparator` argument. This extra constructor would -have a constraint on the actual type argument, namely that it is an `X` such -that `X extends Comparable`. - -```dart -class SortedList { - final Comparator _comparator; - SortedList(Comparator this._comparator); - // ... lots of stuff that doesn't matter here ... -} - -static extension on SortedList { - SortedList.ofComparable>() - : this((X a, X b) => a.compareTo(b)); -} -``` - -A static extension with type parameters can be specialized for specific -values of specific type arguments by specifying actual type arguments of -the on-declaration to be types that depend on each other, or types with no -type variables: - -```dart -static extension E4 on Map { - factory Map>.listValue(X x) => {x: [x]}; -} - -var int2intList = Map.listValue(1); // `Map>`. -// `Map.listValue(...)` is an error. - -static extension E6 on Map { - factory Map.fromString(Y y) => {y.toString(): y}; -} - -var string2bool = Map.fromString(true); // `Map`. -Map> string2listOfBool = Map.fromString([]); -``` - -## Specification - -### Syntax - -The grammar is modified as follows: - -```ebnf - ::= // Add a new alternative. - | - | - | - | // New alternative. - | - ... - - ::= // New rule. - 'static' 'extension' ? 'on' - '{' ( )* '}' - - ::= // New rule. - 'static'? ';' | - 'static'? | - - - ::= // New rule. - ('final' | 'const') ? | - 'late' 'final' ? | - 'late'? - - ::= // New rule. - | - | - - - ::= // New rule. - | - | - ';' - - ::= // New rule. - 'external' | - 'external' | - | - | - - - ::= // New rule. - - - ::= // New rule. - 'const'? 'factory' - - ::= // New rule. - ? '.' -``` - -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-declaration_ of the static extension is `C`. - -*In contrast, an `extension` declaration has an on-type. The difference -arises because an `extension` declaration enables the invocation of certain -extra, statically resolved methods, and they are enabled based on the type -of the syntactic receiver at the call site. A static extension is not -enabled or disabled based on the type of an expression, they are directly -looked up by reference to a declaration. This implies that a -`static extension` declaration must be unambiguously tied to a specific -declaration, which is then the one whose static interface is expanded.* - -### 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-declaration of a static extension -does not resolve to an enum declaration or a declaration of a class, a -mixin, a mixin class, an extension type, or an extension. - -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. - -*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 -by the static extension, even in the case where there is a different -declaration in scope with the same name. For example:* - -```dart -// Assume that `FreshE` is the implicitly induced extra name for `E`. -static extension E on C { - static void foo() {} -} - -void f() { - // Name clash: type parameter shadows static extension. - C.foo(); // Resolved as `FreshE.foo()`. -} -``` - -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 diagnostic is emitted if a static extension _D_ declares a -constructor or a static member with the same basename as a constructor or a -static member in the on-declaration of _D_. - -*In other words, a static extension can, but should not, have name clashes -with its on-declaration. The warning above is aimed at static members and -constructors, but a similar warning would probably be useful for name -clashes with instance members as well.* - -#### Static extension scopes - -Static extensions introduce several scopes: - -The current scope for the on-clause of a static extension declaration _D_ -is the enclosing scope of _D_, that is, the library scope of the current -library. - -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 -the library scope. - -Static members in a static extension are subject to the same static -analysis as static members in other declarations. - -A non-generic constructor in a static extension introduces scopes in the -same way as other constructor declarations. The return type of the -constructor is the type corresponding on-declaration of the static -extension. - -It is a compile-time error if a non-generic constructor is declared in a -static extension whose on-declaration is generic. - -#### Static extension accessibility - -A static extension declaration _D_ is _accessible_ if _D_ is declared in -the current library, or if _D_ is imported and not hidden. - -*In particular, it is accessible even in the case where there is a name -clash with another locally declared or imported declaration with the same -name. This is also true if _D_ is imported with a prefix. Similarly, it is -accessible even in the case where _D_ does not have a name, if it is -declared in the current library.* - -#### Invocation of a static member - -*The language specification defines the notion of a _member invocation_ in -the section [Member Invocations][], which is used below. This concept -includes method invocations like `e.aMethod(24)`, property extractions -like `e.aGetter` or `e.aMethod` (tear-offs), operator invocations like -`e1 + e2` or `aListOrNull?[1] = e`, and function invocations like `f()`. -Each of these expressions has a _syntactic receiver_ and an _associated -member name_. With `e.aMethod(24)`, the receiver is `e` and the -associated member name is `aMethod`, with `e1 + e2` the receiver is `e1` -and the member name is `+`, and with `f()` the receiver is `f` and the -member name is `call`. Note that the syntactic receiver is a type literal -in the case where the member invocation invokes a static member. In the -following we will specify invocations of static members using this -concept.* - -[Member Invocations]: https://github.com/dart-lang/language/blob/94194cee07d7deadf098b1f1e0475cb424f3d4be/specification/dartLangSpec.tex#L13903 - -Consider an expression `e` which is a member invocation with syntactic -receiver `E` and associated member name `m`, where `E` denotes a static -extension and `m` is a static member declared by `E`. We say that `e` is an -_explicitly resolved invocation_ of said static member of `E`. - -*This can be used to invoke a static member of a specific static extension -in order to manually resolve a name clash.* - -Consider an expression `e` which is a member invocation with syntactic -receiver `C` and an associated member name `m`, where `C` denotes a class -and `m` is a static member declared by `C`. The static analysis and dynamic -semantics of this expression is the same as in Dart before the introduction -of this feature. - -When `C` declares a static member whose basename is the basename of `m`, -but `C` does not declare a static member named `m` or a constructor named -`C.m`, a compile-time error occurs. *This is the same behavior as in -pre-feature Dart. It's about "near name clashes" involving a setter.* - -In the case where `C` does not declare any static members whose basename is -the basename of `m`, and `C` does not declare any constructors named `C.m2` -where `m2` is the basename of `m`, let _M_ be the set containing each -accessible static extension whose on-declaration is `C`, and whose static -members include one with the name `m`, or which declares a constructor -named `C.m`. - -*If `C` does declare a constructor with such a name `C.m2` then the given -expression is not a static member invocation. This case is described in a -section below.* - -Otherwise *(when `C` does not declare such a constructor)*, an error occurs -if _M_ is empty or _M_ contains more than one member. - -Otherwise *(when no error occurred)*, assume that _M_ contains exactly one -element which is an extension `E` that declares a static member named -`m`. The invocation is then treated as `E.m()` *(this is an explicitly -resolved invocation, which is specified above)*. - -Otherwise *(when `E` does not declare such a static member)*, _M_ will -contain exactly one element which is a constructor named `C.m`. This is not -a static member invocation, and it is specified in a section below. - -In addition to these rules for invocations of static members of a static -extension or a class, a corresponding set of rules exist for a static -extension and the following: An enumerated declaration *(`enum ...`)*, a -mixin class, a mixin, and an extension type. They only differ by being -concerned with a different kind of on-declaration. - -In addition to the member invocations specified above, it is also possible -to invoke a static member of the enclosing declaration based on lexical -lookup. This case is applicable when an expression in a class, enum, mixin -or extension type resolves to an invocation of a static member of the -enclosing declaration. - -*This invocation will never invoke a static member of a static extension -which is not the enclosing declaration. In other words, there is nothing -new in this case.* - -#### The instantiated constructor return type of a static extension - -Assume that _D_ is a generic static extension declaration named `E` with -formal type parameters `X1 extends B1, ..., Xs extends Bs` and constructor -return type `C`. Let `T1, ..., Ts` be a list of types. The -_instantiated constructor return type_ of _D_ _with actual type arguments_ -`T1 .. Ts` is then the type `[T1/X1 .. Ts/Xs]C`. - -*As a special case, assume that _D_ has an on-type which denotes a -non-generic class `C`. In this case, the instantiated constructor return -type is `C`, for any list of actual type arguments.* - -*Note that such type arguments can be useful, in spite of the fact that -they do not occur in the type of the newly created object. For example:* - -```dart -class A { - final int i; - A(this.i); -} - -static extension E on A { - A.computed(X x, int Function(X) fun): this(fun(x)); -} - -void main() { - // We can create an `A` "directly". - A a = A(42); - - // We can also use a function to compute the `int`. - a = A.computed('Hello!', (s) => s.length); -} -``` - -*As another special case, assume that _D_ has no formal type parameters, -and it has a constructor return type of the form `C`. In this -case the instantiated constructor return type of _D_ is `C`, -which is a ground type, and it is the same for all call sites.* - -# TODO -- major revisions will be made anywhere beyond this point - -#### 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 a static extension. - -An _explicitly resolved invocation_ of a constructor named `C.name` in a -static extension declaration _D_ named `E` with `s` type parameters and -on-declaration `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). - -*The point is that an explicitly resolved invocation has a static analysis -and dynamic semantics which is very easy to understand, based on the -information. In particular, the actual type arguments passed to the -extension determines the actual type arguments passed to the class, which -means that the explicitly resolved invocation typically has quite some -redundancy (but it is very easy to check whether it is consistent, and it -is an error if it is inconsistent). Every other form is reduced to this -explicitly resolved form.* - -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 -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`. - -*Note that we must be able to choose the values of the type parameters -`X1 .. Xs` such that the instantiated constructor return type is -exactly `C`, it is not sufficient that it is a subtype thereof, -or that it differs in any other way.* - -A compile-time error occurs if the invocation passes actual type arguments -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 equal, not -just mutual subtypes.* - -*Note that explicitly resolved invocations of constructors declared in -static extensions are a rare exception in real code, usable in the case -where a name clash prevents an implicitly resolved invocation. However, -implicitly resolved invocations are specified in the rest of this section -by reducing them to explicitly resolved ones.* - -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-declaration `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`. - -*Note that, as always, a constructor named `C` can also be denoted by -`C.new` (and it must be denoted as such in a constructor tear-off).* - -If a constructor in `C` with the requested name was found, the pre-feature -static analysis and dynamic semantics apply. *That is, the class always -wins.* - -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: - -If `m` is zero and `E` is an accessible extension with on-declaration `C` -that declares a static member whose basename is `name` then the invocation -is a static member invocation *(which is specified in an earlier section)*. - -Otherwise, assume that _kj_ is a constructor declared by a static extension -_D_ named `E` with type parameters `X1 extends B1 .. Xs extends Bs`, -on-declaration `C`, 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`. This may determine the -value of some of the actual type arguments `U1 .. Us`, and others may be -unconstrained (because they do not occur in `C`). Actual type -arguments corresponding to unconstrained type parameters are given as `_` -(and they are subject to inference later on, where the types of the actual -arguments `args` may influence their value). If this inference 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 -henceforth treated as `E.C.name(args)` (respectively -`E.C(args)`). *This is an explicitly resolved static -extension constructor invocation, which is specified above.* - -A constructor invocation of the form `C.name(args)` (respectively -`C(args)`) where `C` denotes a non-generic class is resolved in the -same manner, with `m == 0`. - -Consider a constructor invocation of the form `C.name(args)` (and similarly -for `C(args)`) where `C` denotes a generic class. As usual, the -invocation is treated as in the pre-feature language when it denotes a -constructor declared by the class `C`. - -In the case where the context type schema for this invocation -determines some actual type arguments of `C`, the expression is changed to -receive said actual type arguments, `C.name(args)` (where the -unconstrained actual type arguments are given as `_` and inferred later). -The expression is then treated as described above. - -Next, we construct a set _M_ containing all accessible static extensions -with on-declaration `C` that declare a constructor named `C.name` -(respectively `C`). - -In the case where _M_ contains exactly one extension `E` that declares a -constructor named `C.name` (respectively `C`), 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 -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. The workaround is to specify the actual type arguments -explicitly.* - -In addition to these rules for invocations of constructors of a static -extension or a class, a corresponding set of rules exist for a static -extension and the following: An enumerated declaration *(`enum ...`)*, a -mixin class, a mixin, and an extension type. They only differ by being -concerned with a different kind of declaration. - -### 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 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. -This fully determines the dynamic semantics of this feature. - -### Changelog - -1.1 - August 30, 2024 - -* Clarify many parts. - -1.0 - May 31, 2024 - -* First version of this document released. From b1ba9db4f653ff47b649e1eb703d92716175ab80 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Tue, 4 Mar 2025 17:09:58 +0000 Subject: [PATCH 6/8] WIP --- .../feature-specification-variant2.md | 224 +++++++++--------- 1 file changed, 116 insertions(+), 108 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant2.md b/working/0723-static-extensions/feature-specification-variant2.md index 244817577..17deda825 100644 --- a/working/0723-static-extensions/feature-specification-variant2.md +++ b/working/0723-static-extensions/feature-specification-variant2.md @@ -307,115 +307,124 @@ enclosing declaration. not the enclosing declaration. In other words, there is nothing new in this case.* +#### Declarations of constructors in extensions + +This proposal relies on the [generic constructor proposal][]. In +particular, this proposal uses concepts and definitions from the generic +constructor proposal, and it is assumed that generic constructors are +supported by the underlying Dart language. + +[generic constructor proposal]: https://github.com/dart-lang/language/pull/4265 + +With this proposal, it is also supported to declare a generic constructor +in an extension, with the same syntax as in a class and in other type +introducing declarations. + +It is a compile-time error if an extension declares a generic constructor +which is non-redirecting and generative. *These constructors are only +supported inside the type introducing declaration whose type they are +creating instances of.* + +It is a compile-time error if an extension declaration _D_ declares a +generic constructor whose name is `C` (which includes declarations using +`C.new`) or `C.name` for some identifier `name`, if _D_ does not have an +on-declaration, or the name of the on-declaration is not `C`. Note that `C` +may be an identifier, or an identifier which is prefixed by an import +prefix. + +An extension can declare a constructor which is not generic, that is, it +does not declare any formal type parameters, and the constructor return +type does not receive any actual type arguments. + +If an extension declaration _D_ named `E` declares a non-generic +constructor _D1_ and the on-declaration of _D_ is non-generic then _D1_ is +treated as a generic constructor that declares zero type parameters and +passes zero actual type arguments to the constructor return type. + +*In other words, these constructors get the same treatment as generic +constructors, except that all steps involving type parameters are skipped.* + +A compile-time error occurs if an extension declaration _D_ named `E` +declares a constructor and the on-declaration of _D_ is generic, +and no actual type arguments are passed to the on-declaration in the +on-type of _D_. *In other words, the constructor return type will never +have actual type argumenst that are obtained by instantiation to bound.* + +If an extension declaration _D_ named `E` declares a non-generic +constructor _D1_ and the on-declaration of _D_ is generic then _D1_ is +treated as a generic constructor that declares exactly the same type +parameters as _D_, and it passes exactly the same actual type arguments to +the constructor return type as the ones that are passed to the +on-declaration in the on-type. + +*For example:* + +```dart +extension E1 on C, int> { + C.name(X x, Iterable ys): this(x, ys, 14); + // The previous line has the same meaning as the next line: + C, int>.name(X x, Iterable ys): this(x, ys, 14); +} + +extension E2 on D { // D is non-generic. + D.new(X x, Y y); + // Same as: + D.new(X x, Y y); +} +``` + #### Invocation of a constructor in an extension -Explicit constructor invocations are similar to explicitly resolved static -member invocations, but they need more detailed rules because they can use -the formal type parameters declared by an extension. - -An _explicitly resolved invocation_ of a constructor named `C.name` in an -extension declaration _D_ named `E` with `s` type parameters and -on-declaration `C` can be expressed as `E.name(args)`, or (if it is a -generic constructor) `E.name(args)`. - -An explicitly resolved invocation of a constructor named `C` in an -extension declaration _D_ named `E` with `s` type parameters and -on-declaration `C` can be expressed as `E.new(args)`, or (if it is a -generic constructor) `E.new(args)`. - -*We might be able to allow invocations of the form `E(args)`, but they are -probably too confusing for a reader who does not know that it is a -constructor invocation which could have been written as `C(args)` where `C` -is the on-declaration of `E`.* - -*Note that explicitly resolved invocations of constructors declared in -extensions are a rare exception in real code, usable in the case where a -name clash prevents an implicitly resolved invocation. However, implicitly -resolved invocations are specified in the rest of this section by reducing -them to explicitly resolved ones.* - -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 extension with on-declaration `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`. - -*Note that, as always, a constructor named `C` can also be denoted by -`C.new` (and it must be denoted as such in a constructor tear-off).* - -If a constructor in `C` with the requested name was found, the pre-feature -static analysis and dynamic semantics apply. *That is, the class always -wins.* - -Otherwise, the invocation is partially resolved to a set _M_ of candidate -constructors and static members found in extensions. Each of the candidates -_kj_ is vetted as follows: - -If `m` is zero and `E` is an accessible extension with on-declaration `C` -that declares a static member whose basename is `name` then the invocation -is a static member invocation *(which is specified in an earlier section)*. - -Otherwise, assume that _kj_ is a generic constructor declared by an -extension _D_ named `E` with type parameters -`X1 extends B1 .. Xs extends Bs`, on-declaration `C`, and return type -`C` (or _kj_ is a non-generic constructor that gets the same type -parameters and type arguments from the enclosing extension). - -!!!TODO!!! - -Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds -`B1 .. Bs`, such that `([U1/X1 .. Us/Xs]C) == C`. This -may determine the value of some of the actual type arguments `U1 .. Us`, -and others may be unconstrained (because they do not occur in -`C`). Actual type arguments corresponding to unconstrained type -parameters are given as `_` (and they are subject to inference later on, -where the types of the actual arguments `args` may influence their -value). If this inference 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 -henceforth treated as `E.C.name(args)` (respectively -`E.C(args)`). *This is an explicitly resolved extension -constructor invocation, which is specified above.* - -A constructor invocation of the form `C.name(args)` (respectively -`C(args)`) where `C` denotes a non-generic class is resolved in the -same manner, with `m == 0`. - -Consider a constructor invocation of the form `C.name(args)` (and similarly -for `C(args)`) where `C` denotes a generic class. As usual, the -invocation is treated as in the pre-feature language when it denotes a -constructor declared by the class `C`. - -In the case where the context type schema for this invocation -determines some actual type arguments of `C`, the expression is changed to -receive said actual type arguments, `C.name(args)` (where the -unconstrained actual type arguments are given as `_` and inferred later). -The expression is then treated as described above. - -Next, we construct a set _M_ containing all accessible extensions with -on-declaration `C` that declare a constructor named `C.name` (respectively -`C`). - -In the case where _M_ contains exactly one extension `E` that declares a -constructor named `C.name` (respectively `C`), the invocation is treated as -`E.C.name(args)` (respectively `E.C(args)`). - -Otherwise, when there are two or more candidates from 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. The workaround is to specify the actual type arguments -explicitly.* - -In addition to these rules for invocations of constructors of a class or an -extension, a corresponding set of rules exist for the following: An -enumerated declaration *(`enum ...`)*, a mixin class, a mixin, and an -extension type. They only differ by being concerned with a different kind -of declaration. +Assume that `E` denotes an extension declaration _D_ with on-declaration +_D1_ named `C`, and assume that _D_ declares a constructor whose name is +`C`. + +In that case an invocation of the form `E.new(arguments)` or +the form `E.new(arguments)` is a fully resolved invocation of said +constructor declaration. + +Similarly, if _D_ declares a constructor whose name is `C.name` then an +invocation of the form `E.name(arguments)` or +`E.name(arguments)` is a fully resolved invocation of said constructor +declaration. + +*This just means that there is no doubt about which constructor declaration +named `C` respectively `C.name` is denoted by this invocation.* + +If this invocation does not include actual type arguments and the denoted +constructor declares one or more type parameters then the invocation is +subject to e + + +Fully resolved invocations of constructors declared in extensions are not +expected to be common in actual source code. However, such invocations can +be used in order to resolve name clashes when multiple extensions are +accessible and two or more of them declare a constructor with the same +name, or one declares a constructor named `C.name` and another declares a +static member named `name`. Also, they define the semantics of extension +declared constructors with other forms, because those other forms are +reduced to the fully resolved form. + +*If multiple extensions declare static members with the same name and have +the same on-declaration then + +The forms `E.name(arguments)` and +`E.name(arguments)` are compile-time +errors. + + + + +However, other invocations of such constructors are transformed into fully +resolved ones, which determines their semantics and static analysis. + +Consider an instance creation expression of the form +`C.name(arguments)`, where +`` and `` may be absent. Assume that `C` denotes a class, a +mixin class, an enumerated type, or an extension type declaration + + + ### Dynamic Semantics @@ -438,7 +447,6 @@ This fully determines the dynamic semantics of this feature. * Change the text to rely on generic constructor declarations, rather than introducing a new mechanism which is only used with extensions. -* !!!TODO!!! 1.1 - Aug 21, 2024 From 7550271c35c1901ef8b01e95b7e852ee9e848e8c Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 5 Mar 2025 14:47:35 +0100 Subject: [PATCH 7/8] Done --- .../feature-specification-variant2.md | 173 +++++++++++------- 1 file changed, 102 insertions(+), 71 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant2.md b/working/0723-static-extensions/feature-specification-variant2.md index 17deda825..b70a32442 100644 --- a/working/0723-static-extensions/feature-specification-variant2.md +++ b/working/0723-static-extensions/feature-specification-variant2.md @@ -154,9 +154,10 @@ added to Dart. The grammar remains unchanged. -However, it is no longer an error to declare a factory constructor -(redirecting or not) or a redirecting generative constructor in an -extension declaration that has an on-declaration, possibly constant. +However, it is no longer an error to declare a factory constructor, +redirecting or not, or a redirecting generative constructor in an extension +declaration that has an on-declaration (defined later in this section), +and both kinds can be constant or not. *Such declarations may of course give rise to errors as usual, e.g., if a redirecting factory constructor redirects to a constructor that does not @@ -206,15 +207,15 @@ static members of an extension on that function type.* At first, we establish some sanity requirements for an extension declaration by specifying several errors. -It is a compile-time error to declare a constructor in an extension whose -on-type is not regular-bounded, assuming that the type parameters declared -by the extension satisfy their bounds. +It is a compile-time error to declare a constructor with no type parameters +in an extension whose on-type is not regular-bounded, assuming that the +type parameters declared by the extension satisfy their bounds. -*This is just a restatement of a compile-time error which is reported for -such invocations of the corresponding generic constructor.* - -It is a compile-time error to invoke a constructor of an extension whose -instantiated on-type is not regular-bounded. +*This constructor is desugared into a generic constructor which is +guaranteed to have a compile-time error. As a consequence, it is not +possible to invoke a constructor of an extension passing actual type +arguments (written or inferred) such that the on-type of the extension +is not regular-bounded.* Tools may report diagnostic messages like warnings or lints in certain situations. This is not part of the specification, but here is one @@ -249,7 +250,7 @@ concept.* Consider an expression `e` which is a member invocation with syntactic receiver `E` and associated member name `m`, where `E` denotes an extension -and `m` is a static member declared by `E`. We say that `e` is an +in scope and `m` is a static member declared by `E`. We say that `e` is an _explicitly resolved invocation_ of said static member of `E`. *This can be used to invoke a static member of a specific extension in @@ -258,32 +259,45 @@ the feature which is specified in this document, this is the only way we can invoke a static member of an extension (except when it is in scope, see below), so it can also be useful because it avoids breaking existing code.* +In the following, we assume that `C` denotes a type introducing membered +declaration _D_ (that is, a class, a mixin class, a mixin, an enum, or an +extension type declaration). `C` may be a type identifier, or it may be of +the form `prefix.id` where `prefix` and `id` are type identifiers, `prefix` +denotes an import prefix, and `id` denotes said type introducing membered +declaration in the namespace of that prefix. + Consider an expression `e` which is a member invocation with syntactic -receiver `C` and an associated member name `m`, where `C` denotes a class -and `m` is a static member declared by `C`. The static analysis and dynamic -semantics of this expression is the same as in Dart before the introduction -of this feature. +receiver `C` and an associated member name `m`. Assume that `m` is a static +member declared by _D_. The static analysis and dynamic semantics of this +expression is the same as in Dart before the introduction of this feature. -When `C` declares a static member whose basename is the basename of `m`, -but `C` does not declare a static member named `m` or a constructor named +*In other words, existing invocations of static members will continue to +have the same meaning as they had before this feature was introduced.* + +When _D_ declares a static member whose basename is the basename of `m`, +but _D_ does not declare a static member named `m` or a constructor named `C.m`, a compile-time error occurs. *This is the same behavior as in pre-feature Dart. It's about "near name clashes" involving a setter.* -In the case where `C` does not declare any static members whose basename is -the basename of `m`, and `C` does not declare any constructors named `C.m2` +In the case where _D_ does not declare any static members whose basename is +the basename of `m`, and _D_ does not declare any constructors named `C.m2` where `m2` is the basename of `m`, let _M_ be the set containing each -accessible extension whose on-declaration is `C`, and whose static members +accessible extension whose on-declaration is _D_, and whose static members include one with the name `m`, or which declares a constructor named `C.m`. -*If `C` does declare a constructor with such a name `C.m2` then the given +*If _D_ does declare a constructor with such a name `C.m2` then the given expression is not a static member invocation. This case is described in a section below.* -Otherwise *(when `C` does not declare such a constructor)*, an error occurs -if _M_ is empty or _M_ contains more than one member. +Otherwise *(when _D_ does not declare such a constructor)*, an error occurs +if _M_ is empty, or _M_ contains more than one member. + +*In other words, no attempt is made to disambiguate static member +invocations based on their signature or the on-type of the enclosing +extension declaration.* -Otherwise *(when no error occurred)*, assume that _M_ contains exactly one -element which is an extension `E` that declares a static member named +Otherwise *(when no error occurred)* _M_ contains exactly one element. +Assume that it is an extension `E` that declares a static member named `m`. The invocation is then treated as `E.m()` *(this is an explicitly resolved invocation, which is specified above)*. @@ -291,21 +305,13 @@ Otherwise *(when `E` does not declare such a static member)*, _M_ will contain exactly one element which is a constructor named `C.m`. This is not a static member invocation, and it is specified in a section below. -In addition to these rules for invocations of static members of an -extension or a class, a corresponding set of rules exist for an extension -and the following: An enumerated declaration *(`enum ...`)*, a mixin class, -a mixin, and an extension type. They only differ by being concerned with a -different kind of on-declaration. - In addition to the member invocations specified above, it is also possible to invoke a static member of the enclosing declaration based on lexical -lookup. This case is applicable when an expression in a class, enum, mixin -or extension type resolves to an invocation of a static member of the -enclosing declaration. +lookup. This case is applicable when an expression in an extension +declaration resolves to an invocation of a static member of the enclosing +extension. -*This invocation will never invoke a static member of an extension which is -not the enclosing declaration. In other words, there is nothing new in this -case.* +*There is nothing new in this treatment of lexically resolved invocations.* #### Declarations of constructors in extensions @@ -318,12 +324,12 @@ supported by the underlying Dart language. With this proposal, it is also supported to declare a generic constructor in an extension, with the same syntax as in a class and in other type -introducing declarations. +introducing membered declarations. It is a compile-time error if an extension declares a generic constructor which is non-redirecting and generative. *These constructors are only -supported inside the type introducing declaration whose type they are -creating instances of.* +supported inside the type introducing membered declaration of whose type +they are creating instances.* It is a compile-time error if an extension declaration _D_ declares a generic constructor whose name is `C` (which includes declarations using @@ -342,13 +348,7 @@ treated as a generic constructor that declares zero type parameters and passes zero actual type arguments to the constructor return type. *In other words, these constructors get the same treatment as generic -constructors, except that all steps involving type parameters are skipped.* - -A compile-time error occurs if an extension declaration _D_ named `E` -declares a constructor and the on-declaration of _D_ is generic, -and no actual type arguments are passed to the on-declaration in the -on-type of _D_. *In other words, the constructor return type will never -have actual type argumenst that are obtained by instantiation to bound.* +constructors, except that the type inference step is a no-op.* If an extension declaration _D_ named `E` declares a non-generic constructor _D1_ and the on-declaration of _D_ is generic then _D1_ is @@ -361,7 +361,7 @@ on-declaration in the on-type. ```dart extension E1 on C, int> { - C.name(X x, Iterable ys): this(x, ys, 14); + C.name(X x, Iterable ys): this(x, ys, 14); // The previous line has the same meaning as the next line: C, int>.name(X x, Iterable ys): this(x, ys, 14); } @@ -373,7 +373,7 @@ extension E2 on D { // D is non-generic. } ``` -#### Invocation of a constructor in an extension +#### Resolution of a constructor in an extension Assume that `E` denotes an extension declaration _D_ with on-declaration _D1_ named `C`, and assume that _D_ declares a constructor whose name is @@ -393,8 +393,9 @@ named `C` respectively `C.name` is denoted by this invocation.* If this invocation does not include actual type arguments and the denoted constructor declares one or more type parameters then the invocation is -subject to e - +subject to type inference in the same manner as an invocation of a generic +constructor which is declared in a type introducing membered declaration +*(e.g., a class)*. Fully resolved invocations of constructors declared in extensions are not expected to be common in actual source code. However, such invocations can @@ -405,26 +406,54 @@ static member named `name`. Also, they define the semantics of extension declared constructors with other forms, because those other forms are reduced to the fully resolved form. -*If multiple extensions declare static members with the same name and have -the same on-declaration then - The forms `E.name(arguments)` and `E.name(arguments)` are compile-time -errors. - - - - -However, other invocations of such constructors are transformed into fully -resolved ones, which determines their semantics and static analysis. +errors when `E` denotes an extension. + +*Consider the case where the extension declares type parameters and has a +generic on-declaration, and the constructor does not declare any type +parameters and does not pass any actual type arguments to the class: It +would be misleading to allow the extension as such to accept actual type +arguments in the same way as the class name in an invocation of a generic +constructor: The extension may declare a different number of type +parameters than the class, and it may not pass them directly (e.g., the +class might declare `` and the extension could declare `` and pass `>` to the class in its on-type). It would also +be misleading to allow the extension as such to receive actual type +arguments matching the declared type parameters of the extension, because +those type arguments should be passed after the period: they are being +passed to the constructor.* + +*Consider the case where the constructor declares its own type parameters: +In this case it certainly does not make sense to pass any type parameters +to the extension as such.* Consider an instance creation expression of the form `C.name(arguments)`, where -`` and `` may be absent. Assume that `C` denotes a class, a -mixin class, an enumerated type, or an extension type declaration +`` and `` may be absent. Assume that `C` +denotes a type introducing membered declaration _D_ (where `C` may include +an import prefix). Assume that _D_ does not declare a constructor named +`C.name`. +Let _M_ be the set of accessible extensions with on-declaration _D_ that +declare a constructor named `C.name` or a static member named `name`. +A compile-time error occurs if _M_ includes extensions with constructors as +well as static members. +Otherwise, if _M_ only includes static members then this is not an instance +creation expression, it is a static member invocation and it is specified +in an earlier section. + +Otherwise, _M_ only includes extensions containing constructors with the +requested name. A compile-time error occurs if _M_ is empty, or _M_ +contains two or more elements. Otherwise, the invocation denotes an +invocation of the constructor named `C.name` which is declared by +the extension declaration that _M_ contains. + +*Note that no attempt is made to determine that some constructors are "more +specific" or "less specific", it is simply a conflict if there are multiple +constructors with the requested name in the accessible extensions.* ### Dynamic Semantics @@ -432,18 +461,20 @@ The dynamic semantics of static members of an extension is the same as the dynamic semantics of other static functions. The dynamic semantics of an explicitly resolved invocation of a constructor -in an extension is determined by the normal semantics of function -invocation, except that the type parameters of the extension are -bound to the actual type arguments passed to the extension in the -invocation. +in an extension is determined by the normal semantics of generic +constructor invocations. An implicitly resolved invocation of a constructor declared by a static -extension is reduced to an explicitly resolved one during static analysis. -This fully determines the dynamic semantics of this feature. +extension is resolved as an invocation of a specific constructor in a +specific extension as described in the previous section. + +The semantics of the constructor invocation is the same for a generic +constructor which is declared in the type introducing membered declaration +*(at "home")* and for a constructor which is declared in an extension. ### Changelog -1.2 - Feb 6, 2025 +1.2 - Mar 5, 2025 * Change the text to rely on generic constructor declarations, rather than introducing a new mechanism which is only used with extensions. From 357b6a20b4aa7ed26347734b3d694834ce768c5c Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 5 Mar 2025 14:53:18 +0100 Subject: [PATCH 8/8] Renamed the feature specification that builds on extension declarations --- ...feature-specification-variant2.md => feature-specification.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename working/0723-static-extensions/{feature-specification-variant2.md => feature-specification.md} (100%) diff --git a/working/0723-static-extensions/feature-specification-variant2.md b/working/0723-static-extensions/feature-specification.md similarity index 100% rename from working/0723-static-extensions/feature-specification-variant2.md rename to working/0723-static-extensions/feature-specification.md