From 3142e0459e922574d05cbb4e3312459f705bd2bd Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Wed, 1 Jul 2026 21:55:45 +0200 Subject: [PATCH] [TypeDeclaration] Split ParamTypeByMethodCallTypeRector into Object, Scalar and Array rules Extract shared engine into AbstractParamTypeByMethodCallTypeRector; each concrete rule owns a disjoint param type group via isMatchingParamType(): - ObjectParamTypeByMethodCallTypeRector - object types (incl. nullable) - ScalarParamTypeByMethodCallTypeRector - scalar types (string/int/float/bool) - ArrayParamTypeByMethodCallTypeRector - array types (incl. nullable) - ParamTypeByMethodCallTypeRector - remaining compound (cross-group unions, iterable, callable) Types are null-stripped first, so nullable variants route to their group. --- composer.json | 2 +- ...rayParamTypeByMethodCallTypeRectorTest.php | 28 +++ .../Fixture/nullable_array.php.inc | 35 +++ .../Fixture/skip_object_type.php.inc | 17 ++ .../Fixture/skip_scalar_type.php.inc | 15 ++ .../Fixture/throw_without_new.php.inc | 4 +- .../config/configured_rule.php | 11 + .../Fixture/nullable_object_param.php.inc | 39 ++++ .../Fixture/object_param.php.inc | 39 ++++ .../Fixture/skip_array_type.php.inc | 15 ++ .../Fixture/skip_scalar_type.php.inc | 15 ++ ...ectParamTypeByMethodCallTypeRectorTest.php | 28 +++ .../config/configured_rule.php | 11 + .../Fixture/avoid_nested_call.php.inc | 45 ---- .../Fixture/default_null.php.inc | 41 ---- .../Fixture/func_call_elsewhere.php.inc | 31 --- .../Fixture/skip_pure_array.php.inc | 15 ++ .../Fixture/static_call_elsewhere.php.inc | 31 --- .../Source/FunctionTyped.php | 9 - .../Fixture/also_arrow_function.php.inc | 8 +- .../Fixture/also_closure.php.inc | 8 +- .../Fixture/default_null_union.php.inc | 8 +- .../Fixture/func_call_elsewhere.php.inc | 31 +++ .../Fixture/skip_array_type.php.inc | 15 ++ .../Fixture/skip_object_type.php.inc | 17 ++ .../Fixture/some_class.php.inc | 8 +- .../Fixture/static_call_elsewhere.php.inc | 31 +++ ...lt_constant_integer_type_param_int.php.inc | 4 +- ...larParamTypeByMethodCallTypeRectorTest.php | 28 +++ .../Source/FunctionTyped.php | 9 + .../Source/SomeTypedService.php | 20 ++ .../config/configured_rule.php | 11 + ...bstractParamTypeByMethodCallTypeRector.php | 201 ++++++++++++++++++ .../ArrayParamTypeByMethodCallTypeRector.php | 75 +++++++ .../ObjectParamTypeByMethodCallTypeRector.php | 75 +++++++ .../ParamTypeByMethodCallTypeRector.php | 201 ++---------------- .../ScalarParamTypeByMethodCallTypeRector.php | 75 +++++++ src/Config/Level/TypeDeclarationLevel.php | 6 + 38 files changed, 906 insertions(+), 356 deletions(-) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/ArrayParamTypeByMethodCallTypeRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/nullable_array.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ArrayParamTypeByMethodCallTypeRector}/Fixture/throw_without_new.php.inc (71%) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/config/configured_rule.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/nullable_object_param.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/object_param.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/ObjectParamTypeByMethodCallTypeRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/config/configured_rule.php delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/avoid_nested_call.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/default_null.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/func_call_elsewhere.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_pure_array.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/static_call_elsewhere.php.inc delete mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ScalarParamTypeByMethodCallTypeRector}/Fixture/also_arrow_function.php.inc (54%) rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ScalarParamTypeByMethodCallTypeRector}/Fixture/also_closure.php.inc (56%) rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ScalarParamTypeByMethodCallTypeRector}/Fixture/default_null_union.php.inc (54%) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/func_call_elsewhere.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ScalarParamTypeByMethodCallTypeRector}/Fixture/some_class.php.inc (51%) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/static_call_elsewhere.php.inc rename rules-tests/TypeDeclaration/Rector/ClassMethod/{ParamTypeByMethodCallTypeRector => ScalarParamTypeByMethodCallTypeRector}/Fixture/with_default_constant_integer_type_param_int.php.inc (66%) create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/ScalarParamTypeByMethodCallTypeRectorTest.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/FunctionTyped.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/SomeTypedService.php create mode 100644 rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/config/configured_rule.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/AbstractParamTypeByMethodCallTypeRector.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector.php create mode 100644 rules/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector.php diff --git a/composer.json b/composer.json index 3f843fbf07d..db74cc2c8e8 100644 --- a/composer.json +++ b/composer.json @@ -100,7 +100,7 @@ "files": [ "tests/debug_functions.php", "rules-tests/Transform/Rector/FuncCall/FuncCallToMethodCallRector/Source/some_view_function.php", - "rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php", + "rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/FunctionTyped.php", "rules-tests/TypeDeclaration/Rector/StmtsAwareInterface/SafeDeclareStrictTypesRector/Source/functions.php", "rules-tests/Php70/Rector/ClassMethod/Php4ConstructorRector/Source/ParentClass.php" ] diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/ArrayParamTypeByMethodCallTypeRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/ArrayParamTypeByMethodCallTypeRectorTest.php new file mode 100644 index 00000000000..8f89e0ac2f4 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/ArrayParamTypeByMethodCallTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/nullable_array.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/nullable_array.php.inc new file mode 100644 index 00000000000..261e93af598 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/nullable_array.php.inc @@ -0,0 +1,35 @@ +use($value); + } + + private function use(?array $value): void + { + } +} + +?> +----- +use($value); + } + + private function use(?array $value): void + { + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc new file mode 100644 index 00000000000..a3eb601adfe --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc @@ -0,0 +1,17 @@ +use($value); + } + + private function use(stdClass $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc new file mode 100644 index 00000000000..19485ec7dd3 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc @@ -0,0 +1,15 @@ +use($value); + } + + private function use(string $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc similarity index 71% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc rename to rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc index 227762e9993..20ef3d2ef81 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector/Fixture/throw_without_new.php.inc @@ -1,6 +1,6 @@ withRules([ArrayParamTypeByMethodCallTypeRector::class]) + ->withPhpVersion(PhpVersion::PHP_80); diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/nullable_object_param.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/nullable_object_param.php.inc new file mode 100644 index 00000000000..b2abe73decf --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/nullable_object_param.php.inc @@ -0,0 +1,39 @@ +use($value); + } + + private function use(?stdClass $value): void + { + } +} + +?> +----- +use($value); + } + + private function use(?stdClass $value): void + { + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/object_param.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/object_param.php.inc new file mode 100644 index 00000000000..a00bd794e63 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/object_param.php.inc @@ -0,0 +1,39 @@ +use($value); + } + + private function use(stdClass $value): void + { + } +} + +?> +----- +use($value); + } + + private function use(stdClass $value): void + { + } +} + +?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc new file mode 100644 index 00000000000..350a13c74e1 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc @@ -0,0 +1,15 @@ +use($value); + } + + private function use(array $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc new file mode 100644 index 00000000000..1466d52b589 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/Fixture/skip_scalar_type.php.inc @@ -0,0 +1,15 @@ +use($value); + } + + private function use(string $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/ObjectParamTypeByMethodCallTypeRectorTest.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/ObjectParamTypeByMethodCallTypeRectorTest.php new file mode 100644 index 00000000000..d08883ac632 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/ObjectParamTypeByMethodCallTypeRectorTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/config/configured_rule.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/config/configured_rule.php new file mode 100644 index 00000000000..8d4a9eae7c7 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector/config/configured_rule.php @@ -0,0 +1,11 @@ +withRules([ObjectParamTypeByMethodCallTypeRector::class]) + ->withPhpVersion(PhpVersion::PHP_80); diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/avoid_nested_call.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/avoid_nested_call.php.inc deleted file mode 100644 index bfe3e7573e9..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/avoid_nested_call.php.inc +++ /dev/null @@ -1,45 +0,0 @@ -someTypedService->run($value); - }; - } -} - -?> ------ -someTypedService->run($value); - }; - } -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/default_null.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/default_null.php.inc deleted file mode 100644 index fa1c5789494..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/default_null.php.inc +++ /dev/null @@ -1,41 +0,0 @@ -someTypedService->withDefaultNull($value); - } -} - -?> ------ -someTypedService->withDefaultNull($value); - } -} - -?> diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/func_call_elsewhere.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/func_call_elsewhere.php.inc deleted file mode 100644 index 7bbd0d6bab2..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/func_call_elsewhere.php.inc +++ /dev/null @@ -1,31 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_pure_array.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_pure_array.php.inc new file mode 100644 index 00000000000..5aa27108910 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/skip_pure_array.php.inc @@ -0,0 +1,15 @@ +use($value); + } + + private function use(array $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/static_call_elsewhere.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/static_call_elsewhere.php.inc deleted file mode 100644 index 5184535590e..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/static_call_elsewhere.php.inc +++ /dev/null @@ -1,31 +0,0 @@ - ------ - diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php deleted file mode 100644 index 460bfc7c9ab..00000000000 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Source/FunctionTyped.php +++ /dev/null @@ -1,9 +0,0 @@ - +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc new file mode 100644 index 00000000000..158858feb41 --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_array_type.php.inc @@ -0,0 +1,15 @@ +use($value); + } + + private function use(array $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc new file mode 100644 index 00000000000..da28901380e --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/skip_object_type.php.inc @@ -0,0 +1,17 @@ +use($value); + } + + private function use(stdClass $value): void + { + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc similarity index 51% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc rename to rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc index ab8030187c2..547f2b8d8d2 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/some_class.php.inc @@ -1,8 +1,8 @@ +----- + diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc similarity index 66% rename from rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc rename to rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc index 08fdf355636..f0585ec3e13 100644 --- a/rules-tests/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Fixture/with_default_constant_integer_type_param_int.php.inc @@ -1,6 +1,6 @@ doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/FunctionTyped.php b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/FunctionTyped.php new file mode 100644 index 00000000000..2bfd1e39cdc --- /dev/null +++ b/rules-tests/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector/Source/FunctionTyped.php @@ -0,0 +1,9 @@ +withRules([ScalarParamTypeByMethodCallTypeRector::class]) + ->withPhpVersion(PhpVersion::PHP_80); diff --git a/rules/TypeDeclaration/Rector/ClassMethod/AbstractParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/AbstractParamTypeByMethodCallTypeRector.php new file mode 100644 index 00000000000..d53e90cfe9f --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/AbstractParamTypeByMethodCallTypeRector.php @@ -0,0 +1,201 @@ +> + */ + public function getNodeTypes(): array + { + return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; + } + + /** + * @param ClassMethod|Function_|Closure|ArrowFunction $node + */ + public function refactor(Node $node): ?Node + { + if ($node->params === []) { + return null; + } + + // has params with at least one missing type + if (! $this->hasAtLeastOneParamWithoutType($node)) { + return null; + } + + if ($node instanceof ClassMethod && $this->shouldSkipClassMethod($node)) { + return null; + } + + /** @var array $callers */ + $callers = $this->betterNodeFinder->findInstancesOfScoped( + [$node], + [StaticCall::class, MethodCall::class, FuncCall::class] + ); + + // keep only callers with args + $callersWithArgs = array_filter( + $callers, + fn (StaticCall|MethodCall|FuncCall $caller): bool => $caller->args !== [] + ); + + if ($callersWithArgs === []) { + return null; + } + + $hasChanged = $this->refactorFunctionLike($node, $callersWithArgs); + if ($hasChanged) { + return $node; + } + + return null; + } + + /** + * Decide whether the inferred param type belongs to this rule's type group. + */ + abstract protected function isMatchingParamType(Type $type): bool; + + private function shouldSkipClassMethod(ClassMethod $classMethod): bool + { + $isMissingParameterTypes = false; + foreach ($classMethod->params as $param) { + if ($param->type instanceof Node) { + continue; + } + + if ($param->variadic) { + continue; + } + + $isMissingParameterTypes = true; + } + + if ($isMissingParameterTypes === false) { + return true; + } + + return $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod); + } + + private function shouldSkipParam( + Param $param, + ClassMethod|Function_|Closure|ArrowFunction $functionLike + ): bool { + // already has type, skip + if ($param->type instanceof Node) { + return true; + } + + if ($param->variadic) { + return true; + } + + return ! $this->paramTypeAddGuard->isLegal($param, $functionLike); + } + + /** + * @param array $callers + */ + private function refactorFunctionLike( + ClassMethod|Closure|Function_|ArrowFunction $functionLike, + array $callers + ): bool { + $hasChanged = false; + + foreach ($functionLike->params as $param) { + if ($this->shouldSkipParam($param, $functionLike)) { + continue; + } + + $paramTypes = []; + foreach ($callers as $caller) { + $matchCallParam = $this->callerParamMatcher->matchCallParam($caller, $param); + + // nothing to do with param, continue + if (! $matchCallParam instanceof Param) { + continue; + } + + $paramType = $this->callerParamMatcher->matchCallParamType($param, $matchCallParam); + if (! $paramType instanceof Node) { + $paramTypes = []; + break; + } + + if ($caller->getAttribute(AttributeKey::IS_RIGHT_AND)) { + $paramTypes = []; + break; + } + + $paramTypes[] = $this->phpParserNodeMapper->mapToPHPStanType($paramType); + } + + if ($paramTypes === []) { + continue; + } + + $type = $this->typeFactory->createMixedPassedOrUnionType($paramTypes); + + // only handle the type group owned by this rule + if (! $this->isMatchingParamType($type)) { + continue; + } + + $paramNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PARAM); + + if ($paramNodeType instanceof Node) { + $param->type = $paramNodeType; + $hasChanged = true; + } + } + + return $hasChanged; + } + + private function hasAtLeastOneParamWithoutType(ClassMethod|Function_|Closure|ArrowFunction $functionLike): bool + { + return array_any($functionLike->params, fn (Param $param): bool => ! $param->type instanceof Node); + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector.php new file mode 100644 index 00000000000..c590f2af621 --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/ArrayParamTypeByMethodCallTypeRector.php @@ -0,0 +1,75 @@ +someTypedService->run($value); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeTypedService +{ + public function run(array $values) + { + } +} + +final class UseDependency +{ + public function __construct( + private SomeTypedService $someTypedService + ) { + } + + public function go(array $value) + { + $this->someTypedService->run($value); + } +} +CODE_SAMPLE + ), + ]); + } + + protected function isMatchingParamType(Type $type): bool + { + return TypeCombinator::removeNull($type)->isArray() + ->yes(); + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector.php new file mode 100644 index 00000000000..3f5ff4227dd --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/ObjectParamTypeByMethodCallTypeRector.php @@ -0,0 +1,75 @@ +someTypedService->run($value); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeTypedService +{ + public function run(SomeObject $object) + { + } +} + +final class UseDependency +{ + public function __construct( + private SomeTypedService $someTypedService + ) { + } + + public function go(SomeObject $value) + { + $this->someTypedService->run($value); + } +} +CODE_SAMPLE + ), + ]); + } + + protected function isMatchingParamType(Type $type): bool + { + return TypeCombinator::removeNull($type)->isObject() + ->yes(); + } +} diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php index aa8da3d326a..092d2fa3f97 100644 --- a/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php +++ b/rules/TypeDeclaration/Rector/ClassMethod/ParamTypeByMethodCallTypeRector.php @@ -4,52 +4,30 @@ namespace Rector\TypeDeclaration\Rector\ClassMethod; -use PhpParser\Node; -use PhpParser\Node\Expr\ArrowFunction; -use PhpParser\Node\Expr\Closure; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\MethodCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Param; -use PhpParser\Node\Stmt\ClassMethod; -use PhpParser\Node\Stmt\Function_; -use Rector\NodeTypeResolver\Node\AttributeKey; -use Rector\NodeTypeResolver\PHPStan\Type\TypeFactory; -use Rector\PhpParser\Node\BetterNodeFinder; -use Rector\PHPStanStaticTypeMapper\Enum\TypeKind; -use Rector\Rector\AbstractRector; -use Rector\StaticTypeMapper\Mapper\PhpParserNodeMapper; -use Rector\StaticTypeMapper\StaticTypeMapper; -use Rector\TypeDeclaration\Guard\ParamTypeAddGuard; -use Rector\TypeDeclaration\NodeAnalyzer\CallerParamMatcher; -use Rector\VendorLocker\ParentClassMethodTypeOverrideGuard; +use PHPStan\Type\Type; +use PHPStan\Type\TypeCombinator; use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample; use Symplify\RuleDocGenerator\ValueObject\RuleDefinition; /** + * Handles the remaining compound param type group: everything that is neither a pure object, + * a pure scalar, nor a pure array (e.g. cross-group unions like array|string, iterable, callable). + * + * @see \Rector\TypeDeclaration\Rector\ClassMethod\ObjectParamTypeByMethodCallTypeRector for object types + * @see \Rector\TypeDeclaration\Rector\ClassMethod\ScalarParamTypeByMethodCallTypeRector for scalar types + * @see \Rector\TypeDeclaration\Rector\ClassMethod\ArrayParamTypeByMethodCallTypeRector for array types * @see \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ParamTypeByMethodCallTypeRector\ParamTypeByMethodCallTypeRectorTest */ -final class ParamTypeByMethodCallTypeRector extends AbstractRector +final class ParamTypeByMethodCallTypeRector extends AbstractParamTypeByMethodCallTypeRector { - public function __construct( - private readonly CallerParamMatcher $callerParamMatcher, - private readonly ParentClassMethodTypeOverrideGuard $parentClassMethodTypeOverrideGuard, - private readonly ParamTypeAddGuard $paramTypeAddGuard, - private readonly BetterNodeFinder $betterNodeFinder, - private readonly PhpParserNodeMapper $phpParserNodeMapper, - private readonly StaticTypeMapper $staticTypeMapper, - private readonly TypeFactory $typeFactory - ) { - } - public function getRuleDefinition(): RuleDefinition { - return new RuleDefinition('Change param type based on passed method call type', [ + return new RuleDefinition('Change compound param type based on passed method call type', [ new CodeSample( <<<'CODE_SAMPLE' class SomeTypedService { - public function run(string $name) + public function run(iterable $values) { } } @@ -71,7 +49,7 @@ public function go($value) <<<'CODE_SAMPLE' class SomeTypedService { - public function run(string $name) + public function run(iterable $values) { } } @@ -83,7 +61,7 @@ public function __construct( ) { } - public function go(string $value) + public function go(iterable $value) { $this->someTypedService->run($value); } @@ -93,149 +71,16 @@ public function go(string $value) ]); } - /** - * @return array> - */ - public function getNodeTypes(): array - { - return [ClassMethod::class, Function_::class, Closure::class, ArrowFunction::class]; - } - - /** - * @param ClassMethod|Function_|Closure|ArrowFunction $node - */ - public function refactor(Node $node): ?Node - { - if ($node->params === []) { - return null; - } - - // has params with at least one missing type - if (! $this->hasAtLeastOneParamWithoutType($node)) { - return null; - } - - if ($node instanceof ClassMethod && $this->shouldSkipClassMethod($node)) { - return null; - } - - /** @var array $callers */ - $callers = $this->betterNodeFinder->findInstancesOfScoped( - [$node], - [StaticCall::class, MethodCall::class, FuncCall::class] - ); - - // keep only callers with args - $callersWithArgs = array_filter( - $callers, - fn (StaticCall|MethodCall|FuncCall $caller): bool => $caller->args !== [] - ); - - if ($callersWithArgs === []) { - return null; - } - - $hasChanged = $this->refactorFunctionLike($node, $callersWithArgs); - if ($hasChanged) { - return $node; - } - - return null; - } - - private function shouldSkipClassMethod(ClassMethod $classMethod): bool - { - $isMissingParameterTypes = false; - foreach ($classMethod->params as $param) { - if ($param->type instanceof Node) { - continue; - } - - if ($param->variadic) { - continue; - } - - $isMissingParameterTypes = true; - } - - if ($isMissingParameterTypes === false) { - return true; - } - - return $this->parentClassMethodTypeOverrideGuard->hasParentClassMethod($classMethod); - } - - private function shouldSkipParam( - Param $param, - ClassMethod|Function_|Closure|ArrowFunction $functionLike - ): bool { - // already has type, skip - if ($param->type instanceof Node) { - return true; - } - - if ($param->variadic) { - return true; - } - - return ! $this->paramTypeAddGuard->isLegal($param, $functionLike); - } - - /** - * @param array $callers - */ - private function refactorFunctionLike( - ClassMethod|Closure|Function_|ArrowFunction $functionLike, - array $callers - ): bool { - $hasChanged = false; - - foreach ($functionLike->params as $param) { - if ($this->shouldSkipParam($param, $functionLike)) { - continue; - } - - $paramTypes = []; - foreach ($callers as $caller) { - $matchCallParam = $this->callerParamMatcher->matchCallParam($caller, $param); - - // nothing to do with param, continue - if (! $matchCallParam instanceof Param) { - continue; - } - - $paramType = $this->callerParamMatcher->matchCallParamType($param, $matchCallParam); - if (! $paramType instanceof Node) { - $paramTypes = []; - break; - } - - if ($caller->getAttribute(AttributeKey::IS_RIGHT_AND)) { - $paramTypes = []; - break; - } - - $paramTypes[] = $this->phpParserNodeMapper->mapToPHPStanType($paramType); - } - - if ($paramTypes === []) { - continue; - } - - $type = $this->typeFactory->createMixedPassedOrUnionType($paramTypes); - $paramNodeType = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($type, TypeKind::PARAM); - - if ($paramNodeType instanceof Node) { - $param->type = $paramNodeType; - $hasChanged = true; - } - } - - return $hasChanged; - } - - private function hasAtLeastOneParamWithoutType(ClassMethod|Function_|Closure|ArrowFunction $functionLike): bool + protected function isMatchingParamType(Type $type): bool { - return array_any($functionLike->params, fn (Param $param): bool => ! $param->type instanceof Node); + $bareType = TypeCombinator::removeNull($type); + + // remaining compound types: not a pure object, scalar, nor array (iterable, callable, cross-group unions) + return ! $bareType->isObject() + ->yes() + && ! $bareType->isScalar() + ->yes() + && ! $bareType->isArray() + ->yes(); } } diff --git a/rules/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector.php b/rules/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector.php new file mode 100644 index 00000000000..39a088a9346 --- /dev/null +++ b/rules/TypeDeclaration/Rector/ClassMethod/ScalarParamTypeByMethodCallTypeRector.php @@ -0,0 +1,75 @@ +someTypedService->run($value); + } +} +CODE_SAMPLE + , + <<<'CODE_SAMPLE' +class SomeTypedService +{ + public function run(string $name) + { + } +} + +final class UseDependency +{ + public function __construct( + private SomeTypedService $someTypedService + ) { + } + + public function go(string $value) + { + $this->someTypedService->run($value); + } +} +CODE_SAMPLE + ), + ]); + } + + protected function isMatchingParamType(Type $type): bool + { + return TypeCombinator::removeNull($type)->isScalar() + ->yes(); + } +} diff --git a/src/Config/Level/TypeDeclarationLevel.php b/src/Config/Level/TypeDeclarationLevel.php index 10a7a4dd509..9698fa73c33 100644 --- a/src/Config/Level/TypeDeclarationLevel.php +++ b/src/Config/Level/TypeDeclarationLevel.php @@ -28,12 +28,14 @@ use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationBasedOnParentClassMethodRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeFromTryCatchTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; +use Rector\TypeDeclaration\Rector\ClassMethod\ArrayParamTypeByMethodCallTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanConstReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\BoolReturnTypeFromBooleanStrictReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\KnownMagicClassMethodTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\NarrowObjectReturnTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\NumericReturnTypeFromStrictScalarReturnsRector; +use Rector\TypeDeclaration\Rector\ClassMethod\ObjectParamTypeByMethodCallTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByMethodCallTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ParamTypeByParentCallTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnNeverTypeRector; @@ -51,6 +53,7 @@ use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromSymfonySerializerRector; use Rector\TypeDeclaration\Rector\ClassMethod\ReturnUnionTypeRector; +use Rector\TypeDeclaration\Rector\ClassMethod\ScalarParamTypeByMethodCallTypeRector; use Rector\TypeDeclaration\Rector\ClassMethod\StrictArrayParamDimFetchRector; use Rector\TypeDeclaration\Rector\ClassMethod\StringReturnTypeFromStrictScalarReturnsRector; use Rector\TypeDeclaration\Rector\ClassMethod\StringReturnTypeFromStrictStringReturnsRector; @@ -155,6 +158,9 @@ final class TypeDeclarationLevel MergeDateTimePropertyTypeDeclarationRector::class, PropertyTypeFromStrictSetterGetterRector::class, ParamTypeByMethodCallTypeRector::class, + ObjectParamTypeByMethodCallTypeRector::class, + ScalarParamTypeByMethodCallTypeRector::class, + ArrayParamTypeByMethodCallTypeRector::class, TypedPropertyFromContainerGetSetUpRector::class, TypedPropertyFromGetRepositorySetUpRector::class, TypedPropertyFromAssignsRector::class,