Tweak `HhasTypeConstant` C++: Replace Option with Maybe
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_property.rs
blob79dc4376d33d971b68e9c29b7977ef11cdc3fc3e
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
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;
6 use ffi::Maybe::*;
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,
31     pub is_static: bool,
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_,
40     tparams: &[&str],
41     class_is_const: bool,
42     args: FromAstArgs,
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(),
55         _ => false,
56     };
57     if !args.is_static && class.final_ && is_cabstract {
58         return Err(emit_fatal::raise_fatal_parse(
59             &pos,
60             format!(
61                 "Class {} contains non-static property declaration and therefore cannot be declared 'abstract final'",
62                 string_utils::strip_global_ns(&class.name.1),
63             ),
64         ));
65     };
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(
70             alloc,
71             &emit_type_hint::Kind::Property,
72             false,
73             false,
74             tparams,
75             th,
76         )?,
77     };
78     if !(valid_for_prop(&type_info.type_constraint)) {
79         return Err(emit_fatal::raise_fatal_parse(
80             &pos,
81             format!(
82                 "Invalid property type hint for '{}::${}'",
83                 string_utils::strip_global_ns(&class.name.1),
84                 prop::PropType::to_raw_string(&pid)
85             ),
86         ));
87     };
89     let env = Env::make_class_env(alloc, class);
90     let (initial_value, initializer_instrs, mut hhas_property_flags) = match args.initial_value {
91         None => {
92             let initial_value = if is_late_init {
93                 Some(TypedValue::Uninit)
94             } else {
95                 None
96             };
97             (initial_value, None, HhasPropertyFlags::HAS_SYSTEM_INITIAL)
98         }
99         Some(_) if is_late_init => {
100             return Err(emit_fatal::raise_fatal_parse(
101                 &pos,
102                 format!(
103                     "<<__LateInit>> property '{}::${}' cannot have an initial value",
104                     string_utils::strip_global_ns(&class.name.1),
105                     prop::PropType::to_raw_string(&pid)
106                 ),
107             ));
108         }
109         Some(e) => {
110             let is_collection_map = match e.2.as_collection() {
111                 Some(c) if (c.0).1 == "Map" || (c.0).1 == "ImmMap" => true,
112                 _ => false,
113             };
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())
119                 }
120                 _ => {
121                     let label = emitter.label_gen_mut().next_regular();
122                     let (prolog, epilog) = if args.is_static {
123                         (
124                             instr::empty(alloc),
125                             emit_pos::emit_pos_then(
126                                 alloc,
127                                 &class.span,
128                                 instr::initprop(alloc, pid, InitpropOp::Static),
129                             ),
130                         )
131                     } else if args.visibility.is_private() {
132                         (
133                             instr::empty(alloc),
134                             emit_pos::emit_pos_then(
135                                 alloc,
136                                 &class.span,
137                                 instr::initprop(alloc, pid, InitpropOp::NonStatic),
138                             ),
139                         )
140                     } else {
141                         (
142                             InstrSeq::gather(
143                                 alloc,
144                                 vec![
145                                     emit_pos::emit_pos(alloc, &class.span),
146                                     instr::checkprop(alloc, pid),
147                                     instr::jmpnz(alloc, label.clone()),
148                                 ],
149                             ),
150                             InstrSeq::gather(
151                                 alloc,
152                                 vec![
153                                     emit_pos::emit_pos(alloc, &class.span),
154                                     instr::initprop(alloc, pid, InitpropOp::NonStatic),
155                                     instr::label(alloc, label),
156                                 ],
157                             ),
158                         )
159                     };
160                     let mut flags = HhasPropertyFlags::empty();
161                     flags.set(HhasPropertyFlags::IS_DEEP_INIT, deep_init);
162                     (
163                         Some(TypedValue::Uninit),
164                         Some(InstrSeq::gather(
165                             alloc,
166                             vec![
167                                 prolog,
168                                 emit_expression::emit_expr(emitter, &env, e)?,
169                                 epilog,
170                             ],
171                         )),
172                         flags,
173                     )
174                 }
175             }
176         }
177     };
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);
186     Ok(HhasProperty {
187         name: pid,
188         attributes: alloc.alloc_slice_fill_iter(attributes.into_iter()).into(),
189         type_info,
190         initial_value: initial_value.into(),
191         initializer_instrs: initializer_instrs.into(),
192         flags: hhas_property_flags,
193         visibility: args.visibility,
194         doc_comment: args
195             .doc_comment
196             .map(|pstr| ffi::Str::from(alloc.alloc_str(&pstr.0.1)))
197             .into(),
198     })
201 fn valid_for_prop(tc: &constraint::Constraint) -> bool {
202     match &tc.name {
203         Nothing => true,
204         Just(s) => {
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"))
210         }
211     }
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::*;
220     use tast::Expr_;
221     match &expr.2 {
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),
224         Expr_::Lvar(_)
225         | Expr_::Null
226         | Expr_::False
227         | Expr_::True
228         | Expr_::Int(_)
229         | Expr_::Float(_)
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)
233         }
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())
242                 }
243                 _ => true,
244             },
245             None => true,
246         },
247         _ => true,
248     }
251 fn af_expr_requires_deep_init(af: &tast::Afield) -> bool {
252     match af {
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)
256         }
257     }
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 {
265     match name {
266         ast_defs::ShapeFieldName::SFlitInt(_) | ast_defs::ShapeFieldName::SFlitStr(_) => {
267             expr_requires_deep_init_(expr)
268         }
269         ast_defs::ShapeFieldName::SFclassConst(ast_defs::Id(_, s), (_, p)) => {
270             class_const_requires_deep_init(s, p)
271         }
272     }
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)