Skip to content

Commit caa651c

Browse files
committed
refactor(ast): #[content_eq(skip)] attr (#8875)
Introduce custom attribute `#[content_eq(skip)]` to guide code generation for `ContentEq` trait. Instead of a hard-coded list of types to skip in codegen, use this attribute on types to control the behavior.
1 parent f40f494 commit caa651c

File tree

10 files changed

+101
-23
lines changed

10 files changed

+101
-23
lines changed

crates/oxc_ast_macros/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
4747
/// Its only purpose is to allow the occurrence of helper attributes used in `tasks/ast_tools`.
4848
///
4949
/// See [`macro@ast`] for further details.
50-
#[proc_macro_derive(Ast, attributes(clone_in, estree, generate_derive, scope, span, ts, visit))]
50+
#[proc_macro_derive(
51+
Ast,
52+
attributes(clone_in, content_eq, estree, generate_derive, scope, span, ts, visit)
53+
)]
5154
pub fn ast_derive(_input: TokenStream) -> TokenStream {
5255
TokenStream::new()
5356
}

crates/oxc_span/src/span/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ use super::PointerAlign;
6161
#[ast(visit)]
6262
#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)]
6363
#[generate_derive(ESTree)]
64+
#[content_eq(skip)]
6465
#[estree(no_type, flatten)]
6566
pub struct Span {
6667
/// The zero-based start offset of the span

crates/oxc_syntax/src/reference.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
#![allow(missing_docs)] // fixme
22
use bitflags::bitflags;
33
use nonmax::NonMaxU32;
4-
use oxc_allocator::CloneIn;
54
use oxc_index::Idx;
65
#[cfg(feature = "serialize")]
76
use serde::{Serialize, Serializer};
87

8+
use oxc_allocator::CloneIn;
9+
910
use crate::{node::NodeId, symbol::SymbolId};
1011

1112
use oxc_ast_macros::ast;
1213

1314
#[ast]
1415
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
16+
#[content_eq(skip)]
1517
pub struct ReferenceId(NonMaxU32);
1618

1719
impl Idx for ReferenceId {

crates/oxc_syntax/src/scope.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use oxc_ast_macros::ast;
99

1010
#[ast]
1111
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
12+
#[content_eq(skip)]
1213
pub struct ScopeId(NonMaxU32);
1314

1415
impl ScopeId {

crates/oxc_syntax/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use oxc_ast_macros::ast;
99

1010
#[ast]
1111
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
12+
#[content_eq(skip)]
1213
pub struct SymbolId(NonMaxU32);
1314

1415
impl SymbolId {

tasks/ast_tools/src/derives/content_eq.rs

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
use proc_macro2::TokenStream;
44
use quote::{format_ident, quote};
55

6-
use crate::schema::{Def, EnumDef, Schema, StructDef};
6+
use crate::{
7+
schema::{Def, EnumDef, Schema, StructDef, TypeDef},
8+
Result,
9+
};
710

8-
use super::{define_derive, Derive, StructOrEnum};
9-
10-
const IGNORE_FIELD_TYPES: [&str; 4] = ["Span", "ScopeId", "SymbolId", "ReferenceId"];
11+
use super::{
12+
attr_positions, define_derive, AttrLocation, AttrPart, AttrPositions, Derive, StructOrEnum,
13+
};
1114

1215
/// Derive for `ContentEq` trait.
1316
pub struct DeriveContentEq;
@@ -23,6 +26,31 @@ impl Derive for DeriveContentEq {
2326
"oxc_span"
2427
}
2528

29+
/// Register that accept `#[content_eq]` attr on structs, enums, or struct fields.
30+
/// Allow attr on structs and enums which don't derive this trait.
31+
fn attrs(&self) -> &[(&'static str, AttrPositions)] {
32+
&[("content_eq", attr_positions!(StructMaybeDerived | EnumMaybeDerived | StructField))]
33+
}
34+
35+
/// Parse `#[content_eq(skip)]` attr.
36+
fn parse_attr(&self, _attr_name: &str, location: AttrLocation, part: AttrPart) -> Result<()> {
37+
// No need to check attr name is `content_eq`, because that's the only attribute this derive handles.
38+
if !matches!(part, AttrPart::Tag("skip")) {
39+
return Err(());
40+
}
41+
42+
match location {
43+
AttrLocation::Struct(struct_def) => struct_def.content_eq.skip = true,
44+
AttrLocation::Enum(enum_def) => enum_def.content_eq.skip = true,
45+
AttrLocation::StructField(struct_def, field_index) => {
46+
struct_def.fields[field_index].content_eq.skip = true;
47+
}
48+
_ => return Err(()),
49+
}
50+
51+
Ok(())
52+
}
53+
2654
fn prelude(&self) -> TokenStream {
2755
quote! {
2856
#![allow(clippy::match_same_arms)]
@@ -41,30 +69,49 @@ impl Derive for DeriveContentEq {
4169
}
4270

4371
fn derive_struct(struct_def: &StructDef, schema: &Schema) -> TokenStream {
44-
let fields = struct_def
45-
.fields
46-
.iter()
47-
.filter(|field| {
48-
let innermost_type = field.type_def(schema).innermost_type(schema);
49-
!IGNORE_FIELD_TYPES.contains(&innermost_type.name())
50-
})
51-
.map(|field| {
52-
let ident = field.ident();
53-
quote!( ContentEq::content_eq(&self.#ident, &other.#ident) )
54-
});
55-
56-
let mut body = quote!( #(#fields)&&* );
5772
let mut other_name = "other";
58-
if body.is_empty() {
59-
body = quote!(true);
73+
74+
let body = if struct_def.content_eq.skip {
75+
// Struct has `#[content_eq(skip)]` attr. So `content_eq` always returns true.
6076
other_name = "_";
77+
quote!(true)
78+
} else {
79+
let fields = struct_def
80+
.fields
81+
.iter()
82+
.filter(|field| !field.content_eq.skip)
83+
.filter(|field| {
84+
let innermost_type = field.type_def(schema).innermost_type(schema);
85+
match innermost_type {
86+
TypeDef::Struct(struct_def) => !struct_def.content_eq.skip,
87+
TypeDef::Enum(enum_def) => !enum_def.content_eq.skip,
88+
_ => true,
89+
}
90+
})
91+
.map(|field| {
92+
let ident = field.ident();
93+
quote!( ContentEq::content_eq(&self.#ident, &other.#ident) )
94+
});
95+
96+
let mut body = quote!( #(#fields)&&* );
97+
if body.is_empty() {
98+
body = quote!(true);
99+
other_name = "_";
100+
};
101+
body
61102
};
62103

63104
generate_impl(&struct_def.ty_anon(schema), other_name, &body)
64105
}
65106

66107
fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
67-
let body = if enum_def.is_fieldless() {
108+
let mut other_name = "other";
109+
110+
let body = if enum_def.content_eq.skip {
111+
// Enum has `#[content_eq(skip)]` attr. So `content_eq` always returns true.
112+
other_name = "_";
113+
quote!(true)
114+
} else if enum_def.is_fieldless() {
68115
// We assume fieldless enums implement `PartialEq`
69116
quote!(self == other)
70117
} else {
@@ -76,6 +123,7 @@ fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
76123
quote!( (Self::#ident(a), Self::#ident(b)) => a.content_eq(b) )
77124
}
78125
});
126+
79127
quote! {
80128
match (self, other) {
81129
#(#matches,)*
@@ -84,7 +132,7 @@ fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
84132
}
85133
};
86134

87-
generate_impl(&enum_def.ty_anon(schema), "other", &body)
135+
generate_impl(&enum_def.ty_anon(schema), other_name, &body)
88136
}
89137

90138
fn generate_impl(ty: &TokenStream, other_name: &str, body: &TokenStream) -> TokenStream {

tasks/ast_tools/src/schema/defs/enum.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::utils::create_ident;
99

1010
use super::{
1111
extensions::{
12+
content_eq::ContentEqType,
1213
estree::{ESTreeEnum, ESTreeEnumVariant},
1314
kind::Kind,
1415
layout::Layout,
@@ -33,6 +34,7 @@ pub struct EnumDef {
3334
pub visit: VisitEnum,
3435
pub kind: Kind,
3536
pub layout: Layout,
37+
pub content_eq: ContentEqType,
3638
pub estree: ESTreeEnum,
3739
}
3840

@@ -58,6 +60,7 @@ impl EnumDef {
5860
visit: VisitEnum::default(),
5961
kind: Kind::default(),
6062
layout: Layout::default(),
63+
content_eq: ContentEqType::default(),
6164
estree: ESTreeEnum::default(),
6265
}
6366
}

tasks/ast_tools/src/schema/defs/struct.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::utils::create_ident_tokens;
99
use super::{
1010
extensions::{
1111
clone_in::CloneInStructField,
12+
content_eq::{ContentEqStructField, ContentEqType},
1213
estree::{ESTreeStruct, ESTreeStructField},
1314
kind::Kind,
1415
layout::{Layout, Offset},
@@ -31,6 +32,7 @@ pub struct StructDef {
3132
pub kind: Kind,
3233
pub layout: Layout,
3334
pub span: SpanStruct,
35+
pub content_eq: ContentEqType,
3436
pub estree: ESTreeStruct,
3537
}
3638

@@ -55,6 +57,7 @@ impl StructDef {
5557
kind: Kind::default(),
5658
layout: Layout::default(),
5759
span: SpanStruct::default(),
60+
content_eq: ContentEqType::default(),
5861
estree: ESTreeStruct::default(),
5962
}
6063
}
@@ -118,6 +121,7 @@ pub struct FieldDef {
118121
pub visit: VisitFieldOrVariant,
119122
pub offset: Offset,
120123
pub clone_in: CloneInStructField,
124+
pub content_eq: ContentEqStructField,
121125
pub estree: ESTreeStructField,
122126
}
123127

@@ -137,6 +141,7 @@ impl FieldDef {
137141
visit: VisitFieldOrVariant::default(),
138142
offset: Offset::default(),
139143
clone_in: CloneInStructField::default(),
144+
content_eq: ContentEqStructField::default(),
140145
estree: ESTreeStructField::default(),
141146
}
142147
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Details for `ContentEq` derive on a struct or enum.
2+
#[derive(Default, Debug)]
3+
pub struct ContentEqType {
4+
/// `true` if type should ignored by `ContentEq`
5+
pub skip: bool,
6+
}
7+
8+
/// Details for `ContentEq` derive on a struct field.
9+
#[derive(Default, Debug)]
10+
pub struct ContentEqStructField {
11+
/// `true` if field should ignored by `ContentEq`
12+
pub skip: bool,
13+
}

tasks/ast_tools/src/schema/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub use file::File;
1414
/// Extensions to schema for specific derives / generators
1515
pub mod extensions {
1616
pub mod clone_in;
17+
pub mod content_eq;
1718
pub mod estree;
1819
pub mod kind;
1920
pub mod layout;

0 commit comments

Comments
 (0)