1use std::collections::BTreeMap;
4
5use syn::visit::{self, Visit};
6use syn::{Block, Expr, ExprCall, ExprMethodCall, FnArg, Macro, Pat, Signature};
7
8use crate::code::TypeName;
9use crate::handlers::HandlerScope;
10use crate::syntax::{last_name, line_of, macro_expressions, segments};
11
12#[derive(Clone, Debug, Default, PartialEq, Eq)]
13pub struct Effects {
14 pub calls: Vec<Call>,
15 pub method_calls: Vec<MethodCall>,
16 pub raised: Vec<Raised>,
17 pub queued: Vec<Queued>,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct Call {
22 pub path: Vec<String>,
23 pub line: usize,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct MethodCall {
28 pub receiver: Option<String>,
29 pub method: String,
30 pub line: usize,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct Raised {
35 pub event: Option<String>,
36 pub line: usize,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct Queued {
41 pub job: Option<Vec<String>>,
42 pub line: usize,
43}
44
45impl Effects {
46 pub(crate) fn of(
47 signature: &Signature,
48 body: &Block,
49 owner: Option<&str>,
50 scope: &HandlerScope,
51 ) -> Self {
52 let mut visitor = EffectVisitor {
53 typed: typed_parameters(signature, owner),
54 scope,
55 owner,
56 found: Self::default(),
57 };
58 visitor.visit_block(body);
59 visitor.found
60 }
61}
62
63fn typed_parameters(signature: &Signature, owner: Option<&str>) -> BTreeMap<String, String> {
64 let mut typed: BTreeMap<String, String> = signature
65 .inputs
66 .iter()
67 .filter_map(|argument| match argument {
68 FnArg::Typed(typed) => match typed.pat.as_ref() {
69 Pat::Ident(name) => {
70 Some((name.ident.to_string(), referenced(TypeName::of(&typed.ty))))
71 }
72 _ => None,
73 },
74 FnArg::Receiver(_) => None,
75 })
76 .collect();
77 if let Some(owner) = owner {
78 typed.insert("self".to_owned(), owner.to_owned());
79 }
80 typed
81}
82
83fn referenced(mut ty: TypeName) -> String {
84 while ty.name == "&" && ty.arguments.len() == 1 {
85 ty = ty.arguments.remove(0);
86 }
87 ty.name
88}
89
90struct EffectVisitor<'s> {
91 typed: BTreeMap<String, String>,
92 scope: &'s HandlerScope,
93 owner: Option<&'s str>,
94 found: Effects,
95}
96
97impl EffectVisitor<'_> {
98 fn callee(&self, path: &syn::Path) -> Vec<String> {
99 let written = segments(path);
100 match (written.first().map(String::as_str), self.owner) {
101 (Some("Self"), Some(owner)) => [&[owner.to_owned()], &written[1..]].concat(),
102 _ => self.scope.callee(&written),
103 }
104 }
105
106 fn receiver_type(&self, receiver: &Expr) -> Option<String> {
107 let Expr::Path(path) = receiver else {
108 return None;
109 };
110 let name = path.path.get_ident()?.to_string();
111 self.typed.get(&name).cloned()
112 }
113}
114
115impl Visit<'_> for EffectVisitor<'_> {
116 fn visit_expr_call(&mut self, call: &ExprCall) {
117 if let Expr::Path(function) = call.func.as_ref() {
118 self.found.calls.push(Call {
119 path: self.callee(&function.path),
120 line: line_of(call),
121 });
122 }
123 visit::visit_expr_call(self, call);
124 }
125
126 fn visit_expr_method_call(&mut self, call: &ExprMethodCall) {
127 let line = line_of(&call.method);
128 let first = call.args.first();
129 match call.method.to_string().as_str() {
130 "raise" => self.found.raised.push(Raised {
131 event: first.and_then(event_name),
132 line,
133 }),
134 "queue" => self.found.queued.push(Queued {
135 job: first.and_then(|job| match job {
136 Expr::Path(path) => Some(self.callee(&path.path)),
137 _ => None,
138 }),
139 line,
140 }),
141 method => self.found.method_calls.push(MethodCall {
142 receiver: self.receiver_type(&call.receiver),
143 method: method.to_owned(),
144 line,
145 }),
146 }
147 visit::visit_expr_method_call(self, call);
148 }
149
150 fn visit_macro(&mut self, call: &Macro) {
151 for expression in macro_expressions(call) {
152 self.visit_expr(&expression);
153 }
154 }
155}
156
157fn event_name(argument: &Expr) -> Option<String> {
158 match argument {
159 Expr::Struct(literal) => last_name(&literal.path),
160 Expr::Path(path) => last_name(&path.path),
161 _ => None,
162 }
163}