Skip to content

Commit

Permalink
fix(es/parser): Rescan << as < when parsing type args (#8607)
Browse files Browse the repository at this point in the history
**Description:**

The related issue is a product to the fact that the lexer sees a `<<`
token in `fun<<T>...` and therefore parses the type args as an arrow
function.

This PR adds the handling of the "split" of `<<` when beginning to parse
type args.

I am open for suggestions, I still find this a bit odd (in
`parse_ts_type_args()`):
```rs
if is!(p, "<<") {
    p.input.cut_lshift();
} else {
    expect!(p, '<');
}
```

**Related issue:**

 - Closes #7187
 - Closes #8209
 - Closes #8581
  • Loading branch information
yannkaiser authored Feb 6, 2024
1 parent c3f67ce commit 9e6dad9
Show file tree
Hide file tree
Showing 16 changed files with 1,210 additions and 2 deletions.
22 changes: 22 additions & 0 deletions crates/swc/tests/fixture/issues-7xxx/7187/input/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es2022",
"transform": {
"useDefineForClassFields": true,
"react": {
"importSource": "https://esm.sh/preact",
"runtime": "automatic"
}
},
"loose": false
},
"module": {
"type": "es6"
},
"minify": false,
"isModule": true
}
1 change: 1 addition & 0 deletions crates/swc/tests/fixture/issues-7xxx/7187/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
assertType<<K>(key: K) => K>(foo);
1 change: 1 addition & 0 deletions crates/swc/tests/fixture/issues-7xxx/7187/output/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
assertType(foo);
28 changes: 28 additions & 0 deletions crates/swc/tests/fixture/issues-8xxx/8209/input/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true
},
"target": "es2022",
"transform": {
"optimizer": {
"globals": {
"vars": {
"__NODE__": "true"
}
},
},
},
"loose": false,
"minify": {
"compress": false,
"mangle": false
}
},
"module": {
"type": "es6"
},
"minify": false,
"isModule": true
}
3 changes: 3 additions & 0 deletions crates/swc/tests/fixture/issues-8xxx/8209/input/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Context = createContext<<Key extends keyof Config>(key: Key) => Config[Key]>(
(key) => properties[key]?.default
);
1 change: 1 addition & 0 deletions crates/swc/tests/fixture/issues-8xxx/8209/output/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const Context = createContext((key)=>properties[key]?.default);
3 changes: 3 additions & 0 deletions crates/swc_ecma_parser/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ macro_rules! tok {
('<') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Lt)
};
("<<") => {
crate::token::Token::BinOp(crate::token::BinOpToken::LShift)
};
('>') => {
crate::token::Token::BinOp(crate::token::BinOpToken::Gt)
};
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_parser/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1596,7 +1596,7 @@ impl<I: Tokens> Parser<I> {
let callee = self.parse_new_expr()?;
return_if_arrow!(self, callee);

let type_args = if self.input.syntax().typescript() && is!(self, '<') {
let type_args = if self.input.syntax().typescript() && is_one_of!(self, '<', "<<") {
self.try_parse_ts(|p| {
let type_args = p.parse_ts_type_args()?;
if is!(p, '(') {
Expand Down
13 changes: 13 additions & 0 deletions crates/swc_ecma_parser/src/parser/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,19 @@ impl<I: Tokens> Buffer<I> {
}
}

#[inline]
pub fn cut_lshift(&mut self) {
debug_assert!(
self.is(&tok!("<<")),
"parser should only call cut_lshift when encountering LShift token"
);
self.cur = Some(TokenAndSpan {
token: tok!('<'),
span: self.cur_span().with_lo(self.cur_span().lo + BytePos(1)),
had_line_break: false,
});
}

#[inline]
pub fn is(&mut self, expected: &Token) -> bool {
match self.cur() {
Expand Down
6 changes: 5 additions & 1 deletion crates/swc_ecma_parser/src/parser/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2666,7 +2666,11 @@ impl<I: Tokens> Parser<I> {
// Temporarily remove a JSX parsing context, which makes us scan different
// tokens.
p.ts_in_no_context(|p| {
expect!(p, '<');
if is!(p, "<<") {
p.input.cut_lshift();
} else {
expect!(p, '<');
}
p.parse_ts_delimited_list(ParsingContext::TypeParametersOrArguments, |p| {
trace_cur!(p, parse_ts_type_args__arg);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
assertType<<K>(key: K) => K>(foo);
169 changes: 169 additions & 0 deletions crates/swc_ecma_parser/tests/typescript/issue-7187/input.ts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
{
"type": "Script",
"span": {
"start": 1,
"end": 35,
"ctxt": 0
},
"body": [
{
"type": "ExpressionStatement",
"span": {
"start": 1,
"end": 35,
"ctxt": 0
},
"expression": {
"type": "CallExpression",
"span": {
"start": 1,
"end": 34,
"ctxt": 0
},
"callee": {
"type": "Identifier",
"span": {
"start": 1,
"end": 11,
"ctxt": 0
},
"value": "assertType",
"optional": false
},
"arguments": [
{
"spread": null,
"expression": {
"type": "Identifier",
"span": {
"start": 30,
"end": 33,
"ctxt": 0
},
"value": "foo",
"optional": false
}
}
],
"typeArguments": {
"type": "TsTypeParameterInstantiation",
"span": {
"start": 11,
"end": 29,
"ctxt": 0
},
"params": [
{
"type": "TsFunctionType",
"span": {
"start": 12,
"end": 28,
"ctxt": 0
},
"params": [
{
"type": "Identifier",
"span": {
"start": 16,
"end": 22,
"ctxt": 0
},
"value": "key",
"optional": false,
"typeAnnotation": {
"type": "TsTypeAnnotation",
"span": {
"start": 19,
"end": 22,
"ctxt": 0
},
"typeAnnotation": {
"type": "TsTypeReference",
"span": {
"start": 21,
"end": 22,
"ctxt": 0
},
"typeName": {
"type": "Identifier",
"span": {
"start": 21,
"end": 22,
"ctxt": 0
},
"value": "K",
"optional": false
},
"typeParams": null
}
}
}
],
"typeParams": {
"type": "TsTypeParameterDeclaration",
"span": {
"start": 12,
"end": 15,
"ctxt": 0
},
"parameters": [
{
"type": "TsTypeParameter",
"span": {
"start": 13,
"end": 14,
"ctxt": 0
},
"name": {
"type": "Identifier",
"span": {
"start": 13,
"end": 14,
"ctxt": 0
},
"value": "K",
"optional": false
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
]
},
"typeAnnotation": {
"type": "TsTypeAnnotation",
"span": {
"start": 24,
"end": 28,
"ctxt": 0
},
"typeAnnotation": {
"type": "TsTypeReference",
"span": {
"start": 27,
"end": 28,
"ctxt": 0
},
"typeName": {
"type": "Identifier",
"span": {
"start": 27,
"end": 28,
"ctxt": 0
},
"value": "K",
"optional": false
},
"typeParams": null
}
}
}
]
}
}
}
],
"interpreter": null
}
3 changes: 3 additions & 0 deletions crates/swc_ecma_parser/tests/typescript/issue-8209/input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Context = createContext<<Key extends keyof Config>(key: Key) => Config[Key]>(
(key) => properties[key]?.default
);
Loading

0 comments on commit 9e6dad9

Please sign in to comment.