From ae7cda573a38ad7326c7734c922dc62078326efa Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Wed, 27 Jul 2022 08:57:21 -0700 Subject: [PATCH 1/3] eddyb autoref trick written by dtolnay and tweaked by danielhenrymantilla Fix stability annotations `autoref!` cleanup: improve comments, and move it under `core::ops` --- library/core/src/macros/mod.rs | 17 +++++++++--- library/core/src/ops/autoref.rs | 46 +++++++++++++++++++++++++++++++++ library/core/src/ops/mod.rs | 3 +++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 library/core/src/ops/autoref.rs diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index fd96e1ff77d53..d127b1a57163e 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -515,9 +515,15 @@ macro_rules! r#try { #[macro_export] #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "write_macro")] +#[allow_internal_unstable(autoref)] macro_rules! write { ($dst:expr, $($arg:tt)*) => { - $dst.write_fmt($crate::format_args!($($arg)*)) + match $crate::ops::autoref::autoref_mut!($dst) { + mut _dst => { + let result = _dst.write_fmt($crate::format_args!($($arg)*)); + result + } + } }; } @@ -549,13 +555,18 @@ macro_rules! write { #[macro_export] #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "writeln_macro")] -#[allow_internal_unstable(format_args_nl)] +#[allow_internal_unstable(autoref, format_args_nl)] macro_rules! writeln { ($dst:expr $(,)?) => { $crate::write!($dst, "\n") }; ($dst:expr, $($arg:tt)*) => { - $dst.write_fmt($crate::format_args_nl!($($arg)*)) + match $crate::ops::autoref::autoref_mut!($dst) { + mut _dst => { + let result = _dst.write_fmt($crate::format_args_nl!($($arg)*)); + result + } + } }; } diff --git a/library/core/src/ops/autoref.rs b/library/core/src/ops/autoref.rs new file mode 100644 index 0000000000000..0e07277986c46 --- /dev/null +++ b/library/core/src/ops/autoref.rs @@ -0,0 +1,46 @@ +#![allow(missing_docs, missing_debug_implementations)] +// Poor man's `k#autoref` operator. +// +// See https://github.com/rust-lang/rust/issues/99684 +// and https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Desired.20behavior.20of.20.60write!.60.20is.20unimplementable +// for some more context about this idea. +// +// Right now we polyfill this idea, by reducing support to `&mut`-autoref-only +// (effectively "just" preventing an unnecessary `&mut` level of indirection +// from being applied for a thing already behind a `&mut …`), which happens to +// work for `&mut`-based `.write_fmt()` methods, and some cases of `&`-based +// `.write_fmt()` —the whole duck-typed design / API of `write!` is asking for +// trouble—, but won't work for a `self`-based `.write_fmt()`, as pointed out +// here: https://github.com/rust-lang/rust/pull/100202#pullrequestreview-1064499226 +// +// Finally, in order to reduce the chances of name conflicts as much as +// possible, the method name is a bit mangled, and to prevent usage of this +// method in stable-rust, an unstable const generic parameter that needs to be +// turbofished is added to it as well. + +/// The unstable const generic parameter achieving the "unstable seal" effect. +#[unstable(feature = "autoref", issue = "none")] +#[derive(Eq, PartialEq)] +pub struct UnstableMethodSeal; + +#[unstable(feature = "autoref", issue = "none")] +pub trait AutoRef { + #[unstable(feature = "autoref", issue = "none")] + #[inline(always)] + fn __rustc_unstable_auto_ref_mut_helper( + &mut self, + ) -> &mut Self { + self + } +} + +#[unstable(feature = "autoref", issue = "none")] +impl AutoRef for T {} + +#[unstable(feature = "autoref", issue = "none")] +#[allow_internal_unstable(autoref)] +#[rustc_macro_transparency = "semitransparent"] +pub macro autoref_mut($x:expr) {{ + use $crate::ops::autoref::AutoRef as _; + $x.__rustc_unstable_auto_ref_mut_helper::<{ $crate::ops::autoref::UnstableMethodSeal }>() +}} diff --git a/library/core/src/ops/mod.rs b/library/core/src/ops/mod.rs index a5e5b13b33674..36eced1bfb0de 100644 --- a/library/core/src/ops/mod.rs +++ b/library/core/src/ops/mod.rs @@ -139,6 +139,9 @@ #![stable(feature = "rust1", since = "1.0.0")] mod arith; +#[doc(hidden)] +#[unstable(feature = "autoref", issue = "none")] +pub mod autoref; mod bit; mod control_flow; mod deref; From 2310efa1d47ca54e96064bf35366a50e67ead7e1 Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Sat, 6 Aug 2022 19:20:26 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Add=20a=202=CF=86-borrows=20retro-compat=20?= =?UTF-8?q?workaround=20to=20`write!`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `write!`: tweak the 2φ borrows hack to cover tuple indexing too --- compiler/rustc_middle/src/ty/print/pretty.rs | 4 +-- library/core/src/macros/mod.rs | 36 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index c0607a102a90e..f89b5486c3470 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -30,10 +30,10 @@ use super::*; macro_rules! p { (@$lit:literal) => { - write!(scoped_cx!(), $lit)? + scoped_cx!().write_fmt(format_args!($lit))? }; (@write($($data:expr),+)) => { - write!(scoped_cx!(), $($data),+)? + scoped_cx!().write_fmt(format_args!($($data),+))? }; (@print($x:expr)) => { scoped_cx!() = $x.print(scoped_cx!())? diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index d127b1a57163e..3949b836c37a7 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -517,6 +517,30 @@ macro_rules! r#try { #[cfg_attr(not(test), rustc_diagnostic_item = "write_macro")] #[allow_internal_unstable(autoref)] macro_rules! write { + // Retrocompat workaround to support two phased borrows: + // when `$dst` is made of chaining `.field` (or `.idx`) accesses to a local, + // it is guaranteed to be a *place* and thus there is no question of + // late-dropping anything inside it. + // + // Whilst this won't cover *all* the possible syntaxes for places (it + // won't handle places with `[…]`-indexing inside them (see + // https://github.com/rust-lang/rust/pull/100202#pullrequestreview-1064499226) + // qualified paths to `static mut`s, nor macro invocations), it ought to + // cover the vast majority of uses in practice, especially regarding + // retro-compatibility. + ( + $dst_place:ident $(. $field_or_idx:tt)* , + $($arg:tt)* + ) => ({ + let result = + $dst_place $(. $field_or_idx )* + .write_fmt($crate::format_args!($($arg)*)) + ; + result + }); + + // default case: early-dropped `format_args!($($args)*)` while keeping + // `$dst` late-dropped. ($dst:expr, $($arg:tt)*) => { match $crate::ops::autoref::autoref_mut!($dst) { mut _dst => { @@ -560,6 +584,18 @@ macro_rules! writeln { ($dst:expr $(,)?) => { $crate::write!($dst, "\n") }; + + ( + $dst_place:ident $(. $field_or_idx:tt)* , + $($arg:tt)* + ) => ({ + let result = + $dst_place $(. $field_or_idx)* + .write_fmt($crate::format_args_nl!($($arg)*)) + ; + result + }); + ($dst:expr, $($arg:tt)*) => { match $crate::ops::autoref::autoref_mut!($dst) { mut _dst => { From b84d25da12c99ce8cfa587f64a945d7d804e59bb Mon Sep 17 00:00:00 2001 From: Daniel Henry-Mantilla Date: Mon, 8 Aug 2022 23:01:24 +0200 Subject: [PATCH 3/3] =?UTF-8?q?Fix=20format-args-temporaries=20test=20(fai?= =?UTF-8?q?l->pass)=20&=20add=202=CF=86-borrows=20regression=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../format-args-temporaries-in-write.rs | 61 ++++++++++--------- .../format-args-temporaries-in-write.stderr | 43 ------------- 2 files changed, 32 insertions(+), 72 deletions(-) delete mode 100644 src/test/ui/macros/format-args-temporaries-in-write.stderr diff --git a/src/test/ui/macros/format-args-temporaries-in-write.rs b/src/test/ui/macros/format-args-temporaries-in-write.rs index 339ccbc33ac98..f2e96fcd98045 100644 --- a/src/test/ui/macros/format-args-temporaries-in-write.rs +++ b/src/test/ui/macros/format-args-temporaries-in-write.rs @@ -1,50 +1,53 @@ -// check-fail +// check-pass +#![crate_type = "lib"] use std::fmt::{self, Display}; struct Mutex; impl Mutex { - fn lock(&self) -> MutexGuard { - MutexGuard(self) + /// Dependent item with (potential) drop glue to disable NLL. + fn lock(&self) -> impl '_ + Display { + 42 } } -struct MutexGuard<'a>(&'a Mutex); +struct Stderr(); -impl<'a> Drop for MutexGuard<'a> { - fn drop(&mut self) { - // Empty but this is a necessary part of the repro. Otherwise borrow - // checker is fine with 'a dangling at the time that MutexGuard goes out - // of scope. - } -} - -struct Out; - -impl Out { - fn write_fmt(&self, _args: fmt::Arguments) {} -} - -impl<'a> Display for MutexGuard<'a> { - fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { - Ok(()) - } +impl Stderr { + /// A "lending" `write_fmt` method. See: + /// https://docs.rs/async-std/1.12.0/async_std/io/prelude/trait.WriteExt.html#method.write_fmt + fn write_fmt(&mut self, _args: fmt::Arguments) -> &() { &() } } -fn main() { - // FIXME(dtolnay): We actually want both of these to work. I think it's - // sadly unimplementable today though. +fn early_drop_for_format_args_temporaries() { + let mut out = Stderr(); let _write = { let mutex = Mutex; - write!(Out, "{}", mutex.lock()) /* no semicolon */ - //~^ ERROR `mutex` does not live long enough + write!(out, "{}", mutex.lock()) /* no semicolon */ }; let _writeln = { let mutex = Mutex; - writeln!(Out, "{}", mutex.lock()) /* no semicolon */ - //~^ ERROR `mutex` does not live long enough + writeln!(out, "{}", mutex.lock()) /* no semicolon */ }; } + +fn late_drop_for_receiver() { + let mutex = Mutex; + drop(write!(&mut Stderr(), "{}", mutex.lock())); + drop(writeln!(&mut Stderr(), "{}", mutex.lock())); +} + +fn two_phased_borrows_retrocompat(w: (&mut Stderr, i32)) { + write!(w.0, "{}", w.1); + writeln!(w.0, "{}", w.1); + struct Struct { + w: W, + len: i32 + } + let s = (Struct { w: (w.0, ), len: w.1 }, ); + write!(s.0.w.0, "{}", s.0.len); + writeln!(s.0.w.0, "{}", s.0.len); +} diff --git a/src/test/ui/macros/format-args-temporaries-in-write.stderr b/src/test/ui/macros/format-args-temporaries-in-write.stderr deleted file mode 100644 index 03ecc4b4418c6..0000000000000 --- a/src/test/ui/macros/format-args-temporaries-in-write.stderr +++ /dev/null @@ -1,43 +0,0 @@ -error[E0597]: `mutex` does not live long enough - --> $DIR/format-args-temporaries-in-write.rs:41:27 - | -LL | write!(Out, "{}", mutex.lock()) /* no semicolon */ - | ^^^^^^^^^^^^ - | | - | borrowed value does not live long enough - | a temporary with access to the borrow is created here ... -LL | -LL | }; - | -- ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `MutexGuard` - | | - | `mutex` dropped here while still borrowed - | -help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped - --> $SRC_DIR/core/src/macros/mod.rs:LL:COL - | -LL | $dst.write_fmt($crate::format_args!($($arg)*)); - | + - -error[E0597]: `mutex` does not live long enough - --> $DIR/format-args-temporaries-in-write.rs:47:29 - | -LL | writeln!(Out, "{}", mutex.lock()) /* no semicolon */ - | ^^^^^^^^^^^^ - | | - | borrowed value does not live long enough - | a temporary with access to the borrow is created here ... -LL | -LL | }; - | -- ... and the borrow might be used here, when that temporary is dropped and runs the `Drop` code for type `MutexGuard` - | | - | `mutex` dropped here while still borrowed - | -help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped - --> $SRC_DIR/core/src/macros/mod.rs:LL:COL - | -LL | $dst.write_fmt($crate::format_args_nl!($($arg)*)); - | + - -error: aborting due to 2 previous errors - -For more information about this error, try `rustc --explain E0597`.