Skip to content

Commit 395f3f7

Browse files
authored
Merge pull request #10690 from weirdan/9649-this-out-on-constructors
2 parents 13ebb91 + def0489 commit 395f3f7

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
1212
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
1313
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
14+
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodCallReturnTypeFetcher;
1415
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodVisibilityAnalyzer;
1516
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
1617
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
@@ -21,6 +22,7 @@
2122
use Psalm\Internal\MethodIdentifier;
2223
use Psalm\Internal\Type\TemplateResult;
2324
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
25+
use Psalm\Internal\Type\TypeExpander;
2426
use Psalm\Issue\AbstractInstantiation;
2527
use Psalm\Issue\DeprecatedClass;
2628
use Psalm\Issue\ImpureMethodCall;
@@ -58,6 +60,7 @@
5860

5961
use function array_map;
6062
use function array_values;
63+
use function count;
6164
use function in_array;
6265
use function md5;
6366
use function preg_match;
@@ -429,6 +432,8 @@ private static function analyzeNamedConstructor(
429432

430433
$declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id);
431434

435+
$method_storage = null;
436+
432437
if ($declaring_method_id) {
433438
$method_storage = $codebase->methods->getStorage($declaring_method_id);
434439

@@ -500,6 +505,7 @@ private static function analyzeNamedConstructor(
500505
}
501506

502507
$generic_param_types = null;
508+
$self_out_candidate = null;
503509

504510
if ($storage->template_types) {
505511
foreach ($storage->template_types as $template_name => $base_type) {
@@ -537,9 +543,49 @@ private static function analyzeNamedConstructor(
537543
'had_template' => true,
538544
]);
539545
}
546+
547+
if ($method_storage && $method_storage->self_out_type) {
548+
$self_out_candidate = $method_storage->self_out_type;
549+
550+
if ($template_result->lower_bounds) {
551+
$self_out_candidate = TypeExpander::expandUnion(
552+
$codebase,
553+
$self_out_candidate,
554+
$fq_class_name,
555+
null,
556+
$storage->parent_class,
557+
true,
558+
false,
559+
false,
560+
true,
561+
);
562+
}
563+
564+
$self_out_candidate = MethodCallReturnTypeFetcher::replaceTemplateTypes(
565+
$self_out_candidate,
566+
$template_result,
567+
$method_id,
568+
count($stmt->getArgs()),
569+
$codebase,
570+
);
571+
572+
$self_out_candidate = TypeExpander::expandUnion(
573+
$codebase,
574+
$self_out_candidate,
575+
$fq_class_name,
576+
$fq_class_name,
577+
$storage->parent_class,
578+
true,
579+
false,
580+
false,
581+
true,
582+
);
583+
$statements_analyzer->node_data->setType($stmt, $self_out_candidate);
584+
}
540585
}
541586

542-
if ($generic_param_types) {
587+
// XXX: what if we need both?
588+
if ($generic_param_types && !$self_out_candidate) {
543589
$result_atomic_type = new TGenericObject(
544590
$fq_class_name,
545591
$generic_param_types,

tests/ThisOutTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ public function getData(): array { return $this->data; }
8484
'$data3===' => 'list<2|3>',
8585
],
8686
],
87+
'provideDefaultTypeToTypeArguments' => [
88+
'code' => <<<'PHP'
89+
<?php
90+
/** @template T of 'idle'|'running' */
91+
class App {
92+
/** @psalm-this-out self<'idle'> */
93+
public function __construct() {}
94+
95+
/**
96+
* @psalm-if-this-is self<'idle'>
97+
* @psalm-this-out self<'running'>
98+
*/
99+
public function start(): void {}
100+
}
101+
$app = new App();
102+
PHP,
103+
'assertions' => [
104+
'$app===' => "App<'idle'>",
105+
],
106+
],
87107
];
88108
}
89109
}

0 commit comments

Comments
 (0)