-
Notifications
You must be signed in to change notification settings - Fork 1.6k
#![register_{attribute,lint}_tool]
#3808
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
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: BD103 <[email protected]>
- mention that `rustdoc -w json` will include the attributes - fix semver-checks spelling - apparently adding `inline` can be a breaking change 🙄
going to go ahead and ping all the authors of extern tools that i know, in no particular order:
|
I would happily use this in
Both of those are very blunt instruments, and thoroughly unsatisfying as an answer to our users. This RFC addresses those problems directly. I'm excited to see it. |
text/3808-register-tool.md
Outdated
|
||
The tool prelude is separated into the tool attribute prelude (which is in the type namespace) and the lint prelude (which is only active inside lint controls). | ||
|
||
The tool attribute prelude is changed to take precedence over the [Extern prelude]. The tool attribute prelude is only considered when resolving in an attribute macro; types in other positions, such as `fn foo() -> rustfmt::x`, do not consider the tool prelude. This is to avoid unnecessary breakage when the meaning is clear. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This effectively introduces two sub-namespaces to type namespace, which is a relatively large change.
For macro namespace we introduced it because there was basically no choice due to macro modularization breakage, but here we are free to not introduce complications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true; we won't break any existing stable code because the breakage only happens when people explicitly write register_attribute_tool
. The breakage I'm trying to avoid is not writing it -> writing it, so people are more likely to adopt it. but it seems reasonable to just say "write out ::rustfmt::x
", maybe with a MachineApplicable lint, and then the resolution rules can be simpler.
text/3808-register-tool.md
Outdated
- We could "just not do this". That makes it harder to write external tools, and in practice just means that people use `cfg_attr` instead of a namespace, which seems strictly worse. | ||
- We could relax the constraint that tool names cannot conflict with local items. This requires tools to do name resolution; but in practice I do not think we can expect tools to do this, and we must assume that the tool will behave differently than the compiler (`rustfmt` already does this today). | ||
- We could add a syntax to disambiguate tool names from local items. That would add inconsistency with the existing built-in tool attributes, and requires tool authors to parse both the new and existing syntax. | ||
- We could drop the change to name resolution, such that extern crates continue to take precedence over tools. This allows no way to use a tool and a crate with the same name, unless we add a new syntax. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's what I'd just do.
(Perhaps together with the "ambiguity is always an error" change if we can pull it off, to maximize future possibilities.)
rustfmt
misinterpreting mod rustfmt { pub use my_attr_macro as skip; }
for #[rustfmt::skip]
isn't really an issue in practice, and for tools working post-expansion this is not an issue even in theory.
extern crate rustfmt as my_rustfmt
is a good enough way to avoid breaking a tool when you need a crate with the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be clear, you are suggesting that the following be a hard error?
#![register_attribute_tool(pyo3)]
extern crate pyo3;
#[pyo3::pymodule] // error: ambiguous between tool and crate
struct Foo {}
I think that would not be ideal, but I guess I could live with it as long as #[::pyo3::pymodule]
is still allowed. I feel somewhat strongly that #[::pyo3::pymodule]
should not give an error, the extern crate as
workaround is not very well known. even if people use it as my_pyo3 = { package = "pyo3" }
in Cargo.toml it's still a bit indirect.
cc @mejrs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be clear, you are suggesting that the following be a hard error?
Yes, just because if it's an error initially, then in the future we'll be able to backward compatibly do any of the resolution changes from this RFC (precedence, subnamespaces) if really necessary.
#[::pyo3::pymodule]
should certainly work, there's no ambiguity there, ::foo
only looks into extern prelude.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought of another complication. Say we want to add more built-in tools in the future, or to reserve a tool namespace today. If adding a built-in tool causes breakage because it makes crate names ambiguous, suddenly it's much harder to extend the namespace.
I guess we could have different rules for built-in tools than for registered tools, but that kinda sucks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would not be ideal, but I guess I could live with it as long as
#[::pyo3::pymodule]
is still allowed.
I've thought about this and and having #[::pyo3::pymodule]
work in this case but not #[pyo3::pymodule]
seems like a bad idea to me. I think it'd be better if they both errored or if both worked. If we allow the former then it's gonna be one of those gotchas that everyone runs into. "Oh yeah you have to prefix the macro path with ::
because otherwise it's ambiguous between the tool and the macro". I think I'd like to have them named the same but it might be better if authors are forced to choose different names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very opposed to that idea. We should not be adding needless restrictions. If you don't want the tool/macro distinction to be confusing, you as the author can choose a different name for your tool. We should not prevent people from disambiguating their code (for one thing, it makes adding a new built-in tool over an edition much harder).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you as the author can choose a different name for your tool
I do not disagree in principle, but I'm just a bit paranoid of the situation where someone chooses the crate name as their tool name without thinking about it very much and it ends up being awkward later on. Imagine a situation like:
- author creates a library (without proc macros)
- author creates a tool attribute with the same name, this works fine because of no proc macros
- author later decides to add proc macros
- (without prefix
::
) these macros collide with the tool name - the author would have to decide to either rename the tool or tell the users to prefix the macros. The latter sounds like a noob trap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can prevent this situation. The last thing still happens even if we prevent disambiguating the tool, we just force you to rename, we are strictly limiting your options.
Consider a different case: someone writes a crate, someone else writes a tool, they don't know about each other. you combine them and they conflict. I don't think we should force you in this case to use extern crate overlap as my_overlap
, that seems strictly worse than allowing you to disambiguate at the call site.
How would you be able to leverage this? irrc rustdoc gives you immediate attributes but not enough other attributes to resolve lint levels. |
this is not a rustdoc problem. this is a language level problem. semver-checks would have the same problem if it were using |
oh, i misunderstood, you were asking how semver-checks would use this if it were implemented. i would expect semver-checks to reimplement the precedence rules for lints, or to document that it doesn't support module-level lint controls. it doesn't have CLI flags, which means |
- make cargo registration the main user interface - specify that `no_implicit_prelude` does not affect tools
- all ambiguity is forbidden, not just tool<->local ambiguity. as a result, there is no change to precedence order. - no special-casing for attribute macros; tools are visible in any context in the type namespace - the suggested way to add a new built-in tool is "wait for an edition"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's awesome to see this moving forward. Thanks!
For Kani, it's better to include @zhassan-aws / @carolynzech.
|
||
Like today, attributes and lints in a tool namespace are always considered used by the compiler. The compiler does not verify the contents of any tool attribute, except to verify that all attributes are syntactically valid [tool attributes]. | ||
|
||
Registering a predefined tool (`clippy`, `miri`, etc.) using `#![register_*_tool(...)]` is an error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where will this list be maintained?
Also, today you can't add any attribute that starts with rustc
. Will that still be the case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see a full list at https://doc.rust-lang.org/nightly/reference/attributes.html#tool-attributes. That list is currently not as complete as the list in this RFC; I will make sure to update it.
Also, today you can't add any attribute that starts with rustc. Will that still be the case?
I assume you mean namespaced tool attributes like rustc::xxx
, not "bare" attributes like rustc_const_stable
. I expect those to still be banned. The story there is kind of a mess because there are rustc::
lints and they are feature-gated, but differently than the rest of the compiler's feature gates ... but I don't think we need to resolve that in this RFC. I will mention that the rustc::
tool namespace is currently and will continue to be reserved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I mean namespaced tool attributes that starts with rustc
, for example, rustc_foo::bar
. Today you can register a tool rustc_foo
, but the compiler will reject the attribute #[rustc_foo::bar]
(playground link):
#![feature(register_tool)]
#![register_tool(rustc_foo)]
#[rustc_foo::bar]
fn main() {
println!("🦀")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, i see. this is actually not coming from the register_tool code, this is the same code that rejects #[rustc_baz]
.
i think it is fairly reasonable to keep this reserved, but i can make sure it's rejected earlier in register_tool
so that people aren't confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but i can make sure it's rejected earlier in register_tool so that people aren't confused.
I don't think it makes sense to prevent this, there are many other ways to make an unusable attribute that are not rejected, like mod rustc_something { pub use my_attribute; }
or use my_attribute as rustc_something;
, but we do not prohibit modules or imports containing rustc
preventively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as we are consistent. Either it works without any hacks or it fails early on.
I (static analysis software developer) would totally use this for any annotation that is not a contract. Thanks for your work! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
text/3808-register-tool.md
Outdated
- `#![register_lint_tool(tool_name)]` allows controlling namespaced lints with `#[warn(tool_name::lint_name)]` | ||
- `#![register_attribute_tool(tool_name)]` allows using tool names in [inert attributes][inert] with `#[tool_name::attribute_name(token_tree)]`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I suggest we use #![register_tool_{attribute|lint}
instead? I was also wondering if we could have a #[register_tool]
which would add the tool to both namespaces, the attribute and lint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry that register_tool_{attribute|lint}
will imply we are registering individual attributes or individual lints, when that is not the case. If someone sees the attribute #![register_tool_lint(...)]
, I worry they'll do this:
#![register_tool_lint(bevy::panicking_methods)]
#![register_tool_lint(bevy::missing_reflect)]
#![register_tool_lint(bevy::duplicate_bevy_dependencies)]
// ...
I think the name register_lint_tool
much better signals that it's for registering a tool, not a lint or attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha... That makes sense. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless we allow nested tool name like com::example::tool
1, I don't think #![register_tool_lint(bevy::panicking_methods)]
will compile because the tool name can only be an $:ident
not a $:path
, so it is actually not an issue.
Footnotes
-
#![warn(com::example::tool::lint)]
is interpreted as having a tool namecom
. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, but why not avoid the confusion in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added #![register_tool]
as an alias for adding both attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, but why not avoid the confusion in the first place?
IMO #![register_tool(clippy)]
is also confusing, it sounds like it will make rustc
run the "tool" clippy
by "registering" it to the compilation process. The standard reference refers to this as "tool name" rather than just "tool". I think #![register_tool_name(clippy)]
would be clear, but #![register_attribute_tool_name(clippy)]
is quite verbose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not what "register" means. I think there is some amount of confusion possible, but I don't think it's likely enough that we need to make all the names longer. If people are adding these attributes, it's because the tool's docs told them to, and the docs should also say how to run the tool. If people are reading these attributes in existing code, then, well, I don't think it's that much more confusing than any other attribute in the language? #[inline]
especially does not mean what most people think it means.
(Yup). FWIW, the main downside with this proposal is that it requires some extra work on the user's POV. Here's the current Crubit workflow:
(We used to use tool attributes, before @cramertj created this new setup.) Now, with the proposal, AIUI, to have a similar workflow using tool attributes, the user must both depend on the attribute macro crate, as well as independently register the tool attribute which the proc macro will generate. This is unfortunate -- the user needs to do two things, and needs to know the name of the tool attribute even if they never spell it. I like the proc macro wrapper, because it can force compile-time errors even if you happen to not be running the tool at the moment. The metadata Crubit consumes is not necessarily simple and so does need some validation. My preference as far as user experiences go are that a proc macro can generate inert attributes without them being registered by the crate that uses the proc macro, or else that there's a globally-available tool attribute, similar to I'm hoping this helps. FWIW, we have a working process, and I think this proposal is certainly compatible with many paths forward that lead to what I want, and definitely doc-comments are a bit... egh. Thanks for all your work here, and thanks for soliciting more feedback! I really appreciate it. |
text/3808-register-tool.md
Outdated
- `#![register_lint_tool(tool_name)]` allows controlling namespaced lints with `#[warn(tool_name::lint_name)]` | ||
- `#![register_attribute_tool(tool_name)]` allows using tool names in [inert attributes][inert] with `#[tool_name::attribute_name(token_tree)]`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added #![register_tool]
as an alias for adding both attributes.
|
||
- Proc macros wish to register custom lints; see [`proc_macro_lint`]. We would have to establish some mechanism to prevent overlapping namespaces. Perhaps `warn(::project::lint_name)` could refer to the proc macro and `warn(project::lint_name)` would refer to any registered tool (only when a `project` tool is regisetered; in the common case where no tool is registered, `project::` would still refer to the proc macro). | ||
- Projects may wish to have both a proc-macro crate with lints and a CLI with lints. To allow this, we would require `proc_macro_lint` to create an exhaustive list of lints that can be created, such that we can still run `unknown_lints` and do not need to create a new cooperation mechanism between `proc_macro_lint` and `register_lint_tool`, nor to require users of the project to distinguish the two with `::project` (see immediately above). We might still run into difficulty if the proc-macro lint namespace is only active while the proc-macro is expanding; it depends on how `proc_macro_lint` is specified. But I think it's ok to delay that discussion until `proc_macro_lint` gets an RFC. | ||
- We could allow proc-macros to register a scoped tool, such that e.g. `#[serde::flatten]` is valid while the proc-macro is expanding, but not elsewhere in the crate. This is similar to [derive helpers], but namespaced. We would have to take care to avoid ambiguity between the scoped tool and globally registered tools in such a way that external tools still do not need to perform name resolution. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ssbr i'm going to respond to you here so we can have things in threads; i think derive helpers are basically what you're asking for.
Now, with the proposal, AIUI, to have a similar workflow using tool attributes, the user must both depend on the attribute macro crate, as well as independently register the tool attribute which the proc macro will generate. This is unfortunate -- the user needs to do two things, and needs to know the name of the tool attribute even if they never spell it.
so, this reminds me strongly of derive macro helpers. i double checked just now and derive helpers are preserved by macro-expansion. so i expect you to continue to be able to use your proc-macro after this is stabilized, it would just use a different (non-namespaced) syntax. that seems ok - extending derive helpers to a namespace is a nice addition, but i don't think it's strictly necessary for you to keep using this. i don't see a need for proc macros to use the same syntax as hand-written code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are derive macro helpers also able to be emitted by, well, non-derive macros? They could work, I don't think we tried them.
i don't see a need for proc macros to use the same syntax as hand-written code.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not sure if they are today, but that seems like a very reasonable extension RFC. i am not going to write that RFC but you are welcome to and i can give you pointers on how to get started.
i'll add this to future extensions.
text/3808-register-tool.md
Outdated
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
- We could allow registering tools in Cargo.toml (with a `package.tools` field?). This would avoid duplicating tool registration for each crate in the workspace. This depends on [`--crate-attr`] being stabilized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also consider this process allowing registering tool-specific cfg
s (e.g. cfg(kani)
)
We might want to consider a way for tools to define these entries and for users to pull them in somehow.
A tools table may have enough overlap with runnable dependencies that we may want to develop one with the other in mind.
We need to consider whether this table is a workspace
table like [profiles]
or a package table. For the former, we should auto-copy it to the package on publish like we do workspace.resolver
. If the latter, we should consider whether workspace inheritance should be allowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also consider this process allowing registering tool-specific cfgs (e.g. cfg(kani))
This seems reasonable, but not all tools support cfgs, so doing it for all tools defeats the point of check-cfg. So we would need some way to configure the behavior.
I would be extremely surprised to see someone building an external tool as a dependency. I can mention it I guess but I don't know that it makes sense to go out of our way to support it.
I don't have strong opinions on the other things, but I will mention them in future possibilities. I do feel weakly that this should be a package table that allows workspace inheritance, I don't think we need to restrict all packages in a workspace to use the same tools.
|
||
Unknown tool names in lints remain a hard error until the story for proc-macro lints is resolved (see [Future possibilities](#future-possibilities)). | ||
|
||
`#![no_implicit_prelude]` does not affect tools. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this sentence mean?
(1) If by "tool" you mean "tool name", today #![no_implicit_prelude]
does skip the tool prelude and thus remove all the tool names from the attributes (but not lints, perhaps a bug), so the "tool names" do get affected.
#![no_implicit_prelude]
#[deny(clippy::identity_op)] // unexpected, won't raise E0710 (unknown tool name)
#[rustfmt::skip] // expected, error[E0433]: failed to resolve: use of unresolved module or unlinked crate `rustfmt`
const X: u8 = 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 +0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0;
(2) If by "tool" you mean "tool invocation" then yes, the tool does not need to respect #![no_implicit_prelude]
. For instance rustfmt
recognizes the #[rustfmt::skip]
here just fine. But tools that rely on rustc
such as clippy
will still be affected by E0433.
(3) Or "tool" here just mean the register_tool
call and that the following behaves the same with or without #![no_implicit_prelude]
?
#![no_implicit_prelude]
#![register_tool(bevy)] // always ok
#![register_tool(rustc)] // always error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do mean 1), tool name. The RFC is specified in terms of the behavior that will be stabilized, not in terms of the current implementation.
Very nice! 🙌 I'm not understanding 100% the reasoning behind denying the ambiguous inert tool attribute vs. a genuine attribute, although maybe my confusion stems from Consider the following: mod rustfmt {
pub use ::tokio::main as skip;
}
#[rustfmt::skip]
async fn main()
{}
A tool (inert) attribute is just that, an inert / nothing-doing syntactical marker that the compiler is to let slide. But a genuine attribute, is just as much a syntactical marker as an inert attribute; so I do wonder about non-compiler-name-res-using tools such as But I imagine that if a tool were to hook itself onto compiler machinery so as to actually involve name res and semantics, then a difference between a genuine attribute and an inert one could be made. In which case, denying the ambiguity makes sense. But all this may hint at some mention of the distinction between semantics-capable tools (e.g., able to resolve |
Ambiguity between a tool name and any other name in the type namespace is always a hard error. For example, this code would error: | ||
|
||
```rust | ||
#![register_tool(name)] | ||
|
||
extern crate name; | ||
#[name::skip] // ERROR: ambiguous | ||
#[::name::skip] // OK | ||
fn foo() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not understanding 100% the reasoning behind denying the ambiguous inert tool attribute vs. a genuine attribute
A tool (inert) attribute is just that, an inert / nothing-doing syntactical marker that the compiler is to let slide. But a genuine attribute, is just as much a syntactical marker as an inert attribute; so I do wonder about non-compiler-name-res-using tools such as rustfmt not making a difference between the two.
But I imagine that if a tool were to hook itself onto compiler machinery so as to actually involve name res and semantics, then a difference between a genuine attribute and an inert one could be made.
In which case, denying the ambiguity makes sense.
@danielhenrymantilla i think you are imagining that the tool is responsible for checking there is no ambiguity. that is not the case. the compiler is responsible for checking there is no ambiguity, precisely because we want to allow tools that don't do name-res to still use attributes. it's ok if tools accept more code than the compiler, but i think it would be quite bad if the tool interpreted the code differently than the compiler (this is the same reason i have a big "please don't use attributes that change the meaning of the code" mention in the "tool author guide" section).
But all this may hint at some mention of the distinction between semantics-capable tools (e.g., able to resolve cfgs), vs. purely-syntactical ones being warranted? 🙂
i think distinguishing here is unnecessary complexity, and if we can get away with it we should just use the same rules for all of them. the exception here is between attribute_tool and lint_tool: since they don't affect name res for each other, we can make it easier to add lint-only tools that have the same name as an existing crate without causing breakage. but i don't think we should add more types of tools than necessary.
Rendered
Co-authored by @BD103.
Thank you to everyone who gave feedback on early versions of this RFC.