Skip to content

Fix {{fn}} with generic callbacks no longer inferring from the arg type - #1248

Merged
NullVoxPopuli merged 1 commit into
typed-ember:mainfrom
BoussonKarel:fix-1247-fn-generic-callback
Sep 23, 2026
Merged

NullVoxPopuli merged 1 commit into
typed-ember:mainfrom
BoussonKarel:fix-1247-fn-generic-callback

Conversation

@BoussonKarel

Copy link
Copy Markdown
Contributor

Fixes #1247

Since #1189, every {{fn f a}} compiles to a comma pair:

(resolve(fn)(f, a), bindPositional(f, a))

The fn call on the left has no contextual type. That's fine for most callbacks, but not for a generic one like <K extends keyof Person, V extends Person[K]>(key: K, value: V) => void: TypeScript needs the expected type to work out V. Without it, V falls back to string | number and valid code is rejected.

Fix

The comma pair is only needed in one place: inside the bindInvokable(...) half of {{component}} / {{helper}} / {{modifier}}, where a real fn call collapses inference (#1147). So now:

  • Inside bindInvokable(...), fn becomes just bindPositional(...). The keyword call next to it already validates the arguments.
  • Everywhere else, fn is a plain resolve(fn)(...) call again, like in ≤ 1.8.8, so it gets the expected type from its slot.

While in there: bindPositional's Bound is now const. Without that, a literal bound argument like "name" widened to string, and the result became never.

Tests

  • New fn-generic-callback.test.gts with the repro from the issue, plus a mismatched callback that must still error
  • The Issues with fn helper in combination with component helper #1147 guard (fn-in-nested-args.test.gts) and globals/fn.gts still pass unchanged
  • Transform snapshots updated, plus a new one for fn inside a bind-invokable keyword

Not fixed here

(component Field onChange=(fn this.update "name")) with the same generic callback still errors, and a mismatched fn bind inside (component ...) on a generic component isn't caught. Both behave the same on main, so they aren't regressions. The keyword's own validation call doesn't give fn a useful expected type there. That's probably worth a separate issue.

🤖 Generated with Claude Code

…ried keywords

Since typed-ember#1189, every `{{fn f a}}` was emitted as a
`(resolve(fn)(f, a), bindPositional(f, a))` comma pair. The validating call
on the left of the comma has no contextual type, so a callback whose type
parameters depend on each other (`<K extends keyof M, V extends M[K]>`)
could no longer be inferred and valid code was rejected.

The comma pair was only needed inside the result half of the
`{{component}}`/`{{helper}}`/`{{modifier}}` pair, where a real `fn` call
collapses `bindInvokable`'s inference (typed-ember#1147). Now:

- inside `bindInvokable(...)` args, `fn` emits just `bindPositional(...)`
  (the keyword half next to it already validates the arguments)
- everywhere else, `fn` emits a plain `resolve(fn)(...)` call again, so it
  gets its slot's contextual type

`bindPositional`'s `Bound` is also made `const`, so literal bound args like
`"name"` aren't widened to `string` (which made the result `never`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@NullVoxPopuli NullVoxPopuli 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.

tyty

@NullVoxPopuli NullVoxPopuli added the bug Something isn't working label Sep 23, 2026
@NullVoxPopuli
NullVoxPopuli merged commit 6d115dc into typed-ember:main Sep 23, 2026
7 of 8 checks passed
@github-actions github-actions Bot mentioned this pull request Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

{{fn}} with a generic callback no longer infers from the arg's type (regression from #1189)

2 participants