From 83647ad97704c848392fd7dfa7ad95b98581929a Mon Sep 17 00:00:00 2001 From: Shayne Fletcher Date: Thu, 10 Feb 2022 15:34:03 -0800 Subject: [PATCH] handle typeconst inheritance Summary: inheritance of type constants requires consideration of some cases Differential Revision: D34146499 fbshipit-source-id: 5ec89cef09676651294b9f0733ec713b74b8ab9d --- hphp/hack/src/rupro/lib/alloc/alloc_decl_defs.rs | 6 +- hphp/hack/src/rupro/lib/decl_defs/folded.rs | 13 +++- hphp/hack/src/rupro/lib/decl_defs/mod.rs | 6 +- hphp/hack/src/rupro/lib/decl_defs/shallow.rs | 13 +++- .../src/rupro/lib/folded_decl_provider/inherit.rs | 78 ++++++++++++++++++++-- 5 files changed, 104 insertions(+), 12 deletions(-) diff --git a/hphp/hack/src/rupro/lib/alloc/alloc_decl_defs.rs b/hphp/hack/src/rupro/lib/alloc/alloc_decl_defs.rs index f4c670e8023..af64d9ade8a 100644 --- a/hphp/hack/src/rupro/lib/alloc/alloc_decl_defs.rs +++ b/hphp/hack/src/rupro/lib/alloc/alloc_decl_defs.rs @@ -294,7 +294,11 @@ impl Allocator { shallow::ShallowTypeconst { name: self.pos_type_const_from_decl(stc.name), kind: self.typeconst(stc.kind), - enforceable: (self.pos_from_decl(stc.enforceable.0), stc.enforceable.1), + enforceable: if stc.enforceable.1 { + Some(self.pos_from_decl(stc.enforceable.0)) + } else { + None + }, reifiable: stc.reifiable.map(|pos| self.pos_from_decl(pos)), is_ctx: stc.is_ctx, } diff --git a/hphp/hack/src/rupro/lib/decl_defs/folded.rs b/hphp/hack/src/rupro/lib/decl_defs/folded.rs index e580c44bc7f..ed2fe929079 100644 --- a/hphp/hack/src/rupro/lib/decl_defs/folded.rs +++ b/hphp/hack/src/rupro/lib/decl_defs/folded.rs @@ -64,12 +64,21 @@ pub struct TypeConst { pub name: Positioned, pub kind: Typeconst, // abstract or concrete pub origin: TypeName, - pub is_enforceable: Positioned, - pub is_reifiable: Option, + pub enforceable: Option, // When Some, points to __Enforceable attribute + pub reifiable: Option, // When Some, points to __Reifiable attribute pub is_concreteized: bool, pub is_ctx: bool, } +impl TypeConst { + pub fn is_enforceable(&self) -> bool { + self.enforceable.is_some() + } + pub fn is_reifiable(&self) -> bool { + self.reifiable.is_some() + } +} + #[derive(Debug, Clone)] pub struct ClassConst { // note(sf, 2022-02-08): c.f. `Typing_defs.class_const` diff --git a/hphp/hack/src/rupro/lib/decl_defs/mod.rs b/hphp/hack/src/rupro/lib/decl_defs/mod.rs index e5fff649e6a..47aa63d379c 100644 --- a/hphp/hack/src/rupro/lib/decl_defs/mod.rs +++ b/hphp/hack/src/rupro/lib/decl_defs/mod.rs @@ -10,7 +10,7 @@ pub mod ty; pub use folded::{ClassConst, FoldedClass, FoldedElement, SubstContext, TypeConst}; pub use shallow::{FunDecl, ShallowClass, ShallowClassConst, ShallowMethod, ShallowProp}; pub use ty::{ - Abstraction, CeVisibility, ClassConstFrom, ClassConstKind, ClassConstRef, ClassEltFlags, - ClassEltFlagsArgs, ClassishKind, ConsistentKind, DeclTy, DeclTy_, FunParam, FunType, Prim, - Tparam, Typeconst, UserAttribute, Visibility, XhpAttribute, + AbstractTypeconst, Abstraction, CeVisibility, ClassConstFrom, ClassConstKind, ClassConstRef, + ClassEltFlags, ClassEltFlagsArgs, ClassishKind, ConsistentKind, DeclTy, DeclTy_, FunParam, + FunType, Prim, Tparam, Typeconst, UserAttribute, Visibility, XhpAttribute, }; diff --git a/hphp/hack/src/rupro/lib/decl_defs/shallow.rs b/hphp/hack/src/rupro/lib/decl_defs/shallow.rs index 575e2ad4f06..db83b9c6e7b 100644 --- a/hphp/hack/src/rupro/lib/decl_defs/shallow.rs +++ b/hphp/hack/src/rupro/lib/decl_defs/shallow.rs @@ -48,13 +48,22 @@ walkable!(ShallowClassConst => [ty]); pub struct ShallowTypeconst { pub name: Positioned, pub kind: Typeconst, - pub enforceable: (R::Pos, bool), - pub reifiable: Option, + pub enforceable: Option, // When Some, points to __Enforceable attribute + pub reifiable: Option, // When Some, points to __Reifiable attribute pub is_ctx: bool, } walkable!(ShallowTypeconst => [kind]); +impl ShallowTypeconst { + pub fn is_enforceable(&self) -> bool { + self.enforceable.is_some() + } + pub fn is_reifiable(&self) -> bool { + self.reifiable.is_some() + } +} + #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ShallowProp { pub name: Positioned, diff --git a/hphp/hack/src/rupro/lib/folded_decl_provider/inherit.rs b/hphp/hack/src/rupro/lib/folded_decl_provider/inherit.rs index 4beea1e4eae..a1d36cdba38 100644 --- a/hphp/hack/src/rupro/lib/folded_decl_provider/inherit.rs +++ b/hphp/hack/src/rupro/lib/folded_decl_provider/inherit.rs @@ -4,8 +4,8 @@ // LICENSE file in the "hack" directory of this source tree. use crate::decl_defs::{ - Abstraction, ClassConst, ClassConstKind, ClassishKind, DeclTy, FoldedClass, FoldedElement, - ShallowClass, SubstContext, TypeConst, + AbstractTypeconst, Abstraction, ClassConst, ClassConstKind, ClassishKind, DeclTy, FoldedClass, + FoldedElement, ShallowClass, SubstContext, TypeConst, Typeconst, }; use crate::folded_decl_provider::subst::Subst; use crate::reason::Reason; @@ -184,8 +184,78 @@ impl Inherited { } fn add_type_consts(&mut self, other_type_consts: TypeConstNameMap>) { - //TODO Fill this in - self.type_consts.extend(other_type_consts) + for (name, mut new_const) in other_type_consts { + match self.type_consts.entry(name) { + Entry::Vacant(e) => { + // The type constant didn't exist so far, let's add it. + e.insert(new_const); + } + Entry::Occupied(mut e) => { + let old_const = e.get(); + if new_const.is_enforceable() && !old_const.is_enforceable() { + // If some typeconst in some ancestor was enforceable, + // then the child class' typeconst will be enforceable + // too, even if we didn't take that ancestor typeconst. + e.get_mut().enforceable = new_const.enforceable.clone(); + } + let old_const = e.get(); + match (&old_const.kind, &new_const.kind) { + // This covers the following case + // ``` + // interface I1 { abstract const type T; } + // interface I2 { const type T = int; } + // class C implements I1, I2 {} + // ``` + // Then `C::T == I2::T` since `I2::T `is not abstract + (Typeconst::TCConcrete(_), Typeconst::TCAbstract(_)) => {} + // This covers the following case + // ``` + // interface I { + // abstract const type T as arraykey; + // } + // + // abstract class A { + // abstract const type T as arraykey = string; + // } + // + // final class C extends A implements I {} + // ``` + // `C::T` must come from `A`, not `I`, as `A` + // provides the default that will synthesize into a + // concrete type constant in `C`. + ( + Typeconst::TCAbstract(AbstractTypeconst { + default: Some(_), .. + }), + Typeconst::TCAbstract(AbstractTypeconst { default: None, .. }), + ) => {} + // When a type constant is declared in multiple + // parents we need to make a subtle choice of what + // type we inherit. For example in: + // ``` + // interface I1 { abstract const type t as Container; } + // interface I2 { abstract const type t as KeyedContainer; } + // abstract class C implements I1, I2 {} + // ``` + // Depending on the order the interfaces are + // declared, we may report an error. Since this + // could be confusing there is special logic in + // `Typing_extends` that checks for this potentially + // ambiguous situation and warns the programmer to + // explicitly declare `T` in `C`. + _ => { + if old_const.is_enforceable() && !new_const.is_enforceable() { + // If a typeconst we already inherited from some + // other ancestor was enforceable, then the one + // we inherit here will be enforceable too. + new_const.enforceable = old_const.enforceable.clone(); + } + e.insert(new_const); + } + } + } + } + } } fn add_inherited(&mut self, other: Self) { -- 2.11.4.GIT