Repository navigation
Let a purity template fit wherever its bound does - #12023
Merged
danog merged 1 commit intoSep 28, 2026
Merged
Conversation
A closure whose purity is a purity template (`Closure[P](): int`) never fit a capability set, even its own bound: passed to a constructor binding a class purity template (`new Box($f)` with `@param Closure[C](): int $cb`), it bound nothing and the object fell back to the default purity (`Box[impure]` instead of `Box[P]`). Co-Authored-By: Claude Opus 5.5
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
A closure whose purity is a purity template (
Closure[P](): int) never fit a capability set, not even its own bound. So passing it to a constructor that binds a class purity template bound nothing, and the object fell back to the template's default purity:Now the comparator lets a purity template fit wherever its bound does, so
Cis bound toP. The added tests check thatmakeBox(fn(): int => 1)->fire()is accepted in a pure function, and that the same call with a closure that echoes is reported asImpureMethodCall.This lets a function forward the purity of a closure parameter to the object it returns, instead of inheriting it through
@psalm-purity-from-templatewhen it never calls the closure itself.The full test suite passes except for failures that come from the test machine's environment (callmap/reflection mismatches for its extensions,
zend.assertions, a loadedapcu).🤖 Generated with Claude Code