mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
parent
1ae9ea5fed
commit
2c6854f403
@ -2298,6 +2298,7 @@ class CallAnalyzer
|
||||
self::coerceValueAfterGatekeeperArgument(
|
||||
$statements_analyzer,
|
||||
$input_type,
|
||||
false,
|
||||
$input_expr,
|
||||
$param_type,
|
||||
$signature_param_type,
|
||||
@ -2349,6 +2350,13 @@ class CallAnalyzer
|
||||
$union_comparison_results
|
||||
);
|
||||
|
||||
$replace_input_type = false;
|
||||
|
||||
if ($union_comparison_results->replacement_union_type) {
|
||||
$replace_input_type = true;
|
||||
$input_type = $union_comparison_results->replacement_union_type;
|
||||
}
|
||||
|
||||
if ($type_match_found
|
||||
&& $param_type->hasCallableType()
|
||||
) {
|
||||
@ -2671,6 +2679,7 @@ class CallAnalyzer
|
||||
self::coerceValueAfterGatekeeperArgument(
|
||||
$statements_analyzer,
|
||||
$input_type,
|
||||
$replace_input_type,
|
||||
$input_expr,
|
||||
$param_type,
|
||||
$signature_param_type,
|
||||
@ -2685,6 +2694,7 @@ class CallAnalyzer
|
||||
private static function coerceValueAfterGatekeeperArgument(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
Type\Union $input_type,
|
||||
bool $input_type_changed,
|
||||
PhpParser\Node\Expr $input_expr,
|
||||
Type\Union $param_type,
|
||||
?Type\Union $signature_param_type,
|
||||
@ -2695,9 +2705,7 @@ class CallAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
if ($param_type->from_docblock && !$input_type->hasMixed()) {
|
||||
$input_type_changed = false;
|
||||
|
||||
if (!$input_type_changed && $param_type->from_docblock && !$input_type->hasMixed()) {
|
||||
$input_type = clone $input_type;
|
||||
|
||||
foreach ($param_type->getTypes() as $param_atomic_type) {
|
||||
@ -2707,7 +2715,9 @@ class CallAnalyzer
|
||||
&& $input_atomic_type->value === $param_atomic_type->value
|
||||
) {
|
||||
foreach ($input_atomic_type->type_params as $i => $type_param) {
|
||||
if ($type_param->isEmpty() && isset($param_atomic_type->type_params[$i])) {
|
||||
if (($type_param->isEmpty() || $type_param->had_template)
|
||||
&& isset($param_atomic_type->type_params[$i])
|
||||
) {
|
||||
$input_type_changed = true;
|
||||
|
||||
$input_atomic_type->type_params[$i] = clone $param_atomic_type->type_params[$i];
|
||||
|
@ -152,6 +152,21 @@ class TypeAnalyzer
|
||||
$union_comparison_result->type_coerced_from_scalar
|
||||
= $atomic_comparison_result->type_coerced_from_scalar;
|
||||
}
|
||||
|
||||
if ($is_atomic_contained_by
|
||||
&& $union_comparison_result
|
||||
&& $atomic_comparison_result->replacement_atomic_type
|
||||
) {
|
||||
if (!$union_comparison_result->replacement_union_type) {
|
||||
$union_comparison_result->replacement_union_type = clone $input_type;
|
||||
}
|
||||
|
||||
$union_comparison_result->replacement_union_type->removeType($input_type->getKey());
|
||||
|
||||
$union_comparison_result->replacement_union_type->addType(
|
||||
$atomic_comparison_result->replacement_atomic_type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($input_type_part instanceof TNumeric
|
||||
@ -1759,34 +1774,43 @@ class TypeAnalyzer
|
||||
)) {
|
||||
$all_types_contain = false;
|
||||
} elseif (!$input_type_part instanceof TIterable
|
||||
&& !$container_param->had_template
|
||||
&& !$input_param->had_template
|
||||
&& !$container_param->hasTemplate()
|
||||
&& !$input_param->hasTemplate()
|
||||
&& !$input_param->hasLiteralValue()
|
||||
&& !$input_param->hasEmptyArray()
|
||||
) {
|
||||
$input_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
|
||||
if ($input_param->had_template) {
|
||||
if (!$atomic_comparison_result->replacement_atomic_type) {
|
||||
$atomic_comparison_result->replacement_atomic_type = clone $input_type_part;
|
||||
}
|
||||
|
||||
if (!($input_storage->template_covariants[$i] ?? false)) {
|
||||
// Make sure types are basically the same
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$container_param,
|
||||
$input_param,
|
||||
$container_param->ignore_nullable_issues,
|
||||
$container_param->ignore_falsable_issues,
|
||||
$atomic_comparison_result,
|
||||
$allow_interface_equality
|
||||
) || $atomic_comparison_result->type_coerced
|
||||
) {
|
||||
if ($container_param->hasMixed() || $container_param->isArrayKey()) {
|
||||
$atomic_comparison_result->type_coerced_from_mixed = true;
|
||||
} else {
|
||||
$all_types_contain = false;
|
||||
if ($atomic_comparison_result->replacement_atomic_type instanceof TGenericObject) {
|
||||
$atomic_comparison_result->replacement_atomic_type->type_params[$i]
|
||||
= clone $container_param;
|
||||
}
|
||||
} else {
|
||||
$input_storage = $codebase->classlike_storage_provider->get($input_type_part->value);
|
||||
|
||||
if (!($input_storage->template_covariants[$i] ?? false)) {
|
||||
// Make sure types are basically the same
|
||||
if (!self::isContainedBy(
|
||||
$codebase,
|
||||
$container_param,
|
||||
$input_param,
|
||||
$container_param->ignore_nullable_issues,
|
||||
$container_param->ignore_falsable_issues,
|
||||
$atomic_comparison_result,
|
||||
$allow_interface_equality
|
||||
) || $atomic_comparison_result->type_coerced
|
||||
) {
|
||||
if ($container_param->hasMixed() || $container_param->isArrayKey()) {
|
||||
$atomic_comparison_result->type_coerced_from_mixed = true;
|
||||
} else {
|
||||
$all_types_contain = false;
|
||||
}
|
||||
|
||||
$atomic_comparison_result->type_coerced = false;
|
||||
}
|
||||
|
||||
$atomic_comparison_result->type_coerced = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,10 @@ class TypeComparisonResult
|
||||
|
||||
/** @var ?bool */
|
||||
public $type_coerced_from_scalar = null;
|
||||
|
||||
/** @var ?\Psalm\Type\Union */
|
||||
public $replacement_union_type = null;
|
||||
|
||||
/** @var ?\Psalm\Type\Atomic */
|
||||
public $replacement_atomic_type = null;
|
||||
}
|
||||
|
@ -1999,6 +1999,36 @@ class ClassTemplateTest extends TestCase
|
||||
$mario->ame = "Luigi";',
|
||||
'error_message' => 'InvalidArgument - src' . DIRECTORY_SEPARATOR . 'somefile.php:47:29 - Argument 1 of CharacterRow::__set expects string(id)|string(name)|string(height), string(ame) provided',
|
||||
],
|
||||
'specialiseTypeBeforeReturning' => [
|
||||
'<?php
|
||||
class Base {}
|
||||
class Derived extends Base {}
|
||||
|
||||
/**
|
||||
* @template T of Base
|
||||
*/
|
||||
class Foo {
|
||||
/**
|
||||
* @param T $t
|
||||
*/
|
||||
public function __construct ($t) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Foo<Base>
|
||||
*/
|
||||
function returnFooBase() {
|
||||
$f = new Foo(new Derived());
|
||||
takesFooDerived($f);
|
||||
return $f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Foo<Derived> $foo
|
||||
*/
|
||||
function takesFooDerived($foo): void {}',
|
||||
'error_message' => 'InvalidReturnStatement'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user