Whatever macro solution is come up with, it’s going to be shortchanged for not having type information. It’s probably possible to work around parts of it, but I think it needs type information to be complete.
For the generic/monomorphization problem, consider:
tco! {
pub fn foo() -> Box<dyn Display> {
if rand() {
spam("hi")
} else {
spam("hi".to_string())
}
}
fn spam(d: impl Display) -> Box<dyn Display> {
Box::new(d)
}
}
Never mind that this is a stupid use of TCO, it’s just plain old inlining, this would require any trampoline setup to have multiple versions of the generic fn for every type it’s called with.
Using a trampoline:
enum NextCall {
foo(,),
spam(impl Display,),
__DONE(Box<dyn Display>)
}
pub fn foo() -> Box<dyn Display> {
trampoline(NextCall::foo())
}
fn trampoline(mut next: NextCall) -> Box<dyn Display> {
fn foo() -> NextCall {
if rand() {
NextCall::spam("hi")
} else {
NextCall::spam("hi".to_string())
}
}
fn spam(d: impl Display) -> NextCall {
NextCall::__DONE(Box::new(d))
}
loop {
next = match next {
NextCall::foo() => foo(),
NextCall::spam(d) => spam(d),
NextCall::__DONE(ret) => return ret,
}
}
}
The problem is that our unfolded state needs to be able to hold everything that the call might be called with. This is easily done via monomorphization, but this is impossible to do purely via syntax.
Relatedly, any syntax-based macro solution is going to mess up in some cases around shadowing and finding the tco’d fns via paths other than just naming them. A type-aware plugin could use resolve to actually do the right thing in all cases.