diff options
| author | mark <markm@cs.wisc.edu> | 2020-04-30 12:36:16 -0500 |
|---|---|---|
| committer | Who? Me?! <mark-i-m@users.noreply.github.com> | 2020-05-08 09:42:27 -0500 |
| commit | 60f177e87e0273ab613c81c857a9b262ce9c571d (patch) | |
| tree | 05994297f6c3f8e615a1e0ee51953420faf4d767 /src/doc/rustc-dev-guide | |
| parent | 710677476cefb57252d3f8923b8ee0e1d6891675 (diff) | |
| download | rust-60f177e87e0273ab613c81c857a9b262ce9c571d.tar.gz rust-60f177e87e0273ab613c81c857a9b262ce9c571d.zip | |
expand some notes about expansion :P
Diffstat (limited to 'src/doc/rustc-dev-guide')
| -rw-r--r-- | src/doc/rustc-dev-guide/src/macro-expansion.md | 120 | ||||
| -rw-r--r-- | src/doc/rustc-dev-guide/src/name-resolution.md | 23 |
2 files changed, 104 insertions, 39 deletions
diff --git a/src/doc/rustc-dev-guide/src/macro-expansion.md b/src/doc/rustc-dev-guide/src/macro-expansion.md index 27d9d45d1f8..f076ed5fa48 100644 --- a/src/doc/rustc-dev-guide/src/macro-expansion.md +++ b/src/doc/rustc-dev-guide/src/macro-expansion.md @@ -14,45 +14,87 @@ we will look at the specifics of expanding different types of macros. ## Expansion and AST Integration -TODO: expand these notes (har har)... - -- Expansion happens over a whole crate at once. -- We run `fully_expand_fragment` on the crate - - If `fully_expand_fragment` is run not on a whole crate, it means that we are performing eager expansion. - - We do this for some built-ins that expect literals (not exposed to users). - - It performs a subset of actions performed by non-eager expansion, so the discussion below focuses on eager expansion. - - Original description here: https://github.com/rust-lang/rust/pull/53778#issuecomment-419224049 - - Algorithm: `fully_expand_fragment` works in iterations. We repeat until there are no unresolved macros left. - - Resolve imports in our partially built crate as much as possible. - - (link to name-resolution chapter) names resolved from "closer" scopes (e.g. current block) to further ones (e.g. prelude) - - A resolution fails differently for different scopes, e.g. for a module scope it means no unexpanded macros and no unresolved glob imports in that module. - - Collect as many macro invocations as possible from our partially built crate - (fn-like, attributes, derives) from the crate and add them to the queue. - - Take a macro from the queue, and attempt to resolve it. - - If it's resolved - run its expander function that consumes tokens or AST and produces tokens or AST (depending on the macro kind). (If it's not resolved, then put it back into the queue.) - - At this point, we know everything about the macro itself and can call `set_expn_data` to fill in its properties in the global data -- that is the hygiene data associated with `ExpnId`. - - The macro's expander function returns a piece of AST (or tokens). We need to integrate that piece of AST into the big existing partially built AST. - - If the macro produces tokens (e.g. a proc macro), we will have to parse into an AST, which may produce parse errors. - - During expansion, we create `SyntaxContext`s (heirarchy 2). - - This is essentially where the "token-like mass" becomes a proper set-in-stone AST with side-tables - - These three passes happen one after another on every AST fragment freshly expanded from a macro - - `NodeId`s are assigned by `InvocationCollector` - - also collects new macro calls from this new AST piece and adds them to the queue - - def_paths are created and `DefId`s are assigned to them by `DefCollector` - - `Name`s are put into modules (from the resolver's point of view) by `BuildReducedGraphVisitor` - - After expanding a single macro and integrating its output continue to the next iteration of `fully_expand_fragment`. - - If we make no progress in an iteration, then we have reached a compilation error (e.g. an undefined macro). - - - We attempt to recover from failures (unresolved macros or imports) for the sake of diagnostics - - recovery can't cause compilation to suceed. We know that it will fail at this point. - - we expand errors into `ExprKind::Err` or something like that for unresolved macros - - this allows compilation to continue past the first error so that we can report more errors at a time - -### Relationship to name resolution - -- name resolution is done for macro and import names during expansion and integration into the AST, as discussed above -- For all other names we certainly know whether a name is resolved successfully or not on the first attempt, because no new names can appear, due to hygiene - - They are resolved in a later pass, see `librustc_resolve/late.rs` +First of all, expansion happens at the crate level. Given a raw source code for +a crate, the compiler will produce a massive AST with all macros expanded, all +modules inlined, etc. + +The primary entry point for this process is the +[`MacroExpander::fully_expand_fragment`][fef] method. Usually, we run this +method on a whole crate. If it is not run on a full crate, it means we are +doing _eager macro expansion_. Eager expansion means that we expand the +arguments of a macro invocation before the macro invocation itself. This is +implemented only for a few special built-in macros that expect literals (it's +not a generally available feature of Rust). Eager expansion generally performs +a subset of the things that lazy (normal) expansion does, so we will focus on +lazy expansion for the rest of this chapter. + +At a high level, [`fully_expand_fragment`][fef] works in iterations. We keep a +queue of unresolved macro invocations (that is, macros we haven't found the +definition of yet). We repeatedly try to pick a macro from the queue, resolve +it, expand it, and integrate it back. If we can't make progress in an +iteration, this represents a compile error. Here is the [algorithm][original]: + +[fef]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_expand/expand/struct.MacroExpander.html#method.fully_expand_fragment +[original]: https://github.com/rust-lang/rust/pull/53778#issuecomment-419224049 + +0. Initialize an `queue` of unresolved macros. +1. Repeat until `queue` is empty (or we make no progress, which is an error): + 0. [Resolve](./name-resolution.md) imports in our partially built crate as + much as possible. + 1. Collect as many macro invocations as possible from our partially built + crate (fn-like, attributes, derives) and add them to the queue. + 2. Dequeue the first element, and attempt to resolve it. + 3. If it's resolved: + 0. Run the macro's expander function that consumes tokens or AST and + produces tokens or AST (depending on the macro kind). + - At this point, we know everything about the macro itself and can + call `set_expn_data` to fill in its properties in the global data + -- that is the hygiene data associated with `ExpnId`. (See [the + "Hygiene" section below][hybelow]). + 1. Integrate that piece of AST into the big existing partially built + AST. This is essentially where the "token-like mass" becomes a + proper set-in-stone AST with side-tables. It happens as follows: + - If the macro produces tokens (e.g. a proc macro), we parse into + an AST, which may produce parse errors. + - During expansion, we create `SyntaxContext`s (heirarchy 2). (See + [the "Hygiene" section below][hybelow]) + - These three passes happen one after another on every AST fragment + freshly expanded from a macro: + - [`NodeId`]s are assigned by [`InvocationCollector`]. This + also collects new macro calls from this new AST piece and + adds them to the queue. + - ["Def paths"][defpath] are created and [`DefId`]s are + assigned to them by [`DefCollector`]. + - Names are put into modules (from the resolver's point of + view) by [`BuildReducedGraphVisitor`]. + 2. After expanding a single macro and integrating its output, continue + to the next iteration of [`fully_expand_fragment`][fef]. + 4. If it's not resolved: + 0. Put the macro back in the queue + 1. Continue to next iteration... + +[defpaths]: https://rustc-dev-guide.rust-lang.org/hir.html?highlight=def,path#identifiers-in-the-hir +[`NodeId`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/node_id/struct.NodeId.html +[`InvocationCollector`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_expand/expand/struct.InvocationCollector.html +[`DefId`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def_id/struct.DefId.html +[`DefCollector`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/def_collector/struct.DefCollector.html +[`BuildReducedGraphVisitor`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/build_reduced_graph/struct.BuildReducedGraphVisitor.html +[hybelow]: #hygiene-and-heirarchies + +If we make no progress in an iteration, then we have reached a compilation +error (e.g. an undefined macro). We attempt to recover from failures +(unresolved macros or imports) for the sake of diagnostics. This allows +compilation to continue past the first error, so that we can report more errors +at a time. Recovery can't cause compilation to suceed. We know that it will +fail at this point. The recovery happens by expanding unresolved macros into +[`ExprKind::Err`][err]. + +[err]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/enum.ExprKind.html#variant.Err + +Notice that name resolution is involved here: we need to resolve imports and +macro names in the above algorithm. However, we don't try to resolve other +names yet. This happens later, as we will see in the [next +chapter](./name-resolution.md). ## Hygiene and Heirarchies diff --git a/src/doc/rustc-dev-guide/src/name-resolution.md b/src/doc/rustc-dev-guide/src/name-resolution.md index f3aacba0017..d08fe43f3ca 100644 --- a/src/doc/rustc-dev-guide/src/name-resolution.md +++ b/src/doc/rustc-dev-guide/src/name-resolution.md @@ -1,5 +1,28 @@ # Name resolution +In the previous chapters, we saw how the AST is built with all macros expanded. +We saw how doing that requires doing some name resolution to resolve imports +and macro names. In this chapter, we show how this is actually done and more. + +In fact, we don't do full name resolution during macro expansion -- we only +resolve imports and macros at that time. This is required to know what to even +expand. Later, after we have the whole AST, we due full name resolution to +resolve all names in the crate. This happens in [`rustc_resolve::late`][late]. +Unlike during macro expansion, in this late expansion, we only need to try to +resolve a name once, since no new names can be added. If we fail to resolve a +name now, then it is a compiler error. + +Name resolution can be complex. There are a few different namespaces (e.g. +macros, values, types, lifetimes), and names my be valid at different (nested) +scopes. Also, different types of names can fail to be resolved differently, and +failures can happen differently at different scopes. For example, for a module +scope, failure means no unexpanded macros and no unresolved glob imports in +that module. On the other hand, in a function body, failure requires that a +name be absent from the block we are in, all outer scopes, and the global +scope. + +[late]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/index.html + ## Basics In our programs we can refer to variables, types, functions, etc, by giving them |
