Skip to content

spec(gazprea): consolidated spec-review stack — nested types + audit fixes - #139

Draft
Sir-NoChill wants to merge 61 commits into
masterfrom
spec/consolidated-review
Draft

spec(gazprea): consolidated spec-review stack — nested types + audit fixes#139
Sir-NoChill wants to merge 61 commits into
masterfrom
spec/consolidated-review

Conversation

@Sir-NoChill

Copy link
Copy Markdown
Collaborator

Consolidated spec-review stack — 20 PRs in one

This collapses the stacked-PR chain (gh stack #121, formerly [stack 1/20][stack 20/20], PRs #110#138) into a single PR against master. The 20 individual PRs are closed; their branches and comment threads stay readable at the links in the manifest below. All 40 commits are preserved (nothing squashed) — use the Files changed tab for the cumulative diff, or the commit list for the logical steps.

Footprint: 35 files, +1576 / −229. Spec: 24 files under gazprea/spec/. Infra: the .agents/ reproducibility scaffold + pyproject.toml/uv.lock (from #110).

Provenance: grew out of the vector-semantics discussion (#106) and the Aug-17 rubric-meeting action item (#132). The final nested-types change closes #106, #82, #71, #101, #86.


⚠️ Cross-PR interactions — review the NET result, not each step

The stack intentionally states a rule and later revises it. Review the final tree, not the intermediate commits:


Open questions flagged for reviewer attention (per former PR)

These are the ambiguities each PR flagged as needing a language-owner decision (verify against gazc / the reference compiler where noted).

#122 — combined struct decl form: does struct S (...) x; accept a qualifier (var struct S (...) x;)? The "combined form is always const" claim is inferred from examples; if gazc accepts var, it's wrong.

#123 — mixed concatenation result: is char || char even legal? (patch's "(or characters)" asserts char||char = char[].) Is the rule "at least one operand is string" or "either operand"? char[] || char[] = char[] is unexemplified.

#124 — tuple member conversions: members "convert by their kind" lists only scalar + array. Nested tuple, string, matrix members are unaddressed — now relevant post-#138. Confirm array-in-tuple casts pad/truncate like standalone array casts.

#125 — uninit const & stride: by is restricted to >= 1 (rules out by -1 reverse iteration — intended?). Uninit const is defined as "holds the zero value permanently" — confirm that vs. treating uninit-const as a type error.

#126 — slice bounds: confirm ..-i selects 1..n-i; confirm the 1-indexed assertion doesn't contradict another chapter; confirm grammar accepts a[i..j] by 2 (vs a[i..j by 2]).

#127 — struct fixed-length field: patch chose character[256] over string[256]. If sized/bounded strings are intended, the fix differs. (See cross-PR note — the nesting ban it obeyed is now reversed.)

#128 — procedure call sites: are casts legal on a procedure-call result? (patch adds "and casts" to the operator whitelist.) Confirm the restatement's disallowed-site list is the exact inverse of the chapter-top allowed list.

#129 — method-call surface (largest feat:): (a) receivers are variables only — does gazc accept x.len() on a literal/expression result? (b) mutation limited to function-local vars vs. "no global/captured mutation" — which framing? (c) append tie-break = single-element wins — matches reference? (d) "method call is the only expression usable as a statement" — reconcile with call statements. (e) concatappend rename is breaking — does the reference still expose concat as an alias?

#114 — range vs array-index: is matrix slicing intended? (matrix.rst:103 still "indices must be integers"). No formal def of "range syntax" vs "range expression"; by-on-slice rule lives in a different chapter and may drift.

#115 — promotion vs cast: type_casting.rst:48 still says "promoted" for an explicit as<> cast; type_casting.rst:95 allows array→array casts across dimensions — confirm 1-D↔2-D explicit cast is intended.

#138 — nested aggregates & n-d arrays (deferred, not in scope): a rank-agnostic shape interface (vs rows/columns, per #82), n-d matrix-multiply, and broadcasting are left to a follow-up. The ragged (vector<vector<T>>) vs rectangular (vector<T[*]>, T[*][*]) split from #101 is implied but not yet spelled out operationally.

Lower-priority / follow-ups noted in-PR: #116 (string.rst:8 loose equivalence); #118 (integer.rst Associativity column duplication); #111 (expressions.rst:54 generator-error class; repo-wide "must emit" sweep); #112 (prose String/Vector capitalization left per maintainer decision). #119, #120, #130 flagged no open questions.


Infra (.agents/ scaffold, from #110) — open feedback digest

PR #110 drew 25 inline comments (@Sir-NoChill). Themes (several already addressed by later commits on the same branch — 223c45d drop identity prescription & generated artifacts, 6e2bbba point .agents/ at the DocsDoc image + rewrite skills, 66ae45f uv lockfile):

  • Don't prescribe identity — signing identity should be user-supplied via the manifest, not hardcoded; delete the vendored agent-pubkey.asc. (→ addressed by 223c45d.)
  • Don't commit generated filesbootstrap.sh/check.sh/healthcheck.sh are rendered from templates; gitignore them and regenerate on setup. (→ addressed by 223c45d.)
  • Prefer a Docker/CI image — make this a dev image in cmput415/ci-utils (optionally a nix flake); point the manifest/README at the container instead of a bash env harness. (→ partly addressed by 6e2bbba.)
  • Dependency management via uvpyproject.toml + uv sync on a uv-managed venv. (→ addressed by 66ae45f + pyproject.)
  • Templating via jinja2 — use the jinja2 package rather than a custom renderer (render.py was later approved: "This is perfect").
  • Rewrite the grammar-consistency skill — focus on English prose quality (spelling, passive voice, subject consistency, technical writing), not the Gazprea EBNF (that's the students' exercise); the spec-review skill should consume it after the rewrite. (→ addressed by 6e2bbba.)
  • Local CI via act rather than a hand-rolled check-ci.sh bash harness; add a .agents/scratch gitignore entry.

Full thread: #110.


Manifest — the 20 collapsed PRs

# Title Branch
#110 chore(agents): scaffold reproducible session bootstrap chore/agents-scaffold
#112 spec: replace undefined syntax in examples fix/undefined-syntax-examples
#113 spec: state the vector element-type set (reversed by #138) fix/vector-element-types
#116 spec: scope the vector-array equivalence claim fix/vector-array-divergence
#114 spec: distinguish range slices from array-valued indices fix/range-index-semantics
#115 spec: scope the promotion-implies-cast claim fix/promotion-cast-claim
#118 spec: single home for operator precedence refactor/precedence-single-home
#119 spec: single home for stream_state semantics refactor/stream-state-home
#120 spec: single home for const-by-default rule refactor/const-default-home
#111 spec: name error classes at anonymous error sites feat/errors-chapter
#122 spec: specify combined struct-plus-instance declaration fix/struct-decl-form
#123 spec: state the mixed concatenation result type fix/string-concat-result
#124 spec: tuple member conversions cover array members fix/tuple-conversion-members
#125 spec: close two silent contract gaps (uninit const, stride) fix/uninit-const-and-stride
#126 spec: unify slice bound rule in one section fix/slice-bounds
#127 spec: struct field / fixed-length field (nesting ban reversed by #138) fix/struct-nesting
#128 spec: reconcile procedure call-site restatement fix/procedure-call-sites
#129 spec: specify the method-call surface (largest feat) feat/method-calls
#130 spec: correct misleading examples (batched) fix/example-corrections
#138 spec: permit nested aggregates and n-d arrays feat/nested-composite-types

Consolidated from gh stack #121. Individual PRs closed in favour of this one; commit history preserved.

Manifest-driven apt/check/healthcheck scripts plus the session's
ephemeral agent signing identity, per the agent-bootstrap protocol.
Volatile paths (key material, baselines, agent-env.sh) are gitignored.

The scaffold is vendor-neutral: the directory is `.agents/`, the default
identity is generic ("Agent"), and the skills-directory path is a
manifest variable rather than hardcoded to any one agent tool's
convention.

Assisted-by: Agent <ai@blobfish.icu>
Applies review feedback from PR #110. Removes the committed rendered
scripts (bootstrap.sh, check.sh, healthcheck.sh) and the committed
public key (agent-pubkey.asc); each session regenerates the scripts
from the .tmpl files via render.py before running bootstrap.

Identity is now opt-in: the manifest's `agent:` block is blank by
default, and bootstrap only mints a GPG signing key (and writes
agent-env.sh) when a user fills it in locally. Users choose their own
signing identity; the repo prescribes none.

Python dependencies move to pyproject.toml under uv: bootstrap installs
uv from astral.sh if missing, then runs `uv sync` to provision a venv
pinning sphinx==6.2.1 alongside jinja2 and PyYAML (both needed by
render.py, which now uses stock Jinja2 with a `{## ##}` comment tag to
avoid colliding with bash's `${#arr[@]}` array-length syntax).

Adds an empty `.agents/skills/` directory as the location for bundled
review skills; `manifest.yaml`'s `skills:` list names entries under it,
and healthcheck asserts each listed skill has a non-empty SKILL.md.

BREAKING CHANGE: `.agents/bootstrap.sh`, `.agents/check.sh`, and
`.agents/healthcheck.sh` are no longer tracked. Existing checkouts
must render them once (`for t in .agents/*.tmpl; do
uv run .agents/render.py .agents/manifest.yaml "$t" >
"${t%.tmpl}"; done`) before running bootstrap. Users who had a signing
identity configured must re-populate the `agent:` block in their local
copy of manifest.yaml.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Populates .agents/skills/ with the two skills the repo will actually use
during specification review, and lists them in manifest.yaml so the
healthcheck notices if either goes missing.

spec-review is the editorial/structural checklist a human maintainer
runs before a spec chapter merges: build integrity (`sphinx-build -W -n`),
heading hierarchy, `:term:`/`:ref:`/`:doc:` cross-reference integrity,
admonition placement, gazc-backed sanity check on `.. code-block::
gazprea` examples, TODO/FIXME residue, and targeted sibling-file
consistency spot-checks.

grammar-consistency catches the cross-file syntactic-surface divergences
that PRs like #116 (vector-vs-array) and #118 (precedence single-home)
exist to fix. It compares the same grammar element (keyword, operator,
named rule, type-form) as it appears in different chapters and reports
disagreements without picking a winner -- consistency is orthogonal to
correctness.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Adds .agents/skills/spec-review/check-ci.sh, which runs the same two
checks CI does before a spec change hits the remote:

  * Sphinx build across every doc subdir in the top-level Makefile
    (setup, generator, lolcode, vcalc, gazprea, info), with -W -n so
    warnings and unresolved cross-references become errors -- stricter
    than the CI deploy step's own `make html`.
  * lychee over the exact file globs and args CI's linkcheck.yml uses,
    self-installing the binary from lycheeverse's installer or cargo
    when it is missing rather than silently skipping the check.

Sub-modes `sphinx` and `links` scope the run to one workflow's worth
of checks. The SKILL.md checklist's "build integrity" step now points
at the script instead of open-coding the sphinx-build invocation.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Address the review on #110:

- manifest.yaml now names ghcr.io/cmput415/docs-dev as the preferred
  environment; native apt/uv path stays as a fallback.
- README.md documents the docker-first flow, keeps the "regenerate on
  session start" note, and explicitly frames signing as opt-in and
  non-prescriptive.
- bootstrap.sh.tmpl trims the block-level commentary the reviewer
  called out; identity handling stays gated on a populated agent block.
- skills/grammar-consistency/SKILL.md is rewritten to audit English
  prose (spelling, passive voice, subject/tense, terminology,
  technical-writing anti-patterns). Deriving the Gazprea grammar from
  the informal spec is a student exercise and is now out of scope.
- skills/spec-review/SKILL.md swaps the hand-rolled check-ci.sh for
  act-based workflow replay (act ships in the DocsDev image) and
  points at grammar-consistency for prose review.
- Removes .agents/skills/spec-review/check-ci.sh (subsumed by act).

Assisted-by: Agent (claude) <ai@blobfish.icu>
The /tree/main/DocsDev path does not exist on cmput415/ci-utils and
tripped linkcheck. The repo root already documents the image.
pyproject.toml was committed without its lockfile, so `uv sync` resolved
freely on every checkout and the session environment was not actually
reproducible. Pin it.

Assisted-by: Agent (claude) <ai@blobfish.icu>
String and free len() do not exist (string, length(), or the .len()
method do), char is not a type (character is), and comma indexing
M[1, 2] is defined nowhere (composite M[1][2] is). Each example now uses
the spelling the spec defines.

Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "replace undefined syntax in examples" so the
patch's own examples read cleanly:

*   ``procedures.rst`` byvalue/byreference now call ``x.len()`` on the
    ``string`` argument instead of the free ``length(x)``.  ``string`` is
    a sub-type of ``vector<character>`` (types/string.rst:76) and
    ``vector`` defines ``.len()`` (types/vector.rst:65); using the method
    keeps the string-vs-vector surface consistent, which is the shape
    the rest of the spec assumes.

*   ``types/struct.rst`` renames the ``character char`` and ``real float``
    fields on the ``Another`` example to ``character c`` and ``real r``.
    Naming a field after a type from another language reads like a
    keyword clash even though Gazprea's grammar allows it; the new
    names follow the ``s1`` example's ``i``/``r``/``iv`` convention.
    Also fixes the paragraph that referred to the struct by the wrong
    name (``Struct type "s"`` -> ``s1``) and updates the field-name
    list to match.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up.  The previous rename (fcf3ad8) landed on
`character c, real r, string[256] str, s1 struct_field`, which put
Another's `r` right next to the sibling `s1` example's `r` field two
lines above.  Scoping is fine (each struct owns its field namespace),
but the collision reads as an accidental repeat in a two-line example
whose whole point is to show that field identifiers are chosen freely.

Rename to `character ch, real f, string[256] str, s1 struct_field`,
and update the prose that enumerates them.  Distinct short names,
no shadow of the neighbouring example, and still tracks the `s1`
`i`/`r`/`iv` pattern of using initials-of-the-type.

Assisted-by: Agent (claude) <ai@blobfish.icu>
vector<T> never said which T are legal, leaving vector<vector<T>>,
vector<tuple(...)>, and vector<string> undecidable. Now: base types and
1-D arrays of base types, matching every existing example.

Assisted-by: Agent <ai@blobfish.icu>
Human-review follow-up.  The new `vector<T>` element-type rule
enumerated the base types as `(boolean, character, integer, real)`,
which is inconsistent with the two other places the same set is
enumerated: `types/array.rst:6` and `constexpr.rst:22` both use
`(boolean, integer, real, character)`.  Any drift between these three
enumerations makes a reader wonder if the set itself changed at one
of the sites.

Reorder the vector rule to match.  Same set, no rot; if the set ever
does grow (e.g. `string` promoted to a base type), all three files
will need to change in lockstep and having them start identical makes
that easier.

Assisted-by: Agent (claude) <ai@blobfish.icu>
'Vectors behave exactly like arrays' papered over normative differences
the same file relies on: methods, array-valued binary results, and a
pad-to-first ragged policy that contradicts matrix pad-to-longest for
the same literal. The claim is now an interop list plus an explicit
enumeration of the differences, with a cross-reference at the padding
rule. Also replaces the undefined term 'subroutines'.

Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "scope the vector-array equivalence claim"
(ab6eeed).

Two lingering over-broad claims in ``types/vector.rst``:

*   The intro's differences list said "binary operations *involving a
    vector* produce array results", but the body (the "Operations"
    subsection just below) only supports that for *mixed* vector+array
    operations.  The vector+vector case is not stated anywhere.  Narrow
    the intro to match the body: "a mixed binary operation between a
    vector and an array".

*   The Operations subsection opened with "Operations on vectors are
    identical syntactically **and semantically** to operations on
    arrays" -- the exact over-broad equivalence the patch is scoping
    out of the intro.  Left in place, it re-introduces the paperover
    a few lines down.  Reword to "use the same syntax as ... and,
    except for the differences enumerated above, share their
    semantics" so the enumeration in the intro remains authoritative.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up.  Two related over-commits in the intro
paragraph now that the six spec-review PRs are viewed as a group:

*   "interoperate freely with arrays" — overstates the case once the
    vector element-type restriction (companion PR #113) lands.  A
    `vector<S>` where `S` is a struct never exists, so interop is
    "free" only over the element types both sides support.  Add the
    qualifier: "interoperate with arrays for the element types they
    both support".

*   "the differences are normative: [three-item list]" — presented as
    exhaustive.  Once PR #113 merges, a fourth normative difference
    (element-type set is narrower on vectors) is documented directly
    below, and a reader who trusts this enumeration as complete will
    miss it.  Soften to "differences include (non-exhaustively)" so
    the paragraph remains true after the neighbouring PR merges and
    tolerates future additions without re-editing.

Assisted-by: Agent (claude) <ai@blobfish.icu>
'Arrays cannot be indexed with array expressions' collided with ranges
being arrays and with slicing being defined as indexing by a range.
The rule now names the distinction: array values (including ranges bound
to variables) are illegal indices; literal range syntax in an index
position is the slice form.

Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "distinguish range slices from array-valued
indices" (ebe5f8d).

The patch scoped the general rule in statements.rst but left
`types/array.rst`'s indexing subsection saying only "An array may be
indexed using integers", which continued to contradict the very next
subsection defining slices as indexing by a range.  Extend the rule
here to match: an integer index yields an element, a range at the
index position yields a slice (with a cross-reference to the slices
subsection), and an array *value* (including a range bound to a
variable) is not a legal index.  Same distinction the statements.rst
hunk introduced, phrased for the type chapter's local vocabulary.

Assisted-by: Agent (claude) <ai@blobfish.icu>
…pression

Human-review follow-up.  The original rule at statements.rst:46 said
``v[w]`` is illegal "when ``w`` is an array variable", but the
companion rule at types/array.rst:260 (added on the same branch)
correctly says "an array *value* … is not a legal index".  Different
scopes for the same rule: an expression, a parenthesized value, or a
function call that returns an integer array is caught by the array.rst
wording and slips past the statements.rst one.

Widen statements.rst to "whenever ``w`` evaluates to an array value",
and spell out the class of expressions this covers.  Same rule stated
consistently in both chapters, no more gap on function-returned
ranges.

Assisted-by: Agent (claude) <ai@blobfish.icu>
The universal claim was false for string/character[*] (no as<> form
exists) and glossed the size requirement on scalar-to-array casts. The
vague 'higher dimension' exception is replaced with the concrete rule
(no 1-D to 2-D promotion; scalars broadcast), the square-matrix note is
scoped to ** operands, and the string promotion section now names the
array/vector distinction precisely.

Assisted-by: Agent <ai@blobfish.icu>
Follow-up polish on top of "scope the promotion-implies-cast claim"
(c087971).

*   The scalar-to-matrix promotion note used "a matrix of any shape".
    ``shape`` is a stdlib extension in this project, not part of the
    formal spec (a companion patch consciously scopes it out), and
    reusing the word here as a common noun invites conflation.
    Swap to "of any dimensions".

*   The Character-Array/String section's added tail clause
    ("a ``string`` used where a character array is expected, or
    vice-versa, converts silently") duplicated "implicitly converted"
    from the sentence just above it.  Drop the redundant clause; the
    "of note is between ``string`` and character *arrays*" pointer
    still carries the intended emphasis.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Human-review follow-up.  The first commit introduced a positional
forward reference ("see the final section of this chapter") from the
intro's second caveat down to the Character-Array/String section.
Positional references silently break when a later section is added
to the chapter — the "final section" is no longer the intended
target.

Add an `ssec:typePromotion_string` label on the section heading and
swap the intro's forward reference to `:ref:` against that label.
Same target today, robust against reordering, and readable inline
(Sphinx renders the section title).

Assisted-by: Agent (claude) <ai@blobfish.icu>
The precedence relation existed in three copies (expressions.rst,
integer.rst, boolean.rst); the per-type copies are the divergence trap
since any operator change must land in all three. The per-type pages now
reference the normative table in expressions.rst.

Assisted-by: Agent <ai@blobfish.icu>
… parens note

Two human-review follow-ups on the operator-precedence single-home
refactor:

*   `types/real.rst:55` still routed readers to the integer chapter's
    Operations subsection for "operation and precedence".  Now that
    integer.rst delegates upward instead of hosting the table, this
    is a two-hop indirection where the first hop no longer contains
    what the sentence promises.  Split the sentence: operations
    (semantics — IEEE-754-style behavior, unary rules, C99 remainder)
    still point at `sssec:integer_ops`; precedence and associativity
    point directly at the normative table.

*   The integer chapter's removed precedence paragraph carried a
    useful note that parentheses are absent from the list because
    they override precedence rather than participate in it.  The note
    is normative guidance about the table, not integer-specific, so
    lift it into `expressions.rst` alongside the table itself.

Assisted-by: Agent (claude) <ai@blobfish.icu>
The procedure was fully specified twice: only built_in_functions.rst
had the initial state, only streams.rst had the null-value/position
rule, and the two used different wording. streams.rst error handling is
now normative (codes, initial state, per-type table); built-ins keeps
the signature, marked as notional since input_stream is not a language
type. Also fixes the F 1.0 output that violated the %g rule, the
'characters have no error state' claim contradicted by the state table,
and the undefined 'null value' term.

Assisted-by: Agent <ai@blobfish.icu>
The rule was restated in three files; type_qualifiers.rst is now the
normative home and the misleading 'essentially a no-op' wording is
replaced. The other two sites cross-reference it.

Assisted-by: Agent <ai@blobfish.icu>
…e the last duplicate

Two human-review follow-ups on the const-by-default single-home
refactor:

*   The normative paragraph in `type_qualifiers.rst` now names the
    default-is-const rule explicitly, but the "both spellings are
    legal" corollary was only implicit ("writing ``const`` is
    therefore redundant").  A reader is left to infer that
    ``T x`` and ``const T x`` are exchangeable.  Add a one-sentence
    explicit statement of that equivalence so the normative section
    doesn't rely on the reader's inference.

*   `procedures.rst:9` is the last remaining restatement: "By default
    arguments are ``const`` just like functions."  Its scope
    (parameter-passing) is narrower than the variable-declaration
    rule and it is worth keeping in place for readers landing on the
    procedures chapter, but it should point at the normative home so
    it does not become a fourth divergent copy.  Add a
    `see :ref:sec:typeQualifiers` cross-reference.

Assisted-by: Agent (claude) <ai@blobfish.icu>
The impl chapter (sec:errors) defines the full error taxonomy, but a
dozen spec rules said only 'an error', leaving class and phase to guess.
Each site now names the class from the taxonomy and the first mention in
each file cross-references sec:errors. Also normalizes 'should raise' to
'must raise' at these sites.

Assisted-by: Agent <ai@blobfish.icu>
Two error-class mentions predate the errors-chapter patch but never had
the (see :ref:`sec:errors`) cross-reference the patch introduced at the
first mention in each file. Add the reference and normalize the site's
wording to the "must raise" phrasing used by the patch:

*   typedef.rst: SymbolError for duplicate alias names.  Also fixes a
    literal-role typo (single-backticks would render as an unresolved
    default role) and the missing trailing period.
*   types/array.rst: SizeError for RHS-too-large in an array
    initializer.  This is the first error-class mention in the file
    (line 53); the patch's cross-ref at line 280 was on the second
    mention.

Assisted-by: Agent <ai@blobfish.icu>
Follow-up to the errors-chapter patch.  Three coordinated cleanups, kept
in one commit so the wording change stays local to the sites it touches:

*   Fill omitted error classifications the initial patch missed.  Adds a
    ReturnError classification to the "return reachable by all control
    flows" rule (functions.rst), a GlobalError classification covering
    the non-constexpr / vector-global / non-global-statement bullets
    (globals.rst), a StatementError for a declaration outside the
    leading declaration block of a block statement (declarations.rst),
    a SyntaxError for iterator loops with more than one domain
    (statements.rst iterator loop), and a StatementError for a
    ``continue`` outside a loop (statements.rst continue, mirroring the
    ``break`` rule).

*   Normalize two "should raise" sites in files the patch already
    touched but did not reword: SizeError for matmul dimension mismatch
    (types/matrix.rst) and SizeError for elementwise binop size
    mismatch (types/array.rst).  Also normalizes typedef.rst's inline
    "Should raise a ``SizeError``" callout on the size-mismatch example.

*   Converge every patch insertion (and the two residuals above) on
    "must emit a ``X``" (or the passive "must be emitted") for the
    error-raising rule.  The initial patch used six different verbs
    (issue / raise / yield / cause / is / is to be produced) at
    otherwise identical sites; one verb reads more consistently and
    matches how the errors chapter itself describes the requirement.

Also drops the redundant ``(see :ref:`sec:errors`)`` on
types/array.rst:279 (out-of-bounds indexing) since types/array.rst:53
now carries the first-mention cross-reference for that file.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Four follow-ups on the errors-chapter branch flagged by a human-review
pass across the six spec-review PRs open on this stack:

*   `procedures.rst`: the two "*Gazprea* must emit a ``CallError``"
    sites drifted from the "the compiler must emit …" subject used
    everywhere else the pass touched.  Normalize both to
    "the compiler must emit".

*   `globals.rst`: the summary paragraph re-enumerated the three
    restrictions from the bullet list right above it, and the
    re-enumeration silently rots when the bullets are edited.
    Collapse to one sentence: "Violations of any of the above must
    be reported as a ``GlobalError``." — no drifting second copy.

*   `functions.rst`: the ReturnError classification I added on the
    return-reachable-by-all-paths rule ended with a sentence
    ("Control-flow constructs are assumed to be undecidable, so
    both branches of every conditional are considered reachable.")
    that `impl/errors.rst:104-106` already states as part of the
    normative ``ReturnError`` definition.  Drop it here; the cross-ref
    carries the rule.

*   `declarations.rst`: the StatementError classification used the
    undefined term "leading declaration block".  Nothing else in the
    spec introduces it, and the paragraph immediately above uses "at
    the start of the block".  Reword to "the declaration prefix at
    the start of its enclosing block statement", and lead with the
    compiler as the subject to match the surrounding rules.

Assisted-by: Agent (claude) <ai@blobfish.icu>
@Sir-NoChill

Copy link
Copy Markdown
Collaborator Author

WRT Open Questions from the agent:

  • Combined struct form is legal, the user can use a const qualifier in front of the combined struct decl, but we need to note the scopes in which struct definitions are legal and then note the associated rules as well. I believe that structs can be specified inside functions/procedures, so a var S(...) x; would be legal wherever a var tuple is.
  • Concatenation between two scalars is Illegal, so the compiler should emit a TypeError in this case. One operand must be a composite type.
  • Nested members follow the same casting rules as standalone casts, confirmed
  • by should be obliterated from the docs, we decided that it open up the possibility for views into arrays, which don't have an efficient implementation.
  • uninit const values do hold the zero value, RAII-style. The type zero value should be defined somewhere in the spec, if it is not we should build that.
  • Slice bounds - yes, that is correct. The language is 1-indexed. Note that the by operator should be obliterated. The expression would have by inside the [... by ...], but as previously stated, the by operator is obliterated.
  • strings are implicitly the size of their underlying character array, so a char[256] is the correct choice. Strings are not of bounded size, more closely resembling a vector than an array for analysis purposes.
  • procedure call results can be legally casted. We might need to review the list and standardize a single source of truth for a table of call-site operations that are legal.
  • method calls
    • This might need to be cleaned up, method calls are all currently 'procedures' and thus have the same semantics. In the future I would like to implement a way to define new methods on structs, but for now all structs are defined as procedure <name>(T: self, args...) returns U: for any T and U in the language for which we have defined methods (so I believe just the vector type. We should also declare that 'string' is just a builtin alias, not a subtype, of vector
    • Global immutability is a rule, so yes, no global mutation.
    • Single element appends is the correct rule, any single element that can be implicitly cast to T will be cast, and then appended as a single additional element.
    • No, we're not going to break this rule. Semantics are the same as with a procedure call, an explicit call statement can be used on a procedure, but it has no effect with existing builtin methods, so a call expression on a method is a no-op in the current spec.
  • We want to unify on the 'cast' terminology.
  • No broadcasting and no shape in this version of the spec. vector<vector<T>> can be ragged, yes. vector<T[]> cannot be ragged, all arrays must be of the same shape within a vector. You should go ahead and note these.
  • strings are a language supplied typealias to a vector<char>, so we can make that equivalence strong. We can remove the duplicated associativity collumn, we should have generator errors folded into all other types of errors. string and vector are the cannonical names, we can lower case all instances.z
  • All of the infrastructure changes are done.

Begin folding PR #106 into the consolidated spec, with review decisions.

- glossary: define `initialization` (renames #106's contested
  "elaboration") and `zero value` (RAII-const default; array padding).
- types/array.rst: new Sizing section and an Array-vs-Vector table, both
  stated for arrays of any rank per #138 -- not 2-D / base-type-only.
- Remove the `by` (stride) operator and `StrideError` entirely (it
  implies array views, which have no efficient implementation): the
  Stride operation, the precedence-table row, and the stride examples
  are gone.
- Concatenating two scalars is now a `TypeError`; at least one operand
  of `||` must be a composite value.

Refs #106.
Assisted-by: Agent (claude) <ai@blobfish.icu>
Renames the implicit-conversion vocabulary from 'promotion' to 'implicit cast' (heading of sec:typePromotion becomes 'Implicit Casts', label kept), and adds two-way array/vector casting sections. Part of folding #106.

Assisted-by: Agent (claude) <ai@blobfish.icu>
string is now a language-supplied typealias for vector<character> (not a sub-type). Methods are defined as procedures with a self parameter; only vector/string have them. Adds ragged rules: vector<vector<T>> may be ragged, vector<T[*]> may not; no broadcasting or shape(). Folds #106.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Assignment sizing (array pads/SizeError vs vector replaces), generators always yield arrays, vectors print as arrays, length() on a vector is current-length. Replaces the shape() built-in with rows/columns. Folds #106.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Explicit-size array params are part of the signature; inferred [*] is initialized at the call; var array can't resize but var vector can. Single source-of-truth list of legal procedure-call positions; call results are castable. Folds #106.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Combined struct form takes const/var and structs may be defined in any scope; drop the duplicated Associativity column and StrideError; matrix sizing note. Corrects the character zero value to a space (not the null char) in the glossary and declarations.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Fixes the unambiguous findings from the adversarial ratification review:
- Complete the promotion -> implicit-cast rename: per-type "Type Casting and
  Implicit Casts" headings, a glossary "implicit cast" term, and dropping the
  false "every promotion has an as<> form" claim.
- Reconcile contradictions: a generator's rank is set by iterator-variable
  count, not domain shape; rows/columns are matrix-only; push/append on an
  array is a TypeError (arrays have no methods), not a SizeError.
- Remove `by` from the keyword list; fix the IndexError example
  (integer[4] -> x[4]) and the mislabeled i[1] SymbolError -> TypeError.
- Renumber the array Operations list (a..f), link lvalue/rvalue, use
  "primitive type", state a global string is illegal, plus many typo,
  curly-quote, agreement, and US-locale fixes.

Design questions the review surfaced are deliberately left untouched
(runtime array size vs constexpr, integer ^ and division semantics, the
boolean/character cast mapping, namespace count) pending a language-owner
decision.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Applies the language-owner rulings on the questions the review surfaced,
plus the pass-2 review findings.

- Runtime array sizing is legal. Sharpen `initialization` (the run-time
  instant immediately before a declaration's first execution; a size is
  evaluated exactly once and the array can never be resized), drop the Ada
  "elaboration" quote, and reconcile constexpr.rst and array.rst so a
  runtime array size is a legal non-constexpr array -- a vector is only for
  a length that must change.
- string / character[*] has both implicit and explicit as<> forms (a string
  is a vector), so drop the "no as<> form" caveat in type_promotion.rst and
  the glossary.
- Built-in names (length, rows, columns, reverse, format, stream_state) are
  semantically reserved, not syntactic keywords: remove them from the
  keyword list; shadowing any of them, by any identifier, is a SymbolError.
- Fixes: scalar-broadcast element-type wording; the misleading method-call
  "no-op"; loops and domains accept vector and string; an array-valued index
  is a TypeError; matrix/array pad with the element type's zero value;
  missing semicolons.

Context (already stated in the spec): everything is 32-bit (i32/f32), so
integer ^ (reals-then-truncate) is well-defined; boolean -> character is
explicit-only, and as<character>(false) is the null character, distinct from
the space zero value.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Last advisory/nit findings from the convergence pass (the cross-cutting review came back clean): scope the built-ins-on-vectors claim to array-shaped built-ins (length/reverse); rename the mislabeled ssec:procedure_alias -> ssec:procedure_implicit_casts; behaviour -> behavior.

Assisted-by: Agent (claude) <ai@blobfish.icu>
Comment thread gazprea/spec/types/array.rst Outdated
Sizing
~~~~~~

Arrays are **initialization-time sized**. The length of an array variable --

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should put this definition in the glossary and write in terms of points and paths

Comment thread gazprea/spec/types/array.rst
Comment thread gazprea/spec/types/struct.rst Outdated

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Comment thread gazprea/spec/types/vector.rst Outdated
Comment on lines +119 to +124
also stand alone as a statement, terminated by a semicolon; this is the
only expression form that may be used as a statement. An explicit
``call`` statement may also be applied to a method call; the method still
runs and still acts on its receiver exactly as in the bare statement form.
The ``call`` keyword adds nothing here -- any result is discarded either
way -- so it is never required for a method call.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, method calls that stand alone must be part of a call statement, else we have a call error

Comment thread gazprea/spec/types/vector.rst Outdated
v2.len() -> std_output; // 3

v2.len(); // Does nothing
v2.len(); // Legal statement; result discarded

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must be call v2.len()

Comment thread gazprea/spec/procedures.rst Outdated
Comment on lines +32 to +35
This is the single authoritative list of those positions: wherever else this
specification refers to where a procedure call may appear, it points back to
this list rather than restating it. In particular, a procedure call may not be
used as the control expression of a control-flow statement.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This is the single authoritative list of those positions: wherever else this
specification refers to where a procedure call may appear, it points back to
this list rather than restating it. In particular, a procedure call may not be
used as the control expression of a control-flow statement.
This is the single authoritative list of those positions. A procedure call
may not be used as the control expression of a control-flow statement.

Comment thread gazprea/spec/procedures.rst Outdated
Comment on lines +166 to +171
An argument may be :ref:`implicitly cast <sec:typePromotion>` to the parameter
type at call time, but only if the argument is passed by value (that is, the
parameter is ``const``). A mutable (``var``) parameter is effectively call by
reference, so the parameter and the argument denote the same *l-value* (a
pointer); there is no separate value to convert, and so no implicit cast can be
inserted.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to fact check this wrt const values being pass by value and var values being passed by reference. I think this is the most sensible way to implement the language for sure, but I'm not sure it is the only way.

Comment thread gazprea/spec/functions.rst
Comment thread gazprea/spec/expressions.rst Outdated
Comment thread gazprea/spec/expressions.rst
Sir-NoChill and others added 8 commits August 23, 2026 14:26
Rename type_promotion.rst -> implicit_casts.rst (sec:implicitCasts) and typedef.rst -> typealias.rst (sec:typealias), updating the toctree and impl/part_1 cross-references so 'cast' is the umbrella term over implicit/explicit casts.

implicit_casts: add an Array-to-Array section, broaden array<->vector casts to composite element types, scope scalar broadcast to 'array of any rank (matrix = rank-2)', and rename the table placeholder var->value. typealias: a local typealias is a StatementError, and aliasable types use the canonical aggregate-type wording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Array slices are half-open (lower-inclusive, upper-exclusive) and are always a VIEW into the backing array, never a copy (a splat copy operator is future work); out-of-range slice bounds raise an IndexError. '**' is the dot product for rank-1 operands and matrix multiplication for rank-2, casting mixed element types to a common type. Add missing SizeError/IndexError citations and normalize 'array of any rank (matrix = rank-2)'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Vector methods split into stateless functions (len, expression-only) and stateful procedures (push/append, call-statement only; bare form is a CallError). Strengthen the string = vector<character> alias. Struct: literal fields may be implicitly cast, dot-access on a non-variable is a TypeError, and field identifiers form a per-struct namespace. Tuple: out-of-range field index is an IndexError, type-inferred init accepts any statically-typed tuple, and a type-signature mismatch on comparison is a TypeError.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integer overflow raises a MathError, except under -ffast-math where it is the language's sole undefined behavior; %-by-zero is a MathError. Real overflow and division by 0.0 raise a MathError under normal evaluation and produce the IEEE-754 result under -ffast-math. integer<->character casts use the ASCII/byte value; a scalar may be cast to a single-element vector with an explicit element type; add the closed-world 'any undescribed cast is a TypeError' rule. Drop duplicate per-type associativity columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single-source the procedure call positions and the operators allowed on a call result (violations are a CallError); a procedure call cannot appear as a control expression or stream operand. Rescope aliasing to var-involved arguments, add the disjoint-field example, and make two var slices of one backing array always alias. Name MainError for main(), StatementError for I/O in a function, TypeError for a return-type mismatch, and normalize assignment/slice/generator error emissions to the canonical form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
State that a non-declaration statement at global scope is a GlobalError and that assigning to an undeclared identifier is a SymbolError. Extend the constexpr operator set (comparison/boolean/%/^), split its unary/binary rule, and make aggregate operators and slices over constexpr arrays constexpr. Declare three identifier namespaces (type, variable/function, struct-field). Define 'value type' and fix the storable-types nesting wording; drop maintainer-only signposts and misc prose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bar tuples and structs from stream output (TypeError) and a procedure call from a stream operand (CallError). format() reuses the output-format representation; length is defined for rank-1/vector/string and a domain-restricted built-in is a TypeError. Carve out -ffast-math integer overflow as the sole undefined behavior in the implementation-defined-behavior entry, add 'explicit cast' and 'value type' glossary entries, and align the referential-transparency entry with the push/append exception.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrap prose paragraphs -- including list items, preserving each marker and its hanging indent -- so no prose line exceeds 80 columns, for a consistent line width across the specification. Formatting only: no content, code-block, or table changes, and the Sphinx build is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The left hand bound is *inclusive* and the right hand bound is *exclusive*:
``a[i..j]`` selects the elements from ``i`` up to but not including ``j``.
(This differs from a range *value*, whose bounds are both inclusive:
``0..10`` written as an expression produces the integers 0 through 10, while

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes me think that we should make ranges upper-bound exclusive to keep everything consistent.

Comment on lines +566 to +567
them, so a later write through either name is visible through the other. (A
dedicated copy operator is a planned future addition.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might have to add this in this version of the spec if we want to have meaningful slice semantics. I think that if students don't have a copy operator (a splat) then they will become confused

Comment thread gazprea/spec/types/array.rst Outdated
Comment thread gazprea/spec/types/boolean.rst Outdated
Comment thread gazprea/spec/types/integer.rst Outdated
Comment on lines +21 to +24
6. Functions cannot call any procedures, with one exception: a mutating
vector/string method (``push``, ``append``) may be called on a variable
local to the function (see :ref:`sssec:vec_methods`); any other procedure
call inside a function must emit a ``CallError`` (see :ref:`sec:errors`).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe be allowed to call a procedure on any local var to a function? Is this even possible to analyze efficiently?

Comment thread gazprea/spec/types.rst Outdated
@Sir-NoChill
Sir-NoChill requested a review from novo52 August 23, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant