Getting around the dead code lint


#1

When writing a lint pass/procedural macro I can use registry.register_attribute(name, AttributeType::Whitelisted) to disable the unused_attribute warnings for a particular attribute.

I was wondering if there is a similar thing to ‘mark’ an item (eg: struct/function/enum) as used so that I don’t get these sort of warnings

warning: struct is never used: `Vertex`, #[warn(dead_code)] on by default
src/main.rs:289 struct Vertex {
src/main.rs:290     #[tag="position"]
src/main.rs:291     position: Vec3<f32>,
src/main.rs:292     #[tag="color"]
src/main.rs:293     color: Vec3<f32>
src/main.rs:294 }

I know that I can write #![allow(dead_code)], but I’m trying to avoid that because that will allow everything. My lint pass uses some structs to generate extra files and therefore only some structs/functions are actually touched.

Thanks if you know a solution.


#2

You can put #[allow(dead_code)] right before each item in question to mark them specifically. See the book’s section on Attributes for the difference between # and #!.


#3

Yeah I realised I could do that but ideally the lint/plugin should tell rustc to ignore certain structs, not the user.

Alternatively, is it possible to modify the AST during a lint pass? I realise that would violate all meanings of safety/type checking/robustness but that would solve the issue as well.

Pretty much I’m looking for the hackiest possible solution to make this as ergonomic as possible to the end user. :smiley:


#4

Why would some hack (that will necessarily conceal its meaning) be more ergonomic than an annotation that does exactly what it says?


#5

Haha. Perhaps I should give some more background information…

I am working on a ‘lint’ that takes a function and spits out a GLSL shader. You can view the code on GitHub. Every function marked with the #[shader] attribute will be converted, so in a sense they are being used: not by rustc's trans but the by lint pass.

There are two things I think I could do:

  • Remove the function from the AST. This is the preferable option because then these functions won’t need to be run through trans.
    • However I’m pretty sure the AST is immutable by the lint stage, and if I did remove the function it might violate type checking etc.
  • Make each function marked with #[shader], and each struct/function/enum referenced by that function exempt from the dead code lint.
    • Yes I could do that by manually adding #[allow(dead_code)], but when a function begins to depend on heaps of other items it becomes annoying.

If you have a better idea then I’d be open to suggestions as this is still highly experimental, but at the moment I’m just trying to implement whatever seems the least difficult.


#6

You have 3 sub-problems:

  1. your lint should run before the dead_code lint gets to the item. As all lints are run in lockstep (which will hopefully stay the same) a simple way to ensure this is to implement visit_crate(..) and use an actual Visitor to walk the syntax tree, as I did on my blog (though for different reasons).
  2. you don’t want to add annotations, but have the dead_code lint be #[allow]ed on your #[shader] items. Your lint has a reference to the LateContext (be sure to implement this) which contains a ty::ctxt reference , which has a node_lint_levels hashmap. which is in a RefCell that you should be able to borrow mutably (be sure to return it as soon as possible). With this you could use your Visitor implementation to walk the crate and set all lint levels for items marked #[shader] as allow(dead_code) once you solve the third problem:
  3. for that to work, you need the LintId of DEAD_CODE. It’s defined in rustc::lint::builtin, so see if you can use rustc::lint::builtin::DEAD_CODE.

Good luck. :smile:


#7

Does modifying node_lint_levels work at all? As far as I know, it is collected for the benefit of lints in trans which runs after the main lint pass, but it does not affect the main lint pass.


#8

I’m not sure. The LintContext type has a lookup_and_emit(..) method that uses LintStore::levels to look up the lint level. This may or may not be the same as the node_lint_levels.


#9

Yes I could do that by manually adding #[allow(dead_code)], but when a function begins to depend on heaps of other items it becomes annoying.

My understanding is that if you mark a function #[allow(dead_code)], you don’t need to mark items referenced by that function. It’s a bug if you need to.


#10

That’s a good point. I tried adding #[allow(dead_code)] to just the one function and yes, it removes the warnings for that function and other structs used by that function.

However it still complains about unused imports (but they are used in the allow(dead_code) function), and unused struct fields.

Is that a bug or expected behaviour?


#11

If rustc warns used imports (irrespective of whether using code is dead) as unused, yes it is a bug. Currently, unused imports lint does not use dead code information.

Same goes for unused struct fields, but note that it is a bit subtle. Writing to struct fields doesn’t count as uses, only reading: if you never read, writing is in a sense dead. Wording probably needs to make that clear. If you are reading a struct field in an allow(dead_code) function but it is warned dead, yes it is a bug.