Skip to content

Commit

Permalink
Fix inferring non-empty-string in array_map closure
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 14, 2021
1 parent 5fbfc14 commit b864a95
Show file tree
Hide file tree
Showing 27 changed files with 199 additions and 43 deletions.
7 changes: 4 additions & 3 deletions src/Analyser/MutatingScope.php
Expand Up @@ -63,6 +63,7 @@
use PHPStan\Type\DynamicReturnTypeExtensionRegistry;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
Expand Down Expand Up @@ -1934,7 +1935,7 @@ private function resolveType(Expr $node): Type
$constantType instanceof ConstantType
&& in_array(sprintf('%s::%s', $propertyClassReflection->getName(), $constantName), $this->dynamicConstantNames, true)
) {
$constantType = $constantType->generalize();
$constantType = $constantType->generalize(GeneralizePrecision::lessSpecific());
}
$types[] = $constantType;
}
Expand Down Expand Up @@ -2196,7 +2197,7 @@ private function getNullsafeShortCircuitingType(Expr $expr, Type $type): Type
private function resolveConstantType(string $constantName, Type $constantType): Type
{
if ($constantType instanceof ConstantType && in_array($constantName, $this->dynamicConstantNames, true)) {
return $constantType->generalize();
return $constantType->generalize(GeneralizePrecision::lessSpecific());
}

return $constantType;
Expand Down Expand Up @@ -4497,7 +4498,7 @@ private static function generalizeType(Type $a, Type $b): Type
continue;
}

$resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0]);
$resultTypes[] = TypeUtils::generalizeType($constantTypes['a'][0], GeneralizePrecision::moreSpecific());
}

if (count($constantArrays['a']) > 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Expand Up @@ -35,6 +35,7 @@
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\FileTypeMapper;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\TemplateTypeHelper;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\MixedType;
Expand Down Expand Up @@ -994,7 +995,7 @@ private function inferAndCachePropertyTypes(
continue;
}

$propertyType = TypeUtils::generalizeType($propertyType);
$propertyType = TypeUtils::generalizeType($propertyType, GeneralizePrecision::lessSpecific());
if ($propertyType instanceof ConstantArrayType) {
$propertyType = new ArrayType(new MixedType(true), new MixedType(true));
}
Expand Down
3 changes: 2 additions & 1 deletion src/Rules/Missing/MissingClosureNativeReturnTypehintRule.php
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Node\ClosureReturnStatementsNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
Expand Down Expand Up @@ -112,7 +113,7 @@ public function processNode(Node $node, Scope $scope): array
return $messages;
}

$returnType = TypeUtils::generalizeType($returnType);
$returnType = TypeUtils::generalizeType($returnType, GeneralizePrecision::lessSpecific());
$description = $returnType->describe(VerbosityLevel::typeOnly());
if ($returnType->isArray()->yes()) {
$description = 'array';
Expand Down
2 changes: 1 addition & 1 deletion src/Type/ArrayType.php
Expand Up @@ -147,7 +147,7 @@ function () use ($level, $isMixedKeyType, $isMixedItemType): string {

public function generalizeValues(): self
{
return new self($this->keyType, TypeUtils::generalizeType($this->itemType));
return new self($this->keyType, TypeUtils::generalizeType($this->itemType, GeneralizePrecision::lessSpecific()));
}

public function getKeysArray(): self
Expand Down
11 changes: 6 additions & 5 deletions src/Type/Constant/ConstantArrayType.php
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\CompoundType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\TemplateMixedType;
use PHPStan\Type\Generic\TemplateTypeMap;
Expand Down Expand Up @@ -498,7 +499,7 @@ public function unsetOffset(Type $offsetType): Type
$arrays[] = new self($tmp->keyTypes, $tmp->valueTypes, $tmp->nextAutoIndex, array_keys($tmp->keyTypes));
}

return TypeUtils::generalizeType(TypeCombinator::union(...$arrays));
return TypeUtils::generalizeType(TypeCombinator::union(...$arrays), GeneralizePrecision::moreSpecific());
}

public function isIterableAtLeastOnce(): TrinaryLogic
Expand Down Expand Up @@ -618,15 +619,15 @@ public function toFloat(): Type
return $this->toBoolean()->toFloat();
}

public function generalize(): Type
public function generalize(?GeneralizePrecision $precision = null): Type
{
if (count($this->keyTypes) === 0) {
return $this;
}

$arrayType = new ArrayType(
TypeUtils::generalizeType($this->getKeyType()),
TypeUtils::generalizeType($this->getItemType())
TypeUtils::generalizeType($this->getKeyType(), $precision),
TypeUtils::generalizeType($this->getItemType(), $precision)
);

if (count($this->keyTypes) > count($this->optionalKeys)) {
Expand All @@ -643,7 +644,7 @@ public function generalizeValues(): ArrayType
{
$valueTypes = [];
foreach ($this->valueTypes as $valueType) {
$valueTypes[] = TypeUtils::generalizeType($valueType);
$valueTypes[] = TypeUtils::generalizeType($valueType, GeneralizePrecision::lessSpecific());
}

return new self($this->keyTypes, $valueTypes, $this->nextAutoIndex, $this->optionalKeys);
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Constant/ConstantArrayTypeBuilder.php
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
Expand Down Expand Up @@ -100,7 +101,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt
return;
}

$this->keyTypes[] = TypeUtils::generalizeType($offsetType);
$this->keyTypes[] = TypeUtils::generalizeType($offsetType, GeneralizePrecision::moreSpecific());
$this->valueTypes[] = $valueType;
$this->degradeToGeneralArray = true;
}
Expand Down
13 changes: 12 additions & 1 deletion src/Type/Constant/ConstantStringType.php
Expand Up @@ -8,13 +8,16 @@
use PHPStan\Reflection\InaccessibleMethod;
use PHPStan\Reflection\TrivialParametersAcceptor;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\ClassStringType;
use PHPStan\Type\CompoundType;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectType;
Expand Down Expand Up @@ -292,11 +295,19 @@ public function append(self $otherString): self
return new self($this->getValue() . $otherString->getValue());
}

public function generalize(): Type
public function generalize(?GeneralizePrecision $precision = null): Type
{
if ($this->isClassString) {
return new ClassStringType();
}

if ($this->getValue() !== '' && $precision !== null && $precision->isMoreSpecific()) {
return new IntersectionType([
new StringType(),
new AccessoryNonEmptyStringType(),
]);
}

return new StringType();
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/ConstantType.php
Expand Up @@ -6,6 +6,6 @@
interface ConstantType extends Type
{

public function generalize(): Type;
public function generalize(?GeneralizePrecision $precision = null): Type;

}
44 changes: 44 additions & 0 deletions src/Type/GeneralizePrecision.php
@@ -0,0 +1,44 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

class GeneralizePrecision
{

private const LESS_SPECIFIC = 1;
private const MORE_SPECIFIC = 2;

/** @var self[] */
private static array $registry;

private int $value;

private function __construct(int $value)
{
$this->value = $value;
}

private static function create(int $value): self
{
self::$registry[$value] = self::$registry[$value] ?? new self($value);
return self::$registry[$value];
}

/** @api */
public static function lessSpecific(): self
{
return self::create(self::LESS_SPECIFIC);
}

/** @api */
public static function moreSpecific(): self
{
return self::create(self::MORE_SPECIFIC);
}

public function isMoreSpecific(): bool
{
return $this->value === self::MORE_SPECIFIC;
}

}
3 changes: 2 additions & 1 deletion src/Type/Generic/TemplateTypeHelper.php
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ConstantType;
use PHPStan\Type\ErrorType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;

Expand Down Expand Up @@ -66,7 +67,7 @@ public static function generalizeType(Type $type): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
if ($type instanceof ConstantType && !$type instanceof ConstantArrayType) {
return $type->generalize();
return $type->generalize(GeneralizePrecision::lessSpecific());
}

return $traverse($type);
Expand Down
2 changes: 1 addition & 1 deletion src/Type/IntegerRangeType.php
Expand Up @@ -269,7 +269,7 @@ public function equals(Type $type): bool
}


public function generalize(): Type
public function generalize(?GeneralizePrecision $precision = null): Type
{
return new parent();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/IntersectionType.php
Expand Up @@ -132,7 +132,7 @@ function () use ($level): string {
if ($type instanceof AccessoryType) {
continue;
}
$typeNames[] = TypeUtils::generalizeType($type)->describe($level);
$typeNames[] = TypeUtils::generalizeType($type, GeneralizePrecision::lessSpecific())->describe($level);
}

return implode('&', $typeNames);
Expand Down
2 changes: 1 addition & 1 deletion src/Type/NullType.php
Expand Up @@ -45,7 +45,7 @@ public function getValue()
return null;
}

public function generalize(): Type
public function generalize(?GeneralizePrecision $precision = null): Type
{
return $this;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Type/Php/ArgumentBasedFunctionReturnTypeExtension.php
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;

Expand Down Expand Up @@ -54,8 +55,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
$argumentKeyType = $argumentType->getIterableKeyType();
$argumentValueType = $argumentType->getIterableValueType();
if ($argument->unpack) {
$argumentKeyType = TypeUtils::generalizeType($argumentKeyType);
$argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType());
$argumentKeyType = TypeUtils::generalizeType($argumentKeyType, GeneralizePrecision::moreSpecific());
$argumentValueType = TypeUtils::generalizeType($argumentValueType->getIterableValueType(), GeneralizePrecision::moreSpecific());
}

return new ArrayType(
Expand Down
Expand Up @@ -7,6 +7,7 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
Expand Down Expand Up @@ -39,7 +40,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
}
}

$keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType());
$keyTypes[] = TypeUtils::generalizeType($argType->getIterableKeyType(), GeneralizePrecision::moreSpecific());
$valueTypes[] = $argType->getIterableValueType();
}

Expand Down
5 changes: 3 additions & 2 deletions src/Type/Php/RangeFunctionReturnTypeExtension.php
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\StringType;
Expand Down Expand Up @@ -68,8 +69,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
new ArrayType(
new IntegerType(),
TypeCombinator::union(
$startConstant->generalize(),
$endConstant->generalize()
$startConstant->generalize(GeneralizePrecision::moreSpecific()),
$endConstant->generalize(GeneralizePrecision::moreSpecific())
)
),
new NonEmptyArrayType(),
Expand Down
3 changes: 2 additions & 1 deletion src/Type/Traits/ConstantScalarTypeTrait.php
Expand Up @@ -6,6 +6,7 @@
use PHPStan\Type\CompoundType;
use PHPStan\Type\CompoundTypeHelper;
use PHPStan\Type\ConstantScalarType;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;

trait ConstantScalarTypeTrait
Expand Down Expand Up @@ -72,7 +73,7 @@ public function isSmallerThanOrEqual(Type $otherType): TrinaryLogic
return TrinaryLogic::createMaybe();
}

public function generalize(): Type
public function generalize(?GeneralizePrecision $precision = null): Type
{
return new parent();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/TypeCombinator.php
Expand Up @@ -290,7 +290,7 @@ public static function union(Type ...$types): Type
}
foreach ($scalarTypeItems as $type) {
if (count($scalarTypeItems) > self::CONSTANT_SCALAR_UNION_THRESHOLD) {
$types[] = $type->generalize();
$types[] = $type->generalize(GeneralizePrecision::moreSpecific());

if ($type instanceof ConstantStringType) {
continue;
Expand Down
6 changes: 3 additions & 3 deletions src/Type/TypeUtils.php
Expand Up @@ -119,11 +119,11 @@ public static function getAnyArrays(Type $type): array
return self::map(ArrayType::class, $type, true, false);
}

public static function generalizeType(Type $type): Type
public static function generalizeType(Type $type, ?GeneralizePrecision $precision = null): Type
{
return TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
return TypeTraverser::map($type, static function (Type $type, callable $traverse) use ($precision): Type {
if ($type instanceof ConstantType) {
return $type->generalize();
return $type->generalize($precision);
}

return $traverse($type);
Expand Down
2 changes: 1 addition & 1 deletion src/Type/UnionType.php
Expand Up @@ -164,7 +164,7 @@ function () use ($joinTypes): string {
$type instanceof ConstantType
&& !$type instanceof ConstantBooleanType
) {
return $type->generalize();
return $type->generalize(GeneralizePrecision::moreSpecific());
}

return $type;
Expand Down

0 comments on commit b864a95

Please sign in to comment.