New literal integer type in macro

Hi! We have a project[1] 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 ?


  1. The project is Creusot, and we want to be able to write 1 in logic code to refer both to the usual integers and the mathematical, unbounded integer 1. Wrapping literals as in Int(1) is really awkward. ↩︎

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 :confused:

Personally I'd try to implement "bigint literal" like 42bi and make your macro just convert this literal to constructing your Int.