From def0489b98f78e395b62912723a9c93d9aac8c24 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Sun, 11 Feb 2024 01:47:08 +0100 Subject: [PATCH] Process `@psalm-this-out` on `__construct()` as well Fixes vimeo/psalm#9649 --- .../Expression/Call/NewAnalyzer.php | 48 ++++++++++++++++++- tests/ThisOutTest.php | 20 ++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 1dbf40420..db767a303 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\ClassAnalyzer; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\FunctionLikeAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer; +use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodCallReturnTypeFetcher; use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodVisibilityAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; @@ -21,6 +22,7 @@ use Psalm\Internal\DataFlow\TaintSink; use Psalm\Internal\MethodIdentifier; use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\TemplateStandinTypeReplacer; +use Psalm\Internal\Type\TypeExpander; use Psalm\Issue\AbstractInstantiation; use Psalm\Issue\DeprecatedClass; use Psalm\Issue\ImpureMethodCall; @@ -58,6 +60,7 @@ use Psalm\Type\Union; use function array_map; use function array_values; +use function count; use function in_array; use function md5; use function preg_match; @@ -429,6 +432,8 @@ final class NewAnalyzer extends CallAnalyzer $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); + $method_storage = null; + if ($declaring_method_id) { $method_storage = $codebase->methods->getStorage($declaring_method_id); @@ -500,6 +505,7 @@ final class NewAnalyzer extends CallAnalyzer } $generic_param_types = null; + $self_out_candidate = null; if ($storage->template_types) { foreach ($storage->template_types as $template_name => $base_type) { @@ -537,9 +543,49 @@ final class NewAnalyzer extends CallAnalyzer 'had_template' => true, ]); } + + if ($method_storage && $method_storage->self_out_type) { + $self_out_candidate = $method_storage->self_out_type; + + if ($template_result->lower_bounds) { + $self_out_candidate = TypeExpander::expandUnion( + $codebase, + $self_out_candidate, + $fq_class_name, + null, + $storage->parent_class, + true, + false, + false, + true, + ); + } + + $self_out_candidate = MethodCallReturnTypeFetcher::replaceTemplateTypes( + $self_out_candidate, + $template_result, + $method_id, + count($stmt->getArgs()), + $codebase, + ); + + $self_out_candidate = TypeExpander::expandUnion( + $codebase, + $self_out_candidate, + $fq_class_name, + $fq_class_name, + $storage->parent_class, + true, + false, + false, + true, + ); + $statements_analyzer->node_data->setType($stmt, $self_out_candidate); + } } - if ($generic_param_types) { + // XXX: what if we need both? + if ($generic_param_types && !$self_out_candidate) { $result_atomic_type = new TGenericObject( $fq_class_name, $generic_param_types, diff --git a/tests/ThisOutTest.php b/tests/ThisOutTest.php index a537996ae..1deb83342 100644 --- a/tests/ThisOutTest.php +++ b/tests/ThisOutTest.php @@ -84,6 +84,26 @@ class ThisOutTest extends TestCase '$data3===' => 'list<2|3>', ], ], + 'provideDefaultTypeToTypeArguments' => [ + 'code' => <<<'PHP' + */ + public function __construct() {} + + /** + * @psalm-if-this-is self<'idle'> + * @psalm-this-out self<'running'> + */ + public function start(): void {} + } + $app = new App(); + PHP, + 'assertions' => [ + '$app===' => "App<'idle'>", + ], + ], ]; } }