-
Notifications
You must be signed in to change notification settings - Fork 166
New lint: prefer_void_to_null #1100
New lint: prefer_void_to_null #1100
Conversation
To help encourage people to use void, and discourage use of the academic & suprising Null type now that void is here to better replace it and serve its functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned about the number of false positives for a rule like this. It's clear from the comment that there are cases where Null
is appropriate. Can we identify them so that we don't have false positives? If not, have you run this rule over any large code bases to estimate the false positive rate?
The Null type is extremely academic. A function should return Null when it never returns (loops forever or throws an error). A function should have a Null parameter when the function is never called. The same is true of functions returning And even in these cases, you can and debatably should return void. There is a high false positive rate, and we can't really detect those cases. I think I've seen about 100 cases of where Null was correctly used internally. The value in this lint is something to the effect of, "if you don't need to use Null to make your code work, you don't want to use Null to make your code work," and a tool to change people's mindsets from "Null is my default behavior" to "void is my default behavior" and skip straight past "I need to have a deep understand of both types to know when to use which" because most people don't ever want to use Null unless they (most likely) already understand it. |
I'm not questioning the validity of using But research shows that lints that have large numbers of false positives are generally not used, which means that they don't bring the intended benefit to the user. As a result, we have attempted to maintain a very high bar for the lints in the linter package. Unless there's evidence of a large amount of support for a rule with a high rate of false positives, we tend to not allow them. Perhaps @davidmorgan could weigh in on internal use, or @leafpetersen on the desirability of effectively preventing the use of That said, there might be other ways to address the issue. We could, for example, always remove |
Not sure of all of the context, but I'd be hesitant to have a blanket warning about using Null. There are valid and good uses for it that aren't covered by void. I'm also worried that it's hard to avoid using Null in places because we weren't able to change inference to infer |
The two thumbs-ups are proof that there is some desire for this. Theres also a g/dart-lints thread where everyone seems open to this. In my experience, even the best google engineers are completely confused by the difference between the two types, and trying to use Null where its not required. Null is best as an obscure type that people can use if they are interested in type theory, like F-bounded quantification, etc. Those people won't enable this lint. Its hard to fairly count usages of this, but my best estimate is that Null has a valid use for one in 28000 LOC. Given that void is used once per 125 LOC, I firmly believe that you want void instead of Null about 200 times to 1. That's accurate enough for me. |
|
Yep, and I see 3 cases of that having been used internally: I also have used it to do poor man's pattern-matching: https://github.com/dart-lang/angular_analyzer_plugin/blob/master/angular_analyzer_plugin/lib/src/model.dart#L626 Null has uses. The description of the lint says: "Do not use Null where void will do." In these cases, "void" will not work, so the lint is not advocating a full-on ban, and even gives practical instructions on when to use Null and ignore the lint. I have simply never made a customer happy about Dart by describing Null vs void. They get frustrated and confused. There is no need for that confusion, except in rare cases. The only reason we have this problem is people are used to using Null and have misconceptions about it. The best way to fix that is to offer them a simple new option -- such as, "enable prefer_void_to_null, and see if you still have any questions." |
That sounds like an easily identifiable false positive that the rule could easily check for and eliminate. Are there others? |
Sounds like a good idea to flag all those questionable uses of
// Assume non-null types are available.
// `T?` is nullable, plain `T` is non-null except that `Null` == `Null?`.
abstract class Thing {
// Things may or may not have a name.
String? name();
}
abstract class BelovedThing implements Thing {
// A beloved thing definitely has a name.
String name();
}
abstract class BoringThing implements Thing {
// A boring thing never has a name. If we know statically that it's
// a boring thing, we might as well know that there is no name.
Null name();
}
So the status might be as follows: Every occurrence of WDYT? If that's too harsh, do we then need to add support for some |
As a followup on this, I certainly overlooked at least a couple of cases where // Default values may need a `Null` type argument, possibly inferred.
class A<X> { final X x; const A([this.x]); }
A<X> foo<X>([A<X> a = const A<Null>(), List<X> l = const []]) => a;
// An empty map may use a `Null` value type for wider applicability.
const hitmap = <String, Null>{};
main() {
void bar(Map<String, Map<int, int>> map) {}
bar(hitmap); // OK.
} So we do probably want to have an escape hatch such that some actual type arguments can be |
I think we can safely ignore inferred types and allow this lint to flag places where the user has explicitly used |
Yeah, I think this change will definitely need an escape hatch. Even I assumed I'll change it so that Null within a generic function type declaration is OK, that seems like a really strong indication the user is doing something useful & intentional. |
Also made allowances for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of false positive rate are we seeing with these changes in place?
<void, Object>{null: null}; // OK | ||
<void, void>{null: null}; // OK | ||
|
||
// TODO(mfairhurst): is it worth handling more complex literals? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of "complex literals" are you thinking of?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like this: <List<Null>, Map<Null, Null>>{}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How often does that arise in practice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is very rare. I'm not sure how to search for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming @bwilkerson has no lingering concerns, LGTM!
Kicking appveyor in the hopes that its break is transient. 🤞 |
Only the question I asked before:
|
appveyor is still unhappy but surely unrelated to this. https://ci.appveyor.com/project/pq/linter#L129 No need to hold for it. |
Its extremely hard to come up with a false positive rate, but best I can tell, I would expect there to be nom much more than 100 false positives internally, excluding third party code. I found ~6600 violations that are likely >99% real, ~200 violations that seem >80% real. That'd be about ~106. But its impossible to triage so large a number. I also don't know what the denominator on that is. Arguably void seems ~450x more common than valid usages of Null. That number would rise as violations get fixed, but it also counts void where people would never write Null in the first place. |
FWIW issue tracking appveyor failure opened: dart-lang/sdk#57759. |
For external code I've analyzed, there are some hardcore users like mockito and package:expect and the SDK itself. Otherwise, about 70 or so false positives, and valid voids outnumber valid Nulls by about 100x. Same caveats apply |
@MichaelRFairhurst: if you do want to flag this as experimental, that's described by a lint's |
So in terms of false positives, the false positive rate will be extremely, extremely low based on the prevalence of Future and such. Over time, if people fix all their Futures, and get out of the habit of it, the false positive rate will approach 100%. Assuming you only use Null when you thoroughly think it through first, you can probably get good enough that you never misuse it. (and that includes "Future" which itself has valid usages). If everyone is OK with that being true, but we also accept that we need some kind of tooling in the meantime to encourage a switch from Null to void, then this lint can probably go out as such and we can see if people request it to be relaxed in any kinds of ways. Maybe we can introduce a new Maturity later, "Maturity.sunsetting" that we can add to this when we have good evidence that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working hard to minimize the possibility of false positives! I think this is a much more valuable lint because of it.
Hear, hear. Thanks a million @MichaelRFairhurst ! PS: the appveyor failures appear to be unrelated (see: dart-lang/sdk#57759). |
* master: (41 commits) New lint: prefer_void_to_null (dart-archive#1100) Fix warnings in the linter code base echo dart version (dart-archive#1103) Update google.yaml (dart-archive#1094) improved description (dart-archive#1092) Address review comments special case with ignore for lines_longer_than_80_chars 0.1.58 (dart-archive#1086) Roll-back use of optional new/const; add `no-preview-dart-2` bot. (dart-archive#1084) 0.1.57 (dart-archive#1080) address review comments address review comments fix lines_longer_than_80_chars for \r Fix npe in non_constant_identifier_names chore: set max SDK version to <3.0.0 (dart-archive#1077) Update google.yaml (dart-archive#1076) Google internal rules (dart-archive#1075) Prevent an NPE when dealing with incomplete/invalid code add missing lints in analysis_options.yaml (dart-archive#1069) fomatting fixes (dart-archive#1067) ...
To help encourage people to use void, and discourage use of the academic
& suprising Null type now that void is here to better replace it and
serve its functions.