# How KCL's Automatic Merge Mechanism Handles Complex Configuration Merging

> Discover how KCL's automatic merge handles complex configurations. Learn about its three-stage AST transformation for deterministic outputs.

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

---

**KCL automatically merges configuration files during the pre-process phase using a three-stage AST transformation pipeline that collects declarations, unifies entries based on merge kinds, and eliminates redundant statements to produce deterministic configuration outputs.**

The KCL configuration language ([kcl-lang/kcl](https://github.com/kcl-lang/kcl)) eliminates manual configuration consolidation through its **automatic merge mechanism**, which reconciles overlapping definitions across multiple files during compilation. This process operates entirely within the semantic analysis phase, ensuring that unification logic remains predictable and order-independent for complex infrastructure definitions.

## Three-Stage Merge Pipeline

The merge implementation lives in [`crates/sema/src/pre_process/config.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/pre_process/config.rs), where the `ConfigMergeTransformer` executes three sequential passes over the abstract syntax tree.

### Stage 1: Collecting Declarations

The transformer indexes every top-level name that introduces a configuration, including unification statements and schema assignments. For each declaration, it stores the filename, module index, statement index, and a **merge kind** classification—either `Union` for standard unifications or `Override` for schema assignments and private fields. This collection logic resides in `ConfigMergeTransformer::merge` (lines 20‑48).

### Stage 2: Merging Config Entries

When a name appears in multiple modules, the transformer gathers the individual `ConfigEntry` lists, concatenates them, and normalizes the result using `unify_config_entries`. 

- **Union** merges retain all entries, allowing values to coexist
- **Override** merges clear previous entries before inserting the new definition, ensuring the latest assignment wins

This logic appears in the match arms for `ConfigMergeKind::Union` and `ConfigMergeKind::Override` (lines 94‑128), with the normalization helper defined at lines 96‑138.

### Stage 3: Cleaning Up Redundant Statements

After the merged configuration replaces the final occurrence, the transformer prunes earlier duplicate statements from the AST. This elimination pass (lines 131‑152) prevents duplicate evaluation while preserving the semantic intent of the consolidated configuration.

## Handling Complex Configuration Scenarios

### Nested Attribute Merges

Before merging occurs, the `ConfigNestAttrTransformer` rewrites dot-notation expressions such as `{a.b.c = 1}` into hierarchical config entry trees (`a: {b: {c = 1}}`). This transformation occurs in `fix_config_expr_nest_attr` (lines 152‑156), ensuring nested attributes merge correctly regardless of their original syntactic form.

### Private Fields

Fields prefixed with underscores (e.g., `_secret`) trigger **Override** semantics even when declared within unification contexts. The `is_private_field` check (lines 61‑68) forces the declaration list to clear before inserting the private entry, guaranteeing that sensitive or internal values dominate previous definitions.

### Mixed Union and Override Semantics

The mechanism handles scenarios where the same identifier uses different merge kinds across files. The index stores the *last* encountered kind for each name, and the transformer selects the appropriate `Union` or `Override` branch based on this stored classification. This guarantees consistent merge semantics even when configuration files disagree on merge strategy.

### Recursive Config Values

`unify_config_entries` recursively descends into `Config` and `Schema` nodes (lines 124‑138), flattening nested structures before performing deduplication. This recursion ensures that configurations nested inside schemas merge with the same deterministic rules as top-level declarations.

### Multiple Files with Identical Module Names

To prevent collisions when identical filenames appear in different packages, modules are keyed by both **filename** and **module index** throughout the collection and merging passes (see line 22). This distinction ensures隔离 between packages while permitting intentional merges within the same logical module.

## Practical Merge Example

Consider a base configuration and an overlay that modify the same resource:

```kcl

# base.k

config = {
    name = "app"
    replicas = 2
    env = {
        LOG_LEVEL = "info"
    }
}

```

```kcl

# overlay.k

config = {
    replicas = 3               # Union: value overridden by later entry

    env = {
        DEBUG = "true"          # Union: additive merge with base.env

    }
    _secret = "top"            # Private field => Override semantics

}

```

Compile with automatic merging enabled (default):

```bash
kcl run base.k overlay.k

```

The resulting configuration applies **Union** merging to `env` (combining both maps) and `replicas` (overriding with the final value), while the private field `_secret` uses **Override** to ensure it replaces any previous definition:

```yaml
config:
  name: app
  replicas: 3
  env:
    LOG_LEVEL: info
    DEBUG: true
  _secret: top

```

## Disabling Automatic Merge

For debugging scenarios requiring unmerged AST inspection, pass the `--no-merge` flag. This option sets `merge_program` to `false` in `ResolverOptions` (see [`crates/sema/src/resolver/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/mod.rs), line 182), bypassing the `ConfigMergeTransformer` entirely.

## Summary

- The **automatic merge mechanism** operates during the pre-process phase in [`crates/sema/src/pre_process/config.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/pre_process/config.rs)
- Three distinct passes collect declarations, unify entries using `Union` or `Override` semantics, and prune redundant AST nodes
- **Union** merges combine entries additively while **Override** merges replace previous definitions entirely
- Private fields (prefixed with `_`) force Override behavior regardless of context
- Nested attributes are normalized via `fix_config_expr_nest_attr` before merging occurs
- The runtime complements this with `dict_merge` in [`crates/runtime/src/value/val_dict.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/runtime/src/value/val_dict.rs) (lines 337‑353) for dynamic dictionary operations

## Frequently Asked Questions

### How does KCL resolve conflicts when the same field is defined in multiple files?

KCL resolves conflicts using the **merge kind** associated with each declaration. Standard unifications use **Union** semantics that preserve all entries but allow later values to override earlier ones for scalar fields. Schema assignments and private fields use **Override** semantics that explicitly clear previous definitions. The transformer applies the strategy recorded during the collection pass (lines 20‑48) when merging entries (lines 94‑128).

### What is the difference between Union and Override merge kinds in KCL?

**Union** merges concatenate `ConfigEntry` lists and keep all historical values, enabling additive configuration patterns where maps and lists accumulate across files. **Override** merges clear the existing entry list before inserting the new definition, ensuring complete replacement. The mechanism selects between these behaviors based on the stored `ConfigMergeKind` enum value associated with each identifier.

### Can I disable automatic merging for specific configuration blocks?

The **automatic merge mechanism** is a global compilation setting controlled by the `merge_program` flag in `ResolverOptions`. You cannot selectively disable merging for individual blocks within a single compilation unit. To prevent merging for specific files, compile them separately or use the `--no-merge` CLI flag to disable the entire pipeline, as implemented in [`crates/sema/src/resolver/mod.rs`](https://github.com/kcl-lang/kcl/blob/main/crates/sema/src/resolver/mod.rs) (line 182).

### How does KCL handle deeply nested configuration overrides?

Deeply nested attributes are normalized before merging occurs. The `ConfigNestAttrTransformer` rewrites dot-notation paths like `a.b.c = 1` into nested config entry trees. The `unify_config_entries` function then recursively processes these trees (lines 124‑138), applying Union or Override semantics at each level to ensure nested structures merge with the same deterministic rules as top-level attributes.