1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 extern crate lazy_static;
9 use lazy_static::lazy_static;
10 use naming_special_names_rust::{classes as ns_classes, members};
16 static ref HH_NS_RE: Regex = Regex::new(r"^\\?HH\\").unwrap();
17 static ref NS_RE: Regex = Regex::new(r".*\\").unwrap();
18 static ref TYPE_RE: Regex = Regex::new(r"<.*>").unwrap();
25 unescape: fn(String) -> String,
29 pub fn new(string: Vec<u8>, unescape: fn(String) -> String) -> GetName {
30 GetName { string, unescape }
33 pub fn get(&self) -> &Vec<u8> {
36 pub fn to_string(&self) -> String {
37 String::from_utf8_lossy(&self.string).to_string()
39 pub fn to_unescaped_string(&self) -> String {
40 let unescape = self.unescape;
41 unescape(self.to_string())
45 impl std::fmt::Debug for GetName {
46 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
47 write!(f, "GetName {{ string: {}, unescape:? }}", self.to_string())
51 thread_local!(static MANGLE_XHP_MODE: Cell<bool> = Cell::new(true));
53 pub fn without_xhp_mangling<T>(f: impl FnOnce() -> T) -> T {
54 MANGLE_XHP_MODE.with(|cur| {
55 let old = cur.replace(false);
57 cur.set(old); // use old instead of true to support nested calls in the same thread
62 pub fn is_xhp(name: &str) -> bool {
63 name.chars().next().map_or(false, |c| c == ':')
66 pub fn clean(s: &str) -> &str {
67 if is_xhp(s) { strip_colon(s) } else { s }
70 fn strip_colon(s: &str) -> &str {
71 s.trim_start_matches(":")
74 fn ignore_id(name: &str) -> bool {
75 name.starts_with("Closure$")
78 pub fn mangle_xhp_id(mut name: String) -> String {
79 if !ignore_id(&name) && MANGLE_XHP_MODE.with(|x| x.get()) {
81 name.replace_range(..1, "xhp_")
83 name.replace(":", "__").replace("-", "_")
89 fn unmangle_xhp_id(name: &str) -> String {
90 if name.starts_with("xhp_") {
93 lstrip(name, "xhp_").replace("__", ":").replace("_", "-")
96 name.replace("__", ":").replace("_", "-")
100 pub fn mangle(mut name: String) -> String {
101 if !ignore_id(&name) {
102 if let Some(pos) = name.rfind('\\') {
103 name.replace_range(pos + 1.., &mangle_xhp_id(name[pos + 1..].to_string()))
105 name = mangle_xhp_id(name);
111 pub fn unmangle(name: String) -> String {
112 if ignore_id(&name) {
115 let ids = name.split("\\").collect::<Vec<_>>();
116 match ids.split_last() {
117 None => String::new(),
118 Some((last, rest)) => {
120 unmangle_xhp_id(last)
122 format!("{}\\{}", rest.join("\\"), unmangle_xhp_id(last))
128 pub fn quote_string(s: &str) -> String {
129 format!("\"{}\"", escape(s))
132 pub fn quote_string_with_escape(s: &str) -> String {
133 format!("\\\"{}\\\"", escape(s))
136 pub fn single_quote_string_with_escape(s: &str) -> String {
137 format!("'{}'", escape(s))
140 pub fn triple_quote_string(s: &str) -> String {
141 format!("\"\"\"{}\"\"\"", escape(s))
144 pub fn prefix_namespace(n: &str, s: &str) -> String {
145 format!("{}\\{}", n, s)
148 pub fn strip_global_ns(s: &str) -> &str {
149 if s.starts_with("\\") { &s[1..] } else { s }
152 // Strip zero or more chars followed by a backslash
153 pub fn strip_ns(s: &str) -> &str {
154 s.rfind('\\').map_or(s, |i| &s[i + 1..])
157 // Remove \HH\ or HH\ preceding a string
158 pub fn strip_hh_ns(s: &str) -> Cow<str> {
159 HH_NS_RE.replace(&s, "")
162 pub fn has_ns(s: &str) -> bool {
166 pub fn strip_type_list(s: &str) -> Cow<str> {
167 TYPE_RE.replace_all(&s, "")
170 pub fn cmp(s1: &str, s2: &str, case_sensitive: bool, ignore_ns: bool) -> bool {
171 fn canon(s: &str, ignore_ns: bool) -> &str {
172 if ignore_ns { strip_ns(s) } else { s }
175 let s1 = canon(s1, ignore_ns);
176 let s2 = canon(s2, ignore_ns);
181 s1.eq_ignore_ascii_case(&s2)
185 pub fn is_self(s: impl AsRef<str>) -> bool {
186 s.as_ref().eq_ignore_ascii_case(ns_classes::SELF)
189 pub fn is_parent(s: impl AsRef<str>) -> bool {
190 s.as_ref().eq_ignore_ascii_case(ns_classes::PARENT)
193 pub fn is_static(s: impl AsRef<str>) -> bool {
194 s.as_ref().eq_ignore_ascii_case(ns_classes::STATIC)
197 pub fn is_class(s: impl AsRef<str>) -> bool {
198 s.as_ref().eq_ignore_ascii_case(members::M_CLASS)
201 pub fn mangle_meth_caller(mangled_cls_name: &str, f_name: &str) -> String {
202 format!("\\MethCaller${}${}", mangled_cls_name, f_name)
205 pub fn lstrip<'a>(s: &'a str, p: &str) -> &'a str {
206 if s.len() < p.len() {
209 let sb = s.as_bytes();
210 let pb = p.as_bytes();
211 for i in 0..pb.len() {
216 // s and p are valid then unwrap should never panic.
217 std::str::from_utf8(&sb[pb.len()..]).unwrap()
222 pub fn fix_casing(s: &str) -> &str {
223 match s.to_lowercase().as_str() {
224 "vector" => "Vector",
225 "immvector" => "ImmVector",
227 "immset" => "ImmSet",
229 "immmap" => "ImmMap",
236 /* Integers are represented as strings */
238 pub fn to_decimal(s: &str) -> Option<String> {
239 /* Don't accidentally convert 0 to 0o */
240 let r = if s.len() > 1
241 && s.as_bytes()[0] == b'0'
242 && s.as_bytes()[1] >= b'0'
243 && s.as_bytes()[1] <= b'9'
245 ocaml_helper::int_of_string_wrap(format!("0o{}", &s[1..]).as_bytes())
247 ocaml_helper::int_of_string_wrap(s.as_bytes())
249 r.map(|n| n.to_string())
254 fn sprintf(f: f64) -> Option<String> {
255 const BUF_SIZE: usize = 256;
257 let format = "%.17g\0";
258 let mut buffer = [0u8; BUF_SIZE];
261 buffer.as_mut_ptr() as *mut libc::c_char,
263 format.as_ptr() as *const libc::c_char,
270 String::from_utf8(buffer[..n].to_vec()).ok()
274 pub fn to_string(f: impl Into<f64>) -> String {
276 // or_else should not happen, but just in case it does fall back
277 // to Rust native formatting
278 let res = sprintf(f).unwrap_or_else(|| f.to_string());
280 "-nan" => "NAN".to_string(),
281 "nan" => "NAN".to_string(),
282 "-inf" => "-INF".to_string(),
283 "inf" => "INF".to_string(),
290 pub fn strip_dollar(s: &str) -> &str {
291 if s.len() > 0 && s.as_bytes()[0] == b'$' {
300 pub fn mangle_class(prefix: &str, scope: &str, idx: u32) -> String {
302 format!("{}${}", prefix.to_string(), scope.to_string())
315 pub fn mangle_closure(scope: &str, idx: u32) -> String {
316 super::classes::mangle_class("Closure", scope, idx)
319 /* Closure classes have names of the form
320 * Closure$ scope ix ; num
324 * | <class-name> :: <method-name>
329 pub fn unmangle_closure(mangled_name: &str) -> Option<&str> {
330 if is_closure_name(mangled_name) {
331 let prefix_length = "Closure$".chars().count();
332 match mangled_name.find('#') {
333 Some(pos) => Some(&mangled_name[prefix_length..pos]),
334 None => Some(&mangled_name[prefix_length..]),
341 pub fn is_closure_name(s: &str) -> bool {
342 s.starts_with("Closure$")
347 pub static PROP_NAME: &'static str = "86reified_prop";
348 pub static INIT_METH_NAME: &'static str = "86reifiedinit";
349 pub static INIT_METH_PARAM_NAME: &'static str = "$__typestructures";
350 pub static GENERICS_LOCAL_NAME: &'static str = "$0ReifiedGenerics";
351 pub static CAPTURED_PREFIX: &'static str = "$__captured$reifiedgeneric$";
353 pub fn reified_generic_captured_name(is_fun: bool, i: usize) -> String {
354 let type_ = if is_fun { "function" } else { "class" };
355 // to_string() due to T52404885
356 format!("$__captured$reifiedgeneric${}${}", type_, i.to_string())
359 pub fn mangle_reified_param(no_dollar: bool, s: &str) -> String {
360 format!("{}__reified${}", if no_dollar { "" } else { "$" }, s)
363 pub fn captured_name(is_fun: bool, i: usize) -> String {
367 if is_fun { "function" } else { "class" },
372 pub fn is_captured_generic(id: &str) -> Option<(bool, u32)> {
373 if id.starts_with(CAPTURED_PREFIX) {
374 if let [name, i] = id
375 .trim_start_matches(CAPTURED_PREFIX)
380 let is_captured = match *name {
385 let captured_id = i.parse();
386 if captured_id.is_ok() {
387 return Some((is_captured, captured_id.unwrap()));
396 mod string_utils_tests {
397 use pretty_assertions::assert_eq;
400 fn quote_string_test() {
401 let some_string = "test";
402 assert_eq!(super::quote_string(&some_string), "\"test\"");
406 fn quote_string_with_escape_test() {
407 let some_string = "test";
409 super::quote_string_with_escape(&some_string),
415 fn single_quote_string_with_escape_test() {
416 let some_string = "test";
418 super::single_quote_string_with_escape(&some_string),
424 fn triple_quote_string_test() {
425 let some_string = "test";
426 assert_eq!(super::triple_quote_string(&some_string), "\"\"\"test\"\"\"");
430 fn prefix_namespace_test() {
431 let namespace = "ns";
432 let some_string = "test";
434 super::prefix_namespace(&namespace, &some_string),
440 fn strip_global_ns_test() {
441 let some_string = "\\test";
442 let another_string = "\\\\";
443 assert_eq!(super::strip_global_ns(&some_string), "test");
444 assert_eq!(super::strip_global_ns(&another_string), "\\");
449 let with_ns = "ns1\\test";
450 let without_ns = "test";
451 assert_eq!(super::strip_ns(&with_ns), "test");
452 assert_eq!(super::strip_ns(&without_ns), without_ns);
457 let with_ns = "HH\\test";
458 let without_ns = "test";
459 assert_eq!(super::strip_ns(&with_ns), "test");
460 assert_eq!(super::strip_ns(&without_ns), without_ns);
465 let with_ns = "\\HH\\test";
466 let without_ns = "test";
467 assert_eq!(super::strip_ns(&with_ns), "test");
468 assert_eq!(super::strip_ns(&without_ns), without_ns);
473 let with_ns = "\\test";
474 let without_ns = "test";
475 assert_eq!(super::has_ns(&with_ns), true);
476 assert_eq!(super::has_ns(&without_ns), false);
480 fn strip_type_list_test() {
481 let s = "MutableMap<Tk, Tv>";
482 assert_eq!(super::strip_type_list(&s).into_owned(), "MutableMap");
488 let s1_uppercase = "NS1\\S1";
490 let ns2_s1 = "ns2\\s1";
491 let ns2_s1_uppercase = "NS2\\S1";
493 let ns2_s2 = "ns2\\s2";
495 assert_eq!(true, super::cmp(&s1, &s1_uppercase, false, false));
496 assert_eq!(false, super::cmp(&s1, &s1_uppercase, true, false));
497 assert_eq!(true, super::cmp(&s1, &s1_uppercase, false, true));
498 assert_eq!(false, super::cmp(&s1, &s1_uppercase, true, true));
500 assert_eq!(false, super::cmp(&s1, &ns2_s1, false, false));
501 assert_eq!(false, super::cmp(&s1, &ns2_s1, true, false));
502 assert_eq!(true, super::cmp(&s1, &ns2_s1, false, true));
503 assert_eq!(true, super::cmp(&s1, &ns2_s1, true, true));
505 assert_eq!(false, super::cmp(&s1, &ns2_s1_uppercase, false, false));
506 assert_eq!(false, super::cmp(&s1, &ns2_s1_uppercase, true, false));
507 assert_eq!(true, super::cmp(&s1, &ns2_s1_uppercase, false, true));
508 assert_eq!(false, super::cmp(&s1, &ns2_s1_uppercase, true, true));
510 assert_eq!(false, super::cmp(&s1, &ns2_s2, false, false));
511 assert_eq!(false, super::cmp(&s1, &ns2_s2, true, false));
512 assert_eq!(false, super::cmp(&s1, &ns2_s2, false, true));
513 assert_eq!(false, super::cmp(&s1, &ns2_s2, true, true));
521 assert_eq!(super::is_self(&s1), true);
522 assert_eq!(super::is_self(&s2), false);
526 fn is_parent_test() {
528 let s2 = "not_parent";
530 assert_eq!(super::is_parent(&s1), true);
531 assert_eq!(super::is_parent(&s2), false);
535 fn is_static_test() {
537 let s2 = "not_static";
539 assert_eq!(super::is_static(&s1), true);
540 assert_eq!(super::is_static(&s2), false);
546 let s2 = "not_a_class";
548 assert_eq!(super::is_class(&s1), true);
549 assert_eq!(super::is_class(&s2), false);
553 fn mangle_meth_caller_test() {
554 let cls = "SomeClass";
555 let f = "some_function";
558 super::mangle_meth_caller(cls, f),
559 "\\MethCaller$SomeClass$some_function"
566 super::mangle(":foo:bar-and-baz".into()),
567 "xhp_foo__bar_and_baz"
573 assert_eq!(super::mangle("\\:base".into()), "\\xhp_base");
579 super::mangle("\\NS1\\NS2\\:base".into()),
580 "\\NS1\\NS2\\xhp_base"
586 macro_rules! test_case {
587 ($name: ident, $input: expr, $expected: expr) => {
590 assert_eq!(crate::types::fix_casing($input), $expected);
595 test_case!(lowercase_vector, "vector", "Vector");
596 test_case!(mixedcase_vector, "vEcTor", "Vector");
597 test_case!(uppercase_vector, "VECTOR", "Vector");
599 test_case!(lowercase_immvector, "immvector", "ImmVector");
600 test_case!(mixedcase_immvector, "immvEcTor", "ImmVector");
601 test_case!(uppercase_immvector, "IMMVECTOR", "ImmVector");
603 test_case!(lowercase_set, "set", "Set");
604 test_case!(mixedcase_set, "SeT", "Set");
605 test_case!(uppercase_set, "SET", "Set");
607 test_case!(lowercase_immset, "immset", "ImmSet");
608 test_case!(mixedcase_immset, "ImMSeT", "ImmSet");
609 test_case!(uppercase_immset, "IMMSET", "ImmSet");
611 test_case!(lowercase_map, "map", "Map");
612 test_case!(mixedcase_map, "MaP", "Map");
613 test_case!(uppercase_map, "MAP", "Map");
615 test_case!(lowercase_immmap, "immmap", "ImmMap");
616 test_case!(mixedcase_immmap, "immMaP", "ImmMap");
617 test_case!(uppercase_immmap, "IMMMAP", "ImmMap");
619 test_case!(lowercase_pair, "pair", "Pair");
620 test_case!(mixedcase_pair, "pAiR", "Pair");
621 test_case!(uppercase_pair, "PAIR", "Pair");
624 non_hack_collection_returns_original_string,
629 hack_collection_with_leading_whitespace_returns_original_string,
634 hack_collection_with_trailing_whitespace_returns_original_string,
644 fn test_no_float_part() {
645 assert_eq!(to_string(1.0), "1")
649 fn test_precision() {
650 assert_eq!(to_string(1.1), "1.1000000000000001")
654 fn test_no_trailing_zeroes() {
655 assert_eq!(to_string(1.2), "1.2")
659 fn test_scientific() {
660 assert_eq!(to_string(1e+100), "1e+100")
664 fn test_scientific_precision() {
665 assert_eq!(to_string(-2.1474836480001e9), "-2147483648.0001001")
669 fn test_negative_nan() {
670 assert_eq!(to_string(-std::f32::NAN), "NAN")
676 use crate::integer::*;
680 assert_eq!(to_decimal("0"), Some("0".to_string()));
685 assert_eq!(to_decimal("00"), Some("0".to_string()));
689 fn binary_zero_lowercase() {
690 assert_eq!(to_decimal("0b0"), Some("0".to_string()));
694 fn binary_zero_uppercase() {
695 assert_eq!(to_decimal("0B0"), Some("0".to_string()));
699 fn hex_zero_lowercase() {
700 assert_eq!(to_decimal("0x0"), Some("0".to_string()));
704 fn hex_zero_uppercase() {
705 assert_eq!(to_decimal("0X0"), Some("0".to_string()));
709 fn decimal_random_value() {
710 assert_eq!(to_decimal("1245"), Some("1245".to_string()));
714 fn octal_random_value() {
715 assert_eq!(to_decimal("02335"), Some("1245".to_string()));
719 fn binary_random_value_lowercase() {
720 assert_eq!(to_decimal("0b10011011101"), Some("1245".to_string()));
724 fn binary_random_value_uppercase() {
725 assert_eq!(to_decimal("0B10011011101"), Some("1245".to_string()));
729 fn hex_random_value_lowercase() {
730 assert_eq!(to_decimal("0x4DD"), Some("1245".to_string()));
734 fn hex_random_value_uppercase() {
735 assert_eq!(to_decimal("0X4DD"), Some("1245".to_string()));
739 fn decimal_max_value() {
741 to_decimal("9223372036854775807"),
742 Some("9223372036854775807".to_string())
747 fn octal_max_value() {
749 to_decimal("0777777777777777777777"),
750 Some("9223372036854775807".to_string())
755 fn binary_max_value_lowercase() {
757 to_decimal("0b111111111111111111111111111111111111111111111111111111111111111"),
758 Some("9223372036854775807".to_string())
763 fn binary_max_value_uppercase() {
765 to_decimal("0B111111111111111111111111111111111111111111111111111111111111111"),
766 Some("9223372036854775807".to_string())
771 fn hex_max_value_lowercase() {
773 to_decimal("0x7FFFFFFFFFFFFFFF"),
774 Some("9223372036854775807".to_string())
779 fn hex_max_value_uppercase() {
781 to_decimal("0X7FFFFFFFFFFFFFFF"),
782 Some("9223372036854775807".to_string())
787 fn unparsable_string() {
788 assert!(to_decimal("bad_string").is_none());
794 use crate::locals::*;
797 fn strip_single_leading_dollar() {
798 assert_eq!(strip_dollar("$foo"), "foo");
802 fn return_string_if_no_leading_dollar() {
803 assert_eq!(strip_dollar("foo"), "foo");
808 assert_eq!(strip_dollar(""), "");
812 fn string_of_single_dollar() {
813 assert_eq!(strip_dollar("$"), "");
819 use crate::classes::mangle_class;
823 assert_eq!(mangle_class("foo", "bar", 1), "foo$bar")
828 assert_eq!(mangle_class("foo", "bar", 2), "foo$bar#2")
835 use crate::closures::mangle_closure;
839 assert_eq!(mangle_closure("foo", 1), "Closure$foo")
844 assert_eq!(mangle_closure("foo", 2), "Closure$foo#2")
848 mod unmangle_closure {
849 use crate::closures::unmangle_closure;
853 assert_eq!(unmangle_closure("Closure$foo"), Some("foo"))
858 assert_eq!(unmangle_closure("Closure$foo#2"), Some("foo"))
863 assert_eq!(unmangle_closure("SomePrefix$foo"), None);
864 assert_eq!(unmangle_closure("SomePrefix$foo#2"), None)
868 mod is_closure_name {
869 use crate::closures::is_closure_name;
873 assert_eq!(is_closure_name("Closure$foo"), true)
878 assert_eq!(is_closure_name("Closure$foo#2"), true)
883 assert_eq!(is_closure_name("SomePrefix$foo"), false);
884 assert_eq!(is_closure_name("SomePrefix$foo#2"), false)
890 use crate::reified::*;
893 fn test_mangle_reified_param() {
894 assert_eq!(mangle_reified_param(false, "x"), "$__reified$x");
895 assert_eq!(mangle_reified_param(true, "x"), "__reified$x")
899 fn test_is_captured_generic() {
901 is_captured_generic("$__captured$reifiedgeneric$function$1"),
905 is_captured_generic("$__captured$reifiedgeneric$class$1"),
908 assert_eq!(is_captured_generic("function$1"), None);
910 is_captured_generic("$__captured$reifiedgeneric$function1"),
916 fn test_captured_name() {
918 captured_name(true, 1),
919 "$__captured$reifiedgeneric$function$1"
922 captured_name(false, 1),
923 "$__captured$reifiedgeneric$class$1"
931 assert_eq!(lstrip("a", "a"), "");
932 assert_eq!(lstrip("a", "ab"), "a");
933 assert_eq!(lstrip("", "ab"), "");
934 assert_eq!(lstrip("", ""), "");
935 assert_eq!(lstrip("a", ""), "a");
936 assert_eq!(lstrip("aa", "a"), "a");
937 assert_eq!(lstrip("aa", "a"), "a");