diff --git a/src/Analyser/EnsuredNonNullabilityResultExpression.php b/src/Analyser/EnsuredNonNullabilityResultExpression.php index 33f94341e6..a7ed1572f8 100644 --- a/src/Analyser/EnsuredNonNullabilityResultExpression.php +++ b/src/Analyser/EnsuredNonNullabilityResultExpression.php @@ -3,7 +3,6 @@ namespace PHPStan\Analyser; use PhpParser\Node\Expr; -use PHPStan\TrinaryLogic; use PHPStan\Type\Type; class EnsuredNonNullabilityResultExpression @@ -13,7 +12,6 @@ public function __construct( private Expr $expression, private Type $originalType, private Type $originalNativeType, - private TrinaryLogic $certainty, ) { } @@ -33,9 +31,4 @@ public function getOriginalNativeType(): Type return $this->originalNativeType; } - public function getCertainty(): TrinaryLogic - { - return $this->certainty; - } - } diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c5ddd96917..91ee5eb759 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -2341,10 +2341,6 @@ public function isSpecified(Expr $node): bool /** @api */ public function hasExpressionType(Expr $node): TrinaryLogic { - if ($node instanceof Variable && is_string($node->name)) { - return $this->hasVariableType($node->name); - } - $exprString = $this->getNodeKey($node); if (!isset($this->expressionTypes[$exprString])) { return TrinaryLogic::createNo(); @@ -3437,7 +3433,7 @@ public function unsetExpression(Expr $expr): self return $scope->invalidateExpression($expr); } - public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, ?TrinaryLogic $certainty = null): self + public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType): self { if ($expr instanceof ConstFetch) { $loweredConstName = strtolower($expr->name->toString()); @@ -3471,7 +3467,6 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, if ($dimType instanceof ConstantIntegerType) { $types[] = new StringType(); } - $scope = $scope->specifyExpressionType( $expr->var, TypeCombinator::intersect( @@ -3479,23 +3474,16 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, new HasOffsetValueType($dimType, $type), ), $scope->getNativeType($expr->var), - $certainty, ); } } } - if ($certainty === null) { - $certainty = TrinaryLogic::createYes(); - } elseif ($certainty->no()) { - throw new ShouldNotHappenException(); - } - $exprString = $this->getNodeKey($expr); $expressionTypes = $scope->expressionTypes; - $expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); + $expressionTypes[$exprString] = ExpressionTypeHolder::createYes($expr, $type); $nativeTypes = $scope->nativeExpressionTypes; - $nativeTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); + $nativeTypes[$exprString] = ExpressionTypeHolder::createYes($expr, $nativeType); return $this->scopeFactory->create( $this->context, diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 3c3acb499b..1f8fd4d8d1 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1753,14 +1753,6 @@ private function ensureShallowNonNullability(MutatingScope $scope, Scope $origin if ($isNull->yes()) { return new EnsuredNonNullabilityResult($scope, []); } - - // keep certainty - $certainty = TrinaryLogic::createYes(); - $hasExpressionType = $originalScope->hasExpressionType($exprToSpecify); - if (!$hasExpressionType->no()) { - $certainty = $hasExpressionType; - } - $exprTypeWithoutNull = TypeCombinator::removeNull($exprType); if ($exprType->equals($exprTypeWithoutNull)) { $originalExprType = $originalScope->getType($exprToSpecify); @@ -1768,7 +1760,7 @@ private function ensureShallowNonNullability(MutatingScope $scope, Scope $origin $originalNativeType = $originalScope->getNativeType($exprToSpecify); return new EnsuredNonNullabilityResult($scope, [ - new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType, $certainty), + new EnsuredNonNullabilityResultExpression($exprToSpecify, $originalExprType, $originalNativeType), ]); } return new EnsuredNonNullabilityResult($scope, []); @@ -1784,7 +1776,7 @@ private function ensureShallowNonNullability(MutatingScope $scope, Scope $origin return new EnsuredNonNullabilityResult( $scope, [ - new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType, $certainty), + new EnsuredNonNullabilityResultExpression($exprToSpecify, $exprType, $nativeType), ], ); } @@ -1814,7 +1806,6 @@ private function revertNonNullability(MutatingScope $scope, array $specifiedExpr $specifiedExpressionResult->getExpression(), $specifiedExpressionResult->getOriginalType(), $specifiedExpressionResult->getOriginalNativeType(), - $specifiedExpressionResult->getCertainty(), ); } diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 1280e3fedc..0a5fbb8511 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -717,14 +717,11 @@ public function specifyTypesInCondition( $types = null; foreach ($vars as $var) { - $type = new SpecifiedTypes(); - if ($var instanceof Expr\Variable && is_string($var->name)) { if ($scope->hasVariableType($var->name)->no()) { return new SpecifiedTypes([], [], false, [], $rootExpr); } } - if ( $var instanceof ArrayDimFetch && $var->dim !== null @@ -741,32 +738,36 @@ public function specifyTypesInCondition( $scope, $rootExpr, ); + } else { + $type = new SpecifiedTypes(); } + + $type = $type->unionWith( + $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr), + ); + } else { + $type = $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr); } if ( $var instanceof PropertyFetch && $var->name instanceof Node\Identifier ) { - $type = $this->create($var->var, new IntersectionType([ + $type = $type->unionWith($this->create($var->var, new IntersectionType([ new ObjectWithoutClassType(), new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr); + ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr)); } elseif ( $var instanceof StaticPropertyFetch && $var->class instanceof Expr && $var->name instanceof Node\VarLikeIdentifier ) { - $type = $this->create($var->class, new IntersectionType([ + $type = $type->unionWith($this->create($var->class, new IntersectionType([ new ObjectWithoutClassType(), new HasPropertyType($var->name->toString()), - ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr); + ]), TypeSpecifierContext::createTruthy(), false, $scope, $rootExpr)); } - $type = $type->unionWith( - $this->create($var, new NullType(), TypeSpecifierContext::createFalse(), false, $scope, $rootExpr), - ); - if ($types === null) { $types = $type; } else { @@ -791,15 +792,6 @@ public function specifyTypesInCondition( } elseif ( $expr instanceof Expr\Empty_ ) { - if (!$scope instanceof MutatingScope) { - throw new ShouldNotHappenException(); - } - - $isset = $scope->issetCheck($expr->expr, static fn () => true); - if ($isset === false) { - return new SpecifiedTypes(); - } - return $this->specifyTypesInCondition($scope, new BooleanOr( new Expr\BooleanNot(new Expr\Isset_([$expr->expr])), new Expr\BooleanNot($expr->expr), diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index ac945e2ed3..d77d7e435c 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -1362,9 +1362,6 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/impure-connection-fns.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9963.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9995.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/falsey-isset-certainty.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/falsey-empty-certainty.php'); - yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7291.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-7291.php b/tests/PHPStan/Analyser/data/bug-7291.php deleted file mode 100644 index cae3e945b3..0000000000 --- a/tests/PHPStan/Analyser/data/bug-7291.php +++ /dev/null @@ -1,25 +0,0 @@ -foo; - - assertType('stdClass|null', $a); - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } -} diff --git a/tests/PHPStan/Analyser/data/falsey-empty-certainty.php b/tests/PHPStan/Analyser/data/falsey-empty-certainty.php deleted file mode 100644 index cde86585aa..0000000000 --- a/tests/PHPStan/Analyser/data/falsey-empty-certainty.php +++ /dev/null @@ -1,69 +0,0 @@ - null]; - if (rand() % 3) { - $a = ['bar' => 'hello']; - } - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (empty($a['bar'])) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function falseyEmptyUncertainPropertyFetch(): void -{ - if (rand() % 2) { - $a = new \stdClass(); - if (rand() % 3) { - $a->x = 'hello'; - } - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (empty($a->x)) { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function justEmpty(): void -{ - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - if (!empty($foo)) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } else { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } - assertVariableCertainty(TrinaryLogic::createNo(), $foo); -} - -function maybeEmpty(): void -{ - if (rand() % 2) { - $foo = 1; - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - if (!empty($foo)) { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - } -} diff --git a/tests/PHPStan/Analyser/data/falsey-isset-certainty.php b/tests/PHPStan/Analyser/data/falsey-isset-certainty.php deleted file mode 100644 index f13f702364..0000000000 --- a/tests/PHPStan/Analyser/data/falsey-isset-certainty.php +++ /dev/null @@ -1,168 +0,0 @@ -bar = null; - if (rand() % 3) { - $a->bar = 'hello'; - } - - assertVariableCertainty(TrinaryLogic::createYes(), $a); - if (isset($a->bar)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } - - assertVariableCertainty(TrinaryLogic::createYes(), $a); -} - -function falseyIssetUncertainArrayDimFetchOnProperty(): void -{ - if (rand() % 2) { - $a = new \stdClass(); - $a->bar = null; - $a = ['bar' => null]; - if (rand() % 3) { - $a->bar = 'hello'; - } - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (isset($a->bar)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function falseyIssetUncertainPropertyFetch(): void -{ - if (rand() % 2) { - $a = new \stdClass(); - if (rand() % 3) { - $a->x = 'hello'; - } - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (isset($a->x)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function falseyIssetArrayDimFetch(): void -{ - $a = ['bar' => null]; - if (rand() % 3) { - $a = ['bar' => 'hello']; - } - - assertVariableCertainty(TrinaryLogic::createYes(), $a); - if (isset($a['bar'])) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } - - assertVariableCertainty(TrinaryLogic::createYes(), $a); -} - -function falseyIssetUncertainArrayDimFetch(): void -{ - if (rand() % 2) { - $a = ['bar' => null]; - if (rand() % 3) { - $a = ['bar' => 'hello']; - } - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (isset($a['bar'])) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function falseyIssetVariable(): void -{ - if (rand() % 2) { - $a = 'bar'; - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - if (isset($a)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - } else { - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); - } - - assertVariableCertainty(TrinaryLogic::createMaybe(), $a); -} - -function falseyIssetWithAssignment(): void -{ - if (rand() % 2) { - $x = ['x' => 1]; - } - - if (isset($x[$z = getFoo()])) { - assertVariableCertainty(TrinaryLogic::createYes(), $z); - assertVariableCertainty(TrinaryLogic::createYes(), $x); - - } else { - assertVariableCertainty(TrinaryLogic::createYes(), $z); - assertVariableCertainty(TrinaryLogic::createMaybe(), $x); - } - - assertVariableCertainty(TrinaryLogic::createYes(), $z); - assertVariableCertainty(TrinaryLogic::createMaybe(), $x); -} - -function justIsset(): void -{ - if (isset($foo)) { - assertVariableCertainty(TrinaryLogic::createNo(), $foo); - } -} - -function maybeIsset(): void -{ - if (rand() % 2) { - $foo = 1; - } - assertVariableCertainty(TrinaryLogic::createMaybe(), $foo); - if (isset($foo)) { - assertVariableCertainty(TrinaryLogic::createYes(), $foo); - assertType('1', $foo); - } -} - -function isStringNarrowsMaybeCertainty(int $i, string $s): void -{ - if (rand(0, 1)) { - $a = rand(0,1) ? $i : $s; - } - - if (is_string($a)) { - assertVariableCertainty(TrinaryLogic::createYes(), $a); - echo $a; - } -} diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 0bae922d49..8e4e9a3033 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -102,10 +102,6 @@ public function testDefinedVariables(): void 'Undefined variable: $variableInEmpty', 145, ], - [ - 'Undefined variable: $negatedVariableInEmpty', - 152, - ], [ 'Undefined variable: $variableInEmpty', 155, @@ -997,24 +993,6 @@ public function testBug5326(): void $this->analyse([__DIR__ . '/data/bug-5326.php'], []); } - public function testIsStringNarrowsCertainty(): void - { - $this->cliArgumentsVariablesRegistered = true; - $this->polluteScopeWithLoopInitialAssignments = true; - $this->checkMaybeUndefinedVariables = true; - $this->polluteScopeWithAlwaysIterableForeach = true; - $this->analyse([__DIR__ . '/data/isstring-certainty.php'], [ - [ - 'Variable $a might not be defined.', - 11, - ], - [ - 'Undefined variable: $a', - 19, - ], - ]); - } - public function testBug5266(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index 34caffcc7b..a9c594bec5 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -450,21 +450,4 @@ public function testObjectShapes(): void $this->analyse([__DIR__ . '/data/isset-object-shapes.php'], []); } - public function testBug3985(): void - { - $this->treatPhpDocTypesAsCertain = true; - $this->strictUnnecessaryNullsafePropertyFetch = true; - - $this->analyse([__DIR__ . '/../../Analyser/data/bug-3985.php'], [ - [ - 'Variable $foo in isset() is never defined.', - 13, - ], - [ - 'Variable $foo in isset() is never defined.', - 21, - ], - ]); - } - } diff --git a/tests/PHPStan/Rules/Variables/data/isstring-certainty.php b/tests/PHPStan/Rules/Variables/data/isstring-certainty.php deleted file mode 100644 index 270e978e68..0000000000 --- a/tests/PHPStan/Rules/Variables/data/isstring-certainty.php +++ /dev/null @@ -1,22 +0,0 @@ -