Replace mro_source with inheritance behavior flags
commit4db2e41cec3a986b4c13479aefa1ce5b45b40841
authorJake Bailey (Hacklang) <jakebailey@fb.com>
Wed, 6 Mar 2019 22:09:19 +0000 (6 14:09 -0800)
committerHhvm Bot <hhvm-bot@users.noreply.github.com>
Wed, 6 Mar 2019 22:13:22 +0000 (6 14:13 -0800)
tree8124ffd44dc1bd3158e603b68084a3e01c911b27
parent53fffd156a4b4955d5fa350b1efb27ef7f75fbbb
Replace mro_source with inheritance behavior flags

Summary:
The original design of linearization supposed that we could determine how to handle member lookup simply by knowing the final step of how an ancestor was included in a linearization. For example, in the original design, the class C in this file:

```
interface I {}
class B implements I {
  private function foo(): void {}
}
trait T1 {
  private function bar(): void {}
}
trait T2 {
  use T1;
}
class C extends B {
  use T2;
}
```

would be given the linearization [C(child), T2(trait), T1(trait), B(parent), I(parent)]. Each element in the MRO is marked with an "MRO source"--C is given (child), since C is the class which was linearized, T2 is given (trait), since C used it directly, T1 is given (trait), since C used it via the trait T2, B is given (parent), since C extends B, and I is given (parent) (rather than (interface)!), since C implements it via C's parent B.

If we attempt to look up C::foo, we would traverse the linearization until we reached B. We would observe that B has a method named foo, but since it is private, C does not inherit it, and the lookup fails.

If we attempt to look up C::bar, we would traverse the linearization until we reached T1. We would observe that T1 has a method named bar, and it is private, but since the MRO element T1 has the MRO source "(trait)", C has access to it, and the lookup succeeds.

Unfortunately, this is not sufficient. Consider this example:

```
class A {
  private function foo(): void {}
}
trait T {
  require extends A;
  private function bar(): void {}
}
class C extends A {
  use T;
}
```

The original design assigned to C the linearization [C(child), T(trait), A(trait), A(parent)]. If we attempted to look up C::foo, we would traverse to A(trait), observe that foo was private,  but consider it to be inherited by C because of the (trait) source on the MRO element A(trait)!

The reason A(trait) is included in the linearization of C is because of T--when C uses T, the entire linearization of T is included in C's linearization, including T's require-extends ancestor A. It is marked as coming from a (trait) source because we mark T's entire linearization as coming from a trait-use.

Instead of blindly marking the entire linearization of T with the (trait) MRO source, we should be a little more clever, and only inherit private members if there is an unbroken chain of trait-uses connecting the child class to the private member (consider T1::bar above, where C does not directly use T1, but does inherit bar).

We end up in a similar situation when it comes to interfaces (where we inherit constants and type constants only) and XHP attribute uses (where we inherit XHP attributes only).

This change replaces the mro_source on each element with separate flags for "xhp_attrs_only", "consts_only", and "copy_private_members". We then can (for instance) avoid inheriting a non-XHP-attribute member if *any* ancestor in the chain reaching them was marked xhp_attrs_only, and copy a private member only if *all* ancestors in the chain reaching that member were marked with copy_private_members.

Reviewed By: arxanas

Differential Revision: D13822817

fbshipit-source-id: 284b3808da7667a5bcd4d16243ab7087d75f2125
13 files changed:
hphp/hack/src/decl/decl_defs.ml
hphp/hack/src/decl/decl_linearize.ml
hphp/hack/src/hh_single_type_check.ml
hphp/hack/test/mro/basic_interfaces.php.exp
hphp/hack/test/mro/req_extends.php.exp
hphp/hack/test/mro/req_extends2.php.exp
hphp/hack/test/mro/test_const.php.exp
hphp/hack/test/mro/traits.php.exp
hphp/hack/test/mro/type_params.php.exp
hphp/hack/test/mro/type_params2.php.exp
hphp/hack/test/mro/type_params3.php.exp
hphp/hack/test/mro/type_params4.php.exp
hphp/hack/test/mro/xhp.php.exp