Expand description
Input capture and source mapping.
The whole point of cinrs is that a diagnostic produced anywhere in the
pipeline — by our own lexer/parser/sema, or by rustc on the code we
generate — must point at the exact C token that caused it. To make that
possible the front end never works on a TokenStream directly: it first
recovers a plain-text C translation unit plus a SourceMap that can turn
any byte offset in that text back into a proc_macro2::Span.
§Capture strategies
There are two shapes of input, and the raw-token shape has two strategies:
-
String-literal mode (
InputMode::StringLiteral). The entire macro input is a single Rust string literal ("…",r"…",r#"…"#). We unescape it and use the text as-is. This accepts any C code, including the lexemes the Rust 2024 lexer rejects (hex float literals,'ab',L"…",\line continuations,##). The catch is that stable Rust has no way to build a span pointing inside a string literal (Literal::subspanis unstable), so every diagnostic is reported at the literal as a whole and the line/column inside the C text is appended to the message instead. Such a file is flagged as notSourceFile::precise— unless the caller hands capture aSubspanhook, which is what thenightlyfeature ofcinrs-macrosdoes; seeSubspan. -
Raw-token mode, primary path (
InputMode::FileSlice). We flatten the token trees, ask the first token forSpan::local_file, read that.rsfile from disk and slice outfirst.start() .. last.end(). This gives back the exact original text, including whitespace, newlines and comments — which the preprocessor needs, since it is line-oriented and since#errorreproduces what was written. The slice is validated against every token’sSpan::source_textbefore it is trusted. -
File mode (
InputMode::CFile), which is whatinclude_c99!("…")captures withcapture_c_file: the text is a.cfile read from disk, and nothing in the.rsfile corresponds to a position in it, so every range resolves to the span of the macro invocation and the file names its own position in the message — exactly as an#included header does. -
Raw-token mode, search path (also
InputMode::FileSlice). A host may hand a procedural macro tokens with no positions at all:rust-analyzerreports no file, no source text and line 1 column 0 for every token alike. The text is still on disk, and which text it is can be proved — thelocatemodule searches the crate’s.rsfiles, the directoryCARGO_MANIFEST_DIRnames, for an invocation whose token sequence is exactly the one we were handed. A match gives the same three things the primary path gives (path, text, anchors), so everything downstream — diagnostics,__FILE__, a quoted#include,include_str!tracking, the unit id — is unchanged. SeeOriginfor what capture has to be told for this to be possible, and note that it can only run when the primary path found no position whatsoever: in a normal build it costs nothing because it never happens. -
Raw-token mode, fallback path (
InputMode::Reconstructed). When there is no local file (macro-generated input, some IDE contexts such as rust-analyzer, or unit tests that build aTokenStreamwithTokenStream::from_str), or when the slice fails validation, we rebuild the text from the tokens. Where their positions are usable, every token goes at its original line/column, padded with newlines and spaces: comments are lost, but the line structure — and therefore all reported positions — survives. Where they are not (every token at the same position, which is whatrust-analyzergives an unsaved buffer), the text is rebuilt from the tokens alone: one space between two tokens, none where the host says they were written together, so that->,<<=and++survive and- -stays two tokens. A directive is a line, and tokens keep no lines; the forms whose end the tokens themselves give away (#include <…>,#ifdef X,#endif, …) are written on a line of their own and anything else —#define,#if— is one clear diagnostic rather than a guess.
§Coordinates
SourceMap owns any number of SourceFiles in a single, global byte
offset space: file i occupies base .. base + text.len(). A Pos is
therefore enough to identify both a file and an offset inside it, which is
what will let #include drop extra files into the same map without
changing a single signature in the lexer, parser or AST.
A file also remembers which .rs file it came from and which line of it
the text’s own line 1 sits on, which is what the preprocessor’s __FILE__
and __LINE__ are made of.
Structs§
- FileId
- Identifies one file inside a
SourceMap. - Origin
- What the host can tell the front end about an invocation, over and above the tokens themselves.
- Source
- The captured macro input: a
SourceMapplus the id of the root file. - Source
File - A single file of C source together with its Rust-token anchors.
- Source
Map - Maps byte offsets back to
proc_macro2spans. - Source
Range - A half-open range
[start, end)in aSourceMap’s global offset space. - Subspan
- Turns a byte range of the macro input’s source spelling into a span pointing exactly at those bytes.
Enums§
- Input
Mode - How the C source text of a file was obtained.
Functions§
- capture
- Recovers the C source text of a macro invocation.
- capture_
c_ file - Makes a
.cfile read from disk the source of a translation unit. - capture_
with - Recovers the C source text of a macro invocation, with everything the host can say about the invocation itself.
- invocation_
directory - The directory of the
.rsfile an invocation whose whole input isinputis written in, found by searching the crate’s sources. - string_
literal_ value - The text a
Literaldenotes, when it is a plain or raw string literal.