Look up line ignores under the analysed file for trait errors reported in the context of a class - #6174
Open
phpstan-bot wants to merge 1 commit into
Open
Conversation
…d in the context of a class
- `AnalyserResultFinalizer` matched deferred (`CollectedDataNode`) errors against
`$allLinesToIgnore[$error->getTraitFilePath() ?? $error->getFilePath()]`. That outer key is
the *analysed* file, so for an error reported as `Trait.php (in context of class Foo)` the
ignores of the class file were never consulted and `@phpstan-ignore` had no effect.
- New `AnalyserResultFinalizer::resolveAnalysedFileWithLineIgnores()` picks the entry that
actually contains the error's file description: the using-class file for in-context errors,
the trait file when the context was removed by `Error::removeTraitContext()`.
- Fixes all identifiers deferred through `FunctionCallConstantConditionRule` and
`ConstantConditionInTraitRule` (`function.alreadyNarrowedType`, `function.impossibleType`,
`identical.alwaysFalse`, ...) and all inline ignore forms (`@phpstan-ignore <identifier>`,
`@phpstan-ignore-line`, `@phpstan-ignore-next-line`).
- Probed and also fixed by the same change: classes in separate files, nested traits,
anonymous classes, aliased trait methods (`use T { m as n; }`), primed result cache.
- Probed and found already correct: a trait used by a single class (`removeTraitContext()`,
the case fixed by phpstan#5780 - kept as a regression test), baseline generation for in-context
trait errors, and non-trait collector errors.
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.
Summary
An inline
@phpstan-ignoreinside a trait had no effect on errors that PHPStan reports once perusing class (
Trait.php (in context of class Foo)) when the trait and the classes live inseparate files. The reported case was
function.alreadyNarrowedTypefromis_subclass_of(self::class, ...)in a trait used by two classes; the same snippet in theplayground (single file) was correctly suppressed, which is why it looked like it should work.
The fix makes
AnalyserResultFinalizerlook up the line ignores under the file whose analysisactually recorded them.
Changes
src/Analyser/AnalyserResultFinalizer.php$file = $tempCollectorError->getTraitFilePath() ?? $tempCollectorError->getFilePath()with a new private
resolveAnalysedFileWithLineIgnores()helper that picks the outer key whoseentry actually contains the error's file description, trying the analysed (using-class) file
first and the trait file second.
tests/PHPStan/Analyser/AnalyserTraitsIntegrationTest.php— five new tests plus arunAnalyseAndFinalize()helper (the existingrunAnalyse()skips the finalizer, where thedeferred errors are produced).
tests/PHPStan/Analyser/traits/ignore/*— new fixtures.e2e/bug-14993/+.github/workflows/e2e-tests.yml— end-to-end coverage including theresult cache and baseline generation, mirroring the existing
e2e/bug-14718block.Analogous cases probed, all covered by the same fix and by a test each:
trait A { use B; });use T { check as checkSecond; });@phpstan-ignore <identifier>(own line and trailing),@phpstan-ignore-line,@phpstan-ignore-next-line;FunctionCallConstantConditionRule(
function.alreadyNarrowedType,function.impossibleType, and therefore the wholeImpossibleCheckType*/FunctionCallConstantConditionHelperfamily) andConstantConditionInTraitRule(identical.alwaysFalseand the rest of theConstantConditionInTraitHelperfamily);Probed and found already correct (no change needed):
Error::removeTraitContext()— this is the case fixed by Fix file path of trait errors reported directly in the trait #5780 on the very same line; kept astestIgnoreErrorsReportedDirectlyInTraitUsedByASingleClassso the fallback stays covered;getTraitFilePath() === null), whose lookup is unchanged.Probed, broken, but not fixed here (different root cause, noted for a follow-up): the
*WithoutImpurePointsdead-code rules (CallToFunctionStatementWithoutImpurePointsRuleand itsmethod/static-method/constructor siblings) build their errors from the collected-data key with
RuleErrorBuilder::file(), so a call inside a trait is reported at the using class file withthe trait's line number, and once per using class. Those errors never carry trait context at
all, so they cannot be reached by a lookup fix — repairing them requires the collectors to keep
the trait context and the rules to deduplicate per trait the way
ConstantConditionInTraitRuledoes.Root cause
AnalyserResult::getLinesToIgnore()is a two-level map:The outer key is the file that was passed to
FileAnalyser(Analyser::analyse()/WorkerRunnerstore it as$linesToIgnore[$file] = $fileAnalyserResult->getLinesToIgnore()).The inner key is what
Error::getFile()returns — the plain path for normal errors and"Trait.php (in context of class Foo)"for errors found while analysing trait statements in aclass context, which
FileAnalyserCallbackrecords when it sees anInTraitNode.When a class uses a trait from another file, the trait's ignore lines are therefore stored under
the class file's entry, keyed by the trait's file description. The finalizer, however, keyed
the lookup by the error's trait file path, landing on the entry produced by analysing the trait
file on its own — which only ever contains the trait file's plain path as an inner key. The
identifier lookup in
LocalIgnoresProcessorthen never found the ignore.That
getTraitFilePath() ?? getFilePath()expression was introduced by #5780 to fix the oppositehalf of the problem: errors that deduplicate into the trait file via
removeTraitContext(), whosegetFilePath()is the class file but whosegetFile()is the trait path. Instead of choosing oneof the two keys up front, the finalizer now picks the one that actually holds the error's file
description, so both halves work.
This only affects errors deferred to
CollectedDataNoderules; errors reported directly from arule are locally ignored inside
FileAnalyser, which passes the complete per-file map and wasnever affected.
Test
AnalyserTraitsIntegrationTest, using the newrunAnalyseAndFinalize()helper:testIgnoreErrorsReportedInContextOfEachClassUsingTheTrait— the reported reproducer, extendedto cover
function.alreadyNarrowedType,function.impossibleType,identical.alwaysFalse, allfour inline ignore syntaxes and an aliased trait method;
testIgnoreErrorsReportedInContextOfClassesLivingInSeparateFiles;testIgnoreErrorsReportedInContextOfClassUsingNestedTrait;testIgnoreErrorsReportedInContextOfAnonymousClassUsingTrait;testIgnoreErrorsReportedDirectlyInTraitUsedByASingleClass(regression guard for the Fix file path of trait errors reported directly in the trait #5780behaviour that the fix must keep).
Four of the five fail before the fix (the single-class one passes both before and after, as
intended) and all five pass after it.
e2e/bug-14993additionally checks the empty and primedresult cache, the unignored output, and baseline generation.
make tests,make phpstanandmake cs-fixare green.Fixes phpstan/phpstan#14993