diff --git a/Cargo.lock b/Cargo.lock index dc7c716..50e561a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" name = "generativity" version = "1.2.0" dependencies = [ + "never-say-never", "trybuild", ] @@ -49,6 +50,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "never-say-never" +version = "6.6.666" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5a574dadd7941adeaa71823ecba5e28331b8313fb2e1c6a5c7e5981ea53ad6" + [[package]] name = "proc-macro2" version = "1.0.95" diff --git a/Cargo.toml b/Cargo.toml index 18565f2..eeac7f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,5 @@ categories = ["no-std", "rust-patterns"] license = "MIT OR Apache-2.0" [dev-dependencies] - -[dependencies] +never-say-never = "6.6.666" trybuild = "1.0.110" diff --git a/src/lib.rs b/src/lib.rs index 3c48844..6af5530 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,9 +187,108 @@ macro_rules! make_guard { #[allow(unused)] let lifetime_brand = unsafe { $crate::LifetimeBrand::new(&branded_place) }; let $name = unsafe { $crate::Guard::new(branded_place) }; + + // The whole following `if false {}` block has only one role: to handle + // the case where follow-up code might diverge. + // See https://github.com/CAD97/generativity/issues/15 for more info. + if false { + #[allow(unreachable_code)] { + let phantom_ret_ty = $crate::__private::PhantomReturnType::<_>::NEW; + if false { + // Use inference to set the type parameter to that of the return type. + return $crate::__private::DefaultReify::reify(&phantom_ret_ty); + } + + // Guarding against `PhantomReturnType` itself does not suffice, + // we may be dealing with `PhantomReturnType<(!, !)>`, for instance. + // + // Observation: the very same mechanism which causes us trouble + // yields an `unreachable_code` warning in the following situation: + if false { + let _reified_ret = + $crate::__private::DefaultReify::reify(&phantom_ret_ty); + #[forbid(unreachable_code)] { + // any arbitrary statement works to trigger the lint. + if true {} + } + } + + // Poorman's specialization; only shadowed in the + // `PhantomReturnType` case. + // + // This is not strictly needed for soundness *per se*, since the above + // `forbid(unreachable_code)` takes care of that. + // + // But it greatly improves the diagnostics for the non-niche case. + #[allow(unused)] + use $crate::__private::DefaultReify as _; + return phantom_ret_ty.reify(); + } + } }; } +#[doc(hidden)] +/// NOT STABLE PUBLIC API. Used by the expansion of [`make_guard!`]. +pub mod __private { + pub struct PhantomReturnType(::core::marker::PhantomData); + + impl PhantomReturnType { + pub const NEW: Self = Self(::core::marker::PhantomData); + } + + /// Inlined [`::never-say-never`](https://docs.rs/never-say-never). + mod never_say_never { + pub trait FnPtr { type Never; } + impl FnPtr for fn() -> R { type Never = R; } + } + type Never = ! as never_say_never::FnPtr>::Never; + + /// Poorman's specialization for `Phony::::get()`. + pub trait DefaultReify { + /// Function to be used in dead code to reify/synthesize a `T` instance + /// out of our [`PhantomReturnType`]. + fn reify(&self) -> T { unreachable!() } + } + + impl DefaultReify for PhantomReturnType {} + + impl PhantomReturnType { + /// Uncallable method, via an unmet predicate. + pub fn reify(&self) -> Never + where + // Clause needs to involve some lifetime parameter in order not to + // cause a `trivial_bounds` eager error. + // `for<'trivial>` is the "typical" workaround so far. + for<'trivial> Never : sealed::SupportedReturnType, + { + unreachable!() + } + } + + mod sealed { + #[diagnostic::on_unimplemented( + message = "\ + `make_guard!()` cannot be used in a diverging/`!`-returning function\ + ", + label = "encompassing functions \"diverges\", e.g., returns `-> !`", + note = "\ + `make_guard!()` temporary and lifetime shenanigans, on which its soundness model hinges, \ + are broken whenever both a diverging expression follows the `make_guard!()` statement(s), \ + and also if the return type of the encompassing function is `!`, for technical reasons. \ + \n\ + \n\ + To this day, no workaround is known, so there is no other choice but to reject the \ + `-> !`-returning function case: it is quite niche, and sacrificing it allows every \ + other single instance of `make_guard!()` to remain sound.\ + \n\ + See https://github.com/CAD97/generativity/issues/15 for more info. + ", + )] + pub trait SupportedReturnType {} + } +} + #[cfg(test)] mod test { use super::*; diff --git a/tests/ui/diverging_fn.rs b/tests/ui/diverging_fn.rs new file mode 100644 index 0000000..124f459 --- /dev/null +++ b/tests/ui/diverging_fn.rs @@ -0,0 +1,17 @@ +use generativity::{Id, make_guard}; + +fn assert_eq_lt<'id>(_: Id<'id>, _: Id<'id>) {} + +fn diverging_fn() -> ! { + make_guard!(g_a); + make_guard!(g_b); + + let a: Id = g_a.into(); + let b: Id = g_b.into(); + + assert_eq_lt(a, b); + + loop {} +} + +fn main() {} diff --git a/tests/ui/diverging_fn.stderr b/tests/ui/diverging_fn.stderr new file mode 100644 index 0000000..3b72541 --- /dev/null +++ b/tests/ui/diverging_fn.stderr @@ -0,0 +1,75 @@ +error: unreachable expression + --> tests/ui/diverging_fn.rs:6:5 + | +6 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ + | | + | unreachable expression + | any code following this expression is unreachable + | +note: the lint level is defined here + --> tests/ui/diverging_fn.rs:6:5 + | +6 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `make_guard!()` cannot be used in a diverging/`!`-returning function + --> tests/ui/diverging_fn.rs:6:5 + | +6 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ encompassing functions "diverges", e.g., returns `-> !` + | + = help: the trait `__private::sealed::SupportedReturnType` is not implemented for `!` + = note: `make_guard!()` temporary and lifetime shenanigans, on which its soundness model hinges, are broken whenever both a diverging expression follows the `make_guard!()` statement(s), and also if the return type of the encompassing function is `!`, for technical reasons. + + To this day, no workaround is known, so there is no other choice but to reject the `-> !`-returning function case: it is quite niche, and sacrificing it allows every other single instance of `make_guard!()` to remain sound. + See https://github.com/CAD97/generativity/issues/15 for more info. + +note: required by a bound in `PhantomReturnType::< ! as __private::never_say_never::FnPtr>::Never>::reify` + --> src/lib.rs + | + | pub fn reify(&self) -> Never + | ----- required by a bound in this associated function +... + | for<'trivial> Never : sealed::SupportedReturnType, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PhantomReturnType::< ! as FnPtr>::Never>::reify` + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unreachable expression + --> tests/ui/diverging_fn.rs:7:5 + | +7 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ + | | + | unreachable expression + | any code following this expression is unreachable + | +note: the lint level is defined here + --> tests/ui/diverging_fn.rs:7:5 + | +7 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: `make_guard!()` cannot be used in a diverging/`!`-returning function + --> tests/ui/diverging_fn.rs:7:5 + | +7 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ encompassing functions "diverges", e.g., returns `-> !` + | + = help: the trait `__private::sealed::SupportedReturnType` is not implemented for `!` + = note: `make_guard!()` temporary and lifetime shenanigans, on which its soundness model hinges, are broken whenever both a diverging expression follows the `make_guard!()` statement(s), and also if the return type of the encompassing function is `!`, for technical reasons. + + To this day, no workaround is known, so there is no other choice but to reject the `-> !`-returning function case: it is quite niche, and sacrificing it allows every other single instance of `make_guard!()` to remain sound. + See https://github.com/CAD97/generativity/issues/15 for more info. + +note: required by a bound in `PhantomReturnType::< ! as __private::never_say_never::FnPtr>::Never>::reify` + --> src/lib.rs + | + | pub fn reify(&self) -> Never + | ----- required by a bound in this associated function +... + | for<'trivial> Never : sealed::SupportedReturnType, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `PhantomReturnType::< ! as FnPtr>::Never>::reify` + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/diverging_fn_cursed.rs b/tests/ui/diverging_fn_cursed.rs new file mode 100644 index 0000000..993f9c7 --- /dev/null +++ b/tests/ui/diverging_fn_cursed.rs @@ -0,0 +1,18 @@ +use generativity::{Id, make_guard}; +use never_say_never::Never; + +fn assert_eq_lt<'id>(_: Id<'id>, _: Id<'id>) {} + +fn diverging_fn_cursed() -> (Never, Never) { + make_guard!(g_a); + make_guard!(g_b); + + let a: Id = g_a.into(); + let b: Id = g_b.into(); + + assert_eq_lt(a, b); + + loop {} +} + +fn main() {} diff --git a/tests/ui/diverging_fn_cursed.stderr b/tests/ui/diverging_fn_cursed.stderr new file mode 100644 index 0000000..e2b3f25 --- /dev/null +++ b/tests/ui/diverging_fn_cursed.stderr @@ -0,0 +1,41 @@ +error: unreachable expression + --> tests/ui/diverging_fn_cursed.rs:8:5 + | +8 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ + | | + | unreachable expression + | any code following this expression is unreachable + | +note: this expression has type `(!, !)`, which is uninhabited + --> tests/ui/diverging_fn_cursed.rs:8:5 + | +8 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/diverging_fn_cursed.rs:8:5 + | +8 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unreachable expression + --> tests/ui/diverging_fn_cursed.rs:7:5 + | +7 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ + | | + | unreachable expression + | any code following this expression is unreachable + | +note: this expression has type `(!, !)`, which is uninhabited + --> tests/ui/diverging_fn_cursed.rs:7:5 + | +7 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/diverging_fn_cursed.rs:7:5 + | +7 | make_guard!(g_a); + | ^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/issue_15_panic_dropck.rs b/tests/ui/issue_15_panic_dropck.rs new file mode 100644 index 0000000..17ec1df --- /dev/null +++ b/tests/ui/issue_15_panic_dropck.rs @@ -0,0 +1,15 @@ +use generativity::{Id, make_guard}; + +fn assert_eq_lt<'id>(_: Id<'id>, _: Id<'id>) {} + +fn main() { + make_guard!(g_a); + make_guard!(g_b); + + let a: Id = g_a.into(); + let b: Id = g_b.into(); + + assert_eq_lt(a, b); + + loop {} +} diff --git a/tests/ui/issue_15_panic_dropck.stderr b/tests/ui/issue_15_panic_dropck.stderr new file mode 100644 index 0000000..414524e --- /dev/null +++ b/tests/ui/issue_15_panic_dropck.stderr @@ -0,0 +1,14 @@ +error[E0716]: temporary value dropped while borrowed + --> tests/ui/issue_15_panic_dropck.rs:7:5 + | +7 | make_guard!(g_b); + | ^^^^^^^^^^^^^^^^ creates a temporary value which is freed while still in use +... +15 | } + | - + | | + | temporary value is freed at the end of this statement + | borrow might be used here, when `lifetime_brand` is dropped and runs the `Drop` code for type `LifetimeBrand` + | + = note: consider using a `let` binding to create a longer lived value + = note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info)