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.
7 use lazy_static::lazy_static;
8 use naming_special_names_rust::{classes as ns_classes, members};
14 static ref HH_NS_RE: Regex = Regex::new(r"^\\?HH\\").unwrap();
15 static ref NS_RE: Regex = Regex::new(r".*\\").unwrap();
16 static ref TYPE_RE: Regex = Regex::new(r"<.*>").unwrap();
23 unescape: fn(String) -> String,
27 pub fn new(string: Vec<u8>, unescape: fn(String) -> String) -> GetName {
28 GetName { string, unescape }
31 pub fn get(&self) -> &Vec<u8> {
34 #[allow(clippy::inherent_to_string)]
35 pub fn to_string(&self) -> String {
36 String::from_utf8_lossy(&self.string).to_string()
38 pub fn to_unescaped_string(&self) -> String {
39 let unescape = self.unescape;
40 unescape(self.to_string())
44 impl std::fmt::Debug for GetName {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "GetName {{ string: {}, unescape:? }}", self.to_string())
50 thread_local!(static MANGLE_XHP_MODE: Cell<bool> = Cell::new(true));
52 pub fn without_xhp_mangling<T>(f: impl FnOnce() -> T) -> T {
53 MANGLE_XHP_MODE.with(|cur| {
54 let old = cur.replace(false);
56 cur.set(old); // use old instead of true to support nested calls in the same thread
61 pub fn is_xhp(name: &str) -> bool {
62 name.chars().next().map_or(false, |c| c == ':')
65 pub fn clean(s: &str) -> &str {
66 if is_xhp(s) { strip_colon(s) } else { s }
69 fn strip_colon(s: &str) -> &str {
70 s.trim_start_matches(':')
73 fn ignore_id(name: &str) -> bool {
74 name.starts_with("Closure$")
77 pub fn mangle_xhp_id(mut name: String) -> String {
78 if !ignore_id(&name) && MANGLE_XHP_MODE.with(|x| x.get()) {
80 name.replace_range(..1, "xhp_")
82 name.replace(":", "__").replace("-", "_")
88 fn unmangle_xhp_id(name: &str) -> String {
89 if name.starts_with("xhp_") {
92 lstrip(name, "xhp_").replace("__", ":").replace("_", "-")
95 name.replace("__", ":").replace("_", "-")
99 pub fn mangle(mut name: String) -> String {
100 if !ignore_id(&name) {
101 if let Some(pos) = name.rfind('\\') {
102 name.replace_range(pos + 1.., &mangle_xhp_id(name[pos + 1..].to_string()))
104 name = mangle_xhp_id(name);
110 pub fn unmangle(name: String) -> String {
111 if ignore_id(&name) {
114 let ids = name.split('\\').collect::<Vec<_>>();
115 match ids.split_last() {
116 None => String::new(),
117 Some((last, rest)) => {
119 unmangle_xhp_id(last)
121 format!("{}\\{}", rest.join("\\"), unmangle_xhp_id(last))
127 pub fn quote_string(s: &str) -> String {
128 format!("\"{}\"", escape(s))
131 pub fn quote_string_with_escape(s: &str) -> String {
132 format!("\\\"{}\\\"", escape(s))
135 pub fn single_quote_string_with_escape(s: &str) -> String {
136 format!("'{}'", escape(s))
139 pub fn triple_quote_string(s: &str) -> String {
140 format!("\"\"\"{}\"\"\"", escape(s))
143 pub fn prefix_namespace(n: &str, s: &str) -> String {
144 format!("{}\\{}", n, s)
147 pub fn strip_global_ns(s: &str) -> &str {
148 if let Some(rest) = s.strip_prefix('\\') {
155 // Strip zero or more chars followed by a backslash
156 pub fn strip_ns(s: &str) -> &str {
157 s.rfind('\\').map_or(s, |i| &s[i + 1..])
160 // Remove \HH\ or HH\ preceding a string
161 pub fn strip_hh_ns(s: &str) -> Cow<'_, str> {
162 HH_NS_RE.replace(s, "")
165 pub fn has_ns(s: &str) -> bool {
169 pub fn strip_type_list(s: &str) -> Cow<'_, str> {
170 TYPE_RE.replace_all(s, "")
173 pub fn cmp(s1: &str, s2: &str, case_sensitive: bool, ignore_ns: bool) -> bool {
174 fn canon(s: &str, ignore_ns: bool) -> &str {
175 if ignore_ns { strip_ns(s) } else { s }
178 let s1 = canon(s1, ignore_ns);
179 let s2 = canon(s2, ignore_ns);
184 s1.eq_ignore_ascii_case(s2)
188 pub fn is_self(s: impl AsRef<str>) -> bool {
189 s.as_ref().eq_ignore_ascii_case(ns_classes::SELF)
192 pub fn is_parent(s: impl AsRef<str>) -> bool {
193 s.as_ref().eq_ignore_ascii_case(ns_classes::PARENT)
196 pub fn is_static(s: impl AsRef<str>) -> bool {
197 s.as_ref().eq_ignore_ascii_case(ns_classes::STATIC)
200 pub fn is_class(s: impl AsRef<str>) -> bool {
201 s.as_ref().eq_ignore_ascii_case(members::M_CLASS)
204 pub fn mangle_meth_caller(mangled_cls_name: &str, f_name: &str) -> String {
205 format!("\\MethCaller${}${}", mangled_cls_name, f_name)
208 pub fn lstrip<'a>(s: &'a str, p: &str) -> &'a str {
209 if s.len() < p.len() {
212 let sb = s.as_bytes();
213 let pb = p.as_bytes();
214 for i in 0..pb.len() {
219 // s and p are valid then unwrap should never panic.
220 std::str::from_utf8(&sb[pb.len()..]).unwrap()
225 pub fn fix_casing(s: &str) -> &str {
226 match s.to_lowercase().as_str() {
227 "vector" => "Vector",
228 "immvector" => "ImmVector",
230 "immset" => "ImmSet",
232 "immmap" => "ImmMap",
239 /* Integers are represented as strings */
241 pub fn to_decimal(s: &str) -> Result<String, ocaml_helper::ParseIntError> {
242 /* Don't accidentally convert 0 to 0o */
243 let r = if s.len() > 1
244 && s.as_bytes()[0] == b'0'
245 && s.as_bytes()[1] >= b'0'
246 && s.as_bytes()[1] <= b'9'
248 ocaml_helper::int_of_string_wrap(format!("0o{}", &s[1..]).as_bytes())
250 ocaml_helper::int_of_string_wrap(s.as_bytes())
252 r.map(|n| n.to_string())
257 fn sprintf(f: f64) -> Option<String> {
258 const BUF_SIZE: usize = 256;
260 let format = "%.17g\0";
261 let mut buffer = [0u8; BUF_SIZE];
264 buffer.as_mut_ptr() as *mut libc::c_char,
266 format.as_ptr() as *const libc::c_char,
273 String::from_utf8(buffer[..n].to_vec()).ok()
277 pub fn to_string(f: impl Into<f64>) -> String {
279 // or_else should not happen, but just in case it does fall back
280 // to Rust native formatting
281 let res = sprintf(f).unwrap_or_else(|| f.to_string());
283 "-nan" => "NAN".to_string(),
284 "nan" => "NAN".to_string(),
285 "-inf" => "-INF".to_string(),
286 "inf" => "INF".to_string(),
293 pub fn strip_dollar(s: &str) -> &str {
294 if !s.is_empty() && s.as_bytes()[0] == b'$' {
303 pub fn mangle_class(prefix: &str, scope: &str, idx: u32) -> String {
305 format!("{}${}", prefix.to_string(), scope.to_string())
318 pub fn mangle_closure(scope: &str, idx: u32) -> String {
319 super::classes::mangle_class("Closure", scope, idx)
322 /* Closure classes have names of the form
323 * Closure$ scope ix ; num
327 * | <class-name> :: <method-name>
332 pub fn unmangle_closure(mangled_name: &str) -> Option<&str> {
333 if is_closure_name(mangled_name) {
334 let prefix_length = "Closure$".chars().count();
335 match mangled_name.find('#') {
336 Some(pos) => Some(&mangled_name[prefix_length..pos]),
337 None => Some(&mangled_name[prefix_length..]),
344 pub fn is_closure_name(s: &str) -> bool {
345 s.starts_with("Closure$")
350 #[allow(clippy::redundant_static_lifetimes)]
351 pub static PROP_NAME: &'static str = "86reified_prop";
352 #[allow(clippy::redundant_static_lifetimes)]
353 pub static INIT_METH_NAME: &'static str = "86reifiedinit";
354 #[allow(clippy::redundant_static_lifetimes)]
355 pub static INIT_METH_PARAM_NAME: &'static str = "$__typestructures";
356 #[allow(clippy::redundant_static_lifetimes)]
357 pub static GENERICS_LOCAL_NAME: &'static str = "$0ReifiedGenerics";
358 #[allow(clippy::redundant_static_lifetimes)]
359 pub static CAPTURED_PREFIX: &'static str = "$__captured$reifiedgeneric$";
361 pub fn reified_generic_captured_name(is_fun: bool, i: usize) -> String {
362 let type_ = if is_fun { "function" } else { "class" };
363 // to_string() due to T52404885
364 format!("$__captured$reifiedgeneric${}${}", type_, i.to_string())
367 pub fn mangle_reified_param(no_dollar: bool, s: &str) -> String {
368 format!("{}__reified${}", if no_dollar { "" } else { "$" }, s)
371 pub fn captured_name(is_fun: bool, i: usize) -> String {
375 if is_fun { "function" } else { "class" },
380 pub fn is_captured_generic(id: &str) -> Option<(bool, u32)> {
381 if id.starts_with(CAPTURED_PREFIX) {
382 if let [name, i] = id
383 .trim_start_matches(CAPTURED_PREFIX)
388 let is_captured = match *name {
393 let captured_id = i.parse();
394 if let Ok(captured) = captured_id {
395 return Some((is_captured, captured));
404 #[allow(clippy::redundant_static_lifetimes)]
405 pub static LOCAL_NAME: &'static str = "$0Coeffects";
406 #[allow(clippy::redundant_static_lifetimes)]
407 pub static CALLER: &'static str = "86caller";
411 mod string_utils_tests {
412 use pretty_assertions::assert_eq;
415 fn quote_string_test() {
416 let some_string = "test";
417 assert_eq!(super::quote_string(some_string), "\"test\"");
421 fn quote_string_with_escape_test() {
422 let some_string = "test";
423 assert_eq!(super::quote_string_with_escape(some_string), "\\\"test\\\"");
427 fn single_quote_string_with_escape_test() {
428 let some_string = "test";
430 super::single_quote_string_with_escape(some_string),
436 fn triple_quote_string_test() {
437 let some_string = "test";
438 assert_eq!(super::triple_quote_string(some_string), "\"\"\"test\"\"\"");
442 fn prefix_namespace_test() {
443 let namespace = "ns";
444 let some_string = "test";
445 assert_eq!(super::prefix_namespace(namespace, some_string), "ns\\test");
449 fn strip_global_ns_test() {
450 let some_string = "\\test";
451 let another_string = "\\\\";
452 assert_eq!(super::strip_global_ns(some_string), "test");
453 assert_eq!(super::strip_global_ns(another_string), "\\");
458 let with_ns = "ns1\\test";
459 let without_ns = "test";
460 assert_eq!(super::strip_ns(with_ns), "test");
461 assert_eq!(super::strip_ns(without_ns), without_ns);
466 let with_ns = "HH\\test";
467 let without_ns = "test";
468 assert_eq!(super::strip_ns(with_ns), "test");
469 assert_eq!(super::strip_ns(without_ns), without_ns);
474 let with_ns = "\\HH\\test";
475 let without_ns = "test";
476 assert_eq!(super::strip_ns(with_ns), "test");
477 assert_eq!(super::strip_ns(without_ns), without_ns);
482 let with_ns = "\\test";
483 let without_ns = "test";
484 assert!(super::has_ns(with_ns));
485 assert!(!super::has_ns(without_ns));
489 fn strip_type_list_test() {
490 let s = "MutableMap<Tk, Tv>";
491 assert_eq!(super::strip_type_list(s).into_owned(), "MutableMap");
497 let s1_uppercase = "NS1\\S1";
499 let ns2_s1 = "ns2\\s1";
500 let ns2_s1_uppercase = "NS2\\S1";
502 let ns2_s2 = "ns2\\s2";
504 assert!(super::cmp(s1, s1_uppercase, false, false));
505 assert!(!super::cmp(s1, s1_uppercase, true, false));
506 assert!(super::cmp(s1, s1_uppercase, false, true));
507 assert!(!super::cmp(s1, s1_uppercase, true, true));
509 assert!(!super::cmp(s1, ns2_s1, false, false));
510 assert!(!super::cmp(s1, ns2_s1, true, false));
511 assert!(super::cmp(s1, ns2_s1, false, true));
512 assert!(super::cmp(s1, ns2_s1, true, true));
514 assert!(!super::cmp(s1, ns2_s1_uppercase, false, false));
515 assert!(!super::cmp(s1, ns2_s1_uppercase, true, false));
516 assert!(super::cmp(s1, ns2_s1_uppercase, false, true));
517 assert!(!super::cmp(s1, ns2_s1_uppercase, true, true));
519 assert!(!super::cmp(s1, ns2_s2, false, false));
520 assert!(!super::cmp(s1, ns2_s2, true, false));
521 assert!(!super::cmp(s1, ns2_s2, false, true));
522 assert!(!super::cmp(s1, ns2_s2, true, true));
530 assert!(super::is_self(s1));
531 assert!(!super::is_self(s2));
535 fn is_parent_test() {
537 let s2 = "not_parent";
539 assert!(super::is_parent(s1));
540 assert!(!super::is_parent(s2));
544 fn is_static_test() {
546 let s2 = "not_static";
548 assert!(super::is_static(s1));
549 assert!(!super::is_static(s2));
555 let s2 = "not_a_class";
557 assert!(super::is_class(s1));
558 assert!(!super::is_class(s2));
562 fn mangle_meth_caller_test() {
563 let cls = "SomeClass";
564 let f = "some_function";
567 super::mangle_meth_caller(cls, f),
568 "\\MethCaller$SomeClass$some_function"
575 super::mangle(":foo:bar-and-baz".into()),
576 "xhp_foo__bar_and_baz"
582 assert_eq!(super::mangle("\\:base".into()), "\\xhp_base");
588 super::mangle("\\NS1\\NS2\\:base".into()),
589 "\\NS1\\NS2\\xhp_base"
595 macro_rules! test_case {
596 ($name: ident, $input: expr, $expected: expr) => {
599 assert_eq!(crate::types::fix_casing($input), $expected);
604 test_case!(lowercase_vector, "vector", "Vector");
605 test_case!(mixedcase_vector, "vEcTor", "Vector");
606 test_case!(uppercase_vector, "VECTOR", "Vector");
608 test_case!(lowercase_immvector, "immvector", "ImmVector");
609 test_case!(mixedcase_immvector, "immvEcTor", "ImmVector");
610 test_case!(uppercase_immvector, "IMMVECTOR", "ImmVector");
612 test_case!(lowercase_set, "set", "Set");
613 test_case!(mixedcase_set, "SeT", "Set");
614 test_case!(uppercase_set, "SET", "Set");
616 test_case!(lowercase_immset, "immset", "ImmSet");
617 test_case!(mixedcase_immset, "ImMSeT", "ImmSet");
618 test_case!(uppercase_immset, "IMMSET", "ImmSet");
620 test_case!(lowercase_map, "map", "Map");
621 test_case!(mixedcase_map, "MaP", "Map");
622 test_case!(uppercase_map, "MAP", "Map");
624 test_case!(lowercase_immmap, "immmap", "ImmMap");
625 test_case!(mixedcase_immmap, "immMaP", "ImmMap");
626 test_case!(uppercase_immmap, "IMMMAP", "ImmMap");
628 test_case!(lowercase_pair, "pair", "Pair");
629 test_case!(mixedcase_pair, "pAiR", "Pair");
630 test_case!(uppercase_pair, "PAIR", "Pair");
633 non_hack_collection_returns_original_string,
638 hack_collection_with_leading_whitespace_returns_original_string,
643 hack_collection_with_trailing_whitespace_returns_original_string,
653 fn test_no_float_part() {
654 assert_eq!(to_string(1.0), "1")
658 fn test_precision() {
659 assert_eq!(to_string(1.1), "1.1000000000000001")
663 fn test_no_trailing_zeroes() {
664 assert_eq!(to_string(1.2), "1.2")
668 fn test_scientific() {
669 assert_eq!(to_string(1e+100), "1e+100")
673 fn test_scientific_precision() {
674 assert_eq!(to_string(-2.1474836480001e9), "-2147483648.0001001")
678 fn test_negative_nan() {
679 assert_eq!(to_string(-std::f32::NAN), "NAN")
685 use crate::integer::*;
689 assert_eq!(to_decimal("0"), Ok("0".to_string()));
694 assert_eq!(to_decimal("00"), Ok("0".to_string()));
698 fn binary_zero_lowercase() {
699 assert_eq!(to_decimal("0b0"), Ok("0".to_string()));
703 fn binary_zero_uppercase() {
704 assert_eq!(to_decimal("0B0"), Ok("0".to_string()));
708 fn hex_zero_lowercase() {
709 assert_eq!(to_decimal("0x0"), Ok("0".to_string()));
713 fn hex_zero_uppercase() {
714 assert_eq!(to_decimal("0X0"), Ok("0".to_string()));
718 fn decimal_random_value() {
719 assert_eq!(to_decimal("1245"), Ok("1245".to_string()));
723 fn octal_random_value() {
724 assert_eq!(to_decimal("02335"), Ok("1245".to_string()));
728 fn binary_random_value_lowercase() {
729 assert_eq!(to_decimal("0b10011011101"), Ok("1245".to_string()));
733 fn binary_random_value_uppercase() {
734 assert_eq!(to_decimal("0B10011011101"), Ok("1245".to_string()));
738 fn hex_random_value_lowercase() {
739 assert_eq!(to_decimal("0x4DD"), Ok("1245".to_string()));
743 fn hex_random_value_uppercase() {
744 assert_eq!(to_decimal("0X4DD"), Ok("1245".to_string()));
748 fn decimal_max_value() {
750 to_decimal("9223372036854775807"),
751 Ok("9223372036854775807".to_string())
756 fn octal_max_value() {
758 to_decimal("0777777777777777777777"),
759 Ok("9223372036854775807".to_string())
764 fn binary_max_value_lowercase() {
766 to_decimal("0b111111111111111111111111111111111111111111111111111111111111111"),
767 Ok("9223372036854775807".to_string())
772 fn binary_max_value_uppercase() {
774 to_decimal("0B111111111111111111111111111111111111111111111111111111111111111"),
775 Ok("9223372036854775807".to_string())
780 fn hex_max_value_lowercase() {
782 to_decimal("0x7FFFFFFFFFFFFFFF"),
783 Ok("9223372036854775807".to_string())
788 fn hex_max_value_uppercase() {
790 to_decimal("0X7FFFFFFFFFFFFFFF"),
791 Ok("9223372036854775807".to_string())
796 fn unparsable_string() {
797 assert!(to_decimal("bad_string").is_err());
803 use crate::locals::*;
806 fn strip_single_leading_dollar() {
807 assert_eq!(strip_dollar("$foo"), "foo");
811 fn return_string_if_no_leading_dollar() {
812 assert_eq!(strip_dollar("foo"), "foo");
817 assert_eq!(strip_dollar(""), "");
821 fn string_of_single_dollar() {
822 assert_eq!(strip_dollar("$"), "");
828 use crate::classes::mangle_class;
832 assert_eq!(mangle_class("foo", "bar", 1), "foo$bar")
837 assert_eq!(mangle_class("foo", "bar", 2), "foo$bar#2")
844 use crate::closures::mangle_closure;
848 assert_eq!(mangle_closure("foo", 1), "Closure$foo")
853 assert_eq!(mangle_closure("foo", 2), "Closure$foo#2")
857 mod unmangle_closure {
858 use crate::closures::unmangle_closure;
862 assert_eq!(unmangle_closure("Closure$foo"), Some("foo"))
867 assert_eq!(unmangle_closure("Closure$foo#2"), Some("foo"))
872 assert_eq!(unmangle_closure("SomePrefix$foo"), None);
873 assert_eq!(unmangle_closure("SomePrefix$foo#2"), None)
877 mod is_closure_name {
878 use crate::closures::is_closure_name;
882 assert_eq!(is_closure_name("Closure$foo"), true)
887 assert_eq!(is_closure_name("Closure$foo#2"), true)
892 assert_eq!(is_closure_name("SomePrefix$foo"), false);
893 assert_eq!(is_closure_name("SomePrefix$foo#2"), false)
899 use crate::reified::*;
902 fn test_mangle_reified_param() {
903 assert_eq!(mangle_reified_param(false, "x"), "$__reified$x");
904 assert_eq!(mangle_reified_param(true, "x"), "__reified$x")
908 fn test_is_captured_generic() {
910 is_captured_generic("$__captured$reifiedgeneric$function$1"),
914 is_captured_generic("$__captured$reifiedgeneric$class$1"),
917 assert_eq!(is_captured_generic("function$1"), None);
919 is_captured_generic("$__captured$reifiedgeneric$function1"),
925 fn test_captured_name() {
927 captured_name(true, 1),
928 "$__captured$reifiedgeneric$function$1"
931 captured_name(false, 1),
932 "$__captured$reifiedgeneric$class$1"
940 assert_eq!(lstrip("a", "a"), "");
941 assert_eq!(lstrip("a", "ab"), "a");
942 assert_eq!(lstrip("", "ab"), "");
943 assert_eq!(lstrip("", ""), "");
944 assert_eq!(lstrip("a", ""), "a");
945 assert_eq!(lstrip("aa", "a"), "a");
946 assert_eq!(lstrip("aa", "a"), "a");