Macro naming and modularisation by nrc · Pull Request #1561 · rust-lang/rfcs (original) (raw)
I don't think this is a good idea, because it won't play nice with macro-defining macros
This is definitely an important point, thanks for raising it. In fact, I think the system as we've envisioned it plays great with macro-defining macros, but there are some patterns in Racket that will not work. Let's start with your example:
macro_rules! macro_defining { ($name:ident) => { macro_rules! $name { () => (println!("Yay!");) } } } macro_defining!( say_yay ); say_yay!();
This interacts with the overall name resolution algorithm defined in #1560. The idea is roughly that expansion and name resolution is a process that occurs in rounds. In this case, in the first round, the path say_yay!
will be "unresolved" -- because there is no definition for it. However, macro_defining!
will resolve and expand. In the next round, then, say_yay
will resolve. Errors are only reported if we have expanded all macros and still have paths that cannot be resolved at the end of the process.
One danger here is that expanding a macro may cause a path that resolved in the past to resolve differently. Mostly this isn't possible, because expanding a macro can only add items, and duplicate items cause errors. However, one case where it can cause trouble is around globs and shadowing -- if a path relies on item X
that is later shadowed by another item X
produced by a macro, then it resolves differently. To counter this, we make it an error (in #1560) for a macro to expand to an item X
that shadows an item in an outer scope, at least if that item was used in a macro expansion path. (See @jseyfried's comment for more details.)
Personally I find the idea of item ordering being significant in Rust to be very unappealing. It is strikingly different from how most things in Rust work, and it particularly doesn't mix well with use statements in my mind. (Perhaps the ordering could be some sort of DAG derived from the use
statements, but my efforts to find such a system that works out well mostly failed.)
But this does make it more difficult to have "comunicating" macros, which I understand in Racket at least often rely on expansion order. An example that I was given by @samth where this could break Racket patterns of macro interop was something about pattern matching. I think the idea was that a class!
macro might generate some metadata that is later used by a match!
macro (in Racket, I mean), and that this introduces an ordering dependency. However, he also added that in practice macros like match!
would generally try to "reschedule" themselves to run late in the expansion process so that the relative ordering of a class declaration and a match wouldn't be important. (I don't remember more details than that.)
I think a potential example of who this could come into play in Rust might be that, e.g., a macro might want to access data from a type definition. (Perhaps one would be expected e.g. to annotate the type definition in one way, and this would enable uses elsewhere that "communicate" with that annotation to extract data about the types of the fields, or something.) This is not well supported in the current system, which is targeted at more "self-contained" macros.
I'm not sure what's a "general" fix here but I think a lot will depend on the details. For example, if the "uses" that wish to communicate with the definition are inside of fns or other code blocks, I expect we can do things to ensure that all module-levels macros and items are expanded first (given that names defined in fns can't be used from outside that fn).
I can also imagine patterns where a module is decorated (#[foo] mod { ... }
) -- this would then allow code in the module to reference data generated by the #[foo]
macro. This works just by default because the code that will reference what #[foo]
generates is ultimately governed by foo
and results from the expansion of foo
itself.
Finally, we might develop somewhat more ad-hoc solutions, similar to how Racket macros might "reschedule" themselves to make themselves order independent, where a macro can ask to be deferred from executing if it finds that other macros it might depend on are not yet expanded.
In short, I'd rather tackle this in a declarative way than relying on the "AST ordering", which I don't think is a natural concept in Rust.
(As one last point, I'd sort of like to get rid of explicit mod
declarations, at least for private modules, and instead infer their presence from the file-system -- but if we did so, it would interact very poorly with having a defined AST ordering.)