-
Notifications
You must be signed in to change notification settings - Fork 0
Fix issue #560 residuals: returns in try blocks and container methods #599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
257f2e1
fix: infer action return types through try blocks and for container m…
claude 10e5754
Merge branch 'main' into claude/issue-560-fix-lu08sd
logbie 9eaa2c4
fix: refine static container method return types and validate annotat…
claude ae02dbc
chore: remove stray test artifacts accidentally committed
claude c9a8455
Merge branch 'main' into claude/issue-560-fix-lu08sd
logbie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
115 changes: 115 additions & 0 deletions
115
Dev diary/2026-07-10-issue-560-residuals-try-returns-container-methods.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # Issue #560 Residuals: Returns Inside `try` Blocks and Container Method Results No Longer Typed `Nothing` | ||
|
|
||
| **Date:** 2026-07-10 | ||
|
|
||
| ## Background | ||
|
|
||
| Issue #560 ("unannotated action return types default to `Nothing`") was fixed | ||
| in two rounds: #575 added post-body return-type inference for top-level | ||
| actions, and #591 seeded the provisional return type as `Unknown` so | ||
| self-recursive calls degrade gracefully. Working on Scriptorium surfaced the | ||
| same false `Cannot index into Nothing` diagnostic again, so this pass hunted | ||
| down the remaining shapes that still slipped through. Two were found and | ||
| fixed; both are static-diagnostics-only bugs (runtime was always correct). | ||
|
|
||
| ## Residual 1 — `return` inside a `try:` block was invisible to inference | ||
|
|
||
| `collect_return_types` (`src/typechecker/mod.rs`) descends into conditionals | ||
| and loops to find an action's `return` statements, but it never descended into | ||
| `Statement::TryStatement`. An action whose only returns live inside a `try:` | ||
| body, a `when error` clause, an `otherwise` clause, or a `finally` block was | ||
| inferred as returning `Nothing`, and indexing its call result raised the false | ||
| error: | ||
|
|
||
| ```wfl | ||
| define action called load_data: | ||
| try: | ||
| return [1 and 2] | ||
| when error: | ||
| return [3 and 4] | ||
| end try | ||
| end action | ||
|
|
||
| store xs as call load_data | ||
| store x0 as xs[0] // error[ERROR]: Cannot index into Nothing (false) | ||
| ``` | ||
|
|
||
| This is a very common shape — try blocks wrap exactly the file/database/parse | ||
| work that helper actions return values from, which is why Scriptorium hit it. | ||
|
|
||
| **Fix:** `collect_return_types` now descends into `TryStatement` (body, every | ||
| `when` clause, `otherwise`, `finally`) and `WaitForStatement` (its wrapped | ||
| inner statement). Its sibling `check_return_statements` — the traversal used | ||
| when an annotation *is* present — got the same arms so the two stay in sync. | ||
|
|
||
| ## Residual 2 — container method results were registered as `Nothing` | ||
|
|
||
| The analyzer registers container methods in its container registry with | ||
|
|
||
| ```rust | ||
| return_type: return_type.as_ref().cloned().unwrap_or(Type::Nothing), | ||
| ``` | ||
|
|
||
| and the type checker's `Expression::MethodCall` arm reads that registry to | ||
| type `instance.method()`. Unannotated value-returning methods (the norm) were | ||
| therefore typed `Nothing` at every call site — the container-flavored twin of | ||
| the original #560, which #575 only fixed for top-level actions: | ||
|
|
||
| ```wfl | ||
| create container Store: | ||
| property label: Text | ||
| action get_items: | ||
| return [1 and 2] | ||
| end | ||
| end | ||
| ... | ||
| store xs as s.get_items() | ||
| store x0 as xs[0] // error[ERROR]: Cannot index into Nothing (false) | ||
| ``` | ||
|
|
||
| **Fix,** mirroring the #575 + #591 design for top-level actions: | ||
|
|
||
| - The analyzer seeds unannotated instance *and* static methods with a | ||
| provisional `Type::Unknown` (degrades gracefully after #588/#589) instead of | ||
| `Type::Nothing`. | ||
| - The type checker's `ContainerDefinition` arm now checks each method body — | ||
| instance *and* static — with the method's parameters in scope (mirroring the | ||
| top-level action arm's #553 handling), infers the real return type from the | ||
| body's `return` statements, and writes it back into the registry through a | ||
| new `Analyzer::get_container_mut`. Void methods still end up `Nothing`, | ||
| exactly as before. | ||
| - Static methods matter here even though static method *calls* | ||
| (`Container.method()`) are still a future feature at runtime: the | ||
| refinement keeps `Container.method` member access reporting an accurate | ||
| function type, and without it a void static method would have stayed | ||
| `Unknown` forever instead of `Nothing` (a strictness regression flagged by | ||
| review on the first revision of this change). | ||
| - Annotated container methods (e.g. `action get_info: Text`) now also get | ||
| their `return` statements validated against the annotation via | ||
| `check_return_statements`, mirroring the top-level action arm. | ||
| - Inherited methods get the fix for free: the `MethodCall` parent-walk reads | ||
| the same registry entries. | ||
|
|
||
| ## Verification (TDD) | ||
|
|
||
| `tests/action_return_type_residuals_test.rs` was written first and confirmed | ||
| failing (4/4) before the fix: | ||
|
|
||
| - return inside `try`/`when error` infers the return type | ||
| - return inside `try`/`when`/`otherwise` infers the return type | ||
| - unannotated container method result is not typed `Nothing` | ||
| - inherited container method result is not typed `Nothing` | ||
|
|
||
| All four pass after the fix, alongside the full `cargo test --all` suite, | ||
| `cargo clippy --all-targets --all-features -- -D warnings`, and the | ||
| `TestPrograms/` backward-compatibility run. A combined Scriptorium-shaped | ||
| stress case (recursive action indexing a map returned from inside `try`, | ||
| wrapped by a container method) type-checks completely clean. | ||
|
|
||
| ## Notes | ||
|
|
||
| - No syntax, keyword, or runtime behavior changed — this is purely a | ||
| false-positive-diagnostics fix, so no user-facing docs needed updating. | ||
| - Remaining known limitation (unchanged by this pass): a method call typed | ||
| *before* its container's definition statement is reached in program order | ||
| still sees the provisional `Unknown` — safe (permissive) but not concrete. | ||
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.