fix: recognize bound trait methods on $this in test closures - #8
Open
maks-oleksyuk wants to merge 1 commit into
Open
fix: recognize bound trait methods on $this in test closures#8maks-oleksyuk wants to merge 1 commit into
maks-oleksyuk wants to merge 1 commit into
Conversation
`$this->someMethod()` inside a test closure was reported as `method.notFound` whenever `someMethod()` came from a trait bound via `pest()->use()`/`uses()`, since a trait isn't a valid ObjectType and can't be mixed into `$this`'s inferred type. This forced manual `@phpstan-ignore-next-line` workarounds for common patterns like `use PHPMock;` in a test file. Add `BoundTraitMethodCallIgnoreExtension`, which suppresses that specific `method.notFound` error when the current file's Pest config (file-scoped `uses()` or directory-scoped `.in()` bindings, via `PestConfigReader`) declares a bound trait that natively has the called method. It's scoped per-file via `Scope::getFile()`, so it doesn't leak trait methods onto unrelated test files. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Problem
$this->someMethod()inside a test closure is reported asCall to an undefined methodwheneversomeMethod()comes from a trait bound viapest()->use()/uses(), rather than a realextend()-ed class.TestClosureThisTypeExtensionalready resolves the bound classes/traits for$this's type, but it explicitly filters out traits when building theObjectTypefor$this— a trait isn't a validObjectTypetarget, so there's no way to represent "also has these trait methods" in$this's inferred type directly.In practice this forces manual
@phpstan-ignore-next-line method.notFoundworkarounds for very common patterns, e.g.:Fix
Add
BoundTraitMethodCallIgnoreExtension, anIgnoreErrorExtensionthat suppressesmethod.notFoundspecifically when:$this->someMethod(),PestConfigReader, covering both file-scopeduses()/pest()->use()and directory-scoped.in()bindings — declares a bound trait that natively hassomeMethod().This is scoped per-file via
Scope::getFile(), so it can't leak a trait method's availability onto unrelated test files that don't bind that trait — unlike a global "mixin" registration, which would apply everywhere regardless of which file is being analysed.I considered representing this precisely in
$this's actual type instead (e.g. a customTypedecorator overridinghasMethod()/getMethod()), which would also give correct return-type inference for calls into the trait. I went with the ignore-extension approach instead because it mirrors the existing, already-established pattern for this kind of known-safe suppression in this package (ProtectedMethodCallIgnoreExtension,PestInternalClassAccessIgnoreExtension,ArchExpectationPropertyIgnoreExtension), and is a much smaller, lower-risk surface than hand-implementing PHPStan'sTypeinterface.$this's inferred type itself is unchanged — confirmed by the existinglocal-uses-trait-only.phpfixture, which still asserts$thistypes as plainPHPUnit\Framework\TestCasewhen only a trait is bound.Testing
BoundTraitMethodCallIgnoreExtensionTest.php: the positive case (method declared on a bound trait), a method not declared on any bound trait, a non-method.notFounderror identifier, and a method call on a variable other than$this.pest --parallel: all tests pass.phpstan analyse: no errors.pint/rector --dry-run: clean.uses(PHPMock::class); ... $this->getFunctionMock(...)inside anit()closure no longer needs a manual@phpstan-ignore-next-line.