feat(ecma): define math constants as known globals and resolve their types (#26585)

`Number.*` constants will now be treated as side effect free
`typeof Math.*` and `typeof Number.*` will now resolve to `number`

ref: https://github.com/rolldown/rolldown/issues/10852
This commit is contained in:
Armano
2026-09-14 03:15:54 +02:00
committed by GitHub
parent 4d5c812d6b
commit ca649e059c
5 changed files with 61 additions and 7 deletions
@@ -455,6 +455,9 @@ pub(super) fn is_known_global_property(global: &str, property: &str) -> bool {
match global {
"Math" => matches!(property, "E" | "LN10" | "LN2" | "LOG10E" | "LOG2E" | "PI" | "SQRT1_2" | "SQRT2")
|| is_pure_math_method(property),
"Number" => matches!(property,
"POSITIVE_INFINITY" | "NEGATIVE_INFINITY" | "EPSILON" | "NaN"
| "MAX_VALUE" | "MIN_VALUE" | "MAX_SAFE_INTEGER" | "MIN_SAFE_INTEGER"),
"console" => matches!(property,
"assert" | "clear" | "count" | "countReset" | "debug" | "dir" | "dirxml"
| "error" | "group" | "groupCollapsed" | "groupEnd" | "info" | "log"
+17 -5
View File
@@ -272,12 +272,24 @@ impl<'a> DetermineValueType<'a> for LogicalExpression<'a> {
impl<'a> DetermineValueType<'a> for StaticMemberExpression<'a> {
fn value_type(&self, ctx: &impl GlobalContext<'a>) -> ValueType {
if matches!(self.property.name.as_str(), "POSITIVE_INFINITY" | "NEGATIVE_INFINITY")
&& ctx.is_global_expr("Number", &self.object)
{
return ValueType::Number;
match self.property.name.as_str() {
"POSITIVE_INFINITY" | "NEGATIVE_INFINITY" | "EPSILON" | "NaN" | "MAX_VALUE"
| "MIN_VALUE" => {
if ctx.is_global_expr("Number", &self.object) {
ValueType::Number
} else {
ValueType::Undetermined
}
}
"E" | "LN10" | "LN2" | "LOG10E" | "LOG2E" | "PI" | "SQRT1_2" | "SQRT2" => {
if ctx.is_global_expr("Math", &self.object) {
ValueType::Number
} else {
ValueType::Undetermined
}
}
_ => ValueType::Undetermined,
}
ValueType::Undetermined
}
}
@@ -915,11 +915,28 @@ fn test_known_global_property_reads() {
// Math properties
test("Math.PI", false);
test("Math.E", false);
test("Math.LN10", false);
test("Math.LN2", false);
test("Math.LOG10E", false);
test("Math.LOG2E", false);
test("Math.SQRT1_2", false);
test("Math.SQRT2", false);
test("Math.abs", false);
test("Math.floor", false);
test("Math.random", false);
test("Math.unknownProp", true);
// Number properties
test("Number.POSITIVE_INFINITY", false);
test("Number.NEGATIVE_INFINITY", false);
test("Number.EPSILON", false);
test("Number.NaN", false);
test("Number.MAX_VALUE", false);
test("Number.MIN_VALUE", false);
test("Number.MAX_SAFE_INTEGER", false);
test("Number.MIN_SAFE_INTEGER", false);
test("Number.UNKNOWN", true);
// Object properties
test("Object.keys", false);
test("Object.create", false);
@@ -448,6 +448,28 @@ fn js_typeof() {
fold_same("x = typeof[1,[foo()]]");
fold_same("x = typeof{bathwater:baby()}");
fold_same("x = typeof class { static { foo() } }");
fold("typeof NaN", "'number'");
fold("typeof Infinity", "'number'");
fold("typeof Math.E", "'number'");
fold("typeof Math.LN10", "'number'");
fold("typeof Math.LN2", "'number'");
fold("typeof Math.LOG10E", "'number'");
fold("typeof Math.LOG2E", "'number'");
fold("typeof Math.PI", "'number'");
fold("typeof Math.SQRT1_2", "'number'");
fold("typeof Math.SQRT2", "'number'");
fold_same("typeof Math.missing");
fold("typeof Number.POSITIVE_INFINITY", "'number'");
fold("typeof Number.NEGATIVE_INFINITY", "'number'");
fold("typeof Number.EPSILON", "'number'");
fold("typeof Number.NaN", "'number'");
fold("typeof Number.MAX_VALUE", "'number'");
fold("typeof Number.MIN_VALUE", "'number'");
fold("typeof Number.MAX_SAFE_INTEGER", "'number'");
fold("typeof Number.MIN_SAFE_INTEGER", "'number'");
fold_same("typeof Number.UNKNOWN");
}
#[test]
@@ -130,8 +130,8 @@ fn test_mathematical_expression_edge_cases() {
test("Infinity + 1", ""); // eliminated as unused expression
test("Infinity - Infinity", ""); // eliminated as unused expression
test("Infinity / Infinity", ""); // eliminated as unused expression
test_same("Math.PI * 2"); // runtime value
test_same("Math.E + 1"); // runtime value
test("Math.PI * 2", ""); // eliminated as unused expression
test("Math.E + 1", ""); // eliminated as unused expression
test("-0 + 0", ""); // eliminated as unused expression
test("-0 * 1", ""); // eliminated as unused expression
test("1 / -0", ""); // eliminated as unused expression