Repository navigation
Conversation
test() without arguments returns a proxy to the running test case, but PHPStan only saw the declared HigherOrderTapProxy|TestCall, so method calls through it were unchecked. Resolve it the same way $this is resolved in test closures. Files no binding covers keep the declared type, so helper files outside a Pest.php binding report nothing new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hook property reader only collected `$this->name = …` assignments, so a hook written as `test()->name = …` left the property mixed. A bare test() proxies the running test case, so treat it as the same receiver. Reads through test() resolve via the existing property extension now that test() is typed as the test case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| } | ||
|
|
||
| // @note: a bare test() proxies the running test case, so it gets the same type as $this in a test closure; an unbound file keeps the declared type. | ||
| return $this->pestTestCaseType->resolveIfBound($scope->getFile()); |
There was a problem hiding this comment.
Should this be limited to test and hook closures?
Right now any bare test() call in a bound file gets the test case type, including calls inside normal helper functions. The new testBareTestCallInHelperFunction test makes that behavior explicit.
A file binding tells us which test case Pest will use, but it does not mean test() is always running in a test context. This seems broader than $this inference, which only exists inside the bound closures.
There was a problem hiding this comment.
Ah, good catch! Indeed, $this behaves a bit different, and I agree test() should follow it.
At runtime, a bare test() returns the test case only while a test is running. Inside a helper function, the type depends on the caller, so we can't know it.
Changed in 9ac88da.
Side note: $this (and also a bare test()) is typed as the test case inside a describe() body too, although that body runs before any test does (so it can't be the test case). It was like that before this PR and likely doesn't matter in practice, so I left it alone, but I'm happy to fix it if you want.
A bare test() returns the running test case only while a test runs. In a helper function the type depends on the caller, so it now keeps the declared type. Inside test, hook and describe closures, and in closures nested in them, test() gets the same type as $this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The problem
$thisin a test closure is typed as the bound test case. A baretest()call is not. Many suites calltest()->instead of$this->, so those calls get no checking:Projects hide the noise with a blanket ignore of
Call to an undefined method Pest\PendingCalls\TestCall|Pest\Support\HigherOrderTapProxy::…. That ignore also hides real typos. Properties set throughtest()->name = …in abeforeEachstaymixed.The change
Two commits.
TestFunctionReturnTypeExtensiontypes a baretest()call as the bound test case. It is the same type$thisgets in test closures. It uses a newPestTestCaseType::resolveIfBound().resolve()is unchanged.uses()orpest()binding keeps the declared return type oftest(). Helper files outside a binding report nothing new.test('description')is unchanged.PestHookPropertyReaderaccepts a baretest()as the assignment receiver, as well as$this.test()->name = …in abeforeEachnow types the property. Reads work through the existingTestCaseDynamicPropertyTypeExtension.Tests
tests/Fixtures/CustomTestCaseInference/Feature/test-function-custom-testcase.php:test()in a bound file, including hook properties.tests/Type/data/test-function.php:test()in an unbound file keeps its declared type.tests/Type/data/test-hook-properties.php: hook properties assigned throughtest().Each new bound test fails when the matching change is disabled.
Checks
pest: 529 passed (520 before)phpstan analyseat level max: 0 errorsrector --dry-run: 0 changespint --test: passThe suite needed
php -d memory_limit=2G vendor/bin/pest, run serially. At the default 128M it runs out of memory on a clean checkout too.🤖 Generated with Claude Code