dart-lang / language

Design of the Dart language
Other
2.65k stars 202 forks source link

Specification: Generic function type parameters have no scope #3754

Open eernstg opened 5 years ago

eernstg commented 5 years ago

Cf. https://github.com/dart-lang/sdk/issues/29555. The language specification does not specify a scope for a type variable which is introduced by a generic function type (e.g., void Function<X>(X), which introduces X with the formal type parameter declaration list <X>).

Instead, we rely on using 'capture-free substitution'. This means that the specification has no notion of the scope of such formal type parameters in a generic function type. We do get the right semantics, based on substitution, but the definition of the substitution mechanism itself could be made more explicit.

Alternatively, we could introduce language like 'a formal type parameter declaration X extends B enters X into the scope of the enclosing generic function type', etc. This would make the treatment of name binding more homogeneous, but it would probably also make the specification of everything that's concerned with generic function types substantially more verbose.

However, we do specify a scope for the formal type parameters declared by a generic function declaration (so in that sense it is similar to the treatment of classes), it's only function types whose scoping differs from the rest.

lrhn commented 5 years ago

We should be consistent. We generally let variable introductions create a new scope, nested in the surrounding scope, and make that the scope of everything covered by the variable declaration. That means that scopes should be properly nested. A type alias like:

typedef Foo<X> = X Function(Foo);

introduces a new scope for X, and it introduces Foo into the surrounding scope. The body of the declaration should be in the scope of both. It may be a compile-time error to refer to the name in a cyclic way.

We should not allow:

import "something.dart" show Foo;
typedef Foo<X> = X Function(Foo);

where the Foo in X Function(Foo) refers to the imported Foo. That is what would happen if the type-alias declared Foo "is not in scope" for the body, because the other Foo clearly is in scope. We need to shadow that scope with the top-level declaration scope of the library, then reject the cyclic declaration.

So: A type alias typedef A<X1 extends B1, ... , Xn extends Bn> = T; introduces A into the surrounding declaration scope, and introduces a new type variable scope where X1,...,Xn are declared, nested inside the surrounding scope. The type declaration scope is the scope for B1...Bn and T. It is a compile-time error if any of B1,...,Bn, or T refer to A.

lrhn commented 4 months ago

Just re-read this, and the original message was also about function types, not just type alias declarations.

A function type like R Function<X1 extends B1, ..., Xn extends Bn>(Params) introduces a type variable scope nested into the surrounding lexical scope, in which X1,...,Xn are bound to the type variable declarations X1 extends B1,...,Xn extends Bn, and this type variable scope is the lexical scope for B1,...,Bn, Params and R.

(The Function keyword is not a resolved identifier, so we don't need to say which scope applies to it.)