Skip to content

Commit

Permalink
Fix @phpstan-self-out with GenericStaticType when method is called …
Browse files Browse the repository at this point in the history
…on `$this`
  • Loading branch information
ondrejmirtes committed Feb 11, 2025
1 parent dab99cb commit cf64761
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 26 deletions.
64 changes: 38 additions & 26 deletions src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
use PHPStan\Reflection\ExtendedParametersAcceptor;
use PHPStan\Reflection\Php\ExtendedDummyParameter;
use PHPStan\Reflection\ResolvedMethodReflection;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use function array_map;

final class CallbackUnresolvedMethodPrototypeReflection implements UnresolvedMethodPrototypeReflection
Expand Down Expand Up @@ -82,32 +84,42 @@ public function withCalledOnType(Type $type): UnresolvedMethodPrototypeReflectio

private function transformMethodWithStaticType(ClassReflection $declaringClass, ExtendedMethodReflection $method): ExtendedMethodReflection
{
$variantFn = fn (ExtendedParametersAcceptor $acceptor): ExtendedParametersAcceptor => new ExtendedFunctionVariant(
$acceptor->getTemplateTypeMap(),
$acceptor->getResolvedTemplateTypeMap(),
array_map(
fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter(
$parameter->getName(),
$this->transformStaticType($parameter->getType()),
$parameter->isOptional(),
$parameter->passedByReference(),
$parameter->isVariadic(),
$parameter->getDefaultValue(),
$parameter->getNativeType(),
$this->transformStaticType($parameter->getPhpDocType()),
$parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null,
$parameter->isImmediatelyInvokedCallable(),
$parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null,
$parameter->getAttributes(),
$selfOutType = $method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null;
$variantFn = function (ExtendedParametersAcceptor $acceptor) use (&$selfOutType): ExtendedParametersAcceptor {
$originalReturnType = $acceptor->getReturnType();
if ($originalReturnType instanceof ThisType && $selfOutType !== null) {
$returnType = TypeCombinator::intersect($selfOutType, $this->transformStaticType($originalReturnType));
$selfOutType = $returnType;
} else {
$returnType = $this->transformStaticType($originalReturnType);
}
return new ExtendedFunctionVariant(
$acceptor->getTemplateTypeMap(),
$acceptor->getResolvedTemplateTypeMap(),
array_map(
fn (ExtendedParameterReflection $parameter): ExtendedParameterReflection => new ExtendedDummyParameter(
$parameter->getName(),
$this->transformStaticType($parameter->getType()),
$parameter->isOptional(),
$parameter->passedByReference(),
$parameter->isVariadic(),
$parameter->getDefaultValue(),
$parameter->getNativeType(),
$this->transformStaticType($parameter->getPhpDocType()),
$parameter->getOutType() !== null ? $this->transformStaticType($parameter->getOutType()) : null,
$parameter->isImmediatelyInvokedCallable(),
$parameter->getClosureThisType() !== null ? $this->transformStaticType($parameter->getClosureThisType()) : null,
$parameter->getAttributes(),
),
$acceptor->getParameters(),
),
$acceptor->getParameters(),
),
$acceptor->isVariadic(),
$this->transformStaticType($acceptor->getReturnType()),
$this->transformStaticType($acceptor->getPhpDocReturnType()),
$this->transformStaticType($acceptor->getNativeReturnType()),
$acceptor->getCallSiteVarianceMap(),
);
$acceptor->isVariadic(),
$returnType,
$this->transformStaticType($acceptor->getPhpDocReturnType()),
$this->transformStaticType($acceptor->getNativeReturnType()),
$acceptor->getCallSiteVarianceMap(),
);
};
$variants = array_map($variantFn, $method->getVariants());
$namedArgumentVariants = $method->getNamedArgumentsVariants();
$namedArgumentVariants = $namedArgumentVariants !== null
Expand All @@ -119,7 +131,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass,
$method,
$variants,
$namedArgumentVariants,
$method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null,
$selfOutType,
);
}

Expand Down
24 changes: 24 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12575.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ public function add(string $class)
class Bar extends Foo
{

public function doFoo(): void
{
assertType('$this(Bug12575\Bar<T of object (class Bug12575\Bar, argument)>)&static(Bug12575\Bar<Bug12575\A&T of object (class Bug12575\Bar, argument)>)', $this->add(A::class));
assertType('$this(Bug12575\Bar<T of object (class Bug12575\Bar, argument)>)&static(Bug12575\Bar<Bug12575\A&T of object (class Bug12575\Bar, argument)>)', $this);
assertType('T of object (class Bug12575\Bar, argument)', $this->getT());
}

public function doBar(): void
{
$this->add(B::class);
assertType('$this(Bug12575\Bar<T of object (class Bug12575\Bar, argument)>)&static(Bug12575\Bar<Bug12575\B&T of object (class Bug12575\Bar, argument)>)', $this);
assertType('T of object (class Bug12575\Bar, argument)', $this->getT());
}

/**
* @return T
*/
public function getT()
{

}

}

interface A
Expand All @@ -49,6 +71,7 @@ interface B
function doFoo(Bar $bar): void {
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar->add(B::class));
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
assertType('Bug12575\A&Bug12575\B', $bar->getT());
};

/**
Expand All @@ -58,4 +81,5 @@ function doFoo(Bar $bar): void {
function doBar(Bar $bar): void {
$bar->add(B::class);
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
assertType('Bug12575\A&Bug12575\B', $bar->getT());
};

0 comments on commit cf64761

Please sign in to comment.