Skip to content
7 changes: 7 additions & 0 deletions src/ast/e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ pub struct Call {

/// Used when printing to generate the source prop on the fly
pub was_jsx_element: bool,

/// Set for `a.b()` / `a[b]()` in the source. Other property-access targets print as `(0, a.b)()`.
pub target_was_originally_property_access: bool,
}
impl Default for Call {
fn default() -> Self {
Expand All @@ -284,6 +287,7 @@ impl Default for Call {
close_paren_loc: crate::Loc::EMPTY,
can_be_unwrapped_if_unused: CallUnwrap::Never,
was_jsx_element: false,
target_was_originally_property_access: false,
}
}
}
Expand Down Expand Up @@ -2118,6 +2122,8 @@ pub struct Template {
/// `parts()` / `parts_mut()` for ergonomic access; never null.
pub parts: crate::StoreSlice<TemplatePart>,
pub head: TemplateContents,
/// Same as `Call::target_was_originally_property_access`, for the tag.
pub tag_was_originally_property_access: bool,
}

impl Template {
Expand Down Expand Up @@ -2340,6 +2346,7 @@ impl Template {
.expect("infallible: variant checked")
.shallow_clone(),
),
tag_was_originally_property_access: false,
},
loc,
)
Expand Down
2 changes: 2 additions & 0 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2001,6 +2001,7 @@ impl Data {
close_paren_loc: el.close_paren_loc,
can_be_unwrapped_if_unused: el.can_be_unwrapped_if_unused,
was_jsx_element: el.was_jsx_element,
target_was_originally_property_access: el.target_was_originally_property_access,
});
Ok(Data::ECall(StoreRef::from_bump(item)))
}
Expand Down Expand Up @@ -2084,6 +2085,7 @@ impl Data {
// `TemplateContents` is POD-shaped; `shallow_clone` is the
// safe field-wise copy.
head: el.head.shallow_clone(),
tag_was_originally_property_access: el.tag_was_originally_property_access,
});
Ok(Data::ETemplate(StoreRef::from_bump(item)))
}
Expand Down
1 change: 1 addition & 0 deletions src/ast/fold_string_addition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub fn fold_string_addition(
right.head.cooked(),
matches!(l.data, Data::EInlinedEnum(_)),
)),
tag_was_originally_property_access: false,
},
l.loc,
));
Expand Down
1 change: 1 addition & 0 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@ pub mod parse_worker {
target: require_property,
// SAFETY: bump-owned slice; never grown via this Vec.
args: unsafe { bun_ast::ExprNodeList::from_bump_slice(require_args) },
target_was_originally_property_access: true,
..Default::default()
},
Loc { start: 0 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ pub(crate) fn convert_stmts_for_chunk_for_dev_server<'bump>(
},
record.range.loc,
)]),
target_was_originally_property_access: true,
..Default::default()
},
stmt.loc,
Expand Down
1 change: 1 addition & 0 deletions src/bundler/linker_context/generateCodeForLazyExport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ pub(crate) fn generate_code_for_lazy_export(
tag: None,
parts: parts_slice,
head: E::TemplateContents::Cooked(E::String::init(b"")),
tag_was_originally_property_access: false,
},
stmt.loc,
);
Expand Down
2 changes: 2 additions & 0 deletions src/js_parser/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,12 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// To avoid thinking too much about edgecases, only do this for:
// 1) Objects with a single property
// 2) Not a method, not a computed property
// 3) Not a call target or template tag ("this" would change)
if obj.properties.len_u32() == 1
&& !identifier_opts.is_delete_target()
&& identifier_opts.assign_target() == js_ast::AssignTarget::None
&& !identifier_opts.is_call_target()
&& !identifier_opts.is_template_tag()
{
let prop: &G::Property = &obj.properties.slice()[0];
if let (Some(value), Some(key)) = (prop.value, prop.key) {
Expand Down
1 change: 1 addition & 0 deletions src/js_parser/lower/lower_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
new_args.push(*arg);
}
e.target = call_target;
e.target_was_originally_property_access = true;
e.args = ExprNodeList::from_bump_vec(new_args);
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/js_parser/lower/lower_esm_exports_hmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ impl<'a> ConvertESMExportsForHmr<'a> {
bun_ast::Loc::EMPTY,
),
args: bun_alloc::AstAlloc::vec(),
target_was_originally_property_access: true,
..Default::default()
},
bun_ast::Loc::EMPTY,
Expand Down
2 changes: 2 additions & 0 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ pub struct P<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> {
// syntactic constructs as appropriate.
pub(crate) stmt_expr_value: js_ast::ExprData,
pub(crate) call_target: js_ast::ExprData,
pub(crate) template_tag: js_ast::ExprData,
pub(crate) delete_target: js_ast::ExprData,
pub(crate) loop_body: js_ast::StmtData,
pub(crate) module_scope: js_ast::StoreRef<js_ast::Scope>,
Expand Down Expand Up @@ -8626,6 +8627,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
allow_in: true,

call_target: null_expr_data(),
template_tag: null_expr_data(),
delete_target: null_expr_data(),
stmt_expr_value: null_expr_data(),
loop_body: null_stmt_data(),
Expand Down
1 change: 1 addition & 0 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ fn lower_one_date_time_literal<'a>(
target: from_dot,
args: Vec::from_arena_slice(args_slice),
can_be_unwrapped_if_unused: E::CallUnwrap::IfUnused,
target_was_originally_property_access: true,
..Default::default()
},
loc,
Expand Down
1 change: 1 addition & 0 deletions src/js_parser/parse/parse_prefix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
tag: None,
head: E::TemplateContents::Cooked(head),
parts,
tag_was_originally_property_access: false,
},
loc,
))
Expand Down
5 changes: 5 additions & 0 deletions src/js_parser/parse/parse_suffix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let target = *left;
*left = p.new_expr(
E::Call {
target_was_originally_property_access: target.has_value_for_this_in_call(),
target,
args: list_loc.list,
close_paren_loc: list_loc.loc,
Expand Down Expand Up @@ -204,6 +205,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let target = *left;
*left = p.new_expr(
E::Call {
target_was_originally_property_access: target.has_value_for_this_in_call(),
target,
args: list_loc.list,
close_paren_loc: list_loc.loc,
Expand Down Expand Up @@ -286,6 +288,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let tag = *left;
*left = p.new_expr(
E::Template {
tag_was_originally_property_access: tag.has_value_for_this_in_call(),
tag: Some(tag),
head: E::TemplateContents::Raw(head),
parts: E::Template::empty_parts(),
Expand Down Expand Up @@ -316,6 +319,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let loc = left.loc;
*left = p.new_expr(
E::Template {
tag_was_originally_property_access: tag.has_value_for_this_in_call(),
tag: Some(tag),
head: E::TemplateContents::Raw(head),
parts,
Expand Down Expand Up @@ -386,6 +390,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let target = *left;
*left = p.new_expr(
E::Call {
target_was_originally_property_access: target.has_value_for_this_in_call(),
target,
args: list_loc.list,
close_paren_loc: list_loc.loc,
Expand Down
2 changes: 2 additions & 0 deletions src/js_parser/parse/parse_typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let args = p.parse_call_args()?;
expr = p.new_expr(
E::Call {
target_was_originally_property_access: expr
.has_value_for_this_in_call(),
target: expr,
args: args.list,
close_paren_loc: args.loc,
Expand Down
13 changes: 12 additions & 1 deletion src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,8 @@ impl AsyncPrefixExpression {
}

// Packed u8 — assign_target:u2, is_delete_target:b1,
// was_originally_identifier:b1, is_call_target:b1, _padding:u3 (LSB-first).
// was_originally_identifier:b1, is_call_target:b1, is_template_tag:b1,
// _padding:u2 (LSB-first).
Comment thread
robobun marked this conversation as resolved.
Resolved
// Not all-bool (assign_target is a 2-bit enum), so per PORTING.md we use a
// transparent u8 with manual shift accessors.
#[repr(transparent)]
Expand All @@ -895,6 +896,7 @@ impl IdentifierOpts {
const IS_DELETE_TARGET: u8 = 1 << 2;
const WAS_ORIGINALLY_IDENTIFIER: u8 = 1 << 3;
const IS_CALL_TARGET: u8 = 1 << 4;
const IS_TEMPLATE_TAG: u8 = 1 << 5;

#[inline]
pub(crate) const fn assign_target(self) -> js_ast::AssignTarget {
Expand Down Expand Up @@ -922,6 +924,10 @@ impl IdentifierOpts {
pub(crate) const fn is_call_target(self) -> bool {
self.0 & Self::IS_CALL_TARGET != 0
}
#[inline]
pub(crate) const fn is_template_tag(self) -> bool {
self.0 & Self::IS_TEMPLATE_TAG != 0
}

// Builder-style helpers (this stays a packed u8 rather than a
// named-field struct).
Expand Down Expand Up @@ -949,6 +955,11 @@ impl IdentifierOpts {
self.0 = (self.0 & !Self::IS_CALL_TARGET) | ((v as u8) << 4);
self
}
#[inline]
pub(crate) const fn with_is_template_tag(mut self, v: bool) -> Self {
self.0 = (self.0 & !Self::IS_TEMPLATE_TAG) | ((v as u8) << 5);
self
}
}

pub(crate) fn statement_cares_about_scope(stmt: &Stmt) -> bool {
Expand Down
61 changes: 37 additions & 24 deletions src/js_parser/visit/visit_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
.expect("infallible: variant checked")
.ref_,
);
// Both hold the parse-time ref, so compare before `find_symbol` resolves `e_.ref_`.
let is_call_target = matches!(p.call_target, Data::EIdentifier(ct) if ct.ref_.eql(e_.ref_));
let is_template_tag =
matches!(p.template_tag, Data::EIdentifier(tt) if tt.ref_.eql(e_.ref_));

let name = p.load_name_from_ref(e_.ref_);
if p.is_strict_mode() && js_lexer::is_strict_mode_reserved_word(name) {
Expand Down Expand Up @@ -271,7 +275,17 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
|| in_.assign_target == js_ast::AssignTarget::None
{
p.ignore_usage(e_.ref_);
*e = newvalue;
// "--define foo=a.b" is one identifier named "a.b": "foo()" => "(0, a.b)()"
let is_dotted_name = matches!(newvalue.data.tag(), Tag::EIdentifier)
&& def
.original_name()
.is_some_and(|n| strings::contains_char(n, b'.'));
*e = if (is_call_target || is_template_tag) && is_dotted_name {
p.new_expr(E::Number::new(0.0), expr.loc)
.join_with_comma(newvalue)
} else {
newvalue
};
return;
}

Expand Down Expand Up @@ -300,15 +314,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// Substitute uncalled "require" for the require target
if p.require_ref.eql(e_.ref_) && !p.is_source_runtime() {
// mark a reference to __require only if this is not about to be used for a call target
if !(matches!(p.call_target.tag(), Tag::EIdentifier)
&& expr.data.e_identifier().unwrap().ref_.eql(
p.call_target
.e_identifier()
.expect("infallible: variant checked")
.ref_,
))
&& p.options.features.allow_runtime
{
if !is_call_target && p.options.features.allow_runtime {
p.record_usage_of_runtime_require();
}

Expand All @@ -324,15 +330,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
IdentifierOpts::default()
.with_assign_target(in_.assign_target)
.with_is_delete_target(is_delete_target)
.with_is_call_target(
matches!(p.call_target.tag(), Tag::EIdentifier)
&& expr.data.e_identifier().unwrap().ref_.eql(
p.call_target
.e_identifier()
.expect("infallible: variant checked")
.ref_,
),
)
.with_is_call_target(is_call_target)
.with_is_template_tag(is_template_tag)
.with_was_originally_identifier(true),
);
}
Expand Down Expand Up @@ -470,6 +469,8 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
// Call createElement()
*e = p.new_expr(
E::Call {
target_was_originally_property_access: target
.has_value_for_this_in_call(),
target,
args,
// Enable tree shaking
Expand Down Expand Up @@ -686,8 +687,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let expr = *e;
let _ = in_;
let mut e_ = expr.data.e_template().expect("infallible: variant checked");
if e_.tag.is_some() {
p.visit_expr(e_.tag.as_mut().unwrap());
if let Some(tag) = e_.tag.as_mut() {
p.template_tag = tag.data;
p.visit_expr(tag);
}

// Visit the interpolation values before the macro dispatch below: its
Expand Down Expand Up @@ -879,6 +881,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let expr = *e;
let mut e_ = expr.data.e_index().expect("infallible: variant checked");
let is_call_target = matches!(p.call_target, Data::EIndex(ct) if core::ptr::eq(&raw const *e_, &raw const *ct));
let is_template_tag = matches!(p.template_tag, Data::EIndex(tt) if core::ptr::eq(&raw const *e_, &raw const *tt));
let is_delete_target = matches!(p.delete_target, Data::EIndex(dt) if core::ptr::eq(&raw const *e_, &raw const *dt));

// "a['b']" => "a.b"
Expand All @@ -899,6 +902,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if is_call_target {
p.call_target = dot.data;
}
if is_template_tag {
p.template_tag = dot.data;
}
if is_delete_target {
p.delete_target = dot.data;
}
Expand Down Expand Up @@ -1014,6 +1020,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if is_call_target {
p.call_target = dot.data;
}
if is_template_tag {
p.template_tag = dot.data;
}
if is_delete_target {
p.delete_target = dot.data;
}
Expand All @@ -1035,7 +1044,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
unwrapped.loc,
IdentifierOpts::default()
.with_is_call_target(is_call_target)
// .is_template_tag = is_template_tag,
.with_is_template_tag(is_template_tag)
.with_is_delete_target(is_delete_target)
.with_assign_target(in_.assign_target),
) {
Expand Down Expand Up @@ -1100,7 +1109,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
}
if inlined.can_be_inlined_from_property_access() {
// "[obj.m][0]()" => "(0, obj.m)()"
*e = if is_call_target && inlined.has_value_for_this_in_call() {
// "[obj.m][0]``" => "(0, obj.m)``"
*e = if (is_call_target || is_template_tag)
&& inlined.has_value_for_this_in_call()
{
p.new_expr(E::Number::new(0.0), expr.loc)
.join_with_comma(inlined)
} else {
Expand Down Expand Up @@ -1335,6 +1347,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
let mut e_ = expr.data.e_dot().expect("infallible: variant checked");
let is_delete_target = matches!(p.delete_target, Data::EDot(dt) if core::ptr::eq(&raw const *e_, &raw const *dt));
let is_call_target = matches!(p.call_target, Data::EDot(ct) if core::ptr::eq(&raw const *e_, &raw const *ct));
let is_template_tag = matches!(p.template_tag, Data::EDot(tt) if core::ptr::eq(&raw const *e_, &raw const *tt));

// `p.define: &'a Define` is `Copy`; hoist so the `dots.get` borrow is
// tied to `'a`, not `&*p`, and `&mut self` helpers below can be called
Expand Down Expand Up @@ -1427,9 +1440,9 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
e_.name_loc,
IdentifierOpts::default()
.with_is_call_target(is_call_target)
.with_is_template_tag(is_template_tag)
.with_assign_target(in_.assign_target)
.with_is_delete_target(is_delete_target),
// .is_template_tag = p.template_tag != null,
) {
*e = _expr;
return;
Expand Down
Loading
Loading