mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Move static property fetch analyzer to own class
This commit is contained in:
parent
98622783ec
commit
f609a01497
@ -8,7 +8,7 @@ use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\PropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
@ -762,7 +762,7 @@ class PropertyAssignmentAnalyzer
|
||||
if ($lhs_type_part instanceof Type\Atomic\TGenericObject) {
|
||||
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
||||
|
||||
$class_property_type = PropertyFetchAnalyzer::localizePropertyType(
|
||||
$class_property_type = InstancePropertyFetchAnalyzer::localizePropertyType(
|
||||
$codebase,
|
||||
$class_property_type,
|
||||
$lhs_type_part,
|
||||
|
@ -37,7 +37,6 @@ use function is_string;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
use function array_merge;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -19,7 +19,6 @@ use Psalm\Issue\PossiblyNullOperand;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use function array_merge;
|
||||
use function strtolower;
|
||||
use function strlen;
|
||||
|
||||
|
@ -10,7 +10,6 @@ use Psalm\Issue\ImpureMethodCall;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use function array_merge;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -37,8 +37,6 @@ use Psalm\Type\Atomic\TList;
|
||||
use function strtolower;
|
||||
use function strpos;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use Psalm\Issue\TaintedInput;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -10,7 +10,6 @@ use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentMapPopulator;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\ArgumentsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\PropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
|
@ -57,7 +57,6 @@ use function strtolower;
|
||||
use function in_array;
|
||||
use function is_int;
|
||||
use function preg_match;
|
||||
use Psalm\Internal\Taint\TaintNode;
|
||||
use Psalm\Internal\Taint\Source;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
|
||||
|
@ -9,7 +9,6 @@ use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\Internal\Type\TemplateResult;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
@ -20,7 +19,6 @@ use Psalm\Issue\MissingPropertyType;
|
||||
use Psalm\Issue\MixedPropertyFetch;
|
||||
use Psalm\Issue\NoInterfaceProperties;
|
||||
use Psalm\Issue\NullPropertyFetch;
|
||||
use Psalm\Issue\ParentNotFound;
|
||||
use Psalm\Issue\PossiblyInvalidPropertyFetch;
|
||||
use Psalm\Issue\PossiblyNullPropertyFetch;
|
||||
use Psalm\Issue\UndefinedClass;
|
||||
@ -41,16 +39,14 @@ use function strtolower;
|
||||
use function array_values;
|
||||
use function in_array;
|
||||
use function array_keys;
|
||||
use function count;
|
||||
use function explode;
|
||||
use Psalm\Internal\Taint\TaintNode;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyFetchAnalyzer
|
||||
class InstancePropertyFetchAnalyzer
|
||||
{
|
||||
public static function analyzeInstance(
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
Context $context
|
||||
@ -1072,348 +1068,4 @@ class PropertyFetchAnalyzer
|
||||
$type->parent_nodes = [$localized_property_node];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
|
||||
* @param Context $context
|
||||
*/
|
||||
public static function analyzeStatic(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\StaticPropertyFetch $stmt,
|
||||
Context $context
|
||||
) : bool {
|
||||
if (!$stmt->class instanceof PhpParser\Node\Name) {
|
||||
$old_data_provider = $statements_analyzer->node_data;
|
||||
|
||||
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
||||
|
||||
$fake_instance_property = new PhpParser\Node\Expr\PropertyFetch(
|
||||
$stmt->class,
|
||||
$stmt->name,
|
||||
$stmt->getAttributes()
|
||||
);
|
||||
|
||||
$analysis_result = self::analyzeInstance($statements_analyzer, $fake_instance_property, $context);
|
||||
|
||||
$stmt_type = $statements_analyzer->node_data->getType($fake_instance_property);
|
||||
|
||||
$statements_analyzer->node_data = $old_data_provider;
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type ?: Type::getMixed());
|
||||
|
||||
return $analysis_result;
|
||||
}
|
||||
|
||||
$fq_class_name = null;
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (count($stmt->class->parts) === 1
|
||||
&& in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
|
||||
) {
|
||||
if ($stmt->class->parts[0] === 'parent') {
|
||||
$fq_class_name = $statements_analyzer->getParentFQCLN();
|
||||
|
||||
if ($fq_class_name === null) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ParentNotFound(
|
||||
'Cannot check property fetch on parent as this class does not extend another',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$fq_class_name = (string)$context->self;
|
||||
}
|
||||
|
||||
if ($context->isPhantomClass($fq_class_name)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$aliases = $statements_analyzer->getAliases();
|
||||
|
||||
if ($context->calling_method_id
|
||||
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
|
||||
) {
|
||||
$codebase->file_reference_provider->addMethodReferenceToClassMember(
|
||||
$context->calling_method_id,
|
||||
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
|
||||
);
|
||||
}
|
||||
|
||||
$fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
||||
$stmt->class,
|
||||
$aliases
|
||||
);
|
||||
|
||||
if ($context->isPhantomClass($fq_class_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($context->check_classes) {
|
||||
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
||||
$statements_analyzer,
|
||||
$fq_class_name,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt->class),
|
||||
$context->self,
|
||||
$context->calling_method_id,
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
false
|
||||
) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fq_class_name
|
||||
&& $codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[$context->calling_method_id])
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[$context->calling_method_id];
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
$fq_class_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1
|
||||
);
|
||||
}
|
||||
|
||||
if ($fq_class_name) {
|
||||
$statements_analyzer->node_data->setType(
|
||||
$stmt->class,
|
||||
new Type\Union([new TNamedObject($fq_class_name)])
|
||||
);
|
||||
}
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\VarLikeIdentifier) {
|
||||
$prop_name = $stmt->name->name;
|
||||
} elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name))
|
||||
&& $stmt_name_type->isSingleStringLiteral()
|
||||
) {
|
||||
$prop_name = $stmt_name_type->getSingleStringLiteral()->value;
|
||||
} else {
|
||||
$prop_name = null;
|
||||
}
|
||||
|
||||
if (!$prop_name) {
|
||||
if ($fq_class_name) {
|
||||
$codebase->analyzer->addMixedMemberName(
|
||||
strtolower($fq_class_name) . '::$',
|
||||
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$fq_class_name
|
||||
|| !$context->check_classes
|
||||
|| !$context->check_variables
|
||||
|| ExpressionAnalyzer::isMock($fq_class_name)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$var_id = ExpressionIdentifier::getVarId(
|
||||
$stmt,
|
||||
$context->self ?: $statements_analyzer->getFQCLN(),
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$property_id = $fq_class_name . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
) {
|
||||
$codebase->analyzer->addNodeReference(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$property_id
|
||||
);
|
||||
}
|
||||
|
||||
if ($context->mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\ImpureStaticProperty(
|
||||
'Cannot use a static property in a mutation-free context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($var_id && $context->hasVariable($var_id, $statements_analyzer)) {
|
||||
$stmt_type = $context->vars_in_scope[$var_id];
|
||||
|
||||
// we don't need to check anything
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
if ($codebase->collect_references) {
|
||||
// log the appearance
|
||||
$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context,
|
||||
$codebase->collect_locations
|
||||
? new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
||||
) {
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$stmt_type->getId()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context,
|
||||
$codebase->collect_locations
|
||||
? new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
: null
|
||||
)
|
||||
) {
|
||||
if ($context->inside_isset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyFetch(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
|
||||
$fq_class_name . '::$' . $prop_name,
|
||||
true,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if ($declaring_property_class === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->alter_code) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
|
||||
$file_manipulations = [];
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1,
|
||||
Type::getStringFromFQCLN(
|
||||
$new_fq_class_name,
|
||||
$statements_analyzer->getNamespace(),
|
||||
$statements_analyzer->getAliasedClassesFlipped(),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
$property = $class_storage->properties[$prop_name];
|
||||
|
||||
if ($var_id) {
|
||||
if ($property->type) {
|
||||
$context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
clone $property->type,
|
||||
$class_storage->name,
|
||||
$class_storage->name,
|
||||
$class_storage->parent_class
|
||||
);
|
||||
} else {
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
}
|
||||
|
||||
$stmt_type = clone $context->vars_in_scope[$var_id];
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
) {
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$stmt_type->getId()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,374 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\ParentNotFound;
|
||||
use Psalm\Issue\UndefinedPropertyFetch;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use function strtolower;
|
||||
use function in_array;
|
||||
use function count;
|
||||
use function explode;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StaticPropertyFetchAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
|
||||
* @param Context $context
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\StaticPropertyFetch $stmt,
|
||||
Context $context
|
||||
) : bool {
|
||||
if (!$stmt->class instanceof PhpParser\Node\Name) {
|
||||
$old_data_provider = $statements_analyzer->node_data;
|
||||
|
||||
$statements_analyzer->node_data = clone $statements_analyzer->node_data;
|
||||
|
||||
$fake_instance_property = new PhpParser\Node\Expr\PropertyFetch(
|
||||
$stmt->class,
|
||||
$stmt->name,
|
||||
$stmt->getAttributes()
|
||||
);
|
||||
|
||||
$analysis_result = InstancePropertyFetchAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$fake_instance_property,
|
||||
$context
|
||||
);
|
||||
|
||||
$stmt_type = $statements_analyzer->node_data->getType($fake_instance_property);
|
||||
|
||||
$statements_analyzer->node_data = $old_data_provider;
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type ?: Type::getMixed());
|
||||
|
||||
return $analysis_result;
|
||||
}
|
||||
|
||||
$fq_class_name = null;
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (count($stmt->class->parts) === 1
|
||||
&& in_array(strtolower($stmt->class->parts[0]), ['self', 'static', 'parent'], true)
|
||||
) {
|
||||
if ($stmt->class->parts[0] === 'parent') {
|
||||
$fq_class_name = $statements_analyzer->getParentFQCLN();
|
||||
|
||||
if ($fq_class_name === null) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ParentNotFound(
|
||||
'Cannot check property fetch on parent as this class does not extend another',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$fq_class_name = (string)$context->self;
|
||||
}
|
||||
|
||||
if ($context->isPhantomClass($fq_class_name)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$aliases = $statements_analyzer->getAliases();
|
||||
|
||||
if ($context->calling_method_id
|
||||
&& !$stmt->class instanceof PhpParser\Node\Name\FullyQualified
|
||||
) {
|
||||
$codebase->file_reference_provider->addMethodReferenceToClassMember(
|
||||
$context->calling_method_id,
|
||||
'use:' . $stmt->class->parts[0] . ':' . \md5($statements_analyzer->getFilePath())
|
||||
);
|
||||
}
|
||||
|
||||
$fq_class_name = ClassLikeAnalyzer::getFQCLNFromNameObject(
|
||||
$stmt->class,
|
||||
$aliases
|
||||
);
|
||||
|
||||
if ($context->isPhantomClass($fq_class_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($context->check_classes) {
|
||||
if (ClassLikeAnalyzer::checkFullyQualifiedClassLikeName(
|
||||
$statements_analyzer,
|
||||
$fq_class_name,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt->class),
|
||||
$context->self,
|
||||
$context->calling_method_id,
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
false
|
||||
) !== true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($fq_class_name
|
||||
&& $codebase->methods_to_move
|
||||
&& $context->calling_method_id
|
||||
&& isset($codebase->methods_to_move[$context->calling_method_id])
|
||||
) {
|
||||
$destination_method_id = $codebase->methods_to_move[$context->calling_method_id];
|
||||
|
||||
$codebase->classlikes->airliftClassLikeReference(
|
||||
$fq_class_name,
|
||||
explode('::', $destination_method_id)[0],
|
||||
$statements_analyzer->getFilePath(),
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1
|
||||
);
|
||||
}
|
||||
|
||||
if ($fq_class_name) {
|
||||
$statements_analyzer->node_data->setType(
|
||||
$stmt->class,
|
||||
new Type\Union([new TNamedObject($fq_class_name)])
|
||||
);
|
||||
}
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\VarLikeIdentifier) {
|
||||
$prop_name = $stmt->name->name;
|
||||
} elseif (($stmt_name_type = $statements_analyzer->node_data->getType($stmt->name))
|
||||
&& $stmt_name_type->isSingleStringLiteral()
|
||||
) {
|
||||
$prop_name = $stmt_name_type->getSingleStringLiteral()->value;
|
||||
} else {
|
||||
$prop_name = null;
|
||||
}
|
||||
|
||||
if (!$prop_name) {
|
||||
if ($fq_class_name) {
|
||||
$codebase->analyzer->addMixedMemberName(
|
||||
strtolower($fq_class_name) . '::$',
|
||||
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$fq_class_name
|
||||
|| !$context->check_classes
|
||||
|| !$context->check_variables
|
||||
|| ExpressionAnalyzer::isMock($fq_class_name)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$var_id = ExpressionIdentifier::getVarId(
|
||||
$stmt,
|
||||
$context->self ?: $statements_analyzer->getFQCLN(),
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$property_id = $fq_class_name . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
) {
|
||||
$codebase->analyzer->addNodeReference(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$property_id
|
||||
);
|
||||
}
|
||||
|
||||
if ($context->mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new \Psalm\Issue\ImpureStaticProperty(
|
||||
'Cannot use a static property in a mutation-free context',
|
||||
new CodeLocation($statements_analyzer, $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($var_id && $context->hasVariable($var_id, $statements_analyzer)) {
|
||||
$stmt_type = $context->vars_in_scope[$var_id];
|
||||
|
||||
// we don't need to check anything
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
if ($codebase->collect_references) {
|
||||
// log the appearance
|
||||
$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context,
|
||||
$codebase->collect_locations
|
||||
? new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
||||
) {
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$stmt_type->getId()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context,
|
||||
$codebase->collect_locations
|
||||
? new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
: null
|
||||
)
|
||||
) {
|
||||
if ($context->inside_isset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyFetch(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_class = $codebase->properties->getDeclaringClassForProperty(
|
||||
$fq_class_name . '::$' . $prop_name,
|
||||
true,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if ($declaring_property_class === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_id = strtolower($declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->alter_code) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
|
||||
$file_manipulations = [];
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1,
|
||||
Type::getStringFromFQCLN(
|
||||
$new_fq_class_name,
|
||||
$statements_analyzer->getNamespace(),
|
||||
$statements_analyzer->getAliasedClassesFlipped(),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
$property = $class_storage->properties[$prop_name];
|
||||
|
||||
if ($var_id) {
|
||||
if ($property->type) {
|
||||
$context->vars_in_scope[$var_id] = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
clone $property->type,
|
||||
$class_storage->name,
|
||||
$class_storage->name,
|
||||
$class_storage->parent_class
|
||||
);
|
||||
} else {
|
||||
$context->vars_in_scope[$var_id] = Type::getMixed();
|
||||
}
|
||||
|
||||
$stmt_type = clone $context->vars_in_scope[$var_id];
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
if ($codebase->store_node_types
|
||||
&& !$context->collect_initializations
|
||||
&& !$context->collect_mutations
|
||||
) {
|
||||
$codebase->analyzer->addNodeType(
|
||||
$statements_analyzer->getFilePath(),
|
||||
$stmt->name,
|
||||
$stmt_type->getId()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TraitAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\PropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
@ -147,7 +147,7 @@ class YieldAnalyzer
|
||||
|
||||
if ($classlike_storage->yield) {
|
||||
if ($expression_atomic_type instanceof Type\Atomic\TGenericObject) {
|
||||
$yield_candidate_type = PropertyFetchAnalyzer::localizePropertyType(
|
||||
$yield_candidate_type = InstancePropertyFetchAnalyzer::localizePropertyType(
|
||||
$codebase,
|
||||
clone $classlike_storage->yield,
|
||||
$expression_atomic_type,
|
||||
|
@ -202,7 +202,7 @@ class ExpressionAnalyzer
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch) {
|
||||
return Expression\Fetch\PropertyFetchAnalyzer::analyzeInstance(
|
||||
return Expression\Fetch\InstancePropertyFetchAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$context
|
||||
@ -210,7 +210,7 @@ class ExpressionAnalyzer
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch) {
|
||||
return Expression\Fetch\PropertyFetchAnalyzer::analyzeStatic(
|
||||
return Expression\Fetch\StaticPropertyFetchAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$context
|
||||
|
Loading…
Reference in New Issue
Block a user