mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
refactor(traverse): indicate scope entry point with scope(enter_before) attr (#3882)
Improve annotation of AST types for codegen.
Currently:
```rs
#[visited_node(
scope(ScopeFlags::empty()),
enter_scope_before(cases),
)]
pub struct SwitchStatement<'a> {
pub span: Span,
pub discriminant: Expression<'a>,
pub cases: Vec<'a, SwitchCase<'a>>,
pub scope_id: Cell<Option<ScopeId>>,
}
```
After this PR:
```rs
#[visited_node(scope(ScopeFlags::empty()))]
pub struct SwitchStatement<'a> {
pub span: Span,
pub discriminant: Expression<'a>,
#[scope(enter_before)]
pub cases: Vec<'a, SwitchCase<'a>>,
pub scope_id: Cell<Option<ScopeId>>,
}
```
I think this is easier to read.
In order to enable use of `#[scope]` attr, this introduces a dummy `VisitedNode` derive macro. Like the `visited_node` macro, `VisitedNode` derive macro is designed to do very minimal work and have no heavy dependencies, so it should be almost 0 cost in terms of compile time.
This commit is contained in:
parent
24979c98b2
commit
fcd21a6a75
4 changed files with 37 additions and 9 deletions
|
|
@ -1228,7 +1228,7 @@ pub struct WithStatement<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch Statement
|
/// Switch Statement
|
||||||
#[visited_node(scope(ScopeFlags::empty()), enter_scope_before(cases))]
|
#[visited_node(scope(ScopeFlags::empty()))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
||||||
#[cfg_attr(feature = "serialize", serde(tag = "type"))]
|
#[cfg_attr(feature = "serialize", serde(tag = "type"))]
|
||||||
|
|
@ -1236,6 +1236,7 @@ pub struct SwitchStatement<'a> {
|
||||||
#[cfg_attr(feature = "serialize", serde(flatten))]
|
#[cfg_attr(feature = "serialize", serde(flatten))]
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub discriminant: Expression<'a>,
|
pub discriminant: Expression<'a>,
|
||||||
|
#[scope(enter_before)]
|
||||||
pub cases: Vec<'a, SwitchCase<'a>>,
|
pub cases: Vec<'a, SwitchCase<'a>>,
|
||||||
pub scope_id: Cell<Option<ScopeId>>,
|
pub scope_id: Cell<Option<ScopeId>>,
|
||||||
}
|
}
|
||||||
|
|
@ -1566,7 +1567,7 @@ pub struct YieldExpression<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Class Definitions
|
/// Class Definitions
|
||||||
#[visited_node(scope(ScopeFlags::StrictMode), enter_scope_before(id))]
|
#[visited_node(scope(ScopeFlags::StrictMode))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
||||||
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
|
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
|
||||||
|
|
@ -1575,6 +1576,7 @@ pub struct Class<'a> {
|
||||||
#[cfg_attr(feature = "serialize", serde(flatten))]
|
#[cfg_attr(feature = "serialize", serde(flatten))]
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub decorators: Vec<'a, Decorator<'a>>,
|
pub decorators: Vec<'a, Decorator<'a>>,
|
||||||
|
#[scope(enter_before)]
|
||||||
pub id: Option<BindingIdentifier<'a>>,
|
pub id: Option<BindingIdentifier<'a>>,
|
||||||
pub super_class: Option<Expression<'a>>,
|
pub super_class: Option<Expression<'a>>,
|
||||||
pub body: Box<'a, ClassBody<'a>>,
|
pub body: Box<'a, ClassBody<'a>>,
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ pub struct TSThisParameter<'a> {
|
||||||
/// Enum Declaration
|
/// Enum Declaration
|
||||||
///
|
///
|
||||||
/// `const_opt` enum `BindingIdentifier` { `EnumBody_opt` }
|
/// `const_opt` enum `BindingIdentifier` { `EnumBody_opt` }
|
||||||
#[visited_node(scope(ScopeFlags::empty()), enter_scope_before(members))]
|
#[visited_node(scope(ScopeFlags::empty()))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
#[cfg_attr(feature = "serialize", derive(Serialize, Tsify))]
|
||||||
#[cfg_attr(feature = "serialize", serde(tag = "type"))]
|
#[cfg_attr(feature = "serialize", serde(tag = "type"))]
|
||||||
|
|
@ -54,6 +54,7 @@ pub struct TSEnumDeclaration<'a> {
|
||||||
#[cfg_attr(feature = "serialize", serde(flatten))]
|
#[cfg_attr(feature = "serialize", serde(flatten))]
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub id: BindingIdentifier<'a>,
|
pub id: BindingIdentifier<'a>,
|
||||||
|
#[scope(enter_before)]
|
||||||
pub members: Vec<'a, TSEnumMember<'a>>,
|
pub members: Vec<'a, TSEnumMember<'a>>,
|
||||||
pub r#const: bool,
|
pub r#const: bool,
|
||||||
pub declare: bool,
|
pub declare: bool,
|
||||||
|
|
@ -784,7 +785,6 @@ pub enum TSTypePredicateName<'a> {
|
||||||
|
|
||||||
#[visited_node(
|
#[visited_node(
|
||||||
scope(ScopeFlags::TsModuleBlock),
|
scope(ScopeFlags::TsModuleBlock),
|
||||||
enter_scope_before(body),
|
|
||||||
strict_if(self.body.as_ref().is_some_and(|body| body.is_strict())),
|
strict_if(self.body.as_ref().is_some_and(|body| body.is_strict())),
|
||||||
)]
|
)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -794,6 +794,7 @@ pub struct TSModuleDeclaration<'a> {
|
||||||
#[cfg_attr(feature = "serialize", serde(flatten))]
|
#[cfg_attr(feature = "serialize", serde(flatten))]
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub id: TSModuleDeclarationName<'a>,
|
pub id: TSModuleDeclarationName<'a>,
|
||||||
|
#[scope(enter_before)]
|
||||||
pub body: Option<TSModuleDeclarationBody<'a>>,
|
pub body: Option<TSModuleDeclarationBody<'a>>,
|
||||||
/// The keyword used to define this module declaration
|
/// The keyword used to define this module declaration
|
||||||
/// ```text
|
/// ```text
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,28 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
/// Attach to AST node type (struct or enum), to signal to codegen to create visitor for this type.
|
/// Attach to AST node type (struct or enum), to signal to codegen to create visitor for this type.
|
||||||
/// Macro itself does nothing - just passes through the token stream unchanged.
|
///
|
||||||
|
/// Macro does not generate any code - it's purely a means to communicate information to the codegen.
|
||||||
|
///
|
||||||
|
/// Only thing macro does is add `#[derive(VisitedNode)]` to the item.
|
||||||
|
/// Deriving `VisitedNode` does nothing, but supports the `#[scope]` attr on struct fields.
|
||||||
|
/// This is a workaround for Rust not supporting helper attributes for `proc_macro_attribute` macros,
|
||||||
|
/// so we need to use a derive macro to get that support.
|
||||||
|
///
|
||||||
|
/// Use native Rust `TokenStream`, to avoid dependency on slow-compiling crates like `syn` and `quote`.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn visited_node(_args: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn visited_node(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
input
|
let mut stream = TokenStream::from_str("#[derive(::oxc_ast_macros::VisitedNode)]").unwrap();
|
||||||
|
stream.extend(input);
|
||||||
|
stream
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dummy derive macro for a non-existent trait `VisitedNode`.
|
||||||
|
///
|
||||||
|
/// Does not generate any code, only purpose is to allow using `#[scope]` attr in the type def.
|
||||||
|
#[proc_macro_derive(VisitedNode, attributes(scope))]
|
||||||
|
pub fn visited_node_derive(_item: TokenStream) -> TokenStream {
|
||||||
|
TokenStream::new()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,11 @@ function parseFile(code, filename, types) {
|
||||||
function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex) {
|
function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex) {
|
||||||
const fields = [];
|
const fields = [];
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
let line = lines[i];
|
||||||
if (line.startsWith('#[')) {
|
const isScopeEntry = line === '#[scope(enter_before)]';
|
||||||
|
if (isScopeEntry) {
|
||||||
|
line = lines[++i];
|
||||||
|
} else if (line.startsWith('#[')) {
|
||||||
while (!lines[i].endsWith(']')) {
|
while (!lines[i].endsWith(']')) {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +89,8 @@ function parseStruct(name, rawName, lines, scopeArgs, filename, startLineIndex)
|
||||||
{name: innerTypeName, wrappers} = typeAndWrappers(typeName);
|
{name: innerTypeName, wrappers} = typeAndWrappers(typeName);
|
||||||
|
|
||||||
fields.push({name, typeName, rawName, rawTypeName, innerTypeName, wrappers});
|
fields.push({name, typeName, rawName, rawTypeName, innerTypeName, wrappers});
|
||||||
|
|
||||||
|
if (isScopeEntry) scopeArgs.enterScopeBefore = name;
|
||||||
}
|
}
|
||||||
return {kind: 'struct', name, rawName, fields, scopeArgs};
|
return {kind: 'struct', name, rawName, fields, scopeArgs};
|
||||||
}
|
}
|
||||||
|
|
@ -128,7 +133,7 @@ function parseScopeArgs(argsStr, filename, lineIndex) {
|
||||||
while (true) {
|
while (true) {
|
||||||
const [key] = matchAndConsume(/^([a-z_]+)\(/);
|
const [key] = matchAndConsume(/^([a-z_]+)\(/);
|
||||||
assert(
|
assert(
|
||||||
['scope', 'scope_if', 'strict_if', 'enter_scope_before'].includes(key),
|
['scope', 'scope_if', 'strict_if'].includes(key),
|
||||||
`Unexpected visited_node macro arg: ${key}`
|
`Unexpected visited_node macro arg: ${key}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue