Open leafpetersen opened 3 months ago
No. I'd only allow adding extension static members to existing static namespaces.
If we say "any type can have static members", we'll have to explain why List<int>
and List<String>
do not have different static namespaces, but List<int> Function()
and List<String> Function ()
do. (Or say how to collapse those function type namespaces into one.)
The static namespaces are introduced by class
, mixin
, enum
, extension type
and extension
declarations. There are no other static namespaces than those.
Some type aliases have a shape which we say denotes a static namespace, and we let them also alias that existing static namespace. (Which cannot be an extension namespace, since those do not introduce a type.)
With this feature, some extensions will also act as extensions of the static namespaces of their on
type, if the on
type clause has a shape that denotes a static namespace.
When we would otherwise do a static namespace lookup on that namespace, and we find no member with the needed name, we check if any extension extends that static namespace with something of the same base name, and if a single one is applicable, the static reference denotes that static member definition.
(And if we did, we would not attach anything to type alias names.)
To be a little more formal:
An identifier or qualified identifier T
denotes a static namespace, N, if and only if:
T
denotes a class
, mixin
, enum
or extension type
declaration, then N is the static namespace of that declaration.T
denotes an extension
declaration, then N is the static namespace of that declaration.T
denotes a type alias declaration, and the uninstantiated aliased type clause of that declaration denotes a static namespace N.A type clause T
, which must denote a type, also denotes a static namespace, N, if and only if:
T
is an identifier or qualified identifier, and T
denotes the static namespace N, orT
has the form R<...>
and R
denotes the static namespace N. (Which, grammatically, means that R
must be an identifier or qualified identifier.)A static access on T
, where T
is an identifier or qualified identifier which denotes a static namespace, has one of the forms:
T.id
, T.id = ...
, T.id<...>
, T.id(...)
or T.id<...>(...)
.T<...>.id
, T.new
, T<...>.new
, T(...)
, T<...>(...)
, T<...>.id(...)
, T.new(...)
, or T<...>..new(...)
.In each case, the static namespace is searched for members with base name id
, or for an unnamed constructor for the new
or no-name (constructor-only) cases.
If one is found, that is the member denoted by the T.id
/T.new
/T
(as constructor).
T.id = ...
, it's an error if a setter is not found. (Might find a getter too, the only valid cases where there can be two static namespace members with the same base name.)With static extensions, let's go with the variant without static
, a declaration of:
extension NumList<T extends num> on List<T> {
factory List.sorted(Iterable<T> values) => [...values]..sort();
}
will be a static extension on the static namespace denoted by List<T>
, if any. (In this case, the static namespace introduced by the class List
declaration).
When doing type inference for a static member access like var x = List.sorted([3, 2, 1]);
:
List.sorted([3, 2, 1])
with context type scheme _
.List
denotes a type declaration or extension declaration, so this is a static access.List
is the static namespace of the List
class declaration(s), so the static namespace definition introduced by a class declaration and possibly a number of augmenting class declarations.sorted
. None is found, so we fall back to checking static extensions.on
type clause does not denote the same static namespace, then the extension does not apply.sorted
, it does not apply.NumList
.List.sorted
denotes the raw extension constructor reference NumList.sorted
(because List
is a generic type, and List.sorted
is a raw/uninstantiated reference, we do not try to infer type parameters for NumList
from that).List<T> Function(List<T>)
with T
still unbound, context type scheme _
and argument list ([3, 2, 1])
. Since the scheme is empty, we don't infer T
during downwards inference.List<T>
infers (<int>[3, 2, 1]) and
int <: T`NumList<int>.sorted(<int>[3, 2, 1])
, which has return type List<int>
, so that is the static type of the invocation. (And int
\<: num
, so the invocation is well-bounded.)
The current proposal (#3835) for static extensions only allows static members and constructors to be accessed only on "class-like" types (that is classes, mixin classes, mixins, enums, and extension types). In principle, we could relax this restriction, either in the case that the
on
type is a typedef (see #4052) or in the case that the receiver is a typedef name. For example, we could allow:If so, do we attach the extension to the name
Pair
or to the underlying type? That is, does the following work?cc @dart-lang/language-team