Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(es/typescript): Support const modifier on type parameters #6672

Merged
merged 4 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [typeParameterConstModifiers.ts]
//!
//! x 'const' modifier can only appear on a type parameter of a function, method or class
//! ,-[41:1]
//! 41 | const fx1 = <const T>(x: T) => x;
//! 42 | const fx2 = <const T,>(x: T) => x;
//! 43 |
//! 44 | interface I1<const T> { x: T } // Error
//! : ^^^^^
//! 45 |
//! 46 | interface I2 {
//! 47 | f<const T>(x: T): T;
//! `----
//!
//! x 'const' modifier can only appear on a type parameter of a function, method or class
//! ,-[47:1]
//! 47 | f<const T>(x: T): T;
//! 48 | }
//! 49 |
//! 50 | type T1<const T> = T; // Error
//! : ^^^^^
//! 51 |
//! 52 | type T2 = <const T>(x: T) => T;
//! 53 | type T3 = { <const T>(x: T): T };
//! `----
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [typeParameterConstModifiers.ts]
//!
//! x 'const' modifier can only appear on a type parameter of a function, method or class
//! ,-[41:1]
//! 41 | const fx1 = <const T>(x: T) => x;
//! 42 | const fx2 = <const T,>(x: T) => x;
//! 43 |
//! 44 | interface I1<const T> { x: T } // Error
//! : ^^^^^
//! 45 |
//! 46 | interface I2 {
//! 47 | f<const T>(x: T): T;
//! `----
//!
//! x 'const' modifier can only appear on a type parameter of a function, method or class
//! ,-[47:1]
//! 47 | f<const T>(x: T): T;
//! 48 | }
//! 49 |
//! 50 | type T1<const T> = T; // Error
//! : ^^^^^
//! 51 |
//! 52 | type T2 = <const T>(x: T) => T;
//! 53 | type T3 = { <const T>(x: T): T };
//! `----
3 changes: 3 additions & 0 deletions crates/swc_ecma_ast/src/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub struct TsTypeParam {
#[serde(default, rename = "out")]
pub is_out: bool,

#[serde(default, rename = "const")]
pub is_const: bool,

#[serde(default)]
pub constraint: Option<Box<TsType>>,

Expand Down
5 changes: 5 additions & 0 deletions crates/swc_ecma_codegen/src/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,11 @@ where
fn emit_ts_type_param(&mut self, n: &TsTypeParam) -> Result {
self.emit_leading_comments_of_span(n.span(), false)?;

if n.is_const {
keyword!("const");
space!();
}

if n.is_in {
keyword!("in");
space!();
Expand Down
6 changes: 6 additions & 0 deletions crates/swc_ecma_parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ pub enum SyntaxError {
TS1267,
TS1273(JsWord),
TS1274(JsWord),
TS1277(JsWord),
TS1383,
TS2206,
TS2207,
Expand Down Expand Up @@ -650,6 +651,11 @@ impl SyntaxError {
word
)
.into(),
SyntaxError::TS1277(word) => format!(
"'{}' modifier can only appear on a type parameter of a function, method or class",
word
)
.into(),
SyntaxError::TS1383 => "Only named exports may use 'export type'.".into(),
SyntaxError::TS2206 => "The 'type' modifier cannot be used on a named import when \
'import type' is used on its import statement."
Expand Down
8 changes: 4 additions & 4 deletions crates/swc_ecma_parser/src/parser/class_and_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl<I: Tokens> Parser<I> {
}

let type_params = if p.input.syntax().typescript() {
p.try_parse_ts_type_params(true)?
p.try_parse_ts_type_params(true, true)?
} else {
None
};
Expand Down Expand Up @@ -716,7 +716,7 @@ impl<I: Tokens> Parser<I> {
self.emit_err(span!(self, start), SyntaxError::TS1098);
self.emit_err(span!(self, start2), SyntaxError::TS1092);
} else {
let type_params = self.try_parse_ts_type_params(false)?;
let type_params = self.try_parse_ts_type_params(false, true)?;

if let Some(type_params) = type_params {
for param in type_params.params {
Expand Down Expand Up @@ -1184,7 +1184,7 @@ impl<I: Tokens> Parser<I> {
trace_cur!(p, parse_fn_args_body__type_params);

Ok(if is!(p, '<') {
Some(p.parse_ts_type_params(false)?)
Some(p.parse_ts_type_params(false, true)?)
} else if is!(p, JSXTagStart) {
debug_assert_eq!(
p.input.token_context().current(),
Expand All @@ -1197,7 +1197,7 @@ impl<I: Tokens> Parser<I> {
);
p.input.token_context_mut().pop();

Some(p.parse_ts_type_params(false)?)
Some(p.parse_ts_type_params(false, true)?)
} else {
None
})
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 @@ -104,7 +104,7 @@ impl<I: Tokens> Parser<I> {
}
}

let type_parameters = p.parse_ts_type_params(false)?;
let type_parameters = p.parse_ts_type_params(false, false)?;
let mut arrow = p.parse_assignment_expr_base()?;
match *arrow {
Expr::Arrow(ArrowExpr {
Expand Down
50 changes: 36 additions & 14 deletions crates/swc_ecma_parser/src/parser/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl<I: Tokens> Parser<I> {
let pos = {
let modifier = match *cur!(self, true)? {
Token::Word(ref w @ Word::Ident(..))
| Token::Word(ref w @ Word::Keyword(Keyword::In)) => w.cow(),
| Token::Word(ref w @ Word::Keyword(Keyword::In | Keyword::Const)) => w.cow(),

_ => return Ok(None),
};
Expand Down Expand Up @@ -369,11 +369,16 @@ impl<I: Tokens> Parser<I> {
}

/// `tsParseTypeParameter`
fn parse_ts_type_param(&mut self, allow_modifier: bool) -> PResult<TsTypeParam> {
fn parse_ts_type_param(
&mut self,
permit_in_out: bool,
permit_const: bool,
) -> PResult<TsTypeParam> {
debug_assert!(self.input.syntax().typescript());

let mut is_in = false;
let mut is_out = false;
let mut is_const = false;

let start = cur_pos!(self);

Expand All @@ -392,8 +397,18 @@ impl<I: Tokens> Parser<I> {
false,
)? {
match modifer {
"const" => {
if !permit_const {
self.emit_err(
self.input.prev_span(),
SyntaxError::TS1277(js_word!("const")),
);
} else {
is_const = true;
}
}
"in" => {
if !allow_modifier {
if !permit_in_out {
self.emit_err(self.input.prev_span(), SyntaxError::TS1274(js_word!("in")));
} else if is_in {
self.emit_err(self.input.prev_span(), SyntaxError::TS1030(js_word!("in")));
Expand All @@ -407,7 +422,7 @@ impl<I: Tokens> Parser<I> {
}
}
"out" => {
if !allow_modifier {
if !permit_in_out {
self.emit_err(self.input.prev_span(), SyntaxError::TS1274(js_word!("out")));
} else if is_out {
self.emit_err(self.input.prev_span(), SyntaxError::TS1030(js_word!("out")));
Expand All @@ -428,6 +443,7 @@ impl<I: Tokens> Parser<I> {
name,
is_in,
is_out,
is_const,
constraint,
default,
})
Expand All @@ -436,7 +452,8 @@ impl<I: Tokens> Parser<I> {
/// `tsParseTypeParameter`
pub(super) fn parse_ts_type_params(
&mut self,
allow_modifier: bool,
permit_in_out: bool,
permit_const: bool,
) -> PResult<Box<TsTypeParamDecl>> {
self.in_type().parse_with(|p| {
p.ts_in_no_context(|p| {
Expand All @@ -449,7 +466,7 @@ impl<I: Tokens> Parser<I> {

let params = p.parse_ts_bracketed_list(
ParsingContext::TypeParametersOrArguments,
|p| p.parse_ts_type_param(allow_modifier), // bracket
|p| p.parse_ts_type_param(permit_in_out, permit_const), // bracket
false,
// skip_first_token
true,
Expand Down Expand Up @@ -1066,7 +1083,7 @@ impl<I: Tokens> Parser<I> {
_ => {}
}

let type_params = self.try_parse_ts_type_params(true)?;
let type_params = self.try_parse_ts_type_params(true, false)?;

let extends = if eat!(self, "extends") {
self.parse_ts_heritage_clause()?
Expand Down Expand Up @@ -1108,7 +1125,7 @@ impl<I: Tokens> Parser<I> {
debug_assert!(self.input.syntax().typescript());

let id = self.parse_ident_name()?;
let type_params = self.try_parse_ts_type_params(true)?;
let type_params = self.try_parse_ts_type_params(true, false)?;
let type_ann = self.expect_then_parse_ts_type(&tok!('='), "=")?;
expect!(self, ';');
Ok(Box::new(TsTypeAliasDecl {
Expand Down Expand Up @@ -1269,7 +1286,7 @@ impl<I: Tokens> Parser<I> {
}

// ----- inlined self.tsFillSignature(tt.colon, node);
let type_params = self.try_parse_ts_type_params(false)?;
let type_params = self.try_parse_ts_type_params(false, true)?;
expect!(self, '(');
let params = self.parse_ts_binding_list_for_signature()?;
let type_ann = if is!(self, ':') {
Expand Down Expand Up @@ -1406,7 +1423,7 @@ impl<I: Tokens> Parser<I> {
let optional = eat!(self, '?');

if !readonly && is_one_of!(self, '(', '<') {
let type_params = self.try_parse_ts_type_params(false)?;
let type_params = self.try_parse_ts_type_params(false, true)?;
expect!(self, '(');
let params = self.parse_ts_binding_list_for_signature()?;
let type_ann = if is!(self, ':') {
Expand Down Expand Up @@ -1606,6 +1623,7 @@ impl<I: Tokens> Parser<I> {
name,
is_in: false,
is_out: false,
is_const: false,
constraint,
default: None,
})
Expand Down Expand Up @@ -1817,7 +1835,7 @@ impl<I: Tokens> Parser<I> {
}

// ----- inlined `self.tsFillSignature(tt.arrow, node)`
let type_params = self.try_parse_ts_type_params(false)?;
let type_params = self.try_parse_ts_type_params(false, true)?;
expect!(self, '(');
let params = self.parse_ts_binding_list_for_signature()?;
let type_ann = self.parse_ts_type_or_type_predicate_ann(&tok!("=>"))?;
Expand Down Expand Up @@ -1985,14 +2003,17 @@ impl<I: Tokens> Parser<I> {
/// `tsTryParseTypeParameters`
pub(super) fn try_parse_ts_type_params(
&mut self,
allow_modifier: bool,
permit_in_out: bool,
permit_const: bool,
) -> PResult<Option<Box<TsTypeParamDecl>>> {
if !cfg!(feature = "typescript") {
return Ok(None);
}

if is!(self, '<') {
return self.parse_ts_type_params(allow_modifier).map(Some);
return self
.parse_ts_type_params(permit_in_out, permit_const)
.map(Some);
}
Ok(None)
}
Expand Down Expand Up @@ -2251,6 +2272,7 @@ impl<I: Tokens> Parser<I> {
name: type_param_name,
is_in: false,
is_out: false,
is_const: false,
constraint,
default: None,
};
Expand Down Expand Up @@ -2604,7 +2626,7 @@ impl<I: Tokens> Parser<I> {

let res = if is_one_of!(self, '<', JSXTagStart) {
self.try_parse_ts(|p| {
let type_params = p.parse_ts_type_params(false)?;
let type_params = p.parse_ts_type_params(false, false)?;
// Don't use overloaded parseFunctionParams which would look for "<" again.
expect!(p, '(');
let params: Vec<Pat> = p
Expand Down
9 changes: 9 additions & 0 deletions crates/swc_ecma_parser/tests/tsc/1.0lib-noErrors.json
Original file line number Diff line number Diff line change
Expand Up @@ -19821,6 +19821,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -20058,6 +20059,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": {
"type": "TsArrayType",
"span": {
Expand Down Expand Up @@ -22339,6 +22341,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -23191,6 +23194,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -23802,6 +23806,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -24120,6 +24125,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -24256,6 +24262,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -24423,6 +24430,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down Expand Up @@ -24559,6 +24567,7 @@
},
"in": false,
"out": false,
"const": false,
"constraint": null,
"default": null
}
Expand Down