From 06581ce4b0e5751446eec44075c48f7bd9e61dc6 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 01:54:06 +0100 Subject: [PATCH 1/2] Add additional checks for concat of non-empty strings to return non-falsy --- .../Expression/BinaryOp/ConcatAnalyzer.php | 25 +++++++++++++++--- src/Psalm/Type.php | 8 ++++++ tests/BinaryOperationTest.php | 26 +++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index e317de46e75..ff0e8eef4a0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -199,6 +199,17 @@ public static function analyze( $numeric_type ); + $numeric_type = Type::getNumericString(); + $numeric_type->addType(new TInt()); + $numeric_type->addType(new TFloat()); + $right_is_numeric = UnionTypeComparator::isContainedBy( + $codebase, + $right_type, + $numeric_type + ); + + $has_numeric_type = $left_is_numeric || $right_is_numeric; + if ($left_is_numeric) { $right_uint = Type::getPositiveInt(); $right_uint->addType(new TLiteralInt(0)); @@ -230,16 +241,23 @@ public static function analyze( $non_empty_string = clone $numeric_type; $non_empty_string->addType(new TNonEmptyString()); - $has_non_empty = UnionTypeComparator::isContainedBy( + $left_non_empty = UnionTypeComparator::isContainedBy( $codebase, $left_type, $non_empty_string - ) || UnionTypeComparator::isContainedBy( + ); + + $right_non_empty = UnionTypeComparator::isContainedBy( $codebase, $right_type, $non_empty_string ); + $has_non_empty = $left_non_empty || $right_non_empty; + $all_non_empty = $left_non_empty && $right_non_empty; + + $has_numeric_and_non_empty = $has_numeric_type && $has_non_empty; + $all_literals = $left_type->allLiterals() && $right_type->allLiterals(); if ($has_non_empty) { @@ -248,7 +266,8 @@ public static function analyze( } elseif ($all_lowercase) { $result_type = Type::getNonEmptyLowercaseString(); } else { - $result_type = Type::getNonEmptyString(); + $result_type = $all_non_empty || $has_numeric_and_non_empty ? + Type::getNonFalsyString() : Type::getNonEmptyString(); } } else { if ($all_literals) { diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 35ca811372e..15d7f1f2c3d 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -34,6 +34,7 @@ use Psalm\Type\Atomic\TNonEmptyList; use Psalm\Type\Atomic\TNonEmptyLowercaseString; use Psalm\Type\Atomic\TNonEmptyString; +use Psalm\Type\Atomic\TNonFalsyString; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; @@ -219,6 +220,13 @@ public static function getNonEmptyString(): Union return new Union([$type]); } + public static function getNonFalsyString(): Union + { + $type = new TNonFalsyString(); + + return new Union([$type]); + } + public static function getNumeric(): Union { $type = new TNumeric; diff --git a/tests/BinaryOperationTest.php b/tests/BinaryOperationTest.php index 241e573e27d..a9dc74982b0 100644 --- a/tests/BinaryOperationTest.php +++ b/tests/BinaryOperationTest.php @@ -830,6 +830,32 @@ function example(object $foo): string return ($foo instanceof FooInterface ? $foo->toString() : null) ?? "Not a stringable foo"; }', ], + 'concatNonEmptyReturnNonFalsyString' => [ + ' [ + '$a===' => 'non-falsy-string', + ], + ], + 'concatNumericWithNonEmptyReturnNonFalsyString' => [ + ' [ + '$a===' => 'non-falsy-string', + '$b===' => 'non-falsy-string', + ], + ], ]; } From b89ff32b7a2494444b7529da892d48a58d7ecac4 Mon Sep 17 00:00:00 2001 From: Greg Hargreaves Date: Fri, 14 Oct 2022 02:00:25 +0100 Subject: [PATCH 2/2] Remove duplicated numeric type declaration --- .../Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index ff0e8eef4a0..44d5e5d4f7b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -199,9 +199,6 @@ public static function analyze( $numeric_type ); - $numeric_type = Type::getNumericString(); - $numeric_type->addType(new TInt()); - $numeric_type->addType(new TFloat()); $right_is_numeric = UnionTypeComparator::isContainedBy( $codebase, $right_type,