Skip to main content

lumen_compiler/compiler/
constraints.rs

1//! Constraint validation for `where` clauses on record fields.
2
3use crate::compiler::ast::*;
4
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum ConstraintError {
9    #[error("invalid constraint on field '{field}' at line {line}: {message}")]
10    Invalid {
11        field: String,
12        line: usize,
13        message: String,
14    },
15}
16
17/// Validate that all `where` constraints are well-formed.
18/// Constraints can use: length(), count(), matches(), comparisons, and/or/not.
19pub fn validate_constraints(program: &Program) -> Result<(), Vec<ConstraintError>> {
20    let mut errors = Vec::new();
21
22    for item in &program.items {
23        if let Item::Record(r) = item {
24            for field in &r.fields {
25                if let Some(ref constraint) = field.constraint {
26                    validate_constraint_expr(constraint, &field.name, &mut errors);
27                }
28            }
29        }
30    }
31
32    if errors.is_empty() {
33        Ok(())
34    } else {
35        Err(errors)
36    }
37}
38
39fn validate_constraint_expr(expr: &Expr, field: &str, errors: &mut Vec<ConstraintError>) {
40    match expr {
41        Expr::BinOp(lhs, op, rhs, _) => match op {
42            BinOp::And
43            | BinOp::Or
44            | BinOp::Eq
45            | BinOp::NotEq
46            | BinOp::Lt
47            | BinOp::LtEq
48            | BinOp::Gt
49            | BinOp::GtEq
50            | BinOp::Add
51            | BinOp::Sub
52            | BinOp::Mul
53            | BinOp::Div
54            | BinOp::FloorDiv
55            | BinOp::Mod
56            | BinOp::Pow
57            | BinOp::Concat
58            | BinOp::In
59            | BinOp::BitAnd
60            | BinOp::BitOr
61            | BinOp::BitXor
62            | BinOp::Shl
63            | BinOp::Shr => {
64                validate_constraint_expr(lhs, field, errors);
65                validate_constraint_expr(rhs, field, errors);
66            }
67            _ => {
68                errors.push(ConstraintError::Invalid {
69                    field: field.to_string(),
70                    line: expr.span().line,
71                    message: format!("unsupported operator '{}' in constraint", op),
72                });
73            }
74        },
75        Expr::UnaryOp(UnaryOp::Not, inner, _) => {
76            validate_constraint_expr(inner, field, errors);
77        }
78        Expr::Call(callee, _args, span) => {
79            // Only allow known constraint functions: length, count, matches
80            if let Expr::Ident(name, _) = callee.as_ref() {
81                match name.as_str() {
82                    "length" | "count" | "matches" | "is_valid_email" => {}
83                    _ => {
84                        errors.push(ConstraintError::Invalid {
85                            field: field.to_string(),
86                            line: span.line,
87                            message: format!("unknown constraint function '{}'", name),
88                        });
89                    }
90                }
91            }
92        }
93        Expr::Ident(_, _)
94        | Expr::IntLit(_, _)
95        | Expr::FloatLit(_, _)
96        | Expr::StringLit(_, _)
97        | Expr::BoolLit(_, _)
98        | Expr::ListLit(_, _) => {}
99        _ => {
100            errors.push(ConstraintError::Invalid {
101                field: field.to_string(),
102                line: expr.span().line,
103                message: "unsupported expression in constraint".to_string(),
104            });
105        }
106    }
107}