Open lrhn opened 1 year ago
Let's try to see which rules we already have in the language specification. This one is clearly relevant to syntax like A<C>.id
, because A<C>
is a type literal in current Dart:
Let
id
be an identifier; a static property extraction i is an expression of the formC.id
, whereC
is a type literal orC
denotes an extension. A compile-time error occurs unlessC
denotes a class, a mixin, or an extension that declares a static member namedm
...
(There's a typo here: it should say "named id
"; #2605 corrects that.)
We should adjust this rule to cover the case where C.id
or C<T1..Ts>.id
is a constructor tear-off (that's OK, too).
We should also adjust this such that it explicitly covers the case where the type literal is a parameterized type (that is, it has the form C<T1 .. Ts>
for some C
and T1 .. Ts
): This is an error for a member marked static
, but OK (subject to further checks, of course) for a constructor tear-off.
Finally, we should generalize the rule that makes it an error to use a type alias that denotes a type variable as a class in specific situations, cf. this location in the specification. This rule should be extended to say that a constructor tear-off is such a location: Assume that F
is a type alias that denotes a type variable. Then F.new
, F<T1 .. Ts>.new
use F
as a class when the denoted class C
declares a constructor named C
. Moreover, F.name
and F<T1..Ts>.name
use the type alias F
as a class when the denoted class C
declares a constructor named C.name
.
(Of course, when the denoted class does not declare a constructor with the given name, nor a member with the modifier static
, it is an error as before. We still do not look into Type
instance members or extension instance members in order to allow this property extraction, we insist that the type literal is used to perform a "static lookup".)
This would make A<C>.stat
an error: A
is a type alias that denotes a type variable, and hence it is an error to use A<C>
as a class, and C.stat
is indeed a member of C
with the modifier static
.
A<C>.named
is an error for nearly the same reason (based on a constructor tear-off rather than a member marked static
). And A<C>.new
would be an error for the same reason.
Finally, A<C>.inst
is an error because A<C>
is a type literal, and this implies that we never evaluate the type literal to an object of type Type
and then invoke an instance member of Type
on it.
I think this clarifies the situation: We need to update the specification, but we already know that, and the updates could be as outlined above.
@dart-lang/language-team, do you think we need to have language repo discussion issues about this, or can we proceed to update the language specification as hinted above?
If we agree on (essentially) the rules mentioned above then we should adjust the tools to report an error in all cases as mentioned above.
@scheglov
Dart allows accessing static members and constructors of classes (class/mixin/enum declarations) through type aliases, but only if the type alias doesn "expand to a type variable" (and then only if it explicitly denotes a class.)
A type alias expands to a type variable if its RHS is a type variable, or it's
Q<TypeArgsOpt>
whereQ
is a (possibly qualified) identifier denoting a type alias which expands to a type variable. (We don't care what the type arguments are, we just follow the name at the head of the type until a non-type-alias is found.)So a type alias explicitly denotes a non-alias declaration
C
if its RHS is= Q<TypeArgsOpt>;
whereQ
is an identifier or qualified identifier directly denotingC
, or transitively ifQ
denotes another type alias declaration which then explicitly denotesC
.Further, accessing statics on instantiated type expressions (
Q<TypeArgs>
) is always an error. IfQ<TypeArgs>
is a type expression, then the only continuations are constructor tear-offs or invocations,(args)
,.new(args)
,.name(args)
,.new
, or.name
. Any other follow-up selector is a compile-time error.Which means you can't do
typedef A<T> = T; A<int>.parse("1");
for two reasons:parse
is not a constructor, so it can't be invoked on an instantiated type expression at all, andA
doesn't denote a class.Example:
The front end gives the following errors (from dartpad.dev).:
which is one error per place the term
A<C>
is used as receiver for a member access, and one whereC2<int>
is used. (Plus two spurious errors where it seems to try doing the access on aType
object instead, which shouldn't happen.)The analyzer gives only one error:
(and one info, saying that the extension method
inst
isn't used, which is doubly weird).Something is going wrong with errors about accessing members through type aliases which expands to a type variable. The analyzer correctly recognizes that
C2<int>.stat
is not allowed, but not thatA<C>.stat
is not allowed for the same reason, and for another reason too.