dillon1000/react

Commit

[rust] Custom deserialization for more precise errors

The overall goal of this workstream is to have a Rust representation of ESTree 
that we can use as the input and output of the compiler. In Rust environments we 
can convert between the native AST of SWC or OXC and ESTree, and when invoked 
from JavaScript we can serialize to/from ESTree-compliant JSON. Given that our 
first target is to plug into a JS-based compilation toolchain, we need to have a 
working serialization to/from ESTree JSON. The point of the codegen-based 
`estree` crate is to allow us to model estree as ergonomic, idiomatic Rust (to 
make consuming it in code easier) while also allowing us to serialize to/from 
spec-compliant ESTree. This PR flushes out one remaining piece. 

Updates our estree codegen to generate a custom `Deserialize` implementation 
instead of using the derived one from serde. ESTree has some enums whose 
variants are themselves enums, for example we have 

```rust 

enum ModuleItem { 

ImportExportDeclaration(ImportExportDeclaration), 

Statement(Statement), 

} 

enum ImportExportDeclaration { ... } 

enum Statement { ... } 

``` 

This sort of works with serde's derive implementation: you have to use the "tag" 
representation for the inner enums, and an "untagged" representation for the 
outer one (ModuleItem). The problem is that with an untagged representation, 
serde doesn't know what type of data it's expecting. All it can do is go one by 
one and try to parse the data as the first variant (eg ImportExportDeclaration) 
then the next one (Statement) and fail when it gets to the end of the list. If 
the data isn't valid for any reason, deserialization will fail with a "not a 
valid ModuleItem" error. That's true but not helpful, especially if you're 
developing estree, are confident that the input json is valid, and need to 
figure out where you messed up the definition. It's also not helpful as an 
end-user if you're not sure your input json is valid. 

So this PR updates our codegen to emit a custom derive implementation that is 
identical for both regular enums (like Statement) and recursive ones (like 
ModuleItem). We first extract the tag to know what type the value is, then 
deserialize exactly as that type. So in the above case, rather than have to 
first try parsing every ModuleItem as an ImportExportDeclaration and then fall 
through to statement, we just decode the tag (`type` in our case, for example 
say it's an "ForStatement"), then deserialize directly as that type (eg, as 
ForStatement), then wrap it in the enum variant (ModuleItem::Statement(...)). 
For recursive enums like ModuleItem we add an extra wrapper as necessary. 

The end result is that we get much more precise errors and deserialization is 
more efficient: we always decode just the tag, then as exactly that type. 

Note that our serialization is also not perfect right now, because we don't 
always emit the `type` key. Serde only emits it when a value appears in an enum. 
We can similarly generate custom serializers for all our types to always emit 
the tag. That will be straightforward when it's necessary. The current PR was 
more of a blocker, because it was really hard to figure out mistakes in the 
estree definition given the ambiguous errors. Thanks to this PR we now get 
precise errors along the lines of "unknown type `JSXElement`" which are easy to 
resolve.
Browse files
Changed paths2 files
First-parent comparison
M compiler/forget/crates/estree-codegen/src/codegen.rs ModifiedM compiler/forget/crates/estree/src/generated.rs Modified
Patch

Files changed

Rendering syntax-highlighted changes…