Skip to content

Commit

Permalink
Adjust rules to use shadowing semantics ("the class always wins"), an…
Browse files Browse the repository at this point in the history
…d recommend a lint
  • Loading branch information
eernstg committed May 24, 2023
1 parent 60c5bc7 commit b39c84a
Showing 1 changed file with 114 additions and 97 deletions.
211 changes: 114 additions & 97 deletions working/0107 - implicit-constructors/feature-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,18 @@ The grammar is modified as follows:
('final' | 'const') <type>? <staticFinalDeclarationList> |
'late' 'final' <type>? <initializedIdentifierList> |
'late'? <varOrType> <initializedIdentifierList>;
<combinator> ::=
'show' <identifierList> |
'hide' <identifierList> |
'enable' <constructorNameList>;
<constructorNameList> ::=
<constructorName> (',' <constructorName>)*;
```

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`
Expand Down Expand Up @@ -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<T1 .. Tk>`, and the actual type parameters passed to `C` do not
Expand All @@ -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
Expand Down Expand Up @@ -254,6 +258,17 @@ void f<E>() {
}
```

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:
Expand All @@ -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
Expand All @@ -287,59 +300,68 @@ 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.*

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<int>.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<S1 .. Sk>`, the instantiated constructor return type is
`C<S1 .. Sk>`. *In this case the on-type is a fixed type (also known as a
ground type), e.g., `List<int>`. 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<int>.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<S1 .. Sk>`, the instantiated constructor return type
of _D_ is `C<S1 .. Sk>`. *In this case the on-type is a fixed type (also
known as a ground type), e.g., `List<int>`. 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<S1 .. Sk>`. 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<S1 .. Sk>`.

#### Explicit invocation of a constructor in a static extension
Expand All @@ -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<S1 .. Ss>.C.name(args)`, `E.C<U1 .. Uk>.name(args)`, or
`E<S1 .. Ss>.C<U1 .. Uk>.name(args)` (and similarly for a constructor
named `C` using `E<S1 .. Ss>.C(args)` etc).
static extension declaration _D_ named `E` with `s` type parameters and
on-class `C` can be expressed as `E<S1 .. Ss>.C.name(args)`, `E.C<U1
.. Uk>.name(args)`, or `E<S1 .. Ss>.C<U1 .. Uk>.name(args)` (and similarly
for a constructor named `C` using `E<S1 .. Ss>.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
Expand All @@ -362,12 +384,12 @@ that the instantiated constructor return type of `E` with said type
arguments is `C<U1 .. Uk>`.

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<U1 .. Uk>`. 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<U1 .. Uk>`. 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
Expand All @@ -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<T1 ... Tm>(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<S1 .. Sm>`.
Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the bounds `B1
.. Bs`, such that `[U1/X1 .. Us/Xs]C<S1 .. Sm> == C<T1 .. Tm>`. 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<S1
.. Sm>`. Find actual values `U1 .. Us` for `X1 .. Xs` satisfying the
bounds `B1 .. Bs`, such that `[U1/X1 .. Us/Xs]C<S1 .. Sm> == C<T1 .. Tm>`.
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
Expand All @@ -423,8 +439,8 @@ receive said actual type arguments, `C<T1 .. Tm>.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
Expand All @@ -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`.
Expand All @@ -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<T1 .. Tk>`. 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<T1 .. Tk> f<X1 extends B1 .. Xs extends Bs>() => 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<T1 .. Tk>` is
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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`
Expand All @@ -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<Aj>.C.name(e0)`
(respectively `Ej<Aj>.C(e0)`).

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit b39c84a

Please sign in to comment.