Skip to content

Fix of form to resolve include-exposed actions in all contexts - #581

Merged
logbie merged 3 commits into
mainfrom
claude/issue-580-testing-7g3aqy
Jul 5, 2026
Merged

Fix of form to resolve include-exposed actions in all contexts#581
logbie merged 3 commits into
mainfrom
claude/issue-580-testing-7g3aqy

Conversation

@logbie

@logbie logbie commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes issue #580 where the idiomatic of form (e.g., greet of "bob") failed to resolve actions exposed by include from statements in top-level code and action bodies, while the call ... with form worked correctly. The root cause was that the of form parses as FunctionCall { function: Variable(..), .. } and was being analyzed as a fatal undefined variable before the include-aware relaxation could apply.

Key Changes

Analyzer (src/analyzer/mod.rs)

  • Skip direct analysis of bare-Variable callees in FunctionCall to prevent premature fatal "undefined variable" errors
  • Add explicit handling for bare-Variable callees that mirrors the ActionCall relaxation:
    • If the callee is in scope or is a builtin/parameter, analyze arguments normally
    • If has_includes is true, emit a non-fatal warning instead of a fatal error (allowing runtime resolution by included files)
    • If no includes, preserve the original fatal behavior for genuinely undefined names
  • Extend builtin recognition to include action parameters and the count loop variable

Type Checker (src/typechecker/mod.rs)

  • Add early return for bare-Variable callees when has_includes is true and the callee is not statically known
  • Return Type::Any for such cases (matching the ActionCall behavior) to avoid cascading "could not infer type" errors
  • Still analyze arguments to catch type errors within them

Test Coverage

Comprehensive regression test suite (tests/include_of_form_resolution_test.rs) covering:

  • of form in top-level statements, action bodies, container actions, main loops, and test blocks
  • Multi-argument and nested/chained of calls
  • Nested includes (file A includes B, B includes C, A uses C's actions)
  • Guard rails: undefined callees without includes remain fatal; typos with includes are warnings; undefined arguments are still reported; call ... with form continues to work

This fix ensures the of form is as capable as the call ... with form while maintaining type safety and error reporting for genuinely undefined names.

https://claude.ai/code/session_01Kj9BVovsRf5DdNWkeuf3wd

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of of-style function calls so valid calls are no longer flagged as undefined too early.
    • Calls that come from included code now analyze more reliably, reducing false errors and warnings.
    • Existing built-in names and special loop variables are recognized correctly in these calls.
    • Type analysis now avoids cascading errors for included actions while still validating arguments.

Issue #548 relaxed the fatal "Undefined action" for include-exposed
actions, but only for the `call <action> with <arg>` form. The idiomatic
`<action> of <arg>` form parses to `Expression::FunctionCall` whose callee
is a bare `Variable`, a path that never reached #548's relaxation — so it
stayed fatal at top level and inside action bodies (while incidentally
"working" inside `main loop`/`describe`/`test`, whose bodies the analyzer
does not descend into). This made natural multi-file libraries unusable
through their idiomatic top-level API.

Analyzer (`src/analyzer/mod.rs`): in the `FunctionCall` arm, stop
recursing into a bare-`Variable` callee (which reported it as fatal before
the block could relax it) and resolve it inline. When the callee is
unresolved, not a builtin/parameter, and the program uses `include from`,
emit the same non-fatal "Undefined action" warning as the `ActionCall`
path; otherwise fall back to the pre-existing `report_undefined_name`
behavior (preserving the `try_depth` downgrade and the no-include fatal).

Type checker (`src/typechecker/mod.rs`): mirror the `ActionCall`
relaxation in the `FunctionCall` arm — an unresolved-`Variable` callee in
an include-using program yields `Type::Any` instead of `Unknown`, avoiding
the spurious "could not infer type" cascade the `call` form already avoids.

The same change fixes the #547-class nested-include case: a file that
references (via `of`) an action from a file it itself includes now passes
its isolated analysis, because it has a top-level `include` and so takes
the relaxation.

Adds `tests/include_of_form_resolution_test.rs`: the `of` form across
top-level / action body / container action / main loop / describe-test,
multi-arg and chained `of`, the nested-include scenario, plus guard tests
(undefined-without-include stays fatal, typo-with-include is a warning,
undefined argument still reported, `call ... with` unregressed) and a
snapshot pinning that main-loop bodies are currently not statically
analyzed (recommended follow-up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kj9BVovsRf5DdNWkeuf3wd
Copilot AI review requested due to automatic review settings July 5, 2026 16:21
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@logbie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ad9fad7-3dc1-4601-a2c3-b192e56f366c

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6bb28 and 2472f48.

📒 Files selected for processing (2)
  • src/analyzer/mod.rs
  • src/typechecker/mod.rs
📝 Walkthrough

Walkthrough

The analyzer defers callee analysis for bare-variable FunctionCall expressions, relaxing undefined-name errors to warnings when include from is present (while preserving fatal errors otherwise for builtins/params/count-unaware cases). The typechecker mirrors this by returning Type::Any for such include-exposed calls. A new integration test suite validates these behaviors.

Changes

Include-aware of-form resolution

Layer / File(s) Summary
Analyzer deferral and relaxation for bare-variable callees
src/analyzer/mod.rs
Skips upfront callee analysis for bare-variable FunctionCall expressions and expands fallback logic to allow builtins/action parameters/count, downgrade undefined callees to warnings when include from exists, and otherwise preserve fatal report_undefined_name behavior.
Typechecker Any-type fallback for include-exposed calls
src/typechecker/mod.rs
Adds an early return in FunctionCall type inference: when the callee is an unresolvable bare variable and include from is present, infers argument types and returns Type::Any instead of erroring.
Integration tests for of-form resolution
tests/include_of_form_resolution_test.rs
Adds test helpers and a full regression/guard-rail suite covering top-level, action, container, main loop, describe/test, multi-argument, nested, and multi-file include scenarios for the of call form, plus fatal-error and analyzer-coverage edge cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SourceProgram
  participant Analyzer
  participant TypeChecker
  participant IncludeState as "has_includes"

  SourceProgram->>Analyzer: analyze_expression(FunctionCall with bare-variable callee)
  Analyzer->>Analyzer: check scope for callee name
  alt name resolved in scope
    Analyzer->>Analyzer: analyze arguments
  else name is builtin, action param, or count
    Analyzer->>Analyzer: treat as callable, analyze arguments
  else name unresolved
    Analyzer->>IncludeState: check include from present
    alt include from present
      Analyzer->>Analyzer: emit "Undefined action" warning
    else no includes
      Analyzer->>Analyzer: report_undefined_name (fatal)
    end
    Analyzer->>Analyzer: analyze arguments
  end

  SourceProgram->>TypeChecker: infer_expression_type(FunctionCall)
  TypeChecker->>TypeChecker: check if callee is unresolvable bare variable
  TypeChecker->>IncludeState: check include from present
  alt include from present
    TypeChecker->>TypeChecker: infer argument types
    TypeChecker-->>SourceProgram: return Type::Any
  else no includes
    TypeChecker->>TypeChecker: standard function-type inference
    TypeChecker-->>SourceProgram: return inferred type or error
  end
Loading

Possibly related issues

Possibly related PRs

  • WebFirstLanguage/wfl#550: Introduces the has_includes flag and suppression of undefined-action errors/Type::Any inference that this PR builds upon for bare-variable callees.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing of form resolution for include-exposed actions across contexts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-580-testing-7g3aqy

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/analyzer/mod.rs (1)

2274-2374: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared include-aware relaxation logic.

This fallback block (builtin/param/count pass-through → has_includes warning → fatal otherwise) is structurally near-identical to the ActionCall relaxation block at Lines 2593-2613. Duplicating this pattern is exactly how issue #580 happened in the first place: call ... with got the #548 relaxation but the of form's separate code path didn't. Extracting a shared helper (e.g. fn resolve_undefined_callee(&mut self, name: &str, line, column, arguments)) would prevent a similar path from drifting out of sync again in the future.

♻️ Sketch of a shared helper
fn report_undefined_callee_relaxed(
    &mut self,
    label: &str, // "Variable" or "action"
    name: &str,
    line: usize,
    column: usize,
) {
    if self.has_includes {
        self.warnings.push(SemanticError::new(
            format!("Undefined {label} '{name}'"),
            line,
            column,
        ));
    } else {
        self.report_undefined_name(format!("{label} '{name}' is not defined"), line, column);
    }
}
🤖 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/analyzer/mod.rs` around lines 2274 - 2374, The include-aware
undefined-callee handling is duplicated here and in the ActionCall path, so
extract the shared fallback into a helper to keep the builtin/parameter/count
pass-through, has_includes warning, and fatal-otherwise behavior in one place.
Move the common logic out of the current `Expression::Variable` function-call
branch and the `ActionCall` relaxation block into a shared method such as
`resolve_undefined_callee` or `report_undefined_callee_relaxed`, then have both
paths call it so future changes stay in sync.
src/typechecker/mod.rs (1)

2662-2681: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct fix; mirrors the analyzer relaxation.

The early-return correctly avoids downgrading the call result to Unknown (which would otherwise trigger a spurious "Could not infer type" at the store site) by returning Any instead, while still inferring argument types so nested type errors aren't lost.

One maintainability note: is_known here duplicates the equivalent check inside ActionCall's inference path (Lines 3062-3097). Since this exact kind of duplication is what let issue #580 slip through in the analyzer, consider extracting a small shared helper (e.g. fn is_statically_known_callee(&self, name: &str) -> bool) used by both FunctionCall and ActionCall inference to keep them from diverging again.

🤖 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/typechecker/mod.rs` around lines 2662 - 2681, The fix is correct, but the
callee-knowledge check in the FunctionCall path duplicates the same logic used
in ActionCall inference, which can drift over time. Extract the shared “is this
callee statically known?” check into a small helper on the analyzer or
typechecker (for example, a method used by both FunctionCall and ActionCall
handling), and update both paths in mod.rs to call it so the
`get_symbol`/builtin/action-parameter logic stays consistent in one place.
🤖 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.

Nitpick comments:
In `@src/analyzer/mod.rs`:
- Around line 2274-2374: The include-aware undefined-callee handling is
duplicated here and in the ActionCall path, so extract the shared fallback into
a helper to keep the builtin/parameter/count pass-through, has_includes warning,
and fatal-otherwise behavior in one place. Move the common logic out of the
current `Expression::Variable` function-call branch and the `ActionCall`
relaxation block into a shared method such as `resolve_undefined_callee` or
`report_undefined_callee_relaxed`, then have both paths call it so future
changes stay in sync.

In `@src/typechecker/mod.rs`:
- Around line 2662-2681: The fix is correct, but the callee-knowledge check in
the FunctionCall path duplicates the same logic used in ActionCall inference,
which can drift over time. Extract the shared “is this callee statically known?”
check into a small helper on the analyzer or typechecker (for example, a method
used by both FunctionCall and ActionCall handling), and update both paths in
mod.rs to call it so the `get_symbol`/builtin/action-parameter logic stays
consistent in one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a7b00a5-ed0e-4ce7-85b4-cb05b1a18f75

📥 Commits

Reviewing files that changed from the base of the PR and between fc40739 and 8a6bb28.

📒 Files selected for processing (3)
  • src/analyzer/mod.rs
  • src/typechecker/mod.rs
  • tests/include_of_form_resolution_test.rs

claude and others added 2 commits July 5, 2026 16:39
…580)

Addresses CodeRabbit review feedback on PR #581. Issue #580 was caused by
the `of` form (`FunctionCall`) and `call ... with` form (`ActionCall`)
maintaining separate copies of the include-aware undefined-callee logic,
so #548's relaxation reached only one of them. Consolidate the shared
decision so the two paths cannot drift apart again. No behavior change.

Analyzer (`src/analyzer/mod.rs`): extract
`warn_undefined_callee_if_includes`, which owns the identical part — the
`has_includes` check and the non-fatal "Undefined action" warning — and
returns whether the caller must still emit its own fatal diagnostic. Both
the `FunctionCall` bare-Variable-callee tail and the `ActionCall` tail
route through it; each keeps its distinct fatal path (the `of` form's
try_depth-aware `report_undefined_name` "Variable '…' is not defined"
vs. the `call` form's "Undefined action" error).

Type checker (`src/typechecker/mod.rs`): extract
`is_callable_without_symbol` (builtins, action parameters, internal test
stubs) and use it in both the `FunctionCall` and `ActionCall` "is this
callee statically known?" checks.

Verified unchanged: analyzer/typechecker lib unit tests (465),
`docs_parser_and_include_fixes_test` (#547/#548/#551/#553),
`include_of_form_resolution_test` (13), full suite, clippy -D warnings,
fmt, and 102/102 TestPrograms integration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kj9BVovsRf5DdNWkeuf3wd
Copilot AI review requested due to automatic review settings July 5, 2026 16:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants