Skip to content

Commit 1e3ed5c

Browse files
magic-akarikdy1
andauthored
fix(es/transforms): use numeric literals for non-finite enum values (#12049)
**Description:** Enum evaluation currently rebuilds computed `NaN` and infinity values as identifiers. That turns numeric constants into apparent references and loses number-literal semantics as values move through the TypeScript transform and fast-DTS evaluators. This PR keeps computed enum numbers as `Number` literals in both paths, applies JavaScript number stringification when names are required, and corrects modulo evaluation for non-finite operands. **Related issue (if exists):** - #12043 - #12047 - #12050 - #12048 - #12051 - #12052 - #12053 - #12054 - #12055 - #12056 - #12057 - #12058 - **#12049** (current) - #12059 - #12060 Co-authored-by: Donny/강동윤 <kdy.1997.dev@gmail.com>
1 parent 46900a3 commit 1e3ed5c

11 files changed

Lines changed: 150 additions & 112 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
swc: patch
3+
swc_core: patch
4+
swc_ecma_transforms_typescript: patch
5+
swc_typescript: patch
6+
---
7+
8+
fix(es/transforms): Use numeric literals for non-finite enum values.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export var Mask = /*#__PURE__*/ function(Mask) {
2-
/* prettier-ignore */ Mask[Mask["UseAll"] = 9007199254740991] = "UseAll";
2+
/* prettier-ignore */ Mask[Mask["UseAll"] = 0x1FFFFFFFFFFFFF] = "UseAll";
33
return Mask;
44
}({});
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
var CHAR = /*#__PURE__*/ function(CHAR) {
2-
CHAR[CHAR[" "] = 9] = " ";
3-
CHAR[CHAR["\n"] = 10] = "\n";
4-
CHAR[CHAR["\v"] = 11] = "\v";
5-
CHAR[CHAR["\f"] = 12] = "\f";
6-
CHAR[CHAR["\r"] = 13] = "\r";
7-
CHAR[CHAR[" "] = 32] = " ";
8-
CHAR[CHAR["-"] = 45] = "-";
9-
CHAR[CHAR["["] = 91] = "[";
2+
CHAR[CHAR[" "] = 0x09] = " ";
3+
CHAR[CHAR["\n"] = 0x0A] = "\n";
4+
CHAR[CHAR["\v"] = 0x0B] = "\v";
5+
CHAR[CHAR["\f"] = 0x0C] = "\f";
6+
CHAR[CHAR["\r"] = 0x0D] = "\r";
7+
CHAR[CHAR[" "] = 0x20] = " ";
8+
CHAR[CHAR["-"] = 0x2D] = "-";
9+
CHAR[CHAR["["] = 0x5B] = "[";
1010
return CHAR;
1111
}(CHAR || {});
1212
var c = "\n";
13-
console.log(c.charCodeAt(0) === 10);
13+
console.log(c.charCodeAt(0) === 0x0A);
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
var CHAR = /*#__PURE__*/ function(CHAR) {
22
CHAR[CHAR["foo"] = 1] = "foo";
3-
CHAR[CHAR[" "] = 9] = " ";
4-
CHAR[CHAR["\n"] = 10] = "\n";
5-
CHAR[CHAR["\v"] = 11] = "\v";
6-
CHAR[CHAR["\f"] = 12] = "\f";
7-
CHAR[CHAR["\r"] = 13] = "\r";
3+
CHAR[CHAR[" "] = 0x09] = " ";
4+
CHAR[CHAR["\n"] = 0x0A] = "\n";
5+
CHAR[CHAR["\v"] = 0x0B] = "\v";
6+
CHAR[CHAR["\f"] = 0x0C] = "\f";
7+
CHAR[CHAR["\r"] = 0x0D] = "\r";
88
return CHAR;
99
}(CHAR || {});

crates/swc_ecma_transforms_typescript/src/semantic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ mod tests {
670670
let value = SemanticAnalyzer::transform_ts_enum_member(
671671
enum_member("A"),
672672
&id("E"),
673-
&TsEnumRecordValue::Number(2.0.into()),
673+
&TsEnumRecordValue::from(2.0),
674674
&Default::default(),
675675
SyntaxContext::empty(),
676676
false,
@@ -679,6 +679,6 @@ mod tests {
679679
let TsEnumRecordValue::Number(value) = value else {
680680
panic!("expected defaulted TypeScript enum member to stay numeric");
681681
};
682-
assert_eq!(*value, 2.0);
682+
assert_eq!(value.value, 2.0);
683683
}
684684
}

crates/swc_ecma_transforms_typescript/src/ts_enum.rs

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_hash::FxHashMap;
2-
use swc_atoms::{atom, Atom, Wtf8Atom};
2+
use swc_atoms::{Atom, Wtf8Atom};
33
use swc_common::{SyntaxContext, DUMMY_SP};
44
use swc_ecma_ast::*;
55
use swc_ecma_utils::{
@@ -26,15 +26,34 @@ pub(crate) type TsEnumRecord = FxHashMap<TsEnumRecordKey, TsEnumRecordValue>;
2626
#[derive(Debug, Clone)]
2727
pub(crate) enum TsEnumRecordValue {
2828
String(Atom),
29-
Number(JsNumber),
29+
Number(Number),
3030
Opaque(Box<Expr>),
3131
Void,
3232
}
3333

3434
impl TsEnumRecordValue {
35+
/// Creates a computed number. Computations intentionally discard source
36+
/// spelling so code generation can choose the canonical representation.
37+
fn number(value: impl Into<f64>) -> Self {
38+
Self::Number(Number {
39+
span: DUMMY_SP,
40+
value: value.into(),
41+
raw: None,
42+
})
43+
}
44+
45+
/// Creates a number converted directly from an AST value.
46+
fn converted_number(value: impl Into<f64>, raw: Option<Atom>) -> Self {
47+
Self::Number(Number {
48+
span: DUMMY_SP,
49+
value: value.into(),
50+
raw,
51+
})
52+
}
53+
3554
pub fn inc(&self) -> Self {
3655
match self {
37-
Self::Number(num) => Self::Number((**num + 1.0).into()),
56+
Self::Number(num) => Self::number(num.value + 1.0),
3857
_ => Self::Void,
3958
}
4059
}
@@ -59,37 +78,7 @@ impl From<TsEnumRecordValue> for Expr {
5978
fn from(value: TsEnumRecordValue) -> Self {
6079
match value {
6180
TsEnumRecordValue::String(string) => Lit::Str(string.into()).into(),
62-
TsEnumRecordValue::Number(num) if num.is_nan() => Ident {
63-
span: DUMMY_SP,
64-
sym: atom!("NaN"),
65-
..Default::default()
66-
}
67-
.into(),
68-
TsEnumRecordValue::Number(num) if num.is_infinite() => {
69-
let value: Expr = Ident {
70-
span: DUMMY_SP,
71-
sym: atom!("Infinity"),
72-
..Default::default()
73-
}
74-
.into();
75-
76-
if num.is_sign_negative() {
77-
UnaryExpr {
78-
span: DUMMY_SP,
79-
op: op!(unary, "-"),
80-
arg: value.into(),
81-
}
82-
.into()
83-
} else {
84-
value
85-
}
86-
}
87-
TsEnumRecordValue::Number(num) => Lit::Num(Number {
88-
span: DUMMY_SP,
89-
value: *num,
90-
raw: None,
91-
})
92-
.into(),
81+
TsEnumRecordValue::Number(num) => Lit::Num(num).into(),
9382
TsEnumRecordValue::Void => *Expr::undefined(DUMMY_SP),
9483
TsEnumRecordValue::Opaque(expr) => *expr,
9584
}
@@ -98,7 +87,7 @@ impl From<TsEnumRecordValue> for Expr {
9887

9988
impl From<f64> for TsEnumRecordValue {
10089
fn from(value: f64) -> Self {
101-
Self::Number(value.into())
90+
Self::number(value)
10291
}
10392
}
10493

@@ -117,7 +106,7 @@ impl EnumValueComputer<'_> {
117106
fn compute_rec(&self, expr: Box<Expr>) -> TsEnumRecordValue {
118107
match *expr {
119108
Expr::Lit(Lit::Str(s)) => TsEnumRecordValue::String(atom_from_wtf8_atom(&s.value)),
120-
Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n.value.into()),
109+
Expr::Lit(Lit::Num(n)) => TsEnumRecordValue::Number(n),
121110
Expr::Ident(ref ident) if ident.ctxt == self.unresolved_ctxt => {
122111
if let Some(value) = self.record.get(&TsEnumRecordKey {
123112
enum_id: self.enum_id.clone(),
@@ -135,8 +124,13 @@ impl EnumValueComputer<'_> {
135124
}
136125
} else {
137126
match ident.sym.as_ref() {
138-
"Infinity" => TsEnumRecordValue::Number(f64::INFINITY.into()),
139-
"NaN" => TsEnumRecordValue::Number(f64::NAN.into()),
127+
"Infinity" => TsEnumRecordValue::converted_number(
128+
f64::INFINITY,
129+
Some(ident.sym.clone()),
130+
),
131+
"NaN" => {
132+
TsEnumRecordValue::converted_number(f64::NAN, Some(ident.sym.clone()))
133+
}
140134
_ => TsEnumRecordValue::Opaque(expr),
141135
}
142136
}
@@ -177,9 +171,9 @@ impl EnumValueComputer<'_> {
177171
};
178172

179173
match expr.op {
180-
op!(unary, "+") => TsEnumRecordValue::Number(num),
181-
op!(unary, "-") => TsEnumRecordValue::Number(-num),
182-
op!("~") => TsEnumRecordValue::Number(!num),
174+
op!(unary, "+") => TsEnumRecordValue::number(num.value),
175+
op!(unary, "-") => TsEnumRecordValue::number(-num.value),
176+
op!("~") => TsEnumRecordValue::number(!JsNumber::from(num.value)),
183177
_ => unreachable!(),
184178
}
185179
}
@@ -209,6 +203,8 @@ impl EnumValueComputer<'_> {
209203

210204
match (left, right, expr.op) {
211205
(TsEnumRecordValue::Number(left), TsEnumRecordValue::Number(right), op) => {
206+
let left = JsNumber::from(left.value);
207+
let right = JsNumber::from(right.value);
212208
let value = match op {
213209
op!(bin, "+") => left + right,
214210
op!(bin, "-") => left - right,
@@ -225,18 +221,18 @@ impl EnumValueComputer<'_> {
225221
_ => unreachable!(),
226222
};
227223

228-
TsEnumRecordValue::Number(value)
224+
TsEnumRecordValue::number(value)
229225
}
230226
(TsEnumRecordValue::String(left), TsEnumRecordValue::String(right), op!(bin, "+")) => {
231227
TsEnumRecordValue::String(format!("{left}{right}").into())
232228
}
233229
(TsEnumRecordValue::Number(left), TsEnumRecordValue::String(right), op!(bin, "+")) => {
234-
let left = left.to_js_string();
230+
let left = left.value.to_js_string();
235231

236232
TsEnumRecordValue::String(format!("{left}{right}").into())
237233
}
238234
(TsEnumRecordValue::String(left), TsEnumRecordValue::Number(right), op!(bin, "+")) => {
239-
let right = right.to_js_string();
235+
let right = right.value.to_js_string();
240236

241237
TsEnumRecordValue::String(format!("{left}{right}").into())
242238
}
@@ -305,7 +301,7 @@ impl EnumValueComputer<'_> {
305301

306302
let expr = match expr {
307303
TsEnumRecordValue::String(s) => s.to_string(),
308-
TsEnumRecordValue::Number(n) => n.to_js_string(),
304+
TsEnumRecordValue::Number(n) => n.value.to_js_string(),
309305
_ => return opaque_expr,
310306
};
311307

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
enum TemplateConstants {
2+
Positive = 1 / 0,
3+
Negative = -Positive,
4+
NotANumber = 0 / 0,
5+
PositiveText = `positive: ${Positive}!`,
6+
NegativeText = `negative: ${Negative}!`,
7+
NaNText = `nan: ${NotANumber}!`,
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var TemplateConstants = /*#__PURE__*/ function(TemplateConstants) {
2+
TemplateConstants[TemplateConstants["Positive"] = 1 / 0] = "Positive";
3+
TemplateConstants[TemplateConstants["Negative"] = -1 / 0] = "Negative";
4+
TemplateConstants[TemplateConstants["NotANumber"] = 0 / 0] = "NotANumber";
5+
TemplateConstants["PositiveText"] = "positive: Infinity!";
6+
TemplateConstants["NegativeText"] = "negative: -Infinity!";
7+
TemplateConstants["NaNText"] = "nan: NaN!";
8+
return TemplateConstants;
9+
}(TemplateConstants || {});

0 commit comments

Comments
 (0)