From 5b714759127ca3c095fd6de6183d5953ca5b25a0 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Fri, 30 Aug 2024 20:46:32 -0400 Subject: [PATCH 1/2] rename GetBindingIdentifier to WithBindingIdentifier --- .github/.generated_ast_watch_list.yml | 1 + crates/oxc_ast/src/ast/js.rs | 12 + crates/oxc_ast/src/ast/ts.rs | 5 + crates/oxc_ast/src/ast_impl/js.rs | 19 +- .../src/generated/derive_symbol_traits.rs | 210 ++++++++++++++++++ crates/oxc_ast/src/lib.rs | 4 + .../src/traits/get_binding_identifier.rs | 8 + crates/oxc_ast/src/traits/mod.rs | 3 + crates/oxc_ast_macros/src/lib.rs | 11 +- .../src/generators/derive_symbol_traits.rs | 94 ++++++++ tasks/ast_tools/src/generators/mod.rs | 18 ++ tasks/ast_tools/src/main.rs | 5 +- tasks/ast_tools/src/markers.rs | 116 +++++++++- tasks/ast_tools/src/schema/defs.rs | 24 +- tasks/ast_tools/src/schema/mod.rs | 6 +- 15 files changed, 529 insertions(+), 7 deletions(-) create mode 100644 crates/oxc_ast/src/generated/derive_symbol_traits.rs create mode 100644 crates/oxc_ast/src/traits/get_binding_identifier.rs create mode 100644 crates/oxc_ast/src/traits/mod.rs create mode 100644 tasks/ast_tools/src/generators/derive_symbol_traits.rs diff --git a/.github/.generated_ast_watch_list.yml b/.github/.generated_ast_watch_list.yml index ae30f7c9855ec..be0bbd6e08ed6 100644 --- a/.github/.generated_ast_watch_list.yml +++ b/.github/.generated_ast_watch_list.yml @@ -16,6 +16,7 @@ src: - 'crates/oxc_ast/src/generated/derive_clone_in.rs' - 'crates/oxc_ast/src/generated/derive_get_span.rs' - 'crates/oxc_ast/src/generated/derive_get_span_mut.rs' + - 'crates/oxc_ast/src/generated/derive_symbol_traits.rs' - 'crates/oxc_ast/src/generated/visit.rs' - 'crates/oxc_ast/src/generated/visit_mut.rs' - 'tasks/ast_codegen/src/**' diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index ac0670df51458..bb863205a6ae1 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1261,6 +1261,7 @@ pub struct VariableDeclarator<'a> { pub span: Span, #[serde(skip)] pub kind: VariableDeclarationKind, + #[symbol(binding, recurse)] pub id: BindingPattern<'a>, pub init: Option>, pub definite: bool, @@ -1563,6 +1564,7 @@ pub struct CatchClause<'a> { pub struct CatchParameter<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding, recurse)] pub pattern: BindingPattern<'a>, } @@ -1589,6 +1591,7 @@ pub struct BindingPattern<'a> { #[serde(flatten)] #[tsify(type = "(BindingIdentifier | ObjectPattern | ArrayPattern | AssignmentPattern)")] #[span] + #[symbol(binding, recurse)] pub kind: BindingPatternKind<'a>, pub type_annotation: Option>>, pub optional: bool, @@ -1621,6 +1624,7 @@ pub enum BindingPatternKind<'a> { pub struct AssignmentPattern<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding, recurse)] pub left: BindingPattern<'a>, pub right: Expression<'a>, } @@ -1649,6 +1653,7 @@ pub struct BindingProperty<'a> { #[serde(flatten)] pub span: Span, pub key: PropertyKey<'a>, + #[symbol(binding, recurse)] pub value: BindingPattern<'a>, pub shorthand: bool, pub computed: bool, @@ -1677,6 +1682,7 @@ pub struct ArrayPattern<'a> { pub struct BindingRestElement<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding, recurse)] pub argument: BindingPattern<'a>, } @@ -1695,6 +1701,7 @@ pub struct Function<'a> { pub r#type: FunctionType, #[serde(flatten)] pub span: Span, + #[symbol(binding, optional)] pub id: Option>, pub generator: bool, pub r#async: bool, @@ -1762,6 +1769,7 @@ pub struct FormalParameter<'a> { #[serde(flatten)] pub span: Span, pub decorators: Vec<'a, Decorator<'a>>, + #[symbol(binding, recurse)] pub pattern: BindingPattern<'a>, pub accessibility: Option, pub readonly: bool, @@ -1858,6 +1866,7 @@ pub struct Class<'a> { /// ``` pub decorators: Vec<'a, Decorator<'a>>, /// Class identifier, AKA the name + #[symbol(binding, optional)] pub id: Option>, #[scope(enter_before)] pub type_parameters: Option>>, @@ -2355,6 +2364,7 @@ pub struct ImportSpecifier<'a> { /// import { Foo as Bar } from 'foo'; /// // ^^^ /// ``` + #[symbol] pub local: BindingIdentifier<'a>, pub import_kind: ImportOrExportKind, } @@ -2375,6 +2385,7 @@ pub struct ImportDefaultSpecifier<'a> { #[serde(flatten)] pub span: Span, /// The name of the imported symbol. + #[symbol] pub local: BindingIdentifier<'a>, } @@ -2392,6 +2403,7 @@ pub struct ImportDefaultSpecifier<'a> { pub struct ImportNamespaceSpecifier<'a> { #[serde(flatten)] pub span: Span, + #[symbol] pub local: BindingIdentifier<'a>, } diff --git a/crates/oxc_ast/src/ast/ts.rs b/crates/oxc_ast/src/ast/ts.rs index 28c24478e7d23..24454c5898525 100644 --- a/crates/oxc_ast/src/ast/ts.rs +++ b/crates/oxc_ast/src/ast/ts.rs @@ -71,6 +71,7 @@ pub struct TSThisParameter<'a> { pub struct TSEnumDeclaration<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding)] pub id: BindingIdentifier<'a>, #[scope(enter_before)] pub members: Vec<'a, TSEnumMember<'a>>, @@ -728,6 +729,7 @@ pub struct TSTypeParameterInstantiation<'a> { pub struct TSTypeParameter<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding)] pub name: BindingIdentifier<'a>, pub constraint: Option>, pub default: Option>, @@ -756,6 +758,7 @@ pub struct TSTypeParameterDeclaration<'a> { pub struct TSTypeAliasDeclaration<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding)] pub id: BindingIdentifier<'a>, #[scope(enter_before)] pub type_parameters: Option>>, @@ -802,6 +805,7 @@ pub struct TSInterfaceDeclaration<'a> { #[serde(flatten)] pub span: Span, /// The identifier (name) of the interface. + #[symbol(binding)] pub id: BindingIdentifier<'a>, #[scope(enter_before)] pub extends: Option>>, @@ -1287,6 +1291,7 @@ pub struct TSTypeAssertion<'a> { pub struct TSImportEqualsDeclaration<'a> { #[serde(flatten)] pub span: Span, + #[symbol(binding)] pub id: BindingIdentifier<'a>, pub module_reference: TSModuleReference<'a>, pub import_kind: ImportOrExportKind, diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index e08c99fffc87b..aae16ebbcc0a1 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -1,4 +1,4 @@ -use crate::ast::*; +use crate::{ast::*, WithBindingIdentifier}; use std::{borrow::Cow, cell::Cell, fmt, hash::Hash}; @@ -999,6 +999,23 @@ impl<'a> BindingPatternKind<'a> { } } +impl<'a> WithBindingIdentifier<'a> for BindingPatternKind<'a> { + fn symbol_id(&self) -> Option { + match self { + Self::BindingIdentifier(ident) => ident.symbol_id.get(), + Self::AssignmentPattern(assign) => assign.left.kind.symbol_id(), + _ => None, + } + } + fn name(&self) -> Option> { + match self { + Self::BindingIdentifier(ident) => Some(ident.name.clone()), + Self::AssignmentPattern(assign) => assign.left.kind.name(), + _ => None, + } + } +} + impl<'a> ObjectPattern<'a> { pub fn is_empty(&self) -> bool { self.properties.is_empty() && self.rest.is_none() diff --git a/crates/oxc_ast/src/generated/derive_symbol_traits.rs b/crates/oxc_ast/src/generated/derive_symbol_traits.rs new file mode 100644 index 0000000000000..df6ca17baf2e7 --- /dev/null +++ b/crates/oxc_ast/src/generated/derive_symbol_traits.rs @@ -0,0 +1,210 @@ +use oxc_syntax::symbol::SymbolId; + +#[allow(clippy::wildcard_imports)] +use crate::ast::*; +use crate::WithBindingIdentifier; +use oxc_span::Atom; + +impl<'a> WithBindingIdentifier<'a> for VariableDeclarator<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.id.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for CatchParameter<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.pattern.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.pattern.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for BindingPattern<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.kind.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.kind.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for AssignmentPattern<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.left.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.left.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for BindingProperty<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.value.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.value.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for BindingRestElement<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.argument.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.argument.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for Function<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.as_ref().and_then(|id| id.symbol_id.get()) + } + + #[inline] + fn name(&self) -> Option> { + self.id.as_ref().map(|id| id.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for FormalParameter<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.pattern.symbol_id() + } + + #[inline] + fn name(&self) -> Option> { + self.pattern.name() + } +} + +impl<'a> WithBindingIdentifier<'a> for Class<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.as_ref().and_then(|id| id.symbol_id.get()) + } + + #[inline] + fn name(&self) -> Option> { + self.id.as_ref().map(|id| id.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for ImportSpecifier<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.local.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.local.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for ImportDefaultSpecifier<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.local.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.local.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for ImportNamespaceSpecifier<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.local.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.local.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for TSEnumDeclaration<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.id.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for TSTypeParameter<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.name.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.name.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for TSTypeAliasDeclaration<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.id.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for TSInterfaceDeclaration<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.id.name.clone()) + } +} + +impl<'a> WithBindingIdentifier<'a> for TSImportEqualsDeclaration<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id.symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.id.name.clone()) + } +} diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index 8ed15f752c5f9..8c973f5343877 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -34,8 +34,11 @@ mod ast_impl; mod ast_kind_impl; pub mod precedence; pub mod syntax_directed_operations; +mod traits; mod trivia; +pub use traits::*; + mod generated { #[cfg(debug_assertions)] pub mod assert_layouts; @@ -44,6 +47,7 @@ mod generated { pub mod derive_clone_in; pub mod derive_get_span; pub mod derive_get_span_mut; + pub mod derive_symbol_traits; pub mod visit; pub mod visit_mut; } diff --git a/crates/oxc_ast/src/traits/get_binding_identifier.rs b/crates/oxc_ast/src/traits/get_binding_identifier.rs new file mode 100644 index 0000000000000..90eb0f7ae5ae4 --- /dev/null +++ b/crates/oxc_ast/src/traits/get_binding_identifier.rs @@ -0,0 +1,8 @@ +use oxc_span::Atom; +use oxc_syntax::symbol::SymbolId; + +pub trait WithBindingIdentifier<'a> { + fn symbol_id(&self) -> Option; + + fn name(&self) -> Option>; +} diff --git a/crates/oxc_ast/src/traits/mod.rs b/crates/oxc_ast/src/traits/mod.rs new file mode 100644 index 0000000000000..c939e7c6486e4 --- /dev/null +++ b/crates/oxc_ast/src/traits/mod.rs @@ -0,0 +1,3 @@ +mod get_binding_identifier; + +pub use get_binding_identifier::WithBindingIdentifier; diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index 6a711592cf435..d21af0107e868 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -163,7 +163,10 @@ pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream { /// The only purpose is to allow the occurrence of helper attributes used with the `tasks/ast_tools`. /// /// Read [`macro@ast`] for further details. -#[proc_macro_derive(Ast, attributes(scope, visit, span, generate_derive, clone_in, serde, tsify))] +#[proc_macro_derive( + Ast, + attributes(scope, symbol, visit, span, generate_derive, clone_in, serde, tsify) +)] pub fn ast_derive(_item: TokenStream) -> TokenStream { TokenStream::new() } @@ -196,3 +199,9 @@ pub fn derive_clone_in(item: TokenStream) -> TokenStream { _ => panic!("At the moment `CloneIn` derive macro only works for types without lifetimes and/or generic params"), } } + +// #[proc_macro_attribute] +// pub fn symbol(args: TokenStream, input: TokenStream, ) -> TokenStream { +// // TokenStream::new() +// input +// } diff --git a/tasks/ast_tools/src/generators/derive_symbol_traits.rs b/tasks/ast_tools/src/generators/derive_symbol_traits.rs new file mode 100644 index 0000000000000..4d0c8d6ba5382 --- /dev/null +++ b/tasks/ast_tools/src/generators/derive_symbol_traits.rs @@ -0,0 +1,94 @@ +use proc_macro2::TokenStream; + +use crate::{ + codegen::LateCtx, + markers::{SymbolBinding, SymbolMarkers}, + output, + schema::{FieldDef, GetIdent, StructDef, TypeDef}, +}; +use quote::quote; + +use super::{define_generator, Generator, GeneratorOutput}; + +define_generator! { + pub struct DeriveSymbolTraitsGenerator; +} + +impl Generator for DeriveSymbolTraitsGenerator { + fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput { + let impls: Vec<_> = ctx + .schema() + .into_iter() + .filter_map(TypeDef::as_struct) + .filter_map(|struct_def| { + struct_def.fields.iter().find_map(|field| { + field + .markers + .binding + .as_ref() + .and_then(SymbolMarkers::as_binding) + .map(|binding| (struct_def, field, binding)) + }) + }) + .map(generate_symbol_id_impl) + .collect(); + + let stream = quote! { + use oxc_syntax::symbol::SymbolId; + + ///@@line_break + #[allow(clippy::wildcard_imports)] + use crate::ast::*; + use crate::WithBindingIdentifier; + use oxc_span::Atom; + + ///@@line_break + #(#impls)* + }; + + GeneratorOutput::Stream((output(crate::AST_CRATE, "derive_symbol_traits.rs"), stream)) + } +} + +fn generate_symbol_id_impl( + (def, field, binding): (&StructDef, &FieldDef, &SymbolBinding), +) -> TokenStream { + let struct_name = &def.ident(); + let field_name = field.ident().unwrap(); + let (symbol_id_impl, name_impl) = if binding.optional { + if binding.recurse { + ( + quote! { self.#field_name.as_ref().and_then(WithBindingIdentifier::symbol_id) }, + quote! { self.#field_name.as_ref().map(WithBindingIdentifier::name) }, + ) + } else { + ( + quote! { self.#field_name.as_ref().and_then(|id| id.symbol_id.get()) }, + quote! { self.#field_name.as_ref().map(|id| id.name.clone()) }, + ) + } + } else if binding.recurse { + (quote! { self.#field_name.symbol_id() }, quote! { self.#field_name.name() }) + } else { + ( + quote! { self.#field_name.symbol_id.get() }, + quote! { Some(self.#field_name.name.clone()) }, + ) + }; + + quote! { + ///@@line_break + impl<'a> WithBindingIdentifier<'a> for #struct_name<'a> { + #[inline] + fn symbol_id(&self) -> Option { + #symbol_id_impl + } + + ///@@line_break + #[inline] + fn name(&self) -> Option> { + #name_impl + } + } + } +} diff --git a/tasks/ast_tools/src/generators/mod.rs b/tasks/ast_tools/src/generators/mod.rs index d4fb22db0e701..4d8ac32268cbc 100644 --- a/tasks/ast_tools/src/generators/mod.rs +++ b/tasks/ast_tools/src/generators/mod.rs @@ -9,6 +9,7 @@ mod ast_builder; mod ast_kind; mod derive_clone_in; mod derive_get_span; +mod derive_symbol_traits; mod visit; pub use assert_layouts::AssertLayouts; @@ -16,6 +17,7 @@ pub use ast_builder::AstBuilderGenerator; pub use ast_kind::AstKindGenerator; pub use derive_clone_in::DeriveCloneIn; pub use derive_get_span::{DeriveGetSpan, DeriveGetSpanMut}; +pub use derive_symbol_traits::DeriveSymbolTraitsGenerator; pub use visit::{VisitGenerator, VisitMutGenerator}; /// Inserts a newline in the `TokenStream`. @@ -103,6 +105,22 @@ impl GeneratorOutput { } } +/// Define a [`Generator`] struct. +/// +/// # Example +/// ```ignore +/// use ast_tools::codegen::LateCtx; +/// use ast_tools::generators::{define_generator, Generator, GeneratorOutput}; +/// +/// define_generator! { +/// pub struct MyGenerator; +/// } +/// impl Generator for MyGenerator { +/// fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput { +/// // generate code here +/// } +/// } +/// ``` macro_rules! define_generator { ($vis:vis struct $ident:ident $($lifetime:lifetime)? $($rest:tt)*) => { $vis struct $ident $($lifetime)? $($rest)* diff --git a/tasks/ast_tools/src/main.rs b/tasks/ast_tools/src/main.rs index de0c9aa857e35..e3826d470be6c 100644 --- a/tasks/ast_tools/src/main.rs +++ b/tasks/ast_tools/src/main.rs @@ -18,8 +18,8 @@ mod util; use fmt::{cargo_fmt, pretty_print}; use generators::{ AssertLayouts, AstBuilderGenerator, AstKindGenerator, DeriveCloneIn, DeriveGetSpan, - DeriveGetSpanMut, GeneratedDataStream, GeneratedTokenStream, Generator, GeneratorOutput, - VisitGenerator, VisitMutGenerator, + DeriveGetSpanMut, DeriveSymbolTraitsGenerator, GeneratedDataStream, GeneratedTokenStream, + Generator, GeneratorOutput, VisitGenerator, VisitMutGenerator, }; use passes::{CalcLayout, Linker}; use util::{write_all_to, NormalizeError}; @@ -66,6 +66,7 @@ fn main() -> std::result::Result<(), Box> { .gen(DeriveCloneIn) .gen(DeriveGetSpan) .gen(DeriveGetSpanMut) + .gen(DeriveSymbolTraitsGenerator) .gen(VisitGenerator) .gen(VisitMutGenerator) .generate()?; diff --git a/tasks/ast_tools/src/markers.rs b/tasks/ast_tools/src/markers.rs index 5252df1ff63a2..531c7f5e81fe8 100644 --- a/tasks/ast_tools/src/markers.rs +++ b/tasks/ast_tools/src/markers.rs @@ -1,4 +1,4 @@ -use proc_macro2::TokenStream; +use proc_macro2::{TokenStream, TokenTree}; use serde::Serialize; use syn::{ ext::IdentExt, @@ -66,6 +66,45 @@ pub struct ScopeMarkers { pub enter_before: bool, } +#[derive(Debug)] +pub enum SymbolMarkers { + Binding(SymbolBinding), + Reference(SymbolReference), +} + +#[derive(Default, Debug, Clone)] +pub struct SymbolBinding { + /// `true` if it's possible for the `BindingIdentifier` to not exist (e.g. + /// the field has type `Option>`). + pub optional: bool, + /// `recurse` option was set, indicating that trait implementations should + /// call methods on the binding-marked property. + pub recurse: bool, +} + +#[derive(Default, Debug, Clone)] +pub struct SymbolReference; + +impl Default for SymbolMarkers { + fn default() -> Self { + Self::Binding(SymbolBinding::default()) + } +} +impl SymbolMarkers { + pub fn as_binding(&self) -> Option<&SymbolBinding> { + match self { + Self::Binding(binding) => Some(binding), + Self::Reference(_) => None, + } + } + pub fn as_reference(&self) -> Option<&SymbolReference> { + match self { + Self::Reference(reference) => Some(reference), + Self::Binding(_) => None, + } + } +} + /// A struct representing all the helper attributes that might be used with `#[generate_derive(...)]` #[derive(Debug, Default, Serialize)] pub struct DeriveAttributes { @@ -182,6 +221,81 @@ where ) } +/// Parses a `#[symbol]` field attribute. +/// +/// Valid forms are: +/// - `#[binding]`: For mandatory bindings. Uses [`SymbolMarkers::default`]. +/// - `#[binding(optional = value)]`: explicitly specifies whether the +/// `BindingIdentifier` stored in this field is [optional](`Option`). +pub fn get_symbol_markers<'a, I>(attrs: I) -> crate::Result> +where + I: IntoIterator, +{ + let Some(symbol) = attrs.into_iter().find(|attr| attr.path().is_ident("symbol")) else { + return Ok(None); + }; + match &symbol.meta { + Meta::Path(_) => Ok(Some(SymbolMarkers::default())), + Meta::List(list) => { + debug_assert!(list.path.is_ident("symbol")); + let options = &list.tokens; + if options.is_empty() { + return Ok(Some(SymbolMarkers::default())); + } + + let mut toks = options.clone().into_iter(); + + let TokenTree::Ident(kind) = toks.next().unwrap() else { + return Err("Invalid `#[symbol(...)]` attribute: first option must be `binding` or `reference`.".into()); + }; + + // fine if there's no token, b/c #[symbol(binding)] is valid + if let Some(tok) = toks.next() { + if !matches!(tok, TokenTree::Punct(_)) { + return Err("Expected a ',' token".to_string()); + } + } + + match kind.to_string().as_str() { + "binding" => syn::parse2(toks.collect()).map(|binding| Some(SymbolMarkers::Binding(binding))).map_err(|e| format!("{e}")), + "reference" => Err("#[symbol(reference)] is not implemented yet.".into()), + _ => Err("Invalid `#[symbol(...)]` attribute: first option must be `binding` or `reference`.".into()), + } + } + Meta::NameValue(_) => Err( + "`#[symbol = ...]` is not allowed. Use `#[symbol]` or `#[symbol(...options)]` instead." + .into(), + ), + } +} +impl Parse for SymbolBinding { + fn parse(input: ParseStream) -> syn::Result { + if input.is_empty() { + return Ok(Self::default()); + } + + let mut symbol = SymbolBinding::default(); + let idents = input.parse_terminated(Ident::parse, Token![,])?; + for ident in idents { + match ident.to_string().as_str() { + "optional" => { + symbol.optional = true; + } + "recurse" => { + symbol.recurse = true; + } + _ => { + return Err(syn::Error::new( + ident.span(), + "Invalid `#[symbol(binding(...))]` input: unknown option.", + )); + } + } + } + Ok(symbol) + } +} + pub fn get_scope_markers<'a, I>(attrs: I) -> crate::Result where I: IntoIterator, diff --git a/tasks/ast_tools/src/schema/defs.rs b/tasks/ast_tools/src/schema/defs.rs index cfa9bc1695f9a..763d89d089ae9 100644 --- a/tasks/ast_tools/src/schema/defs.rs +++ b/tasks/ast_tools/src/schema/defs.rs @@ -1,7 +1,7 @@ use serde::Serialize; use crate::{ - markers::{DeriveAttributes, ScopeAttribute, ScopeMarkers, VisitMarkers}, + markers::{DeriveAttributes, ScopeAttribute, ScopeMarkers, SymbolMarkers, VisitMarkers}, util::{ToIdent, TypeAnalysis, TypeWrapper}, TypeId, }; @@ -16,6 +16,20 @@ pub enum TypeDef { } impl TypeDef { + pub fn as_struct(&self) -> Option<&StructDef> { + match self { + Self::Struct(it) => Some(it), + Self::Enum(_) => None, + } + } + + pub fn as_enum(&self) -> Option<&EnumDef> { + match self { + Self::Enum(it) => Some(it), + Self::Struct(_) => None, + } + } + pub fn id(&self) -> TypeId { with_either!(self, it => it.id) } @@ -24,6 +38,7 @@ impl TypeDef { with_either!(self, it => &it.name) } + /// `true` for AST nodes marked with a `#[visit]` attribute. pub fn visitable(&self) -> bool { with_either!(self, it => it.visitable) } @@ -223,6 +238,10 @@ pub struct OuterMarkers { pub scope: Option, } +/// Attributes that are used on struct fields and enum variants. Struct fields +/// are the most common case. +/// +/// Attributes applied to the entire struct or enum are stored in [`OuterMarkers`]. #[derive(Debug, Serialize)] pub struct InnerMarkers { /// marker that hints to fold span in here @@ -232,4 +251,7 @@ pub struct InnerMarkers { pub visit: VisitMarkers, #[serde(skip)] pub scope: ScopeMarkers, + /// `#[binding]` attribute. Only allowed on struct fields. + #[serde(skip)] + pub binding: Option, } diff --git a/tasks/ast_tools/src/schema/mod.rs b/tasks/ast_tools/src/schema/mod.rs index bb8791fb4c2b3..6f453eb51d720 100644 --- a/tasks/ast_tools/src/schema/mod.rs +++ b/tasks/ast_tools/src/schema/mod.rs @@ -4,7 +4,10 @@ use serde::Serialize; use crate::{ codegen, layout::KnownLayout, - markers::{get_derive_attributes, get_scope_attribute, get_scope_markers, get_visit_markers}, + markers::{ + get_derive_attributes, get_scope_attribute, get_scope_markers, get_symbol_markers, + get_visit_markers, + }, rust_ast as rust, util::{unexpanded_macro_err, TypeExt}, Result, TypeId, @@ -111,6 +114,7 @@ fn parse_inner_markers(attrs: &Vec) -> Result { span: attrs.iter().any(|a| a.path().is_ident("span")), visit: get_visit_markers(attrs)?, scope: get_scope_markers(attrs)?, + binding: get_symbol_markers(attrs)?, derive_attributes: get_derive_attributes(attrs)?, }) } From cda09d460d87af1156da476cab5d51fc50c97820 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Fri, 30 Aug 2024 23:35:15 -0400 Subject: [PATCH 2/2] add impls for Declaration and ImportDeclarationSpecifier --- crates/oxc_ast/src/ast/js.rs | 4 +- crates/oxc_ast/src/ast_impl/js.rs | 42 ++++++++++++++++++- .../src/traits/get_binding_identifier.rs | 20 +++++++++ crates/oxc_ast_macros/src/lib.rs | 33 ++++++++++++--- .../src/generators/derive_symbol_traits.rs | 4 +- tasks/ast_tools/src/markers.rs | 6 --- tasks/ast_tools/src/util.rs | 7 +++- 7 files changed, 98 insertions(+), 18 deletions(-) diff --git a/crates/oxc_ast/src/ast/js.rs b/crates/oxc_ast/src/ast/js.rs index bb863205a6ae1..a697f8a45a84c 100644 --- a/crates/oxc_ast/src/ast/js.rs +++ b/crates/oxc_ast/src/ast/js.rs @@ -1701,7 +1701,7 @@ pub struct Function<'a> { pub r#type: FunctionType, #[serde(flatten)] pub span: Span, - #[symbol(binding, optional)] + #[symbol] pub id: Option>, pub generator: bool, pub r#async: bool, @@ -1866,7 +1866,7 @@ pub struct Class<'a> { /// ``` pub decorators: Vec<'a, Decorator<'a>>, /// Class identifier, AKA the name - #[symbol(binding, optional)] + #[symbol] pub id: Option>, #[scope(enter_before)] pub type_parameters: Option>>, diff --git a/crates/oxc_ast/src/ast_impl/js.rs b/crates/oxc_ast/src/ast_impl/js.rs index aae16ebbcc0a1..86f051a9bdcda 100644 --- a/crates/oxc_ast/src/ast_impl/js.rs +++ b/crates/oxc_ast/src/ast_impl/js.rs @@ -4,7 +4,9 @@ use std::{borrow::Cow, cell::Cell, fmt, hash::Hash}; use oxc_allocator::{Box, FromIn, Vec}; use oxc_span::{Atom, GetSpan, SourceType, Span}; -use oxc_syntax::{operator::UnaryOperator, reference::ReferenceId, scope::ScopeFlags}; +use oxc_syntax::{ + operator::UnaryOperator, reference::ReferenceId, scope::ScopeFlags, symbol::SymbolId, +}; #[cfg(feature = "serialize")] #[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] @@ -363,6 +365,18 @@ impl<'a> BindingIdentifier<'a> { } } +impl<'a> WithBindingIdentifier<'a> for BindingIdentifier<'a> { + #[inline] + fn name(&self) -> Option> { + Some(self.name.clone()) + } + + #[inline] + fn symbol_id(&self) -> Option { + self.symbol_id.get() + } +} + impl<'a> fmt::Display for BindingIdentifier<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -793,6 +807,18 @@ impl<'a> Declaration<'a> { } } +impl<'a> WithBindingIdentifier<'a> for Declaration<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.id().and_then(BindingIdentifier::symbol_id) + } + + #[inline] + fn name(&self) -> Option> { + self.id().and_then(BindingIdentifier::name) + } +} + impl<'a> VariableDeclaration<'a> { pub fn is_typescript_syntax(&self) -> bool { self.declare @@ -1000,7 +1026,7 @@ impl<'a> BindingPatternKind<'a> { } impl<'a> WithBindingIdentifier<'a> for BindingPatternKind<'a> { - fn symbol_id(&self) -> Option { + fn symbol_id(&self) -> Option { match self { Self::BindingIdentifier(ident) => ident.symbol_id.get(), Self::AssignmentPattern(assign) => assign.left.kind.symbol_id(), @@ -1530,6 +1556,18 @@ impl<'a> ImportDeclarationSpecifier<'a> { } } +impl<'a> WithBindingIdentifier<'a> for ImportDeclarationSpecifier<'a> { + #[inline] + fn symbol_id(&self) -> Option { + self.local().symbol_id.get() + } + + #[inline] + fn name(&self) -> Option> { + Some(self.local().name.clone()) + } +} + impl<'a> ImportAttributeKey<'a> { pub fn as_atom(&self) -> Atom<'a> { match self { diff --git a/crates/oxc_ast/src/traits/get_binding_identifier.rs b/crates/oxc_ast/src/traits/get_binding_identifier.rs index 90eb0f7ae5ae4..b248a9708560f 100644 --- a/crates/oxc_ast/src/traits/get_binding_identifier.rs +++ b/crates/oxc_ast/src/traits/get_binding_identifier.rs @@ -1,8 +1,28 @@ use oxc_span::Atom; use oxc_syntax::symbol::SymbolId; +/// Trait for accessing a [`BindingIdentifier`](`crate::ast::BindingIdentifier`) +/// within an AST Node. Most often used on nodes that create symbol bindings. pub trait WithBindingIdentifier<'a> { + /// Get the [`SymbolId`] bound to this AST node. + /// + /// Will return [`None`] if: + /// 1. The AST node does not create a symbol binding, such as anonymous + /// function expressions, + /// 2. Semantic analysis has been skipped, or + /// 3. The symbol binding is within a destructuring pattern and cannot be uniquely + /// identified. fn symbol_id(&self) -> Option; + /// Get the identifier name of the symbol this AST node binds. + /// + /// Will return [`None`] if: + /// 1. The AST node does not create a symbol binding, such as anonymous + /// function expressions, + /// 2. The symbol binding is within a destructuring pattern and cannot be uniquely + /// identified. + /// + /// Note that identifier names are determined at parse time, so this method + /// is unaffected by semantic analysis. fn name(&self) -> Option>; } diff --git a/crates/oxc_ast_macros/src/lib.rs b/crates/oxc_ast_macros/src/lib.rs index d21af0107e868..65c862738fd77 100644 --- a/crates/oxc_ast_macros/src/lib.rs +++ b/crates/oxc_ast_macros/src/lib.rs @@ -119,6 +119,33 @@ fn assert_generated_derives(attrs: &[syn::Attribute]) -> TokenStream2 { /// However, Instead of expanding the derive at compile-time, We do this process on PR submits via `ast_tools` code generation. /// These derived implementations would be output in the `crates/oxc_ast/src/generated` directory. /// +/// ## `#[symbol]` +/// +/// This attribute hints that a field stores an identifier binding or reference. +/// It gets used to derive several useful traits for the entire node struct. +/// +/// There are two variants of this attribute: +/// 1. `#[symbol(binding[, ...opts])]` - hints that this field contains a +/// `BindingIdentifier`. You may also use `#[symbol]` as a shorthand for this. +/// 2. `#[symbol(reference[, ...opts])]` - hints that this field contains an +/// `IdentifierReference`. **NOTE that this is not yet implemented.** +/// +/// ### Example +/// ```ignore +/// // simplified Function node +/// struct Function<'a> { +/// #[symbol] +/// id: Option>, +/// body: Option> +/// } +/// ``` +/// +/// ### `#[symbol(binding[, ...opts])]` Options +/// The `binding` variant contains the following options. All options are +/// optional flags that can be combined. +/// - `recurse`: the field is a recursive binding, i.e. this field stores a +/// struct that itself has a field marked as `#[symbol(binding)]`. +/// /// # Derive Helper Attributes: /// /// These are helper attributes that are only meaningful when their respective trait is derived via `generate_derive`. @@ -199,9 +226,3 @@ pub fn derive_clone_in(item: TokenStream) -> TokenStream { _ => panic!("At the moment `CloneIn` derive macro only works for types without lifetimes and/or generic params"), } } - -// #[proc_macro_attribute] -// pub fn symbol(args: TokenStream, input: TokenStream, ) -> TokenStream { -// // TokenStream::new() -// input -// } diff --git a/tasks/ast_tools/src/generators/derive_symbol_traits.rs b/tasks/ast_tools/src/generators/derive_symbol_traits.rs index 4d0c8d6ba5382..826b0bbf5fb71 100644 --- a/tasks/ast_tools/src/generators/derive_symbol_traits.rs +++ b/tasks/ast_tools/src/generators/derive_symbol_traits.rs @@ -55,7 +55,9 @@ fn generate_symbol_id_impl( ) -> TokenStream { let struct_name = &def.ident(); let field_name = field.ident().unwrap(); - let (symbol_id_impl, name_impl) = if binding.optional { + let analysis = field.typ.analysis(); + let is_optional = analysis.is_optional(); + let (symbol_id_impl, name_impl) = if is_optional { if binding.recurse { ( quote! { self.#field_name.as_ref().and_then(WithBindingIdentifier::symbol_id) }, diff --git a/tasks/ast_tools/src/markers.rs b/tasks/ast_tools/src/markers.rs index 531c7f5e81fe8..07884b035556b 100644 --- a/tasks/ast_tools/src/markers.rs +++ b/tasks/ast_tools/src/markers.rs @@ -74,9 +74,6 @@ pub enum SymbolMarkers { #[derive(Default, Debug, Clone)] pub struct SymbolBinding { - /// `true` if it's possible for the `BindingIdentifier` to not exist (e.g. - /// the field has type `Option>`). - pub optional: bool, /// `recurse` option was set, indicating that trait implementations should /// call methods on the binding-marked property. pub recurse: bool, @@ -278,9 +275,6 @@ impl Parse for SymbolBinding { let idents = input.parse_terminated(Ident::parse, Token![,])?; for ident in idents { match ident.to_string().as_str() { - "optional" => { - symbol.optional = true; - } "recurse" => { symbol.recurse = true; } diff --git a/tasks/ast_tools/src/util.rs b/tasks/ast_tools/src/util.rs index 52e48353e9fc8..59ff934250ed7 100644 --- a/tasks/ast_tools/src/util.rs +++ b/tasks/ast_tools/src/util.rs @@ -118,7 +118,7 @@ impl<'a> TypeIdentResult<'a> { // TODO: remove me #[allow(dead_code)] -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize)] pub enum TypeWrapper { None, Box, @@ -141,6 +141,11 @@ pub struct TypeAnalysis { #[serde(skip)] pub typ: Type, } +impl TypeAnalysis { + pub fn is_optional(&self) -> bool { + matches!(self.wrapper, TypeWrapper::Opt | TypeWrapper::OptBox | TypeWrapper::OptVec) + } +} impl TypeExt for Type { fn get_ident(&self) -> TypeIdentResult {