Proposal
Problem statement
The proc_macro_diagnostic feature has been stuck in limbo for years, with several unresolved design questions and implementation problems. To name a few:
Motivating examples or use cases
Emitting errors and lints from proc macros should be easy and first class.
Solution sketch
Remove the current API in favor of:
mod diagnostic {
/// Attribute proc macro for declaring a lint.
#[rustc_builtin_macro]
pub macro proc_macro_lint($item:item) {
/* compiler built-in */
}
#[derive(Copy, Clone)]
pub struct Lint {
// private
}
pub trait Diagnostic {
// doc(hidden)/internal methods, can only be implemented by the macro
}
pub fn emit_error(span: Span, diag: impl Diagnostic);
pub fn emit_lint(span: Span, lint: Lint, diag: impl Diagnostic);
// Convenience impls for simple errors/lints.
impl Diagnostic for String {}
impl Diagnostic for &str {}
/// Derive `Diagnostic` for structs and enums.
#[rustc_builtin_macro(Diagnostic, attributes(diag, note, help, etc...))]
pub macro Diagnostic($item:item) {
/* compiler built-in */
}
}
Lints are declared with the proc_macro_lint attribute macro:
#[proc_macro_lint(name = "namespace::lint_name", level = "Allow")]
static LINT: proc_macro::Lint;
The macro will create an initializer for Lint. An instance of this type must be used to emit a lint.
The supplied name must be of the form namespace::lint_name, with a single path separator. Both names must be valid identifiers, can only contain a..=z and _ and may not be keywords. It is an error to use a predefined tool name as the namespace name. Note that with register_tool it is possible to declare arbitrarily named tool lints (and attributes). As such detection of unknown lints is useless as a "wrong" lint name could just be an unknown tool lint and we have no way of checking that. Renaming or importing lint names seems similarly pointless to me and it is an implementation headache so I'd really like to not support it.
An optional lint level (Allow/Warn/Deny) can be supplied. The lint level defaults to Warn.
Users can control the lint level with the the lint check attributes. To name the lint in a lint check attribute, users must first register the namespace:
#![register_lint_tool(namespace)]
#[allow(namespace::lint_name)]
mod x {
// ..
}
Simple example
For macro authors that just want to emit a simple message this api is as simple as possible:
#[proc_macro_lint(name = "namespace::no", level = "Deny")]
static DONT_DO_THAT: proc_macro::Lint;
#[proc_macro_attribute]
pub fn my_attr(attr: TokenStream, input: TokenStream) -> TokenStream {
// ....
proc_macro::diagnostic::emit_lint(span, DONT_DO_THAT, "this is really bad");
}
Advanced example
This api is focused around structured diagnostics. It looks similar to how structured diagnostics are implemented in rustc (See here for its documentation).
#[proc_macro_lint(name = "pyo3::deprecated", level = "Warn")]
static DEPRECATED: proc_macro::Lint;
#[derive(Diagnostic)]
#[diag("the option {name} has been deprecated")]
#[note("this option will be removed in pyo3 version {when}")]
pub(crate) struct DeprecatedOption {
pub name: String,
pub when: String,
}
#[proc_macro_attribute]
pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream {
// ....
proc_macro::diagnostic::emit_lint(
span,
DEPRECATED,
DeprecatedOption {
name: "what".into(),
since: "0.30".into(),
},
);
}
I propose structured diagnostics over a builder api for several reasons:
- It encourages the user to structure their diagnostics similarly to rustc's diagnostics, hopefully creating a more seamless user experience.
- A macro based approach makes it easier to guide the macro author by emitting lints/errors/diagnostics.
- Splitting the diagnostic definition from main code paths, in my experience, results in cleaner and less error prone code (for example - from my experience converting rustc diagnostics to structured diagnostics - it's quite easy to have a bunch of spans all named
span around and messing up spans in a diagnostic)
- It makes it easier to make changes across editions if necessary.
Structured diagnostics can be less flexible. There are errors in rustc which are hard to express as structured diagnostics because they are passed though many code paths, each of which add to the error in their own way. However proc macros have access to much less information than the compiler so this flexibility should rarely be necessary. People can also still build builder-style apis on top of a structured api (or the other way around).
Alternatives
We can do nothing. As a macro author you can create errors by returning compile_error! invocations. But those are crude (just a message) and they heavily incentivize returning a TokenStream (containing the compile_error) rather than continuing to parse and emit multiple errors. Similarly you can expand into items that trigger rustc lints (e.g. expand to a deprecated item, which in turn creates a message). Or you can just print to stdout.
We can stabilize the current api. I don't think anyone is really happy with it. I guess people can build nicer apis on top of it, though.
Links and related work
Proc macro diagnostics tracking issue rust-lang/rust#54140
The open (stuck) LintId rust-lang/rust#135432
HackMD version of this issue: https://hackmd.io/Kx0L8qzbTU-n7oV9dHIYzg?view
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
- We think this problem seems worth solving, and the standard library might be the right place to solve it.
- We think that this probably doesn't belong in the standard library.
Second, if there's a concrete solution:
- We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
- We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
the current
Proposal
Problem statement
The
proc_macro_diagnosticfeature has been stuck in limbo for years, with several unresolved design questions and implementation problems. To name a few:proc_macro::Diagnosticapi. We don't doInto<String>bounds and the api feels like "too much" and error proneMotivating examples or use cases
Emitting errors and lints from proc macros should be easy and first class.
Solution sketch
Remove the current API in favor of:
Lints are declared with the
proc_macro_lintattribute macro:The macro will create an initializer for
Lint. An instance of this type must be used to emit a lint.The supplied name must be of the form
namespace::lint_name, with a single path separator. Both names must be valid identifiers, can only containa..=zand_and may not be keywords. It is an error to use a predefined tool name as the namespace name. Note that with register_tool it is possible to declare arbitrarily named tool lints (and attributes). As such detection of unknown lints is useless as a "wrong" lint name could just be an unknown tool lint and we have no way of checking that. Renaming or importing lint names seems similarly pointless to me and it is an implementation headache so I'd really like to not support it.An optional lint level (
Allow/Warn/Deny) can be supplied. The lint level defaults toWarn.Users can control the lint level with the the lint check attributes. To name the lint in a lint check attribute, users must first register the namespace:
Simple example
For macro authors that just want to emit a simple message this api is as simple as possible:
Advanced example
This api is focused around structured diagnostics. It looks similar to how structured diagnostics are implemented in rustc (See here for its documentation).
I propose structured diagnostics over a builder api for several reasons:
spanaround and messing up spans in a diagnostic)Structured diagnostics can be less flexible. There are errors in rustc which are hard to express as structured diagnostics because they are passed though many code paths, each of which add to the error in their own way. However proc macros have access to much less information than the compiler so this flexibility should rarely be necessary. People can also still build builder-style apis on top of a structured api (or the other way around).
Alternatives
We can do nothing. As a macro author you can create errors by returning
compile_error!invocations. But those are crude (just a message) and they heavily incentivize returning aTokenStream(containing thecompile_error) rather than continuing to parse and emit multiple errors. Similarly you can expand into items that trigger rustc lints (e.g. expand to a deprecated item, which in turn creates a message). Or you can just print to stdout.We can stabilize the current api. I don't think anyone is really happy with it. I guess people can build nicer apis on top of it, though.
Links and related work
Proc macro diagnostics tracking issue rust-lang/rust#54140
The open (stuck) LintId rust-lang/rust#135432
HackMD version of this issue: https://hackmd.io/Kx0L8qzbTU-n7oV9dHIYzg?view
What happens now?
This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.
Possible responses
The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):
Second, if there's a concrete solution:
the current