Repository navigation
Skip UnusedMethodParametersRule for private methods referenced as callables within their class - #6805
phpstan-bot wants to merge 3 commits into
Conversation
…allables within their class - A private method handed out as a callable is called from outside the class with every argument, so its signature is a contract like a public method's. - Before reporting, the rule scans the declaring class-like AST (via the cached `defaultAnalysisParser`) for a first-class callable (`self::m(...)`, `static::m(...)`, `$this->m(...)`), a callable array (`[self::class, 'm']`, `[$this, 'm']`, also inside `Closure::fromCallable()`) or a callable string (`'self::m'`, `'Foo::m'`). Names match case-insensitively. - For trait methods, both the using class and the trait itself are scanned. Anonymous classes are matched by their start line. - The scan runs only when there would be an error to report, so methods with no unused parameters cost nothing extra. - Probed nullsafe first-class callables: PHP does not allow them, so they are not handled. `UnusedFunctionParametersRule` already reports global functions whatever their visibility, so it was left alone.
ClassMethodsNode now carries the VariableWritesNode of each method body together with the scope inside the method, so rules listening to it can combine parameter usage with the method calls gathered in the class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Instead of re-parsing the file and searching the class AST, the rule now listens to ClassMethodsNode and finds callable references among the method calls ClassStatementsGatherer collected. Errors are still transformed with the scope inside the method, so they stay identical (including trait context). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Yes, it can be done that way. I've reworked the rule to use the gatherer's Why it needed more than a lookup: the rule listened to Commit 1,
Commit 2,
Behaviour change: callable strings like Verification:
|
Summary
With bleeding edge
unusedParameters,UnusedMethodParametersRulereportedmethod.unusedParameteron private methods that the class hands out as callables (for exampleself::plus(...)orClosure::fromCallable([self::class, 'minus'])). Once a method escapes this way, outside code calls it with every argument, so its signature is a contract. The rule now skips private methods that their class references as a callable.Changes
src/Rules/Methods/UnusedMethodParametersRule.php: after computing errors, and only if there are any, scan the class-like AST for a callable reference to the method:self::m(...),static::m(...),$this->m(...)(anyMethodCall/StaticCallfirst-class callable with that name)[self::class, 'm'],[$this, 'm'](this also coversClosure::fromCallable([...]),array_map([...], ...), and similar)'self::m','Foo::m'conf/config.level4.neon: inject@defaultAnalysisParserinto the rule. The parser is cached, so the file being analysed is not parsed again.$x?->m(...)): not valid PHP.UnusedFunctionParametersRule: it reports all global functions, public ones included, so being referenced as a callable doesn't change its premise.Root cause
The rule's premise is that "a private method has no callers outside the class". A callable reference to the method breaks that premise, but the rule never looked for one.
Test
tests/PHPStan/Rules/Methods/data/bug-15405.phpcovers:[$this, '...'],'self::...', a mixed-case nameFixes phpstan/phpstan#15405
🤖 Generated with Claude Code