Skip to content

Instantly share code, notes, and snippets.

@Nadrieril
Created November 23, 2023 22:35
Show Gist options
  • Save Nadrieril/10d909b02e07ae493db2fe98ce48c715 to your computer and use it in GitHub Desktop.
Save Nadrieril/10d909b02e07ae493db2fe98ce48c715 to your computer and use it in GitHub Desktop.
Test the method selection algorithm of `arbitrary_self_types`
#![feature(arbitrary_self_types)]
#![allow(dead_code)]
macro_rules! assert_priority {
(prefer method on $expected:ident given: $($rest:tt)*) => {
{
// Define one wrapper per entry, with any `foo` methods specified.
assert_priority!(@define_ptrs, $($rest)*);
// This is the `P<Q<...>>` type we want to play with.
type Combined = assert_priority!(@combined_type, $($rest)*);
// Use `Default` to build a `P<Q<...>>` value.
let x = <Combined as Default>::default();
// Call `foo()` and check we got the expected method.
assert_eq!(x.foo(), stringify!($expected));
}
};
// Define one `P<_>` per entry.
(@define_ptrs, foo($arg:ty) on $name:ident, $($rest:tt)*) => {
// Add the `foo($arg)` impl if any.
impl<S> $name<S> {
pub fn foo(self: $arg) -> &'static str {
stringify!($name)
}
}
assert_priority!(@define_ptrs, no foo on $name, $($rest)*);
};
(@define_ptrs, no foo on $name:ident, $($rest:tt)*) => {
// Define a simple wrapper with `Deref`.
#[derive(Default)]
pub struct $name<S>(pub S);
impl<S> std::ops::Deref for $name<S> {
type Target = S;
fn deref(&self) -> &S {
&self.0
}
}
assert_priority!(@define_ptrs, $($rest)*);
};
(@define_ptrs,) => {};
// Construct the type `P<Q<...<()>...>>`
(@combined_type, foo($arg:ty) on $name:ident, $($rest:tt)*) => {
$name<assert_priority!(@combined_type, $($rest)*)>
};
(@combined_type, no foo on $name:ident, $($rest:tt)*) => {
$name<assert_priority!(@combined_type, $($rest)*)>
};
(@combined_type,) => {()};
}
fn main() {
// For each case, we:
// - define `struct Arc<T>(T)` that derefs to `T` and has a `foo(self: $ty)`.
// - define `struct Box<T>(T)` that derefs to `T` and has a `foo(self: $ty)`.
// - define a struct `T` that has a `foo(self: $ty)`.
// - construct `x: Arc<Box<T>>` and call `x.foo()`. The output of `x.foo()` tells us
// which method was chosen.
// Note: the macro accepts other names instead of Box/Arc/T, and as many or as few of them as
// you like. They must be defined outermost first.
// I claim the method selection algorithm is compatible with the following order of priority:
// a. `foo(&Arc<Box<Self>>)` on `T`
// b. `foo(&Arc<Self>)` on `Box<T>`
// c. `foo(&self)` on `Arc<T>`
// d. `foo(&Box<Self>)` on `T`
// e. `foo(&self)` on `Box<T>`
// f. `foo(&self)` on `T`
// In order to check that, I test all compatible pairs below. We get the following priority
// orderings:
// - a < e
// - b < d
// - b < f
// - c < d (that's the annoying case imo)
// - c < e < f (that's just the normal `Deref` order)
// All other pairs are ambiguous or incompatible.
// ERROR: ambiguous
// assert_priority!(
// prefer method on T given:
// no foo on Arc,
// foo(&Arc<Self>) on Box, // b
// foo(&Arc<Box<Self>>) on T, // a
// );
// ERROR: ambiguous
// assert_priority!(
// prefer method on T given:
// foo(&Self) on Arc, // c
// no foo on Box,
// foo(&Arc<Box<Self>>) on T, // a
// );
assert_priority!(
prefer method on T given:
no foo on Arc,
foo(&Self) on Box, // e
foo(&Arc<Box<Self>>) on T, // a
);
assert_priority!(
prefer method on Box given:
no foo on Arc,
foo(&Arc<Self>) on Box, // b
foo(&Box<Self>) on T, // d
);
// ERROR: ambiguous
// assert_priority!(
// prefer method on Box given:
// foo(&Self) on Arc, // c
// foo(&Arc<Self>) on Box, // b
// no foo on T,
// );
assert_priority!(
prefer method on Box given:
no foo on Arc,
foo(&Arc<Self>) on Box, // b
foo(&Self) on T, // f
);
assert_priority!(
prefer method on Arc given:
foo(&Self) on Arc, // c
no foo on Box,
foo(&Box<Self>) on T, // d
);
// ERROR: ambiguous
// assert_priority!(
// prefer method on T given:
// no foo on Arc,
// foo(&Self) on Box, // e
// foo(&Box<Self>) on T, // d
// );
assert_priority!(
prefer method on Arc given:
foo(&Self) on Arc, // c
foo(&Self) on Box, // e
foo(&Self) on T, // f
);
}
@Nadrieril
Copy link
Author

I whipped that out in the discussion on method shadowing in the Arbitrary Self Types v2 RFC (here) and I thought it was too cool to lose.

Try it on the rust playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment