Repository navigation
feat(analyser): make Any a top type that takes a cast to get a value out 📦 - #353
Conversation
…out 📦 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. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1c1d0187b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| argument.replace(&|part| { | ||
| matches!(part, StaticType::Generic(_)).then_some(StaticType::Any) | ||
| matches!(part, StaticType::Any).then(|| StaticType::Generic("T".into())) |
There was a problem hiding this comment.
Specialize generic bodies for actual Any arguments
When a generic wrapper receives an actual Any, its copied constraints now select only the overload accepting Any, but instance_for still reuses the unspecialized body whenever every key type is Any. That body retains the permissive runtime dispatch recorded here. For example, with fn pick(x: Int) => 1; fn pick(x: Any) => "fallback"; fn wrap(x) => pick(x); let a: Any = 1;, wrap(a) is inferred as String but executes the Int overload and returns 1. Besides violating the new overload rule, this lets subsequent statically typed operations receive the wrong runtime type. Actual Any calls need their own specialized picks rather than reusing the generic runtime-dispatched body.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 58993f3: a call with Any arguments now gets its own instance (test 017_dynamic_any/027).
A generic function called with an Any argument reused its own body, whose calls dispatch at runtime, so its result could disagree with its type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58993f3d09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if !self.declared_in_batch.contains(&function) { | ||
| return function; |
There was a problem hiding this comment.
Preserve Any-specific overload picks across REPL batches
When a generic function was declared on an earlier REPL line, this branch still bypasses specialization. For example, evaluate fn pick(x: Int) => 1; fn pick(x: Any) => "fallback"; fn wrap(x) => pick(x); in one batch, then let a: Any = 1; let s: String = wrap(a); print(s); in the next. The copied constraints infer String, but the previously compiled body dynamically selects pick(Int) and returns 1, giving a statically typed variable the wrong runtime type. Although same-batch calls now get an instance, the remaining earlier-batch bypass is fresh evidence that the mismatch persists in the REPL. Earlier-batch calls also need to preserve the overload selection established by their actual argument types.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Real, but it needs later REPL lines to compile instances of earlier lines' functions; tracked in #354.
…ed 🧹 (#356) Since #353 an `Any` value only leaves `Any` through a cast, so the checks the analyser added where an `Any` was passed or stored never fire in a program that analyses without errors. Part of #331. - Delete the `ExpectType` opcode, the `type_checks` tables, the analyser code that recorded them (`expect`, `expected_of_any`, the instance checks), and the argument nodes calls carried only for them. - An early pick is always a direct call now, and the rule that turns a runtime pick among one overload into a direct call lives in one helper. - Delete branches that only `Any` reached: the `Dynamic` arm of `choose_function_value`, the `Any` relaxation for function types in `StaticType::matches`, and the string branches of the generic `prefixes`/`suffixes`. The VM still picks overloads by looking inside containers, for a generic function's own body when it's used as a value (#355). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Closes #331.
Anywas gradual: a value of typeAnywent anywhere a type was expected, with a check the analyser inserted, and a call with anAnyargument 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, theop=and slice-write holes, function values throughAny). NowAnyis a top type, like Rust'sBox<dyn Any>, Kotlin'sAnyor TypeScript'sunknown:Anyonly goes whereAnyis taken. Passing, storing, iterating, calling it or using an operator on it is an error unless an overload takesAnythere, with help to cast it withasor test it withas?. A call with anAnyargument only considers overloads that takeAnyat that position. Inside containersAnyis exact, as before.bind_argument,may_fit,type_parameters_agree,could_take/could_be_callableand iteration. Failures give the error type from fix(analyser): don't report errors that follow from an earlier one 🧯 #348, so each mistake is one error.print,dbgand the variadicziptake each argument as its own type instead of turning it intoAny; no built-in signature changed.Anyhad hidden are fixed: a generic function's own body (what runs for a function value) picks its calls with type parameters rather thanAny, which kept+on tuples working there; and a literal whose element type became a parameter's type no longer falls back toAny.Tests: 16 became error tests, 26 now cast with
as, 4 got an annotation instead of a strayAny, and 6 that would have silently changed which overload runs were rewritten with a type test. The manual's sections onAny(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
Anyand then show up in errors (a lambda nothing calls, a REPL linelet xs = [];), and a user function whose body ties two parameters together acceptsf(a, 1)but notf(1, a)witha: Any. The runtime checks the analyser used to insert forAny(ExpectType,type_checks, the element-scanning pass of runtime overload picking) are no longer reachable and go next.🤖 Generated with Claude Code