Skip to content

feat: add NarrowTooWideReturnTypeRector#7150

Merged
samsonasik merged 33 commits intorectorphp:mainfrom
calebdw:calebdw/push-rzmvxsssrszs
Aug 18, 2025
Merged

feat: add NarrowTooWideReturnTypeRector#7150
samsonasik merged 33 commits intorectorphp:mainfrom
calebdw:calebdw/push-rzmvxsssrszs

Conversation

@calebdw
Copy link
Contributor

@calebdw calebdw commented Aug 17, 2025

Hello!

Supercedes #7147
Closes rectorphp/rector#9309

This adds a rule that automatically narrows too wide return types (helps solve the return.unusedType phpstan errors):

final class SomeClass
{
-   public function getData(): string|int|\DateTime
+   public function getData(): string|int
    {
        if (rand(0, 1)) {
            return 'text';
        }
        return 1000;
    }
}

Thanks!

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

This skips all native mixed returns as can't be sure

@calebdw calebdw force-pushed the calebdw/push-rzmvxsssrszs branch from 83bd9a1 to 3e59478 Compare August 17, 2025 02:06
@samsonasik
Copy link
Member

Please run on existing rector-src codebase:

bin/rector

this change seems invalid and should be skipped:

9) src/Console/Command/SetupCICommand.php:50

    ---------- begin diff ----------
@@ @@
         return self::SUCCESS;
     }

-    /**
-     * @return CiDetector::CI_*|null
-     */
-    private function resolveCurrentCI(): ?string
+    private function resolveCurrentCI(): null
     {
         if (file_exists(getcwd() . '/.github')) {
             return CiDetector::CI_GITHUB_ACTIONS;
    ----------- end diff -----------

Applied rules:
 * RemoveUselessReturnTagRector
 * NarrowTooWideReturnTypeRector

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

@samsonasik, there's only 1 change now:

image

@samsonasik
Copy link
Member

samsonasik commented Aug 17, 2025

The issue on @return CiDetector::CI_*|null seems because of CiDetector::CI_* which type incorrectly detection target as autocompleting type.

so probably better loop the docblock type, if found something that can't be converted to native type, just skip it, eg these pattern:

CiDetector::CI_*
@template TType of object|null
@return TType

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

is CiDetector::CI_* even a valid phpdoc?

The docs would seem to want to use key-of<Type::ARRAY_CONST> or value-of<Type::ARRAY_CONST>

https://phpstan.org/writing-php-code/phpdoc-types#key-and-value-types-of-arrays-and-iterables

@samsonasik
Copy link
Member

Yes, that's valid docblock, see https://phpstan.org/r/77bb2280-9ef5-48a3-846a-02009e413399

@samsonasik
Copy link
Member

You can fixture to skip this:

use OndraM\CiDetector\CiDetector;

/**
 * @return int|CiDetector::CI_*|null
 */
function skipSomeComplexDocSubType(): int|string|null
{
    if (file_exists(getcwd() . '/.github')) {
        return CiDetector::CI_GITHUB_ACTIONS;
    }

    if (file_exists(getcwd() . '/.gitlab-ci.yml')) {
        return CiDetector::CI_GITLAB;
    }

    if (rand(0, 1)) {
        return 1;
    }

    return null;
}

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

The issue on @return CiDetector::CI_|null seems because of CiDetector::CI_ which type incorrectly detection target as autocompleting type.

I'm digging into this, and it's a bug in the TypeFactory::uniquateTypes() method---when the code falls back to the phpstan resolution it works perfectly

@samsonasik
Copy link
Member

Please add fixture for this example to be skipped:

use OndraM\CiDetector\CiDetector;

/**
 * @return int|CiDetector::CI_*|null
 */
function skipSomeComplexDocSubType(): int|string|null
{
    if (file_exists(getcwd() . '/.github')) {
        return CiDetector::CI_GITHUB_ACTIONS;
    }

    if (file_exists(getcwd() . '/.gitlab-ci.yml')) {
        return CiDetector::CI_GITLAB;
    }

    if (rand(0, 1)) {
        return 1;
    }

    return null;
}

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

Yes, I'm working on that. However, commenting this out causes the code to pass:

image

@samsonasik
Copy link
Member

That seems not, as its docblock value become extracted, and not what we want:

1) src/Console/Command/SetupCICommand.php:51

    ---------- begin diff ----------
@@ @@
     }

     /**
-     * @return int|CiDetector::CI_*|null
+     * @return 'GitHub Actions'|'GitLab'|int|null
      */
     private function resolveCurrentCI(): int|string|null
     {
@@ @@
             return CiDetector::CI_GITLAB;
         }

-        if (rand(0, 1)) {
+        if (random_int(0, 1) !== 0) {
             return 1;
         }
    ----------- end diff -----------

Applied rules:
 * ExplicitBoolCompareRector
 * NarrowTooWideReturnTypeRector
 * RandomFunctionRector

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

Sorry, I should have clarified, I have another test fixture with a union of exact values and it's removing all but the last one

@calebdw
Copy link
Contributor Author

calebdw commented Aug 17, 2025

I'll work on this more tomorrow, I'm going to restructure the rector so that the native and phpdoc return types are handled separately

@calebdw calebdw requested a review from samsonasik August 18, 2025 05:06
@calebdw
Copy link
Contributor Author

calebdw commented Aug 18, 2025

@samsonasik, I've refactored the code to handle the native return types and the phpdoc return types separately and this helps with flexibility.

However, the test is failing because calling $phpDocInfo->getReturnType() removes extra constant string types:

image

Commenting out this line causes the test to pass because the extra constant types are no longer being removed:

image

@samsonasik
Copy link
Member

samsonasik commented Aug 18, 2025

I will check your branch and take care the rest, I prefer to just skip these for now:

  • final result is true, false, null
  • if there is docblock that can't be convertible to native type: constfetch magic * , template type, etc
  • both parent and child exists in union

which improvement can be in separate PRs

@samsonasik samsonasik self-assigned this Aug 18, 2025
@samsonasik
Copy link
Member

@calebdw this should be good to merge now 👍 , I skipped various complex sub type and mostly placeholder: null, false, true usage.

Improvement if needed, can be in separate PR.

thank you

@TomasVotruba let's merge it and test in the wild ;)

@samsonasik samsonasik merged commit 6e90294 into rectorphp:main Aug 18, 2025
49 checks passed
@TomasVotruba
Copy link
Member

Can't wait to test this on clients' projects 👌😎

Thank you @calebdw

@calebdw calebdw deleted the calebdw/push-rzmvxsssrszs branch August 18, 2025 12:38
@github-actions
Copy link
Contributor

This pull request has been automatically locked because it has been closed for 150 days. Please open a new PR if you want to continue the work.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rector to automatically remove return type that the function never returns

3 participants