Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions .claude/commands/sprint-5-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ Read, in order:
> for block scoping, now based at the frame rather than at a single global base.
> `GetLocal`/`SetLocal` decode against the active frame's base. No dictionary, no
> string lookup.
> - **Recursion.** Falls out of the call frame with no special handling — a
> function that calls itself pushes another frame. The only bound is the frame
> array.
> - **Callee resolution — a top-level `fn` is a global, resolved late.** A
> function name at a call site resolves to its **global slot** (a `GetGlobal`-style
> read), not a direct compile-time `BytecodeFunction` reference baked into the
> call. The fixed script structure puts every `fn` declaration above the top-level
> code, so all functions are defined before any code runs; resolving the callee by
> global slot at the call is what makes a **forward** call (to a function declared
> later in the file) and **mutual** recursion work at runtime, not just at
> type-check. Compiling a direct `BytecodeFunction` reference instead would break
> on forward order — do not.
> - **Recursion — direct and mutual.** Falls out of the call frame and the global
> callee resolution with no special handling: a function that calls itself, or two
> functions that call each other (`isEven`/`isOdd`), each push another frame and
> resolve the callee by global slot. The only bound is the frame array.
> - **Stack overflow (E5901).** When the frame count would exceed the fixed
> `CallFrame[256]` capacity, raise the `RuntimeError` **E5901** (call-stack
> overflow) through its `ErrorCatalog` descriptor, carrying the source line. The
Expand Down Expand Up @@ -158,8 +168,10 @@ narrowing.**
its own `Chunk`. Parameters occupy the first local slots; `Return` is emitted
for explicit returns and for the end of a non-void body per the spec's return
rule.
4. **Compiler — calls.** Emit the callee, the argument expressions in order, then
`Call` with the argument count.
4. **Compiler — calls.** Resolve the callee to its **global slot** (not a direct
compile-time `BytecodeFunction` reference), emit that read, then the argument
expressions in order, then `Call` with the argument count. Global-slot resolution
is what makes forward and mutual references work at runtime.
5. **VM — `Call` / `Return` dispatch.** Implement the two handlers against the
`CallFrame[256]` array and the stack-base arithmetic in
`grob-vm-architecture.md`. `GetLocal`/`SetLocal` resolve against the active
Expand Down Expand Up @@ -207,6 +219,10 @@ Per §3.5, route each test to the project matching its kind.
the expected depth (the callee's locals and the callee value are discarded on
`Return`).
- A directly recursive function (e.g. factorial) computes correctly.
- **Mutual recursion** (an `isEven`/`isOdd` pair where each calls the other, the
second declared after the first) computes correctly — proving a forward
function reference resolves at **runtime** via the global slot, not only at
type-check.
- A non-terminating recursion raises **E5901** as a `RuntimeError` and does
**not** crash the host with a CLR `StackOverflowException`.
- **Integration tests (`Grob.Integration.Tests`):**
Expand Down
106 changes: 0 additions & 106 deletions docs/design/_pending/D-318-drop-in.md

This file was deleted.

43 changes: 43 additions & 0 deletions docs/design/grob-decisions-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ ubiquity not quality. Python owns education but is dynamically typed. Grob targe
| D-315 | June 2026 | Control flow | `break`/`continue` in `select`: asymmetric. `break` inside `select` is a compile error (E2211) at any nesting; `continue` passes through to the nearest enclosing loop. `select` is not loop-control-transparent. Resolves Requirements/Fundamentals contradiction; E2212 added for `break`/`continue` outside any loop |
| D-316 | June 2026 | Tooling — quality gate | Corpus consistency regime: one-time A1 reconciliation (stale error-code total 99→103 and stale Sprint-1 status row corrected) plus a permanent CI-enforced drift gate (`tests/Grob.Consistency.Tests`) asserting error-code count agreement, decisions-log lockstep, ADR-reference integrity and opcode/TokenKind completeness, with D-308's `ErrorCatalog` agreement test referenced as the catalog↔registry guard. Generalises D-308; self-relative checks, no frozen baseline |
| D-317 | June 2026 | Tooling — supply chain | Project hardening interlude (Sprint 4→5): central package management with exact pins and committed lockfiles under locked-mode restore, NuGet and CodeQL vulnerability gates, deterministic builds with SourceLink, SHA-pinned least-privilege workflows, gitleaks CI secret scanning, and CycloneDX SBOM and build-provenance scaffolding. Additive repository hardening; complements §11 language/runtime security, does not overlap it. Code signing deferred to first public release (OQ-018) |
| D-318 | June 2026 | Type system — named-arg diagnostics | Named-argument call-site errors (D-113) get dedicated codes E0008–E0011 — named-before-positional, naming a required parameter, duplicate named argument, unknown parameter name — rather than folding into E0003. Registered in Sprint 5 Increment B through their `ErrorCatalog` descriptors |

---

Expand Down Expand Up @@ -3087,6 +3088,42 @@ Superseded by: none

---

### D-318 — Named-argument call-site diagnostics get dedicated codes (June 2026)

Area: Type system — named-argument diagnostics
Supersedes: none
Superseded by: none

The four named-argument call-site errors D-113 specifies — a named argument
before a positional one, a named argument naming a required (defaultless)
parameter, a duplicate named argument, and a named argument naming an unknown
parameter — get four dedicated error codes rather than folding into E0003 (wrong
number of arguments):

- E0008 — named argument before positional
- E0009 — named argument names a required parameter
- E0010 — duplicate named argument
- E0011 — unknown parameter name

Dedicated codes, not a fold into E0003, because each of the four is a distinct
mistake with a distinct fix, and the diagnostics are part of the product: E0008
wants "move named arguments after positional ones", E0009 "this parameter has no
default, pass it positionally", E0010 "this parameter is already supplied", E0011
"no parameter named X — did you mean Y?". Folding all four into E0003 would
mis-describe them (none is an arity error) and collapse four targeted suggestions
into one generic message, against the error-message quality bar. The alternative
considered — reuse E0003 for arity-shaped cases and E0004 for the rest — was
rejected for the same reason: the call-site binding errors are a category of their
own, not instances of arity or type mismatch, which still apply on the bound
argument set after binding succeeds.

The codes occupy the next-free slots in the E00xx Type block (E0006 and E0007
taken). Registered in Sprint 5 Increment B through their `ErrorCatalog` descriptors
(D-308); immutable once shipped (ADR-0017). Full calling-convention detail is in
`grob-language-fundamentals.md` (the named-argument rules) and D-113.

---

## Post-MVP Decisions

---
Expand Down Expand Up @@ -3308,6 +3345,12 @@ _(Full detail in `grob-vm-architecture.md`)_
---

_This document is the authoritative decisions record for Grob._
_Updated June 2026 — D-318: the four named-argument call-site errors (D-113)_
_assigned dedicated codes E0008–E0011 — named-before-positional, naming a_
_required parameter, duplicate named argument, unknown parameter name — rather_
_than folding into E0003, since none is an arity error and each carries a_
_distinct fix. Registered in Sprint 5 Increment B through their `ErrorCatalog`_
_descriptors; no codes added to the catalog by this entry._
_Updated June 2026 (interlude B) — D-317: project hardening interlude. Additive_
_supply-chain and build-integrity hardening of the repository: central package_
_management with exact pins and committed lockfiles under locked-mode restore,_
Expand Down
26 changes: 21 additions & 5 deletions prompts/archive/sprint-5/sprint-5-a.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,19 @@ Read, in order:
> for block scoping, now based at the frame rather than at a single global base.
> `GetLocal`/`SetLocal` decode against the active frame's base. No dictionary, no
> string lookup.
> - **Recursion.** Falls out of the call frame with no special handling — a
> function that calls itself pushes another frame. The only bound is the frame
> array.
> - **Callee resolution — a top-level `fn` is a global, resolved late.** A
> function name at a call site resolves to its **global slot** (a `GetGlobal`-style
> read), not a direct compile-time `BytecodeFunction` reference baked into the
> call. The fixed script structure puts every `fn` declaration above the top-level
> code, so all functions are defined before any code runs; resolving the callee by
> global slot at the call is what makes a **forward** call (to a function declared
> later in the file) and **mutual** recursion work at runtime, not just at
> type-check. Compiling a direct `BytecodeFunction` reference instead would break
> on forward order — do not.
> - **Recursion — direct and mutual.** Falls out of the call frame and the global
> callee resolution with no special handling: a function that calls itself, or two
> functions that call each other (`isEven`/`isOdd`), each push another frame and
> resolve the callee by global slot. The only bound is the frame array.
> - **Stack overflow (E5901).** When the frame count would exceed the fixed
> `CallFrame[256]` capacity, raise the `RuntimeError` **E5901** (call-stack
> overflow) through its `ErrorCatalog` descriptor, carrying the source line. The
Expand Down Expand Up @@ -158,8 +168,10 @@ narrowing.**
its own `Chunk`. Parameters occupy the first local slots; `Return` is emitted
for explicit returns and for the end of a non-void body per the spec's return
rule.
4. **Compiler — calls.** Emit the callee, the argument expressions in order, then
`Call` with the argument count.
4. **Compiler — calls.** Resolve the callee to its **global slot** (not a direct
compile-time `BytecodeFunction` reference), emit that read, then the argument
expressions in order, then `Call` with the argument count. Global-slot resolution
is what makes forward and mutual references work at runtime.
5. **VM — `Call` / `Return` dispatch.** Implement the two handlers against the
`CallFrame[256]` array and the stack-base arithmetic in
`grob-vm-architecture.md`. `GetLocal`/`SetLocal` resolve against the active
Expand Down Expand Up @@ -207,6 +219,10 @@ Per §3.5, route each test to the project matching its kind.
the expected depth (the callee's locals and the callee value are discarded on
`Return`).
- A directly recursive function (e.g. factorial) computes correctly.
- **Mutual recursion** (an `isEven`/`isOdd` pair where each calls the other, the
second declared after the first) computes correctly — proving a forward
function reference resolves at **runtime** via the global slot, not only at
type-check.
- A non-terminating recursion raises **E5901** as a `RuntimeError` and does
**not** crash the host with a CLR `StackOverflowException`.
- **Integration tests (`Grob.Integration.Tests`):**
Expand Down
26 changes: 26 additions & 0 deletions src/Grob.Compiler/Compiler.Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,27 @@ private static GrobType GetSwitchExprResultType(SwitchExprNode node) {
return result;
}

// -----------------------------------------------------------------------
// Call (Sprint 5 Increment A — positional)
// -----------------------------------------------------------------------

/// <inheritdoc/>
/// <remarks>
/// Emits the callee, then the arguments in source order, then
/// <see cref="OpCode.Call"/> with the argument count as its 1-byte operand. The
/// pushed arguments become the callee's first locals over the new frame base.
/// Built-in <c>print</c>/<c>exit</c> calls in statement position are intercepted
/// by <see cref="VisitExpressionStmt"/> and never reach here.
/// </remarks>
public override object? VisitCall(CallExpr node) {
int line = node.Range.Start.Line;
Visit(node.Callee);
foreach (CallArgument arg in node.Arguments) Visit(arg.Value);
_chunk.WriteOpCode(OpCode.Call, line);
_chunk.WriteByte(ToByteOperand(node.Arguments.Count, "call argument count"), line);
return null;
}

// -----------------------------------------------------------------------
// Member access (optional chaining)
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -642,6 +663,11 @@ private static OpCode ThrowUnsupportedBinaryOp(BinaryOperator op, GrobType type)
BinaryExpr b => GetBinaryResultType(b),
TernaryExpr t => GetTernaryResultType(t),
SwitchExprNode s => GetSwitchExprResultType(s),
// A call to a user function resolves to that function's declared return type,
// so a surrounding operator selects the right typed opcode. Built-in and
// unresolved callees stay Unknown.
CallExpr { Callee: IdentifierExpr { Declaration: FnDecl fn } }
=> TypeChecker.ResolveTypeRef(fn.ReturnType),
_ => GrobType.Unknown
};

Expand Down
Loading
Loading