Pre-RFC: Implementation-defined Lints

Reading through the documentation on lints, it seems like really isn't an opening for lints defined by the implementation that are accepted by other implementations (even if they do not support such lints). As someone working on an alternative implementation of rust, I would like to propose such a mechanism, for implementations to define lints beyond those which are a part of the core language. This mechanism is "implementation-defined" lints, which extends tool lints

A lint name of the form IDENTIFIER::SimplePath is an implementation-defined lint. An implementation shall accept any lint of this form, and issue no diagnostic for an unrecongized lint in this form, except that it may issue an implementation-defined warn-by-default lint if an unrecognized lint name occurs within the tool-name chosen below. The lints of this form that are recognized and their meanings are implementation-defined. An implementation should choose a tool-name that all lints recognized by it are defined within, and may accept variants on that namespace with the same meaning (for example, if an implementation accepts implementation-defined lints with the foo namespace, it could also accept __foo and __foo__).

This (Pre-)RFC recommends the use of the rustc namespace by the rustc compiler. Whether or not it accepts variants of this namespace is not specified. All existing lints for rustc should be categorized as core language and implementation-defined, and the latter should be renamed to this form and have it's original form deprecated. This (Pre-)RFC does not provide any recommendations for how existing lints are categorized. A future RFC is recommended to define this split, and the former category shall become mandatory for implementations to be implemented. Implementations should accept, and may issue a lint, for attributes in the second category.

For compatibility with existing tool lints, the clippy namespace is reserved for use with the tool clippy, and continues to operate as specified in RFC 2103 and RFC 2476 as well as any others I have not mentioned to this effect. Future tools that make use of tool lints should use the mechanism prescribed here.

Open Question: This Pre-RFC only seeks to extend lints, but it may be useful similarily extend attributes. One note is that attributes may have effects beyond changing a lint level, and the policy for lint level changes is that lints are minor changes. It is possible that use of (undefined) attributes of this form may be sufficient to "opt-in" to breaking changes from future compiler versions and use in alternative implementations.

Note that the rustc:: prefix is already used for internal lints, so it would have to be renamed to rustc_internal or something. But that's not a big deal, all the internal lints are unstable anyway.

Also note that rustdoc lints are about to become tool lints: Make rustdoc lints a tool lint instead of built-in by jyn514 · Pull Request #80527 · rust-lang/rust · GitHub. So I guess under your scheme they'd have to be reserved too? I don't understand the difference between an "implemention defined lint" and a normal tool lint.

I'm not sure if there is a semantic difference, actually. It's basically a "don't use this tool-name" unless you are this tool, or provide something identical to it (which is already . The reason why I reserved clippy was because if there was a difference between the existing scheme and this one (other than removing the "unknown tool name" error), I wanted to not break code that already used clippy. I could probably look into it in more depth, but otherwise, rustdoc probably could be likewise reserved and fall under the existing 2103 rules. If there isn't a difference, RFC 2476 would almost certainly count as sufficient documentation for the former, and I'm sure something equivalent exists for the latter, and they could simply fall under these rules.

Unstable lints may pose an issue to this. While I think that requiring a feature to enable it could fall under implementation-defined meanings, it would cause a disparity between implementations, where one implmentation accepts but ignores them, and then rustc errors unless you enable a feature (whereas for any other lint under these rules, an error would only occur for deny and forbid, so the behaviour may be suprising). One possibly would be making an exception, and reserving certain names as ill-formed unless accepted, but I'd rather not specify an exception for just rustc. I am open to suggestions in that regard.

If there is no difference between tool lints and implementation lints, I'm not quite sure what you're suggesting. Is the point to separate "core language" lints from lints related to the current implementation, by putting them in different namespaces? I don't know if that makes sense to do - for example, the warning for [].into_iter() only exists because we want to add impl IntoIterator for [T; N] at some point, but it's not implemented yet. It's both a language and implementation lint.

I also think any changes here will be a lot of churn, and I'm not sure what benefit they bring.

The goal is to allow different implementations of rust to provide additional lints that can be manipulated by user crates without having to worry about whether the particular implementation is in-use. A particular example I had was https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=996ad6894fee3b3796fcfa78d92cf8d0, which uses the lccc::pedantic lint from the implementation I am working on (lccc). Currently, this would only be "valid" code when built in lccc (on rustc, this gives an unknown tool error, on lccc, this would trigger the lccc::rust_union_layout and lccc::pointer_interconversion lints). Additionally, a goal is to decide the lints implemented in rustc that should are considered part of the "core language", that is, something that all implementations should provide, and not simply a lint that rustc does provide.

For a lint like this, I would assume it would be a core language lint, but is triggered because of the implementation (for example, using an attribute, or a stability flag. I don't know what this particular lint is off the top of my head). It would be similar to the incomplete_features lint, which should be core language, but different implementations would apply it for different features (or, if it doesn't support unstable features, for example, stable rustc, it wouldn't apply it at all).

Honestly, I think essentially all lints should be considered quality-of-implementation questions, not part of the core language. Given that changing lints is a "minor change" anyway...

2 Likes

If that's a good way to go, then it should be, however I'd still recommend the use of namespaced lints for this. I did just check, and simply using deny(pedantic) does not error (it does warn though, which makes it incompatible with deny(warnings). Seems interesting, though). Though, I believe that adding a lint is a question for the Lang Team, not the Compiler Team, so it seems to me like that would make it a question of the core language.

A particular example I had was Rust Playground, which uses the lccc::pedantic lint from the implementation I am working on (lccc ). Currently, this would only be "valid" code when built in lccc (on rustc, this gives an unknown tool error, on lccc, this would trigger the lccc::rust_union_layout and lccc::pointer_interconversion lints)

It sounds like what you actually need is a way to register tool lints. I think @flip1995 mentioned something like this that's opt-in, but I can't find it documented anywhere. I think having #[register_tool("lccc")] or something would be a better approach than splitting up the existing lints, though.That would be pretty easy to implement, just add a check here: https://github.com/rust-lang/rust/blob/a1a13b2bc4fa6370b9501135d97c5fe0bc401894/compiler/rustc_lint/src/levels.rs#L244

Yeah, what you asking for is exactly what tool lints are for. This is already an accepted feature and implemented in the compiler. Currently the tool lint feature can only be used (by implementer) by Clippy and soon rustdoc, because of a a hard coded whitelist. This was also the case for tool attributes when those were first implemented and then was removed once the first non-official tool wanted to use tool attributes.

If you want to use tool lints attributes, the only thing that currently blocks you is that hard coded whitelist, which causes the unknown tool error. The only reason why this whitelist is still there is that no one complained about it. If you're willing to put some work into this (mostly writing tests and making sure existing tools don't break), I'm supporting removing this whitelist and open up tool lints for everyone.

Yeah, the proposal seeks to remove the whitelist. The split of lints was so that rustc lints can also participate the same way without being warned (or denied) about by implementations that don't understand a particular lint. The issue I raised works both, ways. For example, an implementation that does not have unstable features may not include the stable_features lint. In such an implementation, naming this lint explicitly would cause a compile-time error when used with deny(warnings) as it would trip unknown_lints.

Using deny(warnings) is a bad idea in general, though, it will break any time a new lint is added on nightly (which is why the stability guarantees explicitly don't cover it). Docs.rs uses --cap-lints warn for exactly this reason.

Yeah, the proposal seeks to remove the whitelist.

I'm good with that. If you want to address this in rustc, I'm happy to mentor this work.

In such an implementation, naming this lint explicitly would cause a compile-time error when used with deny(warnings) as it would trip unknown_lints.

Just that I understand you correctly: Are you proposing that there shouldn't be any "unscoped" lints and that rustc lints should be used with the rustc:: prefix? If that is the case, then I'm afraid that this will never happen. According to the Rust reference there are Lint check attributes where the name is unscoped. It is not specified how a Rust compiler treats unknown lints. rustc made the decision, that it will warn on unknown lints. If you're implementing a compiler it is up to you how to deal with unknown lints. If you also warn on them and don't implement all of the lints of rustc, than you will run into the problem, you're having. But you could also just ignore unknown lints, or keep a list of known rustc lints, that aren't in your compiler.

On the other hand, if you want to add lints to your compiler, that aren't in rustc, you can either use tool lints (given the whitelist is gone), to prevent rustc to warn on your lints, or you can just use normal lints and be fine with rustc triggering unknown_lints on them. In the later case, users of your compiler, that also want to use rustc will have to add lints with #[cfg_attr(feature = "lccc", warn(your_own_lint)] (this is how Clippy did it before tool lints).

2 Likes

I'd prefer bringing the proposal to the stage it needs to be brought. I'm trying to avoid the rustc source code while working on lccc.

This sort of sounds like a "if you don't have a lint we do, it's your problem, but if you do have a lint we don't, it's also your problem." Keeping a list for rustc is fairly simple, there's a documented one, and I wouldn't need to update it nightly (since the goal is, when finished, it will be compatible with latest stable rustc after 2 weeks). The problem comes when there are several implementations, each deciding how to deal with lints. The idea here was to provide a consistent mechanism for all implementations to use.

In any case, would something like this be better:

In any case, both forms should be documented as implementation-defined. However, lints of the latter form should really be deprecated, or simply accepted. In any case, code using any unscoped lints would be inherently non-portable, whereas those using the scoped forms would not. That was the idea with the split. That the people who defined the lints would decide which ones are common enough that they should be left unscoped, so all implementations should accept them, and the rest moved to the scoped system where implementations can (and should) simply ignore them.

A while back, I proposed cfg(has_rustc = "<whatever>") (which, in retrospect, is a very poor name, something like cfg(rust_vendor = "<whatever>") would have been better), where the "whatever" was implementation defined. However it did not get very far for a few reasons, so it would have to be detected manually by the package. This seems reasonable for things like unstable features, however, having to detect this for a lint is a bit excessive.

I'd prefer bringing the proposal to the stage it needs to be brought

I don't see a proposal here. What you describe are tool lints. rustc lints won't be renamed. I mean you can try, but something that exists for over 5 years like this in the language will not change. Especially not, if it complicates things.

This sort of sounds like a "if you don't have a lint we do, it's your problem, but if you do have a lint we don't, it's also your problem."

Not at all. It isn't the responsibility of rustc to take care of what other compilers do. It is also not the responsibility of your compiler to take care of things rustc does. But I admit that it may be interpreted like this, but this is because rustc was the first compiler (and currently the only one).

If your compiler has the requirement, that it has to conform to lints implemented in another compiler, this is on your compiler. The alternative would be that every Rust compiler would have to know about every other Rust compiler and know about all lints they implement or don't implement.

rustc is conform with The Rust Reference. You could propose to change the reference, but I doubt that this will be successful.

A lint name of the form IDENTIFIER is a standard lint.

And who says what a standard lint is? Let's say lccc and rustc agree on a standard set. What if a 3rd compiler is implemented and it decides to not implement any lints. Would the list then be updated? Or does the 3rd compiler have to deal with those lints in another way?

Also: Aren't standard lints just errors? (philosophical question)

1 Like

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