Pedantic Dart
By: David Morgan
Tags: Dart, Dartlang, Static Analysis

Evaluating all the lints

It may seem that, even taking all this into account, someone with a close knowledge of Dart should be able to sit down with the list of 146 available lints and produce a list of recommended lints without too much trouble.

But that isn’t what we’ve found; it’s simply too big a task. Just as each lint can do arbitrary evaluation on your code, deciding whether a particular lint is both correct and useful turns out to be almost arbitrarily hard.

A good way to approach a hard question is to ask for hard data. So when considering each lint, we first benchmark its performance and gather information on all violations of the lint currently present in Google’s internal Dart code.

These numbers give us a good starting point for discussion.

If, for example, all of Google’s Dart code contains only five violations of the lint, then each had better be a serious bug; otherwise, it’s unlikely that the lint is pulling its weight. The recursive_getters lint was a rare example of a lint catching a very small number of serious issues; a getter that calls itself is a stack overflow waiting to happen.

// recursive_getters; definitely not what you meant to write!

int get field => field; // LINT

If, on the other hand, we find many violations of the lint, the question turns around: the lint is going to change what a lot of developers are doing, so are we sure it’s an improvement, both overall and in each individual instance? If the lint will make a small number of cases worse, can we justify that? Or, perhaps, can we improve the lint?

The unrelated_type_equality_checks lint is a good example. This lint requires that, before you are allowed to compare two objects, they must be of compatible static types. So you’re not allowed to check if 3 and foo are equal; it’s assumed that because one is an int and the other is a String that this question doesn’t even make sense.

// unrelated_type_equality_checks; or, don't ask stupid questions!

void someFunction() {

var x = '1';

if (x == 1) print('surprise!'); // LINT

}

This sounds good, but it’s not correct for two reasons.

It fails in theory because of implements ; an object can be of more than one type, and so two objects that appear statically to be unrelated might turn out at runtime to implement the same type, and be perfectly valid to compare.

// unrelated_type_equality_checks; objects _can_ hold surprises.

void checkForSurprise(Foo foo, Bar bar) {

if (foo == bar) print('surprise!'); // LINT

} abstract class Foo {}

abstract class Bar {}

class Baz implements Foo, Bar {} void main() {

var baz = Baz();

checkForSurprise(baz, baz);

}

It fails in practice because operator== is left to each class author to implement, and nothing forces them to follow its contract. We found, in particular, that Int64 and Int32 from package:fixnum allow comparisons with int , but only when the int is on the right hand side of the == .

So, the data showed three outcomes for the lint: lots of correct findings (bugs caught), a small number of incorrect findings due to runtime types being compatible when static types were not, and a relatively large number of incorrect findings due to Int64 being compared to int .

What did we do? We improved the lint: it now knows about Int64 (and Int32 ) and allows you to compare it to int as you did before. This left a very small number of false positives due to static types being incomplete; these we opted to refactor, using casts as necessary, to make them comply with the lint.

With these changes, we were able to reach a consensus on having unrelated_type_equality_checks be enforced.