1 use crate::{cfg, file, lookup};
2 use anyhow::Result;
3 use proc_macro2::{Ident, Span, TokenStream};
4 use quote::{format_ident, quote};
5 use syn_codegen::{Data, Definitions, Node, Type};
6
7 const DEBUG_SRC: &str = "../src/gen/eq.rs";
8
always_eq(field_type: &Type) -> bool9 fn always_eq(field_type: &Type) -> bool {
10 match field_type {
11 Type::Syn(node) => node == "Reserved",
12 Type::Ext(ty) => ty == "Span",
13 Type::Token(_) | Type::Group(_) => true,
14 Type::Box(inner) => always_eq(inner),
15 Type::Tuple(inner) => inner.iter().all(always_eq),
16 _ => false,
17 }
18 }
19
expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream20 fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream {
21 let type_name = &node.ident;
22 let ident = Ident::new(type_name, Span::call_site());
23
24 match &node.data {
25 Data::Enum(variants) => {
26 let arms = variants.iter().map(|(variant_name, fields)| {
27 let variant = Ident::new(variant_name, Span::call_site());
28 if fields.is_empty() {
29 quote! {
30 (#ident::#variant, #ident::#variant) => true,
31 }
32 } else {
33 let mut this_pats = Vec::new();
34 let mut other_pats = Vec::new();
35 let mut comparisons = Vec::new();
36 for (i, field) in fields.iter().enumerate() {
37 if always_eq(field) {
38 this_pats.push(format_ident!("_"));
39 other_pats.push(format_ident!("_"));
40 continue;
41 }
42 let this = format_ident!("self{}", i);
43 let other = format_ident!("other{}", i);
44 comparisons.push(match field {
45 Type::Ext(ty) if ty == "TokenStream" => {
46 quote!(TokenStreamHelper(#this) == TokenStreamHelper(#other))
47 }
48 Type::Ext(ty) if ty == "Literal" => {
49 quote!(#this.to_string() == #other.to_string())
50 }
51 _ => quote!(#this == #other),
52 });
53 this_pats.push(this);
54 other_pats.push(other);
55 }
56 if comparisons.is_empty() {
57 comparisons.push(quote!(true));
58 }
59 let mut cfg = None;
60 if node.ident == "Expr" {
61 if let Type::Syn(ty) = &fields[0] {
62 if !lookup::node(defs, ty).features.any.contains("derive") {
63 cfg = Some(quote!(#[cfg(feature = "full")]));
64 }
65 }
66 }
67 quote! {
68 #cfg
69 (#ident::#variant(#(#this_pats),*), #ident::#variant(#(#other_pats),*)) => {
70 #(#comparisons)&&*
71 }
72 }
73 }
74 });
75 quote! {
76 match (self, other) {
77 #(#arms)*
78 _ => false,
79 }
80 }
81 }
82 Data::Struct(fields) => {
83 let mut comparisons = Vec::new();
84 for (f, ty) in fields {
85 if always_eq(ty) {
86 continue;
87 }
88 let ident = Ident::new(f, Span::call_site());
89 comparisons.push(match ty {
90 Type::Ext(ty) if ty == "TokenStream" => {
91 quote!(TokenStreamHelper(&self.#ident) == TokenStreamHelper(&other.#ident))
92 }
93 _ => quote!(self.#ident == other.#ident),
94 });
95 }
96 if comparisons.is_empty() {
97 quote!(true)
98 } else {
99 quote!(#(#comparisons)&&*)
100 }
101 }
102 Data::Private => unreachable!(),
103 }
104 }
105
expand_impl(defs: &Definitions, node: &Node) -> TokenStream106 fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream {
107 if node.ident == "Member" || node.ident == "Index" || node.ident == "Lifetime" {
108 return TokenStream::new();
109 }
110
111 let ident = Ident::new(&node.ident, Span::call_site());
112 let cfg_features = cfg::features(&node.features);
113
114 let eq = quote! {
115 #cfg_features
116 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
117 impl Eq for #ident {}
118 };
119
120 let manual_partial_eq = node.data == Data::Private;
121 if manual_partial_eq {
122 return eq;
123 }
124
125 let body = expand_impl_body(defs, node);
126 let other = if body.to_string() == "true" {
127 quote!(_other)
128 } else {
129 quote!(other)
130 };
131
132 quote! {
133 #eq
134
135 #cfg_features
136 #[cfg_attr(doc_cfg, doc(cfg(feature = "extra-traits")))]
137 impl PartialEq for #ident {
138 fn eq(&self, #other: &Self) -> bool {
139 #body
140 }
141 }
142 }
143 }
144
generate(defs: &Definitions) -> Result<()>145 pub fn generate(defs: &Definitions) -> Result<()> {
146 let mut impls = TokenStream::new();
147 for node in &defs.types {
148 impls.extend(expand_impl(defs, node));
149 }
150
151 file::write(
152 DEBUG_SRC,
153 quote! {
154 #[cfg(any(feature = "derive", feature = "full"))]
155 use crate::tt::TokenStreamHelper;
156 use crate::*;
157
158 #impls
159 },
160 )?;
161
162 Ok(())
163 }
164