`expect()->toBeInstanceOf()` / `not->toBeNull()` / `toBeString()` etc. don't narrow types like PHPUnit's assert methods`
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
This issue has been made with Claude's help, but it's a real human asking 😊
Context
PHPUnit's assertInstanceOf(), assertNotNull(), assertIsString()/Int/Float/Array/Numeric carry @phpstan-assert annotations directly in phpunit/phpunit's Assert class (e.g. @phpstan-assert null $actual on assertNotNull). PHPStan's core reads these natively and narrows the variable's type in the Scope for every subsequent line.
The Pest equivalents (expect($x)->toBeInstanceOf(Y::class), expect($x)->not->toBeNull(), expect($x)->toBeString(), …) don't do this. I checked pest-plugin-phpstan's source and found RedundantExpectationRule / ImpossibleExpectationRule (backed by MatcherAssertionRegistry), which read the scope's already-known type to flag a redundant/impossible expectation — but no class in the package implements PHPStan's TypeSpecifyingExtension, so nothing ever writes a narrowed type back into scope.
Impact
In a large test suite (Laravel/Larastan, level 9), converting $this->assertInstanceOf(...) / $this->assertNotNull(...) to their Pest expect() equivalents breaks static analysis on every line afterward that reads the now-unnarrowed value (Cannot access property $x on Y|null, ... on mixed, etc.). We ended up having to keep ~130 PHPUnit-style assertions specifically for this reason, everywhere the asserted value is read again.
Question
Is this a deliberate scope limitation, or something that could be implemented via a TypeSpecifyingExtension for the matchers in MatcherAssertionRegistry::METHOD_ASSERTIONS (toBeString, toBeInt, toBeFloat, toBeArray, toBeInstanceOf, not->toBeNull, …)? Happy to help investigate/contribute if it's wanted — just want to check first whether there's a known reason (e.g. the ->not chain, or higher-order testing, making this unsafe to implement in general) before looking into a PR.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading MatcherAssertionRegistry::METHOD_ASSERTIONS and the existing RedundantExpectationRule and ImpossibleExpectationRule in pest-plugin-phpstan. Investigate how a PHPStan TypeSpecifyingExtension could consume the Pest matchers, including not-toBeNull and toBeInstanceOf. Done means supported expectations narrow the value for subsequent analysis without making unsafe assumptions, with the relevant static-analysis behavior verified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- testing, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100