Crystal compiler can not detect unused code?


def hi
  id = "unused code"
  puts "hi"



╰─❯ crystal run
1 Like

I’m pretty sure it can/does and is just not included in the final binary, in --release mode at least.

No warnings hint at all

❯ crystal run --release

Correct, that’s not something the compiler currently handles. GitHub - crystal-ameba/ameba: A static code analysis tool for Crystal might be able to call out unused variables and such like that tho.

ameba does not work for this , it seems that it can only detect code style

In this particular case, I think you have to explicitly enable the rule Lint/UselessAssign, then you’d get:

[W] Lint/UselessAssign: Useless assignment to variable `id`
> id = "unused code"

Finished in 587 microseconds
1 inspected, 1 failure

The compiler doesn’t warn you, but --release mode will throw the code away as Blacksmoke16 says.

You can check with:

crystal build --emit asm


crystal build --emit asm --release

There is no unused code string in the --release generated assembler (*.s file).


You can also search for strings directly in binary files.

crystal build
strings test | grep unused
# unused code
crystal build --release
strings test | grep unused

The following commands are sometimes useful

crystal tool unreachable

def hi
  puts "hi"

def meow
  puts "meow meow"

crystal tool unreachable ::meow 3 lines


I think you are looking for the unreachable tool. See


I know this but it doesn’t seem to work for the example

╭╴🪟 $nu hello_crystal on  main [?] via 🔮 v1.12.1 took 3s
╰─❯ crystal tool unreachable
╭╴🪟 $nu hello_crystal on  main [?] via 🔮 v1.12.1 took 4s
1 Like

The unreachable tool only points out unused methods, not local vars and such.

Yeah, unreachabel doesn’t work for this. The assignment is reachable. Its effect just doesn’t matter for anything.

@aiac I’m not sure what your expectation of the compiler is here. Why do you think it should produce a warning?

I recognize that some compilers provide warnings for things like this, but the existence of a dedicated linting tool like Ameba as well as dead-code elimination in LLVM mean there’s really no reason to do this in the compiler. Admittedly, the linter likely exists because the compiler doesn’t lint your code. Chicken vs egg, but the point is that there’s a dedicated tool for static analysis that does a pretty good job.

And since LLVM is also exceedingly good at dead-code elimination, making the compiler detect dead code that LLVM eliminates anyway would be wasted effort. For perspective, LLVM is so good at eliminating dead code that many Crystal benchmarks have to explicitly avoid it via side effects so benchmark blocks won’t be completely eliminated.

Maybe you could argue that Ameba could be rolled into the compiler (as crystal tool lint or similar), but “the compiler doesn’t detect it” isn’t a useful complaint when there is no need for the compiler to detect it.