Skip to content
Permalink
Browse files

Generate missing param types from callers

Fixes #1707
  • Loading branch information...
muglug committed May 31, 2019
1 parent e40aed5 commit cd969c51e5b546344575229cff67737d455bb2cc
@@ -273,11 +273,6 @@ class Context
*/
public $calling_method_id;
/**
* @var bool
*/
public $infer_types = false;
/**
* @var bool
*/
@@ -407,41 +402,6 @@ public function getRedefinedVars(array $new_vars_in_scope, $include_new_vars = f
return $redefined_vars;
}
/**
* @return void
*/
public function inferType(
string $var_name,
FunctionLikeStorage $function_storage,
Type\Union $original_type,
Type\Union $inferred_type,
Codebase $codebase
) {
if ($original_type->getId() !== $inferred_type->getId()
&& !isset($this->assigned_var_ids['$' . $var_name])
&& array_key_exists($var_name, $function_storage->param_types)
&& !$function_storage->param_types[$var_name]
) {
if (isset($this->possible_param_types[$var_name])) {
if (\Psalm\Internal\Analyzer\TypeAnalyzer::isContainedBy(
$codebase,
$inferred_type,
$this->possible_param_types[$var_name]
)) {
$this->possible_param_types[$var_name] = clone $inferred_type;
} else {
$this->possible_param_types[$var_name] = Type::combineUnionTypes(
$this->possible_param_types[$var_name],
$inferred_type
);
}
} else {
$this->possible_param_types[$var_name] = clone $inferred_type;
$this->vars_in_scope['$' . $var_name] = clone $inferred_type;
}
}
}
/**
* @param Context $original_context
* @param Context $new_context
@@ -1393,7 +1393,6 @@ private function analyzeClassMethod(
$method_context = clone $class_context;
$method_context->collect_exceptions = $config->check_for_throws_docblock;
$method_context->infer_types = $codebase->infer_types_from_usage;
$method_analyzer->analyze(
$method_context,
@@ -172,6 +172,19 @@ public function getMethodMutations(
}
}
public function getFunctionLikeAnalyzer(string $method_name) : FunctionLikeAnalyzer
{
foreach ($this->class->stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod &&
strtolower($stmt->name->name) === strtolower($method_name)
) {
return new MethodAnalyzer($stmt, $this);
}
}
throw new \UnexpectedValueException('Should return a function analyzer');
}
/**
* @param string $fq_class_name
* @param array<string> $suppressed_issues
@@ -340,6 +340,19 @@ public function getMethodMutations($method_id, Context $this_context, bool $from
}
}
public function getFunctionLikeAnalyzer(string $method_id) : FunctionLikeAnalyzer
{
list($fq_class_name, $method_name) = explode('::', $method_id);
if (!isset($this->class_analyzers_to_analyze[strtolower($fq_class_name)])) {
throw new \UnexpectedValueException('Should not happen');
}
$class_analyzer_to_examine = $this->class_analyzers_to_analyze[strtolower($fq_class_name)];
return $class_analyzer_to_examine->getFunctionLikeAnalyzer($method_name);
}
/**
* @return null|string
*/
@@ -66,11 +66,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
*/
protected $return_vars_in_scope = [];
/**
* @var array<string, Type\Union>
*/
protected $possible_param_types = [];
/**
* @var ?array<string, bool>
*/
@@ -660,24 +655,6 @@ function (FunctionLikeParameter $p) {
&& $function_param->location
&& !isset($implemented_docblock_param_types[$offset])
) {
if ($codebase->alter_code
&& isset($project_analyzer->getIssuesToFix()['MissingParamType'])
) {
$possible_type = $this->possible_param_types[$function_param->name] ?? null;
if (!$possible_type || $possible_type->hasMixed() || $possible_type->isNull()) {
continue;
}
self::addOrUpdateParamType(
$project_analyzer,
$function_param->name,
$possible_type
);
continue;
}
if ($this->function instanceof Closure) {
IssueBuffer::accepts(
new MissingClosureParamType(
@@ -959,7 +936,7 @@ public function verifyReturnType(
*
* @return void
*/
private function addOrUpdateParamType(
public function addOrUpdateParamType(
ProjectAnalyzer $project_analyzer,
$param_name,
Type\Union $inferred_return_type,
@@ -1035,54 +1012,40 @@ public function examineParamTypes(
Codebase $codebase,
PhpParser\Node $stmt = null
) {
if ($context->infer_types) {
foreach ($context->possible_param_types as $var_id => $type) {
if (isset($this->possible_param_types[$var_id])) {
$this->possible_param_types[$var_id] = Type::combineUnionTypes(
$this->possible_param_types[$var_id],
$type,
$codebase
);
} else {
$this->possible_param_types[$var_id] = clone $type;
}
}
} else {
$storage = $this->getFunctionLikeStorage($statements_analyzer);
$storage = $this->getFunctionLikeStorage($statements_analyzer);
foreach ($storage->params as $i => $param) {
if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name]) && !$param->is_variadic) {
$actual_type = $context->vars_in_scope['$' . $param->name];
$param_out_type = $param->type;
foreach ($storage->params as $i => $param) {
if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name]) && !$param->is_variadic) {
$actual_type = $context->vars_in_scope['$' . $param->name];
$param_out_type = $param->type;
if (isset($storage->param_out_types[$i])) {
$param_out_type = $storage->param_out_types[$i];
}
if (isset($storage->param_out_types[$i])) {
$param_out_type = $storage->param_out_types[$i];
}
if ($param_out_type && !$actual_type->hasMixed() && $param->location) {
if (!TypeAnalyzer::isContainedBy(
$codebase,
$actual_type,
$param_out_type,
$actual_type->ignore_nullable_issues,
$actual_type->ignore_falsable_issues
)
) {
if (IssueBuffer::accepts(
new ReferenceConstraintViolation(
'Variable ' . '$' . $param->name . ' is limited to values of type '
. $param_out_type->getId()
. ' because it is passed by reference, '
. $actual_type->getId() . ' type found. Use @param-out to specify '
. 'a different output type',
$stmt
? new CodeLocation($this, $stmt)
: $param->location
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
if ($param_out_type && !$actual_type->hasMixed() && $param->location) {
if (!TypeAnalyzer::isContainedBy(
$codebase,
$actual_type,
$param_out_type,
$actual_type->ignore_nullable_issues,
$actual_type->ignore_falsable_issues
)
) {
if (IssueBuffer::accepts(
new ReferenceConstraintViolation(
'Variable ' . '$' . $param->name . ' is limited to values of type '
. $param_out_type->getId()
. ' because it is passed by reference, '
. $actual_type->getId() . ' type found. Use @param-out to specify '
. 'a different output type',
$stmt
? new CodeLocation($this, $stmt)
: $param->location
),
$statements_analyzer->getSuppressedIssues()
)) {
// fall through
}
}
}
@@ -953,6 +953,28 @@ public function getMethodMutations(
$file_analyzer->interface_analyzers_to_analyze = [];
}
public function getFunctionLikeAnalyzer(string $method_id, string $file_path) : FunctionLikeAnalyzer
{
$file_analyzer = new FileAnalyzer(
$this,
$file_path,
$this->config->shortenFileName($file_path)
);
$stmts = $this->codebase->getStatementsForFile(
$file_analyzer->getFilePath()
);
$file_analyzer->populateCheckers($stmts);
$function_analyzer = $file_analyzer->getFunctionLikeAnalyzer($method_id);
$file_analyzer->class_analyzers_to_analyze = [];
$file_analyzer->interface_analyzers_to_analyze = [];
return $function_analyzer;
}
/**
* Adapted from https://gist.github.com/divinity76/01ef9ca99c111565a72d3a8a6e42f7fb
* returns number of cpu cores
@@ -283,26 +283,6 @@ function (Clause $c) use ($mixed_var_ids) {
) : null
);
if ($if_context->infer_types) {
$source_analyzer = $statements_analyzer->getSource();
if ($source_analyzer instanceof FunctionLikeAnalyzer) {
$function_storage = $source_analyzer->getFunctionLikeStorage($statements_analyzer);
foreach ($reconcilable_if_types as $var_id => $_) {
if (isset($if_context->vars_in_scope[$var_id])) {
$if_context->inferType(
substr($var_id, 1),
$function_storage,
$if_context->vars_in_scope[$var_id],
$if_vars_in_scope_reconciled[$var_id],
$statements_analyzer->getCodebase()
);
}
}
}
}
$if_context->vars_in_scope = $if_vars_in_scope_reconciled;
foreach ($reconcilable_if_types as $var_id => $_) {
@@ -680,10 +660,6 @@ protected static function analyzeIfBlock(
);
}
}
if ($if_context->infer_types) {
$if_scope->possible_param_types = $if_context->possible_param_types;
}
} else {
if (!$has_break_statement) {
$if_scope->reasonable_clauses = [];
@@ -1204,29 +1180,6 @@ function (Clause $c) use ($conditional_assigned_var_ids) {
$if_scope->reasonable_clauses = [];
}
if ($elseif_context->infer_types) {
$elseif_possible_param_types = $elseif_context->possible_param_types;
if ($if_scope->possible_param_types) {
$vars_to_remove = [];
foreach ($if_scope->possible_param_types as $var => $type) {
if (isset($elseif_possible_param_types[$var])) {
$if_scope->possible_param_types[$var] = Type::combineUnionTypes(
$elseif_possible_param_types[$var],
$type
);
} else {
$vars_to_remove[] = $var;
}
}
foreach ($vars_to_remove as $var) {
unset($if_scope->possible_param_types[$var]);
}
}
}
if ($negated_elseif_types) {
if ($has_leaving_statements) {
$changed_var_ids = [];
@@ -1623,29 +1576,6 @@ protected static function analyzeElseBlock(
if ($outer_context->collect_exceptions) {
$outer_context->mergeExceptions($else_context);
}
if ($else_context->infer_types) {
$else_possible_param_types = $else_context->possible_param_types;
if ($if_scope->possible_param_types) {
$vars_to_remove = [];
foreach ($if_scope->possible_param_types as $var => $type) {
if (isset($else_possible_param_types[$var])) {
$if_scope->possible_param_types[$var] = Type::combineUnionTypes(
$else_possible_param_types[$var],
$type
);
} else {
$vars_to_remove[] = $var;
}
}
foreach ($vars_to_remove as $var) {
unset($if_scope->possible_param_types[$var]);
}
}
}
}
/**

0 comments on commit cd969c5

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