Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking Issue: Procedural Macro Diagnostics (RFC 1566) #54140

Open
2 of 5 tasks
SergioBenitez opened this issue Sep 11, 2018 · 63 comments
Open
2 of 5 tasks

Tracking Issue: Procedural Macro Diagnostics (RFC 1566) #54140

SergioBenitez opened this issue Sep 11, 2018 · 63 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-macros-1.2 Issues which affect macros 1.2 B-unstable Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. I-libs-api-nominated Indicates that an issue has been nominated for discussion during a libs-api team meeting. Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@SergioBenitez
Copy link
Contributor

SergioBenitez commented Sep 11, 2018

This is a tracking issue for diagnostics for procedural macros spawned off from #38356.

Overview

Current Status

Next Steps

Summary

The initial API was implemented in #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:

  1. 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.

  2. 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:

    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".

@sgrif
Copy link
Contributor

sgrif commented Sep 11, 2018

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)

@sgrif
Copy link
Contributor

sgrif commented Sep 11, 2018

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

@SergioBenitez
Copy link
Contributor Author

SergioBenitez commented Sep 11, 2018

@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.

@sgrif
Copy link
Contributor

sgrif commented Sep 11, 2018

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.

@SergioBenitez
Copy link
Contributor Author

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.

@sgrif
Copy link
Contributor

sgrif commented Sep 11, 2018 via email

@zackmdavis
Copy link
Member

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.)

@Havvy Havvy added A-diagnostics Area: Messages for errors, warnings, and lints A-macros-1.2 Issues which affect macros 1.2 C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. labels Sep 12, 2018
@SergioBenitez
Copy link
Contributor Author

SergioBenitez commented Sep 12, 2018

@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, rustcs 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.

@lambda-fairy
Copy link
Contributor

Maud also uses the diagnostic API. It would benefit from both features described in the summary:

  1. 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.

  2. 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.

@macpp
Copy link

macpp commented Sep 21, 2018

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 ?

@alexcrichton alexcrichton added B-unstable Implemented in the nightly compiler and unstable. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Oct 1, 2018
@dhardy
Copy link
Contributor

dhardy commented Oct 31, 2018

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:

  • either procedural macro functions should be revised to return a Result (probably difficult to do now)
  • or there should be a documented, reliable way of aborting (i.e. fn() -> !) with an error code (via panic!, emit() or whatever; currently panic! is reliable but does not give a nice error message)

@SergioBenitez
Copy link
Contributor Author

@dhardy Diagnostic::emit() should always return.

@dhardy
Copy link
Contributor

dhardy commented Nov 1, 2018

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.

@lambda-fairy
Copy link
Contributor

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.

@SergioBenitez
Copy link
Contributor Author

SergioBenitez commented Nov 4, 2018

@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.

@macpp
Copy link

macpp commented Nov 4, 2018

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 :/

@SergioBenitez
Copy link
Contributor Author

SergioBenitez commented Nov 4, 2018

@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.

@Arnavion
Copy link

Arnavion commented Nov 4, 2018

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.

@yaahc
Copy link
Member

yaahc commented Oct 15, 2021

Also, how do we force a Diagnostic to be used, can we have a warning like must_use or sort which requires the user to run .emit() in case they forget?

If this is a particular worry we could also make it so that emit is a free function that takes a closure that is required to return a Diagnostic, then always call the private emit method on that return value.

@yaahc
Copy link
Member

yaahc commented Oct 15, 2021

I'm still getting up to speed on these APIs but after an initial review of this thread and #83363 I'm surprised by the choice to continue to allow Diagnostics that don't have a Span.

It is possible to create span-less diagnostics. I think this is okay. I have not seen this cause any issues in rustc, Rocket, or any other project, and removing this constraint greatly unifies and simplifies the API surface.

I have encountered proc macros (structopt for example) that have had issues with diagnostics being emitted without associated spans, though I'm not confident that wasn't due to some unrelated issues with proc macros losing span info rather than footguns in the Diagnostic-like APIs it uses. Also, I'm not convinced that we cannot create an equally convenient API that forces a primary span to be provided at compile time. As a strawman proposal, I could imagine combining the solutions to accidentally not emitting with providing the span by moving the emit function to be a method on span.

// instead of this
ident.error("unexpected identifier").emit()

// we could require this
ident.emit(Diagnostic::error("unexpected identifier));

On a related note, do we think it's important to require a primary span for child Diagnostics?

@CraftSpider
Copy link
Contributor

I've only used the current API for smaller projects, but personally:

I think the ability to provide span-less diagnostics is fine to leave in. I can imagine wanting to emit a warning about something the macro might notice not part of the code. EG if it's doing something with an external tool. I can't think of a span I'd want more than 'no span' for a case like that.

(As much less significant point, I find the idea of span.emit(Diagnostic::blah(...)) to look much worse than the trailing emit. I'm almost certain there exists an API that is elegant enough to fix that issue, but figured I'd mention it.)

@joshtriplett
Copy link
Member

We discussed this in today's @rust-lang/lang meeting. We confirmed that:

  • T-lang only has a stake in suggesting that there should exist some way for proc macros to report diagnostics, and we do believe there should be; we have no open questions or concerns.
  • The shape of the API is up to @rust-lang/libs-api, in consultation with compiler for what they can support.

@joshtriplett joshtriplett removed the T-lang Relevant to the language team, which will review and decide on the PR/issue. label May 11, 2022
@jhpratt
Copy link
Member

jhpratt commented May 11, 2022

For T-libs-api:

It's quite old at this point, but I've got a PR (#83363) open that implements the API proposed above. The implementation itself works (albeit needing a rebase after 14 months), but there was concern over having the ability to emit diagnostics without a span. What's the use case here (from a user's perspective, not the compiler)?

cc @SergioBenitez, who proposed that API

@CraftSpider
Copy link
Contributor

Is there any movement on this? The linked PR has now been open for over a year with vague concerns about an even better API. Personally I think the proposed API is fine, and that not attaching spans doesn't hurt to support. Mostly, I just would like some movement towards a path to stable.

@jhpratt
Copy link
Member

jhpratt commented Nov 7, 2022

There is no update. I will happily rebase and update the PR if and when it is desired to be merged.

@HTG-YT
Copy link
Contributor

HTG-YT commented Nov 20, 2022

Would any changes to the API require a separate RFC? I'm wondering why suggesting code in these diagnostics aren't supported. 🤔

@Qix-
Copy link

Qix- commented Dec 1, 2022

Sorry if this has been covered already, but could it be possible to implement MultiSpan on TokenStream?

@estebank
Copy link
Contributor

Looking at the state of things, I wonder if it wouldn't be worthwhile to stabilize a minimum set of these APIs: Opaque Span that can only be attained from an existing TokenStream and Diagnostic that can only be created and note/help attached to them, nothing more. There are changes happening on the rustc internal Diagnostic API, mainly around localization, but this basic API I'm proposing hasn't significantly changed since before 1.0 (only "major" changes I recall was when we changed it to accept MultiSpan instead of Span and Into<String> instead of String). For errors in particular we can already rely on panic and compile_error, as proc-macro-error does, but for warnings we have no mechanism, and the results are quite limited.

@jhpratt
Copy link
Member

jhpratt commented Mar 22, 2023

Stabilizing some of #![feature(proc_macro_span)] and #![feature(proc_macro_span_shrink)] would be very useful all on its own. One method I know there are workarounds for (when used in conjunction with compile_error!) is Span::join.

Diagnostic-wise, there's a PR that's been open for a couple years redesigning the public API for #![feature(proc_macro_diagnostics)], with input from Sergio Benitez and David Tolnay. I asked Josh about this just the other day, and he said the main issue is getting the right people in the meeting to discuss it. Personally, I am happy to join any meeting to discuss the API for diagnostics; just ping me wherever beforehand. At least for time, I'm already tracking the exact spans of the diagnostics and have decent error messages, but being able to avoid hacks and emit proper diagnostics would be significant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-macros-1.2 Issues which affect macros 1.2 B-unstable Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. I-libs-api-nominated Indicates that an issue has been nominated for discussion during a libs-api team meeting. Libs-Tracked Libs issues that are tracked on the team's project board. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests