Tweak `HhasTypeConstant` C++: Replace Option with Maybe
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_method.rs
blob6ce4a05c4679bfcd58a9125050d62bcde36413c3
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 hhbc_by_ref_ast_scope::{self as ast_scope, Lambda, Scope, ScopeItem};
7 use hhbc_by_ref_emit_attribute as emit_attribute;
8 use hhbc_by_ref_emit_body as emit_body;
9 use hhbc_by_ref_emit_fatal::{emit_fatal_runtimeomitframe, raise_fatal_parse};
10 use hhbc_by_ref_emit_memoize_helpers as emit_memoize_helpers;
11 use hhbc_by_ref_emit_native_opcode as emit_native_opcode;
12 use hhbc_by_ref_env::emitter::Emitter;
13 use hhbc_by_ref_hhas_attribute as hhas_attribute;
14 use hhbc_by_ref_hhas_coeffects::HhasCoeffects;
15 use hhbc_by_ref_hhas_method::{HhasMethod, HhasMethodFlags};
16 use hhbc_by_ref_hhas_pos::Span;
17 use hhbc_by_ref_hhbc_id::{method, Id};
18 use hhbc_by_ref_hhbc_string_utils as string_utils;
19 use hhbc_by_ref_instruction_sequence::{instr, Result};
20 use hhbc_by_ref_options::{HhvmFlags, Options};
21 use naming_special_names_rust::{classes, special_idents, user_attributes};
22 use ocamlrep::rc::RcOc;
23 use oxidized::{ast as T, ast_defs};
25 use itertools::Either;
26 use std::borrow::Cow;
28 pub fn from_asts<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
29     alloc: &'arena bumpalo::Bump,
30     emitter: &mut Emitter<'arena, 'decl, D>,
31     class: &'a T::Class_,
32     methods: &'a [T::Method_],
33 ) -> Result<Vec<HhasMethod<'arena>>> {
34     methods
35         .iter()
36         .map(|m| from_ast(alloc, emitter, class, m))
37         .collect::<Result<Vec<_>>>()
40 pub fn from_ast<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
41     alloc: &'arena bumpalo::Bump,
42     emitter: &mut Emitter<'arena, 'decl, D>,
43     class: &'a T::Class_,
44     method_: impl Into<Cow<'a, T::Method_>>,
45 ) -> Result<HhasMethod<'arena>> {
46     let method_: Cow<'a, T::Method_> = method_.into();
47     let method = method_.as_ref();
48     let is_memoize = method
49         .user_attributes
50         .iter()
51         .any(|ua| user_attributes::is_memoized(&ua.name.1));
52     let class_name = string_utils::mangle(string_utils::strip_global_ns(&class.name.1).into());
53     let is_closure_body = &method.name.1 == "__invoke" && (class.name.1).starts_with("Closure$");
54     let mut attributes = emit_attribute::from_asts(alloc, emitter, &method.user_attributes)?;
55     if !is_closure_body {
56         attributes.extend(emit_attribute::add_reified_attribute(
57             alloc,
58             &method.tparams[..],
59         ));
60     };
61     let call_context = if is_closure_body {
62         match &method.user_attributes[..] {
63             [T::UserAttribute {
64                 name: ast_defs::Id(_, ref s),
65                 params: _,
66             }] if s.eq_ignore_ascii_case("__DynamicMethCallerForce") => {
67                 Some("__SystemLib\\DynamicContextOverrideUnsafe".to_string())
68             }
69             _ => None,
70         }
71     } else {
72         None
73     };
75     let is_native = attributes
76         .iter()
77         .any(|attr| attr.is(user_attributes::is_native));
78     let is_native_opcode_impl = hhas_attribute::is_native_opcode_impl(&attributes);
79     let is_abstract = class.kind.is_cinterface() || method.abstract_;
80     let is_async = method.fun_kind.is_fasync() || method.fun_kind.is_fasync_generator();
81     let is_no_injection = hhas_attribute::is_no_injection(&attributes);
83     if !(method.static_ || is_closure_body) {
84         for p in method.params.iter() {
85             if p.name == special_idents::THIS {
86                 return Err(raise_fatal_parse(&p.pos, "Cannot re-assign $this"));
87             }
88         }
89     };
90     if class.kind.is_cinterface() && !method.body.ast.is_empty() {
91         return Err(raise_fatal_parse(
92             &method.name.0,
93             format!(
94                 "Interface method {}::{} cannot contain body",
95                 class_name, &method.name.1
96             ),
97         ));
98     };
99     let is_cabstract = match class.kind {
100         ast_defs::ClassishKind::Cclass(k) => k.is_abstract(),
101         _ => false,
102     };
103     if !method.static_ && class.final_ && is_cabstract {
104         return Err(raise_fatal_parse(
105             &method.name.0,
106             format!(
107                 "Class {} contains non-static method {} and therefore cannot be declared 'abstract final'",
108                 class_name, &method.name.1
109             ),
110         ));
111     };
113     let visibility = if is_native_opcode_impl {
114         T::Visibility::Public
115     } else if is_memoize {
116         T::Visibility::Private
117     } else {
118         method.visibility
119     };
120     let deprecation_info = if is_memoize {
121         None
122     } else {
123         hhas_attribute::deprecation_info(attributes.iter())
124     };
125     let default_dropthrough = if method.abstract_ {
126         Some(emit_fatal_runtimeomitframe(
127             alloc,
128             &&method.name.0,
129             format!(
130                 "Cannot call abstract method {}::{}()",
131                 class_name, &method.name.1
132             ),
133         ))
134     } else {
135         None
136     };
137     let mut scope = Scope {
138         items: vec![
139             ScopeItem::Class(ast_scope::Class::new_ref(class)),
140             ScopeItem::Method(match &method_ {
141                 Cow::Borrowed(m) => ast_scope::Method::new_ref(&m),
142                 Cow::Owned(m) => ast_scope::Method::new_rc(&m),
143             }),
144         ],
145     };
146     if is_closure_body {
147         scope.items.push(ScopeItem::Lambda(Lambda {
148             is_async,
149             coeffects: HhasCoeffects::default(),
150         }))
151     };
152     let namespace = RcOc::clone(
153         emitter
154             .emit_global_state()
155             .closure_namespaces
156             .get(&class_name)
157             .unwrap_or(&class.namespace),
158     );
159     let mut coeffects = if method.ctxs == None && is_closure_body {
160         let parent_coeffects = emitter
161             .emit_global_state()
162             .get_lambda_coeffects_of_scope(&class.name.1, &method.name.1);
163         parent_coeffects.inherit_to_child_closure()
164     } else {
165         HhasCoeffects::from_ast(
166             &method.ctxs,
167             &method.params,
168             &method.tparams,
169             &class.tparams,
170         )
171     };
173     if is_native_opcode_impl
174         && (class.name.1.as_str() == classes::GENERATOR
175             || class.name.1.as_str() == classes::ASYNC_GENERATOR)
176     {
177         match method.name.1.as_str() {
178             "send" | "raise" | "throw" | "next" | "rewind" => {
179                 coeffects = coeffects.with_gen_coeffect()
180             }
181             _ => {}
182         }
183     }
184     if emitter.systemlib() {
185         match (class.name.1.as_str(), method.name.1.as_str()) {
186             ("\\__SystemLib\\MethCallerHelper", "__invoke")
187             | ("\\__SystemLib\\DynMethCallerHelper", "__invoke") => {
188                 coeffects = coeffects.with_caller()
189             }
190             _ => {}
191         }
192     }
193     let ast_body_block = &method.body.ast;
194     let (body, is_generator, is_pair_generator) = if is_native_opcode_impl {
195         (
196             emit_native_opcode::emit_body(
197                 alloc,
198                 emitter,
199                 &scope,
200                 &class.user_attributes,
201                 &method.name,
202                 &method.params,
203                 method.ret.1.as_ref(),
204             )?,
205             false,
206             false,
207         )
208     } else {
209         let class_tparam_names = class
210             .tparams
211             .iter()
212             .map(|tp| (tp.name).1.as_str())
213             .collect::<Vec<_>>();
214         let mut flags = emit_body::Flags::empty();
215         flags.set(
216             emit_body::Flags::SKIP_AWAITABLE,
217             method.fun_kind.is_fasync(),
218         );
219         flags.set(emit_body::Flags::MEMOIZE, is_memoize);
220         flags.set(emit_body::Flags::CLOSURE_BODY, is_closure_body);
221         flags.set(emit_body::Flags::NATIVE, is_native);
222         flags.set(emit_body::Flags::ASYNC, is_async);
223         flags.set(
224             emit_body::Flags::HAS_COEFFECTS_LOCAL,
225             coeffects.has_coeffects_local(),
226         );
227         emit_body::emit_body(
228             alloc,
229             emitter,
230             namespace,
231             Either::Right(ast_body_block),
232             instr::null(alloc),
233             scope,
234             emit_body::Args {
235                 immediate_tparams: &method.tparams,
236                 class_tparam_names: class_tparam_names.as_slice(),
237                 ast_params: &method.params,
238                 ret: method.ret.1.as_ref(),
239                 pos: &method.span,
240                 deprecation_info: &deprecation_info,
241                 doc_comment: method.doc_comment.clone(),
242                 default_dropthrough,
243                 call_context,
244                 flags,
245             },
246         )?
247     };
248     let name = {
249         if is_memoize {
250             method::MethodType::from_ast_name_and_suffix(
251                 alloc,
252                 &method.name.1,
253                 emit_memoize_helpers::MEMOIZE_SUFFIX,
254             )
255         } else {
256             method::MethodType::from_ast_name(alloc, &method.name.1)
257         }
258     };
259     let is_interceptable = is_method_interceptable(emitter.options());
260     let span = if is_native_opcode_impl {
261         Span(0, 0)
262     } else {
263         Span::from_pos(&method.span)
264     };
265     let mut flags = HhasMethodFlags::empty();
266     flags.set(HhasMethodFlags::IS_STATIC, method.static_);
267     flags.set(HhasMethodFlags::IS_FINAL, method.final_);
268     flags.set(HhasMethodFlags::IS_ABSTRACT, is_abstract);
269     flags.set(HhasMethodFlags::IS_ASYNC, is_async);
270     flags.set(HhasMethodFlags::IS_GENERATOR, is_generator);
271     flags.set(HhasMethodFlags::IS_PAIR_GENERATOR, is_pair_generator);
272     flags.set(HhasMethodFlags::IS_CLOSURE_BODY, is_closure_body);
273     flags.set(HhasMethodFlags::IS_INTERCEPTABLE, is_interceptable);
274     flags.set(HhasMethodFlags::IS_MEMOIZE_IMPL, is_memoize);
275     flags.set(HhasMethodFlags::NO_INJECTION, is_no_injection);
276     Ok(HhasMethod {
277         attributes,
278         visibility,
279         name,
280         body,
281         span,
282         coeffects,
283         flags,
284     })
287 fn is_method_interceptable(opts: &Options) -> bool {
288     opts.hhvm
289         .flags
290         .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)