Skip to content

Declaring a lint's configuration in the same file as its implementation #11018

Open
@Centri3

Description

@Centri3

TL;DR:
I propose we turn this:

declare_clippy_lint! {
	/// Lint description
	#[clippy::version = "1.72.0"]
	pub NEW_LINT,
	nursery,
	"default lint description"
}
impl_lint_pass!(NewLint => [NEW_LINT]);

With some configuration options in lib.rs and conf.rs.
Into this:

declare_clippy_lint! {
	/// Lint description
	#[clippy::version = "1.72.0"]
	pub NEW_LINT,
	nursery,
	"default lint description"
}
impl_clippy_lint!(NewLint => [])
// ^^ Maybe this can also be generated automatically be `#[derive(Paperclip)]`?

#[derive(Paperclip)]
struct NewLint {
	#[paperclip(key)]
	avoid_breaking_exported_api: bool,
	/// lint name: etc
	///
	/// Some lint description
	#[paperclip(key = "excessive-nesting-threshold")]
	threshold: u64,
	#[paperclip(default)]
	nodes: NodeSet,
	// Maybe even something like...?
	#[paperclip(default = new_nodeset)]
	nodes: NodeSet,
}

fn new_nodeset() -> NodeSet {
	todo!()
}

Creating a new lint in clippy, that has configuration options, is a bit of a pain. You need to edit conf.rs, then lib.rs, then hope that there's no merge conflicts as other lints are merged. Both of these files are the exact opposite place you wish to look to edit your lint's implementation... Which is ideally the same file of its declaration. So let's change that :D

But how, exactly? Wouldn't you need a central hub for all lints to be added to the LintStore? Not exactly, there is a simple-enough way to achieve this: global constructors. And a second, bit more hacky way that I can't even begin to understand...

Global constructors aren't really a thing in Rust, let alone life-before-main, you either have a lazily initialized static or a static evaluated at compile time. But there's nothing stopping you from doing it anyway! This is what both the inventory and ctor crates do (and if it's made by dtolnay, you know it's good). The second way is a bit more complex: https://github.com/dtolnay/linkme

But... There's a major issue. Compatibility. This is an issue with both of these methods. Even if you create a Vec that is filled with every early/late lint, you still need it to run everywhere. These crates support many platforms, but what about ones they don't? They're small, but this is something to keep in mind.

Note though that, AFAICT, linkme is compatible on all platforms, it just can't be built on them.

But, if that's not a big deal, what will this solve? In short, it'll make Clippy's codebase both cleaner and easier to hack onto, with the cost of slightly higher compile time and not supporting a few platforms. Maybe we can convert this to the standard lib.rs under the hood before releasing, but that might be even hackier than how it works currently... As it stands, doing everything right mentioned at the start, despite Clippy having great documentation nowadays, is still pretty difficult and a bit ugly, and this could make it a ton nicer.

Upsides:

  • Easier for new contributors to hack onto
  • A cleaner codebase

Downsides:

  • Less straightforward implementation
  • Compile time
  • Decreased platform support (for inventory)

Alternatives

  • Generating a majority of lib.rs automatically with the same #[derive(Paperclip)] and cargo dev update_lints

Open questions:

  • How would this handle lints with shared configuration options, like avoid-breaking-exported-api or msrv?
  • Would this really be worth the effort?

@rustbot label +C-an-interesting-project

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-an-interesting-projectCategory: Interesting projects, that usually are more involved design/code wise.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions