Pre-RFC: `#[line]` attribute

this attribute sets the base line number for the annotated item, module, or expression.

example:

#[line = 20]
fn main() {
  println!("{}", line!()); // prints 21
}

the primary use case is to annotate doctests to use their proper line numbers.

1 Like

An alternative would for rustdoc tests to use their proper line number without needing a new attribute.

3 Likes

through what mechanism would they do that? i was under the impression that rustdoc turns doctests into valid rust programs through simple text preprocessing.

It could be a purely internal attribute, #[rustc_line] or something. Rustdoc is tightly bound to rustc.

1 Like

that's still a new attribute. i don't see the advantage of making it perma-unstable when this is something that could be useful for any generated code.

The primary advantage is avoiding 6 months of bikeshed, if it’s not intended for stabilization it can just be implemented with whatever semantics are most useful for rustdoc.

2 Likes

We've had requests for this for a while, and we'd generally like to see it happen. The previous round: pre-RFC: Enable setting of source file, line & column - #8 by CAD97

As evidenced by that discussion, this is endlessly bikesheddable.

Summary:

  • Should this be something that appears in a Rust source file, or should it be a separate file of metadata, such as a source map?
  • How can we specify the original source file name, and the line and column numbers?
  • Should this be an attribute or a built-in macro? The latter could be inserted somewhere that isn't an item.
  • What should this be called? And should it be in a namespace (e.g. diagnostic::)?
  • How precisely does this interact with macros?
  • What's the precise specification of which character in the construct has its line/column correspond to what's mentioned in the attribute/macro?

Personally, based on the above-linked thread, I'm tempted by the idea of feeding a source map into the compiler and having this be entirely outside the scope of the text of a Rust source file.

8 Likes

why not both, through a generalized system of allowing attributes to be specified in an external file like i previously proposed.

  1. That's a much more complex mechanism and there isn't consensus we want to add something like that, whereas file/line/column directives, while subject to bikeshedding, are something it's fairly clear we need.
  2. At a minimum, that'd require defining which attributes are both safe and useful to define externally and which aren't, as well as defining a mechanism to map the attributes to what they apply to.
  3. Rust source files should generally be self-contained, and there should be a good reason to do otherwise.
2 Likes
  1. rust source files haven't been truly "self contained" since the extern prelude
  2. these external attributes would not affect the runtime semantics of the program, much like a source map
  3. you were just advocating for source maps as opposed to inline attributes

line!() affects the runtime semantics.

i would think that's would fall under the category of reflection, otherwise wouldn't rustfmt be unable to change macro arguments because stringify!() exists?

stringify!() is already allowed to differ between rustc versions.

In my experience programs that generate source code are much easier to write if they only have to emit one file. So I prefer the idea of "something that appears in a Rust source file".

My gut says attributes would fit the grain of the language better, but a restriction to items might be a serious problem for programs like yacc that switch between synthetic code and user supplied code inside an expression.

2 Likes

I absolutely agree that it's easier for (most) code generators. If we want to do the simplest thing for code generators, it should be a macro in the source code.

The advantage of source maps would be to keep the generated code easier to read because it doesn't have those directives everywhere, for cases where there's value in a human reading it, and to have the source maps be easily read and generated by a variety of established tools. (I started to add another point here, that source maps avoid having to define interactions between a macro and other parts of the language, such as passing a macro-generated argument as a filename, but we could always just prohibit that if we wan to keep the macro simpler for the parser, and require that the arguments be literal strings/integers rather than invocations of anything else.)

That said, those tradeoffs may not be important enough to outweigh the ease of code generation. We could potentially have tools that strip out those directives, or translate between them and source maps. Or, it might make sense to support both: have the attribute for the simple case, have the source map for more complex cases.

I agree; I think it's entirely likely that if we embed something in the source file, it will need to be a macro that can appear anywhere.

And for convenience, the macro should have a simple invocation for "reset to current file/line/column", for cases where the generated code switches between code that came from the user and code that's entirely generated (so the only useful source is the generated one).

3 Likes

I would prefer sourcemaps as the ultimate solution, but that may be a long way off. #[line] seems like a simple addition, so it could be added even if sourcemaps were planned to be added later.

Sourcemaps could be precise enough to specify not just lines, but to map variables from source to the generated code, so they could work in debuggers, and maybe even rust-analyzer.

However, sourcemaps are complicated to generate. This complexity may make them slow to compile or slow to run at build time, so build-time tools could choose not to support source maps anyway. Rust/Cargo may first need to have better caching or distribute pre-built proc macros to enable people to use more complex build-time libraries.

2 Likes

the alternative is making it depend on expression attributes, blocking one nightly feature on another.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.