Skip to content

Move TransformCtx into TraverseCtx #11742

Open
@overlookmotel

Description

@overlookmotel

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 NodeIds 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.

Metadata

Metadata

Assignees

Labels

A-transformerArea - Transformer / Transpiler

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions