Skip to content
Open
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
12 changes: 0 additions & 12 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,6 @@ parameters:
count: 2
path: src/Rules/Comparison/IfConstantConditionRule.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.'
identifier: phpstanApi.instanceofType
count: 2
path: src/Rules/Comparison/ImpossibleCheckTypeHelper.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.'
identifier: phpstanApi.instanceofType
Expand Down Expand Up @@ -1581,12 +1575,6 @@ parameters:
count: 1
path: src/Type/Php/FunctionExistsFunctionTypeSpecifyingExtension.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantArrayType is error-prone and deprecated. Use Type::getConstantArrays() instead.'
identifier: phpstanApi.instanceofType
count: 1
path: src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

-
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
identifier: phpstanApi.instanceofType
Expand Down
103 changes: 0 additions & 103 deletions src/Rules/Comparison/ImpossibleCheckTypeHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
Expand Down Expand Up @@ -95,107 +93,6 @@ public function findSpecifiedType(
return null;
} elseif ($functionName === 'array_search') {
return null;
} elseif ($functionName === 'in_array' && $argsCount >= 2) {
$haystackArg = $args[1]->value;
$haystackType = $this->treatPhpDocTypesAsCertain ? $scope->getType($haystackArg) : $scope->getNativeType($haystackArg);
if ($haystackType instanceof MixedType) {
return null;
}

if (!$haystackType->isArray()->yes()) {
return null;
}

$needleArg = $args[0]->value;
$needleType = $this->treatPhpDocTypesAsCertain ? $scope->getType($needleArg) : $scope->getNativeType($needleArg);

$isStrictComparison = false;
if ($argsCount >= 3) {
$strictNodeType = $scope->getType($args[2]->value);
$isStrictComparison = $strictNodeType->isTrue()->yes();
}

$isStrictComparison = $isStrictComparison
|| $needleType->isEnum()->yes()
|| $haystackType->getIterableValueType()->isEnum()->yes();

if (!$isStrictComparison) {
return null;
}

$valueType = $haystackType->getIterableValueType();
$constantNeedleTypesCount = count($needleType->getFiniteTypes());
$constantHaystackTypesCount = count($valueType->getFiniteTypes());
$isNeedleSupertype = $needleType->isSuperTypeOf($valueType);
if ($haystackType->isConstantArray()->no()) {
if ($haystackType->isIterableAtLeastOnce()->yes()) {
// In this case the generic implementation via typeSpecifier fails, because the argument types cannot be narrowed down.
if ($constantNeedleTypesCount === 1 && $constantHaystackTypesCount === 1) {
if ($isNeedleSupertype->yes()) {
return true;
}
if ($isNeedleSupertype->no()) {
return false;
}
}

return null;
}

if (!$isNeedleSupertype->no()) {
// Array might be empty, so in_array can return false
return null;
}
}

if (!$haystackType instanceof ConstantArrayType || count($haystackType->getValueTypes()) > 0) {
$haystackArrayTypes = $haystackType->getArrays();
if (count($haystackArrayTypes) === 1 && $haystackArrayTypes[0]->getIterableValueType() instanceof NeverType) {
return null;
}

if ($isNeedleSupertype->maybe() || $isNeedleSupertype->yes()) {
foreach ($haystackArrayTypes as $haystackArrayType) {
if ($haystackArrayType instanceof ConstantArrayType) {
foreach ($haystackArrayType->getValueTypes() as $i => $haystackArrayValueType) {
if ($haystackArrayType->isOptionalKey($i)) {
continue;
}

$haystackArrayValueConstantScalarTypes = $haystackArrayValueType->getConstantScalarTypes();
if (count($haystackArrayValueConstantScalarTypes) > 1) {
continue;
}

foreach ($haystackArrayValueConstantScalarTypes as $constantScalarType) {
if ($constantScalarType->isSuperTypeOf($needleType)->yes()) {
continue 3;
}
}
}
} else {
foreach ($haystackArrayType->getIterableValueType()->getConstantScalarTypes() as $constantScalarType) {
if ($constantScalarType->isSuperTypeOf($needleType)->yes()) {
continue 2;
}
}
}

return null;
}
}

if ($isNeedleSupertype->yes()) {
$hasConstantNeedleTypes = $constantNeedleTypesCount > 0;
$hasConstantHaystackTypes = $constantHaystackTypesCount > 0;
if (
(!$hasConstantNeedleTypes && !$hasConstantHaystackTypes)
|| $hasConstantNeedleTypes !== $hasConstantHaystackTypes
) {
return null;
}
}
}
} elseif ($functionName === 'method_exists' && $argsCount >= 2) {
$objectArg = $args[0]->value;
$objectType = $this->treatPhpDocTypesAsCertain ? $scope->getType($objectArg) : $scope->getNativeType($objectArg);
Expand Down
107 changes: 98 additions & 9 deletions src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\BinaryOp\Equal;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
Expand All @@ -16,7 +18,6 @@
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -103,18 +104,21 @@
&& $arrayType->isArray()->yes()
&& $arrayType->getIterableValueType()->isSuperTypeOf($needleType)->yes()
) {
return $this->typeSpecifier->create(
$specifiedTypes = $this->typeSpecifier->create(
$args[1]->value,
TypeCombinator::intersect($arrayType, new NonEmptyArrayType()),
$context,
$scope,
);

return $specifiedTypes->setRootExpr(new ConstFetch(new Name('__PHPSTAN_FAUX_CONSTANT')));
}

return new SpecifiedTypes();
}

$specifiedTypes = new SpecifiedTypes();
$originalArrayValueType = $arrayValueType;
$narrowingValueType = $this->computeNeedleNarrowingType($context, $needleType, $arrayType, $arrayValueType);
if ($narrowingValueType !== null) {
$specifiedTypes = $this->typeSpecifier->create(
Expand Down Expand Up @@ -163,7 +167,7 @@
));
}

return $specifiedTypes;
return $specifiedTypes->setRootExpr($this->determineRootExpr($needleType, $arrayType, $originalArrayValueType));
}

/**
Expand All @@ -185,13 +189,13 @@
return null;
}

$arrays = $arrayType->getArrays();
$constantArrays = $arrayType->getConstantArrays();
$guaranteedValueTypePerArray = [];
foreach ($arrays as $array) {
if ($array instanceof ConstantArrayType) {
if (count($constantArrays) > 0) {
foreach ($constantArrays as $constantArray) {
$innerGuaranteeValueType = [];
foreach ($array->getValueTypes() as $i => $valueType) {
if ($array->isOptionalKey($i)) {
foreach ($constantArray->getValueTypes() as $i => $valueType) {
if ($constantArray->isOptionalKey($i)) {
continue;
}

Expand All @@ -208,7 +212,10 @@
}

$guaranteedValueTypePerArray[] = TypeCombinator::union(...$innerGuaranteeValueType);
} else {
}
} else {
$arrays = $arrayType->getArrays();
foreach ($arrays as $array) {
$finiteValueType = $array->getIterableValueType()->getFiniteTypes();
if (count($finiteValueType) !== 1) {
return null;
Expand All @@ -230,4 +237,86 @@
return $guaranteedValueType;
}

/**
* Returns a rootExpr that ImpossibleCheckTypeHelper evaluates instead of
* using sureTypes-based detection. This bypasses scope leakage issues where
* expressions (e.g. enum ClassConstFetch) are marked as "specified" by
* prior in_array calls in the same scope.
*
* Returns ConstFetch('false') for incompatible types (always false),
* ConstFetch('true') when the needle is guaranteed to be found (always true),
* or ConstFetch('__PHPSTAN_FAUX_CONSTANT') for indeterminate cases.
*/
private function determineRootExpr(Type $needleType, Type $arrayType, Type $arrayValueType): ConstFetch
{
// Types are incompatible -> always false
if ($arrayValueType->isSuperTypeOf($needleType)->no()) {
return new ConstFetch(new Name('false'));
}

// Check if in_array is guaranteed to always return true
if ($this->isGuaranteedToContainNeedle($needleType, $arrayType)) {
return new ConstFetch(new Name('true'));
}

// Indeterminate: types are compatible but can't guarantee result.
// Set rootExpr to prevent false impossibility detection.
return new ConstFetch(new Name('__PHPSTAN_FAUX_CONSTANT'));
}

private function isGuaranteedToContainNeedle(Type $needleType, Type $arrayType): bool
{
if (!$arrayType->isIterableAtLeastOnce()->yes()) {
return false;
}

if (count($needleType->getFiniteTypes()) !== 1) {
return false;
}

$constantArrays = $arrayType->getConstantArrays();
if (count($constantArrays) > 0) {
foreach ($constantArrays as $constantArray) {
$hasGuaranteedMatch = false;
foreach ($constantArray->getValueTypes() as $i => $valueType) {
if ($constantArray->isOptionalKey($i)) {
continue;
}

$constantScalarTypes = $valueType->getConstantScalarTypes();
if (count($constantScalarTypes) > 1) {
continue;
}

foreach ($constantScalarTypes as $constantScalarType) {
if ($constantScalarType->isSuperTypeOf($needleType)->yes()) {

Check warning on line 292 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } foreach ($constantScalarTypes as $constantScalarType) { - if ($constantScalarType->isSuperTypeOf($needleType)->yes()) { + if (!$constantScalarType->isSuperTypeOf($needleType)->no()) { $hasGuaranteedMatch = true; break 2; }

Check warning on line 292 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ } foreach ($constantScalarTypes as $constantScalarType) { - if ($constantScalarType->isSuperTypeOf($needleType)->yes()) { + if ($needleType->isSuperTypeOf($constantScalarType)->yes()) { $hasGuaranteedMatch = true; break 2; }

Check warning on line 292 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ } foreach ($constantScalarTypes as $constantScalarType) { - if ($constantScalarType->isSuperTypeOf($needleType)->yes()) { + if (!$constantScalarType->isSuperTypeOf($needleType)->no()) { $hasGuaranteedMatch = true; break 2; }

Check warning on line 292 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\IsSuperTypeOfCalleeAndArgumentMutator": @@ @@ } foreach ($constantScalarTypes as $constantScalarType) { - if ($constantScalarType->isSuperTypeOf($needleType)->yes()) { + if ($needleType->isSuperTypeOf($constantScalarType)->yes()) { $hasGuaranteedMatch = true; break 2; }
$hasGuaranteedMatch = true;
break 2;
}
}
}
if (!$hasGuaranteedMatch) {
return false;
}
}
} else {
$arrays = $arrayType->getArrays();
if (count($arrays) === 0) {
return false;
}

foreach ($arrays as $array) {
$finiteTypes = $array->getIterableValueType()->getFiniteTypes();
if (count($finiteTypes) !== 1) {
return false;
}
if (!$needleType->isSuperTypeOf($finiteTypes[0])->yes()) {

Check warning on line 313 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if (count($finiteTypes) !== 1) { return false; } - if (!$needleType->isSuperTypeOf($finiteTypes[0])->yes()) { + if ($needleType->isSuperTypeOf($finiteTypes[0])->no()) { return false; } }

Check warning on line 313 in src/Type/Php/InArrayFunctionTypeSpecifyingExtension.php

View workflow job for this annotation

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

Escaped Mutant for Mutator "PHPStan\Infection\TrinaryLogicMutator": @@ @@ if (count($finiteTypes) !== 1) { return false; } - if (!$needleType->isSuperTypeOf($finiteTypes[0])->yes()) { + if ($needleType->isSuperTypeOf($finiteTypes[0])->no()) { return false; } }
return false;
}
}
}

return true;
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/binary.php
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ public function doFoo(array $generalArray)
assertType('bool', is_int($mixed));
assertType('true', is_int($integer));
assertType('false', is_int($string));
assertType('bool', in_array('foo', ['foo', 'bar']));
assertType('true', in_array('foo', ['foo', 'bar']));
assertType('true', in_array('foo', ['foo', 'bar'], true));
assertType('false', in_array('baz', ['foo', 'bar'], true));
assertType('array{2, 3}', $arrToShift);
Expand Down
Loading
Loading