Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 17 additions & 78 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeFinder;
use PHPStan\Analyser\Traverser\CloneTypeTraverser;
use PHPStan\Analyser\Traverser\ConstructorClassTemplateTraverser;
use PHPStan\Analyser\Traverser\GenericTypeTemplateTraverser;
use PHPStan\Analyser\Traverser\InstanceOfClassTypeTraverser;
use PHPStan\Analyser\Traverser\TransformStaticTypeTraverser;
use PHPStan\Analyser\Traverser\VoidToNullTraverser;
use PHPStan\Node\ExecutionEndNode;
use PHPStan\Node\Expr\AlwaysRememberedExpr;
use PHPStan\Node\Expr\ExistingArrayDimFetch;
Expand Down Expand Up @@ -107,7 +113,6 @@
use PHPStan\Type\ExpressionTypeResolverExtensionRegistry;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\GenericStaticType;
use PHPStan\Type\Generic\TemplateType;
Expand Down Expand Up @@ -1255,17 +1260,7 @@ private function resolveType(string $exprString, Expr $node): Type

if ($node instanceof Expr\Clone_) {
$cloneType = TypeCombinator::intersect($this->getType($node->expr), new ObjectWithoutClassType());

return TypeTraverser::map($cloneType, static function (Type $type, callable $traverse): Type {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}
if ($type instanceof ThisType) {
return new StaticType($type->getClassReflection(), $type->getSubtractedType());
}

return $type;
});
return TypeTraverser::map($cloneType, new CloneTypeTraverser());
}

if ($node instanceof Node\Scalar\Int_) {
Expand Down Expand Up @@ -1618,17 +1613,7 @@ private function transformVoidToNull(Type $type, Node $node): Type
return $type;
}

return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}

if ($type->isVoid()->yes()) {
return new NullType();
}

return $type;
});
return TypeTraverser::map($type, new VoidToNullTraverser());
}

/**
Expand Down Expand Up @@ -2298,21 +2283,7 @@ public function enterPropertyHook(

private function transformStaticType(Type $type): Type
{
return TypeTraverser::map($type, function (Type $type, callable $traverse): Type {
if (!$this->isInClass()) {
return $type;
}
if ($type instanceof StaticType) {
$classReflection = $this->getClassReflection();
$changedType = $type->changeBaseClass($classReflection);
if ($classReflection->isFinal() && !$type instanceof ThisType) {
$changedType = $changedType->getStaticObjectType();
}
return $traverse($changedType);
}

return $traverse($type);
});
return TypeTraverser::map($type, new TransformStaticTypeTraverser($this));
}

/**
Expand Down Expand Up @@ -5074,19 +5045,12 @@ private function exactInstantiation(New_ $node, Name $className): Type
$constructorVariant = $constructorVariants[0];
$classTemplateTypes = $classReflection->getTemplateTypeMap()->getTypes();
$originalClassTemplateTypes = $classTemplateTypes;
foreach ($constructorVariant->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), static function (Type $type, callable $traverse) use (&$classTemplateTypes): Type {
if ($type instanceof TemplateType && array_key_exists($type->getName(), $classTemplateTypes)) {
$classTemplateType = $classTemplateTypes[$type->getName()];
if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) {
unset($classTemplateTypes[$type->getName()]);
}
return $type;
}

return $traverse($type);
});
$traverser = new ConstructorClassTemplateTraverser($classTemplateTypes);
foreach ($constructorVariant->getParameters() as $parameter) {
TypeTraverser::map($parameter->getType(), $traverser);
}
$classTemplateTypes = $traverser->getClassTemplateTypes();

if (count($classTemplateTypes) === count($originalClassTemplateTypes)) {
$propertyType = TypeCombinator::removeNull($this->getType($assignedToProperty));
Expand Down Expand Up @@ -5255,18 +5219,7 @@ classReflection: $classReflection->withTypes($types)->asFinal(),
[],
);
}
return TypeTraverser::map($newGenericType, static function (Type $type, callable $traverse) use ($resolvedTemplateTypeMap): Type {
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
return $type->getDefault() ?? $type->getBound();
}

return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType);
}

return $traverse($type);
});
return TypeTraverser::map($newGenericType, new GenericTypeTemplateTraverser($resolvedTemplateTypeMap));
}

private function filterTypeWithMethod(Type $typeWithMethod, string $methodName): ?Type
Expand Down Expand Up @@ -5588,23 +5541,9 @@ private function getInstanceOfType(Expr\Instanceof_ $node): Type
}
} else {
$classType = $this->getType($node->class);
$classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}
if ($type->getObjectClassNames() !== []) {
$uncertainty = true;
return $type;
}
if ($type instanceof GenericClassStringType) {
$uncertainty = true;
return $type->getGenericType();
}
if ($type instanceof ConstantStringType) {
return new ObjectType($type->getValue());
}
return new MixedType();
});
$traverser = new InstanceOfClassTypeTraverser();
$classType = TypeTraverser::map($classType, $traverser);
$uncertainty = $traverser->getUncertainty();
}

if ($classType->isSuperTypeOf(new MixedType())->yes()) {
Expand Down
30 changes: 30 additions & 0 deletions src/Analyser/Traverser/CloneTypeTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Type\IntersectionType;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;
use PHPStan\Type\UnionType;

final class CloneTypeTraverser implements TypeTraverserCallable
{

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}
if ($type instanceof ThisType) {
return new StaticType($type->getClassReflection(), $type->getSubtractedType());
}

return $type;
}

}
46 changes: 46 additions & 0 deletions src/Analyser/Traverser/ConstructorClassTemplateTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;
use function array_key_exists;

final class ConstructorClassTemplateTraverser implements TypeTraverserCallable
{

/**
* @param array<string, Type> $classTemplateTypes
*/
public function __construct(
private array $classTemplateTypes,
)
{
}

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if ($type instanceof TemplateType && array_key_exists($type->getName(), $this->classTemplateTypes)) {
$classTemplateType = $this->classTemplateTypes[$type->getName()];
if ($classTemplateType instanceof TemplateType && $classTemplateType->getScope()->equals($type->getScope())) {
unset($this->classTemplateTypes[$type->getName()]);
}
return $type;
}

return $traverse($type);
}

/**
* @return array<string, Type>
*/
public function getClassTemplateTypes(): array
{
return $this->classTemplateTypes;
}

}
38 changes: 38 additions & 0 deletions src/Analyser/Traverser/GenericTypeTemplateTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;

final class GenericTypeTemplateTraverser implements TypeTraverserCallable
{

public function __construct(
private readonly TemplateTypeMap $resolvedTemplateTypeMap,
)
{
}

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $this->resolvedTemplateTypeMap->getType($type->getName());
if ($newType === null || $newType instanceof ErrorType) {
return $type->getDefault() ?? $type->getBound();
}

return TemplateTypeHelper::generalizeInferredTemplateType($type, $newType);
}

return $traverse($type);
}

}
47 changes: 47 additions & 0 deletions src/Analyser/Traverser/InstanceOfClassTypeTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;
use PHPStan\Type\UnionType;

final class InstanceOfClassTypeTraverser implements TypeTraverserCallable
{

private bool $uncertainty = false;

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}

if ($type->getObjectClassNames() !== []) {
$this->uncertainty = true;
return $type;
}
if ($type instanceof GenericClassStringType) {
$this->uncertainty = true;
return $type->getGenericType();
}
if ($type instanceof ConstantStringType) {
return new ObjectType($type->getValue());
}
return new MixedType();
}

public function getUncertainty(): bool
{
return $this->uncertainty;
}

}
40 changes: 40 additions & 0 deletions src/Analyser/Traverser/TransformStaticTypeTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Analyser\Scope;
use PHPStan\Type\StaticType;
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;

final class TransformStaticTypeTraverser implements TypeTraverserCallable
{

public function __construct(
private readonly Scope $scope,
)
{
}

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if (!$this->scope->isInClass()) {
return $type;
}
if ($type instanceof StaticType) {
$classReflection = $this->scope->getClassReflection();
$changedType = $type->changeBaseClass($classReflection);
if ($classReflection->isFinal() && !$type instanceof ThisType) {
$changedType = $changedType->getStaticObjectType();
}
return $traverse($changedType);
}

return $traverse($type);
}

}
30 changes: 30 additions & 0 deletions src/Analyser/Traverser/VoidToNullTraverser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\Traverser;

use PHPStan\Type\IntersectionType;
use PHPStan\Type\NullType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverserCallable;
use PHPStan\Type\UnionType;

final class VoidToNullTraverser implements TypeTraverserCallable
{

/**
* @param callable(Type): Type $traverse
*/
public function traverse(Type $type, callable $traverse): Type
{
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}

if ($type->isVoid()->yes()) {

Check warning on line 23 in src/Analyser/Traverser/VoidToNullTraverser.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.4, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return $traverse($type); } - if ($type->isVoid()->yes()) { + if (!$type->isVoid()->no()) { return new NullType(); }

Check warning on line 23 in src/Analyser/Traverser/VoidToNullTraverser.php

View workflow job for this annotation

GitHub Actions / Mutation Testing (8.3, ubuntu-latest)

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ return $traverse($type); } - if ($type->isVoid()->yes()) { + if (!$type->isVoid()->no()) { return new NullType(); }
return new NullType();
}

return $type;
}

}
Loading
Loading