Skip to content

Commit

Permalink
[ts 4.7] Support optional variance annotations (#14359)
Browse files Browse the repository at this point in the history
  • Loading branch information
magic-akari committed May 17, 2022
1 parent 59d24a4 commit 6415f09
Show file tree
Hide file tree
Showing 17 changed files with 12,906 additions and 11 deletions.
10 changes: 10 additions & 0 deletions packages/babel-generator/src/generators/typescript.ts
Expand Up @@ -25,6 +25,16 @@ export function TSTypeParameterInstantiation(
export { TSTypeParameterInstantiation as TSTypeParameterDeclaration };

export function TSTypeParameter(this: Printer, node: t.TSTypeParameter) {
if (node.in) {
this.word("in");
this.space();
}

if (node.out) {
this.word("out");
this.space();
}

this.word(
!process.env.BABEL_8_BREAKING
? (node.name as unknown as string)
Expand Down
@@ -0,0 +1,94 @@
// valid JSX
<in T>() => {}</in>;

type Covariant<out T> = {
x: T;
}

declare let super_covariant: Covariant<unknown>;
declare let sub_covariant: Covariant<string>;

super_covariant = sub_covariant;
sub_covariant = super_covariant; // Error

type Contravariant<in T> = {
f: (x: T) => void;
}

declare let super_contravariant: Contravariant<unknown>;
declare let sub_contravariant: Contravariant<string>;

super_contravariant = sub_contravariant; // Error
sub_contravariant = super_contravariant;

type Invariant<in out T> = {
f: (x: T) => T;
}

declare let super_invariant: Invariant<unknown>;
declare let sub_invariant: Invariant<string>;

super_invariant = sub_invariant; // Error
sub_invariant = super_invariant; // Error

// Variance of various type constructors

type T10<out T> = T;
type T11<in T> = keyof T;
type T12<out T, out K extends keyof T> = T[K];
type T13<in out T> = T[keyof T];

// Variance annotation errors

type Covariant1<in T> = { // Error
x: T;
}

type Contravariant1<out T> = keyof T; // Error

type Contravariant2<out T> = { // Error
f: (x: T) => void;
}

type Invariant1<in T> = { // Error
f: (x: T) => T;
}

type Invariant2<out T> = { // Error
f: (x: T) => T;
}

// Variance in circular types

type Foo1<in T> = { // Error
x: T;
f: FooFn1<T>;
}

type FooFn1<T> = (foo: Bar1<T[]>) => void;

type Bar1<T> = {
value: Foo1<T[]>;
}

type Foo2<out T> = { // Error
x: T;
f: FooFn2<T>;
}

type FooFn2<T> = (foo: Bar2<T[]>) => void;

type Bar2<T> = {
value: Foo2<T[]>;
}

type Foo3<in out T> = {
x: T;
f: FooFn3<T>;
}

type FooFn3<T> = (foo: Bar3<T[]>) => void;

type Bar3<T> = {
value: Foo3<T[]>;
}
@@ -0,0 +1,4 @@
{
"BABEL_8_BREAKING": false,
"plugins": ["jsx", "typescript"]
}
@@ -0,0 +1,78 @@
// valid JSX
<in T>() => {}</in>;
type Covariant<out T> = {
x: T;
};
declare let super_covariant: Covariant<unknown>;
declare let sub_covariant: Covariant<string>;
super_covariant = sub_covariant;
sub_covariant = super_covariant; // Error

type Contravariant<in T> = {
f: (x: T) => void;
};
declare let super_contravariant: Contravariant<unknown>;
declare let sub_contravariant: Contravariant<string>;
super_contravariant = sub_contravariant; // Error

sub_contravariant = super_contravariant;
type Invariant<in out T> = {
f: (x: T) => T;
};
declare let super_invariant: Invariant<unknown>;
declare let sub_invariant: Invariant<string>;
super_invariant = sub_invariant; // Error

sub_invariant = super_invariant; // Error
// Variance of various type constructors

type T10<out T> = T;
type T11<in T> = keyof T;
type T12<out T, out K extends keyof T> = T[K];
type T13<in out T> = T[keyof T]; // Variance annotation errors

type Covariant1<in T> = {
// Error
x: T;
};
type Contravariant1<out T> = keyof T; // Error

type Contravariant2<out T> = {
// Error
f: (x: T) => void;
};
type Invariant1<in T> = {
// Error
f: (x: T) => T;
};
type Invariant2<out T> = {
// Error
f: (x: T) => T;
}; // Variance in circular types

type Foo1<in T> = {
// Error
x: T;
f: FooFn1<T>;
};
type FooFn1<T> = (foo: Bar1<T[]>) => void;
type Bar1<T> = {
value: Foo1<T[]>;
};
type Foo2<out T> = {
// Error
x: T;
f: FooFn2<T>;
};
type FooFn2<T> = (foo: Bar2<T[]>) => void;
type Bar2<T> = {
value: Foo2<T[]>;
};
type Foo3<in out T> = {
x: T;
f: FooFn3<T>;
};
type FooFn3<T> = (foo: Bar3<T[]>) => void;
type Bar3<T> = {
value: Foo3<T[]>;
};
91 changes: 80 additions & 11 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -49,7 +49,8 @@ type TsModifier =
| "declare"
| "static"
| "override"
| N.Accessibility;
| N.Accessibility
| N.VarianceAnnotations;

function nonNull<T>(x: ?T): T {
if (x == null) {
Expand Down Expand Up @@ -161,6 +162,14 @@ const TSErrors = ParseErrorEnum`typescript`(_ => ({
InvalidModifierOnTypeMember: _<{| modifier: TsModifier |}>(
({ modifier }) => `'${modifier}' modifier cannot appear on a type member.`,
),
InvalidModifierOnTypeParameter: _<{| modifier: TsModifier |}>(
({ modifier }) =>
`'${modifier}' modifier cannot appear on a type parameter.`,
),
InvalidModifierOnTypeParameterPositions: _<{| modifier: TsModifier |}>(
({ modifier }) =>
`'${modifier}' modifier can only appear on a type parameter of a class, interface or type alias.`,
),
InvalidModifiersOrder: _<{| orderedModifiers: [TsModifier, TsModifier] |}>(
({ orderedModifiers }) =>
`'${orderedModifiers[0]}' modifier must precede '${orderedModifiers[1]}' modifier.`,
Expand Down Expand Up @@ -294,6 +303,10 @@ function tsIsAccessModifier(modifier: string): boolean %checks {
);
}

function tsIsVarianceAnnotations(modifier: string): boolean %checks {
return modifier === "in" || modifier === "out";
}

export default (superClass: Class<Parser>): Class<Parser> =>
class extends superClass {
getScopeHandler(): Class<TypeScriptScopeHandler> {
Expand Down Expand Up @@ -332,7 +345,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
allowedModifiers: T[],
stopOnStartOfClassStaticBlock?: boolean,
): ?T {
if (!tokenIsIdentifier(this.state.type)) {
if (!tokenIsIdentifier(this.state.type) && this.state.type !== tt._in) {
return undefined;
}

Expand All @@ -358,6 +371,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
allowedModifiers,
disallowedModifiers,
stopOnStartOfClassStaticBlock,
errorTemplate = TSErrors.InvalidModifierOnTypeMember,
}: {
modified: {
[key: TsModifier]: ?true,
Expand All @@ -366,6 +380,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
allowedModifiers: TsModifier[],
disallowedModifiers?: TsModifier[],
stopOnStartOfClassStaticBlock?: boolean,
// FIXME: make sure errorTemplate can receive `modifier`
errorTemplate?: any,
}): void {
const enforceOrder = (loc, modifier, before, after) => {
if (modifier === before && modified[after]) {
Expand Down Expand Up @@ -409,6 +425,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>

modified.accessibility = modifier;
}
} else if (tsIsVarianceAnnotations(modifier)) {
if (modified[modifier]) {
this.raise(TSErrors.DuplicateModifier, { at: startLoc, modifier });
}
modified[modifier] = true;

enforceOrder(startLoc, modifier, "in", "out");
} else {
if (Object.hasOwnProperty.call(modified, modifier)) {
this.raise(TSErrors.DuplicateModifier, { at: startLoc, modifier });
Expand All @@ -425,7 +448,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}

if (disallowedModifiers?.includes(modifier)) {
this.raise(TSErrors.InvalidModifierOnTypeMember, {
this.raise(errorTemplate, {
at: startLoc,
modifier,
});
Expand Down Expand Up @@ -625,21 +648,57 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return this.finishNode(node, "TSTypeQuery");
}

tsParseTypeParameter(): N.TsTypeParameter {
tsParseInOutModifiers(node: N.TsTypeParameter) {
this.tsParseModifiers({
modified: node,
allowedModifiers: ["in", "out"],
disallowedModifiers: [
"public",
"private",
"protected",
"readonly",
"declare",
"abstract",
"override",
],
errorTemplate: TSErrors.InvalidModifierOnTypeParameter,
});
}

// for better error recover
tsParseNoneModifiers(node: N.TsTypeParameter) {
this.tsParseModifiers({
modified: node,
allowedModifiers: [],
disallowedModifiers: ["in", "out"],
errorTemplate: TSErrors.InvalidModifierOnTypeParameterPositions,
});
}

tsParseTypeParameter(
parseModifiers: (
node: N.TsTypeParameter,
) => void = this.tsParseNoneModifiers.bind(this),
): N.TsTypeParameter {
const node: N.TsTypeParameter = this.startNode();

parseModifiers(node);

node.name = this.tsParseTypeParameterName();
node.constraint = this.tsEatThenParseType(tt._extends);
node.default = this.tsEatThenParseType(tt.eq);
return this.finishNode(node, "TSTypeParameter");
}

tsTryParseTypeParameters(): ?N.TsTypeParameterDeclaration {
tsTryParseTypeParameters(
parseModifiers: ?(node: N.TsTypeParameter) => void,
): ?N.TsTypeParameterDeclaration {
if (this.match(tt.lt)) {
return this.tsParseTypeParameters();
return this.tsParseTypeParameters(parseModifiers);
}
}

tsParseTypeParameters() {
tsParseTypeParameters(parseModifiers: ?(node: N.TsTypeParameter) => void) {
const node: N.TsTypeParameterDeclaration = this.startNode();

if (this.match(tt.lt) || this.match(tt.jsxTagStart)) {
Expand All @@ -652,7 +711,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>

node.params = this.tsParseBracketedList(
"TypeParametersOrArguments",
this.tsParseTypeParameter.bind(this),
this.tsParseTypeParameter.bind(this, parseModifiers),
/* bracket */ false,
/* skipFirstToken */ true,
refTrailingCommaPos,
Expand Down Expand Up @@ -1642,7 +1701,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.raise(TSErrors.MissingInterfaceName, { at: this.state.startLoc });
}

node.typeParameters = this.tsTryParseTypeParameters();
node.typeParameters = this.tsTryParseTypeParameters(
this.tsParseInOutModifiers.bind(this),
);
if (this.eat(tt._extends)) {
node.extends = this.tsParseHeritageClause("extends");
}
Expand All @@ -1657,8 +1718,12 @@ export default (superClass: Class<Parser>): Class<Parser> =>
): N.TsTypeAliasDeclaration {
node.id = this.parseIdentifier();
this.checkIdentifier(node.id, BIND_TS_TYPE);

node.typeAnnotation = this.tsInType(() => {
node.typeParameters = this.tsTryParseTypeParameters();
node.typeParameters = this.tsTryParseTypeParameters(
this.tsParseInOutModifiers.bind(this),
);

this.expect(tt.eq);

if (
Expand Down Expand Up @@ -2723,7 +2788,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
this.tsParseModifiers({
modified: member,
allowedModifiers: modifiers,
disallowedModifiers: ["in", "out"],
stopOnStartOfClassStaticBlock: true,
errorTemplate: TSErrors.InvalidModifierOnTypeParameterPositions,
});

const callParseClassMemberWithIsStatic = () => {
Expand Down Expand Up @@ -2959,7 +3026,9 @@ export default (superClass: Class<Parser>): Class<Parser> =>
optionalId,
(node: any).declare ? BIND_TS_AMBIENT : BIND_CLASS,
);
const typeParameters = this.tsTryParseTypeParameters();
const typeParameters = this.tsTryParseTypeParameters(
this.tsParseInOutModifiers.bind(this),
);
if (typeParameters) node.typeParameters = typeParameters;
}

Expand Down

0 comments on commit 6415f09

Please sign in to comment.