Skip to content

types: a callback that misses a closed arrow points at ->{e} - #181

Merged
simontreanor merged 1 commit into
mainfrom
closed-callback-hint
Oct 17, 2026
Merged

simontreanor merged 1 commit into
mainfrom
closed-callback-hint

Conversation

@simontreanor

Copy link
Copy Markdown
Owner

Fixes #180.

Before, under the whole call:

error: effect mismatch: cannot unify Writer -> Async unit with Writer ->{io, async} Async unit
  --> 13:14
13 | let listen = serve handle "127.0.0.1" 7777
   |              ^^^^^^^^^^^^

After, on the argument:

error: `handle` performs {io, async}, but `serve` takes this callback as `Writer -> Async unit`, which performs nothing; to accept a callback whatever it does, give that arrow an effect variable in the extern that declares it: `Reader -> Writer ->{e} Async unit`
  --> 13:20
13 | let listen = serve handle "127.0.0.1" 7777
   |                    ^^^^^^

How: infer_apply_plain hands an effect mismatch to closed_callback_hint. It walks the declared callback and the argument arrow by arrow to the first closed declared effect the argument's differs from, then reports at the argument with both effects and the declared type rewritten with ->{e} on that arrow (show_with_effect_var). Diagnostics have no separate help line, so the guidance goes in the message, as the String.concat and List.concat hints do.

The pasted-set trap the issue warns about is covered too. Declared effects are exact, so ->{io, async} rejects a handler that only awaits, and that case now says which performs exactly {io, async} and offers the same ->{e} fix.

Unchanged: a plain type mismatch (serve 5 …) keeps type mismatch: expected Writer -> Async unit, found int. Lambdas (this function performs …), the pipe form (handle |> serve) and a callback in a later argument slot all get the new message.

Also: e2e_an_async_callback_handed_to_python_runs from #177 had pasted in ->{io, async}, the exact mistake #180 describes. It now uses ->{e}. DESIGN §4 records the message.

Tests: the exact message and span for the issue's example, the exact-set case, and the unchanged type mismatch. Checked locally: cargo fmt, cargo clippy --all-targets (clean), cargo test (every binary), docs/verify_lessons.py.

An extern callback arrow written without an effect variable is closed, so a
handler that does anything (an async { } block performs async) failed with
"effect mismatch: cannot unify Writer -> Async unit with Writer ->{io, async}
Async unit" under the whole call. The obvious repair, pasting the inferred
set into the extern, then rejects a handler that only awaits, because declared
effects are exact.

infer_apply_plain now hands an effect mismatch to closed_callback_hint, which
walks the declared callback and the argument arrow by arrow to the first
closed declared effect the argument's differs from, and reports it at the
argument:

  `handle` performs {io, async}, but `serve` takes this callback as
  `Writer -> Async unit`, which performs nothing; to accept a callback
  whatever it does, give that arrow an effect variable in the extern that
  declares it: `Reader -> Writer ->{e} Async unit`

A declared exact set says "which performs exactly {io, async}". A plain type
mismatch keeps its message. The cold-async e2e test, which had pasted in
`->{io, async}`, now uses `->{e}`. DESIGN §4 records the message.

Fixes #180
@simontreanor
simontreanor merged commit 262e368 into main Oct 17, 2026
22 checks passed
@simontreanor
simontreanor deleted the closed-callback-hint branch October 17, 2026 23:37
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.

Point at ->{e} when an argument's effect meets a closed callback arrow in an extern

1 participant