From 023fc05d533c5211e289090d6fbe7e5ef3d0e3d0 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 27 Jul 2021 18:21:22 +0300 Subject: [PATCH 1/7] v0: split existing const generics feature tests to more clearly reflect history. --- src/v0.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/v0.rs b/src/v0.rs index 9bb9a43..e082ac9 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1095,13 +1095,17 @@ mod tests { } #[test] - fn demangle_const_generics() { + fn demangle_const_generics_preview() { // NOTE(eddyb) this was hand-written, before rustc had working // const generics support (but the mangling format did include them). t_nohash_type!( "INtC8arrayvec8ArrayVechKj7b_E", "arrayvec::ArrayVec" ); + } + + #[test] + fn demangle_min_const_generics() { t_nohash!( "_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E", ">" From ee8d0f03c407a4409fb05266ca3863363055ad55 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 27 Jul 2021 18:36:46 +0300 Subject: [PATCH 2/7] v0: use a `t_const!` macro for const generics tests. --- src/v0.rs | 68 ++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index e082ac9..9605859 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1050,6 +1050,11 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { mod tests { use std::prelude::v1::*; + macro_rules! t { + ($a:expr, $b:expr) => {{ + assert_eq!(format!("{}", ::demangle($a)), $b); + }}; + } macro_rules! t_nohash { ($a:expr, $b:expr) => {{ assert_eq!(format!("{:#}", ::demangle($a)), $b); @@ -1060,6 +1065,23 @@ mod tests { t_nohash!(concat!("_RMC0", $a), concat!("<", $b, ">")) }; } + macro_rules! t_const { + ($mangled:expr, $value:expr) => { + t_nohash!( + concat!("_RIC0K", $mangled, "E"), + concat!("::<", $value, ">") + ) + }; + } + macro_rules! t_const_typed { + ($mangled:expr, $value:expr, $value_ty:expr) => {{ + t_const!($mangled, $value); + t!( + concat!("_RIC0K", $mangled, "E"), + concat!("[0]::<", $value, ": ", $value_ty, ">") + ); + }}; + } #[test] fn demangle_crate_with_leading_digit() { @@ -1102,46 +1124,20 @@ mod tests { "INtC8arrayvec8ArrayVechKj7b_E", "arrayvec::ArrayVec" ); + t_const_typed!("j7b_", "123", "usize"); } #[test] fn demangle_min_const_generics() { - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_8UnsignedKhb_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKs98_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_6SignedKanb_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb0_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_4BoolKb1_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc76_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKca_E", - ">" - ); - t_nohash!( - "_RMCs4fqI2P2rA04_13const_genericINtB0_4CharKc2202_E", - ">" - ); - t_nohash!( - "_RNvNvMCs4fqI2P2rA04_13const_genericINtB4_3FooKpE3foo3FOO", - ">::foo::FOO" - ); + t_const!("p", "_"); + t_const_typed!("hb_", "11", "u8"); + t_const_typed!("s98_", "152", "i16"); + t_const_typed!("anb_", "-11", "i8"); + t_const_typed!("b0_", "false", "bool"); + t_const_typed!("b1_", "true", "bool"); + t_const_typed!("c76_", "'v'", "char"); + t_const_typed!("ca_", "'\\n'", "char"); + t_const_typed!("c2202_", "'∂'", "char"); } #[test] From 9d8a7d89aa30b8830ab0c634bd75e30584d87a0b Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 27 Jul 2021 18:49:09 +0300 Subject: [PATCH 3/7] v0: only add types to consts as suffixes on integer literals. --- src/v0.rs | 90 +++++++++++++++++++++++++------------------------------ 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 9605859..24b5373 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -946,73 +946,64 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { } fn print_const(&mut self) -> fmt::Result { - parse!(self, push_depth); - - if self.eat(b'B') { - self.print_backref(Self::print_const)?; - - self.pop_depth(); - return Ok(()); - } - - let ty_tag = parse!(self, next); - - if ty_tag == b'p' { - // We don't encode the type if the value is a placeholder. - self.print("_")?; + let tag = parse!(self, next); - self.pop_depth(); - return Ok(()); - } + parse!(self, push_depth); - match ty_tag { + match tag { + // Placeholder. + b'p' => self.print("_")?, // Unsigned integer types. - b'h' | b't' | b'm' | b'y' | b'o' | b'j' => self.print_const_uint()?, + b'h' | b't' | b'm' | b'y' | b'o' | b'j' => self.print_const_uint(tag)?, // Signed integer types. - b'a' | b's' | b'l' | b'x' | b'n' | b'i' => self.print_const_int()?, + b'a' | b's' | b'l' | b'x' | b'n' | b'i' => self.print_const_int(tag)?, // Bool. b'b' => self.print_const_bool()?, // Char. b'c' => self.print_const_char()?, - // This branch ought to be unreachable. + b'B' => { + self.print_backref(Self::print_const)?; + } + _ => invalid!(self), }; - if let Some(out) = &mut self.out { - if !out.alternate() { - self.print(": ")?; - let ty = basic_type(ty_tag).unwrap(); - self.print(ty)?; - } - } - self.pop_depth(); Ok(()) } - fn print_const_uint(&mut self) -> fmt::Result { + fn print_const_uint(&mut self, ty_tag: u8) -> fmt::Result { let hex = parse!(self, hex_nibbles); // Print anything that doesn't fit in `u64` verbatim. if hex.len() > 16 { self.print("0x")?; - return self.print(hex); + self.print(hex)?; + } else { + let mut v = 0; + for c in hex.chars() { + v = (v << 4) | (c.to_digit(16).unwrap() as u64); + } + self.print(v)?; } - let mut v = 0; - for c in hex.chars() { - v = (v << 4) | (c.to_digit(16).unwrap() as u64); + if let Some(out) = &mut self.out { + if !out.alternate() { + let ty = basic_type(ty_tag).unwrap(); + self.print(ty)?; + } } - self.print(v) + + Ok(()) } - fn print_const_int(&mut self) -> fmt::Result { + fn print_const_int(&mut self, ty_tag: u8) -> fmt::Result { if self.eat(b'n') { self.print("-")?; } - self.print_const_uint() + self.print_const_uint(ty_tag) } fn print_const_bool(&mut self) -> fmt::Result { @@ -1073,12 +1064,12 @@ mod tests { ) }; } - macro_rules! t_const_typed { - ($mangled:expr, $value:expr, $value_ty:expr) => {{ + macro_rules! t_const_suffixed { + ($mangled:expr, $value:expr, $value_ty_suffix:expr) => {{ t_const!($mangled, $value); t!( concat!("_RIC0K", $mangled, "E"), - concat!("[0]::<", $value, ": ", $value_ty, ">") + concat!("[0]::<", $value, $value_ty_suffix, ">") ); }}; } @@ -1124,20 +1115,21 @@ mod tests { "INtC8arrayvec8ArrayVechKj7b_E", "arrayvec::ArrayVec" ); - t_const_typed!("j7b_", "123", "usize"); + t_const_suffixed!("j7b_", "123", "usize"); } #[test] fn demangle_min_const_generics() { t_const!("p", "_"); - t_const_typed!("hb_", "11", "u8"); - t_const_typed!("s98_", "152", "i16"); - t_const_typed!("anb_", "-11", "i8"); - t_const_typed!("b0_", "false", "bool"); - t_const_typed!("b1_", "true", "bool"); - t_const_typed!("c76_", "'v'", "char"); - t_const_typed!("ca_", "'\\n'", "char"); - t_const_typed!("c2202_", "'∂'", "char"); + t_const_suffixed!("hb_", "11", "u8"); + t_const_suffixed!("off00ff00ff00ff00ff_", "0xff00ff00ff00ff00ff", "u128"); + t_const_suffixed!("s98_", "152", "i16"); + t_const_suffixed!("anb_", "-11", "i8"); + t_const!("b0_", "false"); + t_const!("b1_", "true"); + t_const!("c76_", "'v'"); + t_const!("ca_", "'\\n'"); + t_const!("c2202_", "'∂'"); } #[test] From e8509eb6d71b018065b282b06db2f7bae01ebfcd Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 27 Jul 2021 21:02:50 +0300 Subject: [PATCH 4/7] v0: introduce `HexNibbles` abstraction for leaf const parsing. --- src/v0.rs | 81 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 31 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index 24b5373..fa524be 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,3 +1,4 @@ +use core::convert::TryFrom; use core::{char, fmt, mem}; #[allow(unused_macros)] @@ -264,6 +265,30 @@ impl<'s> fmt::Display for Ident<'s> { } } +/// Sequence of lowercase hexadecimal nibbles (`0-9a-f`), used by leaf consts. +struct HexNibbles<'s> { + nibbles: &'s str, +} + +impl<'s> HexNibbles<'s> { + /// Decode an integer value (with the "most significant nibble" first), + /// returning `None` if it can't fit in an `u64`. + // FIXME(eddyb) should this "just" use `u128` instead? + fn try_parse_uint(&self) -> Option { + let nibbles = self.nibbles.trim_start_matches("0"); + + if nibbles.len() > 16 { + return None; + } + + let mut v = 0; + for nibble in nibbles.chars() { + v = (v << 4) | (nibble.to_digit(16).unwrap() as u64); + } + Some(v) + } +} + fn basic_type(tag: u8) -> Option<&'static str> { Some(match tag { b'b' => "bool", @@ -331,7 +356,7 @@ impl<'s> Parser<'s> { Ok(b) } - fn hex_nibbles(&mut self) -> Result<&'s str, ParseError> { + fn hex_nibbles(&mut self) -> Result, ParseError> { let start = self.next; loop { match self.next()? { @@ -340,7 +365,9 @@ impl<'s> Parser<'s> { _ => return Err(ParseError::Invalid), } } - Ok(&self.sym[start..self.next - 1]) + Ok(HexNibbles { + nibbles: &self.sym[start..self.next - 1], + }) } fn digit_10(&mut self) -> Result { @@ -976,16 +1003,14 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { fn print_const_uint(&mut self, ty_tag: u8) -> fmt::Result { let hex = parse!(self, hex_nibbles); - // Print anything that doesn't fit in `u64` verbatim. - if hex.len() > 16 { - self.print("0x")?; - self.print(hex)?; - } else { - let mut v = 0; - for c in hex.chars() { - v = (v << 4) | (c.to_digit(16).unwrap() as u64); + match hex.try_parse_uint() { + Some(v) => self.print(v)?, + + // Print anything that doesn't fit in `u64` verbatim. + None => { + self.print("0x")?; + self.print(hex.nibbles)?; } - self.print(v)?; } if let Some(out) = &mut self.out { @@ -1007,33 +1032,27 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { } fn print_const_bool(&mut self) -> fmt::Result { - match parse!(self, hex_nibbles).as_bytes() { - b"0" => self.print("false"), - b"1" => self.print("true"), + match parse!(self, hex_nibbles).try_parse_uint() { + Some(0) => self.print("false"), + Some(1) => self.print("true"), _ => invalid!(self), } } fn print_const_char(&mut self) -> fmt::Result { - let hex = parse!(self, hex_nibbles); - - // Valid `char`s fit in `u32`. - if hex.len() > 8 { - invalid!(self); - } - - let mut v = 0; - for c in hex.chars() { - v = (v << 4) | (c.to_digit(16).unwrap() as u32); - } - if let Some(c) = char::from_u32(v) { - if let Some(out) = &mut self.out { - fmt::Debug::fmt(&c, out)?; + match parse!(self, hex_nibbles) + .try_parse_uint() + .and_then(|v| u32::try_from(v).ok()) + .and_then(char::from_u32) + { + Some(c) => { + if let Some(out) = &mut self.out { + fmt::Debug::fmt(&c, out)?; + } + Ok(()) } - } else { - invalid!(self); + None => invalid!(self), } - Ok(()) } } From e31ad72108d093afe42c2c2b89f457743d0fdef3 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Tue, 27 Jul 2021 21:34:07 +0300 Subject: [PATCH 5/7] v0: make `print_const` more self-contained, like `print_type`. --- src/v0.rs | 71 +++++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index fa524be..437f81b 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -978,23 +978,42 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { parse!(self, push_depth); match tag { - // Placeholder. b'p' => self.print("_")?, - // Unsigned integer types. + + // Primitive leaves with hex-encoded values (see `basic_type`). b'h' | b't' | b'm' | b'y' | b'o' | b'j' => self.print_const_uint(tag)?, - // Signed integer types. - b'a' | b's' | b'l' | b'x' | b'n' | b'i' => self.print_const_int(tag)?, - // Bool. - b'b' => self.print_const_bool()?, - // Char. - b'c' => self.print_const_char()?, + b'a' | b's' | b'l' | b'x' | b'n' | b'i' => { + if self.eat(b'n') { + self.print("-")?; + } + + self.print_const_uint(tag)?; + } + b'b' => match parse!(self, hex_nibbles).try_parse_uint() { + Some(0) => self.print("false")?, + Some(1) => self.print("true")?, + _ => invalid!(self), + }, + b'c' => { + let valid_char = parse!(self, hex_nibbles) + .try_parse_uint() + .and_then(|v| u32::try_from(v).ok()) + .and_then(char::from_u32); + match valid_char { + Some(c) => { + if let Some(out) = &mut self.out { + fmt::Debug::fmt(&c, out)?; + } + } + None => invalid!(self), + } + } b'B' => { self.print_backref(Self::print_const)?; } - _ => invalid!(self), - }; + } self.pop_depth(); Ok(()) @@ -1022,38 +1041,6 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { Ok(()) } - - fn print_const_int(&mut self, ty_tag: u8) -> fmt::Result { - if self.eat(b'n') { - self.print("-")?; - } - - self.print_const_uint(ty_tag) - } - - fn print_const_bool(&mut self) -> fmt::Result { - match parse!(self, hex_nibbles).try_parse_uint() { - Some(0) => self.print("false"), - Some(1) => self.print("true"), - _ => invalid!(self), - } - } - - fn print_const_char(&mut self) -> fmt::Result { - match parse!(self, hex_nibbles) - .try_parse_uint() - .and_then(|v| u32::try_from(v).ok()) - .and_then(char::from_u32) - { - Some(c) => { - if let Some(out) = &mut self.out { - fmt::Debug::fmt(&c, out)?; - } - Ok(()) - } - None => invalid!(self), - } - } } #[cfg(test)] From a20a29a55f104933938f760986d449d3c6e8bd10 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Fri, 30 Jul 2021 12:49:13 +0300 Subject: [PATCH 6/7] v0: add test showing a const `char` double-quote not being escaped. --- src/v0.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/v0.rs b/src/v0.rs index 437f81b..fcb2089 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1134,6 +1134,7 @@ mod tests { t_const!("b0_", "false"); t_const!("b1_", "true"); t_const!("c76_", "'v'"); + t_const!("c22_", r#"'"'"#); t_const!("ca_", "'\\n'"); t_const!("c2202_", "'∂'"); } From 6b3c45971be29c06693c6c38c8650dbce5a592f5 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Fri, 30 Jul 2021 13:14:51 +0300 Subject: [PATCH 7/7] v0: don't use `fmt::Debug` for printing const `char`s. --- src/v0.rs | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/v0.rs b/src/v0.rs index fcb2089..c83815b 100644 --- a/src/v0.rs +++ b/src/v0.rs @@ -1,5 +1,5 @@ use core::convert::TryFrom; -use core::{char, fmt, mem}; +use core::{char, fmt, iter, mem}; #[allow(unused_macros)] macro_rules! write { @@ -604,6 +604,35 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { Ok(()) } + /// Output the given `char`s (escaped using `char::escape_debug`), with the + /// whole sequence wrapped in quotes, for either a `char` or `&str` literal, + /// if printing isn't being skipped. + fn print_quoted_escaped_chars( + &mut self, + quote: char, + chars: impl Iterator, + ) -> fmt::Result { + if let Some(out) = &mut self.out { + use core::fmt::Write; + + out.write_char(quote)?; + for c in chars { + // Special-case not escaping a single/double quote, when + // inside the opposite kind of quote. + if matches!((quote, c), ('\'', '"') | ('"', '\'')) { + out.write_char(c)?; + continue; + } + + for escaped in c.escape_debug() { + out.write_char(escaped)?; + } + } + out.write_char(quote)?; + } + Ok(()) + } + /// Print the lifetime according to the previously decoded index. /// An index of `0` always refers to `'_`, but starting with `1`, /// indices refer to late-bound lifetimes introduced by a binder. @@ -1000,11 +1029,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> { .and_then(|v| u32::try_from(v).ok()) .and_then(char::from_u32); match valid_char { - Some(c) => { - if let Some(out) = &mut self.out { - fmt::Debug::fmt(&c, out)?; - } - } + Some(c) => self.print_quoted_escaped_chars('\'', iter::once(c))?, None => invalid!(self), } }