Fix #14: an overload member's signature is fixed at its definition - #112
Merged
Conversation
An overload member's return type used to default to `Num` when the definition omitted it, corrected only after the member's body was checked — so what a call saw depended on where it sat relative to that definition. A call above it resolved against the placeholder and either passed `quilon check` and failed at runtime with `Overload not found: g$N`, or was rejected complaining about a type nobody wrote (`expected Text, got Num`). A recursive member returning anything but `Num` was unwritable for the same reason. Members now register with `ret: Option<Type>` — omitted means unknown, not `Num`, so the placeholder cannot answer a call — and the omission is reported at the call that needed the result type, or at the definition when nothing calls it. Annotating fixes it, which also makes a recursive member expressible. The other half of the same finding: members were registered in a pre-pass, so a call could resolve against a definition further down the file that codegen then had no symbol for — a fully annotated program with two mutually recursive members passed the checker and died with `Overload not found: odd$N`. A member now joins its set as its definition is reached, so a call resolves only against the members above it, matching the rule plain functions already followed. Such a forward call is a compile error naming the situation; a definition is still in scope for its own body, so self-recursion is unaffected, while mutual recursion between top-level functions is not expressible (it never was — it only looked type-checked). LANGUAGE.md now states the top-to-bottom rule outright. `Overload::builtin` went with the refinement pass that was its only reader. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…of it v0.9.1 is tagged, so its section is history: the two breaking overload entries the merge folded into it move up to a fresh Unreleased section instead.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was broken
Verified on
mainbefore writing code (finding 12 taught us premises go stale — this one does not). An overload member's return type defaulted toNumwhen the definition omitted it and was corrected only after the member's body was checked, so what a call saw depended on where it sat relative to the definition. Four distinct symptoms, all live:Nummatches a real membercheckpasses,run→❌ Overload not found: g$NNummember existsNo overload of 'g' matches (Num). Candidates: (Text), (Bool)-> TextType mismatch: expected Text, got Num— a type nobody wroteNumAnd a second half of the same finding, independent of annotations: members were registered in a pre-pass, so a call could resolve against a definition further down the file that codegen then had no symbol for. A fully annotated program:
→
checkpasses,run→❌ Overload not found: odd$N. That is the checker/codegen contract break the finding names, and the source of the error string it quotes.What changed (maintainer's decisions)
1. A member must annotate its return type, as it already had to annotate every parameter.
Overload::retis nowOption<Type>— omitted means unknown, notNum, so the placeholder cannot answer a call — and the omission is reported where it bites:cannot call 'g': its overload member (Num) has no return type annotation — annotate it, since exact dispatch needs the full signatureoverload member 'g' (Num) has no return type annotation — …The post-hoc refinement from the body is gone: adopting the inferred body type would make a member's signature depend on where a call sits, which is the disease. Annotating fixes it, and a recursive member becomes expressible (
p = (n :: Num) -> Text => n == 0 ? "done" : p(n - 1)now compiles and runs).2. No hoisting. A member joins its set as its definition is reached, so a call resolves only against the members above it — the rule plain functions already followed (
^ = () -> Num => later()has always beenUndefined variable 'later'). A forward call now says so:cannot call 'odd' before its definition — Quilon resolves names top to bottom; move the definition above this call. A definition is still in scope for its own body, so self-recursion is unaffected. Mutual recursion between top-level functions is not expressible — it never worked, it only looked type-checked.Consequences worth flagging: an unannotated comparison-operator overload (
==,<=, …) now asks for the annotation instead of reporting that it must returnBool(the actionable message wins); andOverload::builtinwas deleted, since the refinement pass was its only reader.Coverage
examples/overload_dispatch.qlextended (self-asserting, in the examples gate): a member defined above its caller, and a recursive member reaching itself.Notes
corelib/,examples/, or the test suite relied on either behavior — the whole suite was green before I added a line of test.mainmoved twice during this work (Source-level debugging (Phase 1): DWARF line info +quilon build --debug#102/Validate & prune Known Limitations #107, then Prepare 0.9.1 release #103's tagged v0.9.1 and Fix watermark follow-ups: false strip claim, issue refs, test/polish cleanups #110). Merged both. The v0.9.1 merge folded my changelog entries into the released 0.9.1 section; sincev0.9.1is tagged and pushed, I moved them to a freshUnreleasedinstead of editing release history. Fold them into 0.9.1 only if you consider that section still open.Gate
cargo fmt --all -- --check,cargo clippy --all-targets --all-features -- -D warnings, and the full suite underRUSTFLAGS=-D warningsare clean (-- --test-threads=1; under default parallelismtail_call_testhits the pre-existing #98 GC abort, reproducible on unmodifiedmain).🤖 Generated with Claude Code