Skip to content

Commit f6b56d6

Browse files
fix(ecmascript): resolve identifier value type via tracked constants
`value_type` returned `Undetermined` for any non-global identifier, even when the binding had zero writes and a known constant initializer. As a result `slot?.()` on a `let slot;` (with all writers tree-shaken away) was not folded to `void 0`, leaving dead optional calls in the output. Consult `get_constant_value_for_reference_id` for non-global identifier references and map the resulting `ConstantValue` to a `ValueType`. This lets `fold_chain_expr` (and other consumers of `value_type`) recognise read-only `let`/`const` bindings whose value is statically known. Closes rolldown/rolldown#9281
1 parent 24946be commit f6b56d6

2 files changed

Lines changed: 32 additions & 0 deletions

File tree

crates/oxc_ecmascript/src/constant_evaluation/value_type.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use oxc_ast::ast::*;
22
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
33

4+
use super::value::ConstantValue;
45
use crate::{GlobalContext, to_numeric::ToNumeric, to_primitive::ToPrimitive};
56

67
/// JavaScript Language Type
@@ -52,6 +53,17 @@ impl ValueType {
5253
}
5354
}
5455

56+
fn constant_value_type(value: &ConstantValue<'_>) -> ValueType {
57+
match value {
58+
ConstantValue::Number(_) => ValueType::Number,
59+
ConstantValue::BigInt(_) => ValueType::BigInt,
60+
ConstantValue::String(_) => ValueType::String,
61+
ConstantValue::Boolean(_) => ValueType::Boolean,
62+
ConstantValue::Undefined => ValueType::Undefined,
63+
ConstantValue::Null => ValueType::Null,
64+
}
65+
}
66+
5567
/// Based on `get_known_value_type` in closure compiler
5668
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/NodeUtil.java#L1517>
5769
///
@@ -91,6 +103,12 @@ impl<'a> DetermineValueType<'a> for Expression<'a> {
91103
"NaN" | "Infinity" => ValueType::Number,
92104
_ => ValueType::Undetermined,
93105
}
106+
} else if let Some(value) = ident
107+
.reference_id
108+
.get()
109+
.and_then(|reference_id| ctx.get_constant_value_for_reference_id(reference_id))
110+
{
111+
constant_value_type(&value)
94112
} else {
95113
ValueType::Undetermined
96114
}

crates/oxc_minifier/tests/peephole/dead_code_elimination.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,3 +546,17 @@ fn remove_pure_function_calls() {
546546
test("var foo = function() {}; foo()", "");
547547
test_same("function foo() { bar() } foo()");
548548
}
549+
550+
#[test]
551+
fn fold_optional_chain_on_undefined_let_binding() {
552+
// https://github.com/rolldown/rolldown/issues/9281
553+
// A `let` binding with no writes is statically known to be `undefined`,
554+
// so optional calls / member accesses on it should fold to `void 0`.
555+
test("let slot; export function call() { slot?.() }", "export function call() {}");
556+
test("let slot; export function call() { slot?.foo }", "export function call() {}");
557+
test("let slot; export function call() { slot?.[foo()] }", "export function call() {}");
558+
// A binding that is written somewhere is not nullish-known: leave it alone.
559+
test_same(
560+
"let slot; export function setSlot(v) { slot = v } export function call() { slot?.() }",
561+
);
562+
}

0 commit comments

Comments
 (0)