plotnik_compiler/analyze/validation/
alt_kinds.rs

1//! Semantic validation for the typed AST.
2//!
3//! Checks constraints that are easier to express after parsing:
4//! - Mixed tagged/untagged alternations
5
6use crate::SourceId;
7use crate::analyze::invariants::ensure_both_branch_kinds;
8use crate::analyze::visitor::{Visitor, walk, walk_alt_expr};
9use crate::diagnostics::{DiagnosticKind, Diagnostics};
10use crate::parser::{AltExpr, AltKind, Branch, Root};
11
12pub fn validate_alt_kinds(source_id: SourceId, ast: &Root, diag: &mut Diagnostics) {
13    let mut visitor = AltKindsValidator { diag, source_id };
14    visitor.visit(ast);
15}
16
17struct AltKindsValidator<'a> {
18    diag: &'a mut Diagnostics,
19    source_id: SourceId,
20}
21
22impl Visitor for AltKindsValidator<'_> {
23    fn visit(&mut self, root: &Root) {
24        assert!(
25            root.exprs().next().is_none(),
26            "alt_kind: unexpected bare Expr in Root (parser should wrap in Def)"
27        );
28        walk(self, root);
29    }
30
31    fn visit_alt_expr(&mut self, alt: &AltExpr) {
32        self.check_mixed_alternation(alt);
33        assert!(
34            alt.exprs().next().is_none(),
35            "alt_kind: unexpected bare Expr in Alt (parser should wrap in Branch)"
36        );
37        walk_alt_expr(self, alt);
38    }
39}
40
41impl AltKindsValidator<'_> {
42    fn check_mixed_alternation(&mut self, alt: &AltExpr) {
43        if alt.kind() != AltKind::Mixed {
44            return;
45        }
46
47        let branches: Vec<Branch> = alt.branches().collect();
48        let first_tagged = branches.iter().find(|b| b.label().is_some());
49        let first_untagged = branches.iter().find(|b| b.label().is_none());
50
51        let (tagged_branch, untagged_branch) =
52            ensure_both_branch_kinds(first_tagged, first_untagged);
53
54        let tagged_range = tagged_branch
55            .label()
56            .expect("tagged branch found via filter must have label")
57            .text_range();
58
59        self.diag
60            .report(
61                self.source_id,
62                DiagnosticKind::MixedAltBranches,
63                untagged_branch.text_range(),
64            )
65            .related_to(self.source_id, tagged_range, "tagged branch here")
66            .emit();
67    }
68}