Skip to content
62 changes: 43 additions & 19 deletions crates/rolldown/src/utils/renamer.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
use std::borrow::Cow;
use std::collections::hash_map::Entry;

use oxc::semantic::ScopeId;
use oxc::syntax::keyword::{GLOBAL_OBJECTS, RESERVED_KEYWORDS};
use rolldown_common::{NormalModule, NormalModuleId, NormalModuleVec, SymbolRef};
use rolldown_rstr::{Rstr, ToRstr};
use rolldown_utils::rayon::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::FxHashMap;

use crate::types::symbols::Symbols;

#[derive(Debug)]
pub struct Renamer<'name> {
used_canonical_names: FxHashSet<Cow<'name, Rstr>>,
/// key is the original name,
/// value is the how many same variable name in the top level are used before
/// It is also used to calculate the candidate_name e.g.
/// ```js
/// // index.js
/// import {a as b} from './a.js'
/// const a = 1; // {a => 0}
/// const a$1 = 1000; // {a => 0, a$1 => 0}
///
///
/// // a.js
/// export const a = 100; // {a => 0, a$1 => 0}, first we try looking up `a`, it is used. So we try the
/// // candidate_name `a$1`(conflict_index + 1 = 1). Then we try `a$2`, so
/// // on and so forth. Until we find a name that is not used. In this case, `a$2` is not used
/// // so we rename `a` to `a$2`
/// ```
///
used_canonical_names: FxHashMap<Cow<'name, Rstr>, u32>,
canonical_names: FxHashMap<SymbolRef, Rstr>,
symbols: &'name Symbols,
}
Expand All @@ -24,32 +42,38 @@ impl<'name> Renamer<'name> {
used_canonical_names: RESERVED_KEYWORDS
.iter()
.chain(GLOBAL_OBJECTS.iter())
.map(|s| Cow::Owned(Rstr::new(s)))
.map(|s| (Cow::Owned(Rstr::new(s)), 0))
.collect(),
}
}

pub fn reserve(&mut self, name: Cow<'name, Rstr>) {
self.used_canonical_names.insert(name);
self.used_canonical_names.insert(name, 0);
}

pub fn add_top_level_symbol(&mut self, symbol_ref: SymbolRef) {
let canonical_ref = self.symbols.par_canonical_ref_for(symbol_ref);
let original_name: Cow<'_, Rstr> =
Cow::Owned(self.symbols.get_original_name(canonical_ref).to_rstr());

match self.canonical_names.entry(canonical_ref) {
std::collections::hash_map::Entry::Vacant(vacant) => {
let mut count = 0;
Entry::Vacant(vacant) => {
let mut candidate_name = original_name.clone();
while self.used_canonical_names.contains(&candidate_name) {
count += 1;
candidate_name = Cow::Owned(format!("{original_name}${count}").into());
loop {
match self.used_canonical_names.entry(candidate_name.clone()) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
match self.used_canonical_names.entry(candidate_name.clone()) {
match self.used_canonical_names.entry(original_name.clone()) {

Should you use original_name name here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to find a fixed point

@hyfdev hyfdev Jun 28, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's is another a, in the current algorithm, will it be renamed to a$1$1$1? If so, I would like to see if we could fix this, it's kind of ugly in the output.

If we don't care the how output looks, we could follow how parcel did which is renaming the vars to a meaningless hashed value. This way it should be more faster.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entry::Occupied(mut occ) => {
let next_conflict_index = *occ.get() + 1;
*occ.get_mut() = next_conflict_index;
candidate_name = Cow::Owned(format!("{original_name}${next_conflict_index}",).into());
}
Entry::Vacant(vac) => {
vac.insert(0);
break;
}
}
}
self.used_canonical_names.insert(candidate_name.clone());
vacant.insert(candidate_name.into_owned());
}
std::collections::hash_map::Entry::Occupied(_) => {
Entry::Occupied(_) => {
// The symbol is already renamed
}
}
Expand All @@ -66,34 +90,34 @@ impl<'name> Renamer<'name> {
fn rename_symbols_of_nested_scopes<'name>(
module: &'name NormalModule,
scope_id: ScopeId,
stack: &mut Vec<Cow<FxHashSet<Cow<'name, Rstr>>>>,
stack: &mut Vec<Cow<FxHashMap<Cow<'name, Rstr>, u32>>>,
canonical_names: &mut FxHashMap<SymbolRef, Rstr>,
) {
let bindings = module.scope.get_bindings(scope_id);
let mut used_canonical_names_for_this_scope = FxHashSet::default();
let mut used_canonical_names_for_this_scope = FxHashMap::default();
used_canonical_names_for_this_scope.shrink_to(bindings.len());
bindings.iter().for_each(|(binding_name, symbol_id)| {
used_canonical_names_for_this_scope.insert(Cow::Owned(binding_name.to_rstr()));
used_canonical_names_for_this_scope.insert(Cow::Owned(binding_name.to_rstr()), 0);
let binding_ref: SymbolRef = (module.id, *symbol_id).into();

let mut count = 1;
let mut candidate_name = Cow::Owned(binding_name.to_rstr());
match canonical_names.entry(binding_ref) {
std::collections::hash_map::Entry::Vacant(slot) => loop {
Entry::Vacant(slot) => loop {
let is_shadowed = stack
.iter()
.any(|used_canonical_names| used_canonical_names.contains(&candidate_name));
.any(|used_canonical_names| used_canonical_names.contains_key(&candidate_name));

if is_shadowed {
candidate_name = Cow::Owned(format!("{binding_name}${count}").into());
count += 1;
} else {
used_canonical_names_for_this_scope.insert(candidate_name.clone());
used_canonical_names_for_this_scope.insert(candidate_name.clone(), 0);
slot.insert(candidate_name.into_owned());
break;
}
},
std::collections::hash_map::Entry::Occupied(_) => {
Entry::Occupied(_) => {
// The symbol is already renamed
}
}
Expand Down