Skip to content

Commit f5eeebd

Browse files
committed
fix(ast_macros): raise compile error on invalid generate_derive input. (#4766)
It checks 2 things. 1) The input is a supported derive 2) The given identifier is the same as the fully qualified target trait. The latter makes sure that the trait for derive is included in the scope. Part of #4704 Here's an expanded example of how we assert traits: ```rust const _:() = { { trait AssertionTrait: ::oxc_allocator::CloneIn<'static> {} impl<T: CloneIn<'static>> AssertionTrait for T {} }; }; ``` It makes sure `CloneIn` is the same as `::oxc_allocator::CloneIn` and more importantly requires the user to include the trait if they wish to use it with `generate_derive`. It also provides LSP jump to definition.
1 parent 6708680 commit f5eeebd

5 files changed

Lines changed: 77 additions & 19 deletions

File tree

crates/oxc_ast/src/ast/js.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
use std::cell::Cell;
99

10-
use oxc_allocator::{Box, Vec};
10+
use oxc_allocator::{Box, CloneIn, Vec};
1111
use oxc_ast_macros::ast;
12-
use oxc_span::{Atom, SourceType, Span};
12+
use oxc_span::{Atom, GetSpan, GetSpanMut, SourceType, Span};
1313
use oxc_syntax::{
1414
operator::{
1515
AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator, UpdateOperator,

crates/oxc_ast/src/ast/jsx.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
// Silence erroneous warnings from Rust Analyser for `#[derive(Tsify)]`
88
#![allow(non_snake_case)]
99

10-
use oxc_allocator::{Box, Vec};
10+
use oxc_allocator::{Box, CloneIn, Vec};
1111
use oxc_ast_macros::ast;
12-
use oxc_span::{Atom, Span};
12+
use oxc_span::{Atom, GetSpan, GetSpanMut, Span};
1313
#[cfg(feature = "serialize")]
1414
use serde::Serialize;
1515
#[cfg(feature = "serialize")]

crates/oxc_ast/src/ast/literal.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
use std::hash::Hash;
1111

1212
use bitflags::bitflags;
13+
use oxc_allocator::CloneIn;
1314
use oxc_ast_macros::{ast, CloneIn};
14-
use oxc_span::{Atom, Span};
15+
use oxc_span::{Atom, GetSpan, GetSpanMut, Span};
1516
use oxc_syntax::number::{BigintBase, NumberBase};
1617
#[cfg(feature = "serialize")]
1718
use serde::Serialize;

crates/oxc_ast/src/ast/ts.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
use std::{cell::Cell, hash::Hash};
1414

15-
use oxc_allocator::{Box, Vec};
15+
use oxc_allocator::{Box, CloneIn, Vec};
1616
use oxc_ast_macros::ast;
17-
use oxc_span::{Atom, Span};
17+
use oxc_span::{Atom, GetSpan, GetSpanMut, Span};
1818
use oxc_syntax::scope::ScopeId;
1919
#[cfg(feature = "serialize")]
2020
use serde::Serialize;

crates/oxc_ast_macros/src/lib.rs

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,91 @@ fn enum_repr(enum_: &syn::ItemEnum) -> TokenStream2 {
1212
}
1313
}
1414

15-
/// This attribute serves two purposes,
16-
/// First, it is a marker for our codegen to detect AST types. Furthermore.
17-
/// It is also a lightweight macro; All of its computation is cached and
18-
/// it only applies the following changes without any complex operation:
15+
/// Generate assertions that traits used in `#[generate_derive]` are in scope.
16+
///
17+
/// e.g. for `#[generate_derive(GetSpan)]`, it generates:
18+
///
19+
/// ```rs
20+
/// const _: () = {
21+
/// {
22+
/// trait AssertionTrait: ::oxc_span::GetSpan {}
23+
/// impl<T: GetSpan> AssertionTrait for T {}
24+
/// }
25+
/// };
26+
/// ```
27+
///
28+
/// If `GetSpan` is not in scope, or it is not the correct `oxc_span::GetSpan`,
29+
/// this will raise a compilation error.
30+
fn assert_generated_derives(attrs: &[syn::Attribute]) -> TokenStream2 {
31+
#[inline]
32+
fn parse(attr: &syn::Attribute) -> impl Iterator<Item = syn::Ident> {
33+
attr.parse_args_with(
34+
syn::punctuated::Punctuated::<syn::Ident, syn::token::Comma>::parse_terminated,
35+
)
36+
.expect("`generate_derive` only accepts traits as single segment paths, Found an invalid argument")
37+
.into_iter()
38+
}
39+
40+
// TODO: benchmark this to see if a lazy static cell would perform better.
41+
#[inline]
42+
fn abs_trait(
43+
ident: &syn::Ident,
44+
) -> (/* absolute type path */ TokenStream2, /* possible generics */ TokenStream2) {
45+
if ident == "CloneIn" {
46+
(quote!(::oxc_allocator::CloneIn), quote!(<'static>))
47+
} else if ident == "GetSpan" {
48+
(quote!(::oxc_span::GetSpan), TokenStream2::default())
49+
} else if ident == "GetSpanMut" {
50+
(quote!(::oxc_span::GetSpanMut), TokenStream2::default())
51+
} else {
52+
panic!("Invalid derive trait(generate_derive): {ident}");
53+
}
54+
}
55+
56+
// NOTE: At this level we don't care if a trait is derived multiple times, It is the
57+
// responsibility of the codegen to raise errors for those.
58+
let assertion =
59+
attrs.iter().filter(|attr| attr.path().is_ident("generate_derive")).flat_map(parse).map(
60+
|derive| {
61+
let (abs_derive, generics) = abs_trait(&derive);
62+
quote! {{
63+
// NOTE: these are wrapped in a scope to avoid the need for unique identifiers.
64+
trait AssertionTrait: #abs_derive #generics {}
65+
impl<T: #derive #generics> AssertionTrait for T {}
66+
}}
67+
},
68+
);
69+
quote!(const _: () = { #(#assertion)* };)
70+
}
71+
72+
/// This attribute serves two purposes.
73+
/// First, it is a marker for our codegen to detect AST types.
74+
/// Secondly, it generates the following code:
1975
///
2076
/// * Prepend `#[repr(C)]` to structs
2177
/// * Prepend `#[repr(C, u8)]` to fieldful enums e.g. `enum E { X: u32, Y: u8 }`
2278
/// * Prepend `#[repr(u8)]` to unit (fieldless) enums e.g. `enum E { X, Y, Z, }`
2379
/// * Prepend `#[derive(oxc_ast_macros::Ast)]` to all structs and enums
24-
///
80+
/// * Add assertions that traits used in `#[generate_derive(...)]` are in scope.
2581
#[proc_macro_attribute]
2682
#[allow(clippy::missing_panics_doc)]
2783
pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
2884
let input = syn::parse_macro_input!(input as syn::Item);
2985

30-
let repr = match input {
31-
syn::Item::Enum(ref enum_) => enum_repr(enum_),
32-
syn::Item::Struct(_) => quote!(#[repr(C)]),
33-
34-
_ => {
35-
unreachable!()
86+
let (head, tail) = match &input {
87+
syn::Item::Enum(enum_) => (enum_repr(enum_), assert_generated_derives(&enum_.attrs)),
88+
syn::Item::Struct(struct_) => {
89+
(quote!(#[repr(C)]), assert_generated_derives(&struct_.attrs))
3690
}
91+
92+
_ => unreachable!(),
3793
};
3894

3995
let expanded = quote! {
4096
#[derive(::oxc_ast_macros::Ast)]
41-
#repr
97+
#head
4298
#input
99+
#tail
43100
};
44101
TokenStream::from(expanded)
45102
}

0 commit comments

Comments
 (0)