Skip to content

Commit

Permalink
Fix parsing of << within a type (#769)
Browse files Browse the repository at this point in the history
Fixes #758

There was already some logic from #716 to handle cases like `f<<T>() => void>`, where we're transitioning from a non-type context to a type context and need to detect if `<<` is actually two open-type-parameter/argument tokens. There was a simpler missing case, though, which is that `<<` is never allowed within a type, so we should tokenize as a simple `<` in a
type context. To handle the various cases correctly, this extra logic needs to only happen for `<<`, and this PR also adds
some additional comments explaining the nuances.
  • Loading branch information
alangpierce authored Nov 28, 2022
1 parent 6ff7348 commit 473a09e
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 10 deletions.
4 changes: 1 addition & 3 deletions src/parser/plugins/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,9 +1138,7 @@ function tsTryParseGenericAsyncArrowFunction(): boolean {
* where bitshift would be illegal anyway (e.g. in a class "extends" clause).
*
* This hack is useful to handle situations like foo<<T>() => void>() where
* there can legitimately be two open-angle-brackets in a row in TS. This
* situation is very obscure and (as of this writing) is handled by Babel but
* not TypeScript itself, so it may be fine in the future to remove this case.
* there can legitimately be two open-angle-brackets in a row in TS.
*/
function tsParseTypeArgumentsWithPossibleBitshift(): void {
if (state.type === tt.bitShiftL) {
Expand Down
33 changes: 27 additions & 6 deletions src/parser/tokenizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,11 +512,25 @@ function readToken_lt(): void {
finishOp(tt.assign, 3);
return;
}
// This still might be two instances of <, e.g. the TS type argument
// expression f<<T>() => void>() , but parse as left shift for now and we'll
// retokenize if necessary. We can't use isType for this case because we
// don't know yet if we're in a type.
finishOp(tt.bitShiftL, 2);
// We see <<, but need to be really careful about whether to treat it as a
// true left-shift or as two < tokens.
if (state.isType) {
// Within a type, << might come up in a snippet like `Array<<T>() => void>`,
// so treat it as two < tokens. Importantly, this should only override <<
// rather than other tokens like <= . If we treated <= as < in a type
// context, then the snippet `a as T <= 1` would incorrectly start parsing
// a type argument on T. We don't need to worry about `a as T << 1`
// because TypeScript disallows that syntax.
finishOp(tt.lessThan, 1);
} else {
// Outside a type, this might be a true left-shift operator, or it might
// still be two open-type-arg tokens, such as in `f<<T>() => void>()`. We
// look at the token while considering the `f`, so we don't yet know that
// we're in a type context. In this case, we initially tokenize as a
// left-shift and correct after-the-fact as necessary in
// tsParseTypeArgumentsWithPossibleBitshift .
finishOp(tt.bitShiftL, 2);
}
return;
}

Expand Down Expand Up @@ -558,7 +572,14 @@ function readToken_gt(): void {

/**
* Called after `as` expressions in TS; we're switching from a type to a
* non-type context, so a > token may actually be >= .
* non-type context, so a > token may actually be >= . This is needed because >=
* must be tokenized as a > in a type context because of code like
* `const x: Array<T>=[];`, but `a as T >= 1` is a code example where it must be
* treated as >=.
*
* Notably, this only applies to >, not <. In a code snippet like `a as T <= 1`,
* we must NOT tokenize as <, or else the type parser will start parsing a type
* argument and fail.
*/
export function rescan_gt(): void {
if (state.type === tt.greaterThan) {
Expand Down
11 changes: 11 additions & 0 deletions test/flow-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -699,4 +699,15 @@ describe("transform flow", () => {
`,
);
});

it("properly parses flow type args that look like left shift", () => {
assertFlowResult(
`
type A = B<<T>() => void>;
`,
`"use strict";
`,
);
});
});
27 changes: 26 additions & 1 deletion test/typescript-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2500,6 +2500,19 @@ describe("typescript transform", () => {
);
});

it("properly handles <= after `as` and `satisfies`", () => {
assertTypeScriptResult(
`
if (x as number <= 5) {}
if (x satisfies number <= 5) {}
`,
`"use strict";
if (x <= 5) {}
if (x <= 5) {}
`,
);
});

it("handles simple template literal interpolations in types", () => {
assertTypeScriptResult(
`
Expand Down Expand Up @@ -3161,7 +3174,7 @@ describe("typescript transform", () => {
);
});

it("properly parses TS angle brackets that look like left shift", () => {
it("properly parses TS function type args that look like left shift", () => {
assertResult(
`
f<<T>(value: T) => void>(g);
Expand All @@ -3173,6 +3186,18 @@ describe("typescript transform", () => {
);
});

it("properly parses TS type args that look like left shift", () => {
assertResult(
`
type A = B<<T>() => void>;
`,
`
`,
{transforms: ["typescript"]},
);
});

it("properly parses actual left shift", () => {
assertResult(
`
Expand Down

0 comments on commit 473a09e

Please # to comment.