1use std::{char, collections::HashMap};
2
3use proc_macro2::Span;
4use quote::quote_spanned;
5use syn::{
6 parse_macro_input, spanned::Spanned, Arm, ExprMatch, Ident, Pat, PatIdent, PatWild, Path, Token,
7};
8
9use crate::errors::emit_error;
10
11#[derive(Debug)]
12struct MessageGroup {
13 kind: GroupKind,
14 arms: Vec<Arm>,
15}
16
17#[derive(Debug, Hash, PartialEq, Eq)]
18enum GroupKind {
19 Regular(Path),
21 Request(Path),
23 Wild,
26}
27
28fn is_valid_token_ident(ident: &PatIdent) -> bool {
29 !ident.ident.to_string().starts_with('_')
30}
31
32fn is_type_ident(ident: &Ident) -> bool {
33 ident
34 .to_string()
35 .chars()
36 .next()
37 .is_some_and(char::is_uppercase)
38}
39
40fn extract_path_to_type(path: &Path) -> Path {
41 let mut ident_rev_it = path.segments.iter().rev();
42
43 if let Some(prev) = ident_rev_it.nth(1) {
49 if is_type_ident(&prev.ident) {
50 let mut path = path.clone();
51 path.segments.pop().unwrap();
52
53 let (last, _) = path.segments.pop().unwrap().into_tuple();
55 path.segments.push(last);
56 return path;
57 }
58 }
59
60 path.clone()
61}
62
63fn extract_kind(pat: &Pat) -> Result<GroupKind, &'static str> {
64 match pat {
65 Pat::Ident(pat) => match pat.subpat.as_ref() {
66 Some(sp) => extract_kind(&sp.1),
67 None if is_type_ident(&pat.ident) => {
68 Ok(GroupKind::Regular(Path::from(pat.ident.clone())))
69 }
70 None => Ok(GroupKind::Wild),
71 },
72 Pat::Lit(_) => Err("literal patterns are forbidden"),
73 Pat::Macro(_) => Err("macros in pattern position are forbidden"),
74 Pat::Or(pat) => pat
75 .cases
76 .iter()
77 .find_map(|pat| extract_kind(pat).ok())
78 .ok_or("cannot determine the message's type"),
79 Pat::Paren(_) => Err("parenthesized patterns are forbidden"),
80 Pat::Path(pat) => Ok(GroupKind::Regular(extract_path_to_type(&pat.path))),
81 Pat::Range(_) => Err("range patterns are forbidden"),
82 Pat::Reference(pat) => extract_kind(&pat.pat),
83 Pat::Rest(_) => Err("rest patterns are forbidden"),
84 Pat::Slice(_) => Err("slice patterns are forbidden"),
85 Pat::Struct(pat) => Ok(GroupKind::Regular(extract_path_to_type(&pat.path))),
86 Pat::Tuple(pat) => {
87 if pat.elems.len() != 2 {
88 return Err("invalid request pattern");
89 }
90
91 match pat.elems.last().unwrap() {
92 Pat::Ident(pat) => {
93 if !is_valid_token_ident(pat) {
94 emit_error!(
95 pat.span(),
96 "the token must be used, or call `drop(_)` explicitly"
97 )
98 }
99 }
100 _ => return Err("token must be identifier"),
101 }
102
103 match extract_kind(pat.elems.first().unwrap())? {
104 GroupKind::Regular(path) => Ok(GroupKind::Request(path)),
105 _ => Err("cannot determine the request's type"),
106 }
107 }
108 Pat::TupleStruct(pat) => Ok(GroupKind::Regular(extract_path_to_type(&pat.path))),
109 Pat::Type(_) => Err("type ascription patterns are forbidden"),
110 Pat::Wild(_) => Ok(GroupKind::Wild),
111 _ => Err("unknown tokens"),
112 }
113}
114
115fn is_likely_type(pat: &Pat) -> bool {
116 match pat {
117 Pat::Ident(i) if i.subpat.is_none() && is_type_ident(&i.ident) => true,
118 Pat::Path(p) if extract_path_to_type(&p.path) == p.path => true,
119 _ => false,
120 }
121}
122
123fn is_binding_with_type(ident: &PatIdent) -> bool {
125 ident
126 .subpat
127 .as_ref()
128 .is_some_and(|sp| is_likely_type(&sp.1))
129}
130
131fn refine_pat(pat: &mut Pat) {
132 match pat {
133 Pat::Ident(ident) if is_binding_with_type(ident) => {
136 ident.subpat = None;
137 }
138 Pat::Tuple(pat) => {
141 assert_eq!(pat.elems.len(), 2, "invalid request pattern");
143
144 match pat.elems.first_mut() {
145 Some(Pat::Ident(ident)) if is_binding_with_type(ident) => {
146 ident.subpat = None;
147 }
148 Some(pat) if is_likely_type(pat) => {
149 *pat = Pat::Wild(PatWild {
150 attrs: Vec::new(),
151 underscore_token: Token),
152 });
153 }
154 _ => {}
155 }
156 }
157 pat if is_likely_type(pat) => {
159 *pat = Pat::Wild(PatWild {
160 attrs: Vec::new(),
161 underscore_token: Token),
162 });
163 }
164 _ => {}
165 };
166}
167
168fn add_groups(groups: &mut Vec<MessageGroup>, mut arm: Arm) {
169 let mut add = |kind, arm: Arm| {
170 match groups.iter_mut().find(|common| common.kind == kind) {
172 Some(common) => common.arms.push(arm),
173 None => groups.push(MessageGroup {
174 kind,
175 arms: vec![arm],
176 }),
177 }
178 };
179
180 if let Pat::Or(pat) = &arm.pat {
181 let mut map = HashMap::new();
182
183 for pat in &pat.cases {
184 let kind = match extract_kind(pat) {
185 Ok(kind) => kind,
186 Err(err) => {
187 emit_error!(pat.span(), "{err}");
188 continue;
189 }
190 };
191 let new_arm = map.entry(kind).or_insert_with(|| {
192 let mut arm = arm.clone();
193 if let Pat::Or(pat) = &mut arm.pat {
194 pat.cases.clear();
195 }
196 arm
197 });
198
199 if let Pat::Or(new_pat) = &mut new_arm.pat {
200 let mut old_pat = pat.clone();
201 refine_pat(&mut old_pat);
202 new_pat.cases.push(old_pat);
203 }
204 }
205
206 for (kind, arm) in map {
207 add(kind, arm);
208 }
209 } else {
210 let kind = match extract_kind(&arm.pat) {
211 Ok(kind) => kind,
212 Err(err) => return emit_error!(arm.pat.span(), "{err}"),
213 };
214 refine_pat(&mut arm.pat);
215 add(kind, arm);
216 }
217}
218
219pub fn msg_impl(input: proc_macro::TokenStream, path_to_elfo: Path) -> proc_macro::TokenStream {
221 let crate_ = path_to_elfo;
222 let mixed_site = Span::mixed_site();
223 let input = parse_macro_input!(input as ExprMatch);
224 let mut groups = Vec::<MessageGroup>::with_capacity(input.arms.len());
225
226 for arm in input.arms.into_iter() {
227 add_groups(&mut groups, arm);
228 }
229
230 let groups = groups
233 .iter()
234 .map(|group| match (&group.kind, &group.arms[..]) {
235 (GroupKind::Regular(path), arms) => quote_spanned! {mixed_site=>
239 else if type_id == <#path as #crate_::Message>::_type_id() {
240 {
244 trait MustBeRegularNotRequest<A, E> { fn test(_: &E) {} }
245 impl<E, M> MustBeRegularNotRequest<(), E> for M {}
246 struct Invalid;
247 impl<E: internal::EnvelopeOwned, M: #crate_::Request>
248 MustBeRegularNotRequest<Invalid, E> for M {}
249 <#path as MustBeRegularNotRequest<_, _>>::test(&envelope)
250 }
251
252 #[allow(unknown_lints, clippy::blocks_in_conditions)]
253 match {
254 #[allow(unused_imports)]
256 use internal::{EnvelopeOwned as _, EnvelopeBorrowed as _};
257 unsafe { envelope.unpack_regular_unchecked::<#path>() }
258 } {
259 #(#arms)*
260 }
261 }
262 },
263 (GroupKind::Request(path), arms) => quote_spanned! {mixed_site=>
264 else if type_id == <#path as #crate_::Message>::_type_id() {
265 {
269 fn must_be_request<R: #crate_::Request>() {}
270 must_be_request::<#path>();
271 }
272
273 #[allow(unknown_lints, clippy::blocks_in_conditions)]
274 match {
275 #[allow(unused_imports)]
277 use internal::EnvelopeOwned as _;
278 unsafe { envelope.unpack_request_unchecked::<#path>() }
279 } {
280 #(#arms)*
281 }
282 }
283 },
284 (GroupKind::Wild, arms) => {
285 let mut arms_iter = arms.iter();
286 let arm = arms_iter.next().unwrap();
287
288 let expanded = quote_spanned! {mixed_site=>
289 else {
290 match envelope { #arm }
291 }
292 };
293
294 for arm in arms_iter {
295 emit_error!(arm.pat.span(), "this branch will never be matched");
296 }
297
298 expanded
299 }
300 });
301
302 let match_expr = input.expr;
303
304 let expanded = quote_spanned!(mixed_site=> {
306 use #crate_::_priv as internal;
307 let envelope = #match_expr;
308 let type_id = envelope.type_id();
309 #[allow(clippy::suspicious_else_formatting)]
310 if false { unreachable!(); }
311 #(#groups)*
312 });
313
314 if let Some(errors) = crate::errors::into_tokens() {
316 quote_spanned!(mixed_site=> { #errors #expanded }).into()
317 } else {
318 expanded.into()
319 }
320}