Compile-time constant expressions
Design document · Original source
RFCs record designs and changes. A proposal appearing here does not mean its feature is ready to use. Explore current language support
Status recorded in the original: Proposed
Bound original source · SHA-25669cc87b7b2e17f4118fb4c6efa8ecc84bbdca8557672fb5af57e1927d6984a2e
- Status: proposed
- Target contract:
0.1.0-experimental - Feature flag:
experimental.comptimeConst=true - CLI negotiation:
--experimental-comptime-const - Owner: N/M language
- Depends on: NM-RFC-0013 token frontend; stable legacy compatibility
- Does not change: stable N/M 0.1 literal-valued global
const Intsubstitution when the flag is off
1. Problem
N/M currently accepts decimal literals in structural positions such as QReg<3>, qreg[3], repeat 4, for i in 0..3, sample 1024, collect<64> and Angle[4]. Stable N/M 0.1 also performs source-wide textual substitution for explicitly typed, literal-valued global declarations such as const N: Int = 3;. It does not strictly evaluate const N: Int = BASE + 1;, preserve the source expression as structural metadata or isolate substitution to typed structural positions.
Generic oracles and typed collection each solve a narrow form of the same problem. This RFC introduces one closed compile-time integer evaluator instead of adding another feature-specific parser.
2. Goals
- Reuse named
const Intvalues in every structural integer position. - Fold a deliberately small integer expression before runtime or export.
- Preserve the original source expression for formatter and IR round trips.
- Keep accepted stable literal and literal-valued
const Intprograms byte-identical when negotiation is absent. - Replace feature-specific textual expansion with typed structural folding as each position enters the staged implementation.
- Produce one diagnostic family across Core, CLI, Worker, LSP and VS Code.
3. Non-goals
- Runtime-sized
QRegor arrays. - Type-level functions,
sqrt,floor, exponentiation or conditional expressions. param, measurement, function result or mutable classical values in a structural expression.- Changing statevector/stabilizer limits.
- General user functions or generalized circuit signatures; those belong to later RFCs.
4. Source model
module scalable_ghz;
const N: Int = 6;
const LAST: Int = N - 1;
const SHOTS: Int = 2 * 512;
fn main() {
let q: QReg<N> = qreg[N];
H(q[0]);
for i in 0..LAST {
CNOT(q[i], q[i + 1]);
}
sample SHOTS {
let bits: BitVec<N> = measure_all(q[0..LAST]);
}
}The allowed expression grammar is closed:
comptime-expression = sum ;
sum = product, { ("+" | "-"), product } ;
product = unary, { ("*" | "/" | "%"), unary } ;
unary = [ "+" | "-" ], primary ;
primary = integer | const-int-reference |
"(", comptime-expression, ")" ;Division is exact integer division. A non-zero remainder is not rounded and is reported as NM-COMPTIME-006. Remainder uses truncation toward zero: a % b = a - trunc(a / b) * b. Every intermediate value must remain a safe signed JavaScript integer in the reference implementation.
5. Name and type rules
- A referenced constant must be declared earlier in source order.
- The declaration must explicitly be
const name: Int = .... Unannotated constants are not structural inputs in contract 0.1. Angle,Float,param, measurement and runtime bindings are prohibited.- Shadowing a structural constant in a nested block is prohibited in 0.1.
- Cycles are impossible under the earlier-declaration rule and still fail closed if encountered through imported metadata.
6. Structural positions and budgets
| Position | Result rule | Existing budget retained |
|---|---|---|
QReg<E> / qreg[E] | positive integer and equal sizes | backend maxQubits |
Angle[E] | positive integer | E <= 4096 vector expansion ceiling |
repeat E | positive integer | 32 effective iterations; larger requests warn and cap |
for i in A..B | non-negative bounds, B >= A | 32 effective iterations; larger ranges warn and cap |
sample E | positive integer | 4096 effective shots; larger requests warn and cap |
collect<E> | positive integer | E <= 4096, no silent cap |
BitVec<E> and slices | positive/full-width match | 100-bit result and owning-register bounds |
This RFC does not increase any budget. It changes only how a structural integer is written.
7. Diagnostics
| Code | Meaning |
|---|---|
NM-COMPTIME-001 | expression-valued structural syntax used without negotiation after stable literal substitution |
NM-COMPTIME-002 | unknown, forward, cyclic or runtime-dependent reference |
NM-COMPTIME-003 | result is not an integer or is not positive where required |
NM-COMPTIME-004 | folded result exceeds the owning structural budget |
NM-COMPTIME-005 | param, Float, Angle or runtime binding used structurally |
NM-COMPTIME-006 | division by zero, non-exact division or safe-integer overflow |
Diagnostics are emitted at the structural use site. A secondary related location may point to the declaration. No partial circuit is produced after a comptime error.
8. AST and IR
Runtime-facing numeric fields retain their current folded number. Structural nodes add optional source metadata:
type NMComptimeSource = {
sourceExpression: string;
foldedValue: number;
referencedConstants: string[];
};For example, a register declaration keeps size: 6 and may additionally carry sizeSource. Strict readers reject unknown future fields or inconsistent foldedValue values. Printer output uses sourceExpression; execution, verification and export use only the folded number.
8.1 Closed structure-provenance carrier
Reconstructing an equivalent structured program from the executable AST is a separate operation from formatting an original source document. The source formatter remains CST-backed and preserves comments and trivia. The AST printer may reconstruct Angle<E>, repeat E and for i in A..B only when the program carries a valid NMComptimeStructureProvenance record:
type NMComptimeStructureProvenance = {
experimental: true;
contractVersion: "0.1.0-experimental";
nodes: NMComptimeStructureNode[];
};
type NMComptimeStatementSpan = {
statementStart: number;
statementCount: number;
};
type NMComptimeStructureNode =
| {
kind: "vector-param";
line: number;
name: string;
size: NMComptimeSource;
elements: NMComptimeAngleExpression[];
scalarNames: string[];
declarationSpan: NMComptimeStatementSpan;
}
| {
kind: "repeat";
line: number;
count: NMComptimeSource;
effectiveIterations: number;
body: NMComptimeGateTemplate[];
expansionSpan: NMComptimeStatementSpan;
}
| {
kind: "for-range";
line: number;
variable: string;
start: NMComptimeSource;
endExclusive: NMComptimeSource;
effectiveIterations: number;
body: NMComptimeGateTemplate[];
expansionSpan: NMComptimeStatementSpan;
};NMComptimeAngleExpression is a closed expression tree. Its only node kinds are finite numeric literal, PI, scalar parameter reference, vector-element reference, the owning for-range induction variable, unary +/-, and binary +, -, *, /. The tree is re-evaluated in a closed environment; it does not carry a second trusted resolved-value field. A vector-element reference names the owning vector and a non-negative literal element index. An induction node is valid only inside the body of its owning for-range. Arbitrary source strings are not accepted as angle-expression provenance.
type NMComptimeAngleExpression =
| { kind: "literal"; value: number }
| { kind: "pi" }
| {
kind: "scalar-reference";
binding: "const" | "param";
name: string;
}
| {
kind: "vector-element";
name: string;
index: number;
}
| { kind: "induction"; name: string }
| {
kind: "unary";
operator: "+" | "-";
operand: NMComptimeAngleExpression;
}
| {
kind: "binary";
operator: "+" | "-" | "*" | "/";
left: NMComptimeAngleExpression;
right: NMComptimeAngleExpression;
};
type NMComptimeIndexTemplate =
| { kind: "literal"; value: number }
| { kind: "induction"; offset: number };
type NMComptimeGateTemplate = {
gate: NMGateCall["gate"];
targets: Array<{
register: string;
index?: NMComptimeIndexTemplate;
}>;
angles: NMComptimeAngleExpression[];
};NMComptimeGateTemplate is also closed and typed. It contains a gate from the current N/M gate union, ordered targets and ordered angle expressions. A target is either a whole named register or a named-register element whose index is one of:
- a non-negative literal;
- the owning
for-rangeinduction variable; or - that induction variable plus or minus a non-negative literal offset.
The carrier does not contain comments, whitespace, localized keyword spelling, circuit-call spelling or arbitrary executable source. A canonical AST print may therefore be semantically and structurally equivalent without being byte-equal to the original document. Byte/trivia preservation remains exclusively the responsibility of formatNMSourceWithCST.
8.2 Normative validation
A carrier is valid only if all checks below succeed. Validation is fail-closed and runs before AST printing, strict sidecar acceptance or sidecar import.
- The object has the exact closed schema and contract version above. Unknown fields, node kinds, expression kinds and gate names are rejected.
- Nodes are in source encounter order. Statement starts are inside the closed interval
0..program.statements.length; counts are non-negative; and spans are sorted, non-overlapping and wholly insideprogram.statements. A zero span is valid only for an empty typed gate body. A non-empty zero-iterationfor-rangehas no executable AST evidence for its body and therefore remains non-printable. Multiple valid zero spans at the same insertion cursor retain node encounter order. - Every
NMComptimeSourceexactly matches the corresponding declaration/use record by line, position, source expression, folded value and referenced constant set. - A
vector-paramsize equalselements.length,scalarNames.lengthand its declaration span length. Scalar names must be unique and must equal the loweredNMParamDeclnames in order. Re-evaluating each closed angle tree must reproduce the lowered finite parameter value exactly. - A
repeatexpansion span length equalseffectiveIterations * body.length.effectiveIterationsmust equal the owning runtime policy result, including the existing 32-iteration cap. Re-lowering the typed body that many times must reproduce every statement in the span exactly, excluding the provenance record itself. - A
for-rangeeffective count must equal the owning runtime policy result forendExclusive - start, including the existing 32-iteration cap. Substituting the induction value and resolving typed target indices for every iteration must reproduce the entire expansion span exactly. - Every vector-element reference must resolve to its owning vector node and index. Every surviving scalar gate reference that corresponds to a vector scalar name must be mapped back to that vector element during canonical printing. Ambiguous or dangling aliases are rejected.
- Requested and effective values must satisfy the owning cap policy. When a parse result or sidecar diagnostic array is available, required cap diagnostics must also be present and consistent. The AST-only printer does not own a diagnostic array and therefore validates the policy values without inventing, suppressing or downgrading diagnostics.
Validation compares semantic AST fields, including gates, target registers, resolved target indices, angle values/names and statement order. Source line numbers may be normalized by a canonical print and are not an equivalence escape hatch for a semantic mismatch.
8.3 Canonical printing and mutation rules
The printer walks statements and provenance nodes together. At the start of a valid span it emits one canonical vector declaration, repeat block or for-range block, then skips the covered lowered statements. Other statements are printed normally. Vector aliases in uncovered gates are printed as name[index] only after the alias check in Section 8.2 succeeds.
Missing provenance keeps the current comptime-expanded-structure-source-preservation unsupported reason. Present but invalid provenance returns comptime-structure-provenance-invalid; the printer must not silently fall back to emitting duplicated expanded statements. Tooling that mutates any covered statement must either update and revalidate the carrier atomically or delete the affected node and make structured AST printing fail closed.
Contract 0.1 does not nest provenance nodes and does not reconstruct original circuit calls. A gate-only canonical body may replace a source circuit call if its independently re-lowered statements are identical. Nested expanded structures require a later contract revision.
8.4 IR and strict sidecar projection
NMProgram.comptimeStructures and NMIR.module.comptimeStructures carry the same record. The strict sidecar reader repeats every Section 8.2 check after it has validated constants and before it accepts executable statements. Import uses only a validated carrier; otherwise it rejects the sidecar instead of inventing structured source. A digest may identify a carrier in tooling but is not a signature, trust proof or substitute for recomputation.
9. Frontend behavior
- Legacy stable frontend remains authoritative while NM-RFC-0013 Stage B is unapproved.
- Both frontends use the same frozen evaluator and diagnostic contract. The token frontend captures and lowers the structural expression independently and participates in full-result parity for supported sources.
- Shared evaluator utilities may be reused, but token semantic lowering cannot call the legacy parser or its source preprocessor to claim independent parity.
- Unsupported feature combinations fail closed and never trigger automatic frontend fallback.
10. Runtime and export
No generic expression reaches runtime. Monomorphized/expanded statements carry the same numeric dimensions as equivalent literal source. QASM and framework exporters therefore need no new executable construct; strict sidecars preserve the source metadata and negotiation record. The canonical writer is nm-qasm3-sidecar@0.2: its reader rejects unknown fields, re-evaluates every declaration and structural use in encounter order, checks the folded value and reference set exactly, reapplies the owning structural budget, validates the closed structure-provenance carrier and verifies it against the embedded IR projection. The permanent 0.1 reader remains supported and rejects the new carrier field instead of silently widening its frozen schema. A carrier-free 0.1 document migrates explicitly and losslessly to 0.2; a legacy document whose comptime uses include expanded Angle, repeat or for structures stays readable as 0.1 but requires source recompilation because a conforming carrier cannot be invented from folded statements. QASM3 sidecar import reconstructs named const Int declarations, all previously supported structural expressions, and validated Angle<E>, repeat E and for A..B structures. Invalid or missing provenance never falls back to guessed source.
11. Tooling
- CLI check/run/debug/estimate/export/lint/test/explain/transpile accept
--experimental-comptime-const. The source-preserving CLI formatter keeps its CST path; Playground's AST formatter may reconstruct supported expanded structures only after the closed carrier validates. - Worker requests carry
experimental.comptimeConst: trueexplicitly. - LSP and VS Code use
initializationOptions.nm.experimental.comptimeConst. - Playground exposes a default-off control and retains it in shared links.
- The localized Comptime Inspector publishes the closed 6-preset by 5-base scenario matrix from real parser output. It shows requested and effective values separately, the owning budget, diagnostics and an exact Playground deep link. Its SHA-256 fingerprint identifies the displayed compiler artifact but is not a signature or a provenance attestation.
- Hover shows the expression, folded value and referenced constants.
- Formatting never replaces
qreg[N]withqreg[6].
12. Staged implementation
- Freeze diagnostics, evaluator and hostile expression fixtures.
- Add parser option and stable flag-off rejection without execution changes.
- Support
QReg<E>/qreg[E>and preserve source metadata. - Add
repeat,for,sample,collect, arrays, BitVec and slices. - Implement independent token semantic lowering and full parity corpus.
- Add printer, IR, strict reader and export-sidecar parity.
- Add CLI, Worker, Playground, LSP and VS Code negotiation.
- Run fuzz, performance, exact Node/OS matrix and named review.
Stages are acceptance slices, not separate language versions. The capability remains experimental until the complete closed structural-position set is implemented.
Revision 9 implementation state: Stages 1 through 7 are implemented for the closed structural-position table in both the Core and independent token frontends. Requested expressions and folded values are retained in program/IR metadata; sample keeps its stable warning-and-cap policy while collect remains strict. CLI, Worker, Playground, LSP and VS Code all require explicit negotiation. The QASM3 strict reader/export sidecar revalidates and preserves the complete metadata record. Core and token frontends emit byte-identical closed carriers; canonical AST printing and strict sidecar 0.2 import both validate before reconstructing expanded structures. The localized Comptime Inspector renders a fixed 30-scenario matrix directly from parser-produced metadata and now exposes carrier nodes and spans where required. The Stage 8 fuzz, exact Node/OS and named-review matrix remains open. The capability therefore remains proposed and experimental.
13. Acceptance criteria
- Flag off: all accepted stable parser, printer, IR and runtime fixtures remain byte-identical; previously unsupported expression-sized registers fail with the frozen
NM-COMPTIME-001diagnostic. const Nsource produces the same executable AST/runtime result as the equivalent literal source, excluding explicit source metadata.- Formatter round-trips original expressions and comments.
- Every diagnostic has positive, negative, EN and TR fixtures.
- Unknown identifiers, forward references, type misuse, divide-by-zero, non-exact division, overflow, negative/zero results and budget overflow fail closed.
- Legacy and independent token frontends agree on the complete
NMParseResultacross stable and all-experimental profiles. - Core and token frontends produce identical closed structure-provenance nodes for vector parameters, bounded repeat and bounded for-range fixtures.
- Parse -> canonical AST print -> parse preserves the executable statements and comptime metadata for each supported structure. Missing, overlapping, out-of-range, dangling-alias, value-mismatched and expansion-mismatched carriers fail closed.
- CLI, Worker, Playground, LSP and VS Code expose identical negotiation state.
- The Comptime Inspector exposes exactly 30 deterministic compiler-produced scenarios, including the
repeat 40 -> 32andsample 5000 -> 4096warning-and-cap boundaries, without describing them as simulated results. - Exact-candidate Windows/Linux Node support artifacts and named review are retained before preview evaluation.
14. Open decisions
sample Eretains its existing warning-and-cap behavior. The requested folded value remains visible in comptime metadata while runtimesampleShotsstores the effective value.collect<E>remains strict.- Whether imported public constants are included in contract 0.1 or deferred to the exact-import follow-up.
Until these decisions close, status remains proposed; implementation may stay behind the experimental flag but cannot be advertised as preview.
15. Revision history
- Revision 1 (2026-08-09): opens the closed integer-expression grammar, diagnostic family, structural-position matrix, source-preserving AST/IR carrier and staged cross-surface implementation plan.
- Revision 2 (2026-08-09): records the pre-existing literal-valued global
const Intsubstitution, implements negotiatedQReg<E>/qreg[E>folding in both frontends, preserves source metadata through printer/IR and adds browser Worker/Playground negotiation without widening runtime limits. - Revision 3 (2026-08-09): completes the closed structural-position table for
Angle,repeat,for,sample,collect,BitVecand inclusive register slices in both frontends; freezes the existing sample cap policy and retains the 32/4096/100 owning budgets. - Revision 4 (2026-08-09): carries explicit
comptimeConstnegotiation through CLI JSON output, Language Server initialization and VS Code settings; all developer and browser tooling surfaces now default the capability off. - Revision 5 (2026-08-09): preserves the complete NM-RFC-0014 metadata in the QASM3 strict sidecar, re-evaluates expressions and budgets on read, rejects tampering and reconstructs named constants plus register dimension expressions during sidecar import.
- Revision 6 (2026-08-09): adds the localized compiler-backed Comptime Inspector and its fixed 30-scenario contract. The revision explicitly keeps signed provenance and expanded
repeat/forplusAngle<E>AST printer reconstruction outside the inspector claim. - Revision 7 (2026-08-09): specifies the closed, typed and independently re-lowered structure-provenance carrier required before the AST printer may reconstruct
Angle<E>,repeat Eorfor i in A..B. Raw-source shortcuts, silent fallback, nested carriers and trust claims remain prohibited. - Revision 8 (2026-08-09): implements the carrier AST/IR types and the fail-closed validator for exact schema, source-use coverage, ordered spans, cap policy, typed expressions, vector aliases and gate re-lowering. Frontend emission, strict sidecar transport and canonical printer support remain open.
- Revision 9 (2026-08-09): implements closed-carrier emission in the Core and independent token frontends, canonical fail-closed AST reconstruction, strict QASM3 sidecar
0.2transport/import, permanent0.1reading with an explicit lossless migration for carrier-free documents and source-recompile rejection for legacy expanded structures, hostile mutation fixtures and carrier-node visibility in the localized Comptime Inspector. Stage 8 promotion evidence remains open.