This is a tracking issue for diagnostics for procedural macros spawned off from https://github.com/rust-lang/rust/issues/38356.
proc_macro_diagnostics
(https://github.com/rust-lang/rust/pull/44125).syn
.The initial API was implemented in https://github.com/rust-lang/rust/pull/44125 and is being used by crates like Rocket and Diesel to emit user-friendly diagnostics. Apart from thorough documentation, I see two blockers for stabilization:
Multi-Span Support
At present, it is not possible to create/emit a diagnostic via proc_macro
that points to more than one Span
. The internal diagnostics API makes this possible, and we should expose this as well.
The changes necessary to support this are fairly minor: a Diagnostic
should encapsulate a Vec<Span>
as opposed to a Span
, and the span_
methods should be made generic such that either a Span
or a Vec<Span>
(ideally also a &[Vec]
) can be passed in. This makes it possible for a user to pass in an empty Vec
, but this case can be handled as if no Span
was explicitly set.
Lint-Associated Warnings
At present, if a proc_macro
emits a warning, it is unconditional as it is not associated with a lint: the user can never silence the warning. I propose that we require proc-macro authors to associate every warning with a lint-level so that the consumer can turn it off.
No API has been formally proposed for this feature. I informally proposed that we allow proc-macros to create lint-levels in an ad-hoc manner; this differs from what happens internally, where all lint-levels have to be known apriori. In code, such an API might look lIke:
rust
val.span.warning(lint!(unknown_media_type), "unknown media type");
The lint!
macro might check for uniqueness and generate a (hidden) structure for internal use. Alternatively, the proc-macro author could simply pass in a string: "unknown_media_type"
.
I'd argue that support for associating warnings with lints should be a separate RFC, and shouldn't block moving forward with unsilenceable warnings (with the expectation that anything to associate warnings with lints would need to be an additional API)
Similarly, I'm not sure we actually need to address multi-span support before this API can be stabilized. The proposed change there involves changing some methods to be generic, which is considered a minor change under RFC #1105. It could also be done by changing Span
itself to be an enum, rather than having a separate MultiSpan
type
@sgrif I suppose the question is: should unsilenceable warnings be allowed at all? I can't think of a reason to remove this control from the end-user. And, if we agree that they shouldn't be allowed, then we should fix the API before stabilizing anything to account for this. I'd really rather not have _four_ warning methods: warning
, span_warning
, lint_warning
, lint_span_warning
.
Similarly, I'm not sure we actually need to address multi-span support before this API can be stabilized.
Sure, but the change is so minor, so why not do it now? What's more, as much as I want this to stabilize as soon as possible, I don't think enough experience has been had with the current API to merit its stabilization. I think we should implement these two features, announce them broadly so others can play with them, gather feedback, and then stabilize.
It could also be done by changing Span itself to be an enum, rather than having a separate MultiSpan type.
Right, that works too.
I suppose the question is: should unsilenceable warnings be allowed at all? I can't think of a reason to remove this control from the end-user.
I think "having a thing we can ship" is a decent reason, but I also think an API that only supports error
/help
/note
, but not errors is sufficiently useful to ship even without warnings. I'd support doing that if it meant we didn't block this on yet another API -- Mostly I just want to avoid having perfect be the enemy of good here.
Sure, but the change is so minor, so why not do it now?
Because we have a perfectly workable API that's being used in the wild right now that we could focus on stabilizing instead. Typically we always trend towards the more conservative option on this sort of thing, shipping an MVP that's forward compatible with extensions we might want in the future.
I don't think enough experience has been had with the current API to merit its stabilization.
So what needs to happen for that? Should we do a public call for testing? Definitely adding more docs is huge. I suppose it'd be good to see what serde looks like using this API as well.
So what needs to happen for that? Should we do a public call for testing? Definitely adding more docs is huge. I suppose it'd be good to see what serde looks like using this API as well.
Yeah, that's exactly what I was thinking.
Because we have a perfectly workable API that's being used in the wild right now that we could focus on stabilizing instead. Typically we always trend towards the more conservative option on this sort of thing, shipping an MVP that's forward compatible with extensions we might want in the future.
I don't think this is an eccentric proposal in any way. When folks play with this, they should have this feature. In any case, I'll be implementing this soon, unless someone beats me to it, as Rocket needs it.
Should we be going through the RFC process for additions to the API here?
(I noticed that Diagnostic
itself actually never got an RFC)
On Tue, Sep 11, 2018 at 3:26 PM Sergio Benitez notifications@github.com
wrote:
So what needs to happen for that? Should we do a public call for testing?
Definitely adding more docs is huge. I suppose it'd be good to see what
serde looks like using this API as well.Yeah, that's exactly what I was thinking.
Because we have a perfectly workable API that's being used in the wild
right now that we could focus on stabilizing instead. Typically we always
trend towards the more conservative option on this sort of thing, shipping
an MVP that's forward compatible with extensions we might want in the
future.I don't think this is an eccentric proposal in any way. When folks play
with this, they should have this feature. In any case, I'll be implementing
this soon, unless someone beats me to it, as Rocket needs it.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/54140#issuecomment-420430972,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABdWK2Gpoc6vTWz8fKpJmVaGysf9w7-Uks5uaCpsgaJpZM4WkOKO
.
Maybe it's too late (I'm lacking context here), but is there any hope of unifying proc-macro diagnostics with those emitted by the compiler itself? It seems sad and unmotivated to have two parallel implementations of diagnostics. (Rustc's diagnostics also have a suggestions API (albeit still somewhat in flux) that harbors a lot of promise given the new cargo fix
subcommand that it would be nice for the Rocket/Diesel/_&c._ proc-macro world to also benefit from.)
@zackmdavis The API being exposed by the diagnostics API in proc_macro
today is a refinement of the internal API; they're already quite unified, with minor differences to account for the context in which they are used. The implementation is a thin shell over the internal implementation.
In general, rustc
s evolving needs and the proc_macro
diagnostics API aim for stability prohibit the two from being identical. This is a _good_ thing, however: rustc
can experiment with unstable APIs as much as it wants without being concerned about stability while proc_macro
authors can have a stable, concrete API to build with. Eventually, features from the former can makes their way into the latter.
Maud also uses the diagnostic API. It would benefit from both features described in the summary:
Multi-span support – Currently, the duplicate attribute check emits two separate diagnostics for each error. It would be cleaner to emit a single diagnostic instead.
Lint-associated warnings – We want to warn on non-standard HTML elements and attributes. But we also want to let the user silence this warning, for forward compatibility with future additions to HTML.
Is there any way to emit warning for arbitrary file? It could be usefull for macros that read additional data from external files (like derive(Template) in https://github.com/djc/askama ) .
If it's not possible, how problematic it is to add to Diagnostics
something equivalent to :
fn new_raw<T: Into<String>>(start: LineColumn, end: LineColumn, file: &path::Path, level: Level, message: T) -> Diagnostic
?
Something I find confusing about the current nightly API: does Diagnostic::emit()
return? (It appears to do so sometimes but not others; even for errors.)
Currently I must use unreachable!()
after cases where I think emit
should not return... and sometimes this results in internal error: entered unreachable code
while at other times it does not (I can't spot a functional difference between the two cases, except for different spans being used).
In my opinion:
Result
(probably difficult to do now)fn() -> !
) with an error code (via panic!
, emit()
or whatever; currently panic!
is reliable but does not give a nice error message)@dhardy Diagnostic::emit()
should _always_ return.
Okay, fair enough; not sure why I some panic messages sometimes and not others.
~Then we could do with something that doesn't return; maybe Diagnostic::abort()
?~
I guess syn::parse::Error::to_compile_error
and std::compile_error
are what I was looking for.
Personally I'd prefer not exposing a Diagnostic::abort()
method, because it encourages macro authors to bail out at the first error instead of collecting as many errors as possible.
The compiler will not proceed with type checking if your macro emits at least one error, so you can get away with returning nonsense (like TokenStream::empty()
) in that case.
@macpp It's not possible today. I agree that something like this should exist, but much thought needs to be given to the API that's exposed. The API you propose, for instance, puts the onus of tracking line and column information on the user, and makes it possible to produce a Diagnostic
where Rust would make it impossible to do so due to Span
restrictions.
The API that I've considered is having a mechanism by which a TokenStream
can be retrieved given a file name:
impl TokenStream {
fn from_source_file<P: AsRef<Path>>(path: P) -> Result<TokenStream>;
}
This would make it possible to get arbitrary (but "well-formed") spans anywhere in the file. As a bonus, it means you can reuse libraries that work with TokenStream
already, and implementing this function is particularly easy given that Rust uses something like it internally already. The downside is that you can only use this when the source contains valid Rust tokens, which in practice means that delimiters must be balanced, which seems like a sane restriction.
Well, of course if you deal with .rs files, then TokenStream::from_source_file
is much better solution. However, i want to notice that this works only with valid rust files. To clarify, what i wanted to propose is api that allows me to emit warning for any kind of file - for example if my procedural macro reads some configuration from config.toml
, then i want to be able to do something better than
panic("bad configuration in config.toml file: line X column Y")
Unfortunately, i forgot about Span
restrictions, so api that i proposed earlier is simply wrong for this use case :/
@macpp No, that would work for almost any kind of text file. A TokenStream
is only beholden to matching delimiters; it need not necessarily be Rust. If it can be used as input to a macro, it would be parseable in this way. It would work just fine for Askama, for instance.
The downside is that you can only use this when the source contains valid Rust tokens, which in practice means that delimiters must be balanced, which seems like a sane restriction.
And that strings inside single quotes must contain a single codepoint, which eliminates a lot more things (including TOML files) that have arbitrary strings in single quotes.
Even the "delimiters must be balanced" requires that the file use the same idea of where a delimiter is and isn't valid. For example the source file must agree that a ")
sequence does not end a previous (
because the Rust parser )
will treat it as part of a string.
In short, I don't have a problem with having a TokenStream::from_source_file
, but I would not push this as a solution to the "diagnostics for arbitrary non-Rust source files" problem.
It's been quite a while since the last activity here. @SergioBenitez could you give a brief status update on what's holding back progress on this and if, which help is needed?
@regexident The issue text is up-to-date. The remaining tasks are to:
Help would be appreciated on 1 and 2. Once implemented, we can discuss and move forward on 3.
There is currently a Pre-RFC on translating compiler output. If accepted, this may affect the Diagnostic API. Please consider this forward-compatibility issue before stabilization.
Doesn't look like any work has been done on this recently. @sgrif Are you still of the opinion that lint-associated warnings should be a separate RFC? If so, it _should_ just be documentation and stabilizing, based on what @SergioBenitez said.
I haven't followed this closely enough to really have much of an opinion at this point. This sat for long enough that the ecosystem has hacked around it and recreated it with emitting compile_error!
. I do think this is a significant enough API to have warranted an RFC in the first place (lint related or not)
Lint-Associated Warnings
At present, if aproc_macro
emits a warning, it is unconditional as it is not associated with a lint: the user can never silence the warning. I propose that we require proc-macro authors to associate every warning with a lint-level so that the consumer can turn it off.
No API has been formally proposed for this feature. I informally proposed that we allow proc-macros to create lint-levels in an ad-hoc manner; this differs from what happens internally, where all lint-levels have to be known apriori. In code, such an API might look lIke:val.span.warning(lint!(unknown_media_type), "unknown media type");
The
lint!
macro might check for uniqueness and generate a (hidden) structure for internal use. Alternatively, the proc-macro author could simply pass in a string:"unknown_media_type"
.
Having only unknown_media_type
could cause problems with semantic versioning and would make it very unintuitive to use.
For example a library author might use one proc-macro, where she/he wants to silence a lint called unknown_character
. Later the author adds a new library, which also has a warning called unknown_character
and the lint!
macro would start to error. This would mean, that every new warning would require a major version bump, because it might break compilation? Silencing both lints is not an option in my opinion.
Therefore, I think it would be better to use namespaces, like it can be seen with rustfmt
and clippy
(#[allow(clippy::use_self)]
).
The lint macro could default to the crate name and otherwise use the provided name:
macro_rules! lint {
($namespace:expr, $lint:literal) => {
println!("{}", concat!($namespace, "::", $lint));
};
($lint:literal) => {
println!("{}", concat!(env!("CARGO_PKG_NAME"), "::", $lint));
}
}
fn main() {
lint!("builder", "some_warning");
lint!("some_warning");
}
Of course this does not prevent namespace conflicts entirely, but it makes it more unlikely and still allows for some flexibility.
Another option would be to do something like this:
#[proc_macro_derive(namespaces(builder))]
Why is the current proposal to attach the name to the Span
?
val.span.warning(lint!(unknown_media_type), "unknown media type");
I would much more prefer to have it in the constructor of a lint:
Diagnostic::new(Level::Warning(lint!("unknown_media_type")), "message");
@Luro02 I think an even cleaner solution would be to make each lint declaration an item, so that namespaces can be handled by the module system (much like with the proc macros themselves).
I've reimplemented Diagnostic
on stable - at least, the error-reporting part - in proc-macro-error
. It delegates to proc_macro::Diagnostic
on nightly and fallbacks to hand-made errors' render on stable.
If anybody is interested, you are welcome to check it out.
Most helpful comment
I've reimplemented
Diagnostic
on stable - at least, the error-reporting part - inproc-macro-error
. It delegates toproc_macro::Diagnostic
on nightly and fallbacks to hand-made errors' render on stable.If anybody is interested, you are welcome to check it out.