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
refactor(traverse): simplified way to get TraverseCtx #3216
refactor(traverse): simplified way to get TraverseCtx #3216
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
CodSpeed Performance ReportMerging #3216 will degrade performances by 3.13%Comparing Summary
Benchmarks breakdown
|
I don't know how to fix these |
2450bad
to
52a307b
Compare
52a307b
to
83f50bc
Compare
It appears that the performance regression is due to the overhead of Rc and RefCell. I'm not sure if it's worth sacrificing 3% to obtain a better DX |
I'm afraid I don't think this is possible. Why you can't do thisThe safety of the scheme depends on making it impossible for an
The thing that prevents you "holding on" to an
So tying the lifetime of the
Some of this is quite subtle. I have added docs a couple of days ago. But probably I should add more docs to explain it more fully. Performance impactIn this PR, you're also introducing an CodSpeed is showing this as a -3% perf regression. 3% doesn't sound like much, but bear in mind that that benchmark time also includes the parser, and the parser is 80% of the time - only ~20% is the transformer itself. So if a change to transformer shows as 3% regression on this benchmark, that's 3% out of 20%. i.e. it means the transformer itself has got 15% slower. That's quite bad. (I'm amazed What we could do insteadI had actually been thinking we could go in the opposite direction - move more stuff that all transforms need into
I already put the allocator and AST builder in Does this make any sense? |
Can you give me an example of this? |
I want to call find_ancestor here. This means that I need to pass TraverseCtx throughout the call stack.
Similar examples are abundant in the typescript plugin. In the typescript plugin. We split the entire plugin transformer into multiple modules. Once we need to use |
Ah I see. And this isn't a very helpful reply, but... sorry, can't do anything about it. If you need to use But... unlike If all individual transforms are struct MyTransform1;
impl<'a> Traverse<'a> for MyTransform1 {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &TraverseCtx<'a>) {
// Do stuff
}
}
struct MyTransform2;
impl<'a> Traverse<'a> for MyTransform2 {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &TraverseCtx<'a>) {
// Do more stuff
}
} Then we can codegen a generic type struct CombinedTraverse<T1: Traverse, T2: Traverse> {
t1: T1,
t2: T2,
}
impl<T1: Traverse, T2: Traverse> CombinedTraverse<T1, T2> {
pub fn new(t1: T1, t2, T2) -> Self {
Self { t1, t2 }
}
}
impl<T1: Traverse, T2: Traverse> CombinedTraverse<T1, T2> {
fn enter_program(&mut self, program: &mut Program<'a>, ctx: &TraverseCtx<'a>) {
self.t1.enter_program(program, ctx);
self.t2.enter_program(program, ctx);
}
// etc - all the enter/exit methods
} Then rather than writing by hand a let transform = CombinedTraverse::new(
TypescriptTransform::new(),
ReactTransform::new(),
);
traverse_mut(&mut transform, program, allocator); or even: let transform = CombinedTraverse::new(
TypescriptTransform::new(),
CombinedTraverse::new(
ReactTransform::new(),
CombinedTraverse::new(
ES6Transform::new(),
ES5Transform::new(),
),
),
);
traverse_mut(&mut transform, program, allocator); Or with a macro to sugar that: let transform = combine_transforms!(
TypescriptTransform::new(),
ReactTransform::new(),
ES6Transform::new(),
ES5Transform::new(),
);
traverse_mut(&mut transform, program, allocator); Can't do anything about needing Does this API appeal to you? NB: What I don't know is whether this would blow up compile times. Maybe it's impractical for that reason. |
Thank you for explaining this. Do we have a runtime check way to avoid a break? #[allow(unsafe_code)]
pub fn traverse_mut<'a, Tr: Traverse<'a>>(traverser: &mut Tr, program: &mut Program<'a>) {
// SAFETY: Walk functions are constructed to avoid unsoundness
// Save an `TraverseCtx` into property a of`self`
unsafe { walk::walk_program(traverser, program as *mut Program) };
debug_assert!(traverser.ctx().ancestors_depth() == 1);
// Remove `TraverseCtx` from self
} Is this possible? I think ease of use is something we have to consider. We have tons of plugins to write. |
Yes, I think it's quite good. It will help us reduce a lot of boilerplate code. But for complex transformations, we still need to abstract into multiple functions (such as |
If you try to make an But this is what the compile failure tests are for - those are the tests which are failing on this PR.
I do see your point, but I don't think it's possible. Let me think about it. Maybe I can come up with something, but it's pretty tricky. The whole scheme is quite finely balanced to give us what we want (ability to access back up the tree) while avoiding the pitfalls of potential UB. I also would like to leave the door open for mutating up, which I think can be possible with this scheme, and could be useful in future. So I would like to avoid blocking that possibility while trying to remove So, yes please leave it with me and I will try to come up with something. But I'm afraid I'm unlikely to get anywhere for probably a week (it took me 3 weeks and 4 attempts to get this far).
Composition can be many levels deep. i.e. any transform can compose multiple other transforms. What I had in mind is that let preset_react_transform = combine_transforms!(
ReactJsx::new(&options),
ReactJsxSelf::new(&options),
ReactJsxSource::new(&options),
ReactDisplayName::new(&options),
); and then the top level of transformer: let all_transform = combine_transforms!(
Typescript::new(&options),
React::new(&options),
// etc...
); i.e. Each transform "plugin" is either a direct implementation of a transform or a composition of multiple other transforms. And each of those other transforms can be a composition of more transforms. You can go as deep as you want to. @Boshen Can you advise if this |
I've implemented |
When we want to use
TraverseCtx
in nested functions, we run into trouble. We need to keep passing theTraverseCtx
around. I've putTraverseCtx
insideTransformerCtx
becauseTransformerCtx
is everywhere.