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