mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #1379 - allow @param-out to change type
This commit is contained in:
parent
82e6876011
commit
472cdf6bea
@ -19,6 +19,7 @@ use Psalm\Issue\MismatchingDocblockParamType;
|
||||
use Psalm\Issue\MissingClosureParamType;
|
||||
use Psalm\Issue\MissingParamType;
|
||||
use Psalm\Issue\MissingThrowsDocblock;
|
||||
use Psalm\Issue\ReferenceConstraintViolation;
|
||||
use Psalm\Issue\ReservedWord;
|
||||
use Psalm\Issue\UnusedParam;
|
||||
use Psalm\IssueBuffer;
|
||||
@ -540,11 +541,6 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
}
|
||||
|
||||
if ($function_param->by_ref) {
|
||||
$context->byref_constraints['$' . $function_param->name]
|
||||
= new \Psalm\Internal\ReferenceConstraint(!$param_type->hasMixed() ? $param_type : null);
|
||||
}
|
||||
|
||||
if ($function_param->by_ref) {
|
||||
// register by ref params as having been used, to avoid false positives
|
||||
// @todo change the assignment analysis *just* for byref params
|
||||
@ -598,7 +594,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
|
||||
$statements_analyzer->analyze($function_stmts, $context, $global_context, true);
|
||||
|
||||
$this->addPossibleParamTypes($context, $codebase);
|
||||
$this->examineParamTypes($statements_analyzer, $context, $codebase);
|
||||
|
||||
foreach ($storage->params as $offset => $function_param) {
|
||||
// only complain if there's no type defined by a parent type
|
||||
@ -981,7 +977,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function addPossibleParamTypes(Context $context, Codebase $codebase)
|
||||
public function examineParamTypes(StatementsAnalyzer $statements_analyzer, Context $context, Codebase $codebase)
|
||||
{
|
||||
if ($context->infer_types) {
|
||||
foreach ($context->possible_param_types as $var_id => $type) {
|
||||
@ -995,6 +991,44 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
$this->possible_param_types[$var_id] = clone $type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$storage = $this->getFunctionLikeStorage($statements_analyzer);
|
||||
|
||||
foreach ($storage->params as $i => $param) {
|
||||
if ($param->by_ref && isset($context->vars_in_scope['$' . $param->name])) {
|
||||
$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 ($param_out_type && !$actual_type->isMixed() && $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',
|
||||
$param->location
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -588,6 +588,7 @@ class CallAnalyzer
|
||||
$statements_analyzer,
|
||||
$arg->value,
|
||||
$by_ref_type,
|
||||
$by_ref_type,
|
||||
$context,
|
||||
false
|
||||
);
|
||||
@ -701,6 +702,7 @@ class CallAnalyzer
|
||||
$statements_analyzer,
|
||||
$array_arg,
|
||||
$by_ref_type,
|
||||
$by_ref_type,
|
||||
$context,
|
||||
false
|
||||
);
|
||||
@ -804,6 +806,7 @@ class CallAnalyzer
|
||||
$statements_analyzer,
|
||||
$array_arg,
|
||||
$by_ref_type,
|
||||
$by_ref_type,
|
||||
$context,
|
||||
false
|
||||
);
|
||||
@ -811,10 +814,13 @@ class CallAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
$array_type = Type::getArray();
|
||||
|
||||
ExpressionAnalyzer::assignByRefParam(
|
||||
$statements_analyzer,
|
||||
$array_arg,
|
||||
Type::getArray(),
|
||||
$array_type,
|
||||
$array_type,
|
||||
$context,
|
||||
false
|
||||
);
|
||||
@ -1157,7 +1163,7 @@ class CallAnalyzer
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (!isset($arg->value->inferredType)) {
|
||||
if ($function_param) {
|
||||
if ($function_param && !$function_param->by_ref) {
|
||||
$codebase->analyzer->incrementMixedCount($statements_analyzer->getFilePath());
|
||||
|
||||
$param_type = $function_param->type;
|
||||
@ -1270,6 +1276,7 @@ class CallAnalyzer
|
||||
true
|
||||
)) {
|
||||
$by_ref_type = null;
|
||||
$by_ref_out_type = null;
|
||||
|
||||
$check_null_ref = true;
|
||||
|
||||
@ -1282,7 +1289,7 @@ class CallAnalyzer
|
||||
}
|
||||
|
||||
if (isset($function_storage->param_out_types[$argument_offset])) {
|
||||
$by_ref_type = $function_storage->param_out_types[$argument_offset];
|
||||
$by_ref_out_type = $function_storage->param_out_types[$argument_offset];
|
||||
}
|
||||
} else {
|
||||
$by_ref_type = $last_param->type;
|
||||
@ -1322,6 +1329,7 @@ class CallAnalyzer
|
||||
$statements_analyzer,
|
||||
$arg->value,
|
||||
$by_ref_type,
|
||||
$by_ref_out_type ?: $by_ref_type,
|
||||
$context,
|
||||
$method_id && (strpos($method_id, '::') !== false || !CallMap::inCallMap($method_id)),
|
||||
$check_null_ref
|
||||
|
@ -166,7 +166,7 @@ class VariableFetchAnalyzer
|
||||
}
|
||||
|
||||
if ($passed_by_reference && $by_ref_type) {
|
||||
ExpressionAnalyzer::assignByRefParam($statements_analyzer, $stmt, $by_ref_type, $context);
|
||||
ExpressionAnalyzer::assignByRefParam($statements_analyzer, $stmt, $by_ref_type, $by_ref_type, $context);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -644,6 +644,7 @@ class ExpressionAnalyzer
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr $stmt,
|
||||
Type\Union $by_ref_type,
|
||||
Type\Union $by_ref_out_type,
|
||||
Context $context,
|
||||
bool $constrain_type = true,
|
||||
bool $prevent_null = false
|
||||
@ -712,7 +713,7 @@ class ExpressionAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$var_id] = $by_ref_type;
|
||||
$context->vars_in_scope[$var_id] = $by_ref_out_type;
|
||||
|
||||
if (!isset($stmt->inferredType) || $stmt->inferredType->isEmpty()) {
|
||||
$stmt->inferredType = clone $by_ref_type;
|
||||
@ -1294,7 +1295,7 @@ class ExpressionAnalyzer
|
||||
if ($source instanceof FunctionLikeAnalyzer
|
||||
&& !($source->getSource() instanceof TraitAnalyzer)
|
||||
) {
|
||||
$source->addPossibleParamTypes($context, $codebase);
|
||||
$source->examineParamTypes($statements_analyzer, $context, $codebase);
|
||||
|
||||
$storage = $source->getFunctionLikeStorage($statements_analyzer);
|
||||
|
||||
|
@ -124,7 +124,7 @@ class ReturnAnalyzer
|
||||
$context
|
||||
);
|
||||
|
||||
$source->addPossibleParamTypes($context, $codebase);
|
||||
$source->examineParamTypes($statements_analyzer, $context, $codebase);
|
||||
|
||||
$storage = $source->getFunctionLikeStorage($statements_analyzer);
|
||||
|
||||
|
@ -1529,8 +1529,6 @@ class TypeAnalyzer
|
||||
* @param bool &$all_types_contain
|
||||
*
|
||||
* @return null|false
|
||||
*
|
||||
* @psalm-suppress ConflictingReferenceConstraint
|
||||
*/
|
||||
private static function compareCallable(
|
||||
Codebase $codebase,
|
||||
|
@ -827,22 +827,6 @@ class AnnotationTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'paramOutChangeType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param-out string $s
|
||||
*/
|
||||
function addFoo(?string &$s) : void {
|
||||
if ($s === null) {
|
||||
$s = "hello";
|
||||
}
|
||||
$s .= "foo";
|
||||
}
|
||||
|
||||
addFoo($a);
|
||||
|
||||
echo strlen($a);',
|
||||
],
|
||||
'annotationOnForeachItems' => [
|
||||
'<?php
|
||||
function foo(array $arr) : void {
|
||||
|
@ -82,6 +82,40 @@ class ReferenceConstraintTest extends TestCase
|
||||
'MixedOperand',
|
||||
],
|
||||
],
|
||||
'paramOutRefineType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param-out string $s
|
||||
*/
|
||||
function addFoo(?string &$s) : void {
|
||||
if ($s === null) {
|
||||
$s = "hello";
|
||||
}
|
||||
$s .= "foo";
|
||||
}
|
||||
|
||||
addFoo($a);
|
||||
|
||||
echo strlen($a);',
|
||||
],
|
||||
'paramOutChangeType' => [
|
||||
'<?php
|
||||
/**
|
||||
* @param-out int $s
|
||||
*/
|
||||
function addFoo(?string &$s) : void {
|
||||
if ($s === null) {
|
||||
$s = 5;
|
||||
return;
|
||||
}
|
||||
$s = 4;
|
||||
}
|
||||
|
||||
addFoo($a);',
|
||||
'assertions' => [
|
||||
'$a' => 'int',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user