Skip to content

Override static in return types with self in final classes #17725

Closed

Description

@rekmixa

Description

After implementing static types in return methods, I noticed one thing that was illogical, in my opinion.

We have the following code:

<?php

declare(strict_types=1);

interface A
{
    public function method1(): static;
}

abstract class B
{
    abstract public function method2(): static;
}

trait C
{
    abstract public function method3(): static;
}

final class Foo extends B implements A
{
    use C;

    public function method1(): static
    {
        return $this;
    }

    public function method2(): static
    {
        return $this;
    }

    public function method3(): static
    {
        return $this;
    }
}

$foo = new Foo();

var_dump($foo->method1());
var_dump($foo->method2());
var_dump($foo->method3());

It seems to me that in final classes, redefining return types in methods from static to self will not be a violation of covariance, because the final class will never have children. But now it triggers an error Fatal error: Declaration of Foo::method2(): Foo must be compatible with B::method2(): static. What do you think about this?

final class Foo extends B implements A
{
    use C;

    public function method1(): self
    {
        return $this;
    }

    public function method2(): self
    {
        return $this;
    }

    public function method3(): self
    {
        return $this;
    }
}

In addition, the same PHPStorm suggests replacing static types with self types in final classes (if this type is not specified in the prototype).

Below I attach a link to the pull request in which I implemented this logic.

#17724

  • There are no changes that break backward compatibility or covariance logic.

Activity

  1. added a commit that references this issue on Feb 7, 2025
  2. Girgias commented on Feb 7, 2025

    @Girgias
    Member

    I cannot see anything wrong with this transformation either.

    static ⊑ self where static is dependent on the class calling it. If self is final, then the class calling the method is always self as such static = self.

  3. iluuu1994 commented on Feb 8, 2025

    @iluuu1994
    Member

    It's sound, but is it necessary? If the child removes final, it will need to migrate back to static. But I don't object.

  4. Girgias commented on Feb 10, 2025

    @Girgias
    Member

    The one advantage I could see is for the engine to do this transformation, which could simplify some variance checks and give more robust Reflection information if/when we add isSubTypeOf style methods to reflection if done in tandem with #17755 / #11460

  5. iluuu1994 commented on Feb 10, 2025

    @iluuu1994
    Member

    Letting the engine do it would be fine, although it is obviously observable. Might be worth sending a quick e-mail to the list.

  6. added a commit that references this issue on Feb 10, 2025
  7. rekmixa commented on Feb 10, 2025

    @rekmixa
    ContributorAuthor

    I added union types support for this feature and wrote additional tests for it. But I don't understand how to add support for this with intersection types) So for now I just added the !intersection check :)

    https://github.com/php/php-src/pull/17724/files#diff-16d00017a3ed574cca152e8754246a1424db74e221934eb1c0c2513442882615R575

    Maybe someone else can do this?

  8. rekmixa commented on Feb 10, 2025

    @rekmixa
    ContributorAuthor

    It's sound, but is it necessary? If the child removes final, it will need to migrate back to static. But I don't object.

    Yes, there may be some inconvenience with this. But here, in fact, we are talking only about the fact that the language has the ability to replace static with self. This should not be mandatory. For me personally, it would be convenient. I am used to not using the static keyword inside when writing final classes, because it is a little confusing.

  9. rekmixa commented on Feb 10, 2025

    @rekmixa
    ContributorAuthor

    It's sound, but is it necessary? If the child removes final, it will need to migrate back to static. But I don't object.

    It seems to me that using static inside classes always implies that the class can have children. And when static occurs in the final class, it's a bit confusing. So I'd like to see the language provide the ability to replace static with self, because again, it's not a violation of covariance.

  10. rekmixa commented on Feb 10, 2025

    @rekmixa
    ContributorAuthor

    Letting the engine do it would be fine, although it is obviously observable. Might be worth sending a quick e-mail to the list.

    Okay, i'll send

  11. rekmixa commented on Feb 10, 2025

    @rekmixa
    ContributorAuthor
  12. added 2 commits that reference this issue on Feb 10, 2025
  13. 4 remaining items

  14. added 10 commits that reference this issue on Feb 11, 2025
  15. added a commit that references this issue on Mar 5, 2025
    4f5136c
  16. added a commit that references this issue on Nov 21, 2025
  17. added a commit that references this issue on Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions