From edfefb4e78f0f667a0e1a53bd4e323be1262af86 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Fri, 30 Aug 2024 21:39:18 +0200 Subject: [PATCH] Many improvements, based on reviews of both documents --- .../feature-specification-variant1.md | 250 +++++++++++------- .../feature-specification-variant2.md | 74 +++--- 2 files changed, 200 insertions(+), 124 deletions(-) diff --git a/working/0723-static-extensions/feature-specification-variant1.md b/working/0723-static-extensions/feature-specification-variant1.md index eff486632..4e48bd95d 100644 --- a/working/0723-static-extensions/feature-specification-variant1.md +++ b/working/0723-static-extensions/feature-specification-variant1.md @@ -68,22 +68,27 @@ constructors in the static extension must return an object whose type is For example: ```dart -// Omit extension type parameters when the static extension only has static -// members. Type parameters are just noise for static members, anyway. +// Omit extension type parameters when the static extension only +// has static members. Type parameters are just noise for static +// members, anyway. static extension E2 on Map { static Map castFromKey(Map source) => Map.castFrom(source); } -// Declare extension type parameters, to be used by constructors. The -// type parameters can have stronger bounds than the on-declaration. +// Declare extension type parameters, to be used by constructors. +// The type parameters can have stronger bounds than the +// on-declaration. static extension E3 on Map { - factory Map.fromJson(Map source) => Map.from(source); + factory Map.fromJson(Map source) => + Map.from(source); } var jsonMap = {"key": 42}; var typedMap = Map.fromJson(jsonMap); -// `Map.fromJson(...)` is an error: Violates the bound of `K`. + +// But `Map.fromJson(...)` is an error: It violates the +// bound of `K`. ``` Another motivation for this mechanism is that it supports constructors of @@ -120,14 +125,14 @@ static extension E4 on Map> { factory Map.listValue(X x) => {x: [x]}; } -var int2intList = Map.listValue(1); // Inferred as `Map>`. +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); // Inferred as `Map`. +var string2bool = Map.fromString(true); // `Map`. Map> string2listOfBool = Map.fromString([]); ``` @@ -147,7 +152,8 @@ The grammar is modified as follows: ...; ::= // New rule. - 'static' 'extension' ? ? 'on' + 'static' 'extension' ? ? + 'on' '{' ( )* '}'; ::= // New rule. @@ -168,9 +174,6 @@ The grammar is modified as follows: ('final' | 'const') ? | 'late' 'final' ? | 'late'? ; - - ::= - (',' )*; ``` In a static extension of the form `static extension E on C {...}` where `C` @@ -257,7 +260,9 @@ 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.* +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 @@ -278,10 +283,10 @@ 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 factory constructor in a static extension introduces scopes in the same -way as other factory constructor declarations. The return type of the -factory constructor is the constructor return type of the static -extension *(that is, the type in the `on` clause)*. +A 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 @@ -295,46 +300,95 @@ 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.* - -#### Invocation of a static member of a static extension - -An _explicitly resolved invocation_ of a static member of a static -extension named `E` is an expression of the form `E.m()` (or any other -member access, *e.g., `E.m`, `E.m = e`, etc*), where `m` is a static member -declared by `E`. +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.* - -A static member invocation on a class `C`, of the form `C.m()` (or any -other member access), is resolved by looking up static members in `C` named -`m` and looking up static members of every accessible static extension with -on-declaration `C` and a member named `m`. - -If `C` contains such a declaration then the expression is an invocation of -that static member of `C`, with the same static analysis and dynamic -semantics as before the introduction of this feature. - -Otherwise, an error occurs if no declarations named `m` or more than one -declaration named `m` were found. *They would necessarily be declared in -static extensions.* - -Otherwise, the invocation is resolved to the given static member -declaration in a static extension named `Ej`, and the invocation is treated -as `Ej.m()` *(this is an explicitly resolved invocation, which is specified -above)*. +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 -We associate a static extension declaration _D_ named `E` with formal type -parameters `X1 extends B1 .. Xs extends Bs` and an actual type argument -list `T1 .. Ts` with a type known as the _instantiated constructor return -type of_ _D_ _with type arguments_ `T1 .. Ts`. +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`. -When a static extension declaration _D_ named `E` has an on-clause which -denotes a non-generic class `C`, the instantiated constructor return type -is `C`, for any list of actual type arguments. +*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:* @@ -352,25 +406,16 @@ static extension E on A { 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); } ``` -When a static extension declaration _D_ has no formal type parameters, and -it has an on-type `C`, the instantiated constructor return type -of _D_ is `C`. - -*In this case the on-type is a fixed type (also known as a ground type), -e.g., `List`. This implies that the constructor return type of D is -the same for every call site.* - -Finally we have the general case: Consider a static extension declaration -_D_ named `E` with formal type parameters `X1 extends B1 .. Xs extends Bs` -and a constructor return type `C`. With actual type arguments -`T1 .. Ts`, the instantiated constructor return type of _D_ with type -arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]C`. +*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 @@ -391,7 +436,7 @@ 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 the +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 @@ -415,10 +460,10 @@ 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 an 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.* +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 @@ -437,46 +482,65 @@ Otherwise, the invocation is partially resolved to a set of candidate constructors found in static extensions. Each of the candidates _kj_ is vetted as follows: -Assume that _kj_ is a constructor declared by a static extension _D_ named -`E` with type parameters `X1 extends B1 .. Xs extends Bs` and on-type -`C`. Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the -bounds `B1 .. Bs`, such that `([U1/X1 .. Us/Xs]C) == C`. -If this fails then remove _kj_ from the set of candidate constructors. -Otherwise note that _kj_ uses actual type arguments `U1 .. Us`. +If `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)`). +`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`. - -*In this case, type parameters declared by `E` may be inferred based on the -constructor signature (similarly to the example with `A.computed` above), -or they will be bound to values selected by instantiation to bound.* +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 fully -determines the actual type arguments of `C`, the expression is changed to -receive said actual type arguments, `C.name(args)`, and treated -as described above. +In the case where the 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 the invocation resolves to exactly one constructor -`C.name` (or `C`) declared by a static extension named `E`, the invocation -is treated as `E.C.name(args)` (respectively `E.C(args)`). +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 +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.* +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 @@ -495,7 +559,7 @@ This fully determines the dynamic semantics of this feature. ### Changelog -1.1 - August 28, 2024 +1.1 - August 30, 2024 * Clarify many parts. diff --git a/working/0723-static-extensions/feature-specification-variant2.md b/working/0723-static-extensions/feature-specification-variant2.md index 8b3729c33..7f62ca96e 100644 --- a/working/0723-static-extensions/feature-specification-variant2.md +++ b/working/0723-static-extensions/feature-specification-variant2.md @@ -131,7 +131,7 @@ 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. +extension declaration that has an on-declaration, possibly constant. *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 @@ -193,13 +193,12 @@ recommended message: A compile-time diagnostic is emitted if an 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_. A similar diagnostic is emitted -when _D_ is an enum, mixin, mixin class, or extension type declaration. +static member in the on-declaration of _D_. *In other words, an 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 instance -members as well.* +constructors, but a similar warning would probably be useful for name +clashes with instance members as well.* #### Invocation of a static member @@ -238,7 +237,7 @@ 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.* +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` @@ -250,32 +249,33 @@ include one with the name `m`, or which declares a constructor named `C.m`. expression is not a static member invocation. This case is described in a section below.* -Otherwise, an error occurs if _M_ is empty or _M_ contains more than one -member. +Otherwise *(when `C` does not declare such a constructor)*, an error occurs +if _M_ is empty or _M_ contains more than one member. -Otherwise, 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 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, _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. +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 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. +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. It will never invoke a static member of an extension -which is not the enclosing declaration. +enclosing declaration. -*In other words, there is nothing new in this case.* +*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.* #### The instantiated constructor return type of an extension @@ -285,7 +285,7 @@ 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-clause which denotes a +*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.* @@ -298,7 +298,7 @@ class A { A(this.i); } -static extension E on A { +extension E on A { A.computed(X x, int Function(X) fun): this(fun(x)); } @@ -322,18 +322,27 @@ 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 a +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 -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`. +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 @@ -360,6 +369,9 @@ 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.* @@ -411,7 +423,7 @@ 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` (or `C`), the invocation is treated as +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