diff --git a/src/Analyser/ExprHandler/FuncCallHandler.php b/src/Analyser/ExprHandler/FuncCallHandler.php index d28a3225de..fa39b3cd59 100644 --- a/src/Analyser/ExprHandler/FuncCallHandler.php +++ b/src/Analyser/ExprHandler/FuncCallHandler.php @@ -936,7 +936,9 @@ public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $e )); $specifiedTypes = $typeSpecifier->specifyTypesFromAsserts($context, $expr, $asserts, $parametersAcceptor, $scope); if ($specifiedTypes !== null) { - return $specifiedTypes; + return $specifiedTypes + ->unionWith($typeSpecifier->handleDefaultTruthyOrFalseyContext($context, $expr, $scope)) + ->setRootExpr($specifiedTypes->getRootExpr()); } } } diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 55138f3de6..f5599c6394 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -350,6 +350,13 @@ private function getSpecifiedType( } } foreach ($sureTypes as $sureType) { + if ($sureType[0] === $node) { + // The check's own truthiness carries no information about + // whether the check is redundant; the informative narrowing + // lives in the argument entries. + continue; + } + if (self::isSpecified($typeSpecifierScope, $node, $sureType[0])) { $results[] = TrinaryLogic::createMaybe(); continue; @@ -384,6 +391,13 @@ private function getSpecifiedType( } foreach ($sureNotTypes as $sureNotType) { + if ($sureNotType[0] === $node) { + // The check's own truthiness carries no information about + // whether the check is redundant; the informative narrowing + // lives in the argument entries. + continue; + } + if (self::isSpecified($typeSpecifierScope, $node, $sureNotType[0])) { $results[] = TrinaryLogic::createMaybe(); continue; diff --git a/src/Rules/Keywords/RequireFileExistsRule.php b/src/Rules/Keywords/RequireFileExistsRule.php index 674fc815c1..9e5fa1eb57 100644 --- a/src/Rules/Keywords/RequireFileExistsRule.php +++ b/src/Rules/Keywords/RequireFileExistsRule.php @@ -33,6 +33,19 @@ final class RequireFileExistsRule implements Rule { + /** + * Functions that, when they return true, guarantee the path exists on the + * filesystem, so guarding a require/include with them suppresses the error. + */ + private const FILE_EXISTENCE_FUNCTIONS = [ + 'file_exists', + 'is_file', + 'is_readable', + 'is_writable', + 'is_writeable', + 'is_executable', + ]; + public function __construct( #[AutowiredParameter] private string $currentWorkingDirectory, @@ -183,7 +196,7 @@ private function resolveFilePaths(Expr $expr, Scope $scope, bool &$magicDirFallb private function isInFileExists(Include_ $node, Scope $scope): bool { - foreach (['file_exists', 'is_file'] as $funcName) { + foreach (self::FILE_EXISTENCE_FUNCTIONS as $funcName) { $expr = new FuncCall(new FullyQualified($funcName), [ new Arg($node->expr), ]); diff --git a/tests/PHPStan/Analyser/nsrt/bug-14829.php b/tests/PHPStan/Analyser/nsrt/bug-14829.php new file mode 100644 index 0000000000..381a638272 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14829.php @@ -0,0 +1,19 @@ +