ReadOnly -> ::Readonly
[hiphop-php.git] / hphp / hack / src / hhbc / bytecode_printer / print.rs
blobc88820bd8190a9016b3d1d9335fd4fb091f468a9
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 mod context;
6 mod special_class_resolver;
7 mod write;
9 pub use context::Context;
10 pub use write::{Error, IoWrite, Result, Write};
12 use ffi::{Maybe, Maybe::*, Pair, Quadruple, Slice, Str, Triple};
14 use indexmap::IndexSet;
15 use itertools::Itertools;
17 use core_utils_rust::add_ns;
18 use escaper::{escape, escape_by, is_lit_printable};
19 use hhbc_by_ref_emit_type_hint as emit_type_hint;
20 use hhbc_by_ref_hhas_adata::{HhasAdata, DICT_PREFIX, KEYSET_PREFIX, VEC_PREFIX};
21 use hhbc_by_ref_hhas_attribute::{self as hhas_attribute, HhasAttribute};
22 use hhbc_by_ref_hhas_body::{HhasBody, HhasBodyEnv};
23 use hhbc_by_ref_hhas_class::{self as hhas_class, HhasClass};
24 use hhbc_by_ref_hhas_coeffects::{HhasCoeffects, HhasCtxConstant};
25 use hhbc_by_ref_hhas_constant::HhasConstant;
26 use hhbc_by_ref_hhas_function::HhasFunction;
27 use hhbc_by_ref_hhas_method::{HhasMethod, HhasMethodFlags};
28 use hhbc_by_ref_hhas_param::HhasParam;
29 use hhbc_by_ref_hhas_pos::{HhasPos, HhasSpan};
30 use hhbc_by_ref_hhas_program::HhasProgram;
31 use hhbc_by_ref_hhas_property::HhasProperty;
32 use hhbc_by_ref_hhas_record_def::{Field, HhasRecord};
33 use hhbc_by_ref_hhas_symbol_refs::{HhasSymbolRefs, IncludePath};
34 use hhbc_by_ref_hhas_type::{constraint, HhasTypeInfo};
35 use hhbc_by_ref_hhas_type_const::HhasTypeConstant;
36 use hhbc_by_ref_hhas_typedef::HhasTypedef;
37 use hhbc_by_ref_hhbc_ast::*;
38 use hhbc_by_ref_hhbc_id::{class::ClassType, Id};
39 use hhbc_by_ref_hhbc_string_utils::{
40     float, integer, is_class, is_parent, is_self, is_static, is_xhp, lstrip, mangle, quote_string,
41     quote_string_with_escape, strip_global_ns, strip_ns, triple_quote_string, types,
43 use hhbc_by_ref_instruction_sequence::{Error::Unrecoverable, InstrSeq};
44 use hhbc_by_ref_iterator::Id as IterId;
45 use hhbc_by_ref_label::Label;
46 use hhbc_by_ref_local::Local;
47 use hhbc_by_ref_runtime::TypedValue;
48 use lazy_static::lazy_static;
49 use naming_special_names_rust::classes;
50 use ocaml_helper::escaped;
51 use oxidized::{ast, ast_defs, local_id};
52 use regex::Regex;
53 use write::*;
55 use std::{borrow::Cow, io::Write as _, path::Path, write};
57 pub struct ExprEnv<'arena, 'e> {
58     pub codegen_env: Option<&'e HhasBodyEnv<'arena>>,
61 pub fn print_program<W: Write>(
62     ctx: &mut Context,
63     w: &mut W,
64     prog: &HhasProgram,
65 ) -> Result<(), W::Error> {
66     match ctx.path {
67         Some(p) => {
68             let abs = p.to_absolute();
69             let p = escape(abs.to_str().ok_or(Error::InvalidUTF8)?);
71             concat_str_by(w, " ", ["#", p.as_ref(), "starts here"])?;
73             newline(w)?;
75             newline(w)?;
76             concat_str(w, [".filepath ", format!("\"{}\"", p).as_str(), ";"])?;
78             newline(w)?;
79             handle_not_impl(|| print_program_(ctx, w, prog))?;
81             newline(w)?;
82             concat_str_by(w, " ", ["#", p.as_ref(), "ends here"])?;
84             newline(w)
85         }
86         None => {
87             w.write("#starts here")?;
89             newline(w)?;
90             handle_not_impl(|| print_program_(ctx, w, prog))?;
92             newline(w)?;
93             w.write("#ends here")?;
95             newline(w)
96         }
97     }
100 fn get_fatal_op(f: &FatalOp) -> &str {
101     match f {
102         FatalOp::Parse => "Parse",
103         FatalOp::Runtime => "Runtime",
104         FatalOp::RuntimeOmitFrame => "RuntimeOmitFrame",
105     }
108 fn print_program_<W: Write>(
109     ctx: &mut Context,
110     w: &mut W,
111     prog: &HhasProgram,
112 ) -> Result<(), W::Error> {
113     if let Just(Triple(fop, p, msg)) = &prog.fatal {
114         newline(w)?;
115         let HhasPos {
116             line_begin,
117             line_end,
118             col_begin,
119             col_end,
120         } = p;
121         let pos = format!("{}:{},{}:{}", line_begin, col_begin, line_end, col_end);
122         concat_str(
123             w,
124             [
125                 ".fatal ",
126                 pos.as_ref(),
127                 " ",
128                 get_fatal_op(fop),
129                 " \"",
130                 escape(msg).as_ref(),
131                 "\";",
132             ],
133         )?;
134     }
136     newline(w)?;
137     concat(w, &prog.adata, |w, a| print_adata_region(ctx, w, a))?;
138     concat(w, &prog.functions, |w, f| print_fun_def(ctx, w, f))?;
139     concat(w, &prog.classes, |w, cd| print_class_def(ctx, w, cd))?;
140     concat(w, &prog.record_defs, |w, rd| print_record_def(ctx, w, rd))?;
141     concat(w, &prog.constants, |w, c| print_constant(ctx, w, c))?;
142     concat(w, &prog.typedefs, |w, td| print_typedef(ctx, w, td))?;
143     print_file_attributes(ctx, w, prog.file_attributes.as_ref())?;
145     if ctx.dump_symbol_refs() {
146         print_include_region(ctx, w, &prog.symbol_refs.includes)?;
147         print_symbol_ref_regions(ctx, w, &prog.symbol_refs)?;
148     }
149     Ok(())
152 fn print_include_region<W: Write>(
153     ctx: &mut Context,
154     w: &mut W,
155     includes: &Slice<IncludePath>,
156 ) -> Result<(), W::Error> {
157     fn print_path<W: Write>(w: &mut W, p: &Path) -> Result<(), W::Error> {
158         option(w, p.to_str(), |w, p: &str| write!(w, "\n  {}", p))
159     }
160     fn print_if_exists<W: Write>(w: &mut W, p: &Path) -> Result<(), W::Error> {
161         if p.exists() { print_path(w, p) } else { Ok(()) }
162     }
163     fn print_include<W: Write>(
164         ctx: &mut Context,
165         w: &mut W,
166         inc: IncludePath,
167     ) -> Result<(), W::Error> {
168         let include_roots = ctx.include_roots;
169         let alloc = bumpalo::Bump::new();
170         match inc.into_doc_root_relative(&alloc, include_roots) {
171             IncludePath::Absolute(p) => print_if_exists(w, Path::new(&p.as_str())),
172             IncludePath::SearchPathRelative(p) => {
173                 let path_from_cur_dirname = ctx
174                     .path
175                     .and_then(|p| p.path().parent())
176                     .unwrap_or_else(|| Path::new(""))
177                     .join(&p.as_str());
178                 if path_from_cur_dirname.exists() {
179                     print_path(w, &path_from_cur_dirname)
180                 } else {
181                     let search_paths = ctx.include_search_paths;
182                     for prefix in search_paths.iter() {
183                         let path = Path::new(prefix).join(&p.as_str());
184                         if path.exists() {
185                             return print_path(w, &path);
186                         }
187                     }
188                     Ok(())
189                 }
190             }
191             IncludePath::IncludeRootRelative(v, p) => {
192                 if !p.is_empty() {
193                     include_roots
194                         .get(&v.as_str().to_owned())
195                         .iter()
196                         .try_for_each(|ir| {
197                             let doc_root = ctx.doc_root;
198                             let resolved = Path::new(doc_root).join(ir).join(&p.as_str());
199                             print_if_exists(w, &resolved)
200                         })?
201                 }
202                 Ok(())
203             }
204             IncludePath::DocRootRelative(p) => {
205                 let doc_root = ctx.doc_root;
206                 let resolved = Path::new(doc_root).join(&p.as_str());
207                 print_if_exists(w, &resolved)
208             }
209         }
210     }
211     if !includes.is_empty() {
212         w.write("\n.includes {")?;
213         for inc in includes.as_ref().iter() {
214             // TODO(hrust): avoid clone. Rethink onwership of inc in
215             // hhas_symbol_refs_rust::IncludePath::into_doc_root_relative
216             print_include(ctx, w, inc.clone())?;
217         }
218         w.write("\n}\n")
219     } else {
220         Ok(())
221     }
224 fn print_symbol_ref_regions<'arena, W: Write>(
225     ctx: &mut Context,
226     w: &mut W,
227     symbol_refs: &HhasSymbolRefs<'arena>,
228 ) -> Result<(), W::Error> {
229     let mut print_region = |name, refs: &Slice<'arena, Str<'arena>>| {
230         if !refs.is_empty() {
231             ctx.newline(w)?;
232             write!(w, ".{} {{", name)?;
233             ctx.block(w, |c, w| {
234                 for s in refs.as_ref().iter() {
235                     c.newline(w)?;
236                     w.write(s)?;
237                 }
238                 Ok(())
239             })?;
240             w.write("\n}\n")
241         } else {
242             Ok(())
243         }
244     };
245     print_region("constant_refs", &symbol_refs.constants)?;
246     print_region("function_refs", &symbol_refs.functions)?;
247     print_region("class_refs", &symbol_refs.classes)
250 fn print_adata_region<W: Write>(
251     ctx: &mut Context,
252     w: &mut W,
253     adata: &HhasAdata,
254 ) -> Result<(), W::Error> {
255     concat_str_by(w, " ", [".adata", adata.id.as_str(), "= "])?;
256     triple_quotes(w, |w| print_adata(ctx, w, &adata.value))?;
257     w.write(";")?;
258     ctx.newline(w)
261 fn print_typedef<W: Write>(ctx: &mut Context, w: &mut W, td: &HhasTypedef) -> Result<(), W::Error> {
262     newline(w)?;
263     w.write(".alias ")?;
264     print_typedef_attributes(ctx, w, td)?;
265     w.write(td.name.to_raw_string())?;
266     w.write(" = ")?;
267     print_typedef_info(w, &td.type_info)?;
268     w.write(" ")?;
269     print_span(w, &td.span)?;
270     w.write(" ")?;
271     triple_quotes(w, |w| print_adata(ctx, w, &td.type_structure))?;
272     w.write(";")
275 fn print_typedef_attributes<W: Write>(
276     ctx: &mut Context,
277     w: &mut W,
278     td: &HhasTypedef,
279 ) -> Result<(), W::Error> {
280     let mut specials = vec![];
281     if ctx.is_system_lib() {
282         specials.push("persistent");
283     }
284     print_special_and_user_attrs(ctx, w, &specials[..], td.attributes.as_ref())
287 fn handle_not_impl<E: std::fmt::Debug, F: FnOnce() -> Result<(), E>>(f: F) -> Result<(), E> {
288     let r = f();
289     match &r {
290         Err(Error::NotImpl(msg)) => {
291             println!("#### NotImpl: {}", msg);
292             eprintln!("NotImpl: {}", msg);
293             Ok(())
294         }
295         _ => r,
296     }
299 fn print_fun_def<W: Write>(
300     ctx: &mut Context,
301     w: &mut W,
302     fun_def: &HhasFunction,
303 ) -> Result<(), W::Error> {
304     let body = &fun_def.body;
305     newline(w)?;
306     w.write(".function ")?;
307     print_upper_bounds_(w, &body.upper_bounds)?;
308     w.write(" ")?;
309     print_fun_attrs(ctx, w, fun_def)?;
310     print_span(w, &fun_def.span)?;
311     w.write(" ")?;
312     option(
313         w,
314         &(Option::from(body.return_type_info.clone())),
315         |w, ti| {
316             print_type_info(w, ti)?;
317             w.write(" ")
318         },
319     )?;
320     w.write(fun_def.name.to_raw_string())?;
321     print_params(ctx, w, fun_def.params())?;
322     if fun_def.is_generator() {
323         w.write(" isGenerator")?;
324     }
325     if fun_def.is_async() {
326         w.write(" isAsync")?;
327     }
328     if fun_def.is_pair_generator() {
329         w.write(" isPairGenerator")?;
330     }
331     if fun_def.is_readonly_return() {
332         w.write(" isReadonlyReturn")?;
333     }
334     w.write(" ")?;
335     braces(w, |w| {
336         ctx.block(w, |c, w| print_body(c, w, body, &fun_def.coeffects))?;
337         newline(w)
338     })?;
340     newline(w)
343 fn print_requirement<W: Write>(
344     ctx: &mut Context,
345     w: &mut W,
346     r: &Pair<ClassType<'_>, hhas_class::TraitReqKind>,
347 ) -> Result<(), W::Error> {
348     ctx.newline(w)?;
349     w.write(".require ")?;
350     match r {
351         Pair(name, hhas_class::TraitReqKind::MustExtend) => {
352             write!(w, "extends <{}>;", name.to_raw_string())
353         }
354         Pair(name, hhas_class::TraitReqKind::MustImplement) => {
355             write!(w, "implements <{}>;", name.to_raw_string())
356         }
357     }
360 fn print_type_constant<W: Write>(
361     ctx: &mut Context,
362     w: &mut W,
363     c: &HhasTypeConstant,
364 ) -> Result<(), W::Error> {
365     ctx.newline(w)?;
366     concat_str_by(w, " ", [".const", c.name.as_str(), "isType"])?;
367     if c.is_abstract {
368         w.write(" isAbstract")?;
369     }
370     option(w, Option::from(c.initializer.as_ref()), |w, init| {
371         w.write(" = ")?;
372         triple_quotes(w, |w| print_adata(ctx, w, init))
373     })?;
374     w.write(";")
377 fn print_ctx_constant<W: Write>(
378     ctx: &mut Context,
379     w: &mut W,
380     c: &HhasCtxConstant,
381 ) -> Result<(), W::Error> {
382     ctx.newline(w)?;
383     concat_str_by(w, " ", [".ctx", &c.name])?;
384     if c.is_abstract {
385         w.write(" isAbstract")?;
386     }
387     if let Some(coeffects) = HhasCoeffects::vec_to_string(&c.coeffects.0, |c| c.to_string()) {
388         w.write(" ")?;
389         w.write(coeffects)?;
390     }
391     if let Some(coeffects) = HhasCoeffects::vec_to_string(&c.coeffects.1, |c| c.to_string()) {
392         w.write(" ")?;
393         w.write(coeffects)?;
394     }
395     w.write(";")?;
396     Ok(())
399 fn print_property_doc_comment<W: Write>(w: &mut W, p: &HhasProperty) -> Result<(), W::Error> {
400     if let Just(s) = p.doc_comment.as_ref() {
401         w.write(triple_quote_string(s.as_str()))?;
402         w.write(" ")?;
403     }
404     Ok(())
407 fn print_property_attributes<W: Write>(
408     ctx: &mut Context,
409     w: &mut W,
410     property: &HhasProperty,
411 ) -> Result<(), W::Error> {
412     let mut special_attributes = vec![];
413     if property.is_late_init() {
414         special_attributes.push("late_init")
415     };
416     if property.is_no_bad_redeclare() {
417         special_attributes.push("no_bad_redeclare")
418     };
419     if property.initial_satisfies_tc() {
420         special_attributes.push("initial_satisfies_tc")
421     }
422     if property.no_implicit_null() {
423         special_attributes.push("no_implicit_null")
424     }
425     if property.has_system_initial() {
426         special_attributes.push("sys_initial_val")
427     }
428     if property.is_const() {
429         special_attributes.push("is_const")
430     }
431     if property.is_readonly() {
432         special_attributes.push("readonly")
433     }
434     if property.is_deep_init() {
435         special_attributes.push("deep_init")
436     }
437     if property.is_lsb() {
438         special_attributes.push("lsb")
439     }
440     if property.is_static() {
441         special_attributes.push("static")
442     }
443     special_attributes.push(property.visibility.as_ref());
444     special_attributes.reverse();
446     w.write("[")?;
447     concat_by(w, " ", &special_attributes, |w, a| w.write(a))?;
448     if !special_attributes.is_empty() && !property.attributes.is_empty() {
449         w.write(" ")?;
450     }
451     print_attributes(ctx, w, &property.attributes)?;
452     w.write("] ")
455 fn print_property_type_info<W: Write>(w: &mut W, p: &HhasProperty) -> Result<(), W::Error> {
456     print_type_info(w, &p.type_info)?;
457     w.write(" ")
460 fn print_property<W: Write>(
461     ctx: &mut Context,
462     w: &mut W,
463     class_def: &HhasClass,
464     property: &HhasProperty,
465 ) -> Result<(), W::Error> {
466     newline(w)?;
467     w.write("  .property ")?;
468     print_property_attributes(ctx, w, property)?;
469     print_property_doc_comment(w, property)?;
470     print_property_type_info(w, property)?;
471     w.write(property.name.to_raw_string())?;
472     w.write(" =\n    ")?;
473     let initial_value = property.initial_value.as_ref();
474     if class_def.is_closure() || initial_value == Just(&TypedValue::Uninit) {
475         w.write("uninit;")
476     } else {
477         triple_quotes(w, |w| match initial_value {
478             Nothing => w.write("N;"),
479             Just(value) => print_adata(ctx, w, &value),
480         })?;
481         w.write(";")
482     }
485 fn print_constant<W: Write>(
486     ctx: &mut Context,
487     w: &mut W,
488     c: &HhasConstant,
489 ) -> Result<(), W::Error> {
490     ctx.newline(w)?;
491     w.write(".const ")?;
492     w.write(c.name.to_raw_string())?;
493     if c.is_abstract {
494         w.write(" isAbstract")?;
495     }
496     match c.value.as_ref() {
497         Just(TypedValue::Uninit) => w.write(" = uninit")?,
498         Just(value) => {
499             w.write(" = ")?;
500             triple_quotes(w, |w| print_adata(ctx, w, value))?;
501         }
502         Nothing => {}
503     }
504     w.write(";")
507 fn print_enum_ty<W: Write>(ctx: &mut Context, w: &mut W, c: &HhasClass) -> Result<(), W::Error> {
508     if let Just(et) = c.enum_type.as_ref() {
509         ctx.newline(w)?;
510         w.write(".enum_ty ")?;
511         print_type_info_(w, true, et)?;
512         w.write(";")?;
513     }
514     Ok(())
517 fn print_doc_comment<'arena, W: Write>(
518     ctx: &mut Context,
519     w: &mut W,
520     doc_comment: &Maybe<Str<'arena>>,
521 ) -> Result<(), W::Error> {
522     if let Just(cmt) = doc_comment {
523         ctx.newline(w)?;
524         write!(w, ".doc {};", triple_quote_string(cmt.as_str()))?;
525     }
526     Ok(())
529 fn print_use_precedence<'arena, W: Write>(
530     ctx: &mut Context,
531     w: &mut W,
532     Triple(id1, id2, ids): &Triple<
533         ClassType<'arena>,
534         ClassType<'arena>,
535         Slice<'arena, ClassType<'arena>>,
536     >,
537 ) -> Result<(), W::Error> {
538     ctx.newline(w)?;
539     concat_str(w, [id1.to_raw_string(), "::", id2.to_raw_string()])?;
540     w.write(" insteadof ")?;
541     let unique_ids: IndexSet<&str> = ids.as_ref().iter().map(|i| i.to_raw_string()).collect();
542     concat_str_by(w, " ", unique_ids.iter().collect::<Vec<_>>())?;
543     w.write(";")
546 fn print_use_as_visibility<W: Write>(w: &mut W, u: UseAsVisibility) -> Result<(), W::Error> {
547     w.write(match u {
548         UseAsVisibility::UseAsPublic => "public",
549         UseAsVisibility::UseAsPrivate => "private",
550         UseAsVisibility::UseAsProtected => "protected",
551         UseAsVisibility::UseAsFinal => "final",
552     })
555 fn print_use_alias<'arena, W: Write>(
556     ctx: &mut Context,
557     w: &mut W,
558     Quadruple(ido1, id, ido2, kindl): &Quadruple<
559         Maybe<ClassType<'arena>>,
560         ClassType<'arena>,
561         Maybe<ClassType<'arena>>,
562         Slice<'arena, UseAsVisibility>,
563     >,
564 ) -> Result<(), W::Error> {
565     ctx.newline(w)?;
566     let id = id.to_raw_string();
567     option_or(
568         w,
569         ido1.as_ref(),
570         |w, i: &ClassType<'arena>| concat_str(w, [i.to_raw_string(), "::", id]),
571         id,
572     )?;
573     w.write(" as ")?;
574     if !kindl.is_empty() {
575         square(w, |w| {
576             concat_by(w, " ", kindl, |w, k| print_use_as_visibility(w, *k))
577         })?;
578     }
579     w.write_if(!kindl.is_empty() && ido2.is_just(), " ")?;
580     option(w, ido2.as_ref(), |w, i: &ClassType<'arena>| {
581         w.write(i.to_raw_string())
582     })?;
583     w.write(";")
586 fn print_uses<'arena, W: Write>(
587     ctx: &mut Context,
588     w: &mut W,
589     c: &HhasClass<'arena>,
590 ) -> Result<(), W::Error> {
591     if c.uses.is_empty() {
592         Ok(())
593     } else {
594         let unique_ids: IndexSet<&str> = c
595             .uses
596             .as_ref()
597             .iter()
598             .map(|e| strip_global_ns(e.as_str()))
599             .collect();
600         let unique_ids: Vec<_> = unique_ids.into_iter().collect();
602         newline(w)?;
603         w.write("  .use ")?;
604         concat_by(w, " ", unique_ids, |w, id| w.write(id))?;
606         if c.use_aliases.is_empty() && c.use_precedences.is_empty() {
607             w.write(";")
608         } else {
609             w.write(" {")?;
610             ctx.block(w, |ctx, w| {
611                 let precs: &[Triple<
612                     ClassType<'arena>,
613                     ClassType<'arena>,
614                     Slice<'arena, ClassType<'arena>>,
615                 >] = c.use_precedences.as_ref();
616                 for x in precs {
617                     print_use_precedence(ctx, w, x)?;
618                 }
619                 let aliases: &[Quadruple<
620                     Maybe<ClassType<'arena>>,
621                     ClassType<'arena>,
622                     Maybe<ClassType<'arena>>,
623                     Slice<'arena, UseAsVisibility>,
624                 >] = c.use_aliases.as_ref();
625                 for x in aliases {
626                     print_use_alias(ctx, w, x)?;
627                 }
628                 Ok(())
629             })?;
630             newline(w)?;
631             w.write("  }")
632         }
633     }
636 fn print_class_special_attributes<W: Write>(
637     ctx: &mut Context,
638     w: &mut W,
639     c: &HhasClass,
640 ) -> Result<(), W::Error> {
641     let user_attrs = c.attributes.as_ref();
642     let is_system_lib = ctx.is_system_lib();
644     let mut special_attributes: Vec<&str> = vec![];
645     if c.needs_no_reifiedinit() {
646         special_attributes.push("noreifiedinit")
647     }
648     if c.no_dynamic_props() {
649         special_attributes.push("no_dynamic_props")
650     }
651     if c.is_const() {
652         special_attributes.push("is_const")
653     }
654     if hhas_attribute::has_foldable(user_attrs) {
655         special_attributes.push("foldable")
656     }
657     if hhas_attribute::has_enum_class(user_attrs) {
658         special_attributes.push("enum_class")
659     }
660     if is_system_lib {
661         special_attributes.extend(&["persistent", "builtin", "unique"])
662     }
663     if hhas_attribute::has_dynamically_constructible(user_attrs) {
664         special_attributes.push("dyn_constructible");
665     }
666     if c.is_closure() && !is_system_lib {
667         special_attributes.push("unique");
668     }
669     if c.is_closure() {
670         special_attributes.push("no_override");
671     }
672     if c.is_trait() {
673         special_attributes.push("trait");
674     }
675     if c.is_interface() {
676         special_attributes.push("interface");
677     }
678     if c.is_final() {
679         special_attributes.push("final");
680     }
681     if c.is_sealed() {
682         special_attributes.push("sealed");
683     }
684     if c.enum_type.is_just() && !hhbc_by_ref_hhas_attribute::has_enum_class(user_attrs) {
685         special_attributes.push("enum");
686     }
687     if c.is_abstract() {
688         special_attributes.push("abstract");
689     }
690     if special_attributes.is_empty() && user_attrs.is_empty() {
691         return Ok(());
692     }
694     special_attributes.reverse();
695     wrap_by_(w, "[", "] ", |w| {
696         concat_by(w, " ", &special_attributes, |w, a| w.write(a))?;
697         w.write_if(
698             !special_attributes.is_empty() && !user_attrs.is_empty(),
699             " ",
700         )?;
701         print_attributes(ctx, w, &user_attrs)
702     })
705 fn print_implements<W: Write>(w: &mut W, implements: &[ClassType<'_>]) -> Result<(), W::Error> {
706     if implements.is_empty() {
707         return Ok(());
708     }
709     w.write(" implements (")?;
710     concat_str_by(
711         w,
712         " ",
713         implements
714             .iter()
715             .map(|x| x.to_raw_string())
716             .collect::<Vec<_>>(),
717     )?;
718     w.write(")")
721 fn print_enum_includes<W: Write>(
722     w: &mut W,
723     enum_includes: &[ClassType<'_>],
724 ) -> Result<(), W::Error> {
725     if enum_includes.is_empty() {
726         return Ok(());
727     }
728     w.write(" enum_includes (")?;
729     concat_str_by(
730         w,
731         " ",
732         enum_includes
733             .iter()
734             .map(|x| x.to_raw_string())
735             .collect::<Vec<_>>(),
736     )?;
737     w.write(")")
740 fn print_shadowed_tparams<'arena, W: Write>(
741     w: &mut W,
742     shadowed_tparams: impl AsRef<[Str<'arena>]>,
743 ) -> Result<(), W::Error> {
744     braces(w, |w| concat_str_by(w, ", ", shadowed_tparams))
747 fn print_method_def<W: Write>(
748     ctx: &mut Context,
749     w: &mut W,
750     method_def: &HhasMethod,
751 ) -> Result<(), W::Error> {
752     let body = &method_def.body;
753     newline(w)?;
754     w.write("  .method ")?;
755     print_shadowed_tparams(w, &body.shadowed_tparams)?;
756     print_upper_bounds_(w, &body.upper_bounds)?;
757     w.write(" ")?;
758     print_method_attrs(ctx, w, method_def)?;
759     print_span(w, &method_def.span)?;
760     w.write(" ")?;
761     option(w, &(Option::from(body.return_type_info.clone())), |w, t| {
762         print_type_info(w, t)?;
763         w.write(" ")
764     })?;
765     w.write(method_def.name.to_raw_string())?;
766     print_params(ctx, w, &body.params)?;
767     if method_def.flags.contains(HhasMethodFlags::IS_GENERATOR) {
768         w.write(" isGenerator")?;
769     }
770     if method_def.flags.contains(HhasMethodFlags::IS_ASYNC) {
771         w.write(" isAsync")?;
772     }
773     if method_def
774         .flags
775         .contains(HhasMethodFlags::IS_PAIR_GENERATOR)
776     {
777         w.write(" isPairGenerator")?;
778     }
779     if method_def.flags.contains(HhasMethodFlags::IS_CLOSURE_BODY) {
780         w.write(" isClosureBody")?;
781     }
782     if method_def
783         .flags
784         .contains(HhasMethodFlags::IS_READONLY_RETURN)
785     {
786         w.write(" isReadonlyReturn")?;
787     }
788     w.write(" ")?;
789     braces(w, |w| {
790         ctx.block(w, |c, w| print_body(c, w, body, &method_def.coeffects))?;
791         newline(w)?;
792         w.write("  ")
793     })
796 fn print_method_attrs<W: Write>(
797     ctx: &mut Context,
798     w: &mut W,
799     m: &HhasMethod,
800 ) -> Result<(), W::Error> {
801     use hhas_attribute::*;
802     let user_attrs = m.attributes.as_ref();
803     let mut special_attrs = vec![];
804     if has_provenance_skip_frame(user_attrs) {
805         special_attrs.push("prov_skip_frame")
806     }
807     if m.is_interceptable() {
808         special_attrs.push("interceptable");
809     }
810     let visibility = m.visibility.as_ref().to_string();
811     special_attrs.push(&visibility);
812     if m.flags.contains(HhasMethodFlags::IS_STATIC) {
813         special_attrs.push("static");
814     }
815     if m.flags.contains(HhasMethodFlags::IS_FINAL) {
816         special_attrs.push("final");
817     }
818     if m.flags.contains(HhasMethodFlags::IS_ABSTRACT) {
819         special_attrs.push("abstract");
820     }
821     if has_foldable(user_attrs) {
822         special_attrs.push("foldable");
823     }
824     if m.is_no_injection() {
825         special_attrs.push("no_injection");
826     }
827     if ctx.is_system_lib() && has_native(user_attrs) && !is_native_opcode_impl(user_attrs) {
828         special_attrs.push("unique");
829     }
830     if ctx.is_system_lib() {
831         special_attrs.push("builtin");
832     }
833     if ctx.is_system_lib() && has_native(user_attrs) && !is_native_opcode_impl(user_attrs) {
834         special_attrs.push("persistent");
835     }
836     if ctx.is_system_lib() || (has_dynamically_callable(user_attrs) && !m.is_memoize_impl()) {
837         special_attrs.push("dyn_callable")
838     }
839     print_special_and_user_attrs(ctx, w, &special_attrs, user_attrs)
842 fn print_class_def<'arena, W: Write>(
843     ctx: &mut Context,
844     w: &mut W,
845     class_def: &HhasClass<'arena>,
846 ) -> Result<(), W::Error> {
847     newline(w)?;
848     w.write(".class ")?;
849     print_upper_bounds(w, class_def.upper_bounds.as_ref())?;
850     w.write(" ")?;
851     print_class_special_attributes(ctx, w, class_def)?;
852     w.write(class_def.name.to_raw_string())?;
853     w.write(" ")?;
854     print_span(w, &class_def.span)?;
855     print_extends(
856         w,
857         Option::from(class_def.base.as_ref()).map(|x: &ClassType<'arena>| x.to_raw_string()),
858     )?;
859     print_implements(w, class_def.implements.as_ref())?;
860     print_enum_includes(w, class_def.enum_includes.as_ref())?;
861     w.write(" {")?;
862     ctx.block(w, |c, w| {
863         print_doc_comment(c, w, &class_def.doc_comment)?;
864         print_uses(c, w, class_def)?;
865         print_enum_ty(c, w, class_def)?;
866         for x in class_def.requirements.as_ref() {
867             print_requirement(c, w, x)?;
868         }
869         for x in class_def.constants.as_ref() {
870             print_constant(c, w, x)?;
871         }
872         for x in class_def.type_constants.as_ref() {
873             print_type_constant(c, w, x)?;
874         }
875         for x in class_def.ctx_constants.as_ref() {
876             print_ctx_constant(c, w, x)?;
877         }
878         for x in class_def.properties.as_ref() {
879             print_property(c, w, class_def, x)?;
880         }
881         for m in class_def.methods.as_ref() {
882             print_method_def(c, w, m)?;
883         }
884         Ok(())
885     })?;
886     newline(w)?;
887     w.write("}")?;
888     newline(w)
891 fn print_pos_as_prov_tag<W: Write>(
892     ctx: &Context,
893     w: &mut W,
894     loc: &Option<ast_defs::Pos>,
895 ) -> Result<(), W::Error> {
896     match loc {
897         Some(l) if ctx.array_provenance => {
898             let (line, ..) = l.info_pos();
899             let filename = l.filename().to_absolute();
900             let filename = match filename.to_str().unwrap() {
901                 "" => "(unknown hackc filename)",
902                 x => x,
903             };
904             write!(
905                 w,
906                 "p:i:{};s:{}:{};",
907                 line,
908                 filename.len(),
909                 quote_string_with_escape(filename)
910             )
911         }
912         _ => Ok(()),
913     }
916 fn print_hhbc_id<'a, W: Write>(w: &mut W, id: &impl Id<'a>) -> Result<(), W::Error> {
917     quotes(w, |w| w.write(escape(id.to_raw_string())))
920 fn print_function_id<W: Write>(w: &mut W, id: &FunctionId) -> Result<(), W::Error> {
921     print_hhbc_id(w, id)
924 fn print_class_id<W: Write>(w: &mut W, id: &ClassId) -> Result<(), W::Error> {
925     print_hhbc_id(w, id)
928 fn print_method_id<W: Write>(w: &mut W, id: &MethodId) -> Result<(), W::Error> {
929     print_hhbc_id(w, id)
932 fn print_const_id<W: Write>(w: &mut W, id: &ConstId) -> Result<(), W::Error> {
933     print_hhbc_id(w, id)
936 fn print_prop_id<W: Write>(w: &mut W, id: &PropId) -> Result<(), W::Error> {
937     print_hhbc_id(w, id)
940 fn print_adata_id<W: Write>(w: &mut W, id: &AdataId) -> Result<(), W::Error> {
941     concat_str(w, ["@", id.as_str()])
944 fn print_adata_mapped_argument<W: Write, F, V>(
945     ctx: &mut Context,
946     w: &mut W,
947     col_type: &str,
948     loc: &Option<ast_defs::Pos>,
949     values: &[V],
950     f: F,
951 ) -> Result<(), W::Error>
952 where
953     F: Fn(&mut Context, &mut W, &V) -> Result<(), W::Error>,
955     write!(w, "{}:{}:{{", col_type, values.len(),)?;
956     print_pos_as_prov_tag(ctx, w, loc)?;
957     for v in values {
958         f(ctx, w, v)?
959     }
960     write!(w, "}}")
963 fn print_adata_collection_argument<W: Write>(
964     ctx: &mut Context,
965     w: &mut W,
966     col_type: &str,
967     loc: &Option<ast_defs::Pos>,
968     values: &[TypedValue],
969 ) -> Result<(), W::Error> {
970     print_adata_mapped_argument(ctx, w, col_type, loc, values, &print_adata)
973 fn print_adata_dict_collection_argument<W: Write>(
974     ctx: &mut Context,
975     w: &mut W,
976     col_type: &str,
977     loc: &Option<ast_defs::Pos>,
978     pairs: &[Pair<TypedValue, TypedValue>],
979 ) -> Result<(), W::Error> {
980     print_adata_mapped_argument(ctx, w, col_type, loc, pairs, |ctx, w, Pair(v1, v2)| {
981         print_adata(ctx, w, v1)?;
982         print_adata(ctx, w, v2)
983     })
986 fn print_adata<W: Write>(ctx: &mut Context, w: &mut W, tv: &TypedValue) -> Result<(), W::Error> {
987     match tv {
988         TypedValue::Uninit => w.write("uninit"),
989         TypedValue::Null => w.write("N;"),
990         TypedValue::String(s) => {
991             write!(w, "s:{}:{};", s.len(), quote_string_with_escape(s.as_str()))
992         }
993         TypedValue::LazyClass(s) => {
994             write!(w, "l:{}:{};", s.len(), quote_string_with_escape(s.as_str()))
995         }
996         TypedValue::Float(f) => write!(w, "d:{};", float::to_string(*f)),
997         TypedValue::Int(i) => write!(w, "i:{};", i),
998         // TODO: The False case seems to sometimes be b:0 and sometimes i:0.  Why?
999         TypedValue::Bool(false) => w.write("b:0;"),
1000         TypedValue::Bool(true) => w.write("b:1;"),
1001         TypedValue::Vec(values) => {
1002             print_adata_collection_argument(ctx, w, VEC_PREFIX, &None, values.as_ref())
1003         }
1004         TypedValue::Dict(pairs) => {
1005             print_adata_dict_collection_argument(ctx, w, DICT_PREFIX, &None, pairs.as_ref())
1006         }
1007         TypedValue::Keyset(values) => {
1008             print_adata_collection_argument(ctx, w, KEYSET_PREFIX, &None, values.as_ref())
1009         }
1010         TypedValue::HhasAdata(s) => w.write(escaped(s.as_str())),
1011     }
1014 fn print_attribute<W: Write>(
1015     ctx: &mut Context,
1016     w: &mut W,
1017     a: &HhasAttribute,
1018 ) -> Result<(), W::Error> {
1019     write!(
1020         w,
1021         "\"{}\"(\"\"\"{}:{}:{{",
1022         a.name.as_str(),
1023         VEC_PREFIX,
1024         a.arguments.len()
1025     )?;
1026     concat(w, &a.arguments, |w, arg| print_adata(ctx, w, arg))?;
1027     w.write("}\"\"\")")
1030 fn print_attributes<'a, W: Write>(
1031     ctx: &mut Context,
1032     w: &mut W,
1033     al: impl AsRef<[HhasAttribute<'a>]>,
1034 ) -> Result<(), W::Error> {
1035     // Adjust for underscore coming before alphabet
1036     let al: Vec<&HhasAttribute> = al
1037         .as_ref()
1038         .iter()
1039         .sorted_by_key(|a| (!a.name.as_str().starts_with("__"), a.name.as_str()))
1040         .collect();
1041     concat_by(w, " ", &al, |w, a| print_attribute(ctx, w, a))
1044 fn print_file_attributes<W: Write>(
1045     ctx: &mut Context,
1046     w: &mut W,
1047     al: &[HhasAttribute],
1048 ) -> Result<(), W::Error> {
1049     if al.is_empty() {
1050         return Ok(());
1051     }
1052     newline(w)?;
1053     w.write(".file_attributes [")?;
1054     print_attributes(ctx, w, al)?;
1055     w.write("] ;")?;
1056     newline(w)
1059 fn is_bareword_char(c: &u8) -> bool {
1060     match *c {
1061         b'_' | b'.' | b'$' | b'\\' => true,
1062         c => (b'0'..=b'9').contains(&c) || (b'a'..=b'z').contains(&c) || (b'A'..=b'Z').contains(&c),
1063     }
1066 fn print_body<W: Write>(
1067     ctx: &mut Context,
1068     w: &mut W,
1069     body: &HhasBody,
1070     coeffects: &HhasCoeffects,
1071 ) -> Result<(), W::Error> {
1072     print_doc_comment(ctx, w, &body.doc_comment)?;
1073     if body.is_memoize_wrapper {
1074         ctx.newline(w)?;
1075         w.write(".ismemoizewrapper;")?;
1076     }
1077     if body.is_memoize_wrapper_lsb {
1078         ctx.newline(w)?;
1079         w.write(".ismemoizewrapperlsb;")?;
1080     }
1081     if body.num_iters > 0 {
1082         ctx.newline(w)?;
1083         write!(w, ".numiters {};", body.num_iters)?;
1084     }
1085     if !body.decl_vars.is_empty() {
1086         ctx.newline(w)?;
1087         w.write(".declvars ")?;
1088         concat_by(w, " ", &body.decl_vars, |w, var| {
1089             if var.as_str().as_bytes().iter().all(is_bareword_char) {
1090                 w.write(var)
1091             } else {
1092                 quotes(w, |w| w.write(escaper::escape(var.as_str())))
1093             }
1094         })?;
1095         w.write(";")?;
1096     }
1097     if body.num_closures > 0 {
1098         ctx.newline(w)?;
1099         write!(w, ".numclosures {};", body.num_closures)?;
1100     }
1101     for s in HhasCoeffects::coeffects_to_hhas(&coeffects).iter() {
1102         ctx.newline(w)?;
1103         w.write(s)?;
1104     }
1105     print_instructions(ctx, w, &body.body_instrs)
1108 fn print_instructions<W: Write>(
1109     ctx: &mut Context,
1110     w: &mut W,
1111     instr_seq: &InstrSeq,
1112 ) -> Result<(), W::Error> {
1113     use Instruct::*;
1114     use InstructTry::*;
1115     for instr in instr_seq.compact_iter() {
1116         match instr {
1117             ISpecialFlow(_) => return Err(Error::fail("Cannot break/continue 1 level")),
1118             IComment(_) => {
1119                 // indetation = 0
1120                 newline(w)?;
1121                 print_instr(w, instr)?;
1122             }
1123             ILabel(_) => ctx.unblock(w, |c, w| {
1124                 c.newline(w)?;
1125                 print_instr(w, instr)
1126             })?,
1127             ITry(TryCatchBegin) => {
1128                 ctx.newline(w)?;
1129                 print_instr(w, instr)?;
1130                 ctx.indent_inc();
1131             }
1132             ITry(TryCatchMiddle) => ctx.unblock(w, |c, w| {
1133                 c.newline(w)?;
1134                 print_instr(w, instr)
1135             })?,
1136             ITry(TryCatchEnd) => {
1137                 ctx.indent_dec();
1138                 ctx.newline(w)?;
1139                 print_instr(w, instr)?;
1140             }
1141             _ => {
1142                 ctx.newline(w)?;
1143                 print_instr(w, instr)?;
1144             }
1145         }
1146     }
1147     Ok(())
1150 fn if_then<F: FnOnce() -> R, R>(cond: bool, f: F) -> Option<R> {
1151     if cond { Some(f()) } else { None }
1154 fn print_fcall_args<W: Write>(
1155     w: &mut W,
1156     FcallArgs(fls, num_args, num_rets, inouts, readonly, async_eager_label, context): &FcallArgs,
1157 ) -> Result<(), W::Error> {
1158     use FcallFlags as F;
1159     let mut flags = vec![];
1160     if_then(fls.contains(F::HAS_UNPACK), || flags.push("Unpack"));
1161     if_then(fls.contains(F::HAS_GENERICS), || flags.push("Generics"));
1162     if_then(fls.contains(F::LOCK_WHILE_UNWINDING), || {
1163         flags.push("LockWhileUnwinding")
1164     });
1165     if_then(fls.contains(F::ENFORCE_MUTABLE_RETURN), || {
1166         flags.push("EnforceMutableReturn")
1167     });
1168     angle(w, |w| concat_str_by(w, " ", flags))?;
1169     w.write(" ")?;
1170     print_int(w, num_args)?;
1171     w.write(" ")?;
1172     print_int(w, num_rets)?;
1173     w.write(" ")?;
1174     quotes(w, |w| {
1175         concat_by(w, "", inouts, |w, i| w.write(if *i { "1" } else { "0" }))
1176     })?;
1177     w.write(" ")?;
1178     quotes(w, |w| {
1179         concat_by(w, "", readonly, |w, i| w.write(if *i { "1" } else { "0" }))
1180     })?;
1181     w.write(" ")?;
1182     option_or(w, async_eager_label.as_ref(), print_label, "-")?;
1183     w.write(" ")?;
1184     match context {
1185         Just(s) => quotes(w, |w| w.write(s)),
1186         Nothing => w.write("\"\""),
1187     }
1190 fn print_special_cls_ref<W: Write>(w: &mut W, cls_ref: &SpecialClsRef) -> Result<(), W::Error> {
1191     w.write(match cls_ref {
1192         SpecialClsRef::Static => "Static",
1193         SpecialClsRef::Self_ => "Self",
1194         SpecialClsRef::Parent => "Parent",
1195     })
1198 fn print_null_flavor<W: Write>(w: &mut W, f: &ObjNullFlavor) -> Result<(), W::Error> {
1199     w.write(match f {
1200         ObjNullFlavor::NullThrows => "NullThrows",
1201         ObjNullFlavor::NullSafe => "NullSafe",
1202     })
1205 fn print_instr<W: Write>(w: &mut W, instr: &Instruct) -> Result<(), W::Error> {
1206     fn print_call<W: Write>(w: &mut W, call: &InstructCall) -> Result<(), W::Error> {
1207         use InstructCall as I;
1208         match call {
1209             I::NewObj => w.write("NewObj"),
1210             I::NewObjR => w.write("NewObjR"),
1211             I::NewObjD(cid) => {
1212                 w.write("NewObjD ")?;
1213                 print_class_id(w, cid)
1214             }
1215             I::NewObjRD(cid) => {
1216                 w.write("NewObjRD ")?;
1217                 print_class_id(w, cid)
1218             }
1219             I::NewObjS(r) => {
1220                 w.write("NewObjS ")?;
1221                 print_special_cls_ref(w, r)
1222             }
1223             I::FCall(fcall_args) => {
1224                 w.write("FCall ")?;
1225                 print_fcall_args(w, &fcall_args)?;
1226                 w.write(r#" "" """#)
1227             }
1228             I::FCallClsMethod(fcall_args, is_log_as_dynamic_call) => {
1229                 w.write("FCallClsMethod ")?;
1230                 print_fcall_args(w, fcall_args)?;
1231                 w.write(r#" "" "#)?;
1232                 w.write(match is_log_as_dynamic_call {
1233                     IsLogAsDynamicCallOp::LogAsDynamicCall => "LogAsDynamicCall",
1234                     IsLogAsDynamicCallOp::DontLogAsDynamicCall => "DontLogAsDynamicCall",
1235                 })
1236             }
1237             I::FCallClsMethodD(fcall_args, cid, mid) => {
1238                 w.write("FCallClsMethodD ")?;
1239                 print_fcall_args(w, fcall_args)?;
1240                 w.write(r#" "" "#)?;
1241                 print_class_id(w, cid)?;
1242                 w.write(" ")?;
1243                 print_method_id(w, mid)
1244             }
1245             I::FCallClsMethodS(fcall_args, r) => {
1246                 w.write("FCallClsMethodS ")?;
1247                 print_fcall_args(w, fcall_args)?;
1248                 w.write(r#" "" "#)?;
1249                 print_special_cls_ref(w, r)
1250             }
1251             I::FCallClsMethodSD(fcall_args, r, mid) => {
1252                 w.write("FCallClsMethodSD ")?;
1253                 print_fcall_args(w, fcall_args)?;
1254                 w.write(r#" "" "#)?;
1255                 print_special_cls_ref(w, r)?;
1256                 w.write(" ")?;
1257                 print_method_id(w, mid)
1258             }
1259             I::FCallCtor(fcall_args) => {
1260                 w.write("FCallCtor ")?;
1261                 print_fcall_args(w, fcall_args)?;
1262                 w.write(r#" """#)
1263             }
1264             I::FCallFunc(fcall_args) => {
1265                 w.write("FCallFunc ")?;
1266                 print_fcall_args(w, fcall_args)
1267             }
1268             I::FCallFuncD(fcall_args, id) => {
1269                 w.write("FCallFuncD ")?;
1270                 print_fcall_args(w, fcall_args)?;
1271                 w.write(" ")?;
1272                 print_function_id(w, id)
1273             }
1274             I::FCallObjMethod(fcall_args, nf) => {
1275                 w.write("FCallObjMethod ")?;
1276                 print_fcall_args(w, fcall_args)?;
1277                 w.write(r#" "" "#)?;
1278                 print_null_flavor(w, nf)
1279             }
1280             I::FCallObjMethodD(fcall_args, nf, id) => {
1281                 w.write("FCallObjMethodD ")?;
1282                 print_fcall_args(w, fcall_args)?;
1283                 w.write(r#" "" "#)?;
1284                 print_null_flavor(w, nf)?;
1285                 w.write(" ")?;
1286                 print_method_id(w, id)
1287             }
1288         }
1289     }
1291     fn print_get<W: Write>(w: &mut W, get: &InstructGet) -> Result<(), W::Error> {
1292         use InstructGet as IG;
1293         match get {
1294             IG::CGetL(id) => {
1295                 w.write("CGetL ")?;
1296                 print_local(w, id)
1297             }
1298             IG::CGetQuietL(id) => {
1299                 w.write("CGetQuietL ")?;
1300                 print_local(w, id)
1301             }
1302             IG::CGetL2(id) => {
1303                 w.write("CGetL2 ")?;
1304                 print_local(w, id)
1305             }
1306             IG::CUGetL(id) => {
1307                 w.write("CUGetL ")?;
1308                 print_local(w, id)
1309             }
1310             IG::PushL(id) => {
1311                 w.write("PushL ")?;
1312                 print_local(w, id)
1313             }
1314             IG::CGetG => w.write("CGetG"),
1315             IG::CGetS(op) => {
1316                 w.write("CGetS ")?;
1317                 print_readonly_op(w, op)
1318             }
1319             IG::ClassGetC => w.write("ClassGetC"),
1320             IG::ClassGetTS => w.write("ClassGetTS"),
1321         }
1322     }
1324     use Instruct::*;
1325     use InstructBasic as IB;
1326     match instr {
1327         IIterator(i) => print_iterator(w, i),
1328         IBasic(b) => w.write(match b {
1329             IB::Nop => "Nop",
1330             IB::EntryNop => "EntryNop",
1331             IB::PopC => "PopC",
1332             IB::PopU => "PopU",
1333             IB::Dup => "Dup",
1334         }),
1335         ILitConst(lit) => print_lit_const(w, lit),
1336         IOp(op) => print_op(w, op),
1337         IContFlow(cf) => print_control_flow(w, cf),
1338         ICall(c) => print_call(w, c),
1339         IMisc(misc) => print_misc(w, misc),
1340         IGet(get) => print_get(w, get),
1341         IMutator(mutator) => print_mutator(w, mutator),
1342         ILabel(l) => {
1343             print_label(w, l)?;
1344             w.write(":")
1345         }
1346         IIsset(i) => print_isset(w, i),
1347         IBase(i) => print_base(w, i),
1348         IFinal(i) => print_final(w, i),
1349         ITry(itry) => print_try(w, itry),
1350         IComment(s) => concat_str_by(w, " ", ["#", s.as_str()]),
1351         ISrcLoc(p) => write!(
1352             w,
1353             ".srcloc {}:{},{}:{};",
1354             p.line_begin, p.col_begin, p.line_end, p.col_end
1355         ),
1356         IAsync(a) => print_async(w, a),
1357         IGenerator(gen) => print_gen_creation_execution(w, gen),
1358         IIncludeEvalDefine(ed) => print_include_eval_define(w, ed),
1359         _ => Err(Error::fail("invalid instruction")),
1360     }
1363 fn print_base<W: Write>(w: &mut W, i: &InstructBase) -> Result<(), W::Error> {
1364     use InstructBase as I;
1365     match i {
1366         I::BaseGC(si, m) => {
1367             w.write("BaseGC ")?;
1368             print_stack_index(w, si)?;
1369             w.write(" ")?;
1370             print_member_opmode(w, m)
1371         }
1372         I::BaseGL(id, m) => {
1373             w.write("BaseGL ")?;
1374             print_local(w, id)?;
1375             w.write(" ")?;
1376             print_member_opmode(w, m)
1377         }
1378         I::BaseSC(si1, si2, m, op) => {
1379             w.write("BaseSC ")?;
1380             print_stack_index(w, si1)?;
1381             w.write(" ")?;
1382             print_stack_index(w, si2)?;
1383             w.write(" ")?;
1384             print_member_opmode(w, m)?;
1385             w.write(" ")?;
1386             print_readonly_op(w, op)
1387         }
1388         I::BaseL(id, m, op) => {
1389             w.write("BaseL ")?;
1390             print_local(w, id)?;
1391             w.write(" ")?;
1392             print_member_opmode(w, m)?;
1393             w.write(" ")?;
1394             print_readonly_op(w, op)
1395         }
1396         I::BaseC(si, m) => {
1397             w.write("BaseC ")?;
1398             print_stack_index(w, si)?;
1399             w.write(" ")?;
1400             print_member_opmode(w, m)
1401         }
1402         I::BaseH => w.write("BaseH"),
1403         I::Dim(m, mk) => {
1404             w.write("Dim ")?;
1405             print_member_opmode(w, m)?;
1406             w.write(" ")?;
1407             print_member_key(w, mk)
1408         }
1409     }
1412 fn print_stack_index<W: Write>(w: &mut W, si: &StackIndex) -> Result<(), W::Error> {
1413     w.write(si.to_string())
1416 fn print_member_opmode<W: Write>(w: &mut W, m: &MemberOpMode) -> Result<(), W::Error> {
1417     use MemberOpMode as M;
1418     w.write(match m {
1419         M::ModeNone => "None",
1420         M::Warn => "Warn",
1421         M::Define => "Define",
1422         M::Unset => "Unset",
1423         M::InOut => "InOut",
1424     })
1427 fn print_member_key<W: Write>(w: &mut W, mk: &MemberKey) -> Result<(), W::Error> {
1428     use MemberKey as M;
1429     match mk {
1430         M::EC(si, op) => {
1431             w.write("EC:")?;
1432             print_stack_index(w, si)?;
1433             w.write(" ")?;
1434             print_readonly_op(w, op)
1435         }
1436         M::EL(local, op) => {
1437             w.write("EL:")?;
1438             print_local(w, local)?;
1439             w.write(" ")?;
1440             print_readonly_op(w, op)
1441         }
1442         M::ET(s, op) => {
1443             w.write("ET:")?;
1444             quotes(w, |w| w.write(escape(s.as_str())))?;
1445             w.write(" ")?;
1446             print_readonly_op(w, op)
1447         }
1448         M::EI(i, op) => {
1449             concat_str(w, ["EI:", i.to_string().as_ref()])?;
1450             w.write(" ")?;
1451             print_readonly_op(w, op)
1452         }
1453         M::PC(si, op) => {
1454             w.write("PC:")?;
1455             print_stack_index(w, si)?;
1456             w.write(" ")?;
1457             print_readonly_op(w, op)
1458         }
1459         M::PL(local, op) => {
1460             w.write("PL:")?;
1461             print_local(w, local)?;
1462             w.write(" ")?;
1463             print_readonly_op(w, op)
1464         }
1465         M::PT(id, op) => {
1466             w.write("PT:")?;
1467             print_prop_id(w, id)?;
1468             w.write(" ")?;
1469             print_readonly_op(w, op)
1470         }
1471         M::QT(id, op) => {
1472             w.write("QT:")?;
1473             print_prop_id(w, id)?;
1474             w.write(" ")?;
1475             print_readonly_op(w, op)
1476         }
1477         M::W => w.write("W"),
1478     }
1481 fn print_iterator<W: Write>(w: &mut W, i: &InstructIterator) -> Result<(), W::Error> {
1482     use InstructIterator as I;
1483     match i {
1484         I::IterInit(iter_args, label) => {
1485             w.write("IterInit ")?;
1486             print_iter_args(w, iter_args)?;
1487             w.write(" ")?;
1488             print_label(w, label)
1489         }
1490         I::IterNext(iter_args, label) => {
1491             w.write("IterNext ")?;
1492             print_iter_args(w, iter_args)?;
1493             w.write(" ")?;
1494             print_label(w, label)
1495         }
1496         I::IterFree(id) => {
1497             w.write("IterFree ")?;
1498             print_iterator_id(w, id)
1499         }
1500     }
1503 fn print_iter_args<W: Write>(w: &mut W, iter_args: &IterArgs) -> Result<(), W::Error> {
1504     print_iterator_id(w, &iter_args.iter_id)?;
1505     w.write(" ")?;
1506     match &iter_args.key_id {
1507         Nothing => w.write("NK")?,
1508         Just(k) => {
1509             w.write("K:")?;
1510             print_local(w, &k)?;
1511         }
1512     };
1513     w.write(" ")?;
1514     w.write("V:")?;
1515     print_local(w, &iter_args.val_id)
1518 fn print_iterator_id<W: Write>(w: &mut W, i: &IterId) -> Result<(), W::Error> {
1519     write!(w, "{}", i)
1522 fn print_async<W: Write>(w: &mut W, a: &AsyncFunctions) -> Result<(), W::Error> {
1523     use AsyncFunctions as A;
1524     match a {
1525         A::WHResult => w.write("WHResult"),
1526         A::Await => w.write("Await"),
1527         A::AwaitAll(Just(Pair(Local::Unnamed(id), count))) => {
1528             write!(w, "AwaitAll L:{}+{}", id, count)
1529         }
1530         A::AwaitAll(Nothing) => w.write("AwaitAll L:0+0"),
1531         _ => Err(Error::fail("AwaitAll needs an unnamed local")),
1532     }
1535 fn print_query_op<W: Write>(w: &mut W, q: QueryOp) -> Result<(), W::Error> {
1536     w.write(match q {
1537         QueryOp::CGet => "CGet",
1538         QueryOp::CGetQuiet => "CGetQuiet",
1539         QueryOp::Isset => "Isset",
1540         QueryOp::InOut => "InOut",
1541     })
1544 fn print_final<W: Write>(w: &mut W, f: &InstructFinal) -> Result<(), W::Error> {
1545     use InstructFinal as F;
1546     match f {
1547         F::QueryM(n, op, mk) => {
1548             w.write("QueryM ")?;
1549             print_int(w, n)?;
1550             w.write(" ")?;
1551             print_query_op(w, *op)?;
1552             w.write(" ")?;
1553             print_member_key(w, mk)
1554         }
1555         F::UnsetM(n, mk) => {
1556             w.write("UnsetM ")?;
1557             print_int(w, n)?;
1558             w.write(" ")?;
1559             print_member_key(w, mk)
1560         }
1561         F::SetM(i, mk) => {
1562             w.write("SetM ")?;
1563             print_int(w, i)?;
1564             w.write(" ")?;
1565             print_member_key(w, mk)
1566         }
1567         F::SetOpM(i, op, mk) => {
1568             w.write("SetOpM ")?;
1569             print_int(w, i)?;
1570             w.write(" ")?;
1571             print_eq_op(w, &op)?;
1572             w.write(" ")?;
1573             print_member_key(w, mk)
1574         }
1575         F::IncDecM(i, op, mk) => {
1576             w.write("IncDecM ")?;
1577             print_int(w, i)?;
1578             w.write(" ")?;
1579             print_incdec_op(w, &op)?;
1580             w.write(" ")?;
1581             print_member_key(w, mk)
1582         }
1583         F::SetRangeM(i, s, op) => {
1584             w.write("SetRangeM ")?;
1585             print_int(w, i)?;
1586             w.write(" ")?;
1587             print_int(w, &(*s as usize))?;
1588             w.write(" ")?;
1589             w.write(match op {
1590                 SetrangeOp::Forward => "Forward",
1591                 SetrangeOp::Reverse => "Reverse",
1592             })
1593         }
1594     }
1597 fn print_isset<W: Write>(w: &mut W, isset: &InstructIsset) -> Result<(), W::Error> {
1598     use InstructIsset as I;
1599     match isset {
1600         I::IssetC => w.write("IssetC"),
1601         I::IssetL(local) => {
1602             w.write("IssetL ")?;
1603             print_local(w, local)
1604         }
1605         I::IsUnsetL(local) => {
1606             w.write("IsUnsetL ")?;
1607             print_local(w, local)
1608         }
1609         I::IssetG => w.write("IssetG"),
1610         I::IssetS => w.write("IssetS"),
1611         I::IsTypeC(op) => {
1612             w.write("IsTypeC ")?;
1613             print_istype_op(w, op)
1614         }
1615         I::IsTypeL(local, op) => {
1616             w.write("IsTypeL ")?;
1617             print_local(w, local)?;
1618             w.write(" ")?;
1619             print_istype_op(w, op)
1620         }
1621     }
1624 fn print_istype_op<W: Write>(w: &mut W, op: &IstypeOp) -> Result<(), W::Error> {
1625     use IstypeOp as Op;
1626     match op {
1627         Op::OpNull => w.write("Null"),
1628         Op::OpBool => w.write("Bool"),
1629         Op::OpInt => w.write("Int"),
1630         Op::OpDbl => w.write("Dbl"),
1631         Op::OpStr => w.write("Str"),
1632         Op::OpObj => w.write("Obj"),
1633         Op::OpRes => w.write("Res"),
1634         Op::OpScalar => w.write("Scalar"),
1635         Op::OpKeyset => w.write("Keyset"),
1636         Op::OpDict => w.write("Dict"),
1637         Op::OpVec => w.write("Vec"),
1638         Op::OpArrLike => w.write("ArrLike"),
1639         Op::OpLegacyArrLike => w.write("LegacyArrLike"),
1640         Op::OpClsMeth => w.write("ClsMeth"),
1641         Op::OpFunc => w.write("Func"),
1642         Op::OpClass => w.write("Class"),
1643     }
1646 fn print_try<W: Write>(w: &mut W, itry: &InstructTry) -> Result<(), W::Error> {
1647     use InstructTry as T;
1648     match itry {
1649         T::TryCatchBegin => w.write(".try {"),
1650         T::TryCatchMiddle => w.write("} .catch {"),
1651         T::TryCatchEnd => w.write("}"),
1652     }
1655 fn print_mutator<W: Write>(w: &mut W, mutator: &InstructMutator) -> Result<(), W::Error> {
1656     use InstructMutator as M;
1657     match mutator {
1658         M::SetL(local) => {
1659             w.write("SetL ")?;
1660             print_local(w, local)
1661         }
1662         M::PopL(id) => {
1663             w.write("PopL ")?;
1664             print_local(w, id)
1665         }
1666         M::SetG => w.write("SetG"),
1667         M::SetS(op) => {
1668             w.write("SetS ")?;
1669             print_readonly_op(w, op)
1670         }
1671         M::SetOpL(id, op) => {
1672             w.write("SetOpL ")?;
1673             print_local(w, id)?;
1674             w.write(" ")?;
1675             print_eq_op(w, op)
1676         }
1677         M::SetOpG(op) => {
1678             w.write("SetOpG ")?;
1679             print_eq_op(w, op)
1680         }
1681         M::SetOpS(op) => {
1682             w.write("SetOpS ")?;
1683             print_eq_op(w, op)
1684         }
1685         M::IncDecL(id, op) => {
1686             w.write("IncDecL ")?;
1687             print_local(w, id)?;
1688             w.write(" ")?;
1689             print_incdec_op(w, op)
1690         }
1691         M::IncDecG(op) => {
1692             w.write("IncDecG ")?;
1693             print_incdec_op(w, op)
1694         }
1695         M::IncDecS(op) => {
1696             w.write("IncDecS ")?;
1697             print_incdec_op(w, op)
1698         }
1699         M::UnsetL(id) => {
1700             w.write("UnsetL ")?;
1701             print_local(w, id)
1702         }
1703         M::UnsetG => w.write("UnsetG"),
1704         M::CheckProp(id) => {
1705             w.write("CheckProp ")?;
1706             print_prop_id(w, id)
1707         }
1708         M::InitProp(id, op) => {
1709             w.write("InitProp ")?;
1710             print_prop_id(w, id)?;
1711             w.write(" ")?;
1712             match op {
1713                 InitpropOp::Static => w.write("Static"),
1714                 InitpropOp::NonStatic => w.write("NonStatic"),
1715             }?;
1716             w.write(" ")
1717         }
1718     }
1721 fn print_eq_op<W: Write>(w: &mut W, op: &EqOp) -> Result<(), W::Error> {
1722     w.write(match op {
1723         EqOp::PlusEqual => "PlusEqual",
1724         EqOp::MinusEqual => "MinusEqual",
1725         EqOp::MulEqual => "MulEqual",
1726         EqOp::ConcatEqual => "ConcatEqual",
1727         EqOp::DivEqual => "DivEqual",
1728         EqOp::PowEqual => "PowEqual",
1729         EqOp::ModEqual => "ModEqual",
1730         EqOp::AndEqual => "AndEqual",
1731         EqOp::OrEqual => "OrEqual",
1732         EqOp::XorEqual => "XorEqual",
1733         EqOp::SlEqual => "SlEqual",
1734         EqOp::SrEqual => "SrEqual",
1735         EqOp::PlusEqualO => "PlusEqualO",
1736         EqOp::MinusEqualO => "MinusEqualO",
1737         EqOp::MulEqualO => "MulEqualO",
1738     })
1741 fn print_readonly_op<W: Write>(w: &mut W, op: &ReadonlyOp) -> Result<(), W::Error> {
1742     w.write(match op {
1743         ReadonlyOp::Readonly => "Readonly",
1744         ReadonlyOp::Mutable => "Mutable",
1745         ReadonlyOp::Any => "Any",
1746         ReadonlyOp::CheckROCOW => "CheckROCOW",
1747         ReadonlyOp::CheckMutROCOW => "CheckMutROCOW",
1748     })
1751 fn print_incdec_op<W: Write>(w: &mut W, op: &IncdecOp) -> Result<(), W::Error> {
1752     w.write(match op {
1753         IncdecOp::PreInc => "PreInc",
1754         IncdecOp::PostInc => "PostInc",
1755         IncdecOp::PreDec => "PreDec",
1756         IncdecOp::PostDec => "PostDec",
1757         IncdecOp::PreIncO => "PreIncO",
1758         IncdecOp::PostIncO => "PostIncO",
1759         IncdecOp::PreDecO => "PreDecO",
1760         IncdecOp::PostDecO => "PostDecO",
1761     })
1764 fn print_gen_creation_execution<W: Write>(
1765     w: &mut W,
1766     gen: &GenCreationExecution,
1767 ) -> Result<(), W::Error> {
1768     use GenCreationExecution as G;
1769     match gen {
1770         G::CreateCont => w.write("CreateCont"),
1771         G::ContEnter => w.write("ContEnter"),
1772         G::ContRaise => w.write("ContRaise"),
1773         G::Yield => w.write("Yield"),
1774         G::YieldK => w.write("YieldK"),
1775         G::ContCheck(CheckStarted::IgnoreStarted) => w.write("ContCheck IgnoreStarted"),
1776         G::ContCheck(CheckStarted::CheckStarted) => w.write("ContCheck CheckStarted"),
1777         G::ContValid => w.write("ContValid"),
1778         G::ContKey => w.write("ContKey"),
1779         G::ContGetReturn => w.write("ContGetReturn"),
1780         G::ContCurrent => w.write("ContCurrent"),
1781     }
1784 fn print_misc<W: Write>(w: &mut W, misc: &InstructMisc) -> Result<(), W::Error> {
1785     use InstructMisc as M;
1786     match misc {
1787         M::This => w.write("This"),
1788         M::CheckThis => w.write("CheckThis"),
1789         M::FuncNumArgs => w.write("FuncNumArgs"),
1790         M::ChainFaults => w.write("ChainFaults"),
1791         M::VerifyRetTypeC => w.write("VerifyRetTypeC"),
1792         M::VerifyRetTypeTS => w.write("VerifyRetTypeTS"),
1793         M::Self_ => w.write("Self"),
1794         M::Parent => w.write("Parent"),
1795         M::LateBoundCls => w.write("LateBoundCls"),
1796         M::ClassName => w.write("ClassName"),
1797         M::LazyClassFromClass => w.write("LazyClassFromClass"),
1798         M::RecordReifiedGeneric => w.write("RecordReifiedGeneric"),
1799         M::CheckReifiedGenericMismatch => w.write("CheckReifiedGenericMismatch"),
1800         M::NativeImpl => w.write("NativeImpl"),
1801         M::AKExists => w.write("AKExists"),
1802         M::Idx => w.write("Idx"),
1803         M::ArrayIdx => w.write("ArrayIdx"),
1804         M::ArrayMarkLegacy => w.write("ArrayMarkLegacy"),
1805         M::ArrayUnmarkLegacy => w.write("ArrayUnmarkLegacy"),
1806         M::BreakTraceHint => w.write("BreakTraceHint"),
1807         M::CGetCUNop => w.write("CGetCUNop"),
1808         M::UGetCUNop => w.write("UGetCUNop"),
1809         M::LockObj => w.write("LockObj"),
1810         M::ThrowNonExhaustiveSwitch => w.write("ThrowNonExhaustiveSwitch"),
1811         M::RaiseClassStringConversionWarning => w.write("RaiseClassStringConversionWarning"),
1812         M::VerifyParamType(id) => {
1813             w.write("VerifyParamType ")?;
1814             print_param_id(w, id)
1815         }
1816         M::VerifyParamTypeTS(id) => {
1817             w.write("VerifyParamTypeTS ")?;
1818             print_param_id(w, id)
1819         }
1820         M::Silence(local, op) => {
1821             w.write("Silence ")?;
1822             print_local(w, local)?;
1823             w.write(" ")?;
1824             match op {
1825                 OpSilence::Start => w.write("Start"),
1826                 OpSilence::End => w.write("End"),
1827             }
1828         }
1829         M::VerifyOutType(id) => {
1830             w.write("VerifyOutType ")?;
1831             print_param_id(w, id)
1832         }
1833         M::CreateCl(n, cid) => concat_str_by(
1834             w,
1835             " ",
1836             ["CreateCl", n.to_string().as_str(), cid.to_string().as_str()],
1837         ),
1838         M::BareThis(op) => concat_str_by(
1839             w,
1840             " ",
1841             [
1842                 "BareThis",
1843                 match op {
1844                     BareThisOp::Notice => "Notice",
1845                     BareThisOp::NoNotice => "NoNotice",
1846                     BareThisOp::NeverNull => "NeverNull",
1847                 },
1848             ],
1849         ),
1851         M::MemoGet(label, Just(Pair(Local::Unnamed(first), local_count))) => {
1852             w.write("MemoGet ")?;
1853             print_label(w, label)?;
1854             write!(w, " L:{}+{}", first, local_count)
1855         }
1856         M::MemoGet(label, Nothing) => {
1857             w.write("MemoGet ")?;
1858             print_label(w, label)?;
1859             w.write(" L:0+0")
1860         }
1861         M::MemoGet(_, _) => Err(Error::fail("MemoGet needs an unnamed local")),
1863         M::MemoSet(Just(Pair(Local::Unnamed(first), local_count))) => {
1864             write!(w, "MemoSet L:{}+{}", first, local_count)
1865         }
1866         M::MemoSet(Nothing) => w.write("MemoSet L:0+0"),
1867         M::MemoSet(_) => Err(Error::fail("MemoSet needs an unnamed local")),
1869         M::MemoGetEager(label1, label2, Just(Pair(Local::Unnamed(first), local_count))) => {
1870             w.write("MemoGetEager ")?;
1871             print_label(w, label1)?;
1872             w.write(" ")?;
1873             print_label(w, label2)?;
1874             write!(w, " L:{}+{}", first, local_count)
1875         }
1876         M::MemoGetEager(label1, label2, Nothing) => {
1877             w.write("MemoGetEager ")?;
1878             print_label(w, label1)?;
1879             w.write(" ")?;
1880             print_label(w, label2)?;
1881             w.write(" L:0+0")
1882         }
1883         M::MemoGetEager(_, _, _) => Err(Error::fail("MemoGetEager needs an unnamed local")),
1885         M::MemoSetEager(Just(Pair(Local::Unnamed(first), local_count))) => {
1886             write!(w, "MemoSetEager L:{}+{}", first, local_count)
1887         }
1888         M::MemoSetEager(Nothing) => w.write("MemoSetEager L:0+0"),
1889         M::MemoSetEager(_) => Err(Error::fail("MemoSetEager needs an unnamed local")),
1891         M::OODeclExists(k) => concat_str_by(
1892             w,
1893             " ",
1894             [
1895                 "OODeclExists",
1896                 match k {
1897                     ClassishKind::Class => "Class",
1898                     ClassishKind::Interface => "Interface",
1899                     ClassishKind::Trait => "Trait",
1900                     ClassishKind::Enum => "Enum",
1901                     ClassishKind::EnumClass => "EnumClass",
1902                 },
1903             ],
1904         ),
1905         M::AssertRATL(local, s) => {
1906             w.write("AssertRATL ")?;
1907             print_local(w, local)?;
1908             w.write(" ")?;
1909             w.write(s)
1910         }
1911         M::AssertRATStk(n, s) => {
1912             concat_str_by(w, " ", ["AssertRATStk", n.to_string().as_str(), s.as_str()])
1913         }
1914         M::GetMemoKeyL(local) => {
1915             w.write("GetMemoKeyL ")?;
1916             print_local(w, local)
1917         }
1918     }
1921 fn print_include_eval_define<W: Write>(
1922     w: &mut W,
1923     ed: &InstructIncludeEvalDefine,
1924 ) -> Result<(), W::Error> {
1925     use InstructIncludeEvalDefine::*;
1926     match ed {
1927         Incl => w.write("Incl"),
1928         InclOnce => w.write("InclOnce"),
1929         Req => w.write("Req"),
1930         ReqOnce => w.write("ReqOnce"),
1931         ReqDoc => w.write("ReqDoc"),
1932         Eval => w.write("Eval"),
1933     }
1936 fn print_control_flow<W: Write>(w: &mut W, cf: &InstructControlFlow) -> Result<(), W::Error> {
1937     use InstructControlFlow as CF;
1938     match cf {
1939         CF::Jmp(l) => {
1940             w.write("Jmp ")?;
1941             print_label(w, l)
1942         }
1943         CF::JmpNS(l) => {
1944             w.write("JmpNS ")?;
1945             print_label(w, l)
1946         }
1947         CF::JmpZ(l) => {
1948             w.write("JmpZ ")?;
1949             print_label(w, l)
1950         }
1951         CF::JmpNZ(l) => {
1952             w.write("JmpNZ ")?;
1953             print_label(w, l)
1954         }
1955         CF::RetC => w.write("RetC"),
1956         CF::RetCSuspended => w.write("RetCSuspended"),
1957         CF::RetM(p) => concat_str_by(w, " ", ["RetM", p.to_string().as_str()]),
1958         CF::Throw => w.write("Throw"),
1959         CF::Switch(kind, base, labels) => print_switch(w, kind, base, labels.as_ref()),
1960         CF::SSwitch(cases) => match cases.as_ref() {
1961             [] => Err(Error::fail("sswitch should have at least one case")),
1962             [rest @ .., Pair(_, lastlabel)] => {
1963                 w.write("SSwitch ")?;
1964                 angle(w, |w| {
1965                     concat_by(w, " ", rest, |w, Pair(s, l)| {
1966                         concat_str(w, [quote_string(s.as_ref()).as_str(), ":"])?;
1967                         print_label(w, l)
1968                     })?;
1969                     w.write(" -:")?;
1970                     print_label(w, &lastlabel)
1971                 })
1972             }
1973         },
1974     }
1977 fn print_switch<W: Write>(
1978     w: &mut W,
1979     kind: &Switchkind,
1980     base: &isize,
1981     labels: &[Label],
1982 ) -> Result<(), W::Error> {
1983     w.write("Switch ")?;
1984     w.write(match kind {
1985         Switchkind::Bounded => "Bounded ",
1986         Switchkind::Unbounded => "Unbounded ",
1987     })?;
1988     w.write(base.to_string())?;
1989     w.write(" ")?;
1990     angle(w, |w| concat_by(w, " ", labels, print_label))
1993 fn print_lit_const<W: Write>(w: &mut W, lit: &InstructLitConst) -> Result<(), W::Error> {
1994     use InstructLitConst as LC;
1995     match lit {
1996         LC::Null => w.write("Null"),
1997         LC::Int(i) => concat_str_by(w, " ", ["Int", i.to_string().as_str()]),
1998         LC::String(s) => {
1999             w.write("String ")?;
2000             quotes(w, |w| w.write(escape(s.as_str())))
2001         }
2002         LC::LazyClass(id) => {
2003             w.write("LazyClass ")?;
2004             print_class_id(w, id)
2005         }
2006         LC::True => w.write("True"),
2007         LC::False => w.write("False"),
2008         LC::Double(d) => concat_str_by(w, " ", ["Double", d.as_str()]),
2009         LC::AddElemC => w.write("AddElemC"),
2010         LC::AddNewElemC => w.write("AddNewElemC"),
2011         LC::NewPair => w.write("NewPair"),
2012         LC::File => w.write("File"),
2013         LC::Dir => w.write("Dir"),
2014         LC::Method => w.write("Method"),
2015         LC::FuncCred => w.write("FuncCred"),
2016         LC::Dict(id) => {
2017             w.write("Dict ")?;
2018             print_adata_id(w, id)
2019         }
2020         LC::Keyset(id) => {
2021             w.write("Keyset ")?;
2022             print_adata_id(w, id)
2023         }
2024         LC::Vec(id) => {
2025             w.write("Vec ")?;
2026             print_adata_id(w, id)
2027         }
2028         LC::NewDictArray(i) => concat_str_by(w, " ", ["NewDictArray", i.to_string().as_str()]),
2029         LC::NewVec(i) => concat_str_by(w, " ", ["NewVec", i.to_string().as_str()]),
2030         LC::NewKeysetArray(i) => concat_str_by(w, " ", ["NewKeysetArray", i.to_string().as_str()]),
2031         LC::NewStructDict(l) => {
2032             let ls: Vec<&str> = l.as_ref().into_iter().map(|s| s.as_str()).collect();
2033             w.write("NewStructDict ")?;
2034             angle(w, |w| print_shape_fields(w, &ls[0..]))
2035         }
2036         LC::NewRecord(cid, l) => {
2037             let ls: Vec<&str> = l.as_ref().into_iter().map(|s| s.as_str()).collect();
2038             w.write("NewRecord ")?;
2039             print_class_id(w, cid)?;
2040             w.write(" ")?;
2041             angle(w, |w| print_shape_fields(w, &ls[0..]))
2042         }
2043         LC::CnsE(id) => {
2044             w.write("CnsE ")?;
2045             print_const_id(w, id)
2046         }
2047         LC::ClsCns(id) => {
2048             w.write("ClsCns ")?;
2049             print_const_id(w, id)
2050         }
2051         LC::ClsCnsD(const_id, cid) => {
2052             w.write("ClsCnsD ")?;
2053             print_const_id(w, const_id)?;
2054             w.write(" ")?;
2055             print_class_id(w, cid)
2056         }
2057         LC::ClsCnsL(id) => {
2058             w.write("ClsCnsL ")?;
2059             print_local(w, id)
2060         }
2061         LC::NewCol(ct) => {
2062             w.write("NewCol ")?;
2063             print_collection_type(w, ct)
2064         }
2065         LC::ColFromArray(ct) => {
2066             w.write("ColFromArray ")?;
2067             print_collection_type(w, ct)
2068         }
2069         LC::NullUninit => w.write("NullUninit"),
2070         LC::TypedValue(_) => Err(Error::fail("print_lit_const: TypedValue")),
2071     }
2074 fn print_collection_type<W: Write>(w: &mut W, ct: &CollectionType) -> Result<(), W::Error> {
2075     use CollectionType as CT;
2076     match ct {
2077         CT::Vector => w.write("Vector"),
2078         CT::Map => w.write("Map"),
2079         CT::Set => w.write("Set"),
2080         CT::Pair => w.write("Pair"),
2081         CT::ImmVector => w.write("ImmVector"),
2082         CT::ImmMap => w.write("ImmMap"),
2083         CT::ImmSet => w.write("ImmSet"),
2084         CT::Dict => w.write("dict"),
2085         CT::Array => w.write("array"),
2086         CT::Keyset => w.write("keyset"),
2087         CT::Vec => w.write("vec"),
2088     }
2091 fn print_shape_fields<W: Write>(w: &mut W, sf: &[&str]) -> Result<(), W::Error> {
2092     concat_by(w, " ", sf, |w, f| quotes(w, |w| w.write(escape(*f))))
2095 fn print_op<W: Write>(w: &mut W, op: &InstructOperator) -> Result<(), W::Error> {
2096     use InstructOperator as I;
2097     match op {
2098         I::Concat => w.write("Concat"),
2099         I::ConcatN(n) => concat_str_by(w, " ", ["ConcatN", n.to_string().as_str()]),
2100         I::Add => w.write("Add"),
2101         I::Sub => w.write("Sub"),
2102         I::Mul => w.write("Mul"),
2103         I::AddO => w.write("AddO"),
2104         I::SubO => w.write("SubO"),
2105         I::MulO => w.write("MulO"),
2106         I::Div => w.write("Div"),
2107         I::Mod => w.write("Mod"),
2108         I::Pow => w.write("Pow"),
2109         I::Not => w.write("Not"),
2110         I::Same => w.write("Same"),
2111         I::NSame => w.write("NSame"),
2112         I::Eq => w.write("Eq"),
2113         I::Neq => w.write("Neq"),
2114         I::Lt => w.write("Lt"),
2115         I::Lte => w.write("Lte"),
2116         I::Gt => w.write("Gt"),
2117         I::Gte => w.write("Gte"),
2118         I::Cmp => w.write("Cmp"),
2119         I::BitAnd => w.write("BitAnd"),
2120         I::BitOr => w.write("BitOr"),
2121         I::BitXor => w.write("BitXor"),
2122         I::BitNot => w.write("BitNot"),
2123         I::Shl => w.write("Shl"),
2124         I::Shr => w.write("Shr"),
2125         I::CastBool => w.write("CastBool"),
2126         I::CastInt => w.write("CastInt"),
2127         I::CastDouble => w.write("CastDouble"),
2128         I::CastString => w.write("CastString"),
2129         I::CastVec => w.write("CastVec"),
2130         I::CastDict => w.write("CastDict"),
2131         I::CastKeyset => w.write("CastKeyset"),
2132         I::InstanceOf => w.write("InstanceOf"),
2133         I::InstanceOfD(id) => {
2134             w.write("InstanceOfD ")?;
2135             print_class_id(w, id)
2136         }
2137         I::IsLateBoundCls => w.write("IsLateBoundCls"),
2138         I::IsTypeStructC(op) => concat_str_by(
2139             w,
2140             " ",
2141             [
2142                 "IsTypeStructC",
2143                 match op {
2144                     TypestructResolveOp::Resolve => "Resolve",
2145                     TypestructResolveOp::DontResolve => "DontResolve",
2146                 },
2147             ],
2148         ),
2149         I::ThrowAsTypeStructException => w.write("ThrowAsTypeStructException"),
2150         I::CombineAndResolveTypeStruct(n) => concat_str_by(
2151             w,
2152             " ",
2153             ["CombineAndResolveTypeStruct", n.to_string().as_str()],
2154         ),
2155         I::Print => w.write("Print"),
2156         I::Clone => w.write("Clone"),
2157         I::Exit => w.write("Exit"),
2158         I::ResolveFunc(id) => {
2159             w.write("ResolveFunc ")?;
2160             print_function_id(w, id)
2161         }
2162         I::ResolveRFunc(id) => {
2163             w.write("ResolveRFunc ")?;
2164             print_function_id(w, id)
2165         }
2166         I::ResolveMethCaller(id) => {
2167             w.write("ResolveMethCaller ")?;
2168             print_function_id(w, id)
2169         }
2170         I::ResolveClsMethod(mid) => {
2171             w.write("ResolveClsMethod ")?;
2172             print_method_id(w, mid)
2173         }
2174         I::ResolveClsMethodD(cid, mid) => {
2175             w.write("ResolveClsMethodD ")?;
2176             print_class_id(w, cid)?;
2177             w.write(" ")?;
2178             print_method_id(w, mid)
2179         }
2180         I::ResolveClsMethodS(r, mid) => {
2181             w.write("ResolveClsMethodS ")?;
2182             print_special_cls_ref(w, r)?;
2183             w.write(" ")?;
2184             print_method_id(w, mid)
2185         }
2186         I::ResolveRClsMethod(mid) => {
2187             w.write("ResolveRClsMethod ")?;
2188             print_method_id(w, mid)
2189         }
2190         I::ResolveRClsMethodD(cid, mid) => {
2191             w.write("ResolveRClsMethodD ")?;
2192             print_class_id(w, cid)?;
2193             w.write(" ")?;
2194             print_method_id(w, mid)
2195         }
2196         I::ResolveRClsMethodS(r, mid) => {
2197             w.write("ResolveRClsMethodS ")?;
2198             print_special_cls_ref(w, r)?;
2199             w.write(" ")?;
2200             print_method_id(w, mid)
2201         }
2202         I::ResolveClass(id) => {
2203             w.write("ResolveClass ")?;
2204             print_class_id(w, id)
2205         }
2206         I::Fatal(fatal_op) => print_fatal_op(w, fatal_op),
2207     }
2210 fn print_fatal_op<W: Write>(w: &mut W, f: &FatalOp) -> Result<(), W::Error> {
2211     match f {
2212         FatalOp::Parse => w.write("Fatal Parse"),
2213         FatalOp::Runtime => w.write("Fatal Runtime"),
2214         FatalOp::RuntimeOmitFrame => w.write("Fatal RuntimeOmitFrame"),
2215     }
2218 fn print_params<'arena, W: Write>(
2219     ctx: &mut Context,
2220     w: &mut W,
2221     params: impl AsRef<[HhasParam<'arena>]>,
2222 ) -> Result<(), W::Error> {
2223     paren(w, |w| {
2224         concat_by(w, ", ", params, |w, i| print_param(ctx, w, i))
2225     })
2228 fn print_param<'arena, W: Write>(
2229     ctx: &mut Context,
2230     w: &mut W,
2231     param: &HhasParam<'arena>,
2232 ) -> Result<(), W::Error> {
2233     print_param_user_attributes(ctx, w, param)?;
2234     w.write_if(param.is_inout, "inout ")?;
2235     w.write_if(param.is_readonly, "readonly ")?;
2236     w.write_if(param.is_variadic, "...")?;
2237     option(w, &Option::from(param.type_info.clone()), |w, ty| {
2238         print_type_info(w, ty)?;
2239         w.write(" ")
2240     })?;
2241     w.write(&param.name)?;
2242     option(
2243         w,
2244         &Option::from(param.default_value.map(|x| (x.0, x.1))),
2245         |w, i| print_param_default_value(w, i),
2246     )
2249 fn print_param_id<W: Write>(w: &mut W, param_id: &ParamId) -> Result<(), W::Error> {
2250     match param_id {
2251         ParamId::ParamUnnamed(i) => w.write(i.to_string()),
2252         ParamId::ParamNamed(s) => w.write(s),
2253     }
2256 fn print_param_default_value<'arena, W: Write>(
2257     w: &mut W,
2258     default_val: &(Label, Str<'arena>),
2259 ) -> Result<(), W::Error> {
2260     w.write(" = ")?;
2261     print_label(w, &default_val.0)?;
2262     paren(w, |w| triple_quotes(w, |w| w.write(default_val.1.as_str())))
2265 fn print_label<W: Write>(w: &mut W, label: &Label) -> Result<(), W::Error> {
2266     match label {
2267         Label::Regular(id) => {
2268             w.write("L")?;
2269             print_int(w, id)
2270         }
2271         Label::DefaultArg(id) => {
2272             w.write("DV")?;
2273             print_int(w, id)
2274         }
2275     }
2278 fn print_local<W: Write>(w: &mut W, local: &Local) -> Result<(), W::Error> {
2279     match local {
2280         Local::Unnamed(id) => {
2281             w.write("_")?;
2282             print_int(w, id)
2283         }
2284         Local::Named(id) => w.write(id),
2285     }
2288 fn print_int<W: Write>(w: &mut W, i: &usize) -> Result<(), W::Error> {
2289     write!(w, "{}", i)
2292 fn print_key_value<W: Write>(
2293     ctx: &mut Context,
2294     w: &mut W,
2295     env: &ExprEnv,
2296     k: &ast::Expr,
2297     v: &ast::Expr,
2298 ) -> Result<(), W::Error> {
2299     print_key_value_(ctx, w, env, k, print_expr, v)
2302 fn print_key_value_<W: Write, K, KeyPrinter>(
2303     ctx: &mut Context,
2304     w: &mut W,
2305     env: &ExprEnv,
2306     k: K,
2307     mut kp: KeyPrinter,
2308     v: &ast::Expr,
2309 ) -> Result<(), W::Error>
2310 where
2311     KeyPrinter: FnMut(&mut Context, &mut W, &ExprEnv, K) -> Result<(), W::Error>,
2313     kp(ctx, w, env, k)?;
2314     w.write(" => ")?;
2315     print_expr(ctx, w, env, v)
2318 fn print_afield<W: Write>(
2319     ctx: &mut Context,
2320     w: &mut W,
2321     env: &ExprEnv,
2322     afield: &ast::Afield,
2323 ) -> Result<(), W::Error> {
2324     use ast::Afield as A;
2325     match afield {
2326         A::AFvalue(e) => print_expr(ctx, w, env, &e),
2327         A::AFkvalue(k, v) => print_key_value(ctx, w, env, &k, &v),
2328     }
2331 fn print_afields<W: Write>(
2332     ctx: &mut Context,
2333     w: &mut W,
2334     env: &ExprEnv,
2335     afields: impl AsRef<[ast::Afield]>,
2336 ) -> Result<(), W::Error> {
2337     concat_by(w, ", ", afields, |w, i| print_afield(ctx, w, env, i))
2340 fn print_uop<W: Write>(w: &mut W, op: ast::Uop) -> Result<(), W::Error> {
2341     use ast::Uop as U;
2342     w.write(match op {
2343         U::Utild => "~",
2344         U::Unot => "!",
2345         U::Uplus => "+",
2346         U::Uminus => "-",
2347         U::Uincr => "++",
2348         U::Udecr => "--",
2349         U::Usilence => "@",
2350         U::Upincr | U::Updecr => {
2351             return Err(Error::fail(
2352                 "string_of_uop - should have been captures earlier",
2353             ));
2354         }
2355     })
2358 fn print_key_values<W: Write>(
2359     ctx: &mut Context,
2360     w: &mut W,
2361     env: &ExprEnv,
2362     kvs: impl AsRef<[(ast::Expr, ast::Expr)]>,
2363 ) -> Result<(), W::Error> {
2364     print_key_values_(ctx, w, env, print_expr, kvs)
2367 fn print_key_values_<W: Write, K, KeyPrinter>(
2368     ctx: &mut Context,
2369     w: &mut W,
2370     env: &ExprEnv,
2371     mut kp: KeyPrinter,
2372     kvs: impl AsRef<[(K, ast::Expr)]>,
2373 ) -> Result<(), W::Error>
2374 where
2375     KeyPrinter: Fn(&mut Context, &mut W, &ExprEnv, &K) -> Result<(), W::Error>,
2377     concat_by(w, ", ", kvs, |w, (k, v)| {
2378         print_key_value_(ctx, w, env, k, &mut kp, v)
2379     })
2382 fn print_expr_darray<W: Write, K, KeyPrinter>(
2383     ctx: &mut Context,
2384     w: &mut W,
2385     env: &ExprEnv,
2386     kp: KeyPrinter,
2387     kvs: impl AsRef<[(K, ast::Expr)]>,
2388 ) -> Result<(), W::Error>
2389 where
2390     KeyPrinter: Fn(&mut Context, &mut W, &ExprEnv, &K) -> Result<(), W::Error>,
2392     wrap_by_(w, "darray[", "]", |w| {
2393         print_key_values_(ctx, w, env, kp, kvs)
2394     })
2397 fn print_expr_varray<W: Write>(
2398     ctx: &mut Context,
2399     w: &mut W,
2400     env: &ExprEnv,
2401     varray: &[ast::Expr],
2402 ) -> Result<(), W::Error> {
2403     wrap_by_(w, "varray[", "]", |w| {
2404         concat_by(w, ", ", varray, |w, e| print_expr(ctx, w, env, e))
2405     })
2408 fn print_shape_field_name<W: Write>(
2409     _: &mut Context,
2410     w: &mut W,
2411     _: &ExprEnv,
2412     field: &ast::ShapeFieldName,
2413 ) -> Result<(), W::Error> {
2414     use ast::ShapeFieldName as S;
2415     match field {
2416         S::SFlitInt((_, s)) => print_expr_int(w, s.as_ref()),
2417         S::SFlitStr((_, s)) => print_expr_string(w, s),
2418         S::SFclassConst(_, (_, s)) => print_expr_string(w, s.as_bytes()),
2419     }
2422 fn print_expr_int<W: Write>(w: &mut W, i: &str) -> Result<(), W::Error> {
2423     match integer::to_decimal(i) {
2424         Ok(s) => w.write(s),
2425         Err(_) => Err(Error::fail("ParseIntError")),
2426     }
2429 fn print_expr_string<W: Write>(w: &mut W, s: &[u8]) -> Result<(), W::Error> {
2430     fn escape_char(c: u8) -> Option<Cow<'static, [u8]>> {
2431         match c {
2432             b'\n' => Some((&b"\\\\n"[..]).into()),
2433             b'\r' => Some((&b"\\\\r"[..]).into()),
2434             b'\t' => Some((&b"\\\\t"[..]).into()),
2435             b'\\' => Some((&b"\\\\\\\\"[..]).into()),
2436             b'"' => Some((&b"\\\\\\\""[..]).into()),
2437             b'$' => Some((&b"\\\\$"[..]).into()),
2438             c if is_lit_printable(c) => None,
2439             c => {
2440                 let mut r = vec![];
2441                 write!(r, "\\\\{:03o}", c).unwrap();
2442                 Some(r.into())
2443             }
2444         }
2445     }
2446     // FIXME: This is not safe--string literals are binary strings.
2447     // There's no guarantee that they're valid UTF-8.
2448     let s = unsafe { std::str::from_utf8_unchecked(s) };
2449     wrap_by(w, "\\\"", |w| w.write(escape_by(s.into(), escape_char)))
2452 fn print_expr_to_string<W: Write>(
2453     ctx: &mut Context,
2454     env: &ExprEnv,
2455     expr: &ast::Expr,
2456 ) -> Result<String, W::Error> {
2457     let mut buf = String::new();
2458     print_expr(ctx, &mut buf, env, expr).map_err(|e| match e {
2459         Error::NotImpl(m) => Error::NotImpl(m),
2460         _ => Error::Fail(format!("Failed: {}", e)),
2461     })?;
2462     Ok(buf)
2465 pub fn print_expr<W: Write>(
2466     ctx: &mut Context,
2467     w: &mut W,
2468     env: &ExprEnv,
2469     ast::Expr(_, _, expr): &ast::Expr,
2470 ) -> Result<(), W::Error> {
2471     fn adjust_id<'a>(env: &ExprEnv, id: &'a str) -> Cow<'a, str> {
2472         let s: Cow<'a, str> = match env.codegen_env {
2473             Some(env) => {
2474                 if env.is_namespaced
2475                     && id
2476                         .as_bytes()
2477                         .iter()
2478                         .rposition(|c| *c == b'\\')
2479                         .map_or(true, |i| i < 1)
2480                 {
2481                     strip_global_ns(id).into()
2482                 } else {
2483                     add_ns(id)
2484                 }
2485             }
2486             _ => id.into(),
2487         };
2488         escaper::escape(s)
2489     }
2490     fn print_expr_id<'a, W: Write>(w: &mut W, env: &ExprEnv, s: &'a str) -> Result<(), W::Error> {
2491         w.write(adjust_id(env, s))
2492     }
2493     fn fmt_class_name<'a>(is_class_constant: bool, id: Cow<'a, str>) -> Cow<'a, str> {
2494         let cn: Cow<'a, str> = if is_xhp(strip_global_ns(&id)) {
2495             escaper::escape(strip_global_ns(&mangle(id.into())))
2496                 .to_string()
2497                 .into()
2498         } else {
2499             escaper::escape(strip_global_ns(&id)).to_string().into()
2500         };
2501         if is_class_constant {
2502             format!("\\\\{}", cn).into()
2503         } else {
2504             cn
2505         }
2506     }
2507     fn get_class_name_from_id<'e>(
2508         ctx: &mut Context,
2509         env: Option<&'e HhasBodyEnv>,
2510         should_format: bool,
2511         is_class_constant: bool,
2512         id: &'e str,
2513     ) -> Cow<'e, str> {
2514         if id == classes::SELF || id == classes::PARENT || id == classes::STATIC {
2515             let name = ctx.special_class_resolver.resolve(env, id);
2516             return fmt_class_name(is_class_constant, name);
2517         }
2518         fn get<'a>(should_format: bool, is_class_constant: bool, id: &'a str) -> Cow<'a, str> {
2519             if should_format {
2520                 fmt_class_name(is_class_constant, id.into())
2521             } else {
2522                 id.into()
2523             }
2524         }
2526         if env.is_some() {
2527             let alloc = bumpalo::Bump::new();
2528             let class_id = ClassType::from_ast_name(&alloc, id);
2529             let id = class_id.to_raw_string();
2530             get(should_format, is_class_constant, id)
2531                 .into_owned()
2532                 .into()
2533         } else {
2534             get(should_format, is_class_constant, id)
2535         }
2536     }
2537     fn handle_possible_colon_colon_class_expr<W: Write>(
2538         ctx: &mut Context,
2539         w: &mut W,
2540         env: &ExprEnv,
2541         is_array_get: bool,
2542         e_: &ast::Expr_,
2543     ) -> Result<Option<()>, W::Error> {
2544         match e_.as_class_const() {
2545             Some((
2546                 ast::ClassId(_, _, ast::ClassId_::CIexpr(ast::Expr(_, _, ast::Expr_::Id(id)))),
2547                 (_, s2),
2548             )) if is_class(&s2) && !(is_self(&id.1) || is_parent(&id.1) || is_static(&id.1)) => {
2549                 Ok(Some({
2550                     let s1 = get_class_name_from_id(ctx, env.codegen_env, false, false, &id.1);
2551                     if is_array_get {
2552                         print_expr_id(w, env, s1.as_ref())?
2553                     } else {
2554                         print_expr_string(w, s1.as_bytes())?
2555                     }
2556                 }))
2557             }
2558             _ => Ok(None),
2559         }
2560     }
2561     use ast::Expr_ as E_;
2562     match expr {
2563         E_::Id(id) => print_expr_id(w, env, id.1.as_ref()),
2564         E_::Lvar(lid) => w.write(escaper::escape(&(lid.1).1)),
2565         E_::Float(f) => {
2566             if f.contains('E') || f.contains('e') {
2567                 let s = format!(
2568                     "{:.1E}",
2569                     f.parse::<f64>()
2570                         .map_err(|_| Error::fail(format!("ParseFloatError: {}", f)))?
2571                 )
2572                 // to_uppercase() here because s might be "inf" or "nan"
2573                 .to_uppercase();
2575                 lazy_static! {
2576                     static ref UNSIGNED_EXP: Regex =
2577                         Regex::new(r"(?P<first>E)(?P<second>\d+)").unwrap();
2578                     static ref SIGNED_SINGLE_DIGIT_EXP: Regex =
2579                         Regex::new(r"(?P<first>E[+-])(?P<second>\d$)").unwrap();
2580                 }
2581                 // turn mEn into mE+n
2582                 let s = UNSIGNED_EXP.replace(&s, "${first}+${second}");
2583                 // turn mE+n or mE-n into mE+0n or mE-0n (where n is a single digit)
2584                 let s = SIGNED_SINGLE_DIGIT_EXP.replace(&s, "${first}0${second}");
2585                 w.write(s)
2586             } else {
2587                 w.write(f)
2588             }
2589         }
2590         E_::Int(i) => print_expr_int(w, i.as_ref()),
2591         E_::String(s) => print_expr_string(w, s),
2592         E_::Null => w.write("NULL"),
2593         E_::True => w.write("true"),
2594         E_::False => w.write("false"),
2595         // For arrays and collections, we are making a conscious decision to not
2596         // match HHMV has HHVM's emitter has inconsistencies in the pretty printer
2597         // https://fburl.com/tzom2qoe
2598         E_::Collection(c) if (c.0).1 == "vec" || (c.0).1 == "dict" || (c.0).1 == "keyset" => {
2599             w.write(&(c.0).1)?;
2600             square(w, |w| print_afields(ctx, w, env, &c.2))
2601         }
2602         E_::Collection(c) => {
2603             let name = strip_ns((c.0).1.as_str());
2604             let name = types::fix_casing(&name);
2605             match name {
2606                 "Set" | "Pair" | "Vector" | "Map" | "ImmSet" | "ImmVector" | "ImmMap" => {
2607                     w.write("HH\\\\")?;
2608                     w.write(name)?;
2609                     wrap_by_(w, " {", "}", |w| {
2610                         Ok(if !c.2.is_empty() {
2611                             w.write(" ")?;
2612                             print_afields(ctx, w, env, &c.2)?;
2613                             w.write(" ")?;
2614                         })
2615                     })
2616                 }
2617                 _ => Err(Error::fail(format!(
2618                     "Default value for an unknow collection - {}",
2619                     name
2620                 ))),
2621             }
2622         }
2623         E_::Shape(fl) => print_expr_darray(ctx, w, env, print_shape_field_name, fl),
2624         E_::Binop(x) => {
2625             let (bop, e1, e2) = &**x;
2626             print_expr(ctx, w, env, e1)?;
2627             w.write(" ")?;
2628             print_bop(w, bop)?;
2629             w.write(" ")?;
2630             print_expr(ctx, w, env, e2)
2631         }
2632         E_::Call(c) => {
2633             let (e, _, es, unpacked_element) = &**c;
2634             match e.as_id() {
2635                 Some(ast_defs::Id(_, call_id)) => {
2636                     w.write(lstrip(adjust_id(env, &call_id).as_ref(), "\\\\"))?
2637                 }
2638                 None => {
2639                     let buf = print_expr_to_string::<W>(ctx, env, e)?;
2640                     w.write(lstrip(&buf, "\\\\"))?
2641                 }
2642             };
2643             paren(w, |w| {
2644                 concat_by(w, ", ", &es, |w, e| print_expr(ctx, w, env, e))?;
2645                 match unpacked_element {
2646                     None => Ok(()),
2647                     Some(e) => {
2648                         if !es.is_empty() {
2649                             w.write(", ")?;
2650                         }
2651                         // TODO: Should probably have ... also but we are not doing that in ocaml
2652                         print_expr(ctx, w, env, e)
2653                     }
2654                 }
2655             })
2656         }
2657         E_::New(x) => {
2658             let (cid, _, es, unpacked_element, _) = &**x;
2659             match cid.2.as_ciexpr() {
2660                 Some(ci_expr) => {
2661                     w.write("new ")?;
2662                     match ci_expr.2.as_id() {
2663                         Some(ast_defs::Id(_, cname)) => w.write(lstrip(
2664                             &adjust_id(
2665                                 env,
2666                                 &ClassType::from_ast_name(&bumpalo::Bump::new(), cname)
2667                                     .to_raw_string(),
2668                             ),
2669                             "\\\\",
2670                         ))?,
2671                         None => {
2672                             let buf = print_expr_to_string::<W>(ctx, env, ci_expr)?;
2673                             w.write(lstrip(&buf, "\\\\"))?
2674                         }
2675                     }
2676                     paren(w, |w| {
2677                         concat_by(w, ", ", es, |w, e| print_expr(ctx, w, env, e))?;
2678                         match unpacked_element {
2679                             None => Ok(()),
2680                             Some(e) => {
2681                                 w.write(", ")?;
2682                                 print_expr(ctx, w, env, e)
2683                             }
2684                         }
2685                     })
2686                 }
2687                 None => {
2688                     match cid.2.as_ci() {
2689                         Some(id) => {
2690                             // Xml exprs rewritten as New exprs come
2691                             // through here.
2692                             print_xml(ctx, w, env, &id.1, es)
2693                         }
2694                         None => not_impl!(),
2695                     }
2696                 }
2697             }
2698         }
2699         E_::Record(r) => {
2700             w.write(lstrip(adjust_id(env, &(r.0).1).as_ref(), "\\\\"))?;
2701             print_key_values(ctx, w, env, &r.1)
2702         }
2703         E_::ClassGet(cg) => {
2704             match &(cg.0).2 {
2705                 ast::ClassId_::CIexpr(e) => match e.as_id() {
2706                     Some(id) => w.write(&get_class_name_from_id(
2707                         ctx,
2708                         env.codegen_env,
2709                         true,  /* should_format */
2710                         false, /* is_class_constant */
2711                         &id.1,
2712                     ))?,
2713                     _ => print_expr(ctx, w, env, e)?,
2714                 },
2715                 _ => return Err(Error::fail("TODO Unimplemented unexpected non-CIexpr")),
2716             }
2717             w.write("::")?;
2718             match &cg.1 {
2719                 ast::ClassGetExpr::CGstring((_, litstr)) => w.write(escaper::escape(litstr)),
2720                 ast::ClassGetExpr::CGexpr(e) => print_expr(ctx, w, env, e),
2721             }
2722         }
2723         E_::ClassConst(cc) => {
2724             if let Some(e1) = (cc.0).2.as_ciexpr() {
2725                 handle_possible_colon_colon_class_expr(ctx, w, env, false, expr)?.map_or_else(
2726                     || {
2727                         let s2 = &(cc.1).1;
2728                         match e1.2.as_id() {
2729                             Some(ast_defs::Id(_, s1)) => {
2730                                 let s1 =
2731                                     get_class_name_from_id(ctx, env.codegen_env, true, true, s1);
2732                                 concat_str_by(w, "::", [&s1.into(), s2])
2733                             }
2734                             _ => {
2735                                 print_expr(ctx, w, env, e1)?;
2736                                 w.write("::")?;
2737                                 w.write(s2)
2738                             }
2739                         }
2740                     },
2741                     Ok,
2742                 )
2743             } else {
2744                 Err(Error::fail("TODO: Only expected CIexpr in class_const"))
2745             }
2746         }
2747         E_::Unop(u) => match u.0 {
2748             ast::Uop::Upincr => {
2749                 print_expr(ctx, w, env, &u.1)?;
2750                 w.write("++")
2751             }
2752             ast::Uop::Updecr => {
2753                 print_expr(ctx, w, env, &u.1)?;
2754                 w.write("--")
2755             }
2756             _ => {
2757                 print_uop(w, u.0)?;
2758                 print_expr(ctx, w, env, &u.1)
2759             }
2760         },
2761         E_::ObjGet(og) => {
2762             print_expr(ctx, w, env, &og.0)?;
2763             w.write(match og.2 {
2764                 ast::OgNullFlavor::OGNullthrows => "->",
2765                 ast::OgNullFlavor::OGNullsafe => "?->",
2766             })?;
2767             print_expr(ctx, w, env, &og.1)
2768         }
2769         E_::Clone(e) => {
2770             w.write("clone ")?;
2771             print_expr(ctx, w, env, e)
2772         }
2773         E_::ArrayGet(ag) => {
2774             print_expr(ctx, w, env, &ag.0)?;
2775             square(w, |w| {
2776                 option(w, &ag.1, |w, e: &ast::Expr| {
2777                     handle_possible_colon_colon_class_expr(ctx, w, env, true, &e.2)
2778                         .transpose()
2779                         .unwrap_or_else(|| print_expr(ctx, w, env, e))
2780                 })
2781             })
2782         }
2783         E_::String2(ss) => concat_by(w, " . ", ss, |w, s| print_expr(ctx, w, env, s)),
2784         E_::PrefixedString(s) => {
2785             w.write(&s.0)?;
2786             w.write(" . ")?;
2787             print_expr(ctx, w, env, &s.1)
2788         }
2789         E_::Eif(eif) => {
2790             print_expr(ctx, w, env, &eif.0)?;
2791             w.write(" ? ")?;
2792             option(w, &eif.1, |w, etrue| print_expr(ctx, w, env, etrue))?;
2793             w.write(" : ")?;
2794             print_expr(ctx, w, env, &eif.2)
2795         }
2796         E_::Cast(c) => {
2797             paren(w, |w| print_hint(w, false, &c.0))?;
2798             print_expr(ctx, w, env, &c.1)
2799         }
2800         E_::Pipe(p) => {
2801             print_expr(ctx, w, env, &p.1)?;
2802             w.write(" |> ")?;
2803             print_expr(ctx, w, env, &p.2)
2804         }
2805         E_::Is(i) => {
2806             print_expr(ctx, w, env, &i.0)?;
2807             w.write(" is ")?;
2808             print_hint(w, true, &i.1)
2809         }
2810         E_::As(a) => {
2811             print_expr(ctx, w, env, &a.0)?;
2812             w.write(if a.2 { " ?as " } else { " as " })?;
2813             print_hint(w, true, &a.1)
2814         }
2815         E_::Varray(va) => print_expr_varray(ctx, w, env, &va.1),
2816         E_::Darray(da) => print_expr_darray(ctx, w, env, print_expr, &da.1),
2817         E_::Tuple(t) => wrap_by_(w, "varray[", "]", |w| {
2818             // A tuple is represented by a varray when using reflection.
2819             concat_by(w, ", ", t, |w, i| print_expr(ctx, w, env, i))
2820         }),
2821         E_::List(l) => wrap_by_(w, "list(", ")", |w| {
2822             concat_by(w, ", ", l, |w, i| print_expr(ctx, w, env, i))
2823         }),
2824         E_::Yield(y) => {
2825             w.write("yield ")?;
2826             print_afield(ctx, w, env, y)
2827         }
2828         E_::Await(e) => {
2829             w.write("await ")?;
2830             print_expr(ctx, w, env, e)
2831         }
2832         E_::Import(i) => {
2833             print_import_flavor(w, &i.0)?;
2834             w.write(" ")?;
2835             print_expr(ctx, w, env, &i.1)
2836         }
2837         E_::Xml(_) => Err(Error::fail(
2838             "expected Xml to be converted to New during rewriting",
2839         )),
2840         E_::Efun(f) => print_efun(ctx, w, env, &f.0, &f.1),
2841         E_::FunctionPointer(fp) => {
2842             let (fp_id, targs) = &**fp;
2843             match fp_id {
2844                 ast::FunctionPtrId::FPId(ast::Id(_, sid)) => {
2845                     w.write(lstrip(adjust_id(env, &sid).as_ref(), "\\\\"))?
2846                 }
2847                 ast::FunctionPtrId::FPClassConst(ast::ClassId(_, _, class_id), (_, meth_name)) => {
2848                     match class_id {
2849                         ast::ClassId_::CIexpr(e) => match e.as_id() {
2850                             Some(id) => w.write(&get_class_name_from_id(
2851                                 ctx,
2852                                 env.codegen_env,
2853                                 true,  /* should_format */
2854                                 false, /* is_class_constant */
2855                                 &id.1,
2856                             ))?,
2857                             _ => print_expr(ctx, w, env, e)?,
2858                         },
2859                         _ => {
2860                             return Err(Error::fail(
2861                                 "TODO Unimplemented unexpected non-CIexpr in function pointer",
2862                             ));
2863                         }
2864                     }
2865                     w.write("::")?;
2866                     w.write(meth_name)?
2867                 }
2868             };
2869             wrap_by_(w, "<", ">", |w| {
2870                 concat_by(w, ", ", targs, |w, _targ| w.write("_"))
2871             })
2872         }
2873         E_::Omitted => Ok(()),
2874         E_::Lfun(lfun) => {
2875             if ctx.dump_lambdas {
2876                 let fun_ = &lfun.0;
2877                 paren(w, |w| {
2878                     paren(w, |w| {
2879                         concat_by(w, ", ", &fun_.params, |w, param| {
2880                             print_fparam(ctx, w, env, param)
2881                         })
2882                     })?;
2883                     w.write(" ==> ")?;
2884                     print_block_(ctx, w, env, &fun_.body.fb_ast, None)
2885                 })
2886             } else {
2887                 Err(Error::fail(
2888                     "expected Lfun to be converted to Efun during closure conversion print_expr",
2889                 ))
2890             }
2891         }
2892         E_::Callconv(_) => Err(Error::fail("illegal default value")),
2893         E_::ETSplice(splice) => {
2894             w.write("${")?;
2895             print_expr(ctx, w, env, splice)?;
2896             w.write("}")
2897         }
2898         _ => Err(Error::fail(format!(
2899             "TODO Unimplemented: Cannot print: {:?}",
2900             expr
2901         ))),
2902     }
2905 fn print_xml<W: Write>(
2906     ctx: &mut Context,
2907     w: &mut W,
2908     env: &ExprEnv,
2909     id: &str,
2910     es: &[ast::Expr],
2911 ) -> Result<(), W::Error> {
2912     use ast::{Expr as E, Expr_ as E_};
2914     fn syntax_error<W: Write>(_: &W) -> crate::write::Error<<W as crate::write::Write>::Error> {
2915         Error::NotImpl(String::from("print_xml: unexpected syntax"))
2916     }
2917     fn print_xhp_attr<W: Write>(
2918         ctx: &mut Context,
2919         w: &mut W,
2920         env: &ExprEnv,
2921         attr: &(ast_defs::ShapeFieldName, ast::Expr),
2922     ) -> Result<(), W::Error> {
2923         match attr {
2924             (ast_defs::ShapeFieldName::SFlitStr(s), e) => print_key_value_(
2925                 ctx,
2926                 w,
2927                 env,
2928                 &s.1,
2929                 |_, w, _, k| print_expr_string(w, k.as_slice()),
2930                 e,
2931             ),
2932             _ => Err(syntax_error(w)),
2933         }
2934     }
2936     let (attrs, children) = if es.len() < 2 {
2937         Err(syntax_error(w))
2938     } else {
2939         match (&es[0], &es[1]) {
2940             (E(_, _, E_::Shape(attrs)), E(_, _, E_::Varray(children))) => Ok((attrs, &children.1)),
2941             _ => Err(syntax_error(w)),
2942         }
2943     }?;
2944     let env = ExprEnv {
2945         codegen_env: env.codegen_env,
2946     };
2947     write!(w, "new {}", mangle(id.into()))?;
2948     paren(w, |w| {
2949         wrap_by_(w, "darray[", "]", |w| {
2950             concat_by(w, ", ", attrs, |w, attr| print_xhp_attr(ctx, w, &env, attr))
2951         })?;
2952         w.write(", ")?;
2953         print_expr_varray(ctx, w, &env, children)?;
2954         w.write(", __FILE__, __LINE__")
2955     })
2958 fn print_efun<W: Write>(
2959     ctx: &mut Context,
2960     w: &mut W,
2961     env: &ExprEnv,
2962     f: &ast::Fun_,
2963     use_list: &[ast::Lid],
2964 ) -> Result<(), W::Error> {
2965     w.write_if(
2966         f.fun_kind.is_fasync() || f.fun_kind.is_fasync_generator(),
2967         "async ",
2968     )?;
2969     w.write("function ")?;
2970     paren(w, |w| {
2971         concat_by(w, ", ", &f.params, |w, p| print_fparam(ctx, w, env, p))
2972     })?;
2973     w.write(" ")?;
2974     if !use_list.is_empty() {
2975         w.write("use ")?;
2976         paren(w, |w| {
2977             concat_by(w, ", ", use_list, |w: &mut W, ast::Lid(_, id)| {
2978                 w.write(local_id::get_name(id))
2979             })
2980         })?;
2981         w.write(" ")?;
2982     }
2983     print_block_(ctx, w, env, &f.body.fb_ast, None)
2986 fn print_block<W: Write>(
2987     ctx: &mut Context,
2988     w: &mut W,
2989     env: &ExprEnv,
2990     block: &[ast::Stmt],
2991     ident: Option<&str>,
2992 ) -> Result<(), W::Error> {
2993     match &block[..] {
2994         [] | [ast::Stmt(_, ast::Stmt_::Noop)] => Ok(()),
2995         [ast::Stmt(_, ast::Stmt_::Block(b))] if b.len() == 1 => print_block_(ctx, w, env, b, ident),
2996         [_, _, ..] => print_block_(ctx, w, env, block, ident),
2997         [stmt] => print_statement(ctx, w, env, stmt, None),
2998     }
3001 fn print_block_<W: Write>(
3002     ctx: &mut Context,
3003     w: &mut W,
3004     env: &ExprEnv,
3005     block: &[ast::Stmt],
3006     ident: Option<&str>,
3007 ) -> Result<(), W::Error> {
3008     wrap_by_(w, "{\\n", "}\\n", |w| {
3009         concat(w, block, |w, stmt| {
3010             option(w, ident, |w, i: &str| w.write(i))?;
3011             print_statement(ctx, w, env, stmt, Some("  "))
3012         })?;
3013         option(w, ident, |w, i: &str| w.write(i))
3014     })
3017 fn print_statement<W: Write>(
3018     ctx: &mut Context,
3019     w: &mut W,
3020     env: &ExprEnv,
3021     stmt: &ast::Stmt,
3022     ident: Option<&str>,
3023 ) -> Result<(), W::Error> {
3024     use ast::Stmt_ as S_;
3025     match &stmt.1 {
3026         S_::Return(expr) => {
3027             option(w, ident, |w, i: &str| w.write(i))?;
3028             wrap_by_(w, "return", ";\\n", |w| {
3029                 option(w, &**expr, |w, e| {
3030                     w.write(" ")?;
3031                     print_expr(ctx, w, env, e)
3032                 })
3033             })
3034         }
3035         S_::Expr(expr) => {
3036             option(w, ident, |w, i: &str| w.write(i))?;
3037             print_expr(ctx, w, env, &**expr)?;
3038             w.write(";\\n")
3039         }
3040         S_::Throw(expr) => {
3041             option(w, ident, |w, i: &str| w.write(i))?;
3042             wrap_by_(w, "throw ", ";\\n", |w| print_expr(ctx, w, env, &**expr))
3043         }
3044         S_::Break => {
3045             option(w, ident, |w, i: &str| w.write(i))?;
3046             w.write("break;\\n")
3047         }
3048         S_::Continue => {
3049             option(w, ident, |w, i: &str| w.write(i))?;
3050             w.write("continue;\\n")
3051         }
3052         S_::While(x) => {
3053             let (cond, block) = &**x;
3054             option(w, ident, |w, i: &str| w.write(i))?;
3055             wrap_by_(w, "while (", ") ", |w| print_expr(ctx, w, env, cond))?;
3056             print_block(ctx, w, env, block.as_ref(), ident)
3057         }
3058         S_::If(x) => {
3059             let (cond, if_block, else_block) = &**x;
3060             option(w, ident, |w, i: &str| w.write(i))?;
3061             wrap_by_(w, "if (", ") ", |w| print_expr(ctx, w, env, cond))?;
3062             print_block(ctx, w, env, if_block, ident)?;
3063             let mut buf = String::new();
3064             print_block(ctx, &mut buf, env, else_block, ident).map_err(|e| match e {
3065                 Error::NotImpl(m) => Error::NotImpl(m),
3066                 _ => Error::Fail(format!("Failed: {}", e)),
3067             })?;
3068             w.write_if(!buf.is_empty(), " else ")?;
3069             w.write_if(!buf.is_empty(), buf)
3070         }
3071         S_::Block(block) => {
3072             option(w, ident, |w, i: &str| w.write(i))?;
3073             print_block_(ctx, w, env, block, ident)
3074         }
3075         S_::Noop => Ok(()),
3076         /* TODO(T29869930) */
3077         _ => w.write("TODO Unimplemented NYI: Default value printing"),
3078     }
3081 fn print_fparam<W: Write>(
3082     ctx: &mut Context,
3083     w: &mut W,
3084     env: &ExprEnv,
3085     param: &ast::FunParam,
3086 ) -> Result<(), W::Error> {
3087     if let Some(ast_defs::ParamKind::Pinout) = param.callconv {
3088         w.write("inout ")?;
3089     }
3090     if param.is_variadic {
3091         w.write("...")?;
3092     }
3093     option(w, &(param.type_hint).1, |w, h| {
3094         print_hint(w, true, h)?;
3095         w.write(" ")
3096     })?;
3097     w.write(&param.name)?;
3098     option(w, &param.expr, |w, e| {
3099         w.write(" = ")?;
3100         print_expr(ctx, w, env, e)
3101     })
3104 fn print_bop<W: Write>(w: &mut W, bop: &ast_defs::Bop) -> Result<(), W::Error> {
3105     use ast_defs::Bop;
3106     match bop {
3107         Bop::Plus => w.write("+"),
3108         Bop::Minus => w.write("-"),
3109         Bop::Star => w.write("*"),
3110         Bop::Slash => w.write("/"),
3111         Bop::Eqeq => w.write("=="),
3112         Bop::Eqeqeq => w.write("==="),
3113         Bop::Starstar => w.write("**"),
3114         Bop::Eq(None) => w.write("="),
3115         Bop::Eq(Some(bop)) => {
3116             w.write("=")?;
3117             print_bop(w, bop)
3118         }
3119         Bop::Ampamp => w.write("&&"),
3120         Bop::Barbar => w.write("||"),
3121         Bop::Lt => w.write("<"),
3122         Bop::Lte => w.write("<="),
3123         Bop::Cmp => w.write("<=>"),
3124         Bop::Gt => w.write(">"),
3125         Bop::Gte => w.write(">="),
3126         Bop::Dot => w.write("."),
3127         Bop::Amp => w.write("&"),
3128         Bop::Bar => w.write("|"),
3129         Bop::Ltlt => w.write("<<"),
3130         Bop::Gtgt => w.write(">>"),
3131         Bop::Percent => w.write("%"),
3132         Bop::Xor => w.write("^"),
3133         Bop::Diff => w.write("!="),
3134         Bop::Diff2 => w.write("!=="),
3135         Bop::QuestionQuestion => w.write("??"),
3136     }
3139 fn print_hint<W: Write>(w: &mut W, ns: bool, hint: &ast::Hint) -> Result<(), W::Error> {
3140     let alloc = bumpalo::Bump::new();
3141     let h = emit_type_hint::fmt_hint(&alloc, &[], false, hint).map_err(|e| match e {
3142         Unrecoverable(s) => Error::fail(s),
3143         _ => Error::fail("Error printing hint"),
3144     })?;
3145     if ns {
3146         w.write(escaper::escape(h))
3147     } else {
3148         w.write(escaper::escape(strip_ns(&h)))
3149     }
3152 fn print_import_flavor<W: Write>(w: &mut W, flavor: &ast::ImportFlavor) -> Result<(), W::Error> {
3153     use ast::ImportFlavor as F;
3154     w.write(match flavor {
3155         F::Include => "include",
3156         F::Require => "require",
3157         F::IncludeOnce => "include_once",
3158         F::RequireOnce => "require_once",
3159     })
3162 fn print_param_user_attributes<W: Write>(
3163     ctx: &mut Context,
3164     w: &mut W,
3165     param: &HhasParam,
3166 ) -> Result<(), W::Error> {
3167     match param.user_attributes.as_ref()[..] {
3168         [] => Ok(()),
3169         _ => square(w, |w| print_attributes(ctx, w, &param.user_attributes)),
3170     }
3173 fn print_span<W: Write>(
3174     w: &mut W,
3175     &HhasSpan(line_begin, line_end): &HhasSpan,
3176 ) -> Result<(), W::Error> {
3177     write!(w, "({},{})", line_begin, line_end)
3180 fn print_fun_attrs<W: Write>(
3181     ctx: &mut Context,
3182     w: &mut W,
3183     f: &HhasFunction,
3184 ) -> Result<(), W::Error> {
3185     use hhas_attribute::*;
3186     let user_attrs = f.attributes.as_ref();
3187     let mut special_attrs = vec![];
3188     if has_meth_caller(user_attrs) {
3189         special_attrs.push("builtin");
3190         special_attrs.push("is_meth_caller");
3191     }
3192     if f.is_interceptable() {
3193         special_attrs.push("interceptable");
3194     }
3195     if has_foldable(user_attrs) {
3196         special_attrs.push("foldable");
3197     }
3198     if has_provenance_skip_frame(user_attrs) {
3199         special_attrs.push("prov_skip_frame");
3200     }
3201     if f.is_no_injection() {
3202         special_attrs.push("no_injection");
3203     }
3204     if ctx.is_system_lib() || (has_dynamically_callable(user_attrs) && !f.is_memoize_impl()) {
3205         special_attrs.push("dyn_callable")
3206     }
3207     if ctx.is_system_lib() {
3208         special_attrs.push("unique");
3209         special_attrs.push("builtin");
3210         special_attrs.push("persistent");
3211     }
3212     print_special_and_user_attrs(ctx, w, &special_attrs, user_attrs)
3215 fn print_special_and_user_attrs<W: Write>(
3216     ctx: &mut Context,
3217     w: &mut W,
3218     specials: &[&str],
3219     users: &[HhasAttribute],
3220 ) -> Result<(), W::Error> {
3221     if !users.is_empty() || !specials.is_empty() {
3222         square(w, |w| {
3223             concat_str_by(w, " ", specials)?;
3224             if !specials.is_empty() && !users.is_empty() {
3225                 w.write(" ")?;
3226             }
3227             print_attributes(ctx, w, users)
3228         })?;
3229         w.write(" ")?;
3230     }
3231     Ok(())
3234 fn print_upper_bounds<'arena, W: Write>(
3235     w: &mut W,
3236     ubs: impl AsRef<[Pair<Str<'arena>, Slice<'arena, HhasTypeInfo<'arena>>>]>,
3237 ) -> Result<(), W::Error> {
3238     braces(w, |w| concat_by(w, ", ", ubs, print_upper_bound))
3241 fn print_upper_bound<'arena, W: Write>(
3242     w: &mut W,
3243     Pair(id, tys): &Pair<Str<'arena>, Slice<'arena, HhasTypeInfo>>,
3244 ) -> Result<(), W::Error> {
3245     paren(w, |w| {
3246         concat_str_by(w, " ", [id.as_str(), "as", ""])?;
3247         concat_by(w, ", ", &tys, print_type_info)
3248     })
3251 fn print_upper_bounds_<'arena, W: Write>(
3252     w: &mut W,
3253     ubs: impl AsRef<[Pair<Str<'arena>, Slice<'arena, HhasTypeInfo<'arena>>>]>,
3254 ) -> Result<(), W::Error> {
3255     braces(w, |w| concat_by(w, ", ", ubs, print_upper_bound_))
3258 fn print_upper_bound_<'arena, W: Write>(
3259     w: &mut W,
3260     Pair(id, tys): &Pair<Str<'arena>, Slice<'arena, HhasTypeInfo<'arena>>>,
3261 ) -> Result<(), W::Error> {
3262     paren(w, |w| {
3263         concat_str_by(w, " ", [id.as_str(), "as", ""])?;
3264         concat_by(w, ", ", &tys, print_type_info)
3265     })
3268 fn print_type_info<W: Write>(w: &mut W, ti: &HhasTypeInfo) -> Result<(), W::Error> {
3269     print_type_info_(w, false, ti)
3272 fn print_type_flags<W: Write>(
3273     w: &mut W,
3274     flag: constraint::ConstraintFlags,
3275 ) -> Result<(), W::Error> {
3276     let mut first = true;
3277     let mut print_space = |w: &mut W| -> Result<(), W::Error> {
3278         if !first {
3279             w.write(" ")
3280         } else {
3281             Ok(first = false)
3282         }
3283     };
3284     use constraint::ConstraintFlags as F;
3285     if flag.contains(F::DISPLAY_NULLABLE) {
3286         print_space(w)?;
3287         w.write("display_nullable")?;
3288     }
3289     if flag.contains(F::EXTENDED_HINT) {
3290         print_space(w)?;
3291         w.write("extended_hint")?;
3292     }
3293     if flag.contains(F::NULLABLE) {
3294         print_space(w)?;
3295         w.write("nullable")?;
3296     }
3298     if flag.contains(F::SOFT) {
3299         print_space(w)?;
3300         w.write("soft")?;
3301     }
3302     if flag.contains(F::TYPE_CONSTANT) {
3303         print_space(w)?;
3304         w.write("type_constant")?;
3305     }
3307     if flag.contains(F::TYPE_VAR) {
3308         print_space(w)?;
3309         w.write("type_var")?;
3310     }
3312     if flag.contains(F::UPPERBOUND) {
3313         print_space(w)?;
3314         w.write("upper_bound")?;
3315     }
3316     Ok(())
3319 fn print_type_info_<W: Write>(w: &mut W, is_enum: bool, ti: &HhasTypeInfo) -> Result<(), W::Error> {
3320     let print_quote_str = |w: &mut W, opt: &Option<String>| {
3321         option_or(
3322             w,
3323             opt,
3324             |w, s: &String| quotes(w, |w| w.write(escape(s))),
3325             "N",
3326         )
3327     };
3328     angle(w, |w| {
3329         print_quote_str(
3330             w,
3331             &Option::from(ti.user_type.map(|n| n.as_str().to_owned())),
3332         )?;
3333         w.write(" ")?;
3334         if !is_enum {
3335             print_quote_str(
3336                 w,
3337                 &Option::from(ti.type_constraint.name.map(|n| n.as_str().to_owned())),
3338             )?;
3339             w.write(" ")?;
3340         }
3341         print_type_flags(w, ti.type_constraint.flags)
3342     })
3345 fn print_typedef_info<W: Write>(w: &mut W, ti: &HhasTypeInfo) -> Result<(), W::Error> {
3346     angle(w, |w| {
3347         w.write(quote_string(
3348             ti.type_constraint.name.as_ref().map_or("", |n| n.as_str()),
3349         ))?;
3350         let flags = ti.type_constraint.flags & constraint::ConstraintFlags::NULLABLE;
3351         if !flags.is_empty() {
3352             wrap_by(w, " ", |w| {
3353                 print_type_flags(
3354                     w,
3355                     ti.type_constraint.flags & constraint::ConstraintFlags::NULLABLE,
3356                 )
3357             })?;
3358         }
3359         Ok(())
3360     })
3363 fn print_extends<W: Write>(w: &mut W, base: Option<&str>) -> Result<(), W::Error> {
3364     match base {
3365         None => Ok(()),
3366         Some(b) => concat_str_by(w, " ", [" extends", b]),
3367     }
3370 fn print_record_field<W: Write>(
3371     ctx: &mut Context,
3372     w: &mut W,
3373     Field(name, type_info, intial_value): &Field,
3374 ) -> Result<(), W::Error> {
3375     ctx.newline(w)?;
3376     w.write(".property ")?;
3377     match intial_value {
3378         Just(_) => w.write("[public] ")?,
3379         Nothing => w.write("[public sys_initial_val] ")?,
3380     }
3381     print_type_info(w, type_info)?;
3382     concat_str_by(w, " ", ["", name.as_str(), "="])?;
3384     ctx.block(w, |c, w| {
3385         c.newline(w)?;
3386         match intial_value {
3387             Nothing => w.write("uninit")?,
3388             Just(value) => triple_quotes(w, |w| print_adata(c, w, value))?,
3389         }
3390         w.write(";")
3391     })
3394 fn print_record_def<W: Write>(
3395     ctx: &mut Context,
3396     w: &mut W,
3397     record: &HhasRecord,
3398 ) -> Result<(), W::Error> {
3399     newline(w)?;
3400     if record.is_abstract {
3401         concat_str_by(w, " ", [".record", record.name.to_raw_string()])?;
3402     } else {
3403         concat_str_by(w, " ", [".record", "[final]", record.name.to_raw_string()])?;
3404     }
3405     w.write(" ")?;
3406     print_span(w, &record.span)?;
3407     print_extends(
3408         w,
3409         Option::from(record.base.as_ref().map(|b| b.to_raw_string())),
3410     )?;
3411     w.write(" ")?;
3413     braces(w, |w| {
3414         ctx.block(w, |c, w| {
3415             concat(w, &record.fields, |w, rf| print_record_field(c, w, rf))
3416         })?;
3417         ctx.newline(w)
3418     })?;
3419     newline(w)
3422 /// Convert an `Expr` to a `String` of the equivalent source code.
3424 /// This is a debugging tool abusing a printer written for bytecode
3425 /// emission. It does not support all Hack expressions, and panics
3426 /// on unsupported syntax.
3428 /// If you have an `Expr` with positions, you are much better off
3429 /// getting the source code at those positions rather than using this.
3430 pub fn expr_to_string_lossy(mut ctx: Context, expr: &ast::Expr) -> String {
3431     ctx.dump_lambdas = true;
3433     let env = ExprEnv { codegen_env: None };
3434     let mut escaped_src = String::new();
3435     print_expr(&mut ctx, &mut escaped_src, &env, expr).expect("Printing failed");
3437     let bs = escaper::unescape_double(&escaped_src).expect("Unescaping failed");
3438     let s = String::from_utf8_lossy(&bs);
3439     s.to_string()