Skip to content

Builtin registry carries a signature, not a return type: BuiltinParam { name, ty } over all 132 rows - #9952

Merged
briansrls merged 5 commits into
mainfrom
session/still-swift-363-builtin-sigs
Sep 2, 2026
Merged

briansrls merged 5 commits into
mainfrom
session/still-swift-363-builtin-sigs

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Grounds the v1 builtin registry: it carried a return type per name and now carries a signature. decide_callable_candidates answered FuncSigUnresolved for every BuiltinCallable precisely because params: [] "would lie about the call's shape to every consumer of a sig" — this is the change that makes that arm answerable. The arm is not flipped here. Nothing consumes params yet; that lands next, with a call-site diff that should be empty because this PR conforms them.

What changed

src/v1/04_method.dag:

type BuiltinParam { name: String, ty: AlgebraTypeTemplate }
type BuiltinSignature { params: List<BuiltinParam>, returns: Node }
data builtin_function_registry: Map<String, BuiltinSignature>   // was Map<String, Node>

All 132 rows converted, no remainder. That is a §3 requirement rather than tidiness: a registry where some rows declare parameters and some do not cannot distinguish a nullary builtin from an undeclared one, which is the state this closes. 45 rows say params: [] and mean it.

infer_builtin_call_type still answers sig.returns, so every existing consumer sees exactly what it saw.

Provenance is not uniform, and one word "declared" over all of it would be rung inflation

grade rows what it means
Type derived from std.algebra 20 derived_signature(names:, method:, returns:) reads param_types out of the roster at construction. No choice in it.
Name agreed by two sites, provenance not independent most of the core string/map/list ops This repository's own call-site labels and its own emitted runtime text were frequently written to match each other. Agreement removes an arbitrary pick; it does not establish the name is right.
Name declared against a disagreement code_point (c over ch), count (xs over items), is_xid_start/is_xid_continue/is_emoji_ident (cp over ch), the map_* family (m, resolving a fork inside runtime_rust.dag, which spells the map receiver m in five functions and map in one) Both losing spellings are recorded.
Name evidenced by a single call site the instrument tail The site is recorded as consistent with the declaration, never as its source. A lone call site is still the corpus, and a check whose name came off the population it judges is measure() == measure().

get forks, and the fork resolves without a judgement call

get is declared in three profiles with two signatures: (Self, Int) -> Element? in free_monoid_collection, (Self, Key) -> Value? in the two function profiles. A name-keyed registry cannot represent that. The collection arm is selected because the registry's own prior return type was Optional<collection_element>, which matches that arm and not the other — a compatibility argument reading the structure being replaced, written as a declared choice so a later reader can reopen it against the algebra instead of finding a rule that looks derived.

Measured before authoring the row: no call site reaches this row with a map receiver. Every free call passes a list and an index; method-position calls resolve through the receiver-keyed profile path, and func_sig_from_global_bare short-circuits every algebra method name before the registry. That a name-keyed registry cannot represent a receiver-polymorphic builtin is a §3 shape fact this change does not repair, and does not widen.

Call sites conformed: 206 labels, 26 files, zero remaining

17 (builtin, position, spelling) classes: char_at i/index → pos; string_contains haystack/needle/sub → s/pattern; to_string n/i → value; code_point ch/s → c; is_xid_* ch → cp; concat left/right → a/b; replace pattern/delimiter/replacement → from/to. Re-measured after: 0.

Builtin arguments bind positionally — v1_interpreter.rs reaches every operand through args.iter().map(|(_, v)| v), the label is literally the wildcard — so these labels have never been judged by anything, which is how one parameter accumulated five spellings. That is what the declaration closes, and it is why the names are declared rather than read off the corpus.

No-op proven by execution. required-regen emits the whole corpus and drifts only on compiler_tests.rs, which predates this branch. The discriminator: the three src/v1 modules edited here have stage0 mirrors that do drift when their .dag changes semantically — 04_method.dag proved that on this same branch — and they do not drift now.

The codemod near-miss, recorded because it nearly shipped the defect it was fixing. The first pass matched call sites anywhere in the file, and .dag files carry emitted source text as data. It rewrote pub fn string_contains(s: &str, sub: String) → (s: &str, pattern: String) inside runtime_rust.dag while the body still said sub — emitted Rust that would not compile, from a pass whose purpose was making names agree. Redone with a string mask: 393 matches were inside string bodies, and the count fell from 216 to 206.

Two declared drops and two stalls, because this change creates classes as well as closing one

  • gunbc.rung_drop builtin_signature_arity_pairing_fabricates — when a row declares more names than std.algebra declares templates, the pairing yields AlgebraTypeVariable { id: "unpaired_declared_parameter" } instead of refusing: a value standing where a refusal belongs. A .dag data initializer has no refusal channel, so the arms were a fabricated value or a silently shorter list, and a marked fabrication is the more honest of the two. No consumer asserts the marker's absence, so it is mitigatable, not a wall — an unchecked marker is an inert lens. Population closed at the 20 rows that pair at all. Trigger names the capability (construction-time refusal in a data initializer); a witness over the marker would raise the rung but not retire the row, because the invalid state stays writable.
  • gunbc.rung_drop concat_binary_signature_exempt_from_arg_binding — concat is declared binary and is binary correctly: std.algebra says [ReceiverSelf, ReceiverSelf] and runtime_rust.dag emits pub fn concat<T: V2Concat>(a: T, b: T) -> T. The algebra and the emitted runtime agree; only the corpus calls it at 64 arities (12 sites at 1, 8297 at 2, 507 at 3, 200 at 4, 178 at 5, tail to 262). concat(a, b, c) is a fold, and what licenses folding it is associativity.
  • gunbc.guarantee_rung_drop algebra_operation_associativity_undeclarable_stall — the grounding that drop waits on. Magma<T> is { op: fn(T, T) -> T } and Semigroup<T> is { op: fn(T, T) -> T }: structurally identical, differing by a blank line where the law would sit. The type whose entire content is the associativity law carries no law. Population is UncountedNotEnumerable, because the class is not concat — it is every operation whose surface arity exceeds its declared arity, and nothing can currently ask which those are. §5 wall-after-grounding, not a ratchet.
  • gunbc.guarantee_rung_drop builtin_parameter_name_forked_across_hand_authored_sites_stall — once names are declared, the interpreter's diagnostic literals ("substring start", "char_at pos") and runtime_rust.dag's emitted signatures become two more hand-authored answers to a question that finally has an authority. One row, not two: the capability is identical for both sites, and splitting it would let each look closable while the capability that closes both stayed dead.

The rule the rows are authored under

A wrong monomorphic signature that looks specific is worse than params: [], which at least announces that it knows nothing. It decided three questions here, not one:

  • NamedTemplate was not extended to name a record. Measured: the hand-built FilesystemReadResult has 1 field; NamedTemplate { name: "FilesystemReadResult" } through instantiate_algebra_type yields 0, with no diagnostic. The arm accepts any string and fabricates a fieldless nominal — safe today only because its four live instantiations happen to be genuinely fieldless.
  • concat stays binary rather than being declared signature-less: the signature is expressible and true, and erasing a fact that holds is a different failure from asserting one that does not.
  • Types are derived where std.algebra declares them, rather than re-authored beside the authority that already answers.

A note on how the disagreements got there

A surface that binds nothing accumulates disagreements silently. Four independent namings exist for these parameters — the algebra (none), the interpreter's diagnostics, the emitted runtime, and the call sites — and no two agree. The act of making the surface bind is what converts those from invisible to blocking. That is the real deliverable here, more than any single row.

gunbc-ci-auto-heal added 5 commits September 1, 2026 13:48
… { name, ty } and BuiltinSignature over all 132 rows

Types for the 20 registry names that std.algebra also declares are DERIVED from it
by lookup; get is disambiguated by the registry's own already-committed return type
rather than by a pick. Names are declared per row. Nothing consumes params yet --
infer_builtin_call_type still answers sig.returns, so decide_callable_candidates
still answers FuncSigUnresolved for a BuiltinCallable and no call site is judged.
…ng fabrication and concat's binary exemption

The pairing fallback yields a marked type variable where a refusal belongs, and no
consumer asserts its absence -- named as a fabricated substitution rather than left
looking like a wall. concat stays binary because the algebra and the emitted runtime
agree it is; what is missing is associativity, and Semigroup<T> is declared without
its law.
…er names

17 (builtin, position, spelling) classes across 26 files: char_at i/index -> pos,
string_contains haystack/needle/sub -> s/pattern, to_string n/i -> value, code_point
ch/s -> c, is_xid_* ch -> cp, concat left/right -> a/b, replace pattern/delimiter/
replacement -> from/to.

Nothing binds these today, so the labels were unchecked decoration and the corpus had
accumulated five spellings for one parameter without anyone noticing. That is what the
declaration closes.

NO-OP PROVEN BY EXECUTION: required-regen emits the whole corpus and reports drift only
on compiler_tests.rs, which predates this branch. The three src/v1 modules edited here
have stage0 mirrors that DO drift when their .dag changes semantically -- 04_method.dag
proved that on this same branch -- and they do not drift now, so the emitted bytes are
identical and the labels bind nothing, measured rather than assumed.

The codemod skips string literals: .dag files carry emitted source text as data, and a
first pass without that filter rewrote a parameter name inside runtime_rust.dag's Rust
signature while its body still used the old one. 393 matches were inside string bodies.
…nd undeclarable associativity

One row, not two, for the naming fork -- the interpreter's diagnostic literals and
runtime_rust.dag's emitted signatures share one capability, and splitting them would let
each look closable while the capability that closes both stayed dead.

The associativity stall is what concat_binary_signature_exempt_from_arg_binding waits on:
Magma<T> is { op: fn(T, T) -> T } and Semigroup<T> is { op: fn(T, T) -> T }, structurally
identical, so the type whose entire content is the associativity law carries no law. Its
population is UncountedNotEnumerable rather than a list, because nothing can currently ask
which operations depend on associativity.

Both rows pass every_live_stall_is_below_its_ceiling and _names_a_next_rung_trigger.
A wrong monomorphic signature that looks specific is worse than params: [], which at
least announces that it knows nothing -- the rule that decided three questions here:
no record-naming NamedTemplate, concat stays binary, types derived where std.algebra
declares them. Regen confirms the annotation is erased: no emission drift.
@gunbai-bot gunbai-bot Bot changed the title Repair the first() interpreter/emitted semantic divergence — census the 187 candidate sites, then derive both arms from one authority Builtin registry carries a signature, not a return type: BuiltinParam { name, ty } over all 132 rows Sep 1, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 1, 2026 17:26
@briansrls
briansrls merged commit dc87875 into main Sep 2, 2026
6 checks passed
@briansrls
briansrls deleted the session/still-swift-363-builtin-sigs branch September 2, 2026 04:46
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