diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 3e50294c94..6441c09472 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -1548,6 +1548,20 @@ private function specifyTypesForConstantBinaryExpression( )->setRootExpr($rootExpr)); } + if ( + $context->true() + && $constantType->toBoolean()->isTrue()->yes() + && ($exprNode instanceof FuncCall || $exprNode instanceof Expr\MethodCall || $exprNode instanceof Expr\StaticCall) + ) { + $types = $this->create($exprNode, $constantType, $context, $scope)->setRootExpr($rootExpr); + + return $types->unionWith($this->specifyTypesInCondition( + $scope, + $exprNode, + TypeSpecifierContext::createTrue(), + )->setRootExpr($rootExpr)); + } + return null; } diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index 8a9fa17b3a..fdc64192b1 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -281,6 +281,10 @@ public function getType(Expr $expr, InitializerExprContext $context): Type $leftType = $this->getType($expr->left, $context); $rightType = $this->getType($expr->right, $context); + if ($leftType instanceof ErrorType) { + return $rightType; + } + return TypeCombinator::union(TypeCombinator::removeNull($leftType), $rightType); } diff --git a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php index 585c02bf9b..ad1510c251 100644 --- a/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php +++ b/src/Type/Php/StrRepeatFunctionReturnTypeExtension.php @@ -68,7 +68,7 @@ public function getTypeFromFunctionCall( $accessoryTypes = []; if ($inputType->isNonEmptyString()->yes()) { if (IntegerRangeType::fromInterval(1, null)->isSuperTypeOf($multiplierType)->yes()) { - if ($inputType->isNonFalsyString()->yes()) { + if ($inputType->isNonFalsyString()->yes() || IntegerRangeType::fromInterval(2, null)->isSuperTypeOf($multiplierType)->yes()) { $accessoryTypes[] = new AccessoryNonFalsyStringType(); } else { $accessoryTypes[] = new AccessoryNonEmptyStringType(); @@ -76,6 +76,7 @@ public function getTypeFromFunctionCall( } } + $addedNumericString = false; if ($inputType->isLiteralString()->yes()) { $accessoryTypes[] = new AccessoryLiteralStringType(); @@ -93,10 +94,19 @@ public function getTypeFromFunctionCall( if ($onlyNumbers) { $accessoryTypes[] = new AccessoryNumericStringType(); + $addedNumericString = true; } } } + if ( + !$addedNumericString + && $inputType->isNumericString()->yes() + && (new ConstantIntegerType(1))->isSuperTypeOf($multiplierType)->yes() + ) { + $accessoryTypes[] = new AccessoryNumericStringType(); + } + if ($inputType->isLowercaseString()->yes()) { $accessoryTypes[] = new AccessoryLowercaseStringType(); } diff --git a/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php b/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php index 7a5daede04..82a6d52460 100644 --- a/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php +++ b/tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php @@ -14,7 +14,7 @@ class Foo public function doFoo(): void { - assertType('*ERROR*', self::COALESCE_SPECIAL); // could be 42 + assertType('42', self::COALESCE_SPECIAL); assertType("0|1|2|'foo'", self::COALESCE); assertType("'bar'|'foo'|true", self::TERNARY_SHORT); assertType("'bar'|'foo'", self::TERNARY_FULL); diff --git a/tests/PHPStan/Analyser/nsrt/literal-string.php b/tests/PHPStan/Analyser/nsrt/literal-string.php index c30fbdac80..28cf58c79f 100644 --- a/tests/PHPStan/Analyser/nsrt/literal-string.php +++ b/tests/PHPStan/Analyser/nsrt/literal-string.php @@ -38,18 +38,18 @@ public function doFoo($literalString, string $string, $numericString) ); assertType('literal-string&lowercase-string&non-falsy-string', str_repeat('a', 100)); assertType('literal-string&non-falsy-string&uppercase-string', str_repeat('A', 100)); - assertType('literal-string&lowercase-string&non-empty-string&numeric-string&uppercase-string', str_repeat('0', 100)); // could be non-falsy-string + assertType('literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string', str_repeat('0', 100)); assertType('literal-string&lowercase-string&non-falsy-string&numeric-string&uppercase-string', str_repeat('1', 100)); // Repeating a numeric type multiple times can lead to a non-numeric type: 3v4l.org/aRBdZ - assertType('non-empty-string', str_repeat($numericString, 100)); + assertType('non-falsy-string', str_repeat($numericString, 100)); assertType("''", str_repeat('1.23', 0)); assertType("''", str_repeat($string, 0)); assertType("''", str_repeat($numericString, 0)); // see https://3v4l.org/U4bM2 - assertType("non-empty-string", str_repeat($numericString, 1)); // could be numeric-string - assertType("non-empty-string", str_repeat($numericString, 2)); + assertType("non-empty-string&numeric-string", str_repeat($numericString, 1)); + assertType("non-falsy-string", str_repeat($numericString, 2)); assertType("literal-string", str_repeat($literalString, 1)); $x = rand(1,2); assertType("literal-string&lowercase-string&non-falsy-string&uppercase-string", str_repeat(' 1 ', $x)); diff --git a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php index 19482b7fd0..bc3887d19a 100644 --- a/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php +++ b/tests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php @@ -92,19 +92,72 @@ public function variants(string $s) { if (strpos($s, ':') !== false) { assertType('non-falsy-string', $s); + } else { + assertType('string', $s); } assertType('string', $s); if (strpos($s, ':') === false) { assertType('string', $s); + } else { + assertType('non-falsy-string', $s); } assertType('string', $s); - if (strpos($s, ':') === 5) { + if (strpos($s, '0') == 0) { // 0|false + assertType('string', $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + $oneOrZero = rand(0, 1); + if (strpos($s, '0') == $oneOrZero) { // 0|1|false + assertType('string', $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + + $oneOrZero = rand(0, 1); + if (strpos($s, '0') === $oneOrZero) { + assertType('string', $s); // could be non-empty-string + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (strpos($s, '0') == 1) { + assertType('string', $s); // could be non-empty-string + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (strpos($s, '0') === 0) { assertType('string', $s); // could be non-empty-string + } else { + assertType('string', $s); + } + assertType('string', $s); + + if (strpos($s, '0') === 5) { + assertType('non-empty-string', $s); // could be non-falsy-string + } else { + assertType('string', $s); } assertType('string', $s); + + if (strpos($s, ':') === 5) { + assertType('non-falsy-string', $s); + } else { + assertType('string', $s); + } + assertType('string', $s); + if (strpos($s, ':') !== 5) { assertType('string', $s); + } else { + assertType('non-falsy-string', $s); } assertType('string', $s); @@ -152,7 +205,7 @@ public function variants(string $s) { assertType('string', $s); if (mb_strpos($s, ':') === 5) { - assertType('string', $s); // could be non-empty-string + assertType('non-falsy-string', $s); } assertType('string', $s); if (mb_strpos($s, ':') !== 5) {