Description
The problem
Currently we have TransformCtx
which contains shared state between all transforms. It's stored in various transforms' self
as a reference &'ctx TransformCtx<'a>
.
Because transforms only have an immutable reference to TransformCtx
, all mutable state has to be wrapped in a RefCell
. e.g. the shared stack VarDeclarations
introduced in #6170. RefCell
is somewhat costly.
Proposed solution
Instead, move TransformCtx
to live within TraverseCtx
. TraverseCtx
is a singleton and is passed to every visitor method in all transforms as a mutable &mut TraverseCtx<'a>
. So there only needs to be one instance of TransformCtx
, and we can get rid of the 'ctx
lifetime too.
But we want to avoid leaking the transformer's internals into Traverse
, as Traverse
is meant to be a tool which can be used for other purposes too (e.g. minifier).
How?
I propose that we parameterize TraverseCtx
so it can hold arbitrary state:
pub trait Traverse<'a, State> {
fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a, State>) {}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a, State>) {}
// etc...
}
pub struct TraverseCtx<'a, State> {
pub ancestry: TraverseAncestry<'a>,
pub scoping: TraverseScoping,
pub ast: AstBuilder<'a>,
pub state: State,
}
// The new name for `TransformCtx`
struct TransformState<'a> {
errors: Vec<OxcDiagnostic>,
pub trivias: Trivias,
var_declarations: SparseStack<Vec<'a, VariableDeclarator<'a>>>,
// ... etc ...
}
type TransCtx<'a> = TraverseCtx<'a, TransformState<'a>>;
struct Common;
impl<'a> Traverse<'a, TransformState<'a>> for Common {
fn enter_statements(&mut self, _stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TransCtx<'a>) {
ctx.state.var_declarations.push(None);
}
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TransCtx<'a>) {
if let Some(declarations) = ctx.state.var_declarations.pop() {
// Insert `var` declaration statement
}
}
}
Preconditions
We currently store AstBuilder
in both TransformCtx
and TraverseCtx
. We will need switch all uses of the copy in TransformCtx
over to TraverseCtx
i.e. self.ctx.ast.null_literal(SPAN)
-> ctx.ast.null_literal(SPAN)
.
We can then remove AstBuilder
from TransformCtx
.
We will need to do this anyway once we have NodeId
s on all AST nodes, and need AstBuilder
to be stateful to maintain a counter for next Node ID - which requires there to be only 1 copy of AstBuilder
in the transformer.