mirror of
https://github.com/danog/psalm.git
synced 2024-12-13 01:37:23 +01:00
commit
f86e3b1bf9
@ -942,26 +942,21 @@ class Codebase
|
||||
return null;
|
||||
}
|
||||
|
||||
$storage = $this->methods->getStorage($declaring_method_id);
|
||||
return $storage;
|
||||
return $this->methods->getStorage($declaring_method_id);
|
||||
}
|
||||
|
||||
$function_id = strtolower(substr($symbol, 0, -2));
|
||||
$file_storage = $this->file_storage_provider->get($file_path);
|
||||
|
||||
if (isset($file_storage->functions[$function_id])) {
|
||||
$function_storage = $file_storage->functions[$function_id];
|
||||
|
||||
return $function_storage;
|
||||
return $file_storage->functions[$function_id];
|
||||
}
|
||||
|
||||
if (!$function_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$function = $this->functions->getStorage(null, $function_id);
|
||||
|
||||
return $function;
|
||||
return $this->functions->getStorage(null, $function_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1166,13 +1161,10 @@ class Codebase
|
||||
return null;
|
||||
}
|
||||
|
||||
$function = $this->functions->getStorage(null, $function_id);
|
||||
return $function->location;
|
||||
return $this->functions->getStorage(null, $function_id)->location;
|
||||
}
|
||||
|
||||
$storage = $this->classlike_storage_provider->get($symbol);
|
||||
|
||||
return $storage->location;
|
||||
return $this->classlike_storage_provider->get($symbol)->location;
|
||||
} catch (\UnexpectedValueException $e) {
|
||||
error_log($e->getMessage());
|
||||
|
||||
@ -1477,8 +1469,8 @@ class Codebase
|
||||
if (!$function_storage || !$function_storage->params) {
|
||||
return null;
|
||||
}
|
||||
$parameter = $function_storage->params[$argument_num];
|
||||
return $parameter->type;
|
||||
|
||||
return $function_storage->params[$argument_num]->type;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -57,8 +57,6 @@ class FileBasedPluginAdapter implements Plugin\PluginEntryPointInterface
|
||||
|
||||
$declared_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $path);
|
||||
|
||||
$fq_class_name = reset($declared_classes);
|
||||
|
||||
return $fq_class_name;
|
||||
return reset($declared_classes);
|
||||
}
|
||||
}
|
||||
|
@ -1036,7 +1036,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($property->type && $property->type->isNullable() && $property->type->from_docblock) {
|
||||
if ($property->type && $property->type->from_docblock && $property->type->isNullable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1359,131 +1359,131 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
if (!$codebase->traitHasCorrectCase($fq_trait_name)) {
|
||||
}
|
||||
|
||||
if (!$codebase->traitHasCorrectCase($fq_trait_name)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedTrait(
|
||||
'Trait ' . $fq_trait_name . ' has wrong casing',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fq_trait_name_resolved = $codebase->classlikes->getUnAliasedName($fq_trait_name);
|
||||
$trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name_resolved);
|
||||
|
||||
if ($trait_storage->deprecated) {
|
||||
if (IssueBuffer::accepts(
|
||||
new DeprecatedTrait(
|
||||
'Trait ' . $fq_trait_name . ' is deprecated',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($trait_storage->extension_requirement !== null) {
|
||||
$extension_requirement = $codebase->classlikes->getUnAliasedName(
|
||||
$trait_storage->extension_requirement
|
||||
);
|
||||
$extensionRequirementMet = in_array($extension_requirement, $storage->parent_classes);
|
||||
|
||||
if (!$extensionRequirementMet) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedTrait(
|
||||
'Trait ' . $fq_trait_name . ' has wrong casing',
|
||||
new ExtensionRequirementViolation(
|
||||
$fq_trait_name . ' requires using class to extend ' . $extension_requirement
|
||||
. ', but ' . $storage->name . ' does not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($trait_storage->implementation_requirements as $implementation_requirement) {
|
||||
$implementation_requirement = $codebase->classlikes->getUnAliasedName($implementation_requirement);
|
||||
$implementationRequirementMet = in_array($implementation_requirement, $storage->class_implements);
|
||||
|
||||
if (!$implementationRequirementMet) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImplementationRequirementViolation(
|
||||
$fq_trait_name . ' requires using class to implement '
|
||||
. $implementation_requirement . ', but ' . $storage->name . ' does not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->mutation_free && !$trait_storage->mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MutableDependency(
|
||||
$storage->name . ' is marked @psalm-immutable but ' . $fq_trait_name . ' is not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name_resolved);
|
||||
$trait_node = $codebase->classlikes->getTraitNode($fq_trait_name_resolved);
|
||||
$trait_aliases = $trait_storage->aliases;
|
||||
if ($trait_aliases === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$trait_analyzer = new TraitAnalyzer(
|
||||
$trait_node,
|
||||
$trait_file_analyzer,
|
||||
$fq_trait_name_resolved,
|
||||
$trait_aliases
|
||||
);
|
||||
|
||||
foreach ($trait_node->stmts as $trait_stmt) {
|
||||
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
|
||||
$trait_method_analyzer = $this->analyzeClassMethod(
|
||||
$trait_stmt,
|
||||
$storage,
|
||||
$trait_analyzer,
|
||||
$class_context,
|
||||
$global_context
|
||||
);
|
||||
|
||||
if ($trait_stmt->name->name === '__construct') {
|
||||
$constructor_analyzer = $trait_method_analyzer;
|
||||
}
|
||||
} elseif ($trait_stmt instanceof PhpParser\Node\Stmt\TraitUse) {
|
||||
if ($this->analyzeTraitUse(
|
||||
$trait_aliases,
|
||||
$trait_stmt,
|
||||
$project_analyzer,
|
||||
$storage,
|
||||
$class_context,
|
||||
$global_context,
|
||||
$constructor_analyzer,
|
||||
$trait_analyzer
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$fq_trait_name_resolved = $codebase->classlikes->getUnAliasedName($fq_trait_name);
|
||||
$trait_storage = $codebase->classlike_storage_provider->get($fq_trait_name_resolved);
|
||||
|
||||
if ($trait_storage->deprecated) {
|
||||
if (IssueBuffer::accepts(
|
||||
new DeprecatedTrait(
|
||||
'Trait ' . $fq_trait_name . ' is deprecated',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if ($trait_storage->extension_requirement !== null) {
|
||||
$extension_requirement = $codebase->classlikes->getUnAliasedName(
|
||||
$trait_storage->extension_requirement
|
||||
);
|
||||
$extensionRequirementMet = in_array($extension_requirement, $storage->parent_classes);
|
||||
|
||||
if (!$extensionRequirementMet) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ExtensionRequirementViolation(
|
||||
$fq_trait_name . ' requires using class to extend ' . $extension_requirement
|
||||
. ', but ' . $storage->name . ' does not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($trait_storage->implementation_requirements as $implementation_requirement) {
|
||||
$implementation_requirement = $codebase->classlikes->getUnAliasedName($implementation_requirement);
|
||||
$implementationRequirementMet = in_array($implementation_requirement, $storage->class_implements);
|
||||
|
||||
if (!$implementationRequirementMet) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImplementationRequirementViolation(
|
||||
$fq_trait_name . ' requires using class to implement '
|
||||
. $implementation_requirement . ', but ' . $storage->name . ' does not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->mutation_free && !$trait_storage->mutation_free) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MutableDependency(
|
||||
$storage->name . ' is marked @psalm-immutable but ' . $fq_trait_name . ' is not',
|
||||
new CodeLocation($previous_trait_analyzer ?: $this, $trait_name)
|
||||
),
|
||||
$storage->suppressed_issues + $this->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
$trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name_resolved);
|
||||
$trait_node = $codebase->classlikes->getTraitNode($fq_trait_name_resolved);
|
||||
$trait_aliases = $trait_storage->aliases;
|
||||
if ($trait_aliases === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$trait_analyzer = new TraitAnalyzer(
|
||||
$trait_node,
|
||||
$trait_file_analyzer,
|
||||
$fq_trait_name_resolved,
|
||||
$trait_aliases
|
||||
);
|
||||
|
||||
foreach ($trait_node->stmts as $trait_stmt) {
|
||||
if ($trait_stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
|
||||
$trait_method_analyzer = $this->analyzeClassMethod(
|
||||
$trait_stmt,
|
||||
$storage,
|
||||
$trait_analyzer,
|
||||
$class_context,
|
||||
$global_context
|
||||
);
|
||||
|
||||
if ($trait_stmt->name->name === '__construct') {
|
||||
$constructor_analyzer = $trait_method_analyzer;
|
||||
}
|
||||
} elseif ($trait_stmt instanceof PhpParser\Node\Stmt\TraitUse) {
|
||||
if ($this->analyzeTraitUse(
|
||||
$trait_aliases,
|
||||
$trait_stmt,
|
||||
$project_analyzer,
|
||||
$storage,
|
||||
$class_context,
|
||||
$global_context,
|
||||
$constructor_analyzer,
|
||||
$trait_analyzer
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$trait_file_analyzer->clearSourceBeforeDestruction();
|
||||
}
|
||||
|
||||
$trait_file_analyzer->clearSourceBeforeDestruction();
|
||||
}
|
||||
|
||||
$class_context->include_location = $previous_context_include_location;
|
||||
|
@ -130,7 +130,7 @@ class ClosureAnalyzer extends FunctionLikeAnalyzer
|
||||
|
||||
// insert the ref into the current context if passed by ref, as whatever we're passing
|
||||
// the closure to could execute it straight away.
|
||||
if (!$context->hasVariable($use_var_id) && $use->byRef) {
|
||||
if ($use->byRef && !$context->hasVariable($use_var_id)) {
|
||||
$context->vars_in_scope[$use_var_id] = Type::getMixed();
|
||||
}
|
||||
|
||||
|
@ -312,23 +312,30 @@ class ReturnTypeCollector
|
||||
}
|
||||
|
||||
return [Type::getMixed()];
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
|
||||
if ($stmt_expr_type = $nodes->getType($stmt->expr)) {
|
||||
return [$stmt_expr_type];
|
||||
}
|
||||
|
||||
return [Type::getMixed()];
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
|
||||
return array_merge(
|
||||
self::getYieldTypeFromExpression($stmt->left, $nodes),
|
||||
self::getYieldTypeFromExpression($stmt->right, $nodes)
|
||||
);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Assign) {
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\Assign) {
|
||||
return self::getYieldTypeFromExpression($stmt->expr, $nodes);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\MethodCall
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\MethodCall
|
||||
|| $stmt instanceof PhpParser\Node\Expr\FuncCall
|
||||
|| $stmt instanceof PhpParser\Node\Expr\StaticCall
|
||||
) {
|
||||
|| $stmt instanceof PhpParser\Node\Expr\StaticCall) {
|
||||
$yield_types = [];
|
||||
|
||||
foreach ($stmt->args as $arg) {
|
||||
|
@ -86,9 +86,7 @@ class ScopeAnalyzer
|
||||
|
||||
$control_actions = [];
|
||||
|
||||
for ($i = 0, $c = count($stmts); $i < $c; ++$i) {
|
||||
$stmt = $stmts[$i];
|
||||
|
||||
foreach ($stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
|
||||
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
|
||||
($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Exit_)
|
||||
|
@ -95,11 +95,7 @@ class ForAnalyzer
|
||||
|
||||
foreach ($stmt->cond as $cond) {
|
||||
if ($cond_type = $statements_analyzer->node_data->getType($cond)) {
|
||||
foreach ($cond_type->getAtomicTypes() as $iterator_type) {
|
||||
$always_enters_loop = $iterator_type instanceof Type\Atomic\TTrue;
|
||||
|
||||
break;
|
||||
}
|
||||
$always_enters_loop = $cond_type->isAlwaysTruthy();
|
||||
}
|
||||
|
||||
if (\count($stmt->init) === 1
|
||||
|
@ -369,7 +369,9 @@ class ForeachAnalyzer
|
||||
}
|
||||
|
||||
return false;
|
||||
} elseif ($iterator_type->isNullable() && !$iterator_type->ignore_nullable_issues) {
|
||||
}
|
||||
|
||||
if ($iterator_type->isNullable() && !$iterator_type->ignore_nullable_issues) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyNullIterator(
|
||||
'Cannot iterate over nullable var ' . $iterator_type,
|
||||
|
@ -1155,9 +1155,10 @@ class AssertionFinder
|
||||
}
|
||||
|
||||
return [$instanceof_class];
|
||||
} elseif ($this_class_name
|
||||
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))
|
||||
) {
|
||||
}
|
||||
|
||||
if ($this_class_name
|
||||
&& (in_array(strtolower($stmt->class->parts[0]), ['self', 'static'], true))) {
|
||||
if ($stmt->class->parts[0] === 'static') {
|
||||
return ['=' . $this_class_name . '&static'];
|
||||
}
|
||||
|
@ -616,7 +616,7 @@ class InstancePropertyAssignmentAnalyzer
|
||||
Context $context,
|
||||
bool $direct_assignment,
|
||||
\Psalm\Codebase $codebase,
|
||||
Type\Union &$assignment_value_type,
|
||||
Type\Union $assignment_value_type,
|
||||
string $prop_name,
|
||||
?string &$var_id
|
||||
): array {
|
||||
|
@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Codebase\Functions;
|
||||
use Psalm\Internal\Codebase\InternalCallMapHandler;
|
||||
use Psalm\Internal\Codebase\TaintFlowGraph;
|
||||
use Psalm\Internal\DataFlow\TaintSink;
|
||||
@ -476,7 +477,7 @@ class ArgumentsAnalyzer
|
||||
if ($function_storage) {
|
||||
$is_variadic = $function_storage->variadic;
|
||||
} elseif (is_string($method_id)) {
|
||||
$is_variadic = $codebase->functions->isVariadic(
|
||||
$is_variadic = Functions::isVariadic(
|
||||
$codebase,
|
||||
strtolower($method_id),
|
||||
$statements_analyzer->getRootFilePath()
|
||||
|
@ -770,7 +770,9 @@ class ArrayFunctionArgumentsAnalyzer
|
||||
}
|
||||
|
||||
return;
|
||||
} elseif ($required_param_count > $max_closure_param_count) {
|
||||
}
|
||||
|
||||
if ($required_param_count > $max_closure_param_count) {
|
||||
$argument_text = $max_closure_param_count === 1 ? 'one argument' : $max_closure_param_count . ' arguments';
|
||||
|
||||
if (IssueBuffer::accepts(
|
||||
|
@ -434,9 +434,9 @@ class FunctionCallAnalyzer extends CallAnalyzer
|
||||
}
|
||||
|
||||
return $function_call_info;
|
||||
} else {
|
||||
$function_call_info->function_exists = true;
|
||||
}
|
||||
|
||||
$function_call_info->function_exists = true;
|
||||
}
|
||||
} else {
|
||||
$function_call_info->function_exists = true;
|
||||
|
@ -169,14 +169,15 @@ class ExpressionIdentifier
|
||||
|
||||
if ($stmt->name instanceof PhpParser\Node\Identifier) {
|
||||
return $object_id . '->' . $stmt->name;
|
||||
} elseif ($source instanceof StatementsAnalyzer
|
||||
&& ($stmt_name_type = $source->node_data->getType($stmt->name))
|
||||
&& $stmt_name_type->isSingleStringLiteral()
|
||||
) {
|
||||
return $object_id . '->' . $stmt_name_type->getSingleStringLiteral()->value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($source instanceof StatementsAnalyzer
|
||||
&& ($stmt_name_type = $source->node_data->getType($stmt->name))
|
||||
&& $stmt_name_type->isSingleStringLiteral()) {
|
||||
return $object_id . '->' . $stmt_name_type->getSingleStringLiteral()->value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||
|
@ -1086,16 +1086,14 @@ class ArrayFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (!$array_access_type) {
|
||||
return Type::getMixed(
|
||||
$type instanceof TEmpty
|
||||
);
|
||||
} else {
|
||||
if ($array_access_type) {
|
||||
return Type::combineUnionTypes(
|
||||
$array_access_type,
|
||||
Type::getMixed($type instanceof TEmpty)
|
||||
);
|
||||
}
|
||||
|
||||
return Type::getMixed($type instanceof TEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -557,9 +557,7 @@ class VariableFetchAnalyzer
|
||||
}
|
||||
|
||||
if (self::isSuperGlobal($var_id)) {
|
||||
$type = Type::getArray();
|
||||
|
||||
return $type;
|
||||
return Type::getArray();
|
||||
}
|
||||
|
||||
return Type::getMixed();
|
||||
|
@ -189,13 +189,20 @@ class SimpleTypeInferer
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) {
|
||||
if (strtolower($stmt->name->parts[0]) === 'false') {
|
||||
$name = strtolower($stmt->name->parts[0]);
|
||||
if ($name === 'false') {
|
||||
return Type::getFalse();
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'true') {
|
||||
}
|
||||
|
||||
if ($name === 'true') {
|
||||
return Type::getTrue();
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'null') {
|
||||
}
|
||||
|
||||
if ($name === 'null') {
|
||||
return Type::getNull();
|
||||
} elseif ($stmt->name->parts[0] === '__NAMESPACE__') {
|
||||
}
|
||||
|
||||
if ($stmt->name->parts[0] === '__NAMESPACE__') {
|
||||
return Type::getString($aliases->namespace);
|
||||
}
|
||||
|
||||
@ -691,7 +698,9 @@ class SimpleTypeInferer
|
||||
if ($unpacked_atomic_type->type_params[0]->hasString()) {
|
||||
// string keys are not supported in unpacked arrays
|
||||
return false;
|
||||
} elseif ($unpacked_atomic_type->type_params[0]->hasInt()) {
|
||||
}
|
||||
|
||||
if ($unpacked_atomic_type->type_params[0]->hasInt()) {
|
||||
$array_creation_info->item_key_atomic_types[] = new Type\Atomic\TInt();
|
||||
}
|
||||
|
||||
|
@ -215,14 +215,13 @@ class UnusedAssignmentRemover
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignOp
|
||||
|| $rhs_exp instanceof PhpParser\Node\Expr\AssignRef
|
||||
) {
|
||||
$rhs_removable = $this->checkRemovableChainAssignment($rhs_exp, $var_loc_map);
|
||||
return $rhs_removable;
|
||||
return $this->checkRemovableChainAssignment($rhs_exp, $var_loc_map);
|
||||
}
|
||||
}
|
||||
return $curr_removable;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,9 +343,9 @@ class UnusedAssignmentRemover
|
||||
$rhs_exp = $current_node->expr;
|
||||
$rhs_search_result = $this->findAssignExp($rhs_exp, $var_id, $var_start_loc, $search_level + 1);
|
||||
return [$rhs_search_result[0], $rhs_search_result[1]];
|
||||
} else {
|
||||
return [null, $search_level];
|
||||
}
|
||||
|
||||
return [null, $search_level];
|
||||
}
|
||||
|
||||
public function checkIfVarRemoved(string $var_id, CodeLocation $var_loc): bool
|
||||
|
@ -10,6 +10,7 @@ use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
||||
use Psalm\Internal\CliUtils;
|
||||
use Psalm\Internal\Composer;
|
||||
use Psalm\Internal\ErrorHandler;
|
||||
use Psalm\Internal\Fork\PsalmRestarter;
|
||||
use Psalm\Internal\IncludeCollector;
|
||||
use Psalm\Internal\Provider;
|
||||
use Psalm\IssueBuffer;
|
||||
@ -902,7 +903,7 @@ final class Psalm
|
||||
// If Xdebug is enabled, restart without it
|
||||
$ini_handler->check();
|
||||
|
||||
if ($config->load_xdebug_stub === null && '' !== $ini_handler->getSkippedVersion()) {
|
||||
if ($config->load_xdebug_stub === null && PsalmRestarter::getSkippedVersion() !== '') {
|
||||
$config->load_xdebug_stub = true;
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ final class CliUtils
|
||||
require_once __DIR__ . '/../../../vendor/netresearch/jsonmapper/src/JsonMapper/Exception.php';
|
||||
}
|
||||
|
||||
if (realpath($psalm_dir) !== realpath($current_dir) && !$in_phar) {
|
||||
if (!$in_phar && realpath($psalm_dir) !== realpath($current_dir)) {
|
||||
$autoload_roots[] = $psalm_dir;
|
||||
}
|
||||
|
||||
@ -241,13 +241,14 @@ final class CliUtils
|
||||
/** @var string */
|
||||
$input_path = $input_paths[$i];
|
||||
|
||||
if (realpath($input_path) === realpath(dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'bin'
|
||||
$real_input_path = realpath($input_path);
|
||||
if ($real_input_path === realpath(dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'bin'
|
||||
. DIRECTORY_SEPARATOR . 'psalm')
|
||||
|| realpath($input_path) === realpath(dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'bin'
|
||||
|| $real_input_path === realpath(dirname(__DIR__, 5) . DIRECTORY_SEPARATOR . 'bin'
|
||||
. DIRECTORY_SEPARATOR . 'psalter')
|
||||
|| realpath($input_path) === realpath(dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'psalm')
|
||||
|| realpath($input_path) === realpath(dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'psalter')
|
||||
|| realpath($input_path) === realpath(Phar::running(false))
|
||||
|| $real_input_path === realpath(dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'psalm')
|
||||
|| $real_input_path === realpath(dirname(__DIR__, 3) . DIRECTORY_SEPARATOR . 'psalter')
|
||||
|| $real_input_path === realpath(Phar::running(false))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
@ -545,13 +545,8 @@ class ClassLikes
|
||||
?string $calling_fq_class_name = null,
|
||||
?string $calling_method_id = null
|
||||
): bool {
|
||||
if (!$this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
&& !$this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
|| $this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -563,14 +558,9 @@ class ClassLikes
|
||||
?string $calling_fq_class_name = null,
|
||||
?string $calling_method_id = null
|
||||
): bool {
|
||||
if (!$this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
&& !$this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
&& !$this->enumExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
|| $this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id)
|
||||
|| $this->enumExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -721,9 +711,7 @@ class ClassLikes
|
||||
{
|
||||
$fq_interface_name = strtolower($fq_interface_name);
|
||||
|
||||
$storage = $this->classlike_storage_provider->get($fq_interface_name);
|
||||
|
||||
return $storage->parent_interfaces;
|
||||
return $this->classlike_storage_provider->get($fq_interface_name)->parent_interfaces;
|
||||
}
|
||||
|
||||
public function traitExists(string $fq_trait_name, ?CodeLocation $code_location = null): bool
|
||||
|
@ -301,16 +301,24 @@ class ConstantTypeResolver
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
return new Type\Atomic\TLiteralString($value);
|
||||
} elseif (\is_int($value)) {
|
||||
return new Type\Atomic\TLiteralInt($value);
|
||||
} elseif (\is_float($value)) {
|
||||
return new Type\Atomic\TLiteralFloat($value);
|
||||
} elseif ($value === false) {
|
||||
return new Type\Atomic\TFalse;
|
||||
} elseif ($value === true) {
|
||||
return new Type\Atomic\TTrue;
|
||||
} else {
|
||||
return new Type\Atomic\TNull;
|
||||
}
|
||||
|
||||
if (\is_int($value)) {
|
||||
return new Type\Atomic\TLiteralInt($value);
|
||||
}
|
||||
|
||||
if (\is_float($value)) {
|
||||
return new Type\Atomic\TLiteralFloat($value);
|
||||
}
|
||||
|
||||
if ($value === false) {
|
||||
return new Type\Atomic\TFalse;
|
||||
}
|
||||
|
||||
if ($value === true) {
|
||||
return new Type\Atomic\TTrue;
|
||||
}
|
||||
|
||||
return new Type\Atomic\TNull;
|
||||
}
|
||||
}
|
||||
|
@ -531,8 +531,7 @@ class Functions
|
||||
);
|
||||
|
||||
try {
|
||||
$method_storage = $codebase->methods->getStorage($count_method_id);
|
||||
return $method_storage->mutation_free;
|
||||
return $codebase->methods->getStorage($count_method_id)->mutation_free;
|
||||
} catch (\Exception $e) {
|
||||
// do nothing
|
||||
}
|
||||
|
@ -955,9 +955,7 @@ class Methods
|
||||
return false;
|
||||
}
|
||||
|
||||
$storage = $this->getStorage($method_id);
|
||||
|
||||
return $storage->returns_by_ref;
|
||||
return $this->getStorage($method_id)->returns_by_ref;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,8 +147,8 @@ class Populator
|
||||
|
||||
$this->progress->debug('FileStorage is populated' . "\n");
|
||||
|
||||
$this->classlike_storage_provider->populated();
|
||||
$this->file_storage_provider->populated();
|
||||
ClassLikeStorageProvider::populated();
|
||||
FileStorageProvider::populated();
|
||||
}
|
||||
|
||||
private function populateClassLikeStorage(ClassLikeStorage $storage, array $dependent_classlikes = []): void
|
||||
|
@ -5,6 +5,7 @@ use Psalm\Codebase;
|
||||
use Psalm\Config;
|
||||
use Psalm\Internal\Analyzer\IssueData;
|
||||
use Psalm\Internal\ErrorHandler;
|
||||
use Psalm\Internal\Provider\ClassLikeStorageProvider;
|
||||
use Psalm\Internal\Provider\FileProvider;
|
||||
use Psalm\Internal\Provider\FileReferenceProvider;
|
||||
use Psalm\Internal\Provider\FileStorageProvider;
|
||||
@ -360,8 +361,8 @@ class Scanner
|
||||
$statements_provider = $codebase->statements_provider;
|
||||
|
||||
$codebase->scanner->isForked();
|
||||
$codebase->file_storage_provider->deleteAll();
|
||||
$codebase->classlike_storage_provider->deleteAll();
|
||||
FileStorageProvider::deleteAll();
|
||||
ClassLikeStorageProvider::deleteAll();
|
||||
|
||||
$statements_provider->resetDiffs();
|
||||
|
||||
|
@ -229,7 +229,7 @@ class FunctionDocblockManipulator
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($chars[$i] === '\\' || preg_match('/\w/', $char)) {
|
||||
if ($char === '\\' || preg_match('/\w/', $char)) {
|
||||
if ($this->return_typehint_start === null) {
|
||||
$this->return_typehint_start = $i + $end_bracket_position + 1;
|
||||
}
|
||||
|
@ -48,9 +48,9 @@ class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function enterNode(PhpParser\Node $node): ?int
|
||||
@ -60,12 +60,13 @@ class CheckTrivialExprVisitor extends PhpParser\NodeVisitorAbstract
|
||||
if ($this->checkNonTrivialExpr($node)) {
|
||||
$this->non_trivial_expr[] = $node;
|
||||
return PhpParser\NodeTraverser::STOP_TRAVERSAL;
|
||||
} elseif ($node instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||
}
|
||||
|
||||
if ($node instanceof PhpParser\Node\Expr\ClassConstFetch
|
||||
|| $node instanceof PhpParser\Node\Expr\ConstFetch
|
||||
|| $node instanceof PhpParser\Node\Expr\Error
|
||||
|| $node instanceof PhpParser\Node\Expr\PropertyFetch
|
||||
|| $node instanceof PhpParser\Node\Expr\StaticPropertyFetch
|
||||
) {
|
||||
|| $node instanceof PhpParser\Node\Expr\StaticPropertyFetch) {
|
||||
return PhpParser\NodeTraverser::STOP_TRAVERSAL;
|
||||
}
|
||||
}
|
||||
|
@ -117,11 +117,17 @@ class ExpressionResolver
|
||||
if ($stmt instanceof PhpParser\Node\Expr\ConstFetch) {
|
||||
if (strtolower($stmt->name->parts[0]) === 'false') {
|
||||
return new UnresolvedConstant\ScalarValue(false);
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'true') {
|
||||
}
|
||||
|
||||
if (strtolower($stmt->name->parts[0]) === 'true') {
|
||||
return new UnresolvedConstant\ScalarValue(true);
|
||||
} elseif (strtolower($stmt->name->parts[0]) === 'null') {
|
||||
}
|
||||
|
||||
if (strtolower($stmt->name->parts[0]) === 'null') {
|
||||
return new UnresolvedConstant\ScalarValue(null);
|
||||
} elseif ($stmt->name->parts[0] === '__NAMESPACE__') {
|
||||
}
|
||||
|
||||
if ($stmt->name->parts[0] === '__NAMESPACE__') {
|
||||
return new UnresolvedConstant\ScalarValue($aliases->namespace);
|
||||
}
|
||||
|
||||
|
@ -967,26 +967,26 @@ class FunctionLikeNodeScanner
|
||||
$duplicate_method_storage->has_visitor_issues = true;
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// skip methods based on @since docblock tag
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
}
|
||||
|
||||
if ($doc_comment) {
|
||||
$docblock_info = null;
|
||||
try {
|
||||
$docblock_info = FunctionLikeDocblockParser::parse($doc_comment);
|
||||
} catch (IncorrectDocblockException|DocblockParseException $e) {
|
||||
}
|
||||
if ($docblock_info) {
|
||||
if ($docblock_info->since_php_major_version && !$this->aliases->namespace) {
|
||||
if ($docblock_info->since_php_major_version > $this->codebase->php_major_version) {
|
||||
return false;
|
||||
}
|
||||
if ($docblock_info->since_php_major_version === $this->codebase->php_major_version
|
||||
&& $docblock_info->since_php_minor_version > $this->codebase->php_minor_version
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// skip methods based on @since docblock tag
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
|
||||
if ($doc_comment) {
|
||||
$docblock_info = null;
|
||||
try {
|
||||
$docblock_info = FunctionLikeDocblockParser::parse($doc_comment);
|
||||
} catch (IncorrectDocblockException|DocblockParseException $e) {
|
||||
}
|
||||
if ($docblock_info) {
|
||||
if ($docblock_info->since_php_major_version && !$this->aliases->namespace) {
|
||||
if ($docblock_info->since_php_major_version > $this->codebase->php_major_version) {
|
||||
return false;
|
||||
}
|
||||
if ($docblock_info->since_php_major_version === $this->codebase->php_major_version
|
||||
&& $docblock_info->since_php_minor_version > $this->codebase->php_minor_version
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,7 +429,7 @@ class FileReferenceProvider
|
||||
|
||||
public function removeDeletedFilesFromReferences(): void
|
||||
{
|
||||
$deleted_files = self::getDeletedReferencedFiles();
|
||||
$deleted_files = $this->getDeletedReferencedFiles();
|
||||
|
||||
if ($deleted_files) {
|
||||
foreach ($deleted_files as $file) {
|
||||
|
@ -126,7 +126,7 @@ class NodeDataProvider implements \Psalm\NodeTypeProvider
|
||||
|
||||
public function isPureCompatible(PhpParser\Node\Expr $node) : bool
|
||||
{
|
||||
$node_type = self::getType($node);
|
||||
$node_type = $this->getType($node);
|
||||
|
||||
return ($node_type && $node_type->reference_free) || isset($node->pure);
|
||||
}
|
||||
|
@ -51,9 +51,10 @@ class ExplodeReturnTypeProvider implements \Psalm\Plugin\EventHandler\FunctionRe
|
||||
? new Type\Atomic\TList($inner_type)
|
||||
: new Type\Atomic\TNonEmptyList($inner_type)
|
||||
]);
|
||||
} elseif (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value))
|
||||
&& $first_arg_type->hasString()
|
||||
) {
|
||||
}
|
||||
|
||||
if (($first_arg_type = $statements_source->node_data->getType($call_args[0]->value))
|
||||
&& $first_arg_type->hasString()) {
|
||||
$can_be_false = true;
|
||||
if ($first_arg_type->isString()) {
|
||||
$can_be_false = false;
|
||||
|
@ -42,7 +42,9 @@ class GetObjectVarsReturnTypeProvider implements FunctionReturnTypeProviderInter
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TKeyedArray($object_type->properties)
|
||||
]);
|
||||
} elseif ($object_type instanceof Type\Atomic\TNamedObject) {
|
||||
}
|
||||
|
||||
if ($object_type instanceof Type\Atomic\TNamedObject) {
|
||||
if (strtolower($object_type->value) === strtolower(stdClass::class)) {
|
||||
return Type::parseString('array<string, mixed>');
|
||||
}
|
||||
|
@ -1335,7 +1335,9 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
|
||||
if ($existing_has_object && !$existing_has_string) {
|
||||
return Type::parseString($assertion, null, $template_type_map);
|
||||
} elseif ($existing_has_string && !$existing_has_object) {
|
||||
}
|
||||
|
||||
if ($existing_has_string && !$existing_has_object) {
|
||||
if (!$allow_string_comparison && $code_location) {
|
||||
if (IssueBuffer::accepts(
|
||||
new TypeDoesNotContainType(
|
||||
|
@ -53,14 +53,16 @@ class IntegerRangeComparator
|
||||
if (isset($container_atomic_types['int'])) {
|
||||
if (get_class($container_atomic_types['int']) === TInt::class) {
|
||||
return true;
|
||||
} elseif (get_class($container_atomic_types['int']) === TPositiveInt::class) {
|
||||
}
|
||||
|
||||
if (get_class($container_atomic_types['int']) === TPositiveInt::class) {
|
||||
if ($input_type_part->isPositive()) {
|
||||
return true;
|
||||
} else {
|
||||
//every positive integer is satisfied by the positive-int int container so we reduce the range
|
||||
$reduced_range->max_bound = 0;
|
||||
unset($container_atomic_types['int']);
|
||||
}
|
||||
|
||||
//every positive integer is satisfied by the positive-int int container so we reduce the range
|
||||
$reduced_range->max_bound = 0;
|
||||
unset($container_atomic_types['int']);
|
||||
} else {
|
||||
throw new \UnexpectedValueException('Should not happen: unknown int key');
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ class ObjectComparator
|
||||
->getStorageFor($intersection_input_type->defining_class);
|
||||
|
||||
if ($codebase->classlikes->traitExists($container_class)
|
||||
&& !\is_null($input_class_like)
|
||||
&& $input_class_like !== null
|
||||
&& isset(
|
||||
$input_class_like->template_extended_params[$container_class][$container_param]
|
||||
)) {
|
||||
|
@ -153,9 +153,13 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
return Type::getNull();
|
||||
} elseif ($assertion === 'array-key-exists') {
|
||||
}
|
||||
|
||||
if ($assertion === 'array-key-exists') {
|
||||
return Type::getEmpty();
|
||||
} elseif (substr($assertion, 0, 9) === 'in-array-') {
|
||||
}
|
||||
|
||||
if (substr($assertion, 0, 9) === 'in-array-') {
|
||||
$assertion = substr($assertion, 9);
|
||||
$new_var_type = null;
|
||||
try {
|
||||
@ -189,7 +193,9 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
}
|
||||
|
||||
return $existing_var_type;
|
||||
} elseif (substr($assertion, 0, 14) === 'has-array-key-') {
|
||||
}
|
||||
|
||||
if (substr($assertion, 0, 14) === 'has-array-key-') {
|
||||
return $existing_var_type;
|
||||
}
|
||||
}
|
||||
|
@ -435,6 +435,7 @@ class TypeParser
|
||||
*/
|
||||
public static function getComputedIntsFromMask(array $potential_ints) : array
|
||||
{
|
||||
/** @var list<int> */
|
||||
$potential_values = [];
|
||||
|
||||
foreach ($potential_ints as $ith) {
|
||||
@ -443,8 +444,8 @@ class TypeParser
|
||||
$new_values[] = $ith;
|
||||
|
||||
if ($ith !== 0) {
|
||||
for ($j = 0; $j < count($potential_values); $j++) {
|
||||
$new_values[] = $ith | $potential_values[$j];
|
||||
foreach ($potential_values as $potential_value) {
|
||||
$new_values[] = $ith | $potential_value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,9 @@ class CompactReport extends Report
|
||||
foreach ($this->issues_data as $i => $issue_data) {
|
||||
if (!$this->show_info && $issue_data->severity === Config::REPORT_INFO) {
|
||||
continue;
|
||||
} elseif ($current_file === null || $current_file !== $issue_data->file_name) {
|
||||
}
|
||||
|
||||
if ($current_file === null || $current_file !== $issue_data->file_name) {
|
||||
// If we're processing a new file, then wrap up the last table and render it out.
|
||||
if ($buffer !== null) {
|
||||
$table->render();
|
||||
|
@ -546,8 +546,10 @@ abstract class Type
|
||||
Codebase $codebase
|
||||
): ?Union {
|
||||
$intersection_performed = false;
|
||||
$type_1_mixed = $type_1->isMixed();
|
||||
$type_2_mixed = $type_2->isMixed();
|
||||
|
||||
if ($type_1->isMixed() && $type_2->isMixed()) {
|
||||
if ($type_1_mixed && $type_2_mixed) {
|
||||
$combined_type = Type::getMixed();
|
||||
} else {
|
||||
$both_failed_reconciliation = false;
|
||||
@ -562,10 +564,10 @@ abstract class Type
|
||||
return $type_1;
|
||||
}
|
||||
|
||||
if ($type_1->isMixed() && !$type_2->isMixed()) {
|
||||
if ($type_1_mixed && !$type_2_mixed) {
|
||||
$combined_type = clone $type_2;
|
||||
$intersection_performed = true;
|
||||
} elseif (!$type_1->isMixed() && $type_2->isMixed()) {
|
||||
} elseif ($type_2_mixed) {
|
||||
$combined_type = clone $type_1;
|
||||
$intersection_performed = true;
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user