Open chharvey opened 3 years ago
Update: add widens
to parameter constraints.
Use the widens
keyword to constrain a type parameter in the opposite direction: to declare it as a supertype.
type Nullish<T widens int> = T | null;
let x: Nullish.<42 | 43> = 42; %> TypeError: Type `int` is not a subtype of type `42 | 43`.
widens
is only recommended when narrows
would require accessing latter parameters.
type T<A narrows B, B> = A;
% ^ ReferenceError: `B` is used before it is declared.
To fix this error, we could switch the parameters:
type T<B, A narrows B> = A;
% ^ ok
However, if switching is not possible, we can use the widens
keyword:
type T<A, B widens A> = A;
% ^ ok
Lexicon
Keyword :::=
// modifier
| "narrows"
+ | "widens"
;
Syntax
ParameterGeneric<Optional>
- ::= IDENTIFIER ( "narrows" Type)? <Optional+>("?=" Type);
+ ::= IDENTIFIER (("narrows" | "widens") Type)? <Optional+>("?=" Type);
Semantics
-SemanticHeritage ::= SemanticType;
+SemanticHeritage[dir: NARROWS | WIDENS] ::= SemanticType;
SemanticTypeDefualt ::= SemanticType;
Decorate
Decorate(ParameterGeneric<-Optional> ::= IDENTIFIER "narrows" Type) -> SemanticTypeParam
:= (SemanticTypeParam
(SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
- (SemanticHeritage Decorate(Type))
+ (SemanticHeritage[dir=NARROWS] Decorate(Type))
);
+Decorate(ParameterGeneric<-Optional> ::= IDENTIFIER "widens" Type) -> SemanticTypeParam
+ := (SemanticTypeParam
+ (SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
+ (SemanticHeritage[dir=WIDENS] Decorate(Type))
+ );
Decorate(ParameterGeneric<+Optional> ::= IDENTIFIER "narrows" Type__0 "?=" Type__1) -> SemanticTypeParam
:= (SemanticTypeParam
(SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
- (SemanticHeritage Decorate(Type__0))
+ (SemanticHeritage[dir=NARROWS] Decorate(Type__0))
(SemanticTypeDefault Decorate(Type__1))
);
+Decorate(ParameterGeneric<+Optional> ::= IDENTIFIER "widens" Type__0 "?=" Type__1) -> SemanticTypeParam
+ := (SemanticTypeParam
+ (SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
+ (SemanticHeritage[dir=WIDENS] Decorate(Type__0))
+ (SemanticTypeDefault Decorate(Type__1))
+ );
The constraints of generic alias “parameters” is now expanded to type aliases themselves.
Optionally, a type alias may be declared with a narrows
or widens
constraint to further stricten the declaration.
type JustInt narrows Maybe.<int> = int;
This constraint clause is optional, but helps catch errors early on. The following change is valid, but could be prone to more bugs:
-type JustInt = int;
+type JustInt = float;
With the constraint in place, the typer throws a compile-time error in one place — at the type alias’s declaration site — rather than at all its reference sites.
type JustInt narrows Maybe.<int> = float; %> TypeError: `float` is not a subtype of `Maybe.<int>`
Note that type alias constraints do not (and cannot) apply to generic type aliases.
type MyType<T> narrows T | null = SomeComplicatedGeneric.<T> | T; %> SyntaxError
The reason is that there’s just no way, in the general case and at the declaration site, for the compiler to evaluate the assigned type expression before it’s specified with a type argument.
-DeclarationType ::= "type" IDENTIFIER ( "<" ","? ParametersGeneric ">")? "=" Type ";";
+DeclarationType ::= "type" IDENTIFIER (("narrows" | "widens") Type | "<" ","? ParametersGeneric ">")? "=" Type ";";
SemanticDeclarationTypeAlias
- ::= SemanticTypeAlias SemanticType;
+ ::= SemanticTypeAlias SemanticHeritage? SemanticType;
Decorate(DeclarationType ::= "type" IDENTIFIER "=" Type ";") -> SemanticDeclarationTypeAlias
:= (SemanticDeclarationTypeAlias
(SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
Decorate(Type)
);
+Decorate(DeclarationType ::= "type" IDENTIFIER "narrows" Type__0 "=" Type__1 ";") -> SemanticDeclarationTypeAlias
+ := (SemanticDeclarationTypeAlias
+ (SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
+ (SemanticHeritage[dir=NARROWS] Decorate(Type__0))
+ Decorate(Type__1)
+ );
+Decorate(DeclarationType ::= "type" IDENTIFIER "widens" Type__0 "=" Type__1 ";") -> SemanticDeclarationTypeAlias
+ := (SemanticDeclarationTypeAlias
+ (SemanticTypeAlias[id=TokenWorth(IDENTIFIER)])
+ (SemanticHeritage[dir=WIDENS] Decorate(Type__0))
+ Decorate(Type__1)
+ );
Generic Types formalize the concept of variable types — types that are variable and may change.
Discussion
A typical example: A generic type
Nullable<T>
that takes a single type argumentT
and produces a union ofT
andnull
.T
is not a type, but a type variable that acts like a parameter. The act of providing an actual value forT
is called specifying the generic typeNullable
. It can also be thought of as “calling” or “instantiating” it.Notice the difference in syntax between generic type declaration and generic type specification:
Nullable<T>
versusNullable.<int>
.Generic type parameters can be reused across type declarations.
Providing the incorrect number of arguments resolves in a TypeError.
Generic type aliases can be thought of as “functions” that have “parameters” and are called with “arguments”, however this is not exactly accurate, since type functions are a related but separate feature (#73). Type generics are resolved eagerly, and like non-generics, they cannot be recursive.
Optional Parameters
Generic type aliases can be defined with optional generic parameters, which must have a default value. When the generic is specified, the argument may be omitted, in which case the default value is assumed. All optional parameters must come after all required parameters.
Constrained Parmeters
Use the
narrows
keyword to constrain a generic parameter. When a parameterT narrows U
is declared, the argument sent in forT
must be a subtype ofU
, otherwise it’s a type error.Specification
Lexicon
Syntax
Semantics
Decorate