Hi! We have a project where, in some code, we want to have a new type of integers (unbounded integers) that can be created from usual integer literals.
This code is always wrapped inside a macro, so there are a lot of hacks we can do, but I haven't found a way to make this work.
At a high level, what we want is the ability to write:
my_magic_macro! {
let x1: u32 = 1;
let x2 = 1 as usize;
let _ = x1 == 2;
let _ = x2 + 3;
let x3 = 4; // fallback on i32
let i1: Int = 1;
let i2 = i1 + 3; // 3 has type Int here
assert!(i2 == 4);
}
Assuming the macro wraps each literal in a function call, I tried using a trait like this:
pub trait IntLit<T> {
fn make_value(self) -> T;
}
/// A type of unbounded integers
pub struct Int(());
macro_rules! impl_int_lit {
( $($t:ty)+ ) => {
$(
impl IntLit<$t> for $t {
fn make_value(self) -> $t {
self
}
}
)+
};
}
impl_int_lit! { u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize }
impl IntLit<Int> for i128 {
fn make_value(self) -> Int {
Int(())
}
}
But it does not work in many cases: e.g. 1u32 + IntLit::make_value(1)
fails trait resolution.
Does anyone have an idea of how to make this work ?
Erm, maybe it's just me, but it's a bit hard to understand what are you trying to achieve. As far as I can tell, there are two parts to the problem: macro wrapping every integer literal with some function calls and subsequent trait resolution in the code expanded from macro.
Maybe provide macro expansion for code in question? This way it will be easier to reason about traits involved.
Sure, here is the expanded output of the above macro:
let x1: u32 = IntLit::make_value(1);
let x2 = IntLit::make_value(1) as usize;
let _ = x1 == IntLit::make_value(2);
let _ = x2 + IntLit::make_value(3);
let x3 = IntLit::make_value(4); // fallback on i32
let i1: Int = IntLit::make_value(1);
let i2 = i1 + IntLit::make_value(3); // 3 has type Int here
assert!(i2 == IntLit::make_value(4));
Note that this is one possible expansion of the macro, and the one I am currently trying. I can accept some other function wrapping/trait mechanism instead.
In the end, my goal is to make the code inside the macro work seamlessly: code using normal integers should continue to work normally, but writing let x: Int = 1;
should also work (I know this is probably impossible in the general case, but I want to get as close as possible).
I think I got it. Basically you want to be able to write
magic_macro!{
let i: Int = 42;
}
or even
fn func(_i: Int) { }
magic_macro!{
func(42);
}
Without needing to wrap every usage of Int
type in struct construction. The problem is that you don't necessarily have any syntactic queues for when the literal is intended to be of Int
type. But wrapping every integer literal in "identity" conversion unless it's supposed to be the Int
doesn't work because type inference in this case works differently and in the case IntLit::make_value(41u32) == IntLit::make_value(1)
second operand is inferred as i32
instead of u32
.
Now that I understand the problem, I can confidently say, that I don't know how to solve it, or is it possible. Sorry
Personally I'd try to implement "bigint literal" like 42bi
and make your macro just convert this literal to constructing your Int
.