Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/oxc_ast_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
/// Its only purpose is to allow the occurrence of helper attributes used in `tasks/ast_tools`.
///
/// See [`macro@ast`] for further details.
#[proc_macro_derive(Ast, attributes(clone_in, estree, generate_derive, scope, span, ts, visit))]
#[proc_macro_derive(
Ast,
attributes(clone_in, content_eq, estree, generate_derive, scope, span, ts, visit)
)]
pub fn ast_derive(_input: TokenStream) -> TokenStream {
TokenStream::new()
}
1 change: 1 addition & 0 deletions crates/oxc_span/src/span/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ use super::PointerAlign;
#[ast(visit)]
#[derive(Default, Clone, Copy, Eq, PartialOrd, Ord)]
#[generate_derive(ESTree)]
#[content_eq(skip)]
#[estree(no_type, flatten)]
pub struct Span {
/// The zero-based start offset of the span
Expand Down
4 changes: 3 additions & 1 deletion crates/oxc_syntax/src/reference.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
#![allow(missing_docs)] // fixme
use bitflags::bitflags;
use nonmax::NonMaxU32;
use oxc_allocator::CloneIn;
use oxc_index::Idx;
#[cfg(feature = "serialize")]
use serde::{Serialize, Serializer};

use oxc_allocator::CloneIn;

use crate::{node::NodeId, symbol::SymbolId};

use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[content_eq(skip)]
pub struct ReferenceId(NonMaxU32);

impl Idx for ReferenceId {
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_syntax/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[content_eq(skip)]
pub struct ScopeId(NonMaxU32);

impl ScopeId {
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_syntax/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use oxc_ast_macros::ast;

#[ast]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[content_eq(skip)]
pub struct SymbolId(NonMaxU32);

impl SymbolId {
Expand Down
90 changes: 69 additions & 21 deletions tasks/ast_tools/src/derives/content_eq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

use crate::schema::{Def, EnumDef, Schema, StructDef};
use crate::{
schema::{Def, EnumDef, Schema, StructDef, TypeDef},
Result,
};

use super::{define_derive, Derive, StructOrEnum};

const IGNORE_FIELD_TYPES: [&str; 4] = ["Span", "ScopeId", "SymbolId", "ReferenceId"];
use super::{
attr_positions, define_derive, AttrLocation, AttrPart, AttrPositions, Derive, StructOrEnum,
};

/// Derive for `ContentEq` trait.
pub struct DeriveContentEq;
Expand All @@ -23,6 +26,31 @@ impl Derive for DeriveContentEq {
"oxc_span"
}

/// Register that accept `#[content_eq]` attr on structs, enums, or struct fields.
/// Allow attr on structs and enums which don't derive this trait.
fn attrs(&self) -> &[(&'static str, AttrPositions)] {
&[("content_eq", attr_positions!(StructMaybeDerived | EnumMaybeDerived | StructField))]
}

/// Parse `#[content_eq(skip)]` attr.
fn parse_attr(&self, _attr_name: &str, location: AttrLocation, part: AttrPart) -> Result<()> {
// No need to check attr name is `content_eq`, because that's the only attribute this derive handles.
if !matches!(part, AttrPart::Tag("skip")) {
return Err(());
}

match location {
AttrLocation::Struct(struct_def) => struct_def.content_eq.skip = true,
AttrLocation::Enum(enum_def) => enum_def.content_eq.skip = true,
AttrLocation::StructField(struct_def, field_index) => {
struct_def.fields[field_index].content_eq.skip = true;
}
_ => return Err(()),
}

Ok(())
}

fn prelude(&self) -> TokenStream {
quote! {
#![allow(clippy::match_same_arms)]
Expand All @@ -41,30 +69,49 @@ impl Derive for DeriveContentEq {
}

fn derive_struct(struct_def: &StructDef, schema: &Schema) -> TokenStream {
let fields = struct_def
.fields
.iter()
.filter(|field| {
let innermost_type = field.type_def(schema).innermost_type(schema);
!IGNORE_FIELD_TYPES.contains(&innermost_type.name())
})
.map(|field| {
let ident = field.ident();
quote!( ContentEq::content_eq(&self.#ident, &other.#ident) )
});

let mut body = quote!( #(#fields)&&* );
let mut other_name = "other";
if body.is_empty() {
body = quote!(true);

let body = if struct_def.content_eq.skip {
// Struct has `#[content_eq(skip)]` attr. So `content_eq` always returns true.
other_name = "_";
quote!(true)
} else {
let fields = struct_def
.fields
.iter()
.filter(|field| !field.content_eq.skip)
.filter(|field| {
let innermost_type = field.type_def(schema).innermost_type(schema);
match innermost_type {
TypeDef::Struct(struct_def) => !struct_def.content_eq.skip,
TypeDef::Enum(enum_def) => !enum_def.content_eq.skip,
_ => true,
}
})
.map(|field| {
let ident = field.ident();
quote!( ContentEq::content_eq(&self.#ident, &other.#ident) )
});

let mut body = quote!( #(#fields)&&* );
if body.is_empty() {
body = quote!(true);
other_name = "_";
};
body
};

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

fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
let body = if enum_def.is_fieldless() {
let mut other_name = "other";

let body = if enum_def.content_eq.skip {
// Enum has `#[content_eq(skip)]` attr. So `content_eq` always returns true.
other_name = "_";
quote!(true)
} else if enum_def.is_fieldless() {
// We assume fieldless enums implement `PartialEq`
quote!(self == other)
} else {
Expand All @@ -76,6 +123,7 @@ fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
quote!( (Self::#ident(a), Self::#ident(b)) => a.content_eq(b) )
}
});

quote! {
match (self, other) {
#(#matches,)*
Expand All @@ -84,7 +132,7 @@ fn derive_enum(enum_def: &EnumDef, schema: &Schema) -> TokenStream {
}
};

generate_impl(&enum_def.ty_anon(schema), "other", &body)
generate_impl(&enum_def.ty_anon(schema), other_name, &body)
}

fn generate_impl(ty: &TokenStream, other_name: &str, body: &TokenStream) -> TokenStream {
Expand Down
3 changes: 3 additions & 0 deletions tasks/ast_tools/src/schema/defs/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::utils::create_ident;

use super::{
extensions::{
content_eq::ContentEqType,
estree::{ESTreeEnum, ESTreeEnumVariant},
kind::Kind,
layout::Layout,
Expand All @@ -33,6 +34,7 @@ pub struct EnumDef {
pub visit: VisitEnum,
pub kind: Kind,
pub layout: Layout,
pub content_eq: ContentEqType,
pub estree: ESTreeEnum,
}

Expand All @@ -58,6 +60,7 @@ impl EnumDef {
visit: VisitEnum::default(),
kind: Kind::default(),
layout: Layout::default(),
content_eq: ContentEqType::default(),
estree: ESTreeEnum::default(),
}
}
Expand Down
5 changes: 5 additions & 0 deletions tasks/ast_tools/src/schema/defs/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::utils::create_ident_tokens;
use super::{
extensions::{
clone_in::CloneInStructField,
content_eq::{ContentEqStructField, ContentEqType},
estree::{ESTreeStruct, ESTreeStructField},
kind::Kind,
layout::{Layout, Offset},
Expand All @@ -31,6 +32,7 @@ pub struct StructDef {
pub kind: Kind,
pub layout: Layout,
pub span: SpanStruct,
pub content_eq: ContentEqType,
pub estree: ESTreeStruct,
}

Expand All @@ -55,6 +57,7 @@ impl StructDef {
kind: Kind::default(),
layout: Layout::default(),
span: SpanStruct::default(),
content_eq: ContentEqType::default(),
estree: ESTreeStruct::default(),
}
}
Expand Down Expand Up @@ -118,6 +121,7 @@ pub struct FieldDef {
pub visit: VisitFieldOrVariant,
pub offset: Offset,
pub clone_in: CloneInStructField,
pub content_eq: ContentEqStructField,
pub estree: ESTreeStructField,
}

Expand All @@ -137,6 +141,7 @@ impl FieldDef {
visit: VisitFieldOrVariant::default(),
offset: Offset::default(),
clone_in: CloneInStructField::default(),
content_eq: ContentEqStructField::default(),
estree: ESTreeStructField::default(),
}
}
Expand Down
13 changes: 13 additions & 0 deletions tasks/ast_tools/src/schema/extensions/content_eq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Details for `ContentEq` derive on a struct or enum.
#[derive(Default, Debug)]
pub struct ContentEqType {
/// `true` if type should ignored by `ContentEq`
pub skip: bool,
}

/// Details for `ContentEq` derive on a struct field.
#[derive(Default, Debug)]
pub struct ContentEqStructField {
/// `true` if field should ignored by `ContentEq`
pub skip: bool,
}
1 change: 1 addition & 0 deletions tasks/ast_tools/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use file::File;
/// Extensions to schema for specific derives / generators
pub mod extensions {
pub mod clone_in;
pub mod content_eq;
pub mod estree;
pub mod kind;
pub mod layout;
Expand Down
Loading