-
Notifications
You must be signed in to change notification settings - Fork 591
Add printer.NodeFactory
for printer/transformer-specific extensions
#815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the way node creation and utility methods are accessed by replacing direct calls on ast.NodeFactory with a new printer.NodeFactory that encapsulates printer/transformer–specific extensions. The key changes include:
- Migrating node creation methods (e.g. NewAssignmentExpression, NewUniqueName) from ast.NodeFactory to printer.NodeFactory.
- Updating call sites across several transformer and printer files to use the new printer.NodeFactory API.
- Removing legacy utility functions from EmitContext to reduce duplication of functionality.
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
File | Description |
---|---|
internal/transformers/utilities.go | Removal of unused name utility functions and update of factory types. |
internal/transformers/transformer.go | Refactor of factory field type from ast.NodeFactory to printer.NodeFactory. |
internal/transformers/runtimesyntax.go | Update of node creation calls (e.g. assignment, logical operators) to use the new printer.NodeFactory. |
internal/transformers/esmodule.go | Refactored usages of EmitContext methods to rely on printer.NodeFactory APIs. |
internal/transformers/commonjsmodule.go | Consistent migration of factory calls, aligning with the new API. |
internal/scanner/scanner.go | Updated parameter types to use NodeFactoryCoercible. |
internal/printer/* | Updated test and helper methods to use the new factory methods. |
internal/printer/emitcontext.go | Removal of EmitContext utility methods now replaced by NodeFactory. |
Comments suppressed due to low confidence (2)
internal/transformers/runtimesyntax.go:1609
- Ensure that NewCommaExpression preserves the evaluation order and semantics of the comma operator as intended by the previous use of NewBinaryExpression with a comma token.
expression = tx.factory.NewCommaExpression(expression, node.Operand.Clone(tx.factory))
internal/printer/emitcontext.go:338
- Verify that the removal of EmitContext utility methods (for prologues and name generation) is fully covered by tests and that all call sites now correctly use the new NodeFactory methods.
// (removed utility functions for enforcing prologues and generating names)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the NodeFactoryCoercible
pattern required here to move the methods off of EmitContext
? My mental model had been that transforms already run with a "context" (the EmitContext
), so always have a reference to something (e.g. CommonJSModuleTransformer
has Transformer
which has EmitContext
) and therefore don't need to have an additional way to plumb information through. I would think that you could still embed NodeFactory
in a struct for convenience methods, but then have that on the emit context.
It's a convenience and helps to better organize related functionality. This approach offers both consistency and convenience. Emit-related factory methods move to a
A reference to something that is growing haphazardly as we continue to stuff more "factory"-like functionality on what is just supposed to hold a bunch of side tables.
We already do, but spelling out factory.createIdentifier("...");
factory.createTempIdentifier(...);
factory.getLocalName(...);
// etc. but in Corsa you have to write tx.factory.NewIdentifier("...")
tx.emitContext.NewTempIdentifier(...)
getLocalName(tx.emitContext, ...) so you need to reach to multiple places to do the same thing. The main value of |
I guess I can understand it, I'm just wary of introducing dynamic dispatch for convenience only if we can be a little more verbose but stay concrete / static. I would personally be fine doing address-of-embedded or adding |
IMO, it would be nice if Go could automatically handle address-of-embedded for arguments at call sites since it should have enough information to do so with static analysis, given it already does so for method calls. The ~99% use case for calling |
…go into printer-nodefactory
It can, but IIRC only if the function is inlined, which then means the interface will be devirtualized to a concrete type and then be static. Locally, I took this PR and removed the But, if you feel strongly that this is going to be more convenient... It's not a big deal and is very easy to test later. Eventually we'll have emit benchmarks to see either way. The other big places that |
Declaration emit, diagnostics, and the LS all rely on the same emitter in any place they would use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want to block #819, so LGTM, but I still don't know if I like the interface idea and once we have emit benchmarks, I think it'd be worth double checking.
FWIW, I agree with @jakebailey on the interface indirection. It's one of those aspects that makes our old codebase so hard to comprehend and navigate. I really want us to avoid unnecessary abstraction. |
The
NodeFactory
in Strada has a large number of utility methods that are useful for transforms but are otherwise unnecessary for parse. In Corsa, we've either been adding these methods as loose top-level utility functions, or adding them toEmitContext
, which pollutesEmitContext
with unrelated methods. Both alternatives make it more cumbersome to port other transforms as you must look in multiple places to find methods missing fromfactory
when porting.To address this, I've added
printer.NodeFactory
which embedsast.NodeFactory
and extends it with these additional methods. To avoid additional complexity when invoking methods likeast.Node.Clone(factory)
, I've also added anast.NodeFactoryCoercible
interface that can be used to accept either anast.NodeFactory
orprinter.NodeFactory
, without the need to litter call sites withnode.Clone(&tx.factory.Factory)
. Theast.NodeFactoryCoercible
interface avoids needing to introduce an interface that covers the whole ofast.NodeFactory
that would need to be maintained in parallel. The.AsNodeFactory()
method also emulates the.AsNode()
method we generally prefer to use when working withNode
subtypes over the use of&node.Node
.