From 2c6854f4035d9c7e17d2c93cf71b1fa86710c42d Mon Sep 17 00:00:00 2001 From: Brown Date: Wed, 10 Jul 2019 12:12:51 -0400 Subject: [PATCH] Massage arg type after coerced param Ref #1927 --- .../Statements/Expression/CallAnalyzer.php | 18 +++-- src/Psalm/Internal/Analyzer/TypeAnalyzer.php | 66 +++++++++++++------ .../Analyzer/TypeComparisonResult.php | 6 ++ tests/Template/ClassTemplateTest.php | 30 +++++++++ 4 files changed, 95 insertions(+), 25 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index 512c0d96b..c1801b8a2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -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]; diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index e8b58908d..006171f32 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -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; } } } diff --git a/src/Psalm/Internal/Analyzer/TypeComparisonResult.php b/src/Psalm/Internal/Analyzer/TypeComparisonResult.php index 3dca2c04e..7615e8859 100644 --- a/src/Psalm/Internal/Analyzer/TypeComparisonResult.php +++ b/src/Psalm/Internal/Analyzer/TypeComparisonResult.php @@ -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; } diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index d0ea50767..650173832 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -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' => [ + ' + */ + function returnFooBase() { + $f = new Foo(new Derived()); + takesFooDerived($f); + return $f; + } + + /** + * @param Foo $foo + */ + function takesFooDerived($foo): void {}', + 'error_message' => 'InvalidReturnStatement' + ], ]; } }