From c0a9d042410b108dd26791acc40a933f4267f396 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 00:18:08 +0200 Subject: [PATCH 01/12] Fix condition on literal-string --- .../Type/Comparator/ScalarTypeComparator.php | 5 +++- tests/Template/ConditionalReturnTypeTest.php | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php index 40360a93b37..56282006bb2 100644 --- a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php @@ -74,7 +74,10 @@ public static function isContainedBy( } if ($container_type_part instanceof TNonEmptyString - && get_class($input_type_part) === TString::class + && ( + get_class($input_type_part) === TString::class + || get_class($input_type_part) === TNonspecificLiteralString::class + ) ) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; diff --git a/tests/Template/ConditionalReturnTypeTest.php b/tests/Template/ConditionalReturnTypeTest.php index bc5e88777da..280d7c23b49 100644 --- a/tests/Template/ConditionalReturnTypeTest.php +++ b/tests/Template/ConditionalReturnTypeTest.php @@ -979,6 +979,34 @@ function getSomething(string $string) ], 'ignored_issues' => [], ], + 'literalStringIsNotNonEmpty' => [ + 'code' => ' [ + '$something' => 'int|string', + '$something2' => 'string', + ], + 'ignored_issues' => [], + ], ]; } } From f1ac41809c25c2b04b396ded3e58edf85aa5e128 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 01:05:53 +0200 Subject: [PATCH 02/12] Try to fix --- src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index ab367ca66c8..1299274456f 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -451,7 +451,10 @@ private static function replaceConditional( null, false, false, - )) { + ) + && ($candidate_atomic_type->getKey() !== 'string' + || $conditional_type->getKey() !== 'string') + ) { $matching_else_types[] = $candidate_atomic_type; } } From 279cfb4c68df53c3c4dad1bf158f1c89fcd2a53e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 11:08:26 +0200 Subject: [PATCH 03/12] Improve --- .../Type/TemplateInferredTypeReplacer.php | 2 +- tests/Template/ConditionalReturnTypeTest.php | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 1299274456f..93d30bb6f3a 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -453,7 +453,7 @@ private static function replaceConditional( false, ) && ($candidate_atomic_type->getKey() !== 'string' - || $conditional_type->getKey() !== 'string') + || false === strpos($conditional_type->getKey(), 'string')) ) { $matching_else_types[] = $candidate_atomic_type; } diff --git a/tests/Template/ConditionalReturnTypeTest.php b/tests/Template/ConditionalReturnTypeTest.php index 280d7c23b49..ac5a913eb14 100644 --- a/tests/Template/ConditionalReturnTypeTest.php +++ b/tests/Template/ConditionalReturnTypeTest.php @@ -1007,6 +1007,34 @@ function getSomething(string $string) ], 'ignored_issues' => [], ], + 'literalStringIsNotNonEmptyWithUnion' => [ + 'code' => ' [ + '$something' => 'int|string', + '$something2' => 'string', + ], + 'ignored_issues' => [], + ], ]; } } From e6d2c8ef1ed70549df5942b28a3f41d8c778626f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 11:34:50 +0200 Subject: [PATCH 04/12] Another try --- .../Type/TemplateInferredTypeReplacer.php | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 93d30bb6f3a..18a6dc937a7 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -442,19 +442,11 @@ private static function replaceConditional( || $conditional_type->getId() !== 'float') ) { $matching_if_types[] = $candidate_atomic_type; - } elseif (!UnionTypeComparator::isContainedBy( - $codebase, - $conditional_type, + } elseif (null === Type::intersectUnionTypes( new Union([$candidate_atomic_type]), - false, - false, - null, - false, - false, - ) - && ($candidate_atomic_type->getKey() !== 'string' - || false === strpos($conditional_type->getKey(), 'string')) - ) { + $conditional_type, + $codebase + )) { $matching_else_types[] = $candidate_atomic_type; } } From d715e334520ebd0ac5158eb6f827b2b891e0e6ae Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 11:41:39 +0200 Subject: [PATCH 05/12] Fix phpdoc --- src/Psalm/Internal/Type/ArrayType.php | 6 ++++-- src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Type/ArrayType.php b/src/Psalm/Internal/Type/ArrayType.php index f115fcc9879..70e60208b0e 100644 --- a/src/Psalm/Internal/Type/ArrayType.php +++ b/src/Psalm/Internal/Type/ArrayType.php @@ -35,8 +35,10 @@ public function __construct(Union $key, Union $value, bool $is_list, ?int $count /** * @return ( - * $type is TArrayKey ? self : ( - * $type is TArray ? self : null + * $type is TKeyedArray ? self : ( + * $type is TNonEmptyArray ? self : ( + * $type is TArray ? self : null + * ) * ) * ) */ diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 18a6dc937a7..518581af454 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -445,7 +445,7 @@ private static function replaceConditional( } elseif (null === Type::intersectUnionTypes( new Union([$candidate_atomic_type]), $conditional_type, - $codebase + $codebase, )) { $matching_else_types[] = $candidate_atomic_type; } From 91cfbff7811d4deb6ae850d6595379b39c9bb29b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 11:49:43 +0200 Subject: [PATCH 06/12] Another try --- .../Internal/Type/TemplateInferredTypeReplacer.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 518581af454..52dfd2e9f5c 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -442,7 +442,16 @@ private static function replaceConditional( || $conditional_type->getId() !== 'float') ) { $matching_if_types[] = $candidate_atomic_type; - } elseif (null === Type::intersectUnionTypes( + } elseif (!UnionTypeComparator::isContainedBy( + $codebase, + $conditional_type, + new Union([$candidate_atomic_type]), + false, + false, + null, + false, + false, + ) && null === Type::intersectUnionTypes( new Union([$candidate_atomic_type]), $conditional_type, $codebase, From 69ecec6f4c65756a660427018f1a47f614620e21 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 12:02:09 +0200 Subject: [PATCH 07/12] Improve --- .../Type/TemplateInferredTypeReplacer.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 52dfd2e9f5c..1664b5caecf 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -9,6 +9,7 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TConditional; +use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TKeyOf; @@ -451,12 +452,17 @@ private static function replaceConditional( null, false, false, - ) && null === Type::intersectUnionTypes( - new Union([$candidate_atomic_type]), - $conditional_type, - $codebase, )) { - $matching_else_types[] = $candidate_atomic_type; + $intersection = Type::intersectUnionTypes( + new Union([$candidate_atomic_type]), + $conditional_type, + $codebase, + ); + if (null === $intersection + || ($candidate_atomic_type instanceof TFloat + && $intersection->getKey() === 'int')) { + $matching_else_types[] = $candidate_atomic_type; + } } } From 78e526c3e5e66781c7c2fb5d7a2c7333a245207e Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 12:27:44 +0200 Subject: [PATCH 08/12] Try --- .../Type/TemplateInferredTypeReplacer.php | 18 +++++++----------- src/Psalm/Type.php | 9 +++++++++ 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index 1664b5caecf..1f654c09998 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -9,7 +9,6 @@ use Psalm\Type\Atomic; use Psalm\Type\Atomic\TClassString; use Psalm\Type\Atomic\TConditional; -use Psalm\Type\Atomic\TFloat; use Psalm\Type\Atomic\TInt; use Psalm\Type\Atomic\TIterable; use Psalm\Type\Atomic\TKeyOf; @@ -452,17 +451,14 @@ private static function replaceConditional( null, false, false, + ) && null === Type::intersectUnionTypes( + new Union([$candidate_atomic_type]), + $conditional_type, + $codebase, + false, + false, )) { - $intersection = Type::intersectUnionTypes( - new Union([$candidate_atomic_type]), - $conditional_type, - $codebase, - ); - if (null === $intersection - || ($candidate_atomic_type instanceof TFloat - && $intersection->getKey() === 'int')) { - $matching_else_types[] = $candidate_atomic_type; - } + $matching_else_types[] = $candidate_atomic_type; } } diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 793c3198b2e..f577c19fe83 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -33,8 +33,10 @@ use Psalm\Type\Atomic\TNamedObject; use Psalm\Type\Atomic\TNever; use Psalm\Type\Atomic\TNonEmptyLowercaseString; +use Psalm\Type\Atomic\TNonEmptyNonspecificLiteralString; use Psalm\Type\Atomic\TNonEmptyString; use Psalm\Type\Atomic\TNonFalsyString; +use Psalm\Type\Atomic\TNonspecificLiteralString; use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TNumeric; use Psalm\Type\Atomic\TNumericString; @@ -909,6 +911,13 @@ private static function intersectAtomicTypes( $intersection_atomic = $type_1_atomic; $wider_type = $type_2_atomic; $intersection_performed = true; + } elseif (($type_1_atomic instanceof TNonspecificLiteralString + && $type_2_atomic instanceof TNonEmptyString) + || ($type_1_atomic instanceof TNonEmptyString + && $type_2_atomic instanceof TNonspecificLiteralString) + ) { + $intersection_atomic = new TNonEmptyNonspecificLiteralString(); + $intersection_performed = true; } if ($intersection_atomic From a184f15570a2ccaad63038399625f47121212b59 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 14:05:34 +0200 Subject: [PATCH 09/12] Fix --- src/Psalm/Type.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index f577c19fe83..d9535f876ee 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -789,8 +789,26 @@ public static function intersectUnionTypes( //if a type is contained by the other, the intersection is the narrowest type if (!$intersection_performed) { - $type_1_in_2 = UnionTypeComparator::isContainedBy($codebase, $type_1, $type_2); - $type_2_in_1 = UnionTypeComparator::isContainedBy($codebase, $type_2, $type_1); + $type_1_in_2 = UnionTypeComparator::isContainedBy( + $codebase, + $type_1, + $type_2, + false, + false, + null, + $allow_interface_equality, + $allow_float_int_equality, + ); + $type_2_in_1 = UnionTypeComparator::isContainedBy( + $codebase, + $type_2, + $type_1, + false, + false, + null, + $allow_interface_equality, + $allow_float_int_equality, + ); if ($type_1_in_2) { $intersection_performed = true; $combined_type = $type_1->getBuilder(); From 91ea61e4170656cb7a54347271d054e9037106d6 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 15:37:54 +0200 Subject: [PATCH 10/12] Fix test --- tests/Template/ConditionalReturnTypeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Template/ConditionalReturnTypeTest.php b/tests/Template/ConditionalReturnTypeTest.php index ac5a913eb14..3a3880248fe 100644 --- a/tests/Template/ConditionalReturnTypeTest.php +++ b/tests/Template/ConditionalReturnTypeTest.php @@ -891,7 +891,7 @@ function getSomethingElse() interface ContainerInterface { /** - * @template TRequestedInstance extends InstanceType + * @template TRequestedInstance of InstanceType * @param class-string|string $name * @return ($name is class-string ? TRequestedInstance : InstanceType) */ From ee675c73c06f0062aa6612d3a26d37e2441cde43 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 16:10:04 +0200 Subject: [PATCH 11/12] Useless phpdoc --- .../Expression/Call/ArrayFunctionArgumentsAnalyzer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 55364514eb9..6497c921b0e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -355,9 +355,6 @@ public static function handleSplice( if (($array_arg_type = $statements_analyzer->node_data->getType($array_arg)) && $array_arg_type->hasArray() ) { - /** - * @var TArray|TKeyedArray - */ $array_type = $array_arg_type->getArray(); if ($generic_array_type = ArrayType::infer($array_type)) { $array_size = $generic_array_type->count; From 0e4fc37fdbbdb38b3cd098454f4036a65d905169 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 29 Apr 2024 18:56:51 +0200 Subject: [PATCH 12/12] Fix --- src/Psalm/Internal/LanguageServer/LanguageServer.php | 5 ++++- stubs/CoreGenericClasses.phpstub | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Internal/LanguageServer/LanguageServer.php b/src/Psalm/Internal/LanguageServer/LanguageServer.php index 54d15a4aa7c..4f878d8a4ca 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageServer.php +++ b/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -400,7 +400,8 @@ public function initialize( $this->path_mapper->configureClientRoot($this->getPathPart($rootUri)); } - return call( + /** @var Promise $promise */ + $promise = call( /** @return Generator */ function () use ($workDoneToken) { $progress = $this->client->makeProgress($workDoneToken ?? uniqid('tkn', true)); @@ -578,6 +579,8 @@ function () use ($workDoneToken) { return new InitializeResult($serverCapabilities, $initializeResultServerInfo); }, ); + + return $promise; } /** diff --git a/stubs/CoreGenericClasses.phpstub b/stubs/CoreGenericClasses.phpstub index 93cb8cb42f0..83c834429bd 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -18,7 +18,7 @@ interface Traversable { * * @template-implements Traversable */ -class Generator implements Traversable { +final class Generator implements Traversable { /** * @psalm-ignore-nullable-return * @return ?TValue Can return any type.