Implement user-defined action overloading - #639
Conversation
…dispatch
Actions may now be defined more than once with the same name in the same
scope, dispatching on argument count and declared parameter types. This
implements the analyzer's long-standing TODO ("Implement proper overload
resolution based on argument types and count") and extends it to full
user-defined overloading across the pipeline.
- Analyzer: same-scope action redefinitions merge into the existing
Vec<FunctionSignature>; exact duplicates and indistinguishable same-arity
pairs (no position where both declare concrete, different types) are
definition-time errors. Both call forms (`of` and `call ... with`) resolve
through a shared checker that filters candidates by arity then by static
argument types, with Elm-style candidate-listing errors; statically
ambiguous calls defer to runtime silently. The `of` form now gets the same
named-argument and type validation the `call` form already had.
- Typechecker: per-overload inferred return types recorded in a side table
keyed by (name, signature index) and resolved at call sites; deferred
calls take the common return type of surviving candidates, else Unknown.
- Interpreter: new Value::Overloaded wraps the overload set in definition
order; FunctionValue now carries declared parameter types (previously
erased). Runtime dispatch filters by arity, then by argument values
against concrete parameter types, preferring the most concretely-matched
candidate (ties resolve to definition order). Zero-arg auto-call, main
entry, exports, and first-class function values handle overload sets.
- Parser: `as text` / `as pattern` parameter annotations now parse (these
type names lex as keywords, which the type position previously rejected).
- Container methods intentionally do not overload yet (documented).
Docs: new overloading section in actions-functions.md, normative dispatch
rules in the language specification, syntax-reference example, Dev Diary
entry. Tests: 29 new cases across analyzer/typechecker/interpreter suites
plus TestPrograms/action_overloading_comprehensive.wfl.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
- Replace the first-class-value overloading example (the analyzer has never accepted calling a function-valued variable, for single actions either) with a direct overloaded-call example, in both the docs and the comprehensive TestProgram. - Fix pre-existing syntax-reference bug: `add_numbers with 5 and 3` is concatenation, not a call — it printed "action add_numbersyes" instead of 8. Use the `of` call form. All snippets and TestPrograms/action_overloading_comprehensive.wfl verified end-to-end with target/release/wfl. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
Add TODO(#638) markers at the four sites a future implementation must touch (analyzer MethodInfo registration x2, interpreter method insert, ContainerDefinitionValue method maps) and link the issue from the docs callout and Dev Diary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Warning Review limit reached
Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughAdds same-scope user-defined action overloading with typed and arity-based dispatch, analyzer and typechecker resolution, runtime overload selection, stored-action support, parser updates, documentation, and comprehensive regression coverage. ChangesAction overloading
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 090ad3ef8b
ℹ️ 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".
| .await?, | ||
| ); | ||
| } | ||
| let func = Self::select_overload(&overloaded, &arg_values, *line, *column)?; |
There was a problem hiding this comment.
Respect named arguments during overload dispatch
For overloaded action calls with named arguments, dispatch receives only arg_values in source order, so a call such as call f with b: 1 and a: "x" against overloads (a as number, b as text) and (a as text, b as number) is accepted by the analyzer for the second signature but runtime selects/binds as if the values were positional. Named arguments need to be mapped to the selected signature's parameter order before calling select_overload/call_function, otherwise overload resolution can run the wrong body.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Named call arguments are not producible from WFL source today: the parser never constructs Argument { name: Some(_) } — every real construction site (12 across src/parser/expr/* and src/parser/stmt/*) passes name: None; only hand-built ASTs in unit tests set a name. So runtime dispatch cannot receive named arguments, and the analyzer's named-argument mapping is defensive only. The pre-existing single-function call path has the same positional-only behavior.
Agreed the mapping will be needed if named-call syntax lands — deferring the runtime change until then rather than adding dead reordering logic now. (The sibling comment about zero-arg auto-call during callee resolution was real and is fixed in be2a1c9.)
Generated by Claude Code
| } else { | ||
| self.check_overloaded_call( | ||
| name, | ||
| &signatures, | ||
| arguments, | ||
| true, | ||
| *line, | ||
| *column, | ||
| ); |
There was a problem hiding this comment.
🔍 of-form calls now get type validation they never had
The old of-form (FunctionCall with a bare Variable callee) only performed an arity check on the first signature and never validated argument types (src/analyzer/mod.rs old lines 2600-2628). The new path routes both of and call forms through check_overloaded_call -> check_call_against_signature, which now emits per-argument type-compatibility errors for the of form too. This is called out as intentional in the dev diary, and the unit test test_function_call_type_checking was updated. However, it is a behavior change: an existing program that passed a statically-concrete argument whose type mismatches a declared parameter type via the of form (e.g. f of some_text where f takes x as number) would now fail static analysis where it previously ran. is_type_compatible is lenient (Unknown/Any/Nothing pass), so only concrete mismatches are affected. Worth confirming all TestPrograms/ still pass given the repo's backward-compatibility rule.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end support for user-defined action overloading in WFL: multiple same-name action definitions in the same scope can coexist when distinguishable by arity and/or declared parameter types, with calls resolved via analyzer/typechecker filtering and interpreter runtime dispatch.
Changes:
- Implemented overload-set signature registration/validation in the analyzer, including call-site candidate filtering and improved diagnostics.
- Added runtime representation (
Value::Overloaded) and overload selection logic in the interpreter, plus environment support for merging same-scope redefinitions. - Extended the typechecker to track per-overload inferred return types and resolve overloaded call expression types; added comprehensive tests, docs, and an end-to-end TestPrograms case.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/analyzer/mod.rs |
Adds signature conflict detection, overload signature formatting, and overload-aware call validation in analyzer pass. |
src/typechecker/mod.rs |
Tracks per-overload inferred return types and resolves call types against multiple signatures. |
src/interpreter/value.rs |
Introduces overloaded function runtime value representation and stores declared param types for dispatch. |
src/interpreter/environment.rs |
Adds same-scope action merge logic to build overload sets and enforce distinctness rules at runtime. |
src/interpreter/mod.rs |
Implements runtime overload dispatch selection and preserves zero-arg auto-call behavior for overloaded names (incl. main). |
src/interpreter/memory_tests.rs |
Updates interpreter memory tests to account for param_types on FunctionValue. |
src/parser/stmt/actions.rs |
Fixes typed parameter parsing for keyword-lexed types via type_from_token (e.g., text, pattern). |
tests/overload_analyzer_test.rs |
Adds analyzer tests for overload definition rules and call-site resolution/diagnostics. |
tests/overload_typechecker_test.rs |
Adds typechecker tests for per-overload return inference and deferred/forward-reference behavior. |
tests/overload_interpreter_test.rs |
Adds interpreter tests for runtime dispatch, errors, recursion, and overload value behavior. |
TestPrograms/action_overloading_comprehensive.wfl |
Adds an end-to-end WFL program covering overload scenarios. |
Docs/03-language-basics/actions-functions.md |
Documents overloading rules, dispatch semantics, and current limitations (e.g., container methods). |
Docs/reference/syntax-reference.md |
Updates syntax reference examples to include typed overload usage. |
Docs/reference/language-specification.md |
Adds specification text describing overload validity and dispatch rules. |
Dev diary/2026-07-20-action-overloading.md |
Captures design decisions, constraints, and test coverage for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/parser/stmt/actions.rs (1)
17-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDivergent type-name casing across the three type parsers.
type_from_tokenrecognizes lowercase primitive spellings (text,number, …), butparse_container_action_definition(Lines 397-404) andparse_parameter_list(Lines 475-482) recognize only capitalized spellings (Text,Number, …), mapping everything else toType::Custom. This means a top-level action acceptsas textwhile a container method with: Textneeds a capital letter, andx as textinsideparse_parameter_listwould silently becomeType::Custom("text"). Consider consolidating onto a single case-insensitive helper to avoid the inconsistency, even while container overloading remains deferred (#638).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/parser/stmt/actions.rs` around lines 17 - 24, Consolidate primitive type-name parsing across type_from_token, parse_container_action_definition, and parse_parameter_list by reusing a shared case-insensitive helper. Ensure text, number, boolean, nothing, and pattern map to their corresponding primitive Type variants regardless of casing, while unrecognized names remain Type::Custom with the original name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Dev` diary/2026-07-20-action-overloading.md:
- Around line 69-72: Update the bullet’s wording to state that typed parameter
annotations using “as text” are now parsed via type_from_token, while “returns
text” remains unsupported; remove the contradictory claim that both forms never
parsed.
In `@src/analyzer/mod.rs`:
- Around line 70-79: Update format_param_type to add an explicit Type::Pattern
arm returning the lowercase source spelling "pattern", keeping the existing
fallback for other unsupported types unchanged.
In `@src/parser/stmt/actions.rs`:
- Around line 13-27: Add direct handling for Token::NothingLiteral in
type_from_token so it returns Type::Nothing, while preserving the existing
identifier and keyword mappings.
---
Nitpick comments:
In `@src/parser/stmt/actions.rs`:
- Around line 17-24: Consolidate primitive type-name parsing across
type_from_token, parse_container_action_definition, and parse_parameter_list by
reusing a shared case-insensitive helper. Ensure text, number, boolean, nothing,
and pattern map to their corresponding primitive Type variants regardless of
casing, while unrecognized names remain Type::Custom with the original name.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1bc9149c-2618-495b-b0a9-18beac281df3
📒 Files selected for processing (15)
Dev diary/2026-07-20-action-overloading.mdDocs/03-language-basics/actions-functions.mdDocs/reference/language-specification.mdDocs/reference/syntax-reference.mdTestPrograms/action_overloading_comprehensive.wflsrc/analyzer/mod.rssrc/interpreter/environment.rssrc/interpreter/memory_tests.rssrc/interpreter/mod.rssrc/interpreter/value.rssrc/parser/stmt/actions.rssrc/typechecker/mod.rstests/overload_analyzer_test.rstests/overload_interpreter_test.rstests/overload_typechecker_test.rs
- of-form calls no longer auto-call a zero-argument overload while resolving the callee: a bare-Variable callee naming an overload set is used directly as the call target, so 'g of 5' dispatches instead of calling g's zero-arg version and then failing to call its result - clear the per-overload return-type table at the start of check_types so a reused TypeChecker (editor/LSP session) never resolves calls against a previous program's recorded returns - accept 'nothing' (NothingLiteral token) in type position, so 'as nothing' parameter annotations parse - render Pattern and Any parameter types in surface syntax in overload diagnostics instead of Debug output - reword the Dev Diary bullet to distinguish the fixed 'as text' parameter parsing from the still-unsupported 'returns <type>' clause Named-argument dispatch (also raised in review) is unreachable today: the parser never constructs named Arguments outside unit tests, so no runtime change is made for it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
…or overloads Copilot review follow-up: 'any' lexes as KeywordAny, so type_from_token rejected 'x as any'. Accept the keyword (and 'any' identifier) mapping to Type::Any — and since an 'any' annotation accepts every value, treat it (and Unknown) like an untyped parameter in the overload distinctness rule (analyzer + runtime) and in dispatch specificity, so 'f(x as any)' vs 'f(x as number)' is rejected as ambiguous instead of dispatching unpredictably. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
…nking CI on this branch failed twice with 'ld terminated with signal 7 [Bus error]' (runner out of disk while linking) — main is green, and the tipping factor is the three new integration-test crates, each linking its own copy of the debug-info-heavy wfl library. Merge them into a single tests/overload_test.rs (analyzer/typechecker/interpreter modules), cutting the new test binaries from three to one. All 32 tests unchanged and passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
Copilot review follow-up: 'as Text' parsed as Custom("Text"), which the
analyzer tolerates (case-insensitive compatibility) but runtime overload
dispatch treated as a container type and failed to match. Normalize the
identifier to lowercase for primitive names (text/number/boolean/
nothing/pattern/any); true custom types keep their original spelling.
Adds a dispatch test using capitalized annotations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
| let compatible: Vec<usize> = | ||
| arity_matches | ||
| .iter() | ||
| .copied() | ||
| .filter(|&i| { | ||
| signatures[i].parameters.iter().zip(arg_types.iter()).all( | ||
| |(param, arg_type)| { | ||
| let param_type = | ||
| param.param_type.as_ref().cloned().unwrap_or(Type::Unknown); | ||
| self.are_types_compatible(¶m_type, arg_type) | ||
| }, | ||
| ) | ||
| }) |
There was a problem hiding this comment.
Same situation as the runtime-dispatch thread (#639 (comment)): named call arguments are not producible from WFL source — the parser never constructs Argument { name: Some(_) } (all real construction sites pass name: None), so infer_overloaded_call_type can only ever see positional arguments. The analyzer's name-aware signature_accepts is defensive. Deferring name-aware mapping here until named-call syntax exists, at which point the analyzer/typechecker/runtime paths should all gain it together.
Generated by Claude Code
logbie
left a comment
There was a problem hiding this comment.
Deep review — merge blockers found
Reviewed current head 769e898. GitHub does not permit this account to submit “Request changes” on its own PR, so this comment records the equivalent review verdict: changes required before merge.
1. P1 — Custom/container-typed overloads are rejected before runtime
Runtime dispatch explicitly supports Type::Custom and descendant container instances, but static overload filtering does not consider Type::Custom("Dog") compatible with the inferred Type::ContainerInstance("Dog"). The analyzer's is_type_compatible and the type checker's are_types_compatible both lack this pairing, so exact custom-type and inherited-type calls can be rejected before the runtime logic at value_matches_type ever runs.
Minimal case:
define action called describe with parameters value as Dog:
return "dog"
end action
define action called describe with parameters value as Cat:
return "cat"
end action
create instance rover from Dog
display describe of rover
Please add exact custom/container matching, descendant-to-parent compatibility through the container registry, and full-pipeline tests for exact and inherited dispatch.
2. P1 — nothing passes static resolution but fails runtime dispatch
The analyzer and type checker intentionally accept Nothing as compatible with every target type, but runtime overload matching accepts null only for an explicit as nothing parameter.
define action called f with parameters x as number:
return "number"
end action
define action called f with parameters x as text:
return "text"
end action
display f of nothing
This passes the static stages and then fails with “No version matches.” It is also a compatibility regression: a single typed action previously received nothing, while adding another overload changes that call into a runtime error. Analyzer, type checker, and interpreter need one consistent rule. Under the existing compatibility semantics, null should likely remain compatible with each candidate, with the documented definition-order tie break.
3. P2 — First-class overload sets lose their signatures in the type checker
Runtime supports storing an overload set and calling it through another variable:
store h as f
store result as h of 5
However, infer_expression_type only invokes overload resolution when the callee name directly resolves to an action symbol. After assignment to h, it is an ordinary variable whose Type::Function describes only the most recently checked overload because each action definition overwrites symbol.symbol_type. Calling a different overload through h therefore emits a false type warning and can infer the wrong return type.
The current first-class-value coverage is interpreter-only, so it misses this. Please propagate overload-set type information through variable declarations and add a full analyzer/type-checker/interpreter regression test.
Additional notes
Current CI is green. Several still-open automated threads are already addressed in the current head (zero-argument callee handling, pattern, any, case normalization, and clearing overload_returns) and can be resolved after verification. Named-call arguments are not source-reachable today, so those threads are future-proofing rather than current blockers.
Deep review — round 2Reviewed current head 1. P1 — A call made before the second overload definition can execute the wrong bodyPASS 1 registers every top-level signature before analysis, so the call below resolves statically to the later At runtime, however, only the first definition exists when the call executes. The environment still stores it as This contradicts the dev-diary statement that calls operate on “the overloads defined so far”: with one defined candidate whose type does not match, the runtime should reject the call, not execute that candidate. It can also run arbitrary side effects from the wrong action body. Please add a full-pipeline regression test with a call interleaved between overload definitions and ensure type-based matching is applied even while only one member of a future overload set has been defined. Relevant paths:
2. P2 — An overloaded action value is not equal to itself
This prints Please add VerdictChanges still required before merge. Current CI is green, but existing tests do not exercise either temporal dispatch or overload-set identity. |
Round 1:
- container-typed overloads: params annotate as Custom("Dog") but
instances infer as ContainerInstance("Dog"); add
Analyzer::container_is_or_extends (depth-guarded extends walk) and
Custom<->ContainerInstance ancestry arms to both is_type_compatible and
are_types_compatible, so exact and inherited container dispatch pass
static analysis (runtime already matched via the parent-instance chain)
- nothing arguments: value_matches_type now accepts Null/Nothing for
every parameter type, mirroring the static (_, Nothing) => true rule;
ties fall to definition order
- stored actions: new analyzer action_aliases map records bare
'store h as f' references (cleared on reassignment); analyzer call arms
and typechecker call paths resolve through the alias, so calls through
a stored action get full overload resolution and return types
Round 2:
- temporal dispatch: call_function now enforces declared parameter types
(nothing/any/untyped still accept everything), so a call interleaved
between overload definitions errors instead of silently running the
lone first overload's body with a non-matching argument
- Value::Overloaded equality: Rc::ptr_eq arms in both the PartialEq fast
path and eq_with_visited, so an overload set equals itself
Adds full-pipeline (analyze -> typecheck -> interpret) regression tests
for all five findings, extends the comprehensive TestProgram, and
updates the actions guide, language spec, and Dev Diary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Fifth-pass review of
I reviewed the full delta from |
Addresses the maintainer's fifth-pass review (two P1 findings): 1. Cross-block merges no longer leave a temporal window: entering a statement block arms enforce_param_types on same-scope existing function members for every name the block defines (enter_block_overloads, wired through _execute_block, the top-level program loop, and the describe/test executors), so an interleaved dynamic call before the in-block definition dispatches strictly. Inherited names stay untouched — defining over them is a shadowing error, not a merge. 2. Alias flow joins now account for abrupt exits: the analyzer tracks which alias names are written at any point inside each loop/try body (alias_mutation_frames; popping merges into the parent frame) and degrades exactly those names to AliasState::Dynamic after loop joins and at try-handler entry. A break/continue or handler transfer can expose an intermediate binding the body's endpoint state has already restored — previously that made static analysis reject calls the runtime accepts. Untouched names keep precise state; branch constructs keep endpoint joins (no mid-branch exit); overload counters are monotonic so endpoint joins already catch them. Three new red-first regressions (60 total in tests/overload_test.rs), a TestProgram loop-reassignment section, and docs/spec/diary updates in the same change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Fifth-pass findings addressed in 82444a3 — both fixed, red-first (three new regressions, 60 total in 1. Block-entry arming for cross-block merges (P1) — New 2. Abrupt-exit-aware alias joins (P1) — I took your "conservatively degrade" option, but scoped precisely: the analyzer keeps a stack of alias-mutation frames, one per loop/ One test-shape note: reassigning an alias across differently-shaped actions trips the pre-existing function-type assignment check (positional parameter compatibility; untyped parameters are compatible with anything), so the regressions reassign to an untyped-parameter action — the same leniency real dynamic code would use. Recorded in the diary. Docs ( Generated by Claude Code |
…types nothing arguments and container-inheritance overlap also leave several statically-valid overload candidates; the three deferral comments now say so (Copilot review nits). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Sixth-pass review of
Current config lint and automated review passed; the main CI workflow is still in progress on this head. |
Addresses the maintainer's sixth-pass review (two P1 findings): 1. repeat while / repeat until / forever / main loop now have analyzer arms with the same scope handling, flow-entry/mutation-frame/join, and mutated-alias degradation as the other loop forms — a break can no longer leave runtime alias state that static analysis contradicts. Because these bodies were never analyzed before, diagnostics arising inside them demote to warnings: a full semantic sweep of all 129 TestPrograms showed five web-server programs (main-loop bodies referencing handler-provided names) would otherwise start failing, and backward compatibility wins. The sweep is back to its pre-change baseline (two deliberate error-demonstration programs). 2. Block-entry enforcement arming is now speculative-and-revertible: arm_block_members records each armed member's prior flag in an ArmedEnforcementGuard whose Drop (block exit and top-level run exit, including error paths) restores members that never actually merged into an overload set — so a run that fails before the merging definition executes no longer permanently converts a still-single action into a runtime-guarded one (the REPL repro from the review). Restoration runs in reverse order so re-armed duplicates unwind to the oldest flag; merged members keep enforcement. Also labels the two call forms separately in the syntax reference (Copilot nit). Three new red-first regressions (63 total), docs/spec enforcement-revert wording, and the diary updated in the same change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Sixth-pass findings addressed in 3ad243e — both fixed, red-first (three new regressions, 63 total in 1. All loop variants participate in alias flow analysis (P1) — One consequence needed a deliberate call, and I want to flag it for your judgment: these bodies were never analyzed before, so walking them surfaces diagnostics in existing programs. A full semantic sweep of all 129 TestPrograms showed five web-server programs would start failing — their 2. Speculative arming is revertible (P1) — Also took Copilot's syntax-reference nit (the two call forms are now labeled statement vs expression). Docs/spec state the revert rule; diary has the round-6 section. Gates: fmt, clippy Generated by Claude Code |
|
Seventh-pass / merge-readiness review of The two round-6 P1s are fixed, but I found one new merge blocker:
Merge recommendation: not yet. This is a fatal resource-limit channel regression, and the main CI workflow is also still in progress. Config lint, CodeRabbit, and the automated review workflow are green; there are no unresolved inline review threads. Once this P1 is fixed and the new head's CI is green, I would do one short final verification before merging. |
The maintainer's merge-readiness review caught that round 6's compatibility drain moved every new error into warnings — including the rendered fatal error a shared-budget breach pushes when analysis aborts inside a repeat/forever/main-loop body. analyze() then returned Ok with budget_error latched, so --analyze and direct callers could report success after an aborted analysis. The demotion now runs only when no budget breach is latched; a breach keeps the whole error tail fatal. Two red-first regressions (main loop + repeat while bodies under a 5-operation cap) mirror analyzer_polls_the_budget_inside_nested_bodies. Also: the pre-existing pin test main_loop_body_is_currently_not_statically_analyzed (which invited a deliberate update when analyzer coverage arrived) is now main_loop_body_diagnostics_demote_to_warnings, asserting the round-6 contract — the reference is surfaced as a warning, never a fatal error (this was the Integration/Build CI failure on 3ad243e); and the describe-teardown guard binding destructures both scopes (Copilot nit). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
|
Seventh-pass blocker fixed in 5937006, red-first: Budget breaches never demote (P1) — Both demotion sites now drain the error tail into warnings only when Also in this commit, closing out the CI failures you saw in progress: the Integration/Build failures on 3ad243e were the pre-existing pin test Gates on this head: fmt, clippy Generated by Claude Code |
|
CI is fully green on 5937006 — Build/Test/Clippy, Integration (ubuntu + windows), Run WFL Programs (ubuntu + windows), Database, Fuzz, formatting, config-lint, CodeQL, and both automated reviews all passed. The budget-breach P1 from your merge-readiness pass is fixed on this head, all inline threads are resolved, and the branch is ready for your final verification. Generated by Claude Code |
|
Eighth-pass / final merge-readiness review of No new findings. The round-7 blocker is fixed correctly: compatibility diagnostics inside the newly analyzed loop bodies are demoted only when no fatal budget breach is latched, so an aborted analysis retains its fatal error. The added Merge recommendation: yes. The PR is open, non-draft, and mergeable; CI, WFL Config Lint, Claude Code Review, and CodeRabbit are all green on this exact head. GitHub reports no unresolved inline review threads. |
Summary
This change implements full support for user-defined action overloading, allowing actions to be defined multiple times with the same name in the same scope when they differ in parameter count or declared parameter types. Calls dispatch automatically on argument count and runtime argument types.
Key Changes
Analyzer (PASS 1):
SignatureConflictenum andsignature_conflict()function to detect exact duplicates and indistinguishable same-arity pairs at definition timeformat_signature()andformat_param_type()helpers for diagnostic messagescheck_overloaded_call()to validate calls against multiple signatures: filters by arity, then by static argument types, with detailed error messages listing candidatessignature_accepts()to check if a signature could accept a call without reporting errors (used for filtering)check_call_against_signature()for full validation of a single resolved signatureInterpreter:
Value::Overloaded(Rc<OverloadedFunction>)variant to wrap overload sets in definition orderFunctionValuewithparam_types: Vec<Option<Type>>to track declared parameter types for runtime dispatchEnvironment::define_or_merge_action()to merge same-scope action redefinitions, enforcing the same overload rules as the analyzerselect_overload()to dispatch at runtime: filters by arity, drops candidates whose concrete parameter types reject argument values, picks the most specific match (ties resolve to definition order)mainentry point to handle overloadedmainby running its zero-argument overload if presentType Checker:
overload_returnsHashMap to track inferred return types per overload (keyed by action name and signature index)action_signatures()to retrieve registered signatures for a namesignature_index_for()to identify which overload a definition corresponds toinfer_overloaded_call_type()to resolve call types against multiple signatures, mirroring the analyzer's filtering logicParser:
type_from_token()helper to map tokens in type position toType(handles keywords liketextandpatternthat lex as keywords rather than identifiers)Documentation & Tests:
tests/overload_analyzer_test.rs) covering definition-time rules, call-site resolution, and error messagestests/overload_interpreter_test.rs) covering runtime dispatch by arity and type, closures, recursion, and error handlingtests/overload_typechecker_test.rs) covering per-overload return type inference and deferred callsTestPrograms/action_overloading_comprehensive.wflend-to-end testDocs/03-language-basics/actions-functions.md) with overloading rules and examplesImplementation Details
Definition-time strictness: Two same-name definitions are rejected when they are exact duplicates (same arity, same normalized types) or when no parameter position has both versions declaring concrete, different types. This rule is enforced identically in the analyzer and runtime to ensure include-driven or dynamically-constructed programs get consistent behavior.
Deferred static resolution: At a call site the analyzer filters signatures by arity, then by static argument types. Zero survivors produces an error listing candidates; exactly one gets full validation; several survivors (only possible with Unknown-typed arguments) defer silently to runtime dispatch.
Runtime dispatch:
Value::Overloadedwraps the overload set in definition order. Dispatch filters by arity, drops candidates whose concrete parameter types reject an argument value, then picks the candidate with the most concrete type match (ties resolve to definition order).Return type tracking: The type checker maintains a per-
https://claude.ai/code/session_01SdLFM2t8Efd89LbaFh7Mkm
Summary by CodeRabbit