Skip to content

Commit db656b0

Browse files
committed
Fixed __toString() return type combining with phpDoc type
1 parent d4f0907 commit db656b0

File tree

5 files changed

+112
-34
lines changed

5 files changed

+112
-34
lines changed

src/Reflection/Php/PhpMethodFromParserNodeReflection.php

+28-28
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,34 @@ public function __construct(
5454
bool $isFinal
5555
)
5656
{
57+
$name = strtolower($classMethod->name->name);
58+
if (
59+
$name === '__construct'
60+
|| $name === '__destruct'
61+
|| $name === '__unset'
62+
|| $name === '__wakeup'
63+
|| $name === '__clone'
64+
) {
65+
$realReturnTypePresent = true;
66+
$realReturnType = new VoidType();
67+
}
68+
if ($name === '__tostring') {
69+
$realReturnTypePresent = true;
70+
$realReturnType = new StringType();
71+
}
72+
if ($name === '__isset') {
73+
$realReturnTypePresent = true;
74+
$realReturnType = new BooleanType();
75+
}
76+
if ($name === '__sleep') {
77+
$realReturnTypePresent = true;
78+
$realReturnType = new ArrayType(new IntegerType(), new StringType());
79+
}
80+
if ($name === '__set_state') {
81+
$realReturnTypePresent = true;
82+
$realReturnType = new ObjectWithoutClassType();
83+
}
84+
5785
parent::__construct(
5886
$classMethod,
5987
$templateTypeMap,
@@ -108,34 +136,6 @@ public function isPublic(): bool
108136
return $this->getClassMethod()->isPublic();
109137
}
110138

111-
protected function getReturnType(): Type
112-
{
113-
$name = strtolower($this->getName());
114-
if (
115-
$name === '__construct'
116-
|| $name === '__destruct'
117-
|| $name === '__unset'
118-
|| $name === '__wakeup'
119-
|| $name === '__clone'
120-
) {
121-
return new VoidType();
122-
}
123-
if ($name === '__tostring') {
124-
return new StringType();
125-
}
126-
if ($name === '__isset') {
127-
return new BooleanType();
128-
}
129-
if ($name === '__sleep') {
130-
return new ArrayType(new IntegerType(), new StringType());
131-
}
132-
if ($name === '__set_state') {
133-
return new ObjectWithoutClassType();
134-
}
135-
136-
return parent::getReturnType();
137-
}
138-
139139
public function getDocComment(): ?string
140140
{
141141
return null;

src/Reflection/Php/PhpMethodReflection.php

+5-5
Original file line numberDiff line numberDiff line change
@@ -377,19 +377,19 @@ private function getReturnType(): Type
377377
|| $name === '__wakeup'
378378
|| $name === '__clone'
379379
) {
380-
return $this->returnType = new VoidType();
380+
return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType);
381381
}
382382
if ($name === '__tostring') {
383-
return $this->returnType = new StringType();
383+
return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType);
384384
}
385385
if ($name === '__isset') {
386-
return $this->returnType = new BooleanType();
386+
return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType);
387387
}
388388
if ($name === '__sleep') {
389-
return $this->returnType = new ArrayType(new IntegerType(), new StringType());
389+
return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType);
390390
}
391391
if ($name === '__set_state') {
392-
return $this->returnType = new ObjectWithoutClassType();
392+
return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType);
393393
}
394394

395395
$this->returnType = TypehintHelper::decideTypeFromReflection(

src/Type/ObjectType.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public function toString(): Type
328328
}
329329

330330
if ($classReflection->hasNativeMethod('__toString')) {
331-
return new StringType();
331+
return ParametersAcceptorSelector::selectSingle($classReflection->getNativeMethod('__toString')->getVariants())->getReturnType();
332332
}
333333

334334
return new ErrorType();

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -9848,6 +9848,11 @@ public function dataConstExprPhpDocType(): array
98489848
return $this->gatherAssertTypes(__DIR__ . '/data/const-expr-phpdoc-type.php');
98499849
}
98509850

9851+
public function dataBug3226(): array
9852+
{
9853+
return $this->gatherAssertTypes(__DIR__ . '/data/bug-3226.php');
9854+
}
9855+
98519856
/**
98529857
* @dataProvider dataBug2574
98539858
* @dataProvider dataBug2577
@@ -9887,6 +9892,7 @@ public function dataConstExprPhpDocType(): array
98879892
* @dataProvider dataArrayShapeKeysStrings
98889893
* @dataProvider dataBug1216
98899894
* @dataProvider dataConstExprPhpDocType
9895+
* @dataProvider dataBug3226
98909896
* @param ConstantStringType $expectedType
98919897
* @param Type $actualType
98929898
*/
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Bug3226;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
/**
10+
* @var class-string
11+
*/
12+
private $class;
13+
14+
/**
15+
* @param class-string $class
16+
*/
17+
public function __construct(string $class)
18+
{
19+
$this->class = $class;
20+
}
21+
22+
/**
23+
* @return class-string
24+
*/
25+
public function __toString(): string
26+
{
27+
return $this->class;
28+
}
29+
}
30+
31+
function (Foo $foo): void {
32+
assertType('class-string', $foo->__toString());
33+
assertType('class-string', (string) $foo);
34+
};
35+
36+
/**
37+
* @template T
38+
*/
39+
class Bar
40+
{
41+
/**
42+
* @var class-string<T>
43+
*/
44+
private $class;
45+
46+
/**
47+
* @param class-string<T> $class
48+
*/
49+
public function __construct(string $class)
50+
{
51+
$this->class = $class;
52+
}
53+
54+
/**
55+
* @return class-string<T>
56+
*/
57+
public function __toString(): string
58+
{
59+
return $this->class;
60+
}
61+
}
62+
63+
function (Bar $bar): void {
64+
assertType('class-string<mixed>', $bar->__toString());
65+
assertType('class-string<mixed>', (string) $bar);
66+
};
67+
68+
function (): void {
69+
$bar = new Bar(\Exception::class);
70+
assertType('class-string<Exception>', $bar->__toString());
71+
assertType('class-string<Exception>', (string) $bar);
72+
};

0 commit comments

Comments
 (0)