hphp/hack/src/rupro/ty/decl/ty.rs (372 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the "hack" directory of this source tree. use crate::reason::{self, Reason}; use eq_modulo_pos::EqModuloPos; use hcons::Hc; use oxidized::{aast, ast_defs}; use pos::{Bytes, ModuleName, Positioned, Symbol, TypeConstName, TypeName}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::BTreeMap; use std::fmt; use utils::core::Ident; pub use oxidized::{ aast_defs::{ReifyKind, Tprim as Prim}, ast_defs::{Abstraction, ClassishKind, ConstraintKind, Visibility}, typing_defs::ClassConstKind, typing_defs_core::{ConsistentKind, Enforcement, Exact, ParamMode, ShapeKind}, typing_defs_flags::{self, ClassEltFlags, ClassEltFlagsArgs, FunParamFlags, FunTypeFlags}, xhp_attribute::{Tag, XhpAttribute}, }; // c.f. ast_defs::XhpEnumValue #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub enum XhpEnumValue { XEVInt(isize), XEVString(Symbol), } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub enum CeVisibility { Public, Private(TypeName), Protected(TypeName), Internal(ModuleName), } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub enum IfcFunDecl { FDPolicied(Option<Symbol>), FDInferFlows, } // The OCaml type `tshape_field_name` includes positions, but ignores those // positions in its `ord` implementation. We can't do the same, though: Rust // hash tables require impls of Hash and Eq to agree, and our Hash impl must // take positions into account (else hash-consing will produce bad results). We // could write a custom Ord impl which disagrees with the Eq impl, but it would // violate the [PartialOrd requirement][] that `a == b` if and only if // `partial_cmp(a, b) == Some(Equal)`, and the [Ord requirement][] for a strict // total order. // // [PartialOrd requirement]: https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html // [Ord requirement]: https://doc.rust-lang.org/std/cmp/trait.Ord.html#corollaries // // Instead, we omit the positions from these keys, and store the field name's // position as part of the map's value (in a `ShapeFieldNamePos`). #[derive(Copy, Clone, Debug, Eq, EqModuloPos, Hash, Ord, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize)] pub enum TshapeFieldName { TSFlitInt(Symbol), TSFlitStr(Bytes), TSFclassConst(TypeName, Symbol), } walkable!(TshapeFieldName); /// The position of a shape field name; e.g., the position of `'a'` in /// `shape('a' => int)`, or the positions of `Foo` and `X` in /// `shape(Foo::X => int)`. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, Ord, PartialEq, PartialOrd)] #[derive(Serialize, Deserialize)] pub enum ShapeFieldNamePos<P> { Simple(P), ClassConst(P, P), } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub enum DependentType { DTexpr(Ident), } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub struct UserAttribute<P> { pub name: Positioned<TypeName, P>, pub classname_params: Box<[TypeName]>, } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub struct Tparam<R: Reason, TY> { pub variance: ast_defs::Variance, pub name: Positioned<TypeName, R::Pos>, pub tparams: Box<[Tparam<R, TY>]>, pub constraints: Box<[(ConstraintKind, TY)]>, pub reified: ReifyKind, pub user_attributes: Box<[UserAttribute<R::Pos>]>, } walkable!(impl<R: Reason, TY> for Tparam<R, TY> => [tparams, constraints]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub struct WhereConstraint<TY>(pub TY, pub ast_defs::ConstraintKind, pub TY); walkable!(impl<R: Reason, TY> for WhereConstraint<TY> => [0, 1, 2]); #[derive(Clone, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct DeclTy<R: Reason>(R, Hc<DeclTy_<R>>); walkable!(DeclTy<R> as visit_decl_ty => [0, 1]); impl<R: Reason> DeclTy<R> { #[inline] pub fn new(reason: R, ty: DeclTy_<R>) -> Self { Self(reason, Hc::new(ty)) } pub fn prim(r: R, prim: Prim) -> Self { Self::new(r, DeclTy_::DTprim(prim)) } pub fn void(r: R) -> Self { Self::prim(r, Prim::Tvoid) } pub fn any(r: R) -> Self { Self::new(r, DeclTy_::DTany) } pub fn this(r: R) -> Self { Self::new(r, DeclTy_::DTthis) } pub fn apply( reason: R, type_name: Positioned<TypeName, R::Pos>, tparams: Box<[DeclTy<R>]>, ) -> Self { Self::new(reason, DeclTy_::DTapply(Box::new((type_name, tparams)))) } pub fn generic(reason: R, name: TypeName, tparams: Box<[DeclTy<R>]>) -> Self { Self::new(reason, DeclTy_::DTgeneric(Box::new((name, tparams)))) } #[inline] pub fn access(reason: R, taccess: TaccessType<R, DeclTy<R>>) -> Self { Self::new(reason, DeclTy_::DTaccess(Box::new(taccess))) } pub fn pos(&self) -> &R::Pos { self.0.pos() } pub fn reason(&self) -> &R { &self.0 } pub fn node(&self) -> &Hc<DeclTy_<R>> { &self.1 } pub fn node_ref(&self) -> &DeclTy_<R> { &self.1 } pub fn unwrap_class_type(&self) -> Option<(&R, &Positioned<TypeName, R::Pos>, &[DeclTy<R>])> { use DeclTy_::*; let r = self.reason(); match &**self.node() { DTapply(id_and_args) => { let (pos_id, args) = &**id_and_args; Some((r, pos_id, args)) } _ => None, } } } /// A shape may specify whether or not fields are required. For example, consider /// this typedef: /// /// ``` /// type ShapeWithOptionalField = shape(?'a' => ?int); /// ``` /// /// With this definition, the field 'a' may be unprovided in a shape. In this /// case, the field 'a' would have sf_optional set to true. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct ShapeFieldType<R: Reason> { pub field_name_pos: ShapeFieldNamePos<R::Pos>, pub optional: bool, pub ty: DeclTy<R>, } walkable!(ShapeFieldType<R> => [ty]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub enum DeclTy_<R: Reason> { /// The late static bound type of a class DTthis, /// Either an object type or a type alias, ty list are the arguments DTapply(Box<(Positioned<TypeName, R::Pos>, Box<[DeclTy<R>]>)>), /// "Any" is the type of a variable with a missing annotation, and "mixed" is /// the type of a variable annotated as "mixed". THESE TWO ARE VERY DIFFERENT! /// Any unifies with anything, i.e., it is both a supertype and subtype of any /// other type. You can do literally anything to it; it's the "trust me" type. /// Mixed, on the other hand, is only a supertype of everything. You need to do /// a case analysis to figure out what it is (i.e., its elimination form). /// /// Here's an example to demonstrate: /// /// ``` /// function f($x): int { /// return $x + 1; /// } /// ``` /// /// In that example, $x has type Tany. This unifies with anything, so adding /// one to it is allowed, and returning that as int is allowed. /// /// In contrast, if $x were annotated as mixed, adding one to that would be /// a type error -- mixed is not a subtype of int, and you must be a subtype /// of int to take part in addition. (The converse is true though -- int is a /// subtype of mixed.) A case analysis would need to be done on $x, via /// is_int or similar. /// /// mixed exists only in the decl_phase phase because it is desugared into ?nonnull /// during the localization phase. DTmixed, DTlike(DeclTy<R>), DTany, DTerr, DTnonnull, /// A dynamic type is a special type which sometimes behaves as if it were a /// top type; roughly speaking, where a specific value of a particular type is /// expected and that type is dynamic, anything can be given. We call this /// behaviour "coercion", in that the types "coerce" to dynamic. In other ways it /// behaves like a bottom type; it can be used in any sort of binary expression /// or even have object methods called from it. However, it is in fact neither. /// /// it captures dynamicism within function scope. /// See tests in typecheck/dynamic/ for more examples. DTdynamic, /// Nullable, called "option" in the ML parlance. DToption(DeclTy<R>), /// All the primitive types: int, string, void, etc. DTprim(aast::Tprim), /// A wrapper around fun_type, which contains the full type information for a /// function, method, lambda, etc. DTfun(Box<FunType<R, DeclTy<R>>>), /// Tuple, with ordered list of the types of the elements of the tuple. DTtuple(Box<[DeclTy<R>]>), /// Whether all fields of this shape are known, types of each of the /// known arms. DTshape(Box<(ShapeKind, BTreeMap<TshapeFieldName, ShapeFieldType<R>>)>), DTvar(Ident), /// The type of a generic parameter. The constraints on a generic parameter /// are accessed through the lenv.tpenv component of the environment, which /// is set up when checking the body of a function or method. See uses of /// Typing_phase.add_generic_parameters_and_constraints. The list denotes /// type arguments. DTgeneric(Box<(TypeName, Box<[DeclTy<R>]>)>), /// Union type. /// The values that are members of this type are the union of the values /// that are members of the components of the union. /// Some examples (writing | for binary union) /// Tunion [] is the "nothing" type, with no values /// Tunion [int;float] is the same as num /// Tunion [null;t] is the same as Toption t DTunion(Box<[DeclTy<R>]>), DTintersection(Box<[DeclTy<R>]>), /// Tvec_or_dict (ty1, ty2) => "vec_or_dict<ty1, ty2>" DTvecOrDict(Box<(DeclTy<R>, DeclTy<R>)>), DTaccess(Box<TaccessType<R, DeclTy<R>>>), } // We've boxed all variants of DeclTy_ which are larger than two usizes, so the // total size should be equal to `[usize; 3]` (one more for the discriminant). // This is important because all variants use the same amount of memory and are // passed around by value, so adding a large unboxed variant can cause a large // regression. static_assertions::assert_eq_size!(DeclTy_<reason::NReason>, [usize; 3]); static_assertions::assert_eq_size!(DeclTy_<reason::BReason>, [usize; 3]); impl<R: Reason> hcons::Consable for DeclTy_<R> { #[inline] fn conser() -> &'static hcons::Conser<DeclTy_<R>> { R::decl_ty_conser() } } impl<R: Reason> crate::visitor::Walkable<R> for DeclTy_<R> { fn recurse(&self, v: &mut dyn crate::visitor::Visitor<R>) { use DeclTy_::*; match self { DTthis | DTmixed | DTany | DTerr | DTnonnull | DTdynamic | DTprim(_) | DTvar(_) => {} DTapply(id_and_args) => { let (_, args) = &**id_and_args; args.accept(v) } DTlike(ty) | DToption(ty) => ty.accept(v), DTfun(ft) => ft.accept(v), DTtuple(tys) | DTunion(tys) | DTintersection(tys) => tys.accept(v), DTshape(kind_and_fields) => { let (_, fields) = &**kind_and_fields; fields.accept(v) } DTgeneric(id_and_args) => { let (_, args) = &**id_and_args; args.accept(v) } DTvecOrDict(key_and_val_tys) => { let (kty, vty) = &**key_and_val_tys; kty.accept(v); vty.accept(v) } DTaccess(tt) => tt.accept(v), } } } /// A Type const access expression of the form <type expr>::C. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub struct TaccessType<R: Reason, TY> { /// Type expression to the left of `::` pub ty: TY, /// Name of type const to the right of `::` pub type_const: Positioned<TypeConstName, R::Pos>, } walkable!(impl<R: Reason, TY> for TaccessType<R, TY> => [ty]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub enum Capability<R: Reason, TY> { CapDefaults(R::Pos), CapTy(TY), } walkable!(impl<R: Reason, TY> for Capability<R, TY> => { Self::CapDefaults(..) => [], Self::CapTy(ty) => [ty], }); /// Companion to fun_params type, intended to consolidate checking of /// implicit params for functions. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub struct FunImplicitParams<R: Reason, TY> { pub capability: Capability<R, TY>, } walkable!(impl<R: Reason, TY> for FunImplicitParams<R, TY> => [capability]); /// The type of a function AND a method. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub struct FunType<R: Reason, TY> { pub tparams: Box<[Tparam<R, TY>]>, pub where_constraints: Box<[WhereConstraint<TY>]>, pub params: FunParams<R, TY>, pub implicit_params: FunImplicitParams<R, TY>, /// Carries through the sync/async information from the aast pub ret: PossiblyEnforcedTy<TY>, pub flags: typing_defs_flags::FunTypeFlags, pub ifc_decl: IfcFunDecl, } walkable!(impl<R: Reason, TY> for FunType<R, TY> => [ tparams, where_constraints, params, implicit_params, ret ]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "TY: Serialize + DeserializeOwned")] pub struct PossiblyEnforcedTy<TY> { /// True if consumer of this type enforces it at runtime pub enforced: Enforcement, pub ty: TY, } walkable!(impl<R: Reason, TY> for PossiblyEnforcedTy<TY> => [ty]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason, TY: Serialize + DeserializeOwned")] pub struct FunParam<R: Reason, TY> { pub pos: R::Pos, pub name: Option<Symbol>, pub ty: PossiblyEnforcedTy<TY>, pub flags: FunParamFlags, } walkable!(impl<R: Reason, TY> for FunParam<R, TY> => [ty]); pub type FunParams<R, TY> = Box<[FunParam<R, TY>]>; /// Origin of Class Constant References: /// In order to be able to detect cycle definitions like /// class C { /// const int A = D::A; /// } /// class D { /// const int A = C::A; /// } /// we need to remember which constants were used during initialization. /// /// Currently the syntax of constants allows direct references to another class /// like D::A, or self references using self::A. /// /// class_const_from encodes the origin (class vs self). #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub enum ClassConstFrom { Self_, From(TypeName), } /// Class Constant References: /// In order to be able to detect cycle definitions like /// class C { /// const int A = D::A; /// } /// class D { /// const int A = C::A; /// } /// we need to remember which constants were used during initialization. /// /// Currently the syntax of constants allows direct references to another class /// like D::A, or self references using self::A. #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] pub struct ClassConstRef(pub ClassConstFrom, pub Symbol); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct ConstDecl<R: Reason> { pub pos: R::Pos, pub ty: DeclTy<R>, } walkable!(ConstDecl<R> => [ty]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct FunElt<R: Reason> { pub deprecated: Option<Bytes>, pub module: Option<Positioned<ModuleName, R::Pos>>, /// Top-level functions have limited visibilities pub internal: bool, pub ty: DeclTy<R>, pub pos: R::Pos, pub php_std_lib: bool, pub support_dynamic_type: bool, } walkable!(FunElt<R> => [ty]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct AbstractTypeconst<R: Reason> { pub as_constraint: Option<DeclTy<R>>, pub super_constraint: Option<DeclTy<R>>, pub default: Option<DeclTy<R>>, } walkable!(AbstractTypeconst<R> => [as_constraint, super_constraint, default]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct ConcreteTypeconst<R: Reason> { pub ty: DeclTy<R>, } walkable!(ConcreteTypeconst<R> => [ty]); #[derive(Clone, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub enum Typeconst<R: Reason> { TCAbstract(AbstractTypeconst<R>), TCConcrete(ConcreteTypeconst<R>), } walkable!(Typeconst<R> => { Self::TCAbstract(x) => [x], Self::TCConcrete(x) => [x], }); impl<R: Reason> fmt::Debug for Typeconst<R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::TCAbstract(x) => x.fmt(f), Self::TCConcrete(x) => x.fmt(f), } } } #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct EnumType<R: Reason> { pub base: DeclTy<R>, pub constraint: Option<DeclTy<R>>, pub includes: Box<[DeclTy<R>]>, } walkable!(EnumType<R> => [base, constraint, includes]); #[derive(Clone, Debug, Eq, EqModuloPos, Hash, PartialEq, Serialize, Deserialize)] #[serde(bound = "R: Reason")] pub struct TypedefType<R: Reason> { pub module: Option<Positioned<ModuleName, R::Pos>>, pub pos: R::Pos, pub vis: aast::TypedefVisibility, pub tparams: Box<[Tparam<R, DeclTy<R>>]>, pub constraint: Option<DeclTy<R>>, pub ty: DeclTy<R>, pub is_ctx: bool, pub attributes: Box<[UserAttribute<R::Pos>]>, } walkable!(TypedefType<R> => [tparams, constraint, ty]); walkable!(ast_defs::ConstraintKind); #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ModuleDefType<R: Reason> { pub pos: R::Pos, } walkable!(ModuleDefType<R> => []);