Result cache: value and file dependencies declared on the scope - #6638
Conversation
e50eb5c to
3aae0d6
Compare
ondrejmirtes
left a comment
There was a problem hiding this comment.
Does it work properly when analysing inside traits?
| */ | ||
| public function doService(string $type): void | ||
| { | ||
| assertType("'getService'|'hasService'", $type); |
There was a problem hiding this comment.
this is wrong, subclass might declare more constants, this assertion would only be okay in a final class
|
|
||
| public function doFoo(string $type): string | ||
| { | ||
| assertType("'container-1'|'getService'|'hasService'|'parameter'", $type); |
There was a problem hiding this comment.
this is wrong, subclass might declare more constants, this assertion would only be okay in a final class
3aae0d6 to
f2f5dd4
Compare
A class constant wildcard on static or on a template type was resolved against the class known at the declaration - an interface declaring none of the constants its implementations do, the bound of the template - and was unresolvable there. Both now resolve once the class is known: static for the class the method is called on, a template once it is resolved. Only a final class, or a template bound to one, is known not to declare more constants than it does now, and only for those an unmatched wildcard is unresolvable. Until then the PHPDoc type cannot be compared with the native type, so it is kept rather than replaced by it, and not reported as incompatible with it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
…pendency() A rule or a dynamic return type extension that reads something the result cache does not know about - a DI container, a config file - declares it with $scope->valueDependency(SomeValueExtension::class, $key). The extension, a ResultCacheValueExtension, returns the value for the key. The value is stored in the result cache, and when it's different on the next run, the files that asked for it are re-analysed. The same value declared more times, by one or several rules and extensions, is recorded once. When it's declared outside the walk of the analysed file, like when a private property type is inferred from the constructor, it is the file of the scope that depends on it, together with the files depending on that file. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
$scope->fileDependency($path) declares that the analysis of the current file depends on the contents of another file - a data file, a template, a docblock in a PHP file - that is read without PHPStan knowing about it. It is a value dependency through FileResultCacheValueExtension: the value is the hash of the file, or that the file does not exist, so creating, changing or deleting it re-analyses the files that declared it. The path is stored relative to the same directory as the other paths in the result cache. A dependency of the analysed file on itself is dropped, the file is re-analysed when it changes anyway. RequireFileExistsRule declares the paths on the scope now, and the paths from the deprecated RuleErrorBuilder::fileDependency() are declared on the scope of the rule's node. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
f2f5dd4 to
223a01f
Compare
|
One thing I found after the merge. It is the cause of the new In the phar, I reproduced it with a minimal extension and a PHPUnit test that passes
A source checkout does not behave like this. I checked it with stand-in interfaces. An implementation declaring Other checks, which found nothing:
The Integration tests (ubuntu-latest) red, |
|
There is a new release of https://github.com/ondrejmirtes/simple-downgrader which looks like related and might fix your finding |
|
Thanks, but that release is the one this PR already uses. 2.2.9 ( It is also what writes the inherited |
Rules and extensions can now tell the result cache what they read besides the analysed code, so that only the files depending on it are analysed again when it changes. Before, the only option was a
ResultCacheMetaExtension, which invalidates the whole result cache.Closes phpstan/phpstan#15144
Closes phpstan/phpstan#4548
Closes phpstan/phpstan#11510
Related: phpstan/phpstan-symfony#455 (the phpstan-symfony side builds on this)
$scope->valueDependency()A rule, a dynamic return type extension or an expression type resolver extension typehints the scope as
Scope&DependencyEmitterin the PHPDoc and declares what it asks about:ServiceValueExtensionimplements the newResultCacheValueExtension(tagphpstan.resultCacheValueExtension). It returns the current value for a key, and converts the key for storing in the result cache and back, e.g. to make paths relative. The value is stored in the result cache. On the next run it is asked for again, and when it's different, the files that declared it are analysed again.$scope->fileDependency()A shortcut through
FileResultCacheValueExtensionfor a file read without PHPStan knowing about it. The value is the hash of the file, or that it does not exist, so creating, changing or deleting it analyses the dependent files again. A dependency of the analysed file on itself is dropped.RequireFileExistsRuleuses it now.RuleErrorBuilder::fileDependency()is deprecated - it can declare a dependency only along with an error - and its paths are declared on the scope of the rule's node.static::FOO_*andT::FOO_*The first commit makes
static::*,static::FOO_*,T::*andT::classin PHPDocs resolve once the class is known, instead of reporting an unresolvable type. It's reported only whenstaticorTis certainly a final class without such constants.staticin a class that is not final, a template type, an interface or an abstract class - a subclass can declare more constants matching a wildcard, so only the native type is certain.staticorTis a concrete class - the class a method is called on, the class being instantiated, or the class-string passed forT- it's its constants, the same way a singlestatic::FOOalready resolves for the class a method is called on.staticin the PHPDoc of an inherited constructor is the class being instantiated.Tests
IncompatiblePhpDocTypeRule,IncompatiblePropertyPhpDocTypeRule,InstantiationRuleandCallToFunctionParametersRuletests for Resolve template with value of BackEnum #4548 and #11510.e2e/result-cache-value-dependency- services and parameters in a JSON "container", asked about by a rule and dynamic return type extensions. ChecksfilesToAnalyseCountofresult-cache-info --jsonafter changing a value somebody asked about, one nobody asked about, a missing one, one asked about by two files, one used in the inferred type of a private property, and one asked about in a trait - the file of the class using it is analysed again.e2e/result-cache-file-dependency- a rule and dynamic return type extensions of a function, a method and a static method reading files; created, edited and deleted files; the private property case; the deprecatedRuleErrorBuilder::fileDependency(). A file of a class whose method declares the dependency is analysed again, the files depending on that class are not.simple-downgrader 2.2.9 downgrades any intersection of
Scopewith interfaces fromPHPStan\AnalysertoScope.🤖 Generated with Claude Code
https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ