Skip to content

Add static return type #5062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed

Add static return type #5062

wants to merge 9 commits into from

Conversation

nikic
Copy link
Member

@nikic nikic commented Jan 7, 2020

Implementation for https://wiki.php.net/rfc/static_return_type.

The type is only supported in covariant contexts (i.e. only return types right now), because it would be unsound anywhere else. static is considered a subtype of self.

Because the static type conflicts syntactically with static properties, it is excluded from property types on the grammar level.

The static is internally implemented as a builtin type because it has lots of magic semantics. Currently it is also exposed via ReflectionType::isBuiltin() as true. However, static is usually considered a class in userland, so it might make sense to hack that to return false? Now returns false.

@nikic nikic added the RFC label Jan 7, 2020
@nikic
Copy link
Member Author

nikic commented Jan 7, 2020

cc @muglug @nicolas-grekas I believe you were interested in having this.

@nicolas-grekas
Copy link
Contributor

Currently it is also exposed via ReflectionType::isBuiltin() as true

returning false would make more sense I agree, same as self and parent.

Because the static type conflicts syntactically with static properties, it is excluded from property types on the grammar level.

if we want to support it, it could be allowed as part of union types: public self|static $foo;.

Thank you for opening this! \o/

@SignpostMarv
Copy link

SignpostMarv commented Jan 7, 2020

However, static is usually considered a class in userland, so it might make sense to hack that to return false?

interface DateTimeInterface {
    public function add(DateTimeInterval $interval) : static;
    public static function createFromFormat(string $format , string $time [, DateTimeZone $timezone ]) : static;
    // etc.
}

i.e. altering DateTimeInterface to include the methods whose only difference between DateTime and DateTimeImmutable is the return type, wouldn't those be ReflectionType::isBuiltIn() === true ?

Copy link
Contributor

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially to be added as invalid scenario: static in combination of a final class:

final class A
{
    function foo(): static { /* ... */ } // is this valid, or an error?
}

Also, what happens when self is used in a child class that overrides a method that returns static?

class A {
    function foo(): static { /* ... */ }
}

class B extends A {
    function foo(): self { /* ... */ }
}

Similar with interfaces:

interface A {
    function foo(): static;
}

class B implements A {
    function foo(): self { /* ... */ } // valid or error?
}

@muglug
Copy link
Contributor

muglug commented Jan 7, 2020

Because the static type conflicts syntactically with static properties, it is excluded from property types on the grammar level.

I wonder if Hack uses this to avoid the clash (regardless, it doesn’t bother me)

@nicolas-grekas
Copy link
Contributor

nicolas-grekas commented Jan 7, 2020

Potentially to be added as invalid scenario: static in combination of a final class:

this looks valid to me, especially considering your next case:

what happens when self is used in a child class that overrides a method that returns static?

looks invalid to me, as it breaks the promise of the base class, stating that the method returns the same type as the object's class

makes sense?

@kocsismate
Copy link
Member

Potentially to be added as invalid scenario: static in combination of a final class:

I agree with Nicolas, it seems ok for me.

what happens when self is used in a child class that overrides a method that returns static?

Maybe I misinterpret LSP, but this example also seems ok for me: returning a child class (self) instead of the parent or any child class (static) is covariant - which is what is expected. Or am I missing something?

@nikic
Copy link
Member Author

nikic commented Jan 7, 2020

Replacing static with self is invalid, as tested in https://github.com/php/php-src/pull/5062/files#diff-1861cb2c956e77e7c533e2e73c1f9549.

A replacement from static to self could only (theoretically speaking) be legal if the new method is final, but I don't think we'd want to special-case that.

@muglug
Copy link
Contributor

muglug commented Jan 7, 2020

@kocsismate

Or am I missing something?

the danger comes here:

class A {
  public static function getInstance() : static {
    return new static();
  }

  public function foo() : void {}

  public static function getInstanceAndCall() : static {
    $i = static::getInstance();
    $i->foo();
    return $i;
  }
}

class B extends A {
  public static function getInstance() : self {
    return new self();
  }
}

class C extends B {}

calling C::getInstanceAndCall() should return an instance of C, but actually returns an instance of B.

@kocsismate
Copy link
Member

kocsismate commented Jan 7, 2020

@muglug Ok, now I see. Thanks for the explanation. I was only considering the most basic scenario (when there is only one child class)

@nikic
Copy link
Member Author

nikic commented Jan 8, 2020

RFC: https://wiki.php.net/rfc/static_return_type

@simensen
Copy link

simensen commented Jan 8, 2020

I'm all for this. I was working with @sgolemon on this as an idea at the end of last year.

https://phpc.social/@pollita/103256122403635150

Basic description: https://gist.github.com/simensen/729c4ac9056b06d60038377f59a92cfe

Sara's implementation: master...sgolemon:static-return

I haven't had time to circle back and try to get an RFC up. Now I guess I don't have to? :) Thanks for the work on this!!! Let me know how I can help champion this.

@TysonAndre
Copy link
Contributor

LGTM, I've wanted this for return types for a while.

Some phpt tests of traits would be useful in validating the implementation, e.g.

  1. A class inheriting a trait and/or an interface which declare a method with a return type of static
  2. A trait has a method returning static, the class overrides it with a return type of : self
  3. A parent class has a method returning static, the class uses a trait with a return type of : self

(I've had a lot of issues with writing code analyzing traits in the past)


object(A)#3 (0) {
}
Return value of A::test2() must be an instance of static, instance of A returned
Copy link
Contributor

@TysonAndre TysonAndre Jan 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minor comment that can be fixed in subsequent PRs for 8.0-dev if needed:

This might be confusing at runtime in more complicated code - the error message gives no indication of what static is. (e.g. if a method returning static has incorrect caching based on $obj->getKey() which can be reused)

An error message mentioning what the current class scope is may save debugging time, e.g. Return value of A::test2() must be an instance of static (currently static is %s(class/trait/interface) B), instance of A returned


Also, what should happen for code such as this? I assume it should be the same TypeError message, because instanceof only applies to classes and interfaces. (this is also a potential test case)

trait T {
    public static function f() : static {
        return new A();
    }
}
class A {
    use T;
}
T::f();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the implementation to display the type static resolves to instead. We already do this for self and parent, so it makes sense to me to treat static the same way.

@mvorisek
Copy link
Contributor

mvorisek commented Feb 6, 2020

Future scope - method cascading:

class Test {
    public function doWhatever(): $this {
        // Do whatever.
        return $this;
    }
}

What do you think about to implement it like the void, but instead of returning nothing return $this.

Syntax sugar in examples:

class Test {
    public function doWhatever(): $this {
        // will return $this even there is no return in the method
    }

    public function doWhatever2(): $this {
        // will return $this even there is no return value specified
        return;
    }

    public function doWhatever3(): $this {
        // error, return value specified, but return value defined in method declaration
        return $this;
    }
}

Reflection should behave exactly the same as if the return type is defined as static.

@nikic nikic force-pushed the static-type branch 2 times, most recently from 9dbb1e3 to 85f0328 Compare February 11, 2020 14:28
@carusogabriel carusogabriel added this to the PHP 8.0 milestone May 29, 2020
jrfnl added a commit to PHPCompatibility/PHPCompatibility that referenced this pull request Jun 29, 2020
As of PHP 8.0, `static` can be used as a return type for function declarations.

Refs:
* https://wiki.php.net/rfc/static_return_type
* php/php-src#5062
* php/php-src@4344385

Includes unit tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants