diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index baa1d9da1..3f061b315 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -29,6 +29,7 @@ use Psalm\Issue\UnusedVariable; use Psalm\IssueBuffer; use Psalm\Mutator\FileMutator; use Psalm\StatementsSource; +use Psalm\Storage\FunctionLikeStorage; use Psalm\Storage\MethodStorage; use Psalm\Type; use Psalm\Type\Atomic\TNamedObject; @@ -364,6 +365,11 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo $statements_checker = new StatementsChecker($this); + // this increases memory, so only do it if running under this flag + if ($project_checker->infer_types_from_usage) { + $this->statements_checker = $statements_checker; + } + $template_types = $storage->template_types; if ($class_storage && $class_storage->template_types) { @@ -700,6 +706,32 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo return $this->getFilePath() . ':' . $this->function->getLine() . ':' . 'closure'; } + /** + * @return FunctionLikeStorage + */ + public function getFunctionLikeStorage() + { + $function_id = $this->getMethodId(); + + $project_checker = $this->getFileChecker()->project_checker; + + if (strpos($function_id, '::')) { + $declaring_method_id = MethodChecker::getDeclaringMethodId($project_checker, $function_id); + + if (!$declaring_method_id) { + throw new \UnexpectedValueException('This should never happen'); + } + + return MethodChecker::getStorage($project_checker, $declaring_method_id); + } + + if (!$this->statements_checker) { + throw new \UnexpectedValueException('This should not happen either'); + } + + return FunctionChecker::getStorage($this->statements_checker, $function_id); + } + /** * @return array */ diff --git a/src/Psalm/Checker/Statements/Expression/CallChecker.php b/src/Psalm/Checker/Statements/Expression/CallChecker.php index 67b86f657..0de2b6ee0 100644 --- a/src/Psalm/Checker/Statements/Expression/CallChecker.php +++ b/src/Psalm/Checker/Statements/Expression/CallChecker.php @@ -2043,46 +2043,14 @@ class CallChecker $method_identifier = $cased_method_id ? ' of ' . $cased_method_id : ''; if ($project_checker->infer_types_from_usage - && $input_expr - && $input_expr instanceof PhpParser\Node\Expr\Variable - && is_string($input_expr->name) && $context - && !isset($context->assigned_vars['$' . $input_expr->name]) + && $input_expr + && $input_expr->inferredType ) { $source_checker = $statements_checker->getSource(); if ($source_checker instanceof FunctionLikeChecker) { - $function_id = $source_checker->getMethodId(); - - if (strpos($function_id, '::')) { - $declaring_method_id = MethodChecker::getDeclaringMethodId($project_checker, $function_id); - - if (!$declaring_method_id) { - throw new \UnexpectedValueException('This should never happen'); - } - - $function_storage = MethodChecker::getStorage($project_checker, $declaring_method_id); - } else { - $function_storage = FunctionChecker::getStorage($statements_checker, $function_id); - } - - if ($function_storage->param_types - && array_key_exists($input_expr->name, $function_storage->param_types) - && !$function_storage->param_types[$input_expr->name] - ) { - if (isset($context->possible_param_types[$input_expr->name])) { - $context->possible_param_types[$input_expr->name] = Type::combineUnionTypes( - $context->possible_param_types[$input_expr->name], - $param_type - ); - } else { - $context->possible_param_types[$input_expr->name] = $param_type; - } - } - } - - if ($input_type->isMixed()) { - $context->vars_in_scope['$' . $input_expr->name] = clone $param_type; + $context->inferType($input_expr, $source_checker->getFunctionLikeStorage(), $param_type); } } diff --git a/src/Psalm/Checker/Statements/ExpressionChecker.php b/src/Psalm/Checker/Statements/ExpressionChecker.php index 6a702351f..f48b9432a 100644 --- a/src/Psalm/Checker/Statements/ExpressionChecker.php +++ b/src/Psalm/Checker/Statements/ExpressionChecker.php @@ -7,7 +7,6 @@ use Psalm\Checker\ClassChecker; use Psalm\Checker\ClassLikeChecker; use Psalm\Checker\ClosureChecker; use Psalm\Checker\CommentChecker; -use Psalm\Checker\FunctionChecker; use Psalm\Checker\FunctionLikeChecker; use Psalm\Checker\MethodChecker; use Psalm\Checker\ProjectChecker; @@ -1324,59 +1323,10 @@ class ExpressionChecker $source_checker = $statements_checker->getSource(); if ($source_checker instanceof FunctionLikeChecker) { - $function_id = $source_checker->getMethodId(); + $function_storage = $source_checker->getFunctionLikeStorage(); - if (strpos($function_id, '::')) { - $declaring_method_id = MethodChecker::getDeclaringMethodId($project_checker, $function_id); - - if (!$declaring_method_id) { - throw new \UnexpectedValueException('This should never happen'); - } - - $function_storage = MethodChecker::getStorage($project_checker, $declaring_method_id); - } else { - $function_storage = FunctionChecker::getStorage($statements_checker, $function_id); - } - - if ($function_storage->param_types) { - if ($left_type - && $left_type->isMixed() - && $left instanceof PhpParser\Node\Expr\Variable - && is_string($left->name) - && !isset($context->assigned_vars['$' . $left->name]) - && array_key_exists($left->name, $function_storage->param_types) - && !$function_storage->param_types[$left->name] - ) { - if (isset($context->possible_param_types[$left->name])) { - $context->possible_param_types[$left->name] = Type::combineUnionTypes( - $context->possible_param_types[$left->name], - Type::getString() - ); - } else { - $context->possible_param_types[$left->name] = Type::getString(); - $context->vars_in_scope['$' . $left->name] = Type::getString(); - } - } - - if ($right_type - && $right_type->isMixed() - && $right instanceof PhpParser\Node\Expr\Variable - && is_string($right->name) - && !isset($context->assigned_vars['$' . $right->name]) - && array_key_exists($right->name, $function_storage->param_types) - && !$function_storage->param_types[$right->name] - ) { - if (isset($context->possible_param_types[$right->name])) { - $context->possible_param_types[$right->name] = Type::combineUnionTypes( - $context->possible_param_types[$right->name], - Type::getString() - ); - } else { - $context->possible_param_types[$right->name] = Type::getString(); - $context->vars_in_scope['$' . $right->name] = Type::getString(); - } - } - } + $context->inferType($left, $function_storage, Type::getString()); + $context->inferType($right, $function_storage, Type::getString()); } } @@ -2021,19 +1971,7 @@ class ExpressionChecker $source_checker = $statements_checker->getSource(); if ($source_checker instanceof FunctionLikeChecker) { - $function_id = $source_checker->getMethodId(); - - if (strpos($function_id, '::')) { - $declaring_method_id = MethodChecker::getDeclaringMethodId($project_checker, $function_id); - - if (!$declaring_method_id) { - throw new \UnexpectedValueException('This should never happen'); - } - - $function_storage = MethodChecker::getStorage($project_checker, $declaring_method_id); - } else { - $function_storage = FunctionChecker::getStorage($statements_checker, $function_id); - } + $function_storage = $source_checker->getFunctionLikeStorage(); } } @@ -2043,27 +1981,8 @@ class ExpressionChecker return false; } - if ($function_storage - && $function_storage->param_types - && $part->inferredType - && $part->inferredType->isMixed() - ) { - if ($part instanceof PhpParser\Node\Expr\Variable - && is_string($part->name) - && !isset($context->assigned_vars['$' . $part->name]) - && array_key_exists($part->name, $function_storage->param_types) - && !$function_storage->param_types[$part->name] - ) { - if (isset($context->possible_param_types[$part->name])) { - $context->possible_param_types[$part->name] = Type::combineUnionTypes( - $context->possible_param_types[$part->name], - Type::getString() - ); - } else { - $context->possible_param_types[$part->name] = Type::getString(); - $context->vars_in_scope['$' . $part->name] = Type::getString(); - } - } + if ($function_storage) { + $context->inferType($part, $function_storage, Type::getString()); } } diff --git a/src/Psalm/Context.php b/src/Psalm/Context.php index 20dc8929e..5f5b90869 100644 --- a/src/Psalm/Context.php +++ b/src/Psalm/Context.php @@ -1,7 +1,9 @@ inferredType)) { + return; + } + + $expr_type = $expr->inferredType; + + if ($expr_type + && ($expr_type->isMixed() || (string)$expr_type === (string)$inferred_type) + && $expr instanceof PhpParser\Node\Expr\Variable + && is_string($expr->name) + && !isset($this->assigned_vars['$' . $expr->name]) + && array_key_exists($expr->name, $function_storage->param_types) + && !$function_storage->param_types[$expr->name] + ) { + if (isset($this->possible_param_types[$expr->name])) { + $this->possible_param_types[$expr->name] = Type::combineUnionTypes( + $this->possible_param_types[$expr->name], + $inferred_type + ); + } else { + $this->possible_param_types[$expr->name] = $inferred_type; + $this->vars_in_scope['$' . $expr->name] = clone $inferred_type; + } + } + } + /** * @param Context $original_context * @param Context $new_context