Skip to content
Permalink
Browse files

Add hopefully-more-portable method for evaluating method/property usage

  • Loading branch information...
muglug committed Apr 16, 2019
1 parent 74f3908 commit ff14f671b1dff874c623b429a2e0596e8d6cd4b0
Showing with 1,085 additions and 294 deletions.
  1. +0 −2 src/Psalm/Codebase.php
  2. +5 −5 src/Psalm/Internal/Analyzer/ClassAnalyzer.php
  3. +3 −1 src/Psalm/Internal/Analyzer/MethodAnalyzer.php
  4. +8 −2 src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php
  5. +4 −2 src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php
  6. +12 −3 src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php
  7. +1 −1 src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php
  8. +1 −1 src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/PropertyFetchAnalyzer.php
  9. +2 −0 src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php
  10. +112 −23 src/Psalm/Internal/Codebase/Analyzer.php
  11. +1 −0 src/Psalm/Internal/Codebase/ClassLikes.php
  12. +85 −50 src/Psalm/Internal/Codebase/Methods.php
  13. +10 −15 src/Psalm/Internal/Codebase/Properties.php
  14. +141 −2 src/Psalm/Internal/Provider/FileReferenceCacheProvider.php
  15. +196 −76 src/Psalm/Internal/Provider/FileReferenceProvider.php
  16. +7 −4 src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php
  17. +7 −4 src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReduceReturnTypeProvider.php
  18. +5 −1 src/Psalm/Internal/Visitor/ReflectorVisitor.php
  19. +8 −2 src/psalm.php
  20. +84 −17 tests/FileReferenceTest.php
  21. +2 −0 tests/FileUpdates/AnalyzedMethodTest.php
  22. +257 −75 tests/FileUpdates/ErrorAfterUpdateTest.php
  23. +61 −1 tests/FileUpdates/ErrorFixTest.php
  24. +71 −5 tests/Internal/Provider/FakeFileReferenceCacheProvider.php
  25. +2 −2 tests/LanguageServer/SymbolLookupTest.php
@@ -338,8 +338,6 @@ public function collectReferences()
{
$this->collect_references = true;
$this->classlikes->collect_references = true;
$this->methods->collect_references = true;
$this->properties->collect_references = true;
}
/**
@@ -862,11 +862,6 @@ private function checkPropertyInitialization(
continue;
}
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
strtolower($fq_class_name) . '::__construct',
strtolower($property_class_name) . '::$' . $property_name
);
if ($property->has_default || !$property->type || $property_is_initialized) {
continue;
}
@@ -875,6 +870,11 @@ private function checkPropertyInitialization(
continue;
}
$codebase->file_reference_provider->addMethodReferenceToMissingClassMember(
strtolower($fq_class_name) . '::__construct',
strtolower($property_class_name) . '::$' . $property_name
);
$uninitialized_variables[] = '$this->' . $property_name;
$uninitialized_properties[$property_class_name . '::$' . $property_name] = $property;
}
@@ -156,7 +156,9 @@ public static function checkMethodExists(
if ($codebase->methods->methodExists(
$method_id,
$calling_method_id,
$calling_method_id !== $method_id ? $code_location : null
$calling_method_id !== $method_id ? $code_location : null,
null,
$code_location->file_path
)) {
return true;
}
@@ -568,7 +568,13 @@ private static function analyzeAtomicCall(
$args = $stmt->args;
if (!$codebase->methods->methodExists($method_id, $context->calling_method_id)
if (!$codebase->methods->methodExists(
$method_id,
$context->calling_method_id,
$codebase->collect_references ? new CodeLocation($source, $stmt->name) : null,
null,
$statements_analyzer->getFilePath()
)
|| !MethodAnalyzer::isMethodVisible(
$method_id,
$context,
@@ -848,7 +854,7 @@ function (PhpParser\Node\Arg $arg) {
if ($context->collect_initializations && $context->calling_method_id) {
list($calling_method_class) = explode('::', $context->calling_method_id);
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$calling_method_class . '::__construct',
strtolower($method_id)
);
@@ -52,7 +52,7 @@ public static function analyze(
if ($context->calling_method_id
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
) {
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$context->calling_method_id,
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
);
@@ -328,7 +328,9 @@ public static function analyze(
if ($codebase->methods->methodExists(
$fq_class_name . '::__construct',
$context->calling_method_id,
$context->collect_references ? new CodeLocation($statements_analyzer->getSource(), $stmt) : null
$context->collect_references ? new CodeLocation($statements_analyzer->getSource(), $stmt) : null,
null,
$statements_analyzer->getFilePath()
)) {
$method_id = $fq_class_name . '::__construct';
@@ -106,7 +106,7 @@ public static function analyze(
if ($context->calling_method_id
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
) {
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$context->calling_method_id,
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
);
@@ -345,7 +345,13 @@ public static function analyze(
}
}
if (!$codebase->methods->methodExists($method_id, $context->calling_method_id)
if (!$codebase->methods->methodExists(
$method_id,
$context->calling_method_id,
$codebase->collect_references ? new CodeLocation($source, $stmt->name) : null,
null,
$statements_analyzer->getFilePath()
)
|| !MethodAnalyzer::isMethodVisible(
$method_id,
$context,
@@ -354,7 +360,10 @@ public static function analyze(
) {
if ($codebase->methods->methodExists(
$fq_class_name . '::__callStatic',
$context->calling_method_id
$context->calling_method_id,
$codebase->collect_references ? new CodeLocation($source, $stmt->name) : null,
null,
$statements_analyzer->getFilePath()
)) {
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
@@ -239,7 +239,7 @@ public static function analyzeClassConst(
}
if ($context->calling_method_id) {
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$context->calling_method_id,
strtolower($fq_class_name) . '::' . $stmt->name->name
);
@@ -761,7 +761,7 @@ public static function analyzeStatic(
if ($context->calling_method_id
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
) {
$codebase->file_reference_provider->addCallingMethodReferenceToClassMember(
$codebase->file_reference_provider->addMethodReferenceToClassMember(
$context->calling_method_id,
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
);
@@ -454,6 +454,8 @@ public static function analyze(
$use_context->vars_possibly_in_scope[$use_var_id] = true;
}
$use_context->calling_method_id = $context->calling_method_id;
$closure_analyzer->analyze($use_context, $context, false, $byref_uses);
if (!isset($stmt->inferredType)) {
@@ -34,19 +34,20 @@
*
* @psalm-type WorkerData = array{
* issues: array<int, IssueData>,
* file_references: array<string, array<string,bool>>,
* file_references_to_classes: array<string, array<string,bool>>,
* file_references_to_class_members: array<string, array<string,bool>>,
* file_references_to_missing_class_members: array<string, array<string,bool>>,
* mixed_counts: array<string, array{0: int, 1: int}>,
* member_references: array<string, array<string,bool>>,
* method_references_to_class_members: array<string, array<string,bool>>,
* method_references_to_missing_class_members: array<string, array<string,bool>>,
* analyzed_methods: array<string, array<string, int>>,
* file_maps: array<
* string,
* array{0: TaggedCodeType, 1: TaggedCodeType}
* >,
* class_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_method_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_property_locations: array<string, array<int, \Psalm\CodeLocation>>,
* class_method_references: array<string, bool>,
* class_property_references: array<string, bool>
* class_property_locations: array<string, array<int, \Psalm\CodeLocation>>
* }
*/
@@ -236,16 +237,22 @@ function () {
return [
'issues' => IssueBuffer::getIssuesData(),
'file_references' => $file_reference_provider->getAllFileReferences(),
'member_references' => $file_reference_provider->getClassMemberReferences(),
'file_references_to_classes'
=> $file_reference_provider->getAllFileReferencesToClasses(),
'file_references_to_class_members'
=> $file_reference_provider->getAllFileReferencesToClassMembers(),
'method_references_to_class_members'
=> $file_reference_provider->getAllMethodReferencesToClassMembers(),
'file_references_to_missing_class_members'
=> $file_reference_provider->getAllFileReferencesToMissingClassMembers(),
'method_references_to_missing_class_members'
=> $file_reference_provider->getAllMethodReferencesToMissingClassMembers(),
'mixed_counts' => $analyzer->getMixedCounts(),
'analyzed_methods' => $analyzer->getAnalyzedMethods(),
'file_maps' => $analyzer->getFileMaps(),
'class_locations' => $file_reference_provider->getAllClassLocations(),
'class_method_locations' => $file_reference_provider->getAllClassMethodLocations(),
'class_property_locations' => $file_reference_provider->getAllClassPropertyLocations(),
'class_method_references' => $file_reference_provider->getAllClassMethodReferences(),
'class_property_references' => $file_reference_provider->getAllClassPropertyReferences(),
];
}
);
@@ -263,11 +270,20 @@ function () {
$codebase->file_reference_provider->addIssue($issue_data['file_path'], $issue_data);
}
$codebase->file_reference_provider->addFileReferencesToClass(
$pool_data['file_references']
$codebase->file_reference_provider->addFileReferencesToClasses(
$pool_data['file_references_to_classes']
);
$codebase->file_reference_provider->addCallingMethodReferencesToClassMember(
$pool_data['member_references']
$codebase->file_reference_provider->addFileReferencesToClassMembers(
$pool_data['file_references_to_class_members']
);
$codebase->file_reference_provider->addMethodReferencesToClassMembers(
$pool_data['method_references_to_class_members']
);
$codebase->file_reference_provider->addFileReferencesToMissingClassMembers(
$pool_data['file_references_to_missing_class_members']
);
$codebase->file_reference_provider->addMethodReferencesToMissingClassMembers(
$pool_data['method_references_to_missing_class_members']
);
$codebase->file_reference_provider->addClassLocations(
$pool_data['class_locations']
@@ -278,12 +294,6 @@ function () {
$codebase->file_reference_provider->addClassPropertyLocations(
$pool_data['class_property_locations']
);
$codebase->file_reference_provider->addClassMethodReferences(
$pool_data['class_method_references']
);
$codebase->file_reference_provider->addClassPropertyReferences(
$pool_data['class_property_references']
);
$this->analyzed_methods = array_merge($pool_data['analyzed_methods'], $this->analyzed_methods);
@@ -356,14 +366,25 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer)
}
$statements_provider = $codebase->statements_provider;
$file_reference_provider = $codebase->file_reference_provider;
$changed_members = $statements_provider->getChangedMembers();
$unchanged_signature_members = $statements_provider->getUnchangedSignatureMembers();
$diff_map = $statements_provider->getDiffMap();
$all_referencing_methods = $codebase->file_reference_provider->getClassMemberReferences();
$this->mixed_counts = $codebase->file_reference_provider->getTypeCoverage();
$method_references_to_class_members
= $file_reference_provider->getAllMethodReferencesToClassMembers();
$method_references_to_missing_class_members =
$file_reference_provider->getAllMethodReferencesToMissingClassMembers();
$all_referencing_methods = $method_references_to_class_members + $method_references_to_missing_class_members;
$file_references_to_class_members
= $file_reference_provider->getAllFileReferencesToClassMembers();
$file_references_to_missing_class_members
= $file_reference_provider->getAllFileReferencesToMissingClassMembers();
$this->mixed_counts = $file_reference_provider->getTypeCoverage();
$classlikes = $codebase->classlikes;
@@ -413,6 +434,11 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer)
);
}
unset($method_references_to_class_members[$member_id]);
unset($file_references_to_class_members[$member_id]);
unset($method_references_to_missing_class_members[$member_id]);
unset($file_references_to_missing_class_members[$member_id]);
$member_stub = preg_replace('/::.*$/', '::*', $member_id);
if (isset($all_referencing_methods[$member_stub])) {
@@ -424,6 +450,16 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer)
}
}
foreach ($newly_invalidated_methods as $method_id => $_) {
foreach ($method_references_to_class_members as &$referencing_method_ids) {
unset($referencing_method_ids[$method_id]);
}
foreach ($method_references_to_missing_class_members as &$referencing_method_ids) {
unset($referencing_method_ids[$method_id]);
}
}
foreach ($this->analyzed_methods as $file_path => $analyzed_methods) {
foreach ($analyzed_methods as $correct_method_id => $_) {
$trait_safe_method_id = $correct_method_id;
@@ -444,10 +480,63 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer)
$this->shiftFileOffsets($diff_map);
foreach ($this->files_to_analyze as $file_path) {
$codebase->file_reference_provider->clearExistingIssuesForFile($file_path);
$codebase->file_reference_provider->clearExistingFileMapsForFile($file_path);
$file_reference_provider->clearExistingIssuesForFile($file_path);
$file_reference_provider->clearExistingFileMapsForFile($file_path);
$this->setMixedCountsForFile($file_path, [0, 0]);
foreach ($file_references_to_class_members as &$referencing_file_paths) {
unset($referencing_file_paths[$file_path]);
}
foreach ($file_references_to_missing_class_members as &$referencing_file_paths) {
unset($referencing_file_paths[$file_path]);
}
}
$method_references_to_class_members = array_filter(
$method_references_to_class_members,
function (array $a) : bool {
return !!$a;
}
);
$method_references_to_missing_class_members = array_filter(
$method_references_to_missing_class_members,
function (array $a) : bool {
return !!$a;
}
);
$file_references_to_class_members = array_filter(
$file_references_to_class_members,
function (array $a) : bool {
return !!$a;
}
);
$file_references_to_missing_class_members = array_filter(
$file_references_to_missing_class_members,
function (array $a) : bool {
return !!$a;
}
);
$file_reference_provider->setCallingMethodReferencesToClassMembers(
$method_references_to_class_members
);
$file_reference_provider->setFileReferencesToClassMembers(
$file_references_to_class_members
);
$file_reference_provider->setCallingMethodReferencesToMissingClassMembers(
$method_references_to_missing_class_members
);
$file_reference_provider->setFileReferencesToMissingClassMembers(
$file_references_to_missing_class_members
);
}
/**
@@ -891,6 +891,7 @@ private function checkMethodReferences(ClassLikeStorage $classlike_storage, Meth
$property_referenced = $this->file_reference_provider->isClassPropertyReferenced(
strtolower($classlike_storage->name) . '::$' . $property_name
);
if (!$property_referenced
&& (substr($property_name, 0, 2) !== '__' || $property_name === '__construct')
&& $property_storage->location

0 comments on commit ff14f67

Please sign in to comment.
You can’t perform that action at this time.