mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
feat(mangler): deduplicate private accessor names (#25601)
## Summary
Fix private-name mangling for `get #x()` / `set #x(value)` accessor pairs.
JavaScript treats an accessor pair as one private name, but the mangler previously assigned a candidate per element before collecting into a name-keyed map. The setter overwrote the getter’s mapping, while still consuming an extra candidate.
### Before
```js
class Outer {
get #x() {}
set #x(value) {}
#longName;
nested() {
return class {
#nestedName;
};
}
}
```
```js
class Outer {
get #t() {}
set #t(e) {}
#n;
nested() {
return class {
#r;
};
}
}
```
### After
```js
class Outer {
get #e() {}
set #e(e) {}
#t;
nested() {
return class {
#n;
};
}
}
```
## Implementation
- Deduplicate private names during map construction with `HashMap::entry`.
- Derive nested-class offsets from already-built lexical parent mappings, preserving collision avoidance.
- Pre-size mappings from the number of private elements.
This commit is contained in:
1 parent
e82495ba54
commit
4922141d0a
4 files changed
+86
-59
No files matched your search
@@ -1,10 +1,13 @@
|
||||
use std::iter::{self, repeat_with};
|
||||
use std::{
|
||||
collections::hash_map::Entry,
|
||||
iter::{self, repeat_with},
|
||||
};
|
||||
|
||||
use itertools::Itertools;
|
||||
use keep_names::collect_name_symbols;
|
||||
use oxc_index::IndexVec;
|
||||
use oxc_syntax::class::ClassId;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||
|
||||
use base54::base54;
|
||||
use oxc_allocator::{Allocator, ArenaHashSet, ArenaVec, BitSet};
|
||||
@@ -414,57 +417,44 @@ impl<'t> Mangler<'t> {
|
||||
) -> IndexVec<ClassId, FxHashMap<String, CompactStr>> {
|
||||
let classes = semantic.classes();
|
||||
|
||||
let private_member_count: IndexVec<ClassId, usize> = classes
|
||||
.elements
|
||||
.iter()
|
||||
.map(|class_elements| {
|
||||
class_elements.iter().filter(|element| element.is_private).count()
|
||||
})
|
||||
.collect();
|
||||
let parent_private_member_count: IndexVec<ClassId, usize> = classes
|
||||
.declarations
|
||||
.iter_enumerated()
|
||||
.map(|(class_id, _)| {
|
||||
classes
|
||||
.ancestors(class_id)
|
||||
.skip(1)
|
||||
.map(|id| private_member_count[id])
|
||||
.sum::<usize>()
|
||||
})
|
||||
.collect();
|
||||
let mut class_private_mappings: IndexVec<ClassId, FxHashMap<String, CompactStr>> =
|
||||
IndexVec::with_capacity(classes.len());
|
||||
for (class_id, class_elements) in classes.elements.iter_enumerated() {
|
||||
// Nested classes are declared after their lexical parents, so their mappings have
|
||||
// already been collected.
|
||||
let parent_private_member_count = classes
|
||||
.ancestors(class_id)
|
||||
.skip(1)
|
||||
.map(|id| class_private_mappings[id].len())
|
||||
.sum::<usize>();
|
||||
assert!(
|
||||
u32::try_from(class_elements.len() + parent_private_member_count).is_ok(),
|
||||
"too many class elements"
|
||||
);
|
||||
|
||||
classes
|
||||
.elements
|
||||
.iter_enumerated()
|
||||
.map(|(class_id, class_elements)| {
|
||||
let parent_private_member_count = parent_private_member_count[class_id];
|
||||
assert!(
|
||||
u32::try_from(class_elements.len() + parent_private_member_count).is_ok(),
|
||||
"too many class elements"
|
||||
let private_member_count =
|
||||
class_elements.iter().filter(|element| element.is_private).count();
|
||||
let mut mappings =
|
||||
FxHashMap::with_capacity_and_hasher(private_member_count, FxBuildHasher);
|
||||
for element in class_elements.iter().filter(|element| element.is_private) {
|
||||
let next_index = mappings.len();
|
||||
let Entry::Vacant(entry) = mappings.entry(element.name.to_string()) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
#[expect(clippy::cast_possible_truncation, reason = "checked above with assert")]
|
||||
let mangled = CompactStr::new(
|
||||
// Avoid reusing the same mangled name in parent classes.
|
||||
// We can improve this by reusing names that are not used in child classes,
|
||||
// but nesting a class inside another class is not common
|
||||
// and that would require liveness analysis.
|
||||
base54((parent_private_member_count + next_index) as u32).as_str(),
|
||||
);
|
||||
class_elements
|
||||
.iter()
|
||||
.filter_map(|element| {
|
||||
if element.is_private { Some(element.name.to_string()) } else { None }
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, name)| {
|
||||
#[expect(
|
||||
clippy::cast_possible_truncation,
|
||||
reason = "checked above with assert"
|
||||
)]
|
||||
let mangled = CompactStr::new(
|
||||
// Avoid reusing the same mangled name in parent classes.
|
||||
// We can improve this by reusing names that are not used in child classes,
|
||||
// but nesting a class inside another class is not common
|
||||
// and that would require liveness analysis.
|
||||
base54((parent_private_member_count + i) as u32).as_str(),
|
||||
);
|
||||
(name, mangled)
|
||||
})
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
})
|
||||
.collect()
|
||||
entry.insert(mangled);
|
||||
}
|
||||
class_private_mappings.push(mappings);
|
||||
}
|
||||
class_private_mappings
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,6 +195,9 @@ fn private_member_mangling() {
|
||||
"class Foo { #method() { return 1; } publicMethod() { return this.#method(); } }",
|
||||
"class Foo { #field; #method() { return this.#field; } get() { return this.#method(); } }",
|
||||
"class Foo { #x; check() { return #x in this; } }",
|
||||
"class Outer { get #x() {} set #x(value) {} #longName; nested() { return class { #nestedName; }; } }",
|
||||
"class Outer { get #x() {} #longName; nested() { return class { #nestedName; }; } }",
|
||||
"class Outer { set #x(value) {} #longName; nested() { return class { #nestedName; }; } }",
|
||||
// Nested classes
|
||||
"class Outer { #outerField = 1; inner() { return class Inner { #innerField = 2; get() { return this.#innerField; } }; } }",
|
||||
"class Outer { #shared = 1; getInner() { let self = this; return class { method() { return self.#shared; } }; } }",
|
||||
|
||||
@@ -47,6 +47,40 @@ class Foo {
|
||||
}
|
||||
}
|
||||
|
||||
class Outer { get #x() {} set #x(value) {} #longName; nested() { return class { #nestedName; }; } }
|
||||
class Outer {
|
||||
get #e() {}
|
||||
set #e(e) {}
|
||||
#t;
|
||||
nested() {
|
||||
return class {
|
||||
#n;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Outer { get #x() {} #longName; nested() { return class { #nestedName; }; } }
|
||||
class Outer {
|
||||
get #e() {}
|
||||
#t;
|
||||
nested() {
|
||||
return class {
|
||||
#n;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Outer { set #x(value) {} #longName; nested() { return class { #nestedName; }; } }
|
||||
class Outer {
|
||||
set #e(e) {}
|
||||
#t;
|
||||
nested() {
|
||||
return class {
|
||||
#n;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Outer { #outerField = 1; inner() { return class Inner { #innerField = 2; get() { return this.#innerField; } }; } }
|
||||
class Outer {
|
||||
#e = 1;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
checker.ts:
|
||||
file size: 2922154 # 2.92 MB
|
||||
sys allocs: 152
|
||||
sys allocs: 150
|
||||
sys reallocs: 5
|
||||
sys deallocs: 152
|
||||
sys deallocs: 150
|
||||
sys alloc bytes: 14996600 # 15.00 MB
|
||||
sys peak growth: 10169560 # 10.17 MB
|
||||
arena allocs: 148428
|
||||
@@ -11,9 +11,9 @@ checker.ts:
|
||||
|
||||
App.tsx:
|
||||
file size: 415342 # 415.34 kB
|
||||
sys allocs: 63
|
||||
sys allocs: 61
|
||||
sys reallocs: 8
|
||||
sys deallocs: 63
|
||||
sys deallocs: 61
|
||||
sys alloc bytes: 2128555 # 2.13 MB
|
||||
sys peak growth: 1612427 # 1.61 MB
|
||||
arena allocs: 16046
|
||||
@@ -33,9 +33,9 @@ RadixUIAdoptionSection.jsx:
|
||||
|
||||
pdf.mjs:
|
||||
file size: 567296 # 567.30 kB
|
||||
sys allocs: 2467
|
||||
sys allocs: 2427
|
||||
sys reallocs: 205
|
||||
sys deallocs: 2467
|
||||
sys deallocs: 2427
|
||||
sys alloc bytes: 5334247 # 5.33 MB
|
||||
sys peak growth: 3693355 # 3.69 MB
|
||||
arena allocs: 45881
|
||||
@@ -66,9 +66,9 @@ binder.ts:
|
||||
|
||||
kitchen-sink.tsx:
|
||||
file size: 732898 # 732.90 kB
|
||||
sys allocs: 1858
|
||||
sys allocs: 1854
|
||||
sys reallocs: 174
|
||||
sys deallocs: 1858
|
||||
sys deallocs: 1854
|
||||
sys alloc bytes: 5307512 # 5.31 MB
|
||||
sys peak growth: 3707425 # 3.71 MB
|
||||
arena allocs: 66771
|
||||
|
||||
Reference in new issue
Block a user