Skip to content

typecheck: a _ctx-first container that returns its handle is not a block setter - #2060

Merged
nicolas-maman merged 5 commits into
mainfrom
fix/e0200-container-returns-handle
Sep 18, 2026
Merged

nicolas-maman merged 5 commits into
mainfrom
fix/e0200-container-returns-handle

Conversation

@nicolas-maman

Copy link
Copy Markdown
Collaborator

Why

Moving aether-ui from 0.645 to 0.681 (aether-lang-dev/aether-ui#147) fails every example in the fan-out with:

error[E0200]: 'vstack' is a block setter, not a node builder; call it inside a builder's block (e.g. `mod.bundle() { vstack() }`), not as a top-level `ui.vstack() { ... }`
  --> examples/counter/counter.ae:14:11

E0200 (b551fe5, 0.667) discriminates a block setter from a DSL container by "the callee's module also defines a builder", on the premise that a widget-style module has none. aether-ui's ui module has four (window, render_to, record, nav_page) beside ~50 _ctx-first containers (vstack, hstack, scrollview, splitview, …), so the rule's own comment ("a widget-style DSL module has no builders and is never flagged") does not hold for the largest DSL consumer in the org.

What

A fourth condition: the callee must yield no value. A container returns the handle its trailing block runs inside — that return value is the block's _ctx — while a block setter records config and returns nothing, which is precisely why its own trailing block can never be entered. That is the semantic line, not a heuristic:

  • every aeb setter (rspec, minitest, env, gem_dep, rbenv, …) is void → still flagged;
  • every aether-ui container returns a handle → not flagged;
  • aether-ui's own void _ctx-first functions (bg_color, font_size, tooltip, …) are setters and are never written with a trailing block in that tree — and would now be flagged if they were, which is the rule working.

The void check runs on the already-resolved symbol (fn_yields_no_value, factored out of call_yields_no_value): the existing helper re-looks the callee up by bare name, which a mod.name call does not resolve, so it answered "yields a value" for every qualified call.

Verified

  • tests/integration/setter_in_builder_position: the three existing cases plus LEGIT C (uimod: a builder beside handle-returning containers — uimod.window("app") { vstack(10) { label("hello") } } compiles) and MISUSE B (uimod.font_size(14) { … }, the module's void setter, still errors). 5/5.
  • All 260 .ae sources under aether-ui examples/, apps/, tests/ type-check with this build (aetherc --check --lib .), against 1 before (every app).

…ock setter

E0200 (0.667) used "the callee's module also defines a builder" as the
whole discriminator between a block setter and a DSL container, on the
premise that a widget-style module has no builders. aether-ui's ui
module has four (window, render_to, record, nav_page) beside some fifty
_ctx-first containers, so `ui.vstack(10) { ... }` — every app in that
repository — became a compile error past 0.666 (aether-ui#147).

The rule now also requires that the callee yield no value. A container
RETURNS the handle its block runs inside (that value is the block's
_ctx); a block setter records config and returns nothing, which is
exactly why its own trailing block can never be entered. aeb's setters
are all void, so the misuse the rule was written for is still caught.

The void check runs on the already-resolved symbol: call_yields_no_value
re-looks the callee up by its bare name, which a `mod.name` call does
not resolve, so it answered "yields a value" for every qualified call.

tests/integration/setter_in_builder_position gains uimod — a builder
beside handle-returning containers and one void setter — with LEGIT C
(the container compiles) and MISUSE B (the setter still errors).
@nicolas-maman
nicolas-maman merged commit 1bd9ae1 into main Sep 18, 2026
28 checks passed
@nicolas-maman
nicolas-maman deleted the fix/e0200-container-returns-handle branch September 18, 2026 07:02
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.

1 participant