From 29403eeef0b175b4a65cc3c7865708ee15d8a7e8 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 12 Nov 2021 22:53:26 -0500 Subject: [PATCH 01/13] add unchecked downcast methods --- library/alloc/src/boxed.rs | 131 +++++++++++++++++++++------ library/core/src/any.rs | 178 +++++++++++++++++++++++++++++++++++-- 2 files changed, 273 insertions(+), 36 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index f6332b072cf30..81ac37a306b44 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1482,8 +1482,6 @@ impl TryFrom> for Box<[T; N]> { } impl Box { - #[inline] - #[stable(feature = "rust1", since = "1.0.0")] /// Attempt to downcast the box to a concrete type. /// /// # Examples @@ -1501,21 +1499,46 @@ impl Box { /// print_if_string(Box::new(my_string)); /// print_if_string(Box::new(0i8)); /// ``` + #[inline] + #[stable(feature = "box_send_sync_any_downcast", since = "1.51.0")] pub fn downcast(self) -> Result, Self> { - if self.is::() { - unsafe { - let (raw, alloc): (*mut dyn Any, _) = Box::into_raw_with_allocator(self); - Ok(Box::from_raw_in(raw as *mut T, alloc)) - } - } else { - Err(self) + if self.is::() { unsafe { Ok(self.downcast_unchecked::()) } } else { Err(self) } + } + + /// Downcasts the box to a concrete type. + /// + /// For a safe alternative see [`downcast`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_unchecked::(), 1); + /// } + /// ``` + /// + /// # Safety + /// + /// The contained value must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + #[inline] + #[unstable(feature = "downcast_unchecked", issue = "none")] + pub unsafe fn downcast_unchecked(self) -> Box { + debug_assert!(self.is::()); + unsafe { + let (raw, alloc): (*mut dyn Any, _) = Box::into_raw_with_allocator(self); + Box::from_raw_in(raw as *mut T, alloc) } } } impl Box { - #[inline] - #[stable(feature = "rust1", since = "1.0.0")] /// Attempt to downcast the box to a concrete type. /// /// # Examples @@ -1533,21 +1556,46 @@ impl Box { /// print_if_string(Box::new(my_string)); /// print_if_string(Box::new(0i8)); /// ``` + #[inline] + #[stable(feature = "box_send_sync_any_downcast", since = "1.51.0")] pub fn downcast(self) -> Result, Self> { - if self.is::() { - unsafe { - let (raw, alloc): (*mut (dyn Any + Send), _) = Box::into_raw_with_allocator(self); - Ok(Box::from_raw_in(raw as *mut T, alloc)) - } - } else { - Err(self) + if self.is::() { unsafe { Ok(self.downcast_unchecked::()) } } else { Err(self) } + } + + /// Downcasts the box to a concrete type. + /// + /// For a safe alternative see [`downcast`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_unchecked::(), 1); + /// } + /// ``` + /// + /// # Safety + /// + /// The contained value must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + #[inline] + #[unstable(feature = "downcast_unchecked", issue = "none")] + pub unsafe fn downcast_unchecked(self) -> Box { + debug_assert!(self.is::()); + unsafe { + let (raw, alloc): (*mut (dyn Any + Send), _) = Box::into_raw_with_allocator(self); + Box::from_raw_in(raw as *mut T, alloc) } } } impl Box { - #[inline] - #[stable(feature = "box_send_sync_any_downcast", since = "1.51.0")] /// Attempt to downcast the box to a concrete type. /// /// # Examples @@ -1565,15 +1613,42 @@ impl Box { /// print_if_string(Box::new(my_string)); /// print_if_string(Box::new(0i8)); /// ``` + #[inline] + #[stable(feature = "box_send_sync_any_downcast", since = "1.51.0")] pub fn downcast(self) -> Result, Self> { - if self.is::() { - unsafe { - let (raw, alloc): (*mut (dyn Any + Send + Sync), _) = - Box::into_raw_with_allocator(self); - Ok(Box::from_raw_in(raw as *mut T, alloc)) - } - } else { - Err(self) + if self.is::() { unsafe { Ok(self.downcast_unchecked::()) } } else { Err(self) } + } + + /// Downcasts the box to a concrete type. + /// + /// For a safe alternative see [`downcast`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_unchecked::(), 1); + /// } + /// ``` + /// + /// # Safety + /// + /// The contained value must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + #[inline] + #[unstable(feature = "downcast_unchecked", issue = "none")] + pub unsafe fn downcast_unchecked(self) -> Box { + debug_assert!(self.is::()); + unsafe { + let (raw, alloc): (*mut (dyn Any + Send + Sync), _) = + Box::into_raw_with_allocator(self); + Box::from_raw_in(raw as *mut T, alloc) } } } diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 1fd5aa27fce46..8ff38e52a6f8e 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -164,7 +164,7 @@ impl fmt::Debug for dyn Any + Send + Sync { } impl dyn Any { - /// Returns `true` if the boxed type is the same as `T`. + /// Returns `true` if the inner type is the same as `T`. /// /// # Examples /// @@ -195,7 +195,7 @@ impl dyn Any { t == concrete } - /// Returns some reference to the boxed value if it is of type `T`, or + /// Returns some reference to the inner value if it is of type `T`, or /// `None` if it isn't. /// /// # Examples @@ -221,13 +221,13 @@ impl dyn Any { // SAFETY: just checked whether we are pointing to the correct type, and we can rely on // that check for memory safety because we have implemented Any for all types; no other // impls can exist as they would conflict with our impl. - unsafe { Some(&*(self as *const dyn Any as *const T)) } + unsafe { Some(self.downcast_ref_unchecked()) } } else { None } } - /// Returns some mutable reference to the boxed value if it is of type `T`, or + /// Returns some mutable reference to the inner value if it is of type `T`, or /// `None` if it isn't. /// /// # Examples @@ -257,15 +257,77 @@ impl dyn Any { // SAFETY: just checked whether we are pointing to the correct type, and we can rely on // that check for memory safety because we have implemented Any for all types; no other // impls can exist as they would conflict with our impl. - unsafe { Some(&mut *(self as *mut dyn Any as *mut T)) } + unsafe { Some(self.downcast_mut_unchecked()) } } else { None } } + + /// Returns a reference to the inner value as type `dyn T`. + /// + /// For a safe alternative see [`downcast_ref`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_ref_unchecked::(), 1); + /// } + /// ``` + /// + /// # Safety + /// + /// The contained value must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_ref_unchecked(&self) -> &T { + debug_assert!(self.is::()); + // SAFETY: caller guarantees that T is the correct type + unsafe { &*(self as *const dyn Any as *const T) } + } + + /// Returns a mutable reference to the inner value as type `dyn T`. + /// + /// For a safe alternative see [`downcast_mut`]. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let mut x: Box = Box::new(1_usize); + /// + /// unsafe { + /// *x.downcast_mut_unchecked::() += 1; + /// } + /// + /// assert_eq!(*x.downcast_ref::().unwrap(), 2); + /// ``` + /// + /// # Safety + /// + /// The contained value must be of type `T`. Calling this method + /// with the incorrect type is *undefined behavior*. + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { + debug_assert!(self.is::()); + // SAFETY: caller guarantees that T is the correct type + unsafe { &mut *(self as *mut dyn Any as *mut T) } + } } impl dyn Any + Send { - /// Forwards to the method defined on the type `Any`. + /// Forwards to the method defined on the type `dyn Any`. /// /// # Examples /// @@ -289,7 +351,7 @@ impl dyn Any + Send { ::is::(self) } - /// Forwards to the method defined on the type `Any`. + /// Forwards to the method defined on the type `dyn Any`. /// /// # Examples /// @@ -313,7 +375,7 @@ impl dyn Any + Send { ::downcast_ref::(self) } - /// Forwards to the method defined on the type `Any`. + /// Forwards to the method defined on the type `dyn Any`. /// /// # Examples /// @@ -340,6 +402,60 @@ impl dyn Any + Send { pub fn downcast_mut(&mut self) -> Option<&mut T> { ::downcast_mut::(self) } + + /// Forwards to the method defined on the type `dyn Any`. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_ref_unchecked::(), 1); + /// } + /// ``` + /// + /// # Safety + /// + /// Same as the method on the type `dyn Any`. + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_ref_unchecked(&self) -> &T { + // SAFETY: guaranteed by caller + unsafe { ::downcast_ref_unchecked::(self) } + } + + /// Forwards to the method defined on the type `dyn Any`. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let mut x: Box = Box::new(1_usize); + /// + /// unsafe { + /// *x.downcast_mut_unchecked::() += 1; + /// } + /// + /// assert_eq!(*x.downcast_ref::().unwrap(), 2); + /// ``` + /// + /// # Safety + /// + /// Same as the method on the type `dyn Any`. + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { + // SAFETY: guaranteed by caller + unsafe { ::downcast_mut_unchecked::(self) } + } } impl dyn Any + Send + Sync { @@ -418,6 +534,52 @@ impl dyn Any + Send + Sync { pub fn downcast_mut(&mut self) -> Option<&mut T> { ::downcast_mut::(self) } + + /// Forwards to the method defined on the type `Any`. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let x: Box = Box::new(1_usize); + /// + /// unsafe { + /// assert_eq!(*x.downcast_ref_unchecked::(), 1); + /// } + /// ``` + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_ref_unchecked(&self) -> &T { + // SAFETY: guaranteed by caller + unsafe { ::downcast_ref_unchecked::(self) } + } + + /// Forwards to the method defined on the type `Any`. + /// + /// # Examples + /// + /// ``` + /// #![feature(downcast_unchecked)] + /// + /// use std::any::Any; + /// + /// let mut x: Box = Box::new(1_usize); + /// + /// unsafe { + /// *x.downcast_mut_unchecked::() += 1; + /// } + /// + /// assert_eq!(*x.downcast_ref::().unwrap(), 2); + /// ``` + #[unstable(feature = "downcast_unchecked", issue = "none")] + #[inline] + pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { + // SAFETY: guaranteed by caller + unsafe { ::downcast_mut_unchecked::(self) } + } } /////////////////////////////////////////////////////////////////////////////// From 6f982930ba2da1ee99c3c4378179bcfe6f615db6 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Fri, 12 Nov 2021 22:55:11 -0500 Subject: [PATCH 02/13] add tracking issue for `downcast_unchecked` --- library/alloc/src/boxed.rs | 6 +++--- library/core/src/any.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 81ac37a306b44..1e2c1b2eee67a 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1528,7 +1528,7 @@ impl Box { /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. #[inline] - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { debug_assert!(self.is::()); unsafe { @@ -1585,7 +1585,7 @@ impl Box { /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. #[inline] - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { debug_assert!(self.is::()); unsafe { @@ -1642,7 +1642,7 @@ impl Box { /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. #[inline] - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { debug_assert!(self.is::()); unsafe { diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 8ff38e52a6f8e..3e306b1333a1e 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -285,7 +285,7 @@ impl dyn Any { /// /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_ref_unchecked(&self) -> &T { debug_assert!(self.is::()); @@ -317,7 +317,7 @@ impl dyn Any { /// /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { debug_assert!(self.is::()); @@ -422,7 +422,7 @@ impl dyn Any + Send { /// # Safety /// /// Same as the method on the type `dyn Any`. - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_ref_unchecked(&self) -> &T { // SAFETY: guaranteed by caller @@ -450,7 +450,7 @@ impl dyn Any + Send { /// # Safety /// /// Same as the method on the type `dyn Any`. - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { // SAFETY: guaranteed by caller @@ -550,7 +550,7 @@ impl dyn Any + Send + Sync { /// assert_eq!(*x.downcast_ref_unchecked::(), 1); /// } /// ``` - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_ref_unchecked(&self) -> &T { // SAFETY: guaranteed by caller @@ -574,7 +574,7 @@ impl dyn Any + Send + Sync { /// /// assert_eq!(*x.downcast_ref::().unwrap(), 2); /// ``` - #[unstable(feature = "downcast_unchecked", issue = "none")] + #[unstable(feature = "downcast_unchecked", issue = "90850")] #[inline] pub unsafe fn downcast_mut_unchecked(&mut self) -> &mut T { // SAFETY: guaranteed by caller From 25271a5a98e362fa118672c33e4861cd42e111e9 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sat, 20 Nov 2021 18:21:52 -0500 Subject: [PATCH 03/13] fix doc links for `downcast_unchecked` --- library/alloc/src/boxed.rs | 6 ++++++ library/core/src/any.rs | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/library/alloc/src/boxed.rs b/library/alloc/src/boxed.rs index 1e2c1b2eee67a..e6f687ddf9633 100644 --- a/library/alloc/src/boxed.rs +++ b/library/alloc/src/boxed.rs @@ -1527,6 +1527,8 @@ impl Box { /// /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. + /// + /// [`downcast`]: Self::downcast #[inline] #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { @@ -1584,6 +1586,8 @@ impl Box { /// /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. + /// + /// [`downcast`]: Self::downcast #[inline] #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { @@ -1641,6 +1645,8 @@ impl Box { /// /// The contained value must be of type `T`. Calling this method /// with the incorrect type is *undefined behavior*. + /// + /// [`downcast`]: Self::downcast #[inline] #[unstable(feature = "downcast_unchecked", issue = "90850")] pub unsafe fn downcast_unchecked(self) -> Box { diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 3e306b1333a1e..72528185707a6 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -265,8 +265,6 @@ impl dyn Any { /// Returns a reference to the inner value as type `dyn T`. /// - /// For a safe alternative see [`downcast_ref`]. - /// /// # Examples /// /// ``` @@ -295,8 +293,6 @@ impl dyn Any { /// Returns a mutable reference to the inner value as type `dyn T`. /// - /// For a safe alternative see [`downcast_mut`]. - /// /// # Examples /// /// ``` From f6b499da167399784955956eb6be4e764628b317 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 29 Nov 2021 22:14:38 -0800 Subject: [PATCH 04/13] Suggest the `pat_param` specifier before `|` on 2021 edition We have a migration warning but no lint for users who have enabled the new edition. --- compiler/rustc_expand/src/mbe/macro_rules.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index f8491654f39e3..537a10e98e59d 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -1027,6 +1027,24 @@ fn check_matcher_core( ), ); err.span_label(sp, format!("not allowed after `{}` fragments", kind)); + + if kind == NonterminalKind::PatWithOr + && sess.edition == Edition::Edition2021 + && next_token.is_token(&BinOp(token::BinOpToken::Or)) + { + let suggestion = quoted_tt_to_string(&TokenTree::MetaVarDecl( + span, + name, + Some(NonterminalKind::PatParam { inferred: false }), + )); + err.span_suggestion( + span, + &format!("try a `pat_param` fragment specifier instead"), + suggestion, + Applicability::MaybeIncorrect, + ); + } + let msg = "allowed there are: "; match possible { &[] => {} From 4c1d3173aba1dbcc9272966ce83ab8a818a07b74 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 29 Nov 2021 22:16:01 -0800 Subject: [PATCH 05/13] Bless tests with new suggestion --- .../macro-pat-pattern-followed-by-or-in-2021.stderr | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/ui/macros/macro-pat-pattern-followed-by-or-in-2021.stderr b/src/test/ui/macros/macro-pat-pattern-followed-by-or-in-2021.stderr index a5987a25551d2..a06487be3d601 100644 --- a/src/test/ui/macros/macro-pat-pattern-followed-by-or-in-2021.stderr +++ b/src/test/ui/macros/macro-pat-pattern-followed-by-or-in-2021.stderr @@ -2,7 +2,9 @@ error: `$x:pat` is followed by `|`, which is not allowed for `pat` fragments --> $DIR/macro-pat-pattern-followed-by-or-in-2021.rs:3:28 | LL | macro_rules! foo { ($x:pat | $y:pat) => {} } - | ^ not allowed after `pat` fragments + | ------ ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$x:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` @@ -10,7 +12,9 @@ error: `$x:pat` is followed by `|`, which is not allowed for `pat` fragments --> $DIR/macro-pat-pattern-followed-by-or-in-2021.rs:4:32 | LL | macro_rules! bar { ($($x:pat)+ | $($y:pat)+) => {} } - | ^ not allowed after `pat` fragments + | ------ ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$x:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` @@ -18,7 +22,9 @@ error: `$pat:pat` may be followed by `|`, which is not allowed for `pat` fragmen --> $DIR/macro-pat-pattern-followed-by-or-in-2021.rs:7:36 | LL | ( $expr:expr , $( $( $pat:pat )|+ => $expr_arm:expr ),+ ) => { - | ^ not allowed after `pat` fragments + | -------- ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$pat:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` From bfd95e1f0830b9ddc166161ab0d9ea5232f91e03 Mon Sep 17 00:00:00 2001 From: Dylan MacKenzie Date: Mon, 29 Nov 2021 22:38:26 -0800 Subject: [PATCH 06/13] Bless duplicate test --- .../macro-pat2021-pattern-followed-by-or.stderr | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/ui/macros/macro-pat2021-pattern-followed-by-or.stderr b/src/test/ui/macros/macro-pat2021-pattern-followed-by-or.stderr index 8aebe98515f4d..c3754dde080a3 100644 --- a/src/test/ui/macros/macro-pat2021-pattern-followed-by-or.stderr +++ b/src/test/ui/macros/macro-pat2021-pattern-followed-by-or.stderr @@ -2,7 +2,9 @@ error: `$x:pat` is followed by `|`, which is not allowed for `pat` fragments --> $DIR/macro-pat2021-pattern-followed-by-or.rs:4:28 | LL | macro_rules! foo { ($x:pat | $y:pat) => {} } - | ^ not allowed after `pat` fragments + | ------ ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$x:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` @@ -10,7 +12,9 @@ error: `$x:pat` is followed by `|`, which is not allowed for `pat` fragments --> $DIR/macro-pat2021-pattern-followed-by-or.rs:7:28 | LL | macro_rules! ogg { ($x:pat | $y:pat_param) => {} } - | ^ not allowed after `pat` fragments + | ------ ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$x:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` @@ -18,7 +22,9 @@ error: `$pat:pat` may be followed by `|`, which is not allowed for `pat` fragmen --> $DIR/macro-pat2021-pattern-followed-by-or.rs:9:35 | LL | ( $expr:expr , $( $( $pat:pat)|+ => $expr_arm:pat),+ ) => { - | ^ not allowed after `pat` fragments + | -------- ^ not allowed after `pat` fragments + | | + | help: try a `pat_param` fragment specifier instead: `$pat:pat_param` | = note: allowed there are: `=>`, `,`, `=`, `if` or `in` From e2846a779d4b211e16e72eb7c01f74d48abb6cca Mon Sep 17 00:00:00 2001 From: Noah Lev Date: Wed, 24 Nov 2021 18:06:23 -0800 Subject: [PATCH 07/13] Implement `@snapshot` check for htmldocck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This form of check allows performing snapshot tests (à la `src/test/ui`) on rustdoc HTML output, making it easier to create and update tests. See this Zulip thread [1] for more information about the motivation for this change. [1]: https://zulip-archive.rust-lang.org/stream/266220-rustdoc/topic/HTML.20snapshot.20tests.html#262651142 --- src/etc/htmldocck.py | 80 ++++++++++++++++++++++++++-- src/tools/compiletest/src/runtest.rs | 12 ++--- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/src/etc/htmldocck.py b/src/etc/htmldocck.py index 8647db5a45dc8..48a341ffe0837 100644 --- a/src/etc/htmldocck.py +++ b/src/etc/htmldocck.py @@ -90,10 +90,20 @@ highlights for example. If you want to simply check for the presence of a given node or attribute, use an empty string (`""`) as a `PATTERN`. -* `@count PATH XPATH COUNT' checks for the occurrence of the given XPath +* `@count PATH XPATH COUNT` checks for the occurrence of the given XPath in the specified file. The number of occurrences must match the given count. +* `@snapshot NAME PATH XPATH` creates a snapshot test named NAME. + A snapshot test captures a subtree of the DOM, at the location + determined by the XPath, and compares it to a pre-recorded value + in a file. The file's name is the test's name with the `.rs` extension + replaced with `.NAME.html`, where NAME is the snapshot's name. + + htmldocck supports the `--bless` option to accept the current subtree + as expected, saving it to the file determined by the snapshot's name. + compiletest's `--bless` flag is forwarded to htmldocck. + * `@has-dir PATH` checks for the existence of the given directory. All conditions can be negated with `!`. `@!has foo/type.NoSuch.html` @@ -137,6 +147,10 @@ channel = os.environ["DOC_RUST_LANG_ORG_CHANNEL"] +# Initialized in main +rust_test_path = None +bless = None + class CustomHTMLParser(HTMLParser): """simplified HTML parser. @@ -387,6 +401,32 @@ def get_tree_count(tree, path): return len(tree.findall(path)) +def check_snapshot(snapshot_name, tree): + assert rust_test_path.endswith('.rs') + snapshot_path = '{}.{}.{}'.format(rust_test_path[:-3], snapshot_name, 'html') + try: + with open(snapshot_path, 'r') as snapshot_file: + expected_str = snapshot_file.read() + except FileNotFoundError: + if bless: + expected_str = None + else: + raise FailedCheck('No saved snapshot value') + + actual_str = ET.tostring(tree).decode('utf-8') + + if expected_str != actual_str: + if bless: + with open(snapshot_path, 'w') as snapshot_file: + snapshot_file.write(actual_str) + else: + print('--- expected ---\n') + print(expected_str) + print('\n\n--- actual ---\n') + print(actual_str) + print() + raise FailedCheck('Actual snapshot value is different than expected') + def stderr(*args): if sys.version_info.major < 3: file = codecs.getwriter('utf-8')(sys.stderr) @@ -448,6 +488,28 @@ def check_command(c, cache): ret = expected == found else: raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd)) + + elif c.cmd == 'snapshot': # snapshot test + if len(c.args) == 3: # @snapshot + [snapshot_name, html_path, pattern] = c.args + tree = cache.get_tree(html_path) + xpath = normalize_xpath(pattern) + subtrees = tree.findall(xpath) + if len(subtrees) == 1: + [subtree] = subtrees + try: + check_snapshot(snapshot_name, subtree) + ret = True + except FailedCheck as err: + cerr = str(err) + ret = False + elif len(subtrees) == 0: + raise FailedCheck('XPATH did not match') + else: + raise FailedCheck('Expected 1 match, but found {}'.format(len(subtrees))) + else: + raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd)) + elif c.cmd == 'has-dir': # has-dir test if len(c.args) == 1: # @has-dir = has-dir test try: @@ -458,11 +520,13 @@ def check_command(c, cache): ret = False else: raise InvalidCheck('Invalid number of @{} arguments'.format(c.cmd)) + elif c.cmd == 'valid-html': raise InvalidCheck('Unimplemented @valid-html') elif c.cmd == 'valid-links': raise InvalidCheck('Unimplemented @valid-links') + else: raise InvalidCheck('Unrecognized @{}'.format(c.cmd)) @@ -483,11 +547,19 @@ def check(target, commands): if __name__ == '__main__': - if len(sys.argv) != 3: - stderr('Usage: {}