1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Improve API for inference

This commit is contained in:
Matthew Brown 2017-09-03 20:52:54 -04:00
parent 11aa3558b7
commit 9f9e8a1b30
4 changed files with 77 additions and 122 deletions

View File

@ -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<string, string>
*/

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -1,7 +1,9 @@
<?php
namespace Psalm;
use PhpParser;
use Psalm\Checker\StatementsChecker;
use Psalm\Storage\FunctionLikeStorage;
use Psalm\Type\Union;
class Context
@ -248,6 +250,40 @@ class Context
return $redefined_vars;
}
/**
* @return void
*/
public function inferType(
PhpParser\Node\Expr $expr,
FunctionLikeStorage $function_storage,
Type\Union $inferred_type
) {
if (!isset($expr->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