Skip to content

Commit

Permalink
Merge branch 'master' into bug-6505
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Feb 27, 2022
2 parents 80c2c47 + dd5633f commit 9e9234c
Show file tree
Hide file tree
Showing 19 changed files with 246 additions and 45 deletions.
8 changes: 3 additions & 5 deletions src/Analyser/MutatingScope.php
Expand Up @@ -1406,13 +1406,11 @@ private function resolveType(Expr $node): Type
return new ErrorType();
}

if (($leftType->isString()->yes() && !$leftType->isNumericString()->yes())
|| ($rightType->isString()->yes() && !$rightType->isNumericString()->yes())) {
return new ErrorType();
}

$leftNumberType = $leftType->toNumber();
$rightNumberType = $rightType->toNumber();
if ($leftNumberType instanceof ErrorType || $rightNumberType instanceof ErrorType) {
return new ErrorType();
}

if (
(new FloatType())->isSuperTypeOf($leftNumberType)->yes()
Expand Down
2 changes: 1 addition & 1 deletion src/PhpDoc/PhpDocNodeResolver.php
Expand Up @@ -279,7 +279,7 @@ public function resolveTemplateTags(PhpDocNode $phpDocNode, NameScope $nameScope

$resolved[$valueNode->name] = new TemplateTag(
$valueNode->name,
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(),
$valueNode->bound !== null ? $this->typeNodeResolver->resolve($valueNode->bound, $nameScope->unsetTemplateType($valueNode->name)) : new MixedType(true),
$variance,
);
$resolvedPrefix[$valueNode->name] = $prefix;
Expand Down
37 changes: 32 additions & 5 deletions src/Reflection/ClassReflection.php
Expand Up @@ -82,6 +82,8 @@ class ClassReflection

private ?TemplateTypeMap $templateTypeMap = null;

private ?TemplateTypeMap $activeTemplateTypeMap = null;

/** @var array<string,ClassReflection>|null */
private ?array $ancestors = null;

Expand Down Expand Up @@ -181,7 +183,8 @@ public function getParentClass(): ?ClassReflection
if ($this->isGeneric()) {
$extendedType = TemplateTypeHelper::resolveTemplateTypes(
$extendedType,
$this->getActiveTemplateTypeMap(),
$this->getPossiblyIncompleteActiveTemplateTypeMap(),
true,
);
}

Expand All @@ -195,7 +198,7 @@ public function getParentClass(): ?ClassReflection
$parentReflection = $this->reflectionProvider->getClass($parentClass->getName());
if ($parentReflection->isGeneric()) {
return $parentReflection->withTypes(
array_values($parentReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()),
array_values($parentReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
);
}

Expand Down Expand Up @@ -224,7 +227,7 @@ public function getDisplayName(bool $withTemplateTypes = true): string
return $name;
}

return $name . '<' . implode(',', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::typeOnly()), $this->resolvedTemplateTypeMap->getTypes())) . '>';
return $name . '<' . implode(',', array_map(static fn (Type $type): string => $type->describe(VerbosityLevel::typeOnly()), $this->getActiveTemplateTypeMap()->getTypes())) . '>';
}

public function getCacheKey(): string
Expand Down Expand Up @@ -728,7 +731,8 @@ public function getImmediateInterfaces(): array
if ($this->isGeneric()) {
$implementedType = TemplateTypeHelper::resolveTemplateTypes(
$implementedType,
$this->getActiveTemplateTypeMap(),
$this->getPossiblyIncompleteActiveTemplateTypeMap(),
true,
);
}

Expand All @@ -743,7 +747,7 @@ public function getImmediateInterfaces(): array

if ($immediateInterface->isGeneric()) {
$immediateInterfaces[$immediateInterface->getName()] = $immediateInterface->withTypes(
array_values($immediateInterface->getTemplateTypeMap()->resolveToBounds()->getTypes()),
array_values($immediateInterface->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()),
);
continue;
}
Expand Down Expand Up @@ -1057,6 +1061,29 @@ public function getTemplateTypeMap(): TemplateTypeMap
}

public function getActiveTemplateTypeMap(): TemplateTypeMap
{
if ($this->activeTemplateTypeMap !== null) {
return $this->activeTemplateTypeMap;
}
$resolved = $this->resolvedTemplateTypeMap;
if ($resolved !== null) {
$templateTypeMap = $this->getTemplateTypeMap();
return $this->activeTemplateTypeMap = $resolved->map(static function (string $name, Type $type) use ($templateTypeMap): Type {
if ($type instanceof ErrorType) {
$templateType = $templateTypeMap->getType($name);
if ($templateType !== null) {
return TemplateTypeHelper::resolveToBounds($templateType);
}
}

return $type;
});
}

return $this->activeTemplateTypeMap = $this->getTemplateTypeMap();
}

public function getPossiblyIncompleteActiveTemplateTypeMap(): TemplateTypeMap
{
return $this->resolvedTemplateTypeMap ?? $this->getTemplateTypeMap();
}
Expand Down
5 changes: 1 addition & 4 deletions src/Type/Accessory/AccessoryLiteralStringType.php
Expand Up @@ -130,10 +130,7 @@ public function isArray(): TrinaryLogic

public function toNumber(): Type
{
return new UnionType([
$this->toInteger(),
$this->toFloat(),
]);
return new ErrorType();
}

public function toInteger(): Type
Expand Down
5 changes: 1 addition & 4 deletions src/Type/Accessory/AccessoryNonEmptyStringType.php
Expand Up @@ -131,10 +131,7 @@ public function isArray(): TrinaryLogic

public function toNumber(): Type
{
return new UnionType([
$this->toInteger(),
$this->toFloat(),
]);
return new ErrorType();
}

public function toInteger(): Type
Expand Down
2 changes: 1 addition & 1 deletion src/Type/FileTypeMapper.php
Expand Up @@ -180,7 +180,7 @@ private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
private function getNameScopeMap(string $fileName): array
{
if (!isset($this->memoryCache[$fileName])) {
$cacheKey = sprintf('%s-phpdocstring-v20-template-tags', $fileName);
$cacheKey = sprintf('%s-phpdocstring-v21-explicit-mixed', $fileName);
$variableCacheKey = sprintf('%s-%s', implode(',', array_map(static fn (array $file): string => sprintf('%s-%d', $file['filename'], $file['modifiedTime']), $this->getCachedDependentFilesWithTimestamps($fileName))), $this->phpVersion->getVersionString());
$map = $this->cache->load($cacheKey, $variableCacheKey);

Expand Down
6 changes: 3 additions & 3 deletions src/Type/Generic/TemplateTypeHelper.php
Expand Up @@ -12,16 +12,16 @@ class TemplateTypeHelper
/**
* Replaces template types with standin types
*/
public static function resolveTemplateTypes(Type $type, TemplateTypeMap $standins): Type
public static function resolveTemplateTypes(Type $type, TemplateTypeMap $standins, bool $keepErrorTypes = false): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins): Type {
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($standins, $keepErrorTypes): Type {
if ($type instanceof TemplateType && !$type->isArgument()) {
$newType = $standins->getType($type->getName());
if ($newType === null) {
return $traverse($type);
}

if ($newType instanceof ErrorType) {
if ($newType instanceof ErrorType && !$keepErrorTypes) {
return $traverse($type->getBound());
}

Expand Down
15 changes: 6 additions & 9 deletions src/Type/Generic/TemplateTypeMap.php
Expand Up @@ -2,7 +2,6 @@

namespace PHPStan\Type\Generic;

use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
Expand All @@ -16,6 +15,8 @@ class TemplateTypeMap

private static ?TemplateTypeMap $empty = null;

private ?TemplateTypeMap $resolvedToBounds = null;

/**
* @api
* @param array<string, Type> $types
Expand Down Expand Up @@ -204,14 +205,10 @@ public function map(callable $cb): self

public function resolveToBounds(): self
{
return $this->map(static function (string $name, Type $type): Type {
$type = TemplateTypeHelper::resolveToBounds($type);
if ($type instanceof MixedType && $type->isExplicitMixed()) {
return new MixedType(false);
}

return $type;
});
if ($this->resolvedToBounds !== null) {
return $this->resolvedToBounds;
}
return $this->resolvedToBounds = $this->map(static fn (string $name, Type $type): Type => TemplateTypeHelper::resolveToBounds($type));
}

/**
Expand Down
34 changes: 27 additions & 7 deletions src/Type/GenericTypeVariableResolver.php
Expand Up @@ -2,6 +2,8 @@

namespace PHPStan\Type;

use PHPStan\Type\Generic\TemplateTypeHelper;

/** @api */
class GenericTypeVariableResolver
{
Expand All @@ -12,19 +14,37 @@ public static function getType(
string $typeVariableName,
): ?Type
{
$ancestor = $type->getAncestorWithClassName($genericClassName);
if ($ancestor === null) {
$classReflection = $type->getClassReflection();
if ($classReflection === null) {
return null;
}

$classReflection = $ancestor->getClassReflection();
if ($classReflection === null) {
$ancestorClassReflection = $classReflection->getAncestorWithClassName($genericClassName);
if ($ancestorClassReflection === null) {
return null;
}

$templateTypeMap = $classReflection->getActiveTemplateTypeMap();
$activeTemplateTypeMap = $ancestorClassReflection->getPossiblyIncompleteActiveTemplateTypeMap();

// todo if type is not defined, return the bound
// in case of mixed bound, return implicit mixed

$type = $activeTemplateTypeMap->getType($typeVariableName);
if ($type instanceof ErrorType) {
$templateTypeMap = $ancestorClassReflection->getTemplateTypeMap();
$templateType = $templateTypeMap->getType($typeVariableName);
if ($templateType === null) {
return $type;
}

$bound = TemplateTypeHelper::resolveToBounds($templateType);
if ($bound instanceof MixedType && $bound->isExplicitMixed()) {
return new MixedType(false);
}

return $bound;
}

return $templateTypeMap->getType($typeVariableName);
return $type;
}

}
2 changes: 1 addition & 1 deletion src/Type/ObjectType.php
Expand Up @@ -1110,7 +1110,7 @@ public function getClassReflection(): ?ClassReflection

$classReflection = $reflectionProvider->getClass($this->className);
if ($classReflection->isGeneric()) {
return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->resolveToBounds()->getTypes()));
return $classReflection->withTypes(array_values($classReflection->getTemplateTypeMap()->map(static fn (): Type => new ErrorType())->getTypes()));
}

return $classReflection;
Expand Down
16 changes: 12 additions & 4 deletions src/Type/UnionType.php
Expand Up @@ -481,12 +481,12 @@ public function isCloneable(): TrinaryLogic

public function isSmallerThan(Type $otherType): TrinaryLogic
{
return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType));
return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThan($otherType));
}

public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic
{
return $this->unionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType));
return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $type->isSmallerThanOrEqual($otherType));
}

public function getSmallerType(): Type
Expand All @@ -511,12 +511,12 @@ public function getGreaterOrEqualType(): Type

public function isGreaterThan(Type $otherType): TrinaryLogic
{
return $this->unionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type));
return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThan($type));
}

public function isGreaterThanOrEqual(Type $otherType): TrinaryLogic
{
return $this->unionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type));
return $this->notBenevolentUnionResults(static fn (Type $type): TrinaryLogic => $otherType->isSmallerThanOrEqual($type));
}

public function toBoolean(): BooleanType
Expand Down Expand Up @@ -671,6 +671,14 @@ protected function unionResults(callable $getResult): TrinaryLogic
return TrinaryLogic::extremeIdentity(...array_map($getResult, $this->types));
}

/**
* @param callable(Type $type): TrinaryLogic $getResult
*/
private function notBenevolentUnionResults(callable $getResult): TrinaryLogic
{
return TrinaryLogic::extremeIdentity(...array_map($getResult, $this->types));
}

/**
* @param callable(Type $type): Type $getType
*/
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -726,6 +726,8 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/countable.php');

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6696.php');
yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/filter-iterator-child-class.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/smaller-than-benevolent.php');
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-2676.php
Expand Up @@ -39,7 +39,7 @@ function (Wallet $wallet): void
assertType('DoctrineIntersectionTypeIsSupertypeOf\Collection&iterable<Bug2676\BankAccount>', $bankAccounts);

foreach ($bankAccounts as $key => $bankAccount) {
assertType('(int|string)', $key);
assertType('mixed', $key);
assertType('Bug2676\BankAccount', $bankAccount);
}
};
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/data/smaller-than-benevolent.php
@@ -0,0 +1,35 @@
<?php

namespace SmallerThanBenevolent;

use function PHPStan\Testing\assertType;

class Foo
{

/**
* @param int[] $a
* @return void
*/
public function doFoo(array $a)
{
uksort($a, function ($x, $y): int {
assertType('(int|string)', $x);
assertType('(int|string)', $y);
if ($x === 31 || $y === 31) {
return 1;
}

assertType('(int<min, 30>|int<32, max>|string)', $x);
assertType('(int<min, 30>|int<32, max>|string)', $y);

assertType('bool', $x < $y);
assertType('bool', $x <= $y);
assertType('bool', $x > $y);
assertType('bool', $x >= $y);

return $x < $y ? 1 : -1;
});
}

}
5 changes: 5 additions & 0 deletions tests/PHPStan/Rules/Generics/ClassAncestorsRuleTest.php
Expand Up @@ -95,6 +95,11 @@ public function testRuleExtends(): void
'Template type T is declared as covariant, but occurs in invariant position in extended type ClassAncestorsExtends\FooGeneric8<T, T> of class ClassAncestorsExtends\FooGeneric9.',
192,
],
[
'Class ClassAncestorsExtends\FilterIteratorChild extends generic class FilterIterator but does not specify its types: TKey, TValue, TIterator',
197,
'You can turn this off by setting <fg=cyan>checkGenericClassInNonGenericObjectType: false</> in your <fg=cyan>%configurationFile%</>.',
],
]);
}

Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Rules/Generics/data/class-ancestors-extends.php
Expand Up @@ -193,3 +193,13 @@ class FooGeneric9 extends FooGeneric8
{

}

class FilterIteratorChild extends \FilterIterator
{

public function accept()
{
return true;
}

}
Expand Up @@ -121,4 +121,9 @@ public function testBug6472(): void
$this->analyse([__DIR__ . '/data/bug-6472.php'], []);
}

public function testFilterIteratorChildClass(): void
{
$this->analyse([__DIR__ . '/data/filter-iterator-child-class.php'], []);
}

}

0 comments on commit 9e9234c

Please sign in to comment.