1use alloc::collections::BTreeMap;
4use alloc::string::{String, ToString};
5use alloc::vec::Vec;
6use core::fmt::Write;
7use proc_macro2::{Group, Ident, Spacing, TokenStream, TokenTree};
8use quote::ToTokens;
9use syn::visit::{self, Visit};
10use syn::visit_mut::{self, VisitMut};
11
12use crate::reference::{ReferenceFacts, ReferenceKind, Resolution};
13use crate::scope::PrimTy;
14
15pub(crate) const OWNER_PARAMETER: &str = "__dejadoc_owner_param_";
17
18pub struct CanonicalForm {
20 key: String,
21 owner_roles: Option<String>,
23 leaf_tokens: usize,
24 body_units: usize,
25}
26
27impl CanonicalForm {
28 pub(crate) fn from_tokens(tokens: TokenStream, body_units: usize) -> Self {
29 let mut key = String::from("K1 S ");
30 let mut leaf_tokens = 0;
31 let mut scratch = String::new();
32 for tree in tokens {
33 framed_tree(&mut key, tree, &mut leaf_tokens, &mut scratch);
34 }
35 key.push('E');
36 Self {
37 key,
38 owner_roles: None,
39 leaf_tokens,
40 body_units,
41 }
42 }
43
44 #[must_use]
46 pub fn key(&self) -> &str {
47 &self.key
48 }
49
50 #[must_use]
53 pub fn owner_role_key(&self) -> Option<&str> {
54 self.owner_roles.as_deref()
55 }
56
57 #[must_use]
59 pub fn into_key(self) -> String {
60 self.key
61 }
62
63 #[must_use]
65 pub fn leaf_tokens(&self) -> usize {
66 self.leaf_tokens
67 }
68
69 #[must_use]
71 pub fn body_units(&self) -> usize {
72 self.body_units
73 }
74}
75
76pub(crate) trait CanonicalNode: ToTokens {
77 fn visit<'ast>(&'ast self, visitor: &mut impl Visit<'ast>);
78
79 fn canonical_body_units(&self, tokens: &TokenStream) -> usize {
80 body_units(tokens.clone())
81 }
82}
83
84impl CanonicalNode for syn::File {
85 fn visit<'ast>(&'ast self, visitor: &mut impl Visit<'ast>) {
86 visitor.visit_file(self);
87 }
88
89 fn canonical_body_units(&self, tokens: &TokenStream) -> usize {
90 if !matches!(self.items.as_slice(), [syn::Item::Fn(_)]) {
91 return 0;
92 }
93 match tokens.clone().into_iter().last() {
94 Some(TokenTree::Group(body)) => body_units(body.stream()),
95 _ => 0,
96 }
97 }
98}
99
100impl CanonicalNode for syn::Block {
101 fn visit<'ast>(&'ast self, visitor: &mut impl Visit<'ast>) {
102 visitor.visit_block(self);
103 }
104}
105
106impl CanonicalNode for syn::Expr {
107 fn visit<'ast>(&'ast self, visitor: &mut impl Visit<'ast>) {
108 visitor.visit_expr(self);
109 }
110}
111
112impl CanonicalNode for syn::Arm {
113 fn visit<'ast>(&'ast self, visitor: &mut impl Visit<'ast>) {
114 visitor.visit_arm(self);
115 }
116}
117
118pub(crate) fn canonical_file_with_options(
119 file: syn::File,
120 compiles: bool,
121 options: crate::alpha::Options<'_>,
122) -> CanonicalForm {
123 canonical_file(file, compiles, options, true).0
124}
125
126pub(crate) struct PayloadProof<'a> {
128 pub(crate) table: Option<&'a crate::helpers::HelperTable>,
129 pub(crate) std_names: bool,
131 pub(crate) frame: &'a crate::scope::Frame,
132 pub(crate) syntax: crate::helpers::HelperSyntax,
133 pub(crate) edges: MarkerEdges,
134}
135
136pub(crate) struct MarkerEdges {
139 idents: crate::reference::MarkerOrigins<proc_macro2::Ident>,
140 macros: crate::reference::MarkerMacroOrigins,
141}
142
143impl MarkerEdges {
144 pub(crate) fn new() -> Self {
145 Self {
146 idents: alloc::collections::BTreeMap::new(),
147 macros: alloc::collections::BTreeMap::new(),
148 }
149 }
150
151 pub(crate) fn record_ident(
152 &mut self,
153 ident: &proc_macro2::Ident,
154 ns: crate::scope::Ns,
155 slot: usize,
156 ) {
157 self.idents.insert(core::ptr::from_ref(ident), (ns, slot));
158 }
159
160 pub(crate) fn record_macro(
161 &mut self,
162 mac: &syn::Macro,
163 occurrences: Vec<Option<(crate::scope::Ns, usize)>>,
164 ) {
165 let Some(first) = mac.path.segments.first() else {
166 return;
167 };
168 self.macros.insert(core::ptr::from_ref(first), occurrences);
169 }
170
171 pub(crate) fn into_parts(
172 self,
173 ) -> (
174 crate::reference::MarkerOrigins<proc_macro2::Ident>,
175 crate::reference::MarkerMacroOrigins,
176 ) {
177 (self.idents, self.macros)
178 }
179}
180
181struct Walker<'a> {
182 facts: &'a ReferenceFacts,
183 out: &'a mut Vec<(crate::scope::Ns, usize)>,
184}
185
186impl<'ast> Visit<'ast> for Walker<'_> {
187 fn visit_path(&mut self, path: &'ast syn::Path) {
188 if let Some(last) = path.segments.last()
189 && let Some((ns, slot)) = self.facts.marker_paths.get(&core::ptr::from_ref(last))
190 {
191 self.out.push((*ns, *slot));
192 }
193 visit::visit_path(self, path);
194 }
195
196 fn visit_macro(&mut self, mac: &'ast syn::Macro) {
197 if let Some(first) = mac.path.segments.first()
198 && let Some(records) = self.facts.marker_macros.get(&core::ptr::from_ref(first))
199 {
200 for record in records.iter().flatten() {
201 self.out.push(*record);
202 }
203 }
204 visit::visit_macro(self, mac);
205 }
206}
207
208pub(crate) fn item_payload(
209 file: syn::File,
210 proof: PayloadProof<'_>,
211) -> (CanonicalForm, Vec<(crate::scope::Ns, usize)>) {
212 let mut syntax = proof.syntax;
213 let (idents, macros) = proof.edges.into_parts();
214 syntax.ingest_marker_edges(&file, idents, macros);
215 let (form, normalized, facts) = canonical_file(
216 file,
217 true,
218 crate::alpha::Options {
219 seed: Some(proof.frame),
220 generics: None,
221 self_owner: None,
222 prim_fallback: true,
223 observed: false,
224 enclosing_observed: false,
225 std_names: proof.std_names,
226 prelude: false,
227 owned_methods: false,
228 clone_free: false,
229 collection_free: false,
230 helper_table: proof.table,
231 helper_module: &[],
232 helper_blocked: false,
233 helper_syntax: Some(syntax),
234 },
235 false,
236 );
237 let mut edges = Vec::new();
238 let mut walker = Walker {
239 facts: &facts,
240 out: &mut edges,
241 };
242 visit::visit_file(&mut walker, &normalized);
243 (form, edges)
244}
245
246fn canonical_file(
247 mut file: syn::File,
248 compiles: bool,
249 mut options: crate::alpha::Options<'_>,
250 items: bool,
251) -> (CanonicalForm, syn::File, ReferenceFacts) {
252 let owner = options.generics.is_some();
253 if owner {
254 ReservedLabels.visit_file_mut(&mut file);
255 }
256 let helpers = (compiles && items && options.helper_table.is_none())
257 .then(|| crate::helpers::module_table(&crate::helpers::Scopes::from_file(&file)));
258 let helper_table = options.helper_table.or(helpers.as_ref());
259 let mut helper_syntax = options.helper_syntax.take().unwrap_or_else(|| {
260 helper_table.map_or_else(crate::helpers::HelperSyntax::default, |table| {
261 crate::helpers::HelperSyntax::from_file(table, &mut file)
262 })
263 });
264 if compiles && items {
265 crate::items::normalize(
266 &mut file,
267 &options,
268 &mut helper_syntax,
269 helper_table.expect("items normalization has a helper table"),
270 );
271 }
272 crate::drift::normalize_file(
273 &mut file,
274 compiles,
275 &mut helper_syntax,
276 options.enclosing_observed,
277 options.std_names,
278 );
279 let options = crate::alpha::Options {
280 helper_table,
281 helper_syntax: Some(helper_syntax),
282 ..options
283 };
284 let facts = crate::alpha::normalize_file(&mut file, compiles, options);
285 (from_node_with(&file, &facts, owner), file, facts)
286}
287
288pub(crate) fn from_node(node: &impl CanonicalNode, facts: &ReferenceFacts) -> CanonicalForm {
289 from_node_with(node, facts, false)
290}
291
292fn from_node_with(node: &impl CanonicalNode, facts: &ReferenceFacts, owner: bool) -> CanonicalForm {
295 let tokens: TokenStream =
296 crate::fold_tokens(node.to_token_stream(), facts.observed, facts.observed)
297 .into_iter()
298 .collect();
299 let units = node.canonical_body_units(&tokens);
300 let mut tail = String::from("R ");
301 node.visit(&mut ReferenceEncoding {
302 out: &mut tail,
303 facts,
304 });
305 tail.push('E');
306 let finish = |tokens: TokenStream| {
307 let mut form = CanonicalForm::from_tokens(tokens, units);
308 form.key.pop();
309 form.key.push_str(&tail);
310 form
311 };
312 if !owner {
313 return finish(tokens);
314 }
315 let mut ranks = BTreeMap::new();
316 let numbered = number_owner_parameters(tokens.clone(), &mut ranks);
317 if ranks.is_empty() {
318 return finish(tokens);
319 }
320 let roles = finish(tokens);
321 let mut form = finish(numbered);
322 form.owner_roles = Some(roles.key);
323 form
324}
325
326fn number_owner_parameters(
328 tokens: TokenStream,
329 ranks: &mut BTreeMap<String, usize>,
330) -> TokenStream {
331 tokens
332 .into_iter()
333 .map(|tree| match tree {
334 TokenTree::Ident(ident) if ident.to_string().starts_with(OWNER_PARAMETER) => {
335 let next = ranks.len();
336 let rank = *ranks.entry(ident.to_string()).or_insert(next);
337 TokenTree::Ident(Ident::new(
338 &alloc::format!("{OWNER_PARAMETER}{rank}"),
339 ident.span(),
340 ))
341 }
342 TokenTree::Group(group) => {
343 let mut renamed = Group::new(
344 group.delimiter(),
345 number_owner_parameters(group.stream(), ranks),
346 );
347 renamed.set_span(group.span());
348 TokenTree::Group(renamed)
349 }
350 other => other,
351 })
352 .collect()
353}
354
355const ESCAPED: &str = "__dejadoc_escaped_";
357
358struct ReservedLabels;
363
364impl ReservedLabels {
365 fn ident(ident: &Ident) -> Option<Ident> {
366 let name = ident.to_string();
367 (name.starts_with(OWNER_PARAMETER) || name.starts_with(ESCAPED))
368 .then(|| Ident::new(&alloc::format!("{ESCAPED}{name}"), ident.span()))
369 }
370
371 fn tokens(tokens: TokenStream) -> TokenStream {
372 tokens
373 .into_iter()
374 .map(|tree| match tree {
375 TokenTree::Ident(ident) => TokenTree::Ident(Self::ident(&ident).unwrap_or(ident)),
376 TokenTree::Group(group) => {
377 let mut escaped = Group::new(group.delimiter(), Self::tokens(group.stream()));
378 escaped.set_span(group.span());
379 TokenTree::Group(escaped)
380 }
381 other => other,
382 })
383 .collect()
384 }
385}
386
387impl VisitMut for ReservedLabels {
388 fn visit_ident_mut(&mut self, ident: &mut Ident) {
389 if let Some(escaped) = Self::ident(ident) {
390 *ident = escaped;
391 }
392 }
393
394 fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
395 visit_mut::visit_macro_mut(self, mac);
396 mac.tokens = Self::tokens(core::mem::take(&mut mac.tokens));
397 }
398
399 fn visit_meta_list_mut(&mut self, list: &mut syn::MetaList) {
400 visit_mut::visit_meta_list_mut(self, list);
401 list.tokens = Self::tokens(core::mem::take(&mut list.tokens));
402 }
403
404 fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
405 match expr {
406 syn::Expr::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
407 other => visit_mut::visit_expr_mut(self, other),
408 }
409 }
410
411 fn visit_type_mut(&mut self, ty: &mut syn::Type) {
412 match ty {
413 syn::Type::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
414 other => visit_mut::visit_type_mut(self, other),
415 }
416 }
417
418 fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
419 match pat {
420 syn::Pat::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
421 other => visit_mut::visit_pat_mut(self, other),
422 }
423 }
424
425 fn visit_item_mut(&mut self, item: &mut syn::Item) {
426 match item {
427 syn::Item::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
428 other => visit_mut::visit_item_mut(self, other),
429 }
430 }
431
432 fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
433 match item {
434 syn::ImplItem::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
435 other => visit_mut::visit_impl_item_mut(self, other),
436 }
437 }
438
439 fn visit_trait_item_mut(&mut self, item: &mut syn::TraitItem) {
440 match item {
441 syn::TraitItem::Verbatim(tokens) => *tokens = Self::tokens(core::mem::take(tokens)),
442 other => visit_mut::visit_trait_item_mut(self, other),
443 }
444 }
445}
446
447struct ReferenceEncoding<'a> {
448 out: &'a mut String,
449 facts: &'a ReferenceFacts,
450}
451
452impl ReferenceEncoding<'_> {
453 fn resolution(&mut self, resolution: Resolution) {
454 self.out.push('r');
455 self.out.push(match resolution.kind {
456 ReferenceKind::Unresolved => '0',
457 ReferenceKind::Owned => '1',
458 ReferenceKind::Inherited => '2',
459 });
460 self.out.push(' ');
461 self.primitive(resolution.prim);
462 }
463
464 fn primitive(&mut self, primitive: Option<PrimTy>) {
465 self.out.push('t');
466 self.out.push_str(primitive.map_or("_", PrimTy::suffix));
467 self.out.push(' ');
468 }
469}
470
471impl<'ast> Visit<'ast> for ReferenceEncoding<'_> {
472 fn visit_block(&mut self, block: &'ast syn::Block) {
473 if let Some(proof) = self.facts.blocks.get(&core::ptr::from_ref(block)) {
474 write!(self.out, "b {} ", proof.statements.len())
475 .expect("writing to a string succeeds");
476 for (&role, &primitive) in proof.statements.iter().zip(&proof.types) {
477 self.out.push(char::from(role));
478 self.primitive(primitive);
479 }
480 write!(self.out, "o {} ", proof.tail_types.len())
481 .expect("writing to a string succeeds");
482 for &primitive in &proof.tail_types {
483 self.primitive(primitive);
484 }
485 }
486 visit::visit_block(self, block);
487 }
488
489 fn visit_path(&mut self, path: &'ast syn::Path) {
490 self.resolution(
491 self.facts
492 .paths
493 .get(&core::ptr::from_ref(path))
494 .copied()
495 .unwrap_or_default(),
496 );
497 visit::visit_path(self, path);
498 }
499
500 fn visit_macro(&mut self, mac: &'ast syn::Macro) {
501 let resolutions = self
502 .facts
503 .macros
504 .get(&core::ptr::from_ref(mac))
505 .map_or(&[][..], alloc::vec::Vec::as_slice);
506 write!(self.out, "m {} ", resolutions.len()).expect("writing to a string succeeds");
507 for &resolution in resolutions {
508 self.resolution(resolution);
509 }
510 visit::visit_macro(self, mac);
511 }
512}
513
514impl core::fmt::Display for CanonicalForm {
515 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
516 f.write_str(&self.key)
517 }
518}
519
520impl core::fmt::Debug for CanonicalForm {
521 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
522 f.debug_tuple("CanonicalForm").field(&self.key).finish()
523 }
524}
525
526impl PartialEq for CanonicalForm {
527 fn eq(&self, other: &Self) -> bool {
528 self.key == other.key
529 }
530}
531
532impl Eq for CanonicalForm {}
533
534impl core::hash::Hash for CanonicalForm {
535 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
536 core::hash::Hash::hash(&self.key, state);
537 }
538}
539
540fn framed_tree(out: &mut String, tree: TokenTree, leaves: &mut usize, scratch: &mut String) {
541 match tree {
542 TokenTree::Ident(ident) => {
543 scratch.clear();
544 write!(scratch, "{ident}").expect("writing to a string succeeds");
545 framed_payload(out, 'i', scratch);
546 *leaves += 1;
547 }
548 TokenTree::Literal(literal) => {
549 scratch.clear();
550 write!(scratch, "{literal}").expect("writing to a string succeeds");
551 framed_payload(out, 'l', scratch);
552 *leaves += 1;
553 }
554 TokenTree::Punct(punct) => {
555 out.push('p');
556 out.push(if punct.spacing() == Spacing::Joint {
557 'j'
558 } else {
559 'a'
560 });
561 out.push(punct.as_char());
562 out.push(' ');
563 *leaves += 1;
564 }
565 TokenTree::Group(group) => {
566 out.push('g');
567 out.push(match group.delimiter() {
568 proc_macro2::Delimiter::None => 'n',
569 proc_macro2::Delimiter::Parenthesis => '(',
570 proc_macro2::Delimiter::Bracket => '[',
571 proc_macro2::Delimiter::Brace => '{',
572 });
573 out.push(' ');
574 for tree in group.stream() {
575 framed_tree(out, tree, leaves, scratch);
576 }
577 out.push_str("e ");
578 }
579 }
580}
581
582fn framed_payload(out: &mut String, kind: char, payload: &str) {
583 write!(out, "{kind} {} {payload} ", payload.len()).expect("writing to a string succeeds");
584}
585
586pub(crate) fn body_units(stream: TokenStream) -> usize {
587 let mut trees = stream.into_iter().peekable();
588 let mut count = 0;
589 let (mut ident, mut joined) = (false, false);
590 while let Some(tree) = trees.next() {
591 match tree {
592 TokenTree::Group(group) => {
593 count += body_units(group.stream());
594 (ident, joined) = (false, false);
595 }
596 TokenTree::Ident(_) => {
597 count += usize::from(!joined);
598 (ident, joined) = (true, false);
599 }
600 TokenTree::Literal(_) => {
601 count += 1;
602 (ident, joined) = (false, false);
603 }
604 TokenTree::Punct(first) => {
605 if first.as_char() == '\'' && matches!(trees.peek(), Some(TokenTree::Ident(_))) {
606 trees.next();
607 count += 1;
608 (ident, joined) = (false, false);
609 continue;
610 }
611 let c = first.as_char();
612 let second = match trees.peek() {
613 Some(TokenTree::Punct(second)) if first.spacing() == Spacing::Joint => {
614 Some((second.as_char(), second.spacing()))
615 }
616 _ => None,
617 };
618 if second.is_some_and(|(second, _)| c == ':' && second == ':') {
619 trees.next();
620 joined = ident;
621 continue;
622 }
623 if let Some((second, spacing)) = second
624 && OPERATORS.iter().any(|op| {
625 let bytes = op.as_bytes();
626 char::from(bytes[0]) == c && char::from(bytes[1]) == second
627 })
628 {
629 trees.next();
630 if spacing == Spacing::Joint
631 && let Some(TokenTree::Punct(third)) = trees.peek()
632 && OPERATORS.iter().any(|op| {
633 let bytes = op.as_bytes();
634 bytes.len() == 3
635 && char::from(bytes[0]) == c
636 && char::from(bytes[1]) == second
637 && char::from(bytes[2]) == third.as_char()
638 })
639 {
640 trees.next();
641 }
642 }
643 count += usize::from(c != ',');
644 (ident, joined) = (false, false);
645 }
646 }
647 }
648 count
649}
650
651const OPERATORS: [&str; 23] = [
652 "<<=", ">>=", "...", "..=", "=>", "==", "!=", "<=", ">=", "&&", "||", "+=", "-=", "*=", "/=",
653 "%=", "^=", "&=", "|=", "<<", ">>", "..", "->",
654];