From b39c84a3c3530404a3621afe3189770263be67a4 Mon Sep 17 00:00:00 2001 From: Erik Ernst Date: Wed, 24 May 2023 21:22:51 +0200 Subject: [PATCH] Adjust rules to use shadowing semantics ("the class always wins"), and recommend a lint --- .../feature-specification.md | 211 ++++++++++-------- 1 file changed, 114 insertions(+), 97 deletions(-) diff --git a/working/0107 - implicit-constructors/feature-specification.md b/working/0107 - implicit-constructors/feature-specification.md index 753b52cd8c..a7a3bb369c 100644 --- a/working/0107 - implicit-constructors/feature-specification.md +++ b/working/0107 - implicit-constructors/feature-specification.md @@ -169,9 +169,18 @@ The grammar is modified as follows: ('final' | 'const') ? | 'late' 'final' ? | 'late'? ; + + ::= + 'show' | + 'hide' | + 'enable' ; + + ::= + (',' )*; ``` The identifier `implicit` is now mentioned in the grammar, but it is not a +built-in identifier nor a reserved word. Similarly, `enable` is not a built-in identifier nor a reserved word. *The parser does not need that.* In a static extension of the form `static extension E on C {...}` where `C` @@ -201,15 +210,15 @@ A compile-time error occurs if the on-class of a static extension does not resolve to an enum declaration or a declaration of a class, a mixin, a mixin class, or an inline class. -A compile-time error occurs if a static extension has an on-clause of the form -`on C` where `C` denotes a generic class and no type arguments are passed to `C` -*(i.e., it is a raw type)*, and the static extension contains one or more -constructor declarations. +A compile-time error occurs if a static extension has an on-clause of the +form `on C` where `C` denotes a generic class and no type arguments are +passed to `C` *(i.e., it is a raw type)*, and the static extension contains +one or more constructor declarations. *In other words, if the static extension ignores the type parameters of the on-class then it can only contain `static` members. Note that if the -on-class is non-generic then it is not a raw type, and the static extension -can contain constructors.* +on-class is non-generic then `C` is not a raw type, and the static +extension can contain constructors.* A compile-time error occurs if a static extension has an on-clause of the form `on C`, and the actual type parameters passed to `C` do not @@ -218,14 +227,9 @@ or more type parameters `X1 extends B1 .. Xs extends Bs`, and these type variables may occur in the types `T1 .. Tk`. During the bounds check, the bounds `B1 .. Bs` are assumed for the type variables `X1 .. Xs`. -A compile-time error occurs if a static extension _D_ declares a -constructor with the same name as a constructor in the on-class of _D_. -*In other words, a constructor in a static extension can never have a name -clash with a constructor declared by its on-class.* - A compile-time error occurs if a static extension declares an `implicit` constructor whose formal parameter list accepts a number of -positional paremeters which is different from one, or if it accepts any +positional parameters which is different from one, or if it accepts any named parameters. Consider a static extension declaration _D_ named `E` which is declared in @@ -254,6 +258,17 @@ void f() { } ``` +Tools may report diagnostic messages like warnings or lints in certain +situations. This is not part of the specification, but here is one +recommended message: + +A compile-time message is emitted if a static extension _D_ declares a +constructor or a static member with the same name as a constructor or a +static member in the on-class of _D_. + +*In other words, a static extension should not have name clashes with its +on-class.* + #### Static extension scopes Static extensions introduce several scopes: @@ -271,9 +286,7 @@ for the member declarations. The enclosing scope for the body scope is the type parameter scope, if any, and otherwise the library scope. Static members in a static extension are subject to the same static -analysis as static members in other declarations. There is one extra rule: -It is a compile-time error if a static member of a static extension has a -name which is also the name of a static member of the on-class. +analysis as static members in other declarations. A factory constructor in a static extension introduces scopes in the same way as other factory constructor declarations. The return type of the @@ -287,15 +300,20 @@ static members of classes, mixins, etc.* #### Static extension accessibility -A static extension _D_ is _accessible_ if _D_ is declared in the current -library, or if _D_ is imported and not hidden. +A static extension declaration _D_ is _accessible_ if _D_ is declared in +the current library, or if _D_ is imported and not hidden. + +An implicit constructor declaration named `C.name` (respectively `C`) in a +static extension declaration _D_ is _enabled_ if _D_ is declared in the +current library, or if _D_ is imported and the import directive that +imports _D_ includes `C.name` (`C`) in an `enable` combinator. #### Invocation of a static member of a static extension An _explicitly resolved invocation_ of a static member of a static -extension `E` *(with zero or more type parameters, which are ignored in -this context)*, is an expression of the form `E.m()` (or any other member -access), where `m` is a static member declared by `E`. +extension named `E` is an expression of the form `E.m()` (or any other +member access, *e.g., `E.m`, `E.m = e`, etc*), where `m` is a static member +declared by `E`. *This can be used to invoke a static member of a specific static extension in order to manually resolve a name clash.* @@ -303,43 +321,47 @@ in order to manually resolve a name clash.* A static member invocation on a class `C`, of the form `C.m()` (or any other member access), is resolved by looking up static members in `C` named `m` and looking up static members of every accessible static extension with -on-class `C` and a member named `m`. An error occurs if fewer than one or -more than one declaration named `m` was found. +on-class `C` and a member named `m`. + +If `C` contains such a declaration then the expression is an invocation of +that static member of `C`, with the same static analysis and dynamic +behavior as before the introduction of this feature. -If the invocation has been resolved to a static member declared by the -class `C` then the static analysis and dynamic semantics are the same as in -the pre-feature language. +Otherwise, an error occurs if fewer than one or more than one declaration +named `m` was found. -Otherwise, when the invocation has been resolved to a static member -declaration in a static extension `E`, the invocation is treated as -`E.m()`, which is an invocation of a specific static function, analyzed and -executed as usual. +Otherwise, the invocation is resolved to the given static member +declaration in a static extension named `E`, and the invocation is treated +as `E.m()` *(this is an explicitly resolved invocation, which is specified +above)*. #### The instantiated constructor return type of a static extension -We associate a static extension `E` with formal type parameters -`X1 extends B1 .. Xs extends Bs` and an actual type argument list -`T1 .. Ts` with a type known as the _instantiated constructor return type -of_ `E` _with type arguments_ `T1 .. Ts`. - -When a static extension `E` has an on-clause which is a non-generic class -`C`, the instantiated constructor return type is `C`, for any list of -actual type arguments. *It is not very useful to declare a type parameter -of a static extension which isn't used in the constructor return type, -because it can only be passed in an explicitly resolved constructor -invocation, e.g., `E.C(42)`. In all other invocations, the value of -such type variables is determined by instantiation to bound.* - -When a static extension has no formal type parameters, and it has an -on-type `C`, the instantiated constructor return type is -`C`. *In this case the on-type is a fixed type (also known as a -ground type), e.g., `List`. The point is that there are no type -variables in the type, and hence it is the same for every call site.* - -Consider a static extension `E` with formal type parameters -`X1 extends B1 .. Xs extends Bs` and a constructor return type +We associate a static extension declaration _D_ named `E` with formal type +parameters `X1 extends B1 .. Xs extends Bs` and an actual type argument +list `T1 .. Ts` with a type known as the _instantiated constructor return +type of_ _D_ _with type arguments_ `T1 .. Ts`. + +When a static extension declaration _D_ named `E` has an on-clause which is +a non-generic class `C`, the instantiated constructor return type is `C`, +for any list of actual type arguments. + +*It is not very useful to declare a type parameter of a static extension +which isn't used in the constructor return type, because it can only be +passed in an explicitly resolved constructor invocation, e.g., +`E.C(42)`. In all other invocations, the value of such type variables +is determined by instantiation to bound.* + +When a static extension declaration _D_ has no formal type parameters, and +it has an on-type `C`, the instantiated constructor return type +of _D_ is `C`. *In this case the on-type is a fixed type (also +known as a ground type), e.g., `List`. This implies that the +constructor return type of D is the same for every call site.* + +Consider a static extension declaration _D_ named `E` with formal type +parameters `X1 extends B1 .. Xs extends Bs` and a constructor return type `C`. With actual type arguments `T1 .. Ts`, the instantiated -constructor return type of `E` with type arguments `T1 .. Ts` is +constructor return type of _D_ with type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]C`. #### Explicit invocation of a constructor in a static extension @@ -349,10 +371,10 @@ but they need more detailed rules because they can use the formal type parameters declared by the static extension. An _explicitly resolved invocation_ of a constructor named `C.name` in a -static extension `E` with `s` type parameters and on-class `C` can be -expressed as `E.C.name(args)`, `E.C.name(args)`, or -`E.C.name(args)` (and similarly for a constructor -named `C` using `E.C(args)` etc). +static extension declaration _D_ named `E` with `s` type parameters and +on-class `C` can be expressed as `E.C.name(args)`, `E.C.name(args)`, or `E.C.name(args)` (and similarly +for a constructor named `C` using `E.C(args)` etc). A compile-time error occurs if the type arguments passed to `E` violate the declared bounds. A compile-time error occurs if no type arguments are @@ -362,12 +384,12 @@ that the instantiated constructor return type of `E` with said type arguments is `C`. A compile-time error occurs if the invocation passes actual type arguments -to both `E` and `C`, `S1 .. Ss` and `U1 .. Uk`, respectively, unless the -instantiated constructor return type of `E` with actual type arguments -`S1 .. Ss` is `C`. In this type comparison, top types like -`dynamic` and `Object?` are considered different, and no type normalization -occurs. *In other words, the types must be identical, not just mutual -subtypes.* +to both `E` and `C`, call them `S1 .. Ss` and `U1 .. Uk`, respectively, +unless the instantiated constructor return type of _D_ with actual type +arguments `S1 .. Ss` is `C`. In this type comparison, top types +like `dynamic` and `Object?` are considered different, and no type +normalization occurs. *In other words, the types must be identical, not +just mutual subtypes.* *Note that explicitly resolved invocations of constructors declared in static extensions are an exception in real code, usable in the case where a @@ -383,25 +405,19 @@ every accessible static extension with on-class `C`. A compile-time error occurs if no such constructor is found. Similarly, an invocation of the form `C(args)` uses a lookup for constructors named `C`. -*It is not possible to find a mixture consisting of one constructor in the -on-class and one or more constructors in static extensions: That's already -a compile-time error at the declarations in the static extension. Hence, -the invocation is resolved to a single constructor in `C`, or it is -partially resolved to a set of constructors in static extensions.* - -If the invocation resolves to a constructor in `C`, the pre-feature static -analysis and dynamic semantics apply. +If a constructor in `C` with the requested name was found, the pre-feature +static analysis and dynamic semantics apply. Otherwise, the invocation is partially resolved to a set of candidate constructors found in static extensions. Each of the candidates _kj_ is vetted as follows: -Assume that _kj_ is a constructor declared by a static extension `E` with -type parameters `X1 extends B1 .. Xs extends Bs` and on-type `C`. -Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds `B1 -.. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. If this -fails then remove _kj_ from the set of candidate constructors. Otherwise -note that _kj_ uses actual type arguments `U1 .. Us`. +Assume that _kj_ is a constructor declared by a static extension _D_ named +`E` with type parameters `X1 extends B1 .. Xs extends Bs` and on-type `C`. Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the +bounds `B1 .. Bs`, such that `[U1/X1 .. Us/Xs]C == C`. +If this fails then remove _kj_ from the set of candidate constructors. +Otherwise note that _kj_ uses actual type arguments `U1 .. Us`. If all candidate constructors have been removed, or more than one candidate remains, a compile-time error occurs. Otherwise, the invocation is @@ -423,8 +439,8 @@ receive said actual type arguments, `C.name(args)`, and treated as described above. In the case where the invocation resolves to exactly one constructor -`C.name` (or `C`) declared by a static extension `E`, the invocation is -treated as `E.C.name(args)` (respectively `E.C(args)`). +`C.name` (or `C`) declared by a static extension named `E`, the invocation +is treated as `E.C.name(args)` (respectively `E.C(args)`). Otherwise, when there are two or more candidates from static extensions, an error occurs. *We do not wish to specify an approach whereby `args` is @@ -441,16 +457,16 @@ that declares an `implicit` constructor _k_ whose formal parameter has type `U` *(which may contain some of `X1 .. Xs`)*. *Note that an `implicit` constructor must always have exactly one formal -parameter.* +parameter. It must be positional and it can be optional.* Let `T1 .. Ts` be types satisfying the bounds `B1 .. Bs`. We then say that the _instantiated parameter type_ of the implicit constructor _k_ with actual type arguments `T1 .. Ts` is `[T1/X1 .. Ts/Xs]U`. - Let `E1` be a static extension with `s` type parameters, let `T1 .. Ts` be types that satisfy the bounds of `E1`, and assume that _k1_ is an `implicit` constructor declared by `E1`. + Let `E2` be a static extension with `t` type parameters, let `S1 .. St` be types that satisfy the bounds of `E2`, and assume that _k2_ is an `implicit` constructor declared by `E2`. @@ -464,24 +480,24 @@ If the two instantiated parameter types are mutual subtypes then we say that the two constructors are equally specific. With a list of extensions `E1 .. En` and corresponding actual type -arguments and implicit constructors _k1 .. km_, we say that the most -specific one is _kj_ iff that constructor with the given type arguments is +arguments and implicit constructors _k1 .. km_, we say that the _most +specific_ one is _kj_ iff that constructor with the given type arguments is more specific than each of the others. #### Static extension applicability -Let `E` be a static extension with type parameters -`X1 extends B1 .. Xs extends Bs` and constructor return type +Let _D_ be a static extension declaration named `E` with type parameters +`X1 extends B1 .. Xs extends Bs` and constructor return type `C`. Let `P` be a context type schema. -Let `f` be a function declared as follows, also known as the +Let `f` be a function declared as follows, also known as the _applicability function_ of `E`: ```dart C f() => f(); ``` -We say that `E` is _applicable with context type schema_ `P` +We say that _D_ is _applicable with context type schema_ `P` _yielding actual type arguments_ `S1 .. Ss` iff type inference of the invocation `f()` with context type schema `P` yields a list of actual type arguments `S1 .. Ss` to `f` such that `[S1/X1 .. Ss/Xs]C` is @@ -494,14 +510,16 @@ invoked implicitly. *For example, we can have a declaration `Distance d = 1;`, and it may be transformed into `Distance d = Distance.fromInt(1);` where -`Distance.fromInt` is an implicit constructor declared in an accessible -static extension with on-class `Distance` whose parameter type is `int`.* +`Distance.fromInt` is an enabled implicit constructor declared in an +accessible static extension with on-class `Distance` whose parameter type +is `int`.* + +First, we need to introduce the notion of an assignment position. -First, we need to introduce the notion of an _assignment position_. -An expression can occur in an assignment position. This includes being +An expression can occur in an _assignment position_. This includes being the right hand side of an assignment, an initializing expression in a variable declaration, an actual argument in a function or method -invocation, and more. +invocation, the right operand of a binary operator, and more. *This concept is already used to determine whether a coercion like generic function instantiation is applicable. In this document we rely on this @@ -510,8 +528,8 @@ specified.* Assume that an expression `e` occurs in an assignment position with context type schema `P`. In this situation, type inference is performed on `e` with -context type schema `P`, and the resulting expression `e1` has some type -`T0`. Assume that `e1` is not subject to any built-in coercions *(at this +context type schema `P`, and the resulting expression `e0` has some type +`T0`. Assume that `e0` is not subject to any built-in coercions *(at this time this means generic function instantiation or call method tear-off)*, and `T0` is not assignable to the greatest closure of `P`. In this case we say that `e` is _potentially subject to implicit construction with source @@ -526,7 +544,7 @@ source type `T0`, the following steps are performed: each with an actual type argument list `A1 .. An` *(each `Aj` is a list of types, whose length is the same as the type parameter list of `Ej`)*. The candidate constructors are then all constructors in `E1 .. En` which - are marked `implicit`. + are marked `implicit`, and which are enabled. - For each candidate constructor _k_, eliminate _k_ from the set of candidates if `T0` is not assignable to the instantiated parameter type of _k_ with the actual type arguments `Aj` of the static extension `Ej` @@ -537,8 +555,8 @@ source type `T0`, the following steps are performed: the given actual type arguments of the enclosing static extension, an error occurs. - Otherwise, one specific static extension `Ej` with actual type arguments - `Aj`, and one constructor _k_ declared by `Ej` is most specific. Let - `C.name` (respectively `C`) be the name of _k_. + `Aj`, and one enabled constructor _k_ declared by `Ej` is most + specific. Let `C.name` (respectively `C`) be the name of _k_. - The expression `e` is then replaced by `Ej.C.name(e0)` (respectively `Ej.C(e0)`). @@ -593,8 +611,7 @@ some rather strict rules: Scala even requires that the entity that provides implicit conversions is explicitly imported (using something similar to `show` in an import). We -could consider doing that, but the current proposal is a little more -permissive. +require that each constructor must be `enabled` in the impert. ### Changelog