1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
5 use decl_provider::DeclProvider;
7 use hhbc_by_ref_ast_constant_folder as ast_constant_folder;
8 use hhbc_by_ref_emit_attribute as emit_attribute;
9 use hhbc_by_ref_emit_expression as emit_expression;
10 use hhbc_by_ref_emit_fatal as emit_fatal;
11 use hhbc_by_ref_emit_pos as emit_pos;
12 use hhbc_by_ref_emit_type_hint as emit_type_hint;
13 use hhbc_by_ref_env::{emitter::Emitter, Env};
14 use hhbc_by_ref_hhas_property::{HhasProperty, HhasPropertyFlags};
15 use hhbc_by_ref_hhas_type::{constraint, Info as TypeInfo};
16 use hhbc_by_ref_hhbc_ast::InitpropOp;
17 use hhbc_by_ref_hhbc_id::{prop, Id};
18 use hhbc_by_ref_hhbc_string_utils as string_utils;
19 use hhbc_by_ref_instruction_sequence::{instr, InstrSeq, Result};
20 use hhbc_by_ref_runtime::TypedValue;
21 use naming_special_names_rust::{pseudo_consts, user_attributes as ua};
22 use oxidized::{aast_defs, ast as tast, ast_defs, doc_comment};
24 pub struct FromAstArgs<'ast> {
25 pub user_attributes: &'ast [tast::UserAttribute],
26 pub id: &'ast tast::Sid,
27 pub initial_value: &'ast Option<tast::Expr>,
28 pub typehint: Option<&'ast aast_defs::Hint>,
29 pub doc_comment: Option<doc_comment::DocComment>,
30 pub visibility: aast_defs::Visibility,
32 pub is_abstract: bool,
33 pub is_readonly: bool,
36 pub fn from_ast<'ast, 'arena, 'decl, D: DeclProvider<'decl>>(
37 alloc: &'arena bumpalo::Bump,
38 emitter: &mut Emitter<'arena, 'decl, D>,
39 class: &'ast tast::Class_,
43 ) -> Result<HhasProperty<'arena>> {
44 let ast_defs::Id(pos, cv_name) = args.id;
45 let pid: prop::PropType<'arena> = prop::PropType::from_ast_name(alloc, cv_name);
46 let attributes = emit_attribute::from_asts(alloc, emitter, args.user_attributes)?;
48 let is_const = (!args.is_static && class_is_const)
49 || attributes.iter().any(|a| a.name.as_str() == ua::CONST);
50 let is_lsb = attributes.iter().any(|a| a.name.as_str() == ua::LSB);
51 let is_late_init = attributes.iter().any(|a| a.name.as_str() == ua::LATE_INIT);
53 let is_cabstract = match class.kind {
54 ast_defs::ClassishKind::Cclass(k) => k.is_abstract(),
57 if !args.is_static && class.final_ && is_cabstract {
58 return Err(emit_fatal::raise_fatal_parse(
61 "Class {} contains non-static property declaration and therefore cannot be declared 'abstract final'",
62 string_utils::strip_global_ns(&class.name.1),
67 let type_info = match args.typehint.as_ref() {
68 None => TypeInfo::make_empty(alloc),
69 Some(th) => emit_type_hint::hint_to_type_info(
71 &emit_type_hint::Kind::Property,
78 if !(valid_for_prop(&type_info.type_constraint)) {
79 return Err(emit_fatal::raise_fatal_parse(
82 "Invalid property type hint for '{}::${}'",
83 string_utils::strip_global_ns(&class.name.1),
84 prop::PropType::to_raw_string(&pid)
89 let env = Env::make_class_env(alloc, class);
90 let (initial_value, initializer_instrs, mut hhas_property_flags) = match args.initial_value {
92 let initial_value = if is_late_init {
93 Some(TypedValue::Uninit)
97 (initial_value, None, HhasPropertyFlags::HAS_SYSTEM_INITIAL)
99 Some(_) if is_late_init => {
100 return Err(emit_fatal::raise_fatal_parse(
103 "<<__LateInit>> property '{}::${}' cannot have an initial value",
104 string_utils::strip_global_ns(&class.name.1),
105 prop::PropType::to_raw_string(&pid)
110 let is_collection_map = match e.2.as_collection() {
111 Some(c) if (c.0).1 == "Map" || (c.0).1 == "ImmMap" => true,
114 let deep_init = !args.is_static
115 && expr_requires_deep_init(e, emitter.options().emit_class_pointers() > 0);
116 match ast_constant_folder::expr_to_typed_value(alloc, emitter, e) {
117 Ok(tv) if !(deep_init || is_collection_map) => {
118 (Some(tv), None, HhasPropertyFlags::empty())
121 let label = emitter.label_gen_mut().next_regular();
122 let (prolog, epilog) = if args.is_static {
125 emit_pos::emit_pos_then(
128 instr::initprop(alloc, pid, InitpropOp::Static),
131 } else if args.visibility.is_private() {
134 emit_pos::emit_pos_then(
137 instr::initprop(alloc, pid, InitpropOp::NonStatic),
145 emit_pos::emit_pos(alloc, &class.span),
146 instr::checkprop(alloc, pid),
147 instr::jmpnz(alloc, label.clone()),
153 emit_pos::emit_pos(alloc, &class.span),
154 instr::initprop(alloc, pid, InitpropOp::NonStatic),
155 instr::label(alloc, label),
160 let mut flags = HhasPropertyFlags::empty();
161 flags.set(HhasPropertyFlags::IS_DEEP_INIT, deep_init);
163 Some(TypedValue::Uninit),
164 Some(InstrSeq::gather(
168 emit_expression::emit_expr(emitter, &env, e)?,
179 hhas_property_flags.set(HhasPropertyFlags::IS_ABSTRACT, args.is_abstract);
180 hhas_property_flags.set(HhasPropertyFlags::IS_STATIC, args.is_static);
181 hhas_property_flags.set(HhasPropertyFlags::IS_LSB, is_lsb);
182 hhas_property_flags.set(HhasPropertyFlags::IS_CONST, is_const);
183 hhas_property_flags.set(HhasPropertyFlags::IS_LATE_INIT, is_late_init);
184 hhas_property_flags.set(HhasPropertyFlags::IS_READONLY, args.is_readonly);
188 attributes: alloc.alloc_slice_fill_iter(attributes.into_iter()).into(),
190 initial_value: initial_value.into(),
191 initializer_instrs: initializer_instrs.into(),
192 flags: hhas_property_flags,
193 visibility: args.visibility,
196 .map(|pstr| ffi::Str::from(alloc.alloc_str(&pstr.0.1)))
201 fn valid_for_prop(tc: &constraint::Constraint) -> bool {
205 !(string_utils::is_self(&s)
206 || string_utils::is_parent(&s)
207 || s.as_str().eq_ignore_ascii_case("hh\\nothing")
208 || s.as_str().eq_ignore_ascii_case("hh\\noreturn")
209 || s.as_str().eq_ignore_ascii_case("callable"))
214 fn expr_requires_deep_init_(expr: &tast::Expr) -> bool {
215 expr_requires_deep_init(expr, false)
218 fn expr_requires_deep_init(expr: &tast::Expr, force_class_init: bool) -> bool {
219 use ast_defs::Uop::*;
222 Expr_::Unop(e) if e.0 == Uplus || e.0 == Uminus => expr_requires_deep_init_(&e.1),
223 Expr_::Binop(e) => expr_requires_deep_init_(&e.1) || expr_requires_deep_init_(&e.2),
230 | Expr_::String(_) => false,
231 Expr_::Collection(e) if (e.0).1 == "keyset" || (e.0).1 == "dict" || (e.0).1 == "vec" => {
232 (e.2).iter().any(af_expr_requires_deep_init)
234 Expr_::Varray(e) => (e.1).iter().any(expr_requires_deep_init_),
235 Expr_::Darray(e) => (e.1).iter().any(expr_pair_requires_deep_init),
236 Expr_::Id(e) if e.1 == pseudo_consts::G__FILE__ || e.1 == pseudo_consts::G__DIR__ => false,
237 Expr_::Shape(sfs) => sfs.iter().any(shape_field_requires_deep_init),
238 Expr_::ClassConst(e) if (!force_class_init) => match e.0.as_ciexpr() {
239 Some(ci_expr) => match (ci_expr.2).as_id() {
240 Some(ast_defs::Id(_, s)) => {
241 class_const_requires_deep_init(&s.as_str(), &(e.1).1.as_str())
251 fn af_expr_requires_deep_init(af: &tast::Afield) -> bool {
253 tast::Afield::AFvalue(e) => expr_requires_deep_init_(e),
254 tast::Afield::AFkvalue(e1, e2) => {
255 expr_requires_deep_init_(e1) || expr_requires_deep_init_(e2)
260 fn expr_pair_requires_deep_init((e1, e2): &(tast::Expr, tast::Expr)) -> bool {
261 expr_requires_deep_init_(e1) || expr_requires_deep_init_(e2)
264 fn shape_field_requires_deep_init((name, expr): &(ast_defs::ShapeFieldName, tast::Expr)) -> bool {
266 ast_defs::ShapeFieldName::SFlitInt(_) | ast_defs::ShapeFieldName::SFlitStr(_) => {
267 expr_requires_deep_init_(expr)
269 ast_defs::ShapeFieldName::SFclassConst(ast_defs::Id(_, s), (_, p)) => {
270 class_const_requires_deep_init(s, p)
275 fn class_const_requires_deep_init(s: &str, p: &str) -> bool {
276 !string_utils::is_class(p)
277 || string_utils::is_self(s)
278 || string_utils::is_parent(s)
279 || string_utils::is_static(s)