plotnik_lib/query/
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 rowan::TextRange;
7
8use super::Query;
9use super::invariants::ensure_both_branch_kinds;
10use crate::diagnostics::DiagnosticKind;
11use crate::parser::{AltExpr, AltKind, Branch, Expr};
12
13impl Query<'_> {
14    pub(super) fn validate_alt_kinds(&mut self) {
15        let defs: Vec<_> = self.ast.defs().collect();
16        for def in defs {
17            let Some(body) = def.body() else { continue };
18            self.validate_alt_expr(&body);
19        }
20
21        assert!(
22            self.ast.exprs().next().is_none(),
23            "alt_kind: unexpected bare Expr in Root (parser should wrap in Def)"
24        );
25    }
26
27    fn validate_alt_expr(&mut self, expr: &Expr) {
28        if let Expr::AltExpr(alt) = expr {
29            self.check_mixed_alternation(alt);
30            assert!(
31                alt.exprs().next().is_none(),
32                "alt_kind: unexpected bare Expr in Alt (parser should wrap in Branch)"
33            );
34        }
35
36        for child in expr.children() {
37            self.validate_alt_expr(&child);
38        }
39    }
40
41    fn check_mixed_alternation(&mut self, alt: &AltExpr) {
42        if alt.kind() != AltKind::Mixed {
43            return;
44        }
45
46        let branches: Vec<Branch> = alt.branches().collect();
47        let first_tagged = branches.iter().find(|b| b.label().is_some());
48        let first_untagged = branches.iter().find(|b| b.label().is_none());
49
50        let (tagged_branch, untagged_branch) =
51            ensure_both_branch_kinds(first_tagged, first_untagged);
52
53        let tagged_range = tagged_branch
54            .label()
55            .map(|t| t.text_range())
56            .unwrap_or_else(|| branch_range(tagged_branch));
57
58        let untagged_range = branch_range(untagged_branch);
59
60        self.alt_kind_diagnostics
61            .report(DiagnosticKind::MixedAltBranches, untagged_range)
62            .related_to("tagged branch here", tagged_range)
63            .emit();
64    }
65}
66
67fn branch_range(branch: &Branch) -> TextRange {
68    branch.text_range()
69}