Repository navigation
fix(analyser): check Any values a generic body passes for its type parameters 🔍 - #328
Merged
Merged
Conversation
…rameters 🔍 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
timfennis
added a commit
that referenced
this pull request
Nov 14, 2026
…out 📦 (#353) Closes #331. `Any` was gradual: a value of type `Any` went anywhere a type was expected, with a check the analyser inserted, and a call with an `Any` argument picked its overload as the program ran. Deciding where those checks went was spread over many places, and every new feature opened another hole (#328, #329, the `op=` and slice-write holes, function values through `Any`). Now `Any` is a top type, like Rust's `Box<dyn Any>`, Kotlin's `Any` or TypeScript's `unknown`: ```ndc let a: Any = 5; // anything goes into Any let n: Int = a; // error: found Any but expected Int let m = a as Int; // the way out: fails the program if it isn't one print((a as? Int).is_some(), a); // a test, and anything that takes Any ``` - A value of type `Any` only goes where `Any` is taken. Passing, storing, iterating, calling it or using an operator on it is an error unless an overload takes `Any` there, with help to cast it with `as` or test it with `as?`. A call with an `Any` argument only considers overloads that take `Any` at that position. Inside containers `Any` is exact, as before. - Enforced in `bind_argument`, `may_fit`, `type_parameters_agree`, `could_take`/`could_be_callable` and iteration. Failures give the error type from #348, so each mistake is one error. - `print`, `dbg` and the variadic `zip` take each argument as its own type instead of turning it into `Any`; no built-in signature changed. - Two weaknesses the permissive `Any` had hidden are fixed: a generic function's own body (what runs for a function value) picks its calls with type parameters rather than `Any`, which kept `+` on tuples working there; and a literal whose element type became a parameter's type no longer falls back to `Any`. Tests: 16 became error tests, 26 now cast with `as`, 4 got an annotation instead of a stray `Any`, and 6 that would have silently changed which overload runs were rewritten with a type test. The manual's sections on `Any` (types, casts, augmented assignment, function values, the overload troubleshooting page) describe the explicit rule. All advent-of-brian solutions give their answers; in advent-of-code-ndc two more programs need an annotation (a loop that falls off the end into `print`, and a map default `%{:fn() => []}` whose list doesn't learn from later pushes). Analysis time and VM instruction counts are unchanged. Known gaps, for follow-ups: values the analyser can't type still fall back to `Any` and then show up in errors (a lambda nothing calls, a REPL line `let xs = [];`), and a user function whose body ties two parameters together accepts `f(a, 1)` but not `f(1, a)` with `a: Any`. The runtime checks the analyser used to insert for `Any` (`ExpectType`, `type_checks`, the element-scanning pass of runtime overload picking) are no longer reachable and go next. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
A value of type
Anythat a generic function's body passed where one of its type parameters was expected went unchecked. A call could then return a value whose static type was wrong:The check was recorded once, in terms of the type parameter, and an instance's key only had the type parameters its waiting constraints, generic calls and literals depend on, so
id2had no instances and its own body checkedaagainstAny. The checks the body makes ofAnyvalues against a type with a type parameter in it are now part of the scheme: they add their type parameters to the key, and each instance checks them against its own types.id2("s")now fails atb.push(a)with "expected String but found Int".🤖 Generated with Claude Code