Skip to content

Commit

Permalink
Fix GenericStaticType in @phpstan-self-out
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 11, 2025
1 parent b4f41c7 commit dab99cb
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 29 deletions.
10 changes: 8 additions & 2 deletions src/Reflection/Dummy/ChangedTypeMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ final class ChangedTypeMethodReflection implements ExtendedMethodReflection
* @param list<ExtendedParametersAcceptor> $variants
* @param list<ExtendedParametersAcceptor>|null $namedArgumentsVariants
*/
public function __construct(private ClassReflection $declaringClass, private ExtendedMethodReflection $reflection, private array $variants, private ?array $namedArgumentsVariants)
public function __construct(
private ClassReflection $declaringClass,
private ExtendedMethodReflection $reflection,
private array $variants,
private ?array $namedArgumentsVariants,
private ?Type $selfOutType,
)
{
}

Expand Down Expand Up @@ -126,7 +132,7 @@ public function acceptsNamedArguments(): TrinaryLogic

public function getSelfOutType(): ?Type
{
return $this->reflection->getSelfOutType();
return $this->selfOutType;
}

public function returnsByReference(): TrinaryLogic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass,
? array_map($variantFn, $namedArgumentVariants)
: null;

return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentVariants);
return new ChangedTypeMethodReflection(
$declaringClass,
$method,
$variants,
$namedArgumentVariants,
$method->getSelfOutType() !== null ? $this->transformStaticType($method->getSelfOutType()) : null,
);
}

private function transformStaticType(Type $type): Type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PHPStan\Reflection\ResolvedMethodReflection;
use PHPStan\Type\Generic\GenericStaticType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use function array_map;
Expand Down Expand Up @@ -79,39 +80,54 @@ 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 = $selfOutType;
} 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());
$namedArgumentsVariants = $method->getNamedArgumentsVariants();
$namedArgumentsVariants = $namedArgumentsVariants !== null
? array_map($variantFn, $namedArgumentsVariants)
: null;

return new ChangedTypeMethodReflection($declaringClass, $method, $variants, $namedArgumentsVariants);
return new ChangedTypeMethodReflection(
$declaringClass,
$method,
$variants,
$namedArgumentsVariants,
$selfOutType,
);
}

private function transformStaticType(Type $type): Type
Expand Down
61 changes: 61 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12575.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Bug12575;

use function PHPStan\Testing\assertType;

/**
* @template T of object
*/
class Foo
{

/**
* @template U of object
* @param class-string<U> $class
* @return $this
* @phpstan-self-out static<T&U>
*/
public function add(string $class)
{
return $this;
}

}

/**
* @template T of object
* @extends Foo<T>
*/
class Bar extends Foo
{

}

interface A
{

}

interface B
{

}

/**
* @param Bar<A> $bar
* @return void
*/
function doFoo(Bar $bar): void {
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar->add(B::class));
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
};

/**
* @param Bar<A> $bar
* @return void
*/
function doBar(Bar $bar): void {
$bar->add(B::class);
assertType('Bug12575\\Bar<Bug12575\\A&Bug12575\\B>', $bar);
};

0 comments on commit dab99cb

Please sign in to comment.