1use crate::ast::{Branch, Protocol, Role};
7use crate::compiler::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticCollector};
8use std::collections::{HashMap, HashSet};
9
10use super::types::{
11 BranchMessages, ChoiceAnalysisResult, ChoiceId, ChoiceKnowledge, KnowledgeSource, MessageInfo,
12};
13
14#[derive(Debug)]
16pub struct ChoiceAnalyzer {
17 roles: Vec<String>,
19 diagnostics: DiagnosticCollector,
21 choice_counts: HashMap<String, usize>,
23}
24
25impl ChoiceAnalyzer {
26 pub fn new(roles: &[Role]) -> Self {
28 Self {
29 roles: roles.iter().map(|r| r.name().to_string()).collect(),
30 diagnostics: DiagnosticCollector::new(),
31 choice_counts: HashMap::new(),
32 }
33 }
34
35 pub fn analyze(&mut self, protocol: &Protocol) -> ChoiceAnalysisResult {
37 let mut choices = Vec::new();
38 self.analyze_protocol(protocol, &mut choices, None);
39
40 for choice in &choices {
42 for uninformed in &choice.uninformed_roles {
43 self.emit_uninformed_role_error(choice, uninformed);
44 }
45 }
46
47 ChoiceAnalysisResult {
48 choices,
49 diagnostics: self.diagnostics.take_diagnostics(),
50 }
51 }
52
53 fn analyze_protocol(
55 &mut self,
56 protocol: &Protocol,
57 choices: &mut Vec<ChoiceKnowledge>,
58 parent_choice: Option<&ChoiceId>,
59 ) {
60 match protocol {
61 Protocol::Choice { role, branches, .. } => {
62 let choice_knowledge = self.analyze_choice(role, branches, parent_choice);
63 let choice_id = choice_knowledge.choice_id.clone();
64 choices.push(choice_knowledge);
65
66 for branch in branches {
68 self.analyze_protocol(&branch.protocol, choices, Some(&choice_id));
69 }
70 }
71 Protocol::Case { branches, .. } => {
72 for branch in branches {
73 self.analyze_protocol(&branch.protocol, choices, parent_choice);
74 }
75 }
76 Protocol::Timeout {
77 body,
78 on_timeout,
79 on_cancel,
80 ..
81 } => {
82 self.analyze_protocol(body, choices, parent_choice);
83 self.analyze_protocol(on_timeout, choices, parent_choice);
84 if let Some(on_cancel) = on_cancel.as_deref() {
85 self.analyze_protocol(on_cancel, choices, parent_choice);
86 }
87 }
88 Protocol::Send { continuation, .. } => {
89 self.analyze_protocol(continuation, choices, parent_choice);
90 }
91 Protocol::Broadcast { continuation, .. } => {
92 self.analyze_protocol(continuation, choices, parent_choice);
93 }
94 Protocol::Loop { body, .. } => {
95 self.analyze_protocol(body, choices, parent_choice);
96 }
97 Protocol::Parallel { protocols } => {
98 for p in protocols {
99 self.analyze_protocol(p, choices, parent_choice);
100 }
101 }
102 Protocol::Rec { body, .. } => {
103 self.analyze_protocol(body, choices, parent_choice);
104 }
105 Protocol::Begin { continuation, .. }
106 | Protocol::Await { continuation, .. }
107 | Protocol::Resolve { continuation, .. }
108 | Protocol::Invalidate { continuation, .. }
109 | Protocol::Extension { continuation, .. }
110 | Protocol::Let { continuation, .. }
111 | Protocol::Publish { continuation, .. }
112 | Protocol::PublishAuthority { continuation, .. }
113 | Protocol::Materialize { continuation, .. }
114 | Protocol::Handoff { continuation, .. }
115 | Protocol::DependentWork { continuation, .. } => {
116 self.analyze_protocol(continuation, choices, parent_choice);
117 }
118 Protocol::Var(_) | Protocol::End => {}
119 }
120 }
121
122 fn analyze_choice(
124 &mut self,
125 chooser: &Role,
126 branches: &[Branch],
127 parent_choice: Option<&ChoiceId>,
128 ) -> ChoiceKnowledge {
129 let chooser_name = chooser.name().to_string();
130
131 let index = *self.choice_counts.get(&chooser_name).unwrap_or(&0);
133 self.choice_counts.insert(chooser_name.clone(), index + 1);
134
135 let choice_id = match parent_choice {
136 Some(parent) => ChoiceId::nested(parent, &chooser_name, index),
137 None => ChoiceId::new(&chooser_name, index),
138 };
139
140 let branch_labels: Vec<String> = branches.iter().map(|b| b.label.to_string()).collect();
142
143 let mut informed_roles: HashMap<String, KnowledgeSource> = HashMap::new();
145 informed_roles.insert(chooser_name.clone(), KnowledgeSource::Chooser);
146
147 let branch_messages = self.collect_branch_messages(branches);
149
150 let roles_receiving_in_all = self.find_roles_receiving_in_all_branches(&branch_messages);
152
153 for (role, messages_per_branch) in roles_receiving_in_all {
155 if !informed_roles.contains_key(&role) {
156 if let Some(sender) = self.find_message_sender(&branch_messages, &role) {
158 informed_roles.insert(
159 role.clone(),
160 KnowledgeSource::MessageFrom {
161 sender,
162 messages: messages_per_branch,
163 },
164 );
165 }
166 }
167 }
168
169 self.propagate_knowledge(&mut informed_roles, &branch_messages, &branch_labels);
171
172 let participating_roles = self.find_participating_roles(branches);
174 let uninformed_roles: HashSet<String> = participating_roles
175 .into_iter()
176 .filter(|r| !informed_roles.contains_key(r))
177 .collect();
178
179 let branch_participation = self.build_branch_participation(&branch_messages);
181
182 ChoiceKnowledge {
183 choice_id,
184 branches: branch_labels,
185 informed_roles,
186 uninformed_roles,
187 location: None, branch_participation,
189 }
190 }
191
192 fn build_branch_participation(
194 &self,
195 branch_messages: &[BranchMessages],
196 ) -> HashMap<String, HashSet<String>> {
197 branch_messages
198 .iter()
199 .map(|branch| {
200 let mut roles = HashSet::new();
201 for msg in &branch.messages {
202 roles.insert(msg.from.clone());
203 roles.insert(msg.to.clone());
204 }
205 (branch.label.clone(), roles)
206 })
207 .collect()
208 }
209
210 fn collect_branch_messages(&self, branches: &[Branch]) -> Vec<BranchMessages> {
212 branches
213 .iter()
214 .map(|branch| {
215 let mut messages = Vec::new();
216 self.collect_messages_from_protocol(&branch.protocol, &mut messages);
217 BranchMessages {
218 label: branch.label.to_string(),
219 messages,
220 }
221 })
222 .collect()
223 }
224
225 #[allow(clippy::only_used_in_recursion)]
227 fn collect_messages_from_protocol(&self, protocol: &Protocol, messages: &mut Vec<MessageInfo>) {
228 match protocol {
229 Protocol::Send {
230 from,
231 to,
232 message,
233 continuation,
234 ..
235 } => {
236 self.push_send_message(messages, from, to, &message.name.to_string());
237 self.collect_messages_from_protocol(continuation, messages);
238 }
239 Protocol::Broadcast {
240 from,
241 to_all,
242 message,
243 continuation,
244 ..
245 } => {
246 self.push_broadcast_messages(messages, from, to_all, &message.name.to_string());
247 self.collect_messages_from_protocol(continuation, messages);
248 }
249 Protocol::Choice { branches, .. } => {
250 self.collect_nested_choice_messages(branches, messages);
251 }
252 Protocol::Case { branches, .. } => {
253 for branch in branches {
254 self.collect_messages_from_protocol(&branch.protocol, messages);
255 }
256 }
257 Protocol::Timeout {
258 body,
259 on_timeout,
260 on_cancel,
261 ..
262 } => {
263 self.collect_messages_from_protocol(body, messages);
264 self.collect_messages_from_protocol(on_timeout, messages);
265 if let Some(on_cancel) = on_cancel.as_deref() {
266 self.collect_messages_from_protocol(on_cancel, messages);
267 }
268 }
269 Protocol::Loop { body, .. } => {
270 self.collect_messages_from_protocol(body, messages);
271 }
272 Protocol::Parallel { protocols } => {
273 for p in protocols {
274 self.collect_messages_from_protocol(p, messages);
275 }
276 }
277 Protocol::Rec { body, .. } => {
278 self.collect_messages_from_protocol(body, messages);
279 }
280 Protocol::Begin { continuation, .. }
281 | Protocol::Await { continuation, .. }
282 | Protocol::Resolve { continuation, .. }
283 | Protocol::Invalidate { continuation, .. }
284 | Protocol::Extension { continuation, .. }
285 | Protocol::Let { continuation, .. }
286 | Protocol::Publish { continuation, .. }
287 | Protocol::PublishAuthority { continuation, .. }
288 | Protocol::Materialize { continuation, .. }
289 | Protocol::Handoff { continuation, .. }
290 | Protocol::DependentWork { continuation, .. } => {
291 self.collect_messages_from_protocol(continuation, messages);
292 }
293 Protocol::Var(_) | Protocol::End => {}
294 }
295 }
296
297 fn push_send_message(
298 &self,
299 messages: &mut Vec<MessageInfo>,
300 from: &Role,
301 to: &Role,
302 message_type: &str,
303 ) {
304 messages.push(MessageInfo {
305 from: from.name().to_string(),
306 to: to.name().to_string(),
307 message_type: message_type.to_string(),
308 });
309 }
310
311 fn push_broadcast_messages(
312 &self,
313 messages: &mut Vec<MessageInfo>,
314 from: &Role,
315 to_all: &crate::ast::NonEmptyVec<Role>,
316 message_type: &str,
317 ) {
318 for to in to_all {
319 self.push_send_message(messages, from, to, message_type);
320 }
321 }
322
323 fn collect_nested_choice_messages(
324 &self,
325 branches: &crate::ast::NonEmptyVec<Branch>,
326 messages: &mut Vec<MessageInfo>,
327 ) {
328 for branch in branches {
329 if let Protocol::Send {
330 from, to, message, ..
331 } = &branch.protocol
332 {
333 self.push_send_message(messages, from, to, &message.name.to_string());
334 }
335 }
336 }
337
338 fn find_roles_receiving_in_all_branches(
340 &self,
341 branch_messages: &[BranchMessages],
342 ) -> HashMap<String, Vec<String>> {
343 if branch_messages.is_empty() {
344 return HashMap::new();
345 }
346
347 let mut role_messages: HashMap<String, Vec<Option<String>>> = HashMap::new();
349
350 for (branch_idx, branch) in branch_messages.iter().enumerate() {
351 let mut seen_roles: HashSet<String> = HashSet::new();
353 for msg in &branch.messages {
354 if !seen_roles.contains(&msg.to) {
355 seen_roles.insert(msg.to.clone());
356 role_messages
357 .entry(msg.to.clone())
358 .or_insert_with(|| vec![None; branch_messages.len()])[branch_idx] =
359 Some(msg.message_type.clone());
360 }
361 }
362 }
363
364 role_messages
366 .into_iter()
367 .filter_map(|(role, messages)| {
368 if messages.iter().all(|m| m.is_some()) {
369 let msg_types: Vec<String> = messages.into_iter().map(|m| m.unwrap()).collect();
370 Some((role, msg_types))
371 } else {
372 None
373 }
374 })
375 .collect()
376 }
377
378 fn find_message_sender(
380 &self,
381 branch_messages: &[BranchMessages],
382 receiver: &str,
383 ) -> Option<String> {
384 for branch in branch_messages {
385 for msg in &branch.messages {
386 if msg.to == receiver {
387 return Some(msg.from.clone());
388 }
389 }
390 }
391 None
392 }
393
394 fn propagate_knowledge(
399 &self,
400 informed: &mut HashMap<String, KnowledgeSource>,
401 branch_messages: &[BranchMessages],
402 _branch_labels: &[String],
403 ) {
404 if branch_messages.is_empty() {
405 return;
406 }
407
408 let mut changed = true;
410 while changed {
411 changed = false;
412
413 let mut receiver_senders_per_branch: HashMap<String, Vec<Option<String>>> =
416 HashMap::new();
417
418 for (branch_idx, branch) in branch_messages.iter().enumerate() {
419 let mut seen_receivers: HashSet<String> = HashSet::new();
421 for msg in &branch.messages {
422 if informed.contains_key(&msg.from) && !seen_receivers.contains(&msg.to) {
423 seen_receivers.insert(msg.to.clone());
424 receiver_senders_per_branch
425 .entry(msg.to.clone())
426 .or_insert_with(|| vec![None; branch_messages.len()])[branch_idx] =
427 Some(msg.from.clone());
428 }
429 }
430 }
431
432 for (receiver, senders) in &receiver_senders_per_branch {
434 if !informed.contains_key(receiver) && senders.iter().all(|s| s.is_some()) {
435 if let Some(sender) = &senders[0] {
437 let source = informed.get(sender).unwrap().clone();
438 informed.insert(
439 receiver.clone(),
440 KnowledgeSource::Transitive {
441 via: sender.clone(),
442 original_source: Box::new(source),
443 },
444 );
445 changed = true;
446 }
447 }
448 }
449 }
450 }
451
452 fn find_participating_roles(&self, branches: &[Branch]) -> HashSet<String> {
454 let mut roles = HashSet::new();
455 for branch in branches {
456 self.collect_roles_from_protocol(&branch.protocol, &mut roles);
457 }
458 roles
459 }
460
461 #[allow(clippy::only_used_in_recursion)]
463 fn collect_roles_from_protocol(&self, protocol: &Protocol, roles: &mut HashSet<String>) {
464 match protocol {
465 Protocol::Send {
466 from,
467 to,
468 continuation,
469 ..
470 } => {
471 roles.insert(from.name().to_string());
472 roles.insert(to.name().to_string());
473 self.collect_roles_from_protocol(continuation, roles);
474 }
475 Protocol::Broadcast {
476 from,
477 to_all,
478 continuation,
479 ..
480 } => {
481 roles.insert(from.name().to_string());
482 for to in to_all {
483 roles.insert(to.name().to_string());
484 }
485 self.collect_roles_from_protocol(continuation, roles);
486 }
487 Protocol::Choice { role, branches, .. } => {
488 roles.insert(role.name().to_string());
489 for branch in branches {
490 self.collect_roles_from_protocol(&branch.protocol, roles);
491 }
492 }
493 Protocol::Case { branches, .. } => {
494 for branch in branches {
495 self.collect_roles_from_protocol(&branch.protocol, roles);
496 }
497 }
498 Protocol::Timeout {
499 role,
500 body,
501 on_timeout,
502 on_cancel,
503 ..
504 } => {
505 roles.insert(role.name().to_string());
506 self.collect_roles_from_protocol(body, roles);
507 self.collect_roles_from_protocol(on_timeout, roles);
508 if let Some(on_cancel) = on_cancel.as_deref() {
509 self.collect_roles_from_protocol(on_cancel, roles);
510 }
511 }
512 Protocol::Loop { body, .. } => {
513 self.collect_roles_from_protocol(body, roles);
514 }
515 Protocol::Parallel { protocols } => {
516 for p in protocols {
517 self.collect_roles_from_protocol(p, roles);
518 }
519 }
520 Protocol::Rec { body, .. } => {
521 self.collect_roles_from_protocol(body, roles);
522 }
523 Protocol::Begin { continuation, .. }
524 | Protocol::Await { continuation, .. }
525 | Protocol::Resolve { continuation, .. }
526 | Protocol::Invalidate { continuation, .. }
527 | Protocol::Extension { continuation, .. }
528 | Protocol::Let { continuation, .. }
529 | Protocol::Publish { continuation, .. }
530 | Protocol::PublishAuthority { continuation, .. }
531 | Protocol::Materialize { continuation, .. }
532 | Protocol::Handoff { continuation, .. }
533 | Protocol::DependentWork { continuation, .. } => {
534 self.collect_roles_from_protocol(continuation, roles);
535 }
536 Protocol::Var(_) | Protocol::End => {}
537 }
538 }
539
540 fn emit_uninformed_role_error(&mut self, choice: &ChoiceKnowledge, role: &str) {
542 let mut diagnostic = Diagnostic::error(
543 DiagnosticCode::ChoicePropagationError,
544 format!(
545 "Role '{}' cannot determine which branch was selected in {}",
546 role,
547 choice.choice_id.display()
548 ),
549 );
550
551 diagnostic.suggestions.push(format!(
553 "Add a message from '{}' to '{}' in each branch to inform about the choice",
554 choice.choice_id.chooser, role
555 ));
556
557 self.add_branch_participation_notes(&mut diagnostic, choice, role);
558 self.add_informed_roles_note(&mut diagnostic, choice);
559 self.add_typo_suggestion(&mut diagnostic, role);
560 diagnostic
561 .notes
562 .push(format!("Choice branches: {:?}", choice.branches));
563
564 if let Some(ref loc) = choice.location {
565 diagnostic.location = Some(loc.clone());
566 }
567
568 self.diagnostics.add(diagnostic);
569 }
570
571 fn add_branch_participation_notes(
572 &self,
573 diagnostic: &mut Diagnostic,
574 choice: &ChoiceKnowledge,
575 role: &str,
576 ) {
577 let present_branches: Vec<&str> = choice
578 .branch_participation
579 .iter()
580 .filter(|(_label, roles)| roles.contains(role))
581 .map(|(label, _)| label.as_str())
582 .collect();
583 let absent_branches: Vec<&str> = choice
584 .branch_participation
585 .iter()
586 .filter(|(_label, roles)| !roles.contains(role))
587 .map(|(label, _)| label.as_str())
588 .collect();
589
590 if !present_branches.is_empty() && !absent_branches.is_empty() {
591 diagnostic.notes.push(format!(
592 "'{}' participates in branch(es): {} but NOT in: {}",
593 role,
594 present_branches.join(", "),
595 absent_branches.join(", ")
596 ));
597 } else if !present_branches.is_empty() {
598 diagnostic.notes.push(format!(
599 "'{}' participates in: {}",
600 role,
601 present_branches.join(", ")
602 ));
603 }
604 }
605
606 fn add_informed_roles_note(&self, diagnostic: &mut Diagnostic, choice: &ChoiceKnowledge) {
607 if choice.informed_roles.is_empty() {
608 return;
609 }
610 let informed_list: Vec<String> = choice
611 .informed_roles
612 .iter()
613 .map(|(r, src)| format!("{} ({})", r, src.description()))
614 .collect();
615 diagnostic
616 .notes
617 .push(format!("Informed roles: {}", informed_list.join(", ")));
618 }
619
620 fn add_typo_suggestion(&self, diagnostic: &mut Diagnostic, role: &str) {
621 if let Some(suggestion) = self.find_similar_role(role) {
622 diagnostic
623 .suggestions
624 .push(format!("Did you mean '{}'?", suggestion));
625 }
626 }
627
628 pub fn find_similar_role(&self, role: &str) -> Option<&String> {
630 let threshold = 2; self.roles
633 .iter()
634 .filter(|r| r.as_str() != role)
635 .min_by_key(|r| super::levenshtein_distance(role, r))
636 .filter(|r| super::levenshtein_distance(role, r) <= threshold)
637 }
638
639 #[must_use]
641 pub fn declared_roles(&self) -> &[String] {
642 &self.roles
643 }
644
645 #[must_use]
647 pub fn is_role_declared(&self, role: &str) -> bool {
648 self.roles.iter().any(|r| r == role)
649 }
650}