Use Tuple in lowerer and hackc
[hiphop-php.git] / hphp / hack / src / hhbc / ast_constant_folder.rs
blobf42be6034d11fb2c74f563a44cb531000ec911bc
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
5 use indexmap::IndexMap;
6 use std::{collections::hash_map::RandomState, fmt, iter::FromIterator};
8 use ast_class_expr_rust as ast_class_expr;
9 use ast_scope_rust as ast_scope;
10 use env::emitter::Emitter;
11 use hhbc_id_rust::Id;
12 use hhbc_string_utils_rust as string_utils;
13 use naming_special_names_rust::{math, members, special_functions, typehints};
14 use options::HhvmFlags;
15 use oxidized::{
16     aast,
17     aast_visitor::{visit_mut, AstParams, NodeMut, VisitorMut},
18     ast as tast, ast_defs,
19     namespace_env::Env as Namespace,
20     pos::Pos,
22 use runtime::TypedValue;
24 use itertools::Itertools;
26 #[derive(Debug, PartialEq, Eq)]
27 pub enum Error {
28     NotLiteral,
29     UserDefinedConstant,
30     Unrecoverable(String),
33 impl Error {
34     fn unrecoverable(s: impl Into<String>) -> Self {
35         Self::Unrecoverable(s.into())
36     }
39 impl fmt::Display for Error {
40     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41         match self {
42             Self::NotLiteral => write!(f, "NotLiteral"),
43             Self::UserDefinedConstant => write!(f, "UserDefinedConstant"),
44             Self::Unrecoverable(msg) => write!(f, "{}", msg),
45         }
46     }
49 enum Radix {
50     Oct,
51     Hex,
52     Dec,
53     Bin,
56 fn radix(s: &str) -> Radix {
57     let s = s.as_bytes();
58     if s.len() > 1 && (s[0] as char) == '0' {
59         match s[1] as char {
60             'b' | 'B' => Radix::Bin,
61             'x' | 'X' => Radix::Hex,
62             _ => Radix::Oct,
63         }
64     } else {
65         Radix::Dec
66     }
69 fn try_type_intlike(s: &str) -> Option<i64> {
70     match radix(s) {
71         Radix::Dec => s.parse().ok(),
72         Radix::Bin => i64::from_str_radix(&s[2..], 2).ok(),
73         Radix::Oct => {
74             let mut i = 1;
75             let sb = s.as_bytes();
76             // Ocaml's version truncate if any digit is greater then 7.
77             while i < sb.len() {
78                 if sb[i] >= b'0' && sb[i] <= b'7' {
79                     i += 1;
80                 } else {
81                     break;
82                 }
83             }
84             if i > 1 {
85                 let sb = &sb[1..i];
86                 i64::from_str_radix(std::str::from_utf8(sb).unwrap(), 8).ok()
87             } else {
88                 Some(0)
89             }
90         }
91         Radix::Hex => i64::from_str_radix(&s[2..], 16).ok(),
92     }
95 fn class_const_to_typed_value(
96     emitter: &Emitter,
97     cid: &tast::ClassId,
98     id: &tast::Pstring,
99 ) -> Result<TypedValue, Error> {
100     if id.1 == members::M_CLASS {
101         let cexpr = ast_class_expr::ClassExpr::class_id_to_class_expr(
102             emitter,
103             false,
104             true,
105             &ast_scope::Scope::toplevel(),
106             cid,
107         );
108         if let ast_class_expr::ClassExpr::Id(ast_defs::Id(_, cname)) = cexpr {
109             if emitter.options().emit_class_pointers() == 2 {
110                 let cname = hhbc_id_rust::class::Type::from_ast_name_and_mangle(&cname).into();
111                 return Ok(TypedValue::LazyClass(cname));
112             } else {
113                 let cname = hhbc_id_rust::class::Type::from_ast_name(&cname).into();
114                 return Ok(TypedValue::String(cname));
115             }
116         }
117     }
118     Err(Error::UserDefinedConstant)
121 fn varray_to_typed_value(
122     emitter: &Emitter,
123     ns: &Namespace,
124     fields: &Vec<tast::Expr>,
125 ) -> Result<TypedValue, Error> {
126     let tv_fields: Vec<TypedValue> = fields
127         .iter()
128         .map(|x| expr_to_typed_value(emitter, ns, x))
129         .collect::<Result<_, Error>>()?;
130     Ok(TypedValue::Vec(tv_fields))
133 fn darray_to_typed_value(
134     emitter: &Emitter,
135     ns: &Namespace,
136     fields: &Vec<(tast::Expr, tast::Expr)>,
137 ) -> Result<TypedValue, Error> {
138     let tv_fields: Vec<(TypedValue, TypedValue)> = fields
139         .iter()
140         .map(|(k, v)| {
141             Ok((
142                 key_expr_to_typed_value(emitter, ns, k)?,
143                 expr_to_typed_value(emitter, ns, v)?,
144             ))
145         })
146         .collect::<Result<_, Error>>()?;
147     Ok(TypedValue::Dict(update_duplicates_in_map(tv_fields)))
150 fn set_afield_to_typed_value_pair(
151     e: &Emitter,
152     ns: &Namespace,
153     afield: &tast::Afield,
154 ) -> Result<(TypedValue, TypedValue), Error> {
155     match afield {
156         tast::Afield::AFvalue(v) => set_afield_value_to_typed_value_pair(e, ns, v),
157         _ => Err(Error::unrecoverable(
158             "set_afield_to_typed_value_pair: unexpected key=>value",
159         )),
160     }
163 fn set_afield_value_to_typed_value_pair(
164     e: &Emitter,
165     ns: &Namespace,
166     v: &tast::Expr,
167 ) -> Result<(TypedValue, TypedValue), Error> {
168     let tv = key_expr_to_typed_value(e, ns, v)?;
169     Ok((tv.clone(), tv))
172 fn afield_to_typed_value_pair(
173     emitter: &Emitter,
174     ns: &Namespace,
175     afield: &tast::Afield,
176 ) -> Result<(TypedValue, TypedValue), Error> {
177     match afield {
178         tast::Afield::AFvalue(_) => Err(Error::unrecoverable(
179             "afield_to_typed_value_pair: unexpected value",
180         )),
181         tast::Afield::AFkvalue(key, value) => kv_to_typed_value_pair(emitter, ns, key, value),
182     }
185 fn kv_to_typed_value_pair(
186     emitter: &Emitter,
187     ns: &Namespace,
188     key: &tast::Expr,
189     value: &tast::Expr,
190 ) -> Result<(TypedValue, TypedValue), Error> {
191     Ok((
192         key_expr_to_typed_value(emitter, ns, key)?,
193         expr_to_typed_value(emitter, ns, value)?,
194     ))
197 fn value_afield_to_typed_value(
198     emitter: &Emitter,
199     ns: &Namespace,
200     afield: &tast::Afield,
201 ) -> Result<TypedValue, Error> {
202     match afield {
203         tast::Afield::AFvalue(e) => expr_to_typed_value(emitter, ns, e),
204         tast::Afield::AFkvalue(_, _) => Err(Error::unrecoverable(
205             "value_afield_to_typed_value: unexpected key=>value",
206         )),
207     }
210 fn key_expr_to_typed_value(
211     emitter: &Emitter,
212     ns: &Namespace,
213     expr: &tast::Expr,
214 ) -> Result<TypedValue, Error> {
215     let tv = expr_to_typed_value(emitter, ns, expr)?;
216     let fold_lc = emitter
217         .options()
218         .hhvm
219         .flags
220         .contains(HhvmFlags::FOLD_LAZY_CLASS_KEYS);
221     match tv {
222         TypedValue::Int(_) | TypedValue::String(_) => Ok(tv),
223         TypedValue::LazyClass(_) if fold_lc => Ok(tv),
224         _ => Err(Error::NotLiteral),
225     }
228 fn keyset_value_afield_to_typed_value(
229     emitter: &Emitter,
230     ns: &Namespace,
231     afield: &tast::Afield,
232 ) -> Result<TypedValue, Error> {
233     let tv = value_afield_to_typed_value(emitter, ns, afield)?;
234     let fold_lc = emitter
235         .options()
236         .hhvm
237         .flags
238         .contains(HhvmFlags::FOLD_LAZY_CLASS_KEYS);
239     match tv {
240         TypedValue::Int(_) | TypedValue::String(_) => Ok(tv),
241         TypedValue::LazyClass(_) if fold_lc => Ok(tv),
242         _ => Err(Error::NotLiteral),
243     }
246 fn shape_to_typed_value(
247     emitter: &Emitter,
248     ns: &Namespace,
249     fields: &Vec<(tast::ShapeFieldName, tast::Expr)>,
250 ) -> Result<TypedValue, Error> {
251     let a = fields
252         .iter()
253         .map(|(sf, expr)| {
254             let key = match sf {
255                 ast_defs::ShapeFieldName::SFlitInt((_, s)) => {
256                     let tv = int_expr_to_typed_value(s)?;
257                     match tv {
258                         TypedValue::Int(_) => tv,
259                         _ => {
260                             return Err(Error::unrecoverable(format!(
261                                 "{} is not a valid integer index",
262                                 s
263                             )));
264                         }
265                     }
266                 }
267                 ast_defs::ShapeFieldName::SFlitStr(id) => {
268                     // FIXME: This is not safe--string literals are binary
269                     // strings. There's no guarantee that they're valid UTF-8.
270                     TypedValue::String(unsafe { String::from_utf8_unchecked(id.1.clone().into()) })
271                 }
272                 ast_defs::ShapeFieldName::SFclassConst(class_id, id) => class_const_to_typed_value(
273                     emitter,
274                     &tast::ClassId(Pos::make_none(), tast::ClassId_::CI(class_id.clone())),
275                     id,
276                 )?,
277             };
278             Ok((key, expr_to_typed_value(emitter, ns, expr)?))
279         })
280         .collect::<Result<_, _>>()?;
281     Ok(TypedValue::Dict(a))
284 pub fn vec_to_typed_value(
285     e: &Emitter,
286     ns: &Namespace,
287     fields: &[tast::Afield],
288 ) -> Result<TypedValue, Error> {
289     Ok(TypedValue::Vec(
290         fields
291             .iter()
292             .map(|f| value_afield_to_typed_value(e, ns, f))
293             .collect::<Result<_, _>>()?,
294     ))
297 pub fn expr_to_typed_value(
298     e: &Emitter,
299     ns: &Namespace,
300     expr: &tast::Expr,
301 ) -> Result<TypedValue, Error> {
302     expr_to_typed_value_(
303         e, ns, expr, false, /*allow_maps*/
304         false, /*force_class_const*/
305     )
308 pub fn expr_to_typed_value_(
309     emitter: &Emitter,
310     ns: &Namespace,
311     expr: &tast::Expr,
312     allow_maps: bool,
313     force_class_const: bool,
314 ) -> Result<TypedValue, Error> {
315     use aast::Expr_::*;
316     // TODO: ML equivalent has this as an implicit parameter that defaults to false.
317     match &expr.1 {
318         Int(s) => int_expr_to_typed_value(s),
319         tast::Expr_::True => Ok(TypedValue::Bool(true)),
320         False => Ok(TypedValue::Bool(false)),
321         Null => Ok(TypedValue::Null),
322         String(s) => {
323             // FIXME: This is not safe--string literals are binary strings.
324             // There's no guarantee that they're valid UTF-8.
325             Ok(TypedValue::String(unsafe {
326                 std::string::String::from_utf8_unchecked(s.clone().into())
327             }))
328         }
329         EnumAtom(s) => Ok(TypedValue::String(s.clone())),
330         Float(s) => {
331             if s == math::INF {
332                 Ok(TypedValue::float(std::f64::INFINITY))
333             } else if s == math::NEG_INF {
334                 Ok(TypedValue::float(std::f64::NEG_INFINITY))
335             } else if s == math::NAN {
336                 Ok(TypedValue::float(std::f64::NAN))
337             } else {
338                 s.parse()
339                     .map(|x| TypedValue::float(x))
340                     .map_err(|_| Error::NotLiteral)
341             }
342         }
344         Call(id)
345             if id
346                 .0
347                 .as_id()
348                 .map(|x| x.1 == special_functions::HHAS_ADATA)
349                 .unwrap_or(false) =>
350         {
351             match &id.2[..] {
352                 &[tast::Expr(_, tast::Expr_::String(ref data))] => {
353                     // FIXME: This is not safe--string literals are binary strings.
354                     // There's no guarantee that they're valid UTF-8.
355                     Ok(TypedValue::HhasAdata(unsafe {
356                         std::string::String::from_utf8_unchecked(data.clone().into())
357                     }))
358                 }
359                 _ => Err(Error::NotLiteral),
360             }
361         }
363         Varray(fields) => varray_to_typed_value(emitter, ns, &fields.1),
364         Darray(fields) => darray_to_typed_value(emitter, ns, &fields.1),
366         Id(id) if id.1 == math::NAN => Ok(TypedValue::float(std::f64::NAN)),
367         Id(id) if id.1 == math::INF => Ok(TypedValue::float(std::f64::INFINITY)),
368         Id(_) => Err(Error::UserDefinedConstant),
370         Collection(x) if x.0.name().eq("vec") => vec_to_typed_value(emitter, ns, &x.2),
371         Collection(x) if x.0.name().eq("keyset") => Ok(TypedValue::Keyset(
372             x.2.iter()
373                 .map(|x| keyset_value_afield_to_typed_value(emitter, ns, x))
374                 .collect::<Result<Vec<_>, _>>()?
375                 .into_iter()
376                 .unique()
377                 .collect(),
378         )),
379         Collection(x)
380             if x.0.name().eq("dict")
381                 || allow_maps
382                     && (string_utils::cmp(&(x.0).1, "Map", false, true)
383                         || string_utils::cmp(&(x.0).1, "ImmMap", false, true)) =>
384         {
385             let values =
386                 x.2.iter()
387                     .map(|x| afield_to_typed_value_pair(emitter, ns, x))
388                     .collect::<Result<_, _>>()?;
389             Ok(TypedValue::Dict(update_duplicates_in_map(values)))
390         }
391         Collection(x)
392             if allow_maps
393                 && (string_utils::cmp(&(x.0).1, "Set", false, true)
394                     || string_utils::cmp(&(x.0).1, "ImmSet", false, true)) =>
395         {
396             let values =
397                 x.2.iter()
398                     .map(|x| set_afield_to_typed_value_pair(emitter, ns, x))
399                     .collect::<Result<_, _>>()?;
400             Ok(TypedValue::Dict(update_duplicates_in_map(values)))
401         }
402         Tuple(x) => Ok(TypedValue::Vec(
403             x.iter()
404                 .map(|e| expr_to_typed_value(emitter, ns, e))
405                 .collect::<Result<_, _>>()?,
406         )),
407         ValCollection(x) if x.0 == tast::VcKind::Vec || x.0 == tast::VcKind::Vector => {
408             Ok(TypedValue::Vec(
409                 x.2.iter()
410                     .map(|e| expr_to_typed_value(emitter, ns, e))
411                     .collect::<Result<_, _>>()?,
412             ))
413         }
414         ValCollection(x) if x.0 == tast::VcKind::Keyset => Ok(TypedValue::Keyset(
415             x.2.iter()
416                 .map(|e| {
417                     expr_to_typed_value(emitter, ns, e).and_then(|tv| match tv {
418                         TypedValue::Int(_) | TypedValue::String(_) => Ok(tv),
419                         TypedValue::LazyClass(_)
420                             if emitter
421                                 .options()
422                                 .hhvm
423                                 .flags
424                                 .contains(HhvmFlags::FOLD_LAZY_CLASS_KEYS) =>
425                         {
426                             Ok(tv)
427                         }
428                         _ => Err(Error::NotLiteral),
429                     })
430                 })
431                 .collect::<Result<Vec<_>, _>>()?
432                 .into_iter()
433                 .unique()
434                 .collect(),
435         )),
436         ValCollection(x) if x.0 == tast::VcKind::Set || x.0 == tast::VcKind::ImmSet => {
437             let values =
438                 x.2.iter()
439                     .map(|e| set_afield_value_to_typed_value_pair(emitter, ns, e))
440                     .collect::<Result<_, _>>()?;
441             Ok(TypedValue::Dict(update_duplicates_in_map(values)))
442         }
444         KeyValCollection(x) => {
445             let values =
446                 x.2.iter()
447                     .map(|e| kv_to_typed_value_pair(emitter, ns, &e.0, &e.1))
448                     .collect::<Result<_, _>>()?;
449             Ok(TypedValue::Dict(update_duplicates_in_map(values)))
450         }
452         Shape(fields) => shape_to_typed_value(emitter, ns, fields),
453         ClassConst(x) => {
454             if emitter.options().emit_class_pointers() == 1 && !force_class_const {
455                 Err(Error::NotLiteral)
456             } else {
457                 class_const_to_typed_value(emitter, &x.0, &x.1)
458             }
459         }
460         ClassGet(_) => Err(Error::UserDefinedConstant),
461         As(x) if (x.1).1.is_hlike() => expr_to_typed_value_(emitter, ns, &x.0, allow_maps, false),
462         _ => Err(Error::NotLiteral),
463     }
466 fn int_expr_to_typed_value(s: &str) -> Result<TypedValue, Error> {
467     Ok(TypedValue::Int(
468         try_type_intlike(&s).unwrap_or(std::i64::MAX),
469     ))
472 fn update_duplicates_in_map(kvs: Vec<(TypedValue, TypedValue)>) -> Vec<(TypedValue, TypedValue)> {
473     IndexMap::<_, _, RandomState>::from_iter(kvs.into_iter())
474         .into_iter()
475         .collect()
478 fn cast_value(hint: &tast::Hint_, v: TypedValue) -> Result<TypedValue, Error> {
479     match hint {
480         tast::Hint_::Happly(ast_defs::Id(_, id), args) if args.is_empty() => {
481             let id = string_utils::strip_hh_ns(id);
482             if id == typehints::BOOL {
483                 v.cast_to_bool()
484             } else if id == typehints::STRING {
485                 v.cast_to_string()
486             } else if id == typehints::FLOAT {
487                 v.cast_to_float()
488             } else {
489                 None
490             }
491         }
492         _ => None,
493     }
494     .ok_or(Error::NotLiteral)
497 fn unop_on_value(unop: &ast_defs::Uop, v: TypedValue) -> Result<TypedValue, Error> {
498     match unop {
499         ast_defs::Uop::Unot => v.not(),
500         ast_defs::Uop::Uplus => v.add(&TypedValue::Int(0)),
501         ast_defs::Uop::Uminus => v.neg(),
502         ast_defs::Uop::Utild => v.bitwise_not(),
503         ast_defs::Uop::Usilence => Some(v.clone()),
504         _ => None,
505     }
506     .ok_or(Error::NotLiteral)
509 fn binop_on_values(
510     binop: &ast_defs::Bop,
511     v1: TypedValue,
512     v2: TypedValue,
513 ) -> Result<TypedValue, Error> {
514     use ast_defs::Bop::*;
515     match binop {
516         Dot => v1.concat(v2),
517         Plus => v1.add(&v2),
518         Minus => v1.sub(&v2),
519         Star => v1.mul(&v2),
520         Ltlt => v1.shift_left(&v2),
521         Slash => v1.div(&v2),
522         Bar => v1.bitwise_or(&v2),
523         _ => None,
524     }
525     .ok_or(Error::NotLiteral)
528 fn value_to_expr_(v: TypedValue) -> Result<tast::Expr_, Error> {
529     use tast::*;
530     use TypedValue::*;
531     Ok(match v {
532         Int(i) => Expr_::Int(i.to_string()),
533         Float(i) => Expr_::Float(hhbc_string_utils_rust::float::to_string(i)),
534         Bool(false) => Expr_::False,
535         Bool(true) => Expr_::True,
536         String(s) => Expr_::String(s.into()),
537         LazyClass(_) => return Err(Error::unrecoverable("value_to_expr: lazyclass NYI")),
538         Null => Expr_::Null,
539         Uninit => return Err(Error::unrecoverable("value_to_expr: uninit value")),
540         Vec(_) => return Err(Error::unrecoverable("value_to_expr: vec NYI")),
541         Keyset(_) => return Err(Error::unrecoverable("value_to_expr: keyset NYI")),
542         HhasAdata(_) => return Err(Error::unrecoverable("value_to_expr: HhasAdata NYI")),
543         Dict(_) => return Err(Error::unrecoverable("value_to_expr: dict NYI")),
544     })
547 struct FolderVisitor<'a> {
548     emitter: &'a Emitter,
549     empty_namespace: &'a Namespace,
552 impl<'a> FolderVisitor<'a> {
553     fn new(emitter: &'a Emitter, empty_namespace: &'a Namespace) -> Self {
554         Self {
555             emitter,
556             empty_namespace,
557         }
558     }
561 impl<'ast> VisitorMut<'ast> for FolderVisitor<'_> {
562     type P = AstParams<(), Error>;
564     fn object(&mut self) -> &mut dyn VisitorMut<'ast, P = Self::P> {
565         self
566     }
568     fn visit_expr_(&mut self, c: &mut (), p: &mut tast::Expr_) -> Result<(), Error> {
569         p.recurse(c, self.object())?;
570         let new_p = match p {
571             tast::Expr_::Cast(e) => expr_to_typed_value(self.emitter, self.empty_namespace, &e.1)
572                 .and_then(|v| cast_value(&(e.0).1, v))
573                 .map(value_to_expr_)
574                 .ok(),
575             tast::Expr_::Unop(e) => expr_to_typed_value(self.emitter, self.empty_namespace, &e.1)
576                 .and_then(|v| unop_on_value(&e.0, v))
577                 .map(value_to_expr_)
578                 .ok(),
579             tast::Expr_::Binop(e) => expr_to_typed_value(self.emitter, self.empty_namespace, &e.1)
580                 .and_then(|v1| {
581                     expr_to_typed_value(self.emitter, self.empty_namespace, &e.2)
582                         .and_then(|v2| binop_on_values(&e.0, v1, v2).map(value_to_expr_))
583                 })
584                 .ok(),
585             _ => None,
586         };
587         if let Some(new_p) = new_p {
588             *p = new_p?
589         }
590         Ok(())
591     }
594 pub fn fold_expr(
595     expr: &mut tast::Expr,
596     e: &mut Emitter,
597     empty_namespace: &Namespace,
598 ) -> Result<(), Error> {
599     visit_mut(&mut FolderVisitor::new(e, empty_namespace), &mut (), expr)
602 pub fn fold_program(
603     p: &mut tast::Program,
604     e: &mut Emitter,
605     empty_namespace: &Namespace,
606 ) -> Result<(), Error> {
607     visit_mut(&mut FolderVisitor::new(e, empty_namespace), &mut (), p)
610 pub fn literals_from_exprs(
611     ns: &Namespace,
612     exprs: &mut [tast::Expr],
613     e: &mut Emitter,
614 ) -> Result<Vec<TypedValue>, Error> {
615     for expr in exprs.iter_mut() {
616         fold_expr(expr, e, ns)?;
617     }
618     let ret = exprs
619         .iter()
620         .map(|expr| expr_to_typed_value_(e, ns, expr, false, true))
621         .collect();
622     if let Err(Error::NotLiteral) = ret {
623         Err(Error::unrecoverable("literals_from_exprs: not literal"))
624     } else {
625         ret
626     }