Skip to content

Full variance support if autoloading is used #4194

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 8 commits into from

Conversation

nikic
Copy link
Member

@nikic nikic commented May 27, 2019

The first commit moves the loading of the parent class into zend_do_link_class as preparation. The second commit has the main functionality, which works as follows:

  • We keep track of the current class linking depth.
  • When we encounter an unresolved compatibility check, we remember it as a "compatibility" delayed variance obligation. The compatibility check is not allowed to autoload classes.
  • We allow using classes/interfaces in extends/implements that have outstanding variance obligations, but are otherwise fully linked. We remember this as a "dependency" obligation.
  • When the linking depth reaches zero, we process the delayed variance obligations. A dependency obligation is satisfied if the dependency is now fully linked. A compatibility obligation is satisfied if the compatibility check passes. Autoloading of new classes is permitted here. If all obligations for a class are satisfied, it is marked as fully linked.
  • As new classes may be loaded or become fully linked, we do this until there are no more changes.
  • Anything that remains is emitted as a compatibility error/warning.

@nikic
Copy link
Member Author

nikic commented May 27, 2019

@dstogov This is my implementation for full variance support with autoloading. It's similar to one of the possibilities we discussed, where we keep track of outstanding obligations and check them later.

This approach should be easy to extend to the non-autoloaded case (if we want to do so). Basically we would have to increment the linking depth before declaring a "group" of classes and then decrementing afterward.

@nikic nikic force-pushed the variance-autoload-2 branch from 23f6e36 to fce9d3a Compare May 27, 2019 12:56
@krakjoe krakjoe requested a review from dstogov May 29, 2019 14:43
Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

In my opinion, the behavior implemented by PR is not consistent.
There are possible situation when class can't be used after declaration, because of delayed variance checks.

It looks like, implementation of full variance support in PHP is not possible at all.

I would prefer not to merge inconsistent solutions, and keep only "minimal" variance support (that is restricted by simple rule - parameter classes, that need to be checked, have to be available during class linking).

zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), Z_STR_P(lcname + 1));
zend_class_entry *parent_ce = zend_lookup_class_ex(Z_STR_P(parent_name), Z_STR_P(parent_name + 1), 0);
zend_class_entry *parent_ce = zend_lookup_class(ce->parent_name);
Copy link
Member

Choose a reason for hiding this comment

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

This will require extra strtolower() call at run-time.

nikic added 3 commits June 7, 2019 10:59
We want the class declaration to be available while compiling the
parent class.
Keep track of delayed variance obligations and enforce them when
class linking depth reaches zero.
@nikic nikic force-pushed the variance-autoload-2 branch from fce9d3a to 8be567c Compare June 7, 2019 13:17
nikic added 3 commits June 7, 2019 15:23
No longer necessary
This reduces the amount of unnecessary diff to current
implementation.
And drop class_order_autoload6 as no longer necessary.
@nikic
Copy link
Member Author

nikic commented Jun 7, 2019

@dstogov I have now implemented a new version that hopefully resolves the issues we discussed. The core approach is the same as before, with a few differences:

  • Delayed variance obligations are processed directly after the class declaration is otherwise done, not when the link depth becomes zero (in fact, link depth is gone now). This means that either the class will link successfully at that time, or there will be an error. There will not be a situation where directly after a class declaration you get a "class not found" error.
  • Autoloading of classes is separated into a separate contained step -- I had too much trouble with concurrent modification of datastructures otherwise.
  • Because variance is checked earlier, we also need to handle classes in various stages of linking in instanceof. unlinked_instanceof is extended to support this.

One very important conceptual difference is that we do not require the classes used in typehints to be fully linked in order to link a class -- we do ensure that they have correct variance, but don't require full linking. For example, the following example works as expected:

spl_autoload_register(function($class) {
    if ($class === 'A') {
        class A {
            public function method() : B {}
        }
        var_dump(new A);
    } else if ($class == 'B') {
        class B extends A {
            public function method() : C {}
        }
        var_dump(new B);
    } else {
        class C extends B {
        }
        var_dump(new C);
    }
});
var_dump(new C);

In particular the use new B works, even though class C is not fully linked yet at that point. Either C will successfully link lateron, or generate an error. But there is no way to actually use C until that point, or replace it with a different class with incorrect variance.

nikic added 2 commits June 7, 2019 16:25
Instead do an explicit recursive resolution of obligations for
dependency classes.
We already looked up the obligations HT at that point, it seems
potentially dangerous.
@nikic
Copy link
Member Author

nikic commented Jun 7, 2019

It would be a good bit more convenient if we could have delayed_obligations as a member of the CE. I didn't put it there because it's only needed during inheritance. If we don't care though, it would simplify code.

@dstogov
Copy link
Member

dstogov commented Jun 11, 2019

This implementation looks better, but personally, I don't see value for this implementation complication.

I also see ~5% slowdown on ZendFramework benchmark, that just loads most of the ZF classes (this is expected).

@nikic
Copy link
Member Author

nikic commented Jun 11, 2019

Merged as 89b2d88 and 8f8fcbb.

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

Successfully merging this pull request may close these issues.

4 participants