# How KCL Rule Constraint Validation Works: A Deep Dive into the Compiler Architecture

> Explore KCL rule constraint validation. Discover its two-phase pipeline: static semantic analysis for type and schema checks, then runtime evaluation for user-defined expressions.

- Repository: [The KCL Programming Language/kcl](https://github.com/kcl-lang/kcl)
- Tags: deep-dive
- Published: 2026-03-05

---

**KCL validates rule constraints through a two-phase pipeline: static semantic analysis for type-checking and schema validation, followed by runtime evaluation of user-defined check expressions against concrete values.**

The KCL (Kusion Configuration Language) compiler enforces configuration correctness through a sophisticated rule constraint validation system implemented in the `kcl-lang/kcl` repository. This mechanism operates across both compile-time semantic analysis and runtime evaluation phases to ensure that configuration data adheres to defined schemas and custom validation logic.

## The Two-Phase Validation Architecture

KCL's rule constraint system bridges static compilation and runtime execution through distinct but tightly coupled phases. The **semantic resolver** ensures structural correctness before code execution, while the **runtime evaluator** executes boolean constraints against actual configuration values.

| Phase | Source File | Primary Function |
|-------|-------------|------------------|
| **Semantic Analysis** | [`crates/sema/src/resolver/config.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/config.rs) | Validates config entries, types, and schema definitions statically |
| **Runtime Evaluation** | [`crates/evaluator/src/rule.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/rule.rs) | Executes rule bodies and evaluates `check` expressions against concrete values |

The semantic phase guarantees that each `check` expression is well-typed and schema-compliant, while the runtime phase performs the actual constraint validation that can trigger user-defined error messages.

## Semantic-Phase Constraint Checking

During compilation, the resolver validates configuration expressions before any code execution occurs. This static checking catches type mismatches and undefined attributes early in the pipeline.

### Entry Point: Config Expression Validation

When the resolver encounters a **ConfigExpr** (a configuration literal), it invokes `check_config_entry` as the primary entry point for attribute-level validation.

```rust
// crates/sema/src/resolver/config.rs
pub(crate) fn check_config_entry(&mut self, key: &str, value: &ValueRef) -> TypeRef {
    // …
    self.check_config_expr_by_key_name(name, key);
    // …
}

```

This function extracts the attribute name and delegates to key-level validation routines that verify the attribute exists within its parent schema's type definition.

### Type Context and Attribute Resolution

The `check_config_expr_by_key_name` function retrieves the current object type from the `config_expr_context` stack and forwards the key to `must_check_config_attr` for concrete validation.

```rust
pub(crate) fn check_config_expr_by_key_name(
    &mut self,
    name: &str,
    key: &'ctx ast::NodeRef<ast::Expr>,
) {
    // Obtain the object type from the context stack
    if !name.is_empty() && let Some(Some(obj)) = self.ctx.config_expr_context.last() {
        let ty = obj.ty.clone();
        self.must_check_config_attr(name, &ty, &key.get_span_pos(), None);
    }
}

```

The `must_check_config_attr` function acts as a dispatcher, determining which specific validation rule applies (`check_defined`, `check_type`, etc.) before invoking `check_config_attr` to handle schema mapping and error reporting via `kcl_error::SEMANTIC_ERROR_MSG`.

### Recursive Schema Validation

For nested configurations, `check_config_value_recursively` walks into dict literals and nested schemas, invoking `check_attr_recursively` for each nested attribute. This ensures that **all** constraints defined in parent schemas are verified statically, including inherited attributes from base schemas.

## Runtime-Phase Rule Evaluation

After successful compilation, the evaluator executes rule bodies and their associated `check` blocks against concrete configuration instances.

### Rule Evaluation Context Creation

The `rule_body` function in [`crates/evaluator/src/rule.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/rule.rs) initializes the runtime environment for rule execution.

```rust
// crates/evaluator/src/rule.rs
pub fn rule_body(
    s: &Evaluator,
    ctx: &RuleEvalContextRef,
    args: &ValueRef,
    kwargs: &ValueRef,
) -> ValueRef {
    // ...
}

```

The **RuleEvalContext** encapsulates:
- The rule's AST node
- The current schema value being validated
- The configuration under validation
- Auxiliary metadata including `config_meta` and `optional_mapping`

### Rule Body Execution Flow

The evaluator executes the rule body through a structured sequence:

1. **Base Schema Handling** — For sub-rules (indicated by `for_host_name`), the evaluator invokes the parent schema's constructor via `call_schema_body_from_rule`
2. **Scope Management** — `push_schema` and `enter_scope` create fresh variable environments, with arguments bound through `walk_arguments`
3. **Decorator Evaluation** — All rule decorators execute via `walk_decorator_with_name`
4. **Constraint Invocation** — For sub-schemas (marked by `is_sub_schema`), the system calls `rule_check`

### Executing Check Expressions

The `rule_check` function implements the core constraint validation logic, handling both inheritance and local constraint evaluation.

```rust
pub fn rule_check(
    s: &Evaluator,
    ctx: &RuleEvalContextRef,
    args: &ValueRef,
    kwargs: &ValueRef,
) -> ValueRef {
    // ① Call base-rule checks (inheritance)
    for parent_name in &ctx.borrow().node.parent_rules {
        let base_constructor_func = s
            .walk_identifier_with_ctx(&parent_name.node, &ast::ExprContext::Load, None)
            .expect(kcl_error::RUNTIME_ERROR_MSG);
        call_rule_check(s, &base_constructor_func, args, kwargs);
    }

    // ② Execute the rule's own check expressions
    for check_expr in &ctx.borrow().node.checks {
        s.walk_check_expr(&check_expr.node)
            .expect(kcl_error::RUNTIME_ERROR_MSG);
    }
    ctx.borrow().value.clone()
}

```

**Parent Rule Checks** — If the rule extends other rules (via `parent_rules`), their checks execute first, ensuring constraint inheritance from base rules.

**Local Check Evaluation** — Each `check_expr` represents an AST node containing a boolean expression (optionally with an error message). The `walk_check_expr` method evaluates this expression; if it returns false, the evaluator raises a runtime error containing the user-defined message.

### VM Integration

The `call_rule_check` and `call_schema_body_from_rule` functions serve as thin proxies to the runtime VM (implemented in [`crates/runtime/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/lib.rs)). These helpers fetch compiled function objects and invoke them while preserving the evaluation stack state.

## Complete Execution Flow

The end-to-end validation process follows this pipeline:

1. **Parsing** — The AST contains `RuleStmt` nodes with associated `checks` arrays
2. **Semantic Resolution** — `check_config_entry` validates static constraints while building the type context
3. **Context Creation** — The evaluator instantiates a `RuleEvalContext` for the target rule
4. **Body Execution** — `rule_body` runs initialization logic and decorator checks
5. **Constraint Validation** — `rule_check` iterates parent rules and local expressions, calling `walk_check_expr` on each
6. **Error Reporting** — Violations trigger runtime errors with precise source spans from the AST

## Key Source Files

Understanding KCL's constraint validation requires familiarity with these core components:

- **[`crates/sema/src/resolver/config.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/config.rs)** — Static constraint checking for config expressions and schema attributes
- **[`crates/evaluator/src/rule.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/rule.rs)** — Runtime rule evaluation, body execution, and check block processing
- **[`crates/runtime/src/lib.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/lib.rs)** — Low-level VM integration for `call_rule_check` and related proxy functions
- **[`crates/ast/src/ast.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/ast/src/ast.rs)** — Definitions for `RuleStmt`, `CheckExpr`, and related AST structures
- **[`crates/evaluator/src/evaluator.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/evaluator/src/evaluator.rs)** — Core evaluation primitives including `walk_check_expr` and scope management

## Summary

- **Two-phase validation** separates static type checking (semantic resolver) from runtime constraint evaluation (rule evaluator)
- **Semantic analysis** in [`config.rs`](https://github.com/kcl-lang/kcl/blob/main/config.rs) validates configuration structure through `check_config_entry` and recursive attribute checking
- **Runtime evaluation** uses `RuleEvalContext` to maintain state while `rule_check` executes inherited and local constraints
- **Inheritance support** allows rules to extend parent rules, with `rule_check` automatically invoking parent validations before local checks
- **AST-based reporting** preserves source spans throughout the pipeline for precise error localization

## Frequently Asked Questions

### How does KCL handle rule inheritance during constraint validation?

When `rule_check` executes, it iterates through the `parent_rules` collection in the rule's AST node before evaluating local checks. For each parent, it resolves the base rule's identifier and invokes `call_rule_check`, ensuring parent constraints execute recursively. This guarantees that sub-rules satisfy all constraints defined in their ancestor rules.

### What happens when a runtime check expression fails?

The evaluator calls `walk_check_expr` on each check expression in the rule's `checks` array. If the boolean evaluation returns false, the runtime raises an error using the message specified in the `CheckExpr` AST node. This error bubbles up through the evaluation stack with precise source span information from the original AST, allowing the error reporter to highlight the exact line in the KCL source code.

### Can semantic analysis catch all constraint violations before runtime?

No. The semantic phase in [`crates/sema/src/resolver/config.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/config.rs) validates structural correctness—ensuring attributes exist, types match, and schema definitions are well-formed. However, user-defined `check` blocks containing arbitrary boolean expressions (e.g., `check age >= 18`) require runtime evaluation because they depend on concrete configuration values that are only available during execution.

### How does the evaluator maintain isolation between different rule evaluations?

The `RuleEvalContext` struct encapsulates all state specific to a single rule evaluation, including the rule's AST node, the schema value being validated, and configuration metadata. The `rule_body` function creates fresh scopes using `push_schema` and `enter_scope`, ensuring that variables and mutations within one rule execution do not leak into subsequent evaluations.