Skip to content

Commit

Permalink
Fixed __toString() return type combining with phpDoc type
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 3, 2020
1 parent d4f0907 commit db656b0
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 34 deletions.
56 changes: 28 additions & 28 deletions src/Reflection/Php/PhpMethodFromParserNodeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,34 @@ public function __construct(
bool $isFinal
)
{
$name = strtolower($classMethod->name->name);
if (
$name === '__construct'
|| $name === '__destruct'
|| $name === '__unset'
|| $name === '__wakeup'
|| $name === '__clone'
) {
$realReturnTypePresent = true;
$realReturnType = new VoidType();
}
if ($name === '__tostring') {
$realReturnTypePresent = true;
$realReturnType = new StringType();
}
if ($name === '__isset') {
$realReturnTypePresent = true;
$realReturnType = new BooleanType();
}
if ($name === '__sleep') {
$realReturnTypePresent = true;
$realReturnType = new ArrayType(new IntegerType(), new StringType());
}
if ($name === '__set_state') {
$realReturnTypePresent = true;
$realReturnType = new ObjectWithoutClassType();
}

parent::__construct(
$classMethod,
$templateTypeMap,
Expand Down Expand Up @@ -108,34 +136,6 @@ public function isPublic(): bool
return $this->getClassMethod()->isPublic();
}

protected function getReturnType(): Type
{
$name = strtolower($this->getName());
if (
$name === '__construct'
|| $name === '__destruct'
|| $name === '__unset'
|| $name === '__wakeup'
|| $name === '__clone'
) {
return new VoidType();
}
if ($name === '__tostring') {
return new StringType();
}
if ($name === '__isset') {
return new BooleanType();
}
if ($name === '__sleep') {
return new ArrayType(new IntegerType(), new StringType());
}
if ($name === '__set_state') {
return new ObjectWithoutClassType();
}

return parent::getReturnType();
}

public function getDocComment(): ?string
{
return null;
Expand Down
10 changes: 5 additions & 5 deletions src/Reflection/Php/PhpMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,19 +377,19 @@ private function getReturnType(): Type
|| $name === '__wakeup'
|| $name === '__clone'
) {
return $this->returnType = new VoidType();
return $this->returnType = TypehintHelper::decideType(new VoidType(), $this->phpDocReturnType);
}
if ($name === '__tostring') {
return $this->returnType = new StringType();
return $this->returnType = TypehintHelper::decideType(new StringType(), $this->phpDocReturnType);
}
if ($name === '__isset') {
return $this->returnType = new BooleanType();
return $this->returnType = TypehintHelper::decideType(new BooleanType(), $this->phpDocReturnType);
}
if ($name === '__sleep') {
return $this->returnType = new ArrayType(new IntegerType(), new StringType());
return $this->returnType = TypehintHelper::decideType(new ArrayType(new IntegerType(), new StringType()), $this->phpDocReturnType);
}
if ($name === '__set_state') {
return $this->returnType = new ObjectWithoutClassType();
return $this->returnType = TypehintHelper::decideType(new ObjectWithoutClassType(), $this->phpDocReturnType);
}

$this->returnType = TypehintHelper::decideTypeFromReflection(
Expand Down
2 changes: 1 addition & 1 deletion src/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ public function toString(): Type
}

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

return new ErrorType();
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9848,6 +9848,11 @@ public function dataConstExprPhpDocType(): array
return $this->gatherAssertTypes(__DIR__ . '/data/const-expr-phpdoc-type.php');
}

public function dataBug3226(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-3226.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand Down Expand Up @@ -9887,6 +9892,7 @@ public function dataConstExprPhpDocType(): array
* @dataProvider dataArrayShapeKeysStrings
* @dataProvider dataBug1216
* @dataProvider dataConstExprPhpDocType
* @dataProvider dataBug3226
* @param ConstantStringType $expectedType
* @param Type $actualType
*/
Expand Down
72 changes: 72 additions & 0 deletions tests/PHPStan/Analyser/data/bug-3226.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Bug3226;

use function PHPStan\Analyser\assertType;

class Foo
{
/**
* @var class-string
*/
private $class;

/**
* @param class-string $class
*/
public function __construct(string $class)
{
$this->class = $class;
}

/**
* @return class-string
*/
public function __toString(): string
{
return $this->class;
}
}

function (Foo $foo): void {
assertType('class-string', $foo->__toString());
assertType('class-string', (string) $foo);
};

/**
* @template T
*/
class Bar
{
/**
* @var class-string<T>
*/
private $class;

/**
* @param class-string<T> $class
*/
public function __construct(string $class)
{
$this->class = $class;
}

/**
* @return class-string<T>
*/
public function __toString(): string
{
return $this->class;
}
}

function (Bar $bar): void {
assertType('class-string<mixed>', $bar->__toString());
assertType('class-string<mixed>', (string) $bar);
};

function (): void {
$bar = new Bar(\Exception::class);
assertType('class-string<Exception>', $bar->__toString());
assertType('class-string<Exception>', (string) $bar);
};

0 comments on commit db656b0

Please sign in to comment.