Summary: No, it's not easily possible, even with procedural macros. And I actually think you shouldn't write such a thing, even if it's possible. Just let your macro evaluate to a Result and let the user deal with it.
Function-like macro
Rust macros, procedural and declarative, only have access to their input stream: just a list of tokens. For function-like macros (the ones you invoke via foo!(...)), the input is just what you pass to them. So you could manually pass the return type:
macro_rules! foo {
(Result $($stuff:tt)*) => { return Err(()); };
($($stuff:tt)*) => { panic!(); };
}
fn returns_result() -> Result<String, ()> {
foo!(Result<(), ()>); // will return `Err`
Ok("hi".into())
}
fn returns_string() -> String {
foo!(String); // will panic
"hi".into()
}
But I guess that is not what you want: the user would have to manually specify the return type for each macro invocation.
The same goes for procedural macros that are invoked this way.
Procedural macro attribute
Can we define a procedural macro where the return type of the function is in the input token stream? Yes, preferably via a proc-macro attribute. If you define such an attribute bar, you could write this:
#[bar]
fn returns_result() -> Result<String, ()> { ... }
And your procedural macro would receive the whole function definition as input, including the return type. But what are you going to do with that information?
You can change the whole function as you like, so one idea would be to search for all foo!() macro invocations in the function and replace them with return Err or panic!() depending on the return type. That is: do the macro invocation step for your own macros via a procedural macro.
But I think this is a bad idea for several reasons. Most importantly, I don't think it's well defined when the compiler calls the procedural macro. So the compiler could attempt to invoke your foo!() macros before calling the procedural macro.
So it could work via procedural macro, but not in a way that is typical. So it's rather hacky.
What I think is the best solution
Lastly, how would I do it? Let your macro evaluate to a Result. Then the user can easily decide themselves what to do with it. If they return a Result, they just need to add ?. If they don't, they have the freedom to choose between .unwrap(), expect() and other ways to panic.
I understand why you are trying to do what you want to do (it's easier for the user and comfortable), but I think it's not a good idea. It probably comes down to "spooky action at a distance": what a macro in your function does suddenly depends on the return type of that function. That means when you change that, the whole semantics of the function change. This sounds like something you could shoot yourself in the foot with very easily. That's also probably the reason why it's not easy in Rust.