ADR 0054 — Bare name in cell context
- Status: accepted
- Date: 2026-05-22
- Spec target: XTL 0.1
- Affects: language.md § "Source Columns" / "Group Keys", evaluation.md, error catalog (ADR-0015)
Context
language.md § "Source Columns" says:
Bare names such as
{{ Customer }}are not source column references in cells. Bare names are reserved for sheet and file group keys.
This pins the non-meaning (bare names ≠ column refs in cells) but
not the error contract. What actually happens when an author
writes {{ Customer }} in a data cell?
The grammar's primary_expr production allows literal,
function_call, bracket_field, source_bracket_field,
reserved_ref, and parenthesized expression. A bare identifier
Customer (no parens) does NOT match function_call (needs ())
and does NOT match any other primary. The grammar therefore rejects
it as a parse error — but the spec does not say which error code,
and the reference impl currently treats unmatched bare names as
literal pass-through (the entire {{ Customer }} block evaluates
as the string "Customer" or falls through to "unknown name"
depending on the parser branch).
A parallel ambiguity exists for the filename pattern:
{{ Customer }}_report.xlsx. Per language.md § "Group Keys", a
filename MAY contain a bare group key. If Customer is not
declared as a group key (no output_file_pattern group-key
binding) and not a runtime input, what is the error?
Three silent fallthroughs identified in the reference impl
(src/template-eval.ts):
- Bare identifier in a cell expression → falls through to
evalExpression's "treat as literal string" branch, producing the literal string"Customer"in output. Author thinks they referenced a column; they got a static label. - Bare identifier in a sheet name pattern → resolves to the group-key value if declared, else silently treats as literal.
- Bare identifier in a filename pattern → same as sheet name.
All three are user-hostile silences in the ADR-0027 / ADR-0029 audit-pass theme.
Considered Options
A. Raise a stable error for any unresolved bare name. A bare
identifier in a cell that does not resolve to a declared input,
group key (for filename / sheet name), or __config__ author key
raises xl3/expression/unknown-name. Pro: fail-loud, matches
ADR-0027's "convert silent fallthrough into a coded error" pattern.
Con: a new error code.
B. Treat any bare name as a literal string by spec. Spec the
current impl behavior as the contract: {{ Customer }} in a cell
evaluates to the literal "Customer". Pro: zero impl change. Con:
this is the user-hostile silence; locks it in.
C. Reject at parse only in cells; allow in sheet/file patterns. Bare names error in cells, resolve to group keys in patterns. Pro: matches the existing "Bare names are reserved for sheet and file group keys" prose. Con: needs context-dependent parsing — parser must know which scope it's in.
Decision
Adopt A with the cell vs pattern split implicit in resolution context.
Normative rules (added to language.md § "Source Columns")
A bare identifier inside a template block resolves in this order:
- Filename pattern context (the
output_file_patternvalue). Resolves to a declared file-group key if one exists. Otherwise resolves to a runtime input value (__inputs__[name]shorthand). Otherwise resolves to a__config__author-defined value (__config__[name]shorthand). Otherwise raisesxl3/expression/unknown-name. - Sheet name pattern context (the sheet's template name).
Same order: sheet-group key, then runtime input, then config
author value. Otherwise raises
xl3/expression/unknown-name. - Data cell context. Bare identifiers in data cells resolve
in the same order as filename/sheet patterns: enclosing file
group key → enclosing sheet group key →
__inputs__[name]shorthand →__config__[name]shorthand. Bare identifiers do NOT resolve to source columns (column references in cells MUST use the explicit[Column]bracket form). If a bare identifier in a cell does not match any of the resolution sources, the parser raisesxl3/expression/unknown-name.
The shorthand resolution is consistent across all three contexts. The behavior change vs. pre-ADR is: previously, unmatched bare identifiers in data cells silently fell through to the string fallback (literal "Department"); now they raise.
Existing fixtures and templates that use {{ GroupKey }} in data
cells to reference the active file/sheet group's value (e.g.,
fixtures 006, 007 in the conformance corpus) continue to work —
the bare name resolves through the group-key chain. The ADR is
specifically targeted at typo-class silences ("user wrote bare
name expecting a column reference and got the literal string").
Error code
New stable code: xl3/expression/unknown-name. Per ADR-0015
append-only catalog. Diagnostic substring (stable for fixtures):
Unknown name
<ident>— bare identifiers in cell expressions must be[Column],__config__[key],__inputs__[name], or a function call. For sheet / file patterns, declare<ident>as a group key.
The same code covers shape (1)/(2)/(3) — the message disambiguates context.
Why not silent fallthrough
The shape-1 silence ("bare {{ Customer }} in cell becomes literal
Customer") is the same user-hostile pattern as the ADR-0027
empty-directive case: the author thinks they got data; they got a
label. The fix is one-token: {{ [Customer] }}. The cost of
loudness is paid back by the saved debug time.
Consequences
- Templates that wrote
{{ Customer }}in a cell expecting a column value now error at parse time with an actionable diagnostic. Migration: rename to{{ [Customer] }}. - One new error code added to ADR-0015 catalog and snapshot.
- The "bare name shorthand in sheet/file patterns" path remains unchanged in observable behavior; the resolution order is now written down.
- Conformance fixture additions:
149-bare-name-in-data-cell-error—{{ Foo }}in a data cell raisesxl3/expression/unknown-name.150-bare-name-in-filename-resolves-to-input—{{ month }}inoutput_file_patternresolves to the__inputs__[month]value when nomonthgroup key exists.151-bare-name-in-filename-unknown-error—{{ Foo }}inoutput_file_patternwith noFooinput, config key, or group key raisesxl3/expression/unknown-name.
References
- ADR-0011 — Reserved sheet naming (
__sheet__pattern; the bracket-vs-bare distinction) - ADR-0015 — Stable error codes (catalog append)
- ADR-0027 — Reserved column names + directive validation (the silent-fallthrough → coded-error pattern this ADR mirrors)
- language.md § "Source Columns" (the "bare names not column refs" prose this ADR converts to an error)
- language.md § "Group Keys" (filename pattern with bare names)
src/template-eval.ts