From 5c3d9fd6598e798a2c338820997d540749b90b25 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 10:12:25 +0100 Subject: [PATCH 01/20] Fix kafka stubs --- dictionaries/CallMap.php | 8 ++++---- dictionaries/CallMap_historical.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 601210ac0..fec8a8e8c 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -10567,7 +10567,7 @@ return [ 'rd_kafka_offset_tail' => ['int', 'cnt'=>'int'], 'RdKafka::addBrokers' => ['int', 'broker_list'=>'string'], 'RdKafka::flush' => ['int', 'timeout_ms'=>'int'], -'RdKafka::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka::getOutQLen' => ['int'], 'RdKafka::newQueue' => ['RdKafka\Queue'], 'RdKafka::newTopic' => ['RdKafka\Topic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], @@ -10582,7 +10582,7 @@ return [ 'RdKafka\Conf::setStatsCb' => ['void', 'callback'=>'callable'], 'RdKafka\Consumer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], 'RdKafka\Consumer::addBrokers' => ['int', 'broker_list'=>'string'], -'RdKafka\Consumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka\Consumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka\Consumer::getOutQLen' => ['int'], 'RdKafka\Consumer::newQueue' => ['RdKafka\Queue'], 'RdKafka\Consumer::newTopic' => ['RdKafka\ConsumerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], @@ -10601,7 +10601,7 @@ return [ 'RdKafka\KafkaConsumer::commitAsync' => ['void', 'message_or_offsets='=>'RdKafka\Message|RdKafka\TopicPartition[]|null'], 'RdKafka\KafkaConsumer::consume' => ['RdKafka\Message', 'timeout_ms'=>'int'], 'RdKafka\KafkaConsumer::getAssignment' => ['RdKafka\TopicPartition[]'], -'RdKafka\KafkaConsumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\KafkaConsumerTopic', 'timeout_ms'=>'int'], +'RdKafka\KafkaConsumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\KafkaConsumerTopic', 'timeout_ms'=>'int'], 'RdKafka\KafkaConsumer::getSubscription' => ['array'], 'RdKafka\KafkaConsumer::subscribe' => ['void', 'topics'=>'array'], 'RdKafka\KafkaConsumer::unsubscribe' => ['void'], @@ -10629,7 +10629,7 @@ return [ 'RdKafka\Metadata\Topic::getTopic' => ['string'], 'RdKafka\Producer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], 'RdKafka\Producer::addBrokers' => ['int', 'broker_list'=>'string'], -'RdKafka\Producer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], +'RdKafka\Producer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka\Producer::getOutQLen' => ['int'], 'RdKafka\Producer::newQueue' => ['RdKafka\Queue'], 'RdKafka\Producer::newTopic' => ['RdKafka\ProducerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 91089cbb2..8acc97724 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -5198,7 +5198,7 @@ return [ 'RarException::setUsingExceptions' => ['RarEntry', 'using_exceptions'=>'bool'], 'RdKafka::addBrokers' => ['int', 'broker_list'=>'string'], 'RdKafka::flush' => ['int', 'timeout_ms'=>'int'], - 'RdKafka::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], + 'RdKafka::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka::getOutQLen' => ['int'], 'RdKafka::newQueue' => ['RdKafka\Queue'], 'RdKafka::newTopic' => ['RdKafka\Topic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], @@ -5213,7 +5213,7 @@ return [ 'RdKafka\Conf::setStatsCb' => ['void', 'callback'=>'callable'], 'RdKafka\Consumer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], 'RdKafka\Consumer::addBrokers' => ['int', 'broker_list'=>'string'], - 'RdKafka\Consumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], + 'RdKafka\Consumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka\Consumer::getOutQLen' => ['int'], 'RdKafka\Consumer::newQueue' => ['RdKafka\Queue'], 'RdKafka\Consumer::newTopic' => ['RdKafka\ConsumerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], @@ -5232,7 +5232,7 @@ return [ 'RdKafka\KafkaConsumer::commitAsync' => ['void', 'message_or_offsets='=>'RdKafka\Message|RdKafka\TopicPartition[]|null'], 'RdKafka\KafkaConsumer::consume' => ['RdKafka\Message', 'timeout_ms'=>'int'], 'RdKafka\KafkaConsumer::getAssignment' => ['RdKafka\TopicPartition[]'], - 'RdKafka\KafkaConsumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\KafkaConsumerTopic', 'timeout_ms'=>'int'], + 'RdKafka\KafkaConsumer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\KafkaConsumerTopic', 'timeout_ms'=>'int'], 'RdKafka\KafkaConsumer::getSubscription' => ['array'], 'RdKafka\KafkaConsumer::subscribe' => ['void', 'topics'=>'array'], 'RdKafka\KafkaConsumer::unsubscribe' => ['void'], @@ -5260,7 +5260,7 @@ return [ 'RdKafka\Metadata\Topic::getTopic' => ['string'], 'RdKafka\Producer::__construct' => ['void', 'conf='=>'?RdKafka\Conf'], 'RdKafka\Producer::addBrokers' => ['int', 'broker_list'=>'string'], - 'RdKafka\Producer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'RdKafka\Topic', 'timeout_ms'=>'int'], + 'RdKafka\Producer::getMetadata' => ['RdKafka\Metadata', 'all_topics'=>'bool', 'only_topic='=>'?RdKafka\Topic', 'timeout_ms'=>'int'], 'RdKafka\Producer::getOutQLen' => ['int'], 'RdKafka\Producer::newQueue' => ['RdKafka\Queue'], 'RdKafka\Producer::newTopic' => ['RdKafka\ProducerTopic', 'topic_name'=>'string', 'topic_conf='=>'?RdKafka\TopicConf'], From 7b25ca75f4739d7f25eea19479107d7a2628c6b0 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 19 Jan 2022 11:45:19 +0100 Subject: [PATCH 02/20] Resolve generics of inherited pseudo methods (fix #7419) --- .../Call/Method/MissingMethodCallHandler.php | 19 ++-- src/Psalm/Internal/Codebase/Populator.php | 3 + .../Reflector/ClassLikeNodeScanner.php | 9 +- src/Psalm/Storage/ClassLikeStorage.php | 10 +++ tests/MagicMethodAnnotationTest.php | 86 +++++++++++++++++++ 5 files changed, 120 insertions(+), 7 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index 29801b70b..f9e196c96 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -113,7 +113,7 @@ class MissingMethodCallHandler $found_generic_params = ClassTemplateParamCollector::collect( $codebase, - $class_storage, + $defining_class_storage, $class_storage, $method_name_lc, $lhs_type_part, @@ -157,7 +157,7 @@ class MissingMethodCallHandler $codebase, $return_type_candidate, $defining_class_storage->name, - $fq_class_name, + $lhs_type_part, $defining_class_storage->parent_class ); @@ -280,7 +280,7 @@ class MissingMethodCallHandler $found_generic_params = ClassTemplateParamCollector::collect( $codebase, - $class_storage, + $defining_class_storage, $class_storage, $method_name_lc, $lhs_type_part, @@ -336,7 +336,7 @@ class MissingMethodCallHandler $codebase, $return_type_candidate, $defining_class_storage->name, - $fq_class_name, + $lhs_type_part, $defining_class_storage->parent_class, true, false, @@ -423,11 +423,20 @@ class MissingMethodCallHandler ClassLikeStorage $static_class_storage, string $method_name_lc ): ?array { + if (isset($static_class_storage->declaring_pseudo_method_ids[$method_name_lc])) { + $method_id = $static_class_storage->declaring_pseudo_method_ids[$method_name_lc]; + $class_storage = $codebase->classlikes->getStorageFor($method_id->fq_class_name); + + if ($class_storage && isset($class_storage->pseudo_methods[$method_name_lc])) { + return [$class_storage->pseudo_methods[$method_name_lc], $class_storage]; + } + } + if ($pseudo_method_storage = $static_class_storage->pseudo_methods[$method_name_lc] ?? null) { return [$pseudo_method_storage, $static_class_storage]; } - $ancestors = $static_class_storage->class_implements + $static_class_storage->parent_classes; + $ancestors = $static_class_storage->class_implements; foreach ($ancestors as $fq_class_name => $_) { $class_storage = $codebase->classlikes->getStorageFor($fq_class_name); diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index c293367f7..09f672854 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -448,6 +448,7 @@ class Populator $storage->pseudo_property_set_types += $trait_storage->pseudo_property_set_types; $storage->pseudo_methods += $trait_storage->pseudo_methods; + $storage->declaring_pseudo_method_ids += $trait_storage->declaring_pseudo_method_ids; } } @@ -606,6 +607,7 @@ class Populator $parent_storage->dependent_classlikes[strtolower($storage->name)] = true; $storage->pseudo_methods += $parent_storage->pseudo_methods; + $storage->declaring_pseudo_method_ids += $parent_storage->declaring_pseudo_method_ids; } private function populateInterfaceDataFromParentInterfaces( @@ -694,6 +696,7 @@ class Populator $this->inheritMethodsFromParent($storage, $parent_interface_storage); $storage->pseudo_methods += $parent_interface_storage->pseudo_methods; + $storage->declaring_pseudo_method_ids += $parent_interface_storage->declaring_pseudo_method_ids; } $storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces); diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 476cb781e..43ceed01f 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -598,11 +598,16 @@ class ClassLikeNodeScanner /** @var MethodStorage */ $pseudo_method_storage = $functionlike_node_scanner->start($method, true); + $lc_method_name = strtolower($method->name->name); if ($pseudo_method_storage->is_static) { - $storage->pseudo_static_methods[strtolower($method->name->name)] = $pseudo_method_storage; + $storage->pseudo_static_methods[$lc_method_name] = $pseudo_method_storage; } else { - $storage->pseudo_methods[strtolower($method->name->name)] = $pseudo_method_storage; + $storage->pseudo_methods[$lc_method_name] = $pseudo_method_storage; + $storage->declaring_pseudo_method_ids[$lc_method_name] = new MethodIdentifier( + $fq_classlike_name, + $method->name->name + ); } $storage->sealed_methods = true; diff --git a/src/Psalm/Storage/ClassLikeStorage.php b/src/Psalm/Storage/ClassLikeStorage.php index 4a09b9edb..0119b950a 100644 --- a/src/Psalm/Storage/ClassLikeStorage.php +++ b/src/Psalm/Storage/ClassLikeStorage.php @@ -236,6 +236,16 @@ class ClassLikeStorage */ public $pseudo_static_methods = []; + /** + * Maps pseudo method names to the original declaring method identifier + * The key is the method name in lowercase, and the value is the original `MethodIdentifier` instance + * + * This property contains all pseudo methods declared on ancestors. + * + * @var array + */ + public $declaring_pseudo_method_ids = []; + /** * @var array */ diff --git a/tests/MagicMethodAnnotationTest.php b/tests/MagicMethodAnnotationTest.php index e2c4afdfe..5098d05f8 100644 --- a/tests/MagicMethodAnnotationTest.php +++ b/tests/MagicMethodAnnotationTest.php @@ -819,6 +819,92 @@ class MagicMethodAnnotationTest extends TestCase consumeInt(B::bar());' ], + 'returnThisShouldKeepGenerics' => [ + ' $a */ + $a = new A(); + $b = $a->foo(); + + /** @var I $i */ + $c = $i->foo();', + [ + '$b' => 'A', + '$c' => 'I', + ] + ], + 'genericsOfInheritedMethodsShouldBeResolved' => [ + ' + */ + class A implements I + { + public function __call(string $name, array $args) {} + } + + /** + * @template E + * @extends I + */ + interface I2 extends I {} + + class B {} + + /** + * @template E + * @method E get() + */ + class C + { + public function __call(string $name, array $args) {} + } + + /** + * @template E + * @extends C + */ + class D extends C {} + + /** @var A $a */ + $a = new A(); + $b = $a->get(); + + /** @var I2 $i */ + $c = $i->get(); + + /** @var D $d */ + $d = new D(); + $e = $d->get();', + [ + '$b' => 'B', + '$c' => 'B', + '$e' => 'B', + ] + ], ]; } From cee9eb0ead149971fa45c812f3d361459c7af870 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Mon, 10 Jan 2022 13:50:16 +0100 Subject: [PATCH 03/20] PHP 8.1: Report missing typehints in overridden native methods --- .../Internal/Analyzer/MethodComparator.php | 29 ++++++++++++++++- tests/MethodSignatureTest.php | 31 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 16ff2d9d9..480d0eced 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -27,6 +27,7 @@ use Psalm\Issue\OverriddenMethodAccess; use Psalm\Issue\ParamNameMismatch; use Psalm\Issue\TraitMethodSignatureMismatch; use Psalm\IssueBuffer; +use Psalm\Storage\AttributeStorage; use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FunctionLikeParameter; use Psalm\Storage\MethodStorage; @@ -35,6 +36,7 @@ use Psalm\Type\Atomic\TNull; use Psalm\Type\Atomic\TTemplateParam; use Psalm\Type\Union; +use function array_filter; use function in_array; use function strpos; use function strtolower; @@ -113,6 +115,27 @@ class MethodComparator ); } + if (!$guide_classlike_storage->user_defined + && $implementer_classlike_storage->user_defined + && $codebase->analysis_php_version_id >= 8_01_00 + && ($guide_method_storage->return_type + || $guide_method_storage->signature_return_type + ) + && !$implementer_method_storage->signature_return_type + && !array_filter( + $implementer_method_storage->attributes, + fn (AttributeStorage $s) => $s->fq_class_name === 'ReturnTypeWillChange' + ) + ) { + IssueBuffer::maybeAdd( + new MethodSignatureMismatch( + 'Method ' . $cased_implementer_method_id . ' is missing a return type signature!', + $implementer_method_storage->location ?: $code_location + ), + $suppressed_issues + $implementer_classlike_storage->suppressed_issues + ); + } + if ($guide_method_storage->return_type && $implementer_method_storage->return_type && !$implementer_method_storage->inherited_return_type @@ -862,7 +885,11 @@ class MethodComparator $implementer_signature_return_type, $guide_signature_return_type ) - : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type); + : (!$implementer_signature_return_type + && $guide_signature_return_type->isMixed() + ? false + : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type) + ); if (!$is_contained_by) { if ($codebase->php_major_version >= 8 diff --git a/tests/MethodSignatureTest.php b/tests/MethodSignatureTest.php index 11f217b17..9862b9463 100644 --- a/tests/MethodSignatureTest.php +++ b/tests/MethodSignatureTest.php @@ -1569,6 +1569,37 @@ class MethodSignatureTest extends TestCase ', 'error_message' => 'MethodSignatureMismatch', ], + 'noMixedTypehintInDescendant' => [ + ' 'MethodSignatureMismatch', + [], + false, + '8.0' + ], + 'noTypehintInNativeDescendant' => [ + ' 'MethodSignatureMismatch', + [], + false, + '8.1' + ], ]; } } From c98930d6ce94ef1e7a20612a408573bc61390acf Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 11 Jan 2022 10:18:15 +0100 Subject: [PATCH 04/20] Fix syntax --- src/Psalm/Internal/Analyzer/MethodComparator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 480d0eced..a1abe86bb 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -117,7 +117,7 @@ class MethodComparator if (!$guide_classlike_storage->user_defined && $implementer_classlike_storage->user_defined - && $codebase->analysis_php_version_id >= 8_01_00 + && $codebase->analysis_php_version_id >= 80100 && ($guide_method_storage->return_type || $guide_method_storage->signature_return_type ) From d8be15a83df8d1f41b8fe3eef4f94f13905fbd0d Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 11 Jan 2022 10:20:37 +0100 Subject: [PATCH 05/20] More legacy php changes --- src/Psalm/Internal/Analyzer/MethodComparator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index a1abe86bb..499850cc8 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -124,7 +124,9 @@ class MethodComparator && !$implementer_method_storage->signature_return_type && !array_filter( $implementer_method_storage->attributes, - fn (AttributeStorage $s) => $s->fq_class_name === 'ReturnTypeWillChange' + function (AttributeStorage $s) { + return $s->fq_class_name === 'ReturnTypeWillChange'; + } ) ) { IssueBuffer::maybeAdd( From e54d666a2eee5b70cf5440154f48bc8f2c322a26 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 11 Jan 2022 10:23:19 +0100 Subject: [PATCH 06/20] cs-fix --- src/Psalm/Internal/Analyzer/MethodComparator.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 499850cc8..a0c3e6621 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -890,7 +890,10 @@ class MethodComparator : (!$implementer_signature_return_type && $guide_signature_return_type->isMixed() ? false - : UnionTypeComparator::isContainedByInPhp($implementer_signature_return_type, $guide_signature_return_type) + : UnionTypeComparator::isContainedByInPhp( + $implementer_signature_return_type, + $guide_signature_return_type + ) ); if (!$is_contained_by) { From d970661182e64ab5040f3e430c84633cb59f4934 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 12:20:50 +0100 Subject: [PATCH 07/20] Add separate issue --- config.xsd | 1 + docs/running_psalm/error_levels.md | 1 + docs/running_psalm/issues.md | 1 + .../MethodSignatureMustProvideReturnType.md | 48 +++++++++++++++++++ .../Internal/Analyzer/MethodComparator.php | 5 +- .../MethodSignatureMustProvideReturnType.php | 9 ++++ tests/MethodSignatureTest.php | 4 +- 7 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md create mode 100644 src/Psalm/Issue/MethodSignatureMustProvideReturnType.php diff --git a/config.xsd b/config.xsd index afd6069e5..7d076a14b 100644 --- a/config.xsd +++ b/config.xsd @@ -325,6 +325,7 @@ + diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index 3babb9248..c58cca026 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -56,6 +56,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [InvalidThrow](issues/InvalidThrow.md) - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) + - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MissingDependency](issues/MissingDependency.md) - [MissingFile](issues/MissingFile.md) - [MissingImmutableAnnotation](issues/MissingImmutableAnnotation.md) diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index c471c86f4..50385f60e 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -99,6 +99,7 @@ - [LoopInvalidation](issues/LoopInvalidation.md) - [MethodSignatureMismatch](issues/MethodSignatureMismatch.md) - [MethodSignatureMustOmitReturnType](issues/MethodSignatureMustOmitReturnType.md) + - [MethodSignatureMustProvideReturnType](issues/MethodSignatureMustProvideReturnType.md) - [MismatchingDocblockParamType](issues/MismatchingDocblockParamType.md) - [MismatchingDocblockPropertyType](issues/MismatchingDocblockPropertyType.md) - [MismatchingDocblockReturnType](issues/MismatchingDocblockReturnType.md) diff --git a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md new file mode 100644 index 000000000..596193095 --- /dev/null +++ b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md @@ -0,0 +1,48 @@ +# MethodSignatureMustProvideReturnType + +In PHP 8.1+, [most non-final internal methods now require overriding methods to declare a compatible return type, otherwise a deprecated notice is emitted during inheritance validation](https://www.php.net/manual/en/migration81.incompatible.php#migration81.incompatible.core.type-compatibility-internal). + +This issue is emitted when a method overriding a native method is defined without a return type. + + +```php +location ?: $code_location ), $suppressed_issues + $implementer_classlike_storage->suppressed_issues diff --git a/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php b/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php new file mode 100644 index 000000000..ab05f06c0 --- /dev/null +++ b/src/Psalm/Issue/MethodSignatureMustProvideReturnType.php @@ -0,0 +1,9 @@ + 'MethodSignatureMismatch', + 'error_message' => 'MethodSignatureMustProvideReturnType', [], false, '8.0' @@ -1595,7 +1595,7 @@ class MethodSignatureTest extends TestCase } } ', - 'error_message' => 'MethodSignatureMismatch', + 'error_message' => 'MethodSignatureMustProvideReturnType', [], false, '8.1' From 9021b13b654cc6f21bc371ef548b9575326e4cf3 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 12:29:44 +0100 Subject: [PATCH 08/20] Update --- .../MethodSignatureMustProvideReturnType.md | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md index 596193095..baf9bb2b1 100644 --- a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md +++ b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md @@ -4,45 +4,14 @@ In PHP 8.1+, [most non-final internal methods now require overriding methods to This issue is emitted when a method overriding a native method is defined without a return type. +**Only if** the return type cannot be declared to keep support for PHP 7, a `#[ReturnTypeWillChange]` attribute can be added to silence the PHP deprecation notice and Psalm issue. ```php Date: Wed, 19 Jan 2022 12:29:49 +0100 Subject: [PATCH 09/20] Update --- .../issues/MethodSignatureMustProvideReturnType.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md index baf9bb2b1..80aa53c8a 100644 --- a/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md +++ b/docs/running_psalm/issues/MethodSignatureMustProvideReturnType.md @@ -11,7 +11,7 @@ This issue is emitted when a method overriding a native method is defined withou class A implements JsonSerializable { public function jsonSerialize() { - return ['type' = 'A']; + return ['type' => 'A']; } } ``` From 78a125ab95859b1dd8a5d1b6b093336619b05154 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 12:33:16 +0100 Subject: [PATCH 10/20] Fix --- tests/MethodSignatureTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MethodSignatureTest.php b/tests/MethodSignatureTest.php index 905c48a16..87c3bc71d 100644 --- a/tests/MethodSignatureTest.php +++ b/tests/MethodSignatureTest.php @@ -1582,7 +1582,7 @@ class MethodSignatureTest extends TestCase } } ', - 'error_message' => 'MethodSignatureMustProvideReturnType', + 'error_message' => 'MethodSignatureMismatch', [], false, '8.0' From 38945018f55339cae3a05f7e8d400c2ac049d592 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 12:39:08 +0100 Subject: [PATCH 11/20] Run analysis on all PHP versions --- src/Psalm/Internal/Analyzer/MethodComparator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index dd0b6ee64..cddeac63b 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -118,7 +118,6 @@ class MethodComparator if (!$guide_classlike_storage->user_defined && $implementer_classlike_storage->user_defined - && $codebase->analysis_php_version_id >= 80100 && ($guide_method_storage->return_type || $guide_method_storage->signature_return_type ) From 1914be4ca1adb12b3d02064cc407dfd6c4f1da26 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Wed, 19 Jan 2022 12:44:55 +0100 Subject: [PATCH 12/20] Rollback --- src/Psalm/Internal/Analyzer/MethodComparator.php | 1 + tests/DocumentationTest.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index cddeac63b..dd0b6ee64 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -118,6 +118,7 @@ class MethodComparator if (!$guide_classlike_storage->user_defined && $implementer_classlike_storage->user_defined + && $codebase->analysis_php_version_id >= 80100 && ($guide_method_storage->return_type || $guide_method_storage->signature_return_type ) diff --git a/tests/DocumentationTest.php b/tests/DocumentationTest.php index 0c8ab29d2..8ccc26002 100644 --- a/tests/DocumentationTest.php +++ b/tests/DocumentationTest.php @@ -218,6 +218,10 @@ class DocumentationTest extends TestCase $this->markTestSkipped(); } + if (strpos($error_message, 'MethodSignatureMustProvideReturnType') !== false) { + $php_version = '8.1'; + } + $this->project_analyzer->setPhpVersion($php_version, 'tests'); if ($check_references) { From 5e277d4060da6db2c2ed2ce6a6bd192044a9cf49 Mon Sep 17 00:00:00 2001 From: Rishi Kumar Ray Date: Wed, 19 Jan 2022 18:07:06 +0530 Subject: [PATCH 13/20] Fix Incomplete return type for mb_split() function --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_historical.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index fec8a8e8c..82601b018 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -7304,7 +7304,7 @@ return [ 'mb_regex_set_options' => ['string', 'options='=>'string|null'], 'mb_scrub' => ['string', 'string'=>'string', 'encoding='=>'string|null'], 'mb_send_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string|array', 'additional_params='=>'string|null'], -'mb_split' => ['list', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'], +'mb_split' => ['list|false', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'], 'mb_str_split' => ['list', 'string'=>'string', 'length='=>'positive-int', 'encoding='=>'string|null'], 'mb_strcut' => ['string', 'string'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string|null'], 'mb_strimwidth' => ['string', 'string'=>'string', 'start'=>'int', 'width'=>'int', 'trim_marker='=>'string', 'encoding='=>'string|null'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 8acc97724..2d3c36a46 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -12914,7 +12914,7 @@ return [ 'mb_regex_encoding' => ['string|bool', 'encoding='=>'string'], 'mb_regex_set_options' => ['string', 'options='=>'string'], 'mb_send_mail' => ['bool', 'to'=>'string', 'subject'=>'string', 'message'=>'string', 'additional_headers='=>'string|array', 'additional_params='=>'string'], - 'mb_split' => ['list', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'], + 'mb_split' => ['list|false', 'pattern'=>'string', 'string'=>'string', 'limit='=>'int'], 'mb_strcut' => ['string', 'string'=>'string', 'start'=>'int', 'length='=>'?int', 'encoding='=>'string'], 'mb_strimwidth' => ['string', 'string'=>'string', 'start'=>'int', 'width'=>'int', 'trim_marker='=>'string', 'encoding='=>'string'], 'mb_stripos' => ['int|false', 'haystack'=>'string', 'needle'=>'string', 'offset='=>'int', 'encoding='=>'string'], From f7252417e456d84d6f49d78865dad814da64f0c3 Mon Sep 17 00:00:00 2001 From: Vincent Date: Wed, 19 Jan 2022 13:49:41 +0100 Subject: [PATCH 14/20] Fix typing (ref #7430) --- .../Expression/Call/Method/MissingMethodCallHandler.php | 4 ++-- .../Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index f9e196c96..c8c3c70d7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -157,7 +157,7 @@ class MissingMethodCallHandler $codebase, $return_type_candidate, $defining_class_storage->name, - $lhs_type_part, + $lhs_type_part instanceof Atomic\TNamedObject ? $lhs_type_part : $fq_class_name, $defining_class_storage->parent_class ); @@ -336,7 +336,7 @@ class MissingMethodCallHandler $codebase, $return_type_candidate, $defining_class_storage->name, - $lhs_type_part, + $lhs_type_part instanceof Atomic\TNamedObject ? $lhs_type_part : $fq_class_name, $defining_class_storage->parent_class, true, false, diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 43ceed01f..d2b0ed25f 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -606,7 +606,7 @@ class ClassLikeNodeScanner $storage->pseudo_methods[$lc_method_name] = $pseudo_method_storage; $storage->declaring_pseudo_method_ids[$lc_method_name] = new MethodIdentifier( $fq_classlike_name, - $method->name->name + $lc_method_name ); } From 52a7f0694ec610ec69d7a5346c2d1245d8fbe471 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 19 Jan 2022 19:29:16 +0100 Subject: [PATCH 15/20] drop compatibility aliases --- tests/Config/PluginListTest.php | 2 +- tests/Config/PluginTest.php | 2 +- tests/DocumentationTest.php | 2 +- tests/FileUpdates/ErrorAfterUpdateTest.php | 2 +- tests/IncludeTest.php | 2 +- tests/PsalmPluginTest.php | 2 +- tests/TaintTest.php | 2 +- tests/TestCase.php | 23 ------------------- tests/Traits/InvalidCodeAnalysisTestTrait.php | 6 +---- tests/UnusedCodeTest.php | 2 +- tests/UnusedVariableTest.php | 2 +- 11 files changed, 10 insertions(+), 37 deletions(-) diff --git a/tests/Config/PluginListTest.php b/tests/Config/PluginListTest.php index 1274eff9d..52fce5d3b 100644 --- a/tests/Config/PluginListTest.php +++ b/tests/Config/PluginListTest.php @@ -159,7 +159,7 @@ class PluginListTest extends TestCase { $plugin_list = new PluginList($this->config_file->reveal(), $this->composer_lock->reveal()); $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageRegExp('/unknown plugin/i'); + $this->expectExceptionMessageMatches('/unknown plugin/i'); $plugin_list->resolvePluginClass('vendor/package'); } diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index 6fd8b4976..88099c63f 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -1156,7 +1156,7 @@ class PluginTest extends TestCase $this->project_analyzer->trackTaintedInputs(); $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/TaintedHtml/'); + $this->expectExceptionMessageMatches('/TaintedHtml/'); $this->analyzeFile($file_path, new Context()); } diff --git a/tests/DocumentationTest.php b/tests/DocumentationTest.php index 0c8ab29d2..99f04cacf 100644 --- a/tests/DocumentationTest.php +++ b/tests/DocumentationTest.php @@ -237,7 +237,7 @@ class DocumentationTest extends TestCase } $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $codebase = $this->project_analyzer->getCodebase(); $codebase->config->visitPreloadedStubFiles($codebase); diff --git a/tests/FileUpdates/ErrorAfterUpdateTest.php b/tests/FileUpdates/ErrorAfterUpdateTest.php index 1f2bade42..c9805f589 100644 --- a/tests/FileUpdates/ErrorAfterUpdateTest.php +++ b/tests/FileUpdates/ErrorAfterUpdateTest.php @@ -90,7 +90,7 @@ class ErrorAfterUpdateTest extends TestCase } $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $codebase->reloadFiles($this->project_analyzer, array_keys($end_files)); diff --git a/tests/IncludeTest.php b/tests/IncludeTest.php index 0f06ff7e6..bfbc4b557 100644 --- a/tests/IncludeTest.php +++ b/tests/IncludeTest.php @@ -88,7 +88,7 @@ class IncludeTest extends TestCase $config->skip_checks_on_unresolvable_includes = false; $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $codebase->scanFiles(); diff --git a/tests/PsalmPluginTest.php b/tests/PsalmPluginTest.php index 4a33e6034..592c2d97e 100644 --- a/tests/PsalmPluginTest.php +++ b/tests/PsalmPluginTest.php @@ -150,7 +150,7 @@ class PsalmPluginTest extends TestCase $help_command = new CommandTester($this->app->find('help')); $help_command->execute(['command_name' => $command]); $output = $help_command->getDisplay(); - $this->assertRegExp('/Usage:.*$\s+' . preg_quote($command, '/') . '\b/m', $output); + $this->assertMatchesRegularExpression('/Usage:.*$\s+' . preg_quote($command, '/') . '\b/m', $output); } /** diff --git a/tests/TaintTest.php b/tests/TaintTest.php index cfae07433..75419f8a6 100644 --- a/tests/TaintTest.php +++ b/tests/TaintTest.php @@ -50,7 +50,7 @@ class TaintTest extends TestCase } $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $file_path = self::$src_dir_path . 'somefile.php'; diff --git a/tests/TestCase.php b/tests/TestCase.php index f48b50cc9..69502082e 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -24,7 +24,6 @@ use function defined; use function getcwd; use function ini_set; use function is_string; -use function method_exists; use const ARRAY_FILTER_USE_KEY; use const DIRECTORY_SEPARATOR; @@ -153,28 +152,6 @@ class TestCase extends BaseTestCase return $this->getName($withDataSet); } - /** - * Compatibility alias - */ - public function expectExceptionMessageRegExp(string $regexp): void - { - if (method_exists($this, 'expectExceptionMessageMatches')) { - $this->expectExceptionMessageMatches($regexp); - } else { - /** @psalm-suppress UndefinedMethod */ - parent::expectExceptionMessageRegExp($regexp); - } - } - - public static function assertRegExp(string $pattern, string $string, string $message = ''): void - { - if (method_exists(self::class, 'assertMatchesRegularExpression')) { - self::assertMatchesRegularExpression($pattern, $string, $message); - } else { - parent::assertRegExp($pattern, $string, $message); - } - } - public static function assertArrayKeysAreStrings(array $array, string $message = ''): void { $validKeys = array_filter($array, 'is_string', ARRAY_FILTER_USE_KEY); diff --git a/tests/Traits/InvalidCodeAnalysisTestTrait.php b/tests/Traits/InvalidCodeAnalysisTestTrait.php index 0bbd2f463..daff91c92 100644 --- a/tests/Traits/InvalidCodeAnalysisTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisTestTrait.php @@ -81,11 +81,7 @@ trait InvalidCodeAnalysisTestTrait $this->expectException(CodeException::class); - if (method_exists($this, 'expectExceptionMessageMatches')) { - $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); - } else { - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); - } + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $codebase = $this->project_analyzer->getCodebase(); $codebase->config->visitPreloadedStubFiles($codebase); diff --git a/tests/UnusedCodeTest.php b/tests/UnusedCodeTest.php index f913b1211..341de7fe6 100644 --- a/tests/UnusedCodeTest.php +++ b/tests/UnusedCodeTest.php @@ -90,7 +90,7 @@ class UnusedCodeTest extends TestCase } $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $file_path = self::$src_dir_path . 'somefile.php'; diff --git a/tests/UnusedVariableTest.php b/tests/UnusedVariableTest.php index 055be038d..61fdb840f 100644 --- a/tests/UnusedVariableTest.php +++ b/tests/UnusedVariableTest.php @@ -82,7 +82,7 @@ class UnusedVariableTest extends TestCase } $this->expectException(CodeException::class); - $this->expectExceptionMessageRegExp('/\b' . preg_quote($error_message, '/') . '\b/'); + $this->expectExceptionMessageMatches('/\b' . preg_quote($error_message, '/') . '\b/'); $file_path = self::$src_dir_path . 'somefile.php'; From 7d07f42790b7aa4a4c2d2d14cae386d52d96c697 Mon Sep 17 00:00:00 2001 From: orklah Date: Wed, 19 Jan 2022 20:36:09 +0100 Subject: [PATCH 16/20] fix CS --- tests/Traits/InvalidCodeAnalysisTestTrait.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Traits/InvalidCodeAnalysisTestTrait.php b/tests/Traits/InvalidCodeAnalysisTestTrait.php index daff91c92..79573f75a 100644 --- a/tests/Traits/InvalidCodeAnalysisTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisTestTrait.php @@ -7,7 +7,6 @@ use Psalm\Context; use Psalm\Exception\CodeException; use function is_int; -use function method_exists; use function preg_quote; use function str_replace; use function strpos; From 9b22d63c5bc15c114007c7afda978b144d85096e Mon Sep 17 00:00:00 2001 From: Alberto Piai Date: Thu, 20 Jan 2022 13:54:37 +0100 Subject: [PATCH 17/20] fix proc_open stub for php >= 8.0 The argument names are defined here: https://github.com/php/php-src/blob/PHP-8.0.0/ext/standard/basic_functions_arginfo.h#L1773-L1780 --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_80_delta.php | 4 ++++ tests/CoreStubsTest.php | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 82601b018..e4977a1d2 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -10291,7 +10291,7 @@ return [ 'proc_close' => ['int', 'process'=>'resource'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], 'proc_nice' => ['bool', 'priority'=>'int'], -'proc_open' => ['resource|false', 'cmd'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], +'proc_open' => ['resource|false', 'command'=>'string|array', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], 'proc_terminate' => ['bool', 'process'=>'resource', 'signal='=>'int'], 'projectionObj::__construct' => ['void', 'projectionString'=>'string'], 'projectionObj::getUnits' => ['int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index f561a75ea..cf8567bf7 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -1129,6 +1129,10 @@ return [ 'old' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], 'new' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], ], + 'proc_open' => [ + 'old' => ['resource|false', 'cmd'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], + 'new' => ['resource|false', 'command'=>'string|array', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], + ], 'session_set_cookie_params' => [ 'old' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], 'new' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index cdae51716..8bbe64355 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -18,5 +18,20 @@ class CoreStubsTest extends TestCase new RecursiveArrayIterator([], RecursiveArrayIterator::CHILD_ARRAYS_ONLY);' ]; + yield 'proc_open() named arguments' => [ + ' [], + 'error_levels' => [], + 'php_version' => '8.0', + ]; } } From d39ccb50bf35507a3e815239c94a53775470c83c Mon Sep 17 00:00:00 2001 From: Alberto Piai Date: Fri, 21 Jan 2022 08:59:16 +0100 Subject: [PATCH 18/20] unify argument names in historical and current CallMap for proc_open Since before 8.0 the named arguments were not part of the interface, we don't care about the intermediate steps of the proc_open definition. For consistency, this makes the definition the same across all versions. This also fixes the type for the `options` argument already in CallMap_historical to be nullable. The names of the arguments are now consistent across versions, while the delta for 7.4 reflects the change of the `command` argument from `string` to `string|array`. --- dictionaries/CallMap_74_delta.php | 4 ++-- dictionaries/CallMap_80_delta.php | 4 ---- dictionaries/CallMap_historical.php | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/dictionaries/CallMap_74_delta.php b/dictionaries/CallMap_74_delta.php index c4d7a9e9e..83b9108bd 100644 --- a/dictionaries/CallMap_74_delta.php +++ b/dictionaries/CallMap_74_delta.php @@ -33,8 +33,8 @@ return [ 'new' => ['bool', 'hash'=>'string', 'algo'=>'int|string|null', 'options='=>'array'], ], 'proc_open' => [ - 'old' => ['resource|false', 'command'=>'string', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], - 'new' => ['resource|false', 'cmd'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], + 'old' => ['resource|false', 'command'=>'string', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], + 'new' => ['resource|false', 'command'=>'string|array', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], ], ], 'removed' => [ diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index cf8567bf7..f561a75ea 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -1129,10 +1129,6 @@ return [ 'old' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], 'new' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}', 'process'=>'resource'], ], - 'proc_open' => [ - 'old' => ['resource|false', 'cmd'=>'string|array', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], - 'new' => ['resource|false', 'command'=>'string|array', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], - ], 'session_set_cookie_params' => [ 'old' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], 'new' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 2d3c36a46..69b1b371d 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -14473,7 +14473,7 @@ return [ 'proc_close' => ['int', 'process'=>'resource'], 'proc_get_status' => ['array{command: string, pid: int, running: bool, signaled: bool, stopped: bool, exitcode: int, termsig: int, stopsig: int}|false', 'process'=>'resource'], 'proc_nice' => ['bool', 'priority'=>'int'], - 'proc_open' => ['resource|false', 'command'=>'string', 'descriptorspec'=>'array', '&w_pipes'=>'resource[]', 'cwd='=>'?string', 'env='=>'?array', 'other_options='=>'array'], + 'proc_open' => ['resource|false', 'command'=>'string', 'descriptor_spec'=>'array', '&pipes'=>'resource[]', 'cwd='=>'?string', 'env_vars='=>'?array', 'options='=>'?array'], 'proc_terminate' => ['bool', 'process'=>'resource', 'signal='=>'int'], 'projectionObj::__construct' => ['void', 'projectionString'=>'string'], 'projectionObj::getUnits' => ['int'], From bb577ec271dc7f12032d94a71dbaeb7de1122750 Mon Sep 17 00:00:00 2001 From: Matthias Wirtz Date: Wed, 5 Jan 2022 07:33:26 +0100 Subject: [PATCH 19/20] change nullable for array signature to be equal to param signature --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_73_delta.php | 4 ++-- dictionaries/CallMap_80_delta.php | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index e4977a1d2..c6c3e1b81 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -12069,7 +12069,7 @@ return [ 'session_reset' => ['bool'], 'session_save_path' => ['string', 'path='=>'string'], 'session_set_cookie_params' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], -'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool,samesite?:string}'], +'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:string}'], 'session_set_save_handler' => ['bool', 'open'=>'callable(string,string):bool', 'close'=>'callable():bool', 'read'=>'callable(string):string', 'write'=>'callable(string,string):bool', 'destroy'=>'callable(string):bool', 'gc'=>'callable(string):bool', 'create_sid='=>'callable():string', 'validate_sid='=>'callable(string):bool', 'update_timestamp='=>'callable(string):bool'], 'session_set_save_handler\'1' => ['bool', 'open'=>'SessionHandlerInterface', 'close='=>'bool'], 'session_start' => ['bool', 'options='=>'array'], diff --git a/dictionaries/CallMap_73_delta.php b/dictionaries/CallMap_73_delta.php index 65d5420b5..fdf6a9810 100644 --- a/dictionaries/CallMap_73_delta.php +++ b/dictionaries/CallMap_73_delta.php @@ -7,7 +7,7 @@ * The 'added' section contains function/method names from FunctionSignatureMap (And alternates, if applicable) that do not exist in php 7.2 * The 'removed' section contains the signatures that were removed in php 7.3. * The 'changed' section contains functions for which the signature has changed for php 7.3. - * Each function in the 'changed' section has an 'old' and a 'new' section, + * Each function in the 'changed' section has an 'old' and a 'new' section, * representing the function as it was in PHP 7.2 and in PHP 7.3, respectively * * @see CallMap.php @@ -42,7 +42,7 @@ return [ 'is_countable' => ['bool', 'value'=>'mixed'], 'net_get_interfaces' => ['array>|false'], 'openssl_pkey_derive' => ['string|false', 'public_key'=>'mixed', 'private_key'=>'mixed', 'key_length='=>'?int'], - 'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:?string,secure?:bool,httponly?:bool,samesite?:string}'], + 'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:string,secure?:bool,httponly?:bool,samesite?:string}'], 'setcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], 'setrawcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], 'socket_wsaprotocol_info_export' => ['string|false', 'socket'=>'resource', 'process_id'=>'int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index f561a75ea..7d8946777 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -1133,6 +1133,10 @@ return [ 'old' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], 'new' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], ], + 'session_set_cookie_params\'1' => [ + 'old' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:string,secure?:bool,httponly?:bool,samesite?:string}'], + 'new' => ['bool', 'options'=>'array{lifetime?:int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:string}'], + ], 'socket_accept' => [ 'old' => ['resource|false', 'socket'=>'resource'], 'new' => ['Socket|false', 'socket'=>'Socket'], From 5b23a0c51e588d40ce3b3bba1fccfcf9a854c009 Mon Sep 17 00:00:00 2001 From: Matthias Wirtz Date: Sat, 22 Jan 2022 15:34:16 +0100 Subject: [PATCH 20/20] in array form all attributes are nullable since PHP 7.3 --- dictionaries/CallMap.php | 2 +- dictionaries/CallMap_73_delta.php | 2 +- dictionaries/CallMap_80_delta.php | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index c6c3e1b81..1873918d5 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -12069,7 +12069,7 @@ return [ 'session_reset' => ['bool'], 'session_save_path' => ['string', 'path='=>'string'], 'session_set_cookie_params' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], -'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:string}'], +'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:?int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:?string}'], 'session_set_save_handler' => ['bool', 'open'=>'callable(string,string):bool', 'close'=>'callable():bool', 'read'=>'callable(string):string', 'write'=>'callable(string,string):bool', 'destroy'=>'callable(string):bool', 'gc'=>'callable(string):bool', 'create_sid='=>'callable():string', 'validate_sid='=>'callable(string):bool', 'update_timestamp='=>'callable(string):bool'], 'session_set_save_handler\'1' => ['bool', 'open'=>'SessionHandlerInterface', 'close='=>'bool'], 'session_start' => ['bool', 'options='=>'array'], diff --git a/dictionaries/CallMap_73_delta.php b/dictionaries/CallMap_73_delta.php index fdf6a9810..7b1d9e508 100644 --- a/dictionaries/CallMap_73_delta.php +++ b/dictionaries/CallMap_73_delta.php @@ -42,7 +42,7 @@ return [ 'is_countable' => ['bool', 'value'=>'mixed'], 'net_get_interfaces' => ['array>|false'], 'openssl_pkey_derive' => ['string|false', 'public_key'=>'mixed', 'private_key'=>'mixed', 'key_length='=>'?int'], - 'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:string,secure?:bool,httponly?:bool,samesite?:string}'], + 'session_set_cookie_params\'1' => ['bool', 'options'=>'array{lifetime?:?int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:?string}'], 'setcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], 'setrawcookie\'1' => ['bool', 'name'=>'string', 'value='=>'string', 'options='=>'array'], 'socket_wsaprotocol_info_export' => ['string|false', 'socket'=>'resource', 'process_id'=>'int'], diff --git a/dictionaries/CallMap_80_delta.php b/dictionaries/CallMap_80_delta.php index 7d8946777..f561a75ea 100644 --- a/dictionaries/CallMap_80_delta.php +++ b/dictionaries/CallMap_80_delta.php @@ -1133,10 +1133,6 @@ return [ 'old' => ['bool', 'lifetime'=>'int', 'path='=>'string', 'domain='=>'string', 'secure='=>'bool', 'httponly='=>'bool'], 'new' => ['bool', 'lifetime'=>'int', 'path='=>'?string', 'domain='=>'?string', 'secure='=>'?bool', 'httponly='=>'?bool'], ], - 'session_set_cookie_params\'1' => [ - 'old' => ['bool', 'options'=>'array{lifetime?:int,path?:string,domain?:string,secure?:bool,httponly?:bool,samesite?:string}'], - 'new' => ['bool', 'options'=>'array{lifetime?:int,path?:?string,domain?:?string,secure?:?bool,httponly?:?bool,samesite?:string}'], - ], 'socket_accept' => [ 'old' => ['resource|false', 'socket'=>'resource'], 'new' => ['Socket|false', 'socket'=>'Socket'],