I'm going to backpedal a bit on this proposal.
Consider my proposed definitions:
macro int @foo2(int v, int w)
macro @foo(int v)
macro int @foo3(int a, {} body)
First the parameters. I think it's reasonable that we could mix both actual real values / variables, and placeholders. So for this, let's use the & character to indicate a variable that is taken from the parent scope.
This means
macro @foo(int v) should be
macro @foo(int &v).
Secondly, in order to make things easier I suggest dropping the type unless it is passed by value. This further modifies it to
macro @foo(&v) if type is required for readability (and perhaps strict matching), then we can use "auto":
macro @foo(auto &v). That would allow strict typing, like
macro @foo(i32 &v), but this type would be strict, so it would fail to compile if the variable is not i32 (i64, i16, u32 would all be disallowed) in this case.
With this, we can add pass-by-value:
macro @foo(i32 v). Here the macro would work identical to an inlined function. Not very useful. The usefulness comes from combining it with body and variable references:
macro @foo(i32 v, auto &b, {} body) in usage:
@foo(32 * a, c) { print("."); }. Here 32 * a is guaranteed to be evaluated once.
We should also consider nested macros, that is, we can give a macro and expand it similar to the "body" here. In fact, we might be able to consider the {} as a macro that's passed in to the macro and expanded as the macro renders.
To summarize:
macro @foo1(i32 a, i32 &b) { ... } // Works like you would expect from C++ if this was an inlined function. b must be i32.
macro @foo2(i32 a, auto &b) { ... } // Like above but the b type can be anything (unlike for a function)
macro @foo3(auto a, auto &b) { ... } // Both a and b have wildcard types
macro i32 @foo4(auto a, auto &b) { ... } // Same as above, but is guaranteed to return an i32
macro auto @foo5(auto a, auto &b) { ... } // Return type determined during macro expansion
(The advantage of the "@" in front of the name is that we don't need to declare @foo1 / @foo2 / @foo3 as void to make parsing simple)
Another thing is the amount of analysis. I wonder if it is a good idea to have much typing on the macros. The more statically typed, the more complex it is to express the type restrictions, and that complexity both makes it harder to use and harder to read. Let's use the strength of C as a weakly typed language and don't go too far. I'm thinking that typed macros (ones that does not use auto in the examples above) should be few rather than many. Semantic analysis on the macros can be done by assuming wildcard types and just check that "there could be a type that satisfies this", trying to hit the sweet spot between Cs text macros and more complex macro systems.
It should be noted that LISP, hailed for its powerful and useful macro system, is completely untyped when it expresses a macro. One of the things that really stand out when reading the examples on C2 is that the code is very clean and simple code compared to many other recent languages out there, and it would be nice to keep that for the macro system as well if possible.