Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ categories = ["no-std", "rust-patterns"]
license = "MIT OR Apache-2.0"

[dev-dependencies]
never-say-never = "6.6.666"

[dependencies]
trybuild = "1.0.110"
94 changes: 94 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,103 @@ macro_rules! make_guard {
#[allow(unused)]
let lifetime_brand = unsafe { $crate::LifetimeBrand::new(&branded_place) };
let $name = unsafe { $crate::Guard::new(branded_place) };
if false {
// Prelude for poorman's specialization.
#[allow(unused)]
use $crate::__private::DefaultGet as _;
#[allow(unreachable_code)] {
let phony = $crate::__private::Phony;
if false {
// Help inference make out that the type param here is that
// of the return type.
return $crate::__private::DefaultGet::get(&phony);
}
// Guarding against `Phony<!>` itself does not suffice, we may
// be dealing with `Phony<(!, !)>`, 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::DefaultGet::get(&phony);
#[forbid(unreachable_code)] {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware we end up in a rather silly #[allow(X)] … #[forbid(X)] situation, here. I've considered removing the allow, but then the lint fires a bunch of times. The way it is done currently, it fires exactly once, so it does yield slightly better DX.

if false {}
}
}
return phony.get();
}
}
};
}

#[doc(hidden)]
/// NOT STABLE PUBLIC API. Used by the expansion of [`make_guard!`].
pub mod __private {
/// Custom `PhantomData` pattern.
///
/// See https://docs.rs/ghost or
/// https://github.com/dtolnay/case-studies/tree/master/unit-type-parameters
/// for more info.
pub use custom_phantom::Phony;
mod custom_phantom {
pub enum Phony<T> {
Phony,
// uninhabited ZST payload to make this branch itself uninhabited,
// and `Self`, a ZST.
_PhantomVariant(super::Never, ::core::marker::PhantomData<T>),
}
// Bring `self::Phony::Phony` from the value namespace into scope, but
// don't make `self::Phony::Phony {}` from the type namespace clash.
pub use self::Phony::*;
}

/// Inlined [`::never-say-never`](https://docs.rs/never-say-never).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably meant to remove this since you added never-say-never as a dependency

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a dev-dependency only: I imagine this crate would like to keep its 0-dependencies aspect (but that's for @CAD97 to decide).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah — if the Never were part of the critical public API, I'd make the semi-canonical crate the way we achieve it. But since it's purely to enhance diagnostics, inlining the trick to name the type seems preferable to me.

mod never_say_never {
pub trait FnPtr { type Never; }
impl<R> FnPtr for fn() -> R { type Never = R; }
}
type Never = <fn() -> ! as never_say_never::FnPtr>::Never;

/// Poorman's specialization for `Phony::<default T / override !>::get()`.
pub trait DefaultGet<T> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name the trait to something like Retifier and the trait method to retify

Copy link
Contributor Author

@danielhenrymantilla danielhenrymantilla Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I now find .reify() to be a rather nice method name! The ret part, however, is always external/outside this type in and of itself (return phantom.reify();).

  • 🤔 tangentially, we could rename Phony as Phantom, or even PhantomReturnType: return phantom_ret.reify();

EDIT: went with that in e104bcc (#16)

fn get(&self) -> T { unreachable!() }
}

impl<T> DefaultGet<T> for Phony<T> {}

impl Phony<Never> {
/// Uncallable method, via an unmet predicate.
pub fn get(&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\", i.e., 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.\

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this note adds more noise than being helpful. I think it suffices to replace this with "see issue #15"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a "see https://github.com/CAD97/generativity/issues/15 for more info" at the end.

Whilst I do personally think we should not underestimate the burden of having to click a link while having internet connectivity, I am amenable to reducing the note to just the link reference, if that's what ends up preferred.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, let's just wait for CD's opinion.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this error should show up super rarely, I'm of the opinion that getting more context inline is preferable; it also serves as in-VCS record of the reasoning.

For anything more prevalent, I'd make it more concise. But it's also worth noting that we can't provide an --explain extended description, so our trade-off will be different than rustc/clippy.

",
)]
pub trait SupportedReturnType {}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
17 changes: 17 additions & 0 deletions tests/ui/diverging_fn.rs
Original file line number Diff line number Diff line change
@@ -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() {}
73 changes: 73 additions & 0 deletions tests/ui/diverging_fn.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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", i.e., 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.
= help: the trait `__private::sealed::SupportedReturnType` is implemented for `()`
note: required by a bound in `__private::<impl generativity::__private::Phony<<fn() -> ! as __private::never_say_never::FnPtr>::Never>>::get`
--> src/lib.rs
|
| pub fn get(&self) -> Never
| --- required by a bound in this associated function
...
| for<'trivial> Never : sealed::SupportedReturnType,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__private::<impl Phony<<fn() -> ! as FnPtr>::Never>>::get`
= 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", i.e., 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.
= help: the trait `__private::sealed::SupportedReturnType` is implemented for `()`
note: required by a bound in `__private::<impl generativity::__private::Phony<<fn() -> ! as __private::never_say_never::FnPtr>::Never>>::get`
--> src/lib.rs
|
| pub fn get(&self) -> Never
| --- required by a bound in this associated function
...
| for<'trivial> Never : sealed::SupportedReturnType,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__private::<impl Phony<<fn() -> ! as FnPtr>::Never>>::get`
= note: this error originates in the macro `make_guard` (in Nightly builds, run with -Z macro-backtrace for more info)
18 changes: 18 additions & 0 deletions tests/ui/diverging_fn_cursed.rs
Original file line number Diff line number Diff line change
@@ -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() {}
41 changes: 41 additions & 0 deletions tests/ui/diverging_fn_cursed.stderr
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions tests/ui/issue_15_panic_dropck.rs
Original file line number Diff line number Diff line change
@@ -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 {}
}
14 changes: 14 additions & 0 deletions tests/ui/issue_15_panic_dropck.stderr
Original file line number Diff line number Diff line change
@@ -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)
Loading