-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a lint to require types on generic methods #57779
Comments
Doesn't this sort of defeat the purpose of using Dart 2 at that point? |
The purpose of using Dart2 in Flutter is to enable more AOT optimisations. I don't see how this affects that? |
I've also has some discussion here #34058 I had proposed something like:
an example is
which will always find some T for any two args. Either we could lint and require:
And we could lint
either based on the And we could even get smart enough to make an exception for:
|
@Hixie I have a patch for |
Can we put @optionalTypeArgs on max and min? |
IIRC dart libraries don't use We could have a special case for those 2 methods but are those 2 methods really different from other generic methods? |
could we move @optionalTypeArgs to where @OverRide is defined, maybe? I guess min and max aren't that special; you could accidentally use it like this:
...which is bogus, and we'd want to catch it. But really I think it'd be better if we just had two overloaded versions, There's other cases that are more special where it's ok to remove the type, because there's no way to use the method incorrectly that would be caught by requiring types but not caught some other way as well. But by and large most of these methods giving the type explicitly will prevent unintentional mistakes. |
That would be up to the language team to decide (and they might not be reading this thread). Are there patterns for methods that don't need to be checked that the lint could look for? Is it reasonable to omit the type argument(s) when they are only used for parameter types? Or only when two or more parameters have the same type parameter ( If nothing else, instead of special-casing |
I think a good argument can be made for saying that min and max should have the type arguments, it's just too ugly in that specific case. :-) |
I find it hard to know where we are currently omitting type arguments so it's hard for me to say with any certainty whether there's any patterns to where I think it makes sense to require them and where I think we should allow them to be omitted. (All this only applies to framework code. Whether end developers would want to give types or not for their code is entirely up to them; we wouldn't enable this by default for them.) |
@a14n Would you mind special casing |
Here's which methods are used without specifying type parameters in flutter:
|
I'm not sure what the pattern is, but here are the ones I think should always have types:
(For some of these I would prefer to say had a default value which would get used if no type was specified. Default type arguments aren't currently a language feature, however.) The following are cases where you often don't know the type and it's ok to pass dynamic, which is the default, and so omitting it is fine (and we should use
The following are cases where the type is used for both the arguments and the return value and it's fine whatever the values are, except that in the case of min and max I'd rather we banned
The following cases I am not familiar with off-hand:
|
I don't know the flutter code, but I would say fold is the only one of the core places where I would want to include a type parameter.
You really want to have to specify And here?
Can you maybe give some more info on what bugs you're trying to avoid? I fear that, if written this way, this lint will be far too strict to get much usage. OTOH, I know that there are sometimes issues from inference choosing the wrong thing and I'd like to have lints targeted at those cases. For instance, this throws me off:
as does this:
Bad example because this code is too complicated (an => inside an =>? no thanks!), but the important part here is that you need to either type
Luckily, if you have implicit downcasts turned off, the resulting List will usually get flagged. But notably, if you don't have implicit downcasts off, its often better to let the types flow:
So I'd rather try to lint inputs & outputs to functions that appear wrong. We could lint places where
|
I definitely think it helps to specify the type on Same with This is especially important because lambdas don't have a way to specify their return type for some reason. We definitely want to turn off implicit casts ASAP for Flutter framework code. (All of this discussion is only about the lints we have on in Flutter framework code. Flutter developers use a much less stringent set of lints by default.) I'm all for the other lints you suggest, but they are solving a different problem. The lint suggested for this issue is really just dealing with the way "always_specify_types" doesn't actually work "always". (We enable "always_declare_return_types" and "always_specify_types" but they don't cover generic type arguments for some reason.) |
Just catching up to this one. Have y'all chatted with @leafpetersen ? Leaf and I are working on some strict analysis flags (#33749). For example (The hard part is coming up with rules that have a low false-positive rate. We found it difficult to get adoption of |
I'd like to point out that the analyzer can never know what you intended. What you're really doing here is writing the type twice, hoping that if there's an inconsistency it will be reported. The analyzer already reports inconsistencies, though, where one thing says something is an int and another thing says it isn't:
So one way to attempt to catch more inconsistencies is to double, triple, quadruple specify types. It stands that any value that gets both created and used is already double specified, though, and I question why having a third redundancy would catch bugs and not just increase headache.
The true answer here is almost certainly more a historical thing, such as specifying types being more common before type inference, and generic methods coming after type inference, and maybe just nobody revisited the issue after generic methods came out. But if I may digress and offer my opinion on this very controversial topic. Firstly I'll point out that the discussion here already isn't suggesting to "always specify generic types," or we wouldn't have the issue with "min"/"max." So always is not really always. I'd also argue this is for the same more or less the same reason that they don't require this:
The fact that we don't require that, and that we can type check that. is quite literally the exact process of downwards type inference. Another example of how "always" is not really "always." I don't want to make a slippery slope argument. I draw the line somewhere, and Flutter's style guide draws it somewhere else, and that is 👍. I don't want to fraction Dart though, and a line has been drawn by the Dart language team, so I don't know how much I want us to invest in tooling that supports a different line, without a higher burden of usability evidence. I'd rather focus on example code snippets that have problems and see what we can do about those particularly/generally. |
Is there some way we can write our own lints, if this can't be an officially supported lint? I remember there was some talk of an analyzer plugin mechanism a while back; did that ever get implemented by any chance? |
We can certainly implement a lint after we have a clear understanding of what the lint should do, but I, at least, do not yet have a clear understanding of the semantics that you're asking for. (And @a14n already has an initial implementation that is primarily awaiting a resolution of this discussion in order to be landed.) I believe that you want to enforce the rule that invocations of generic methods should have explicit type arguments, but it sounds like there are exceptions to that rule ( |
I would be happy with a lint that triggered for all cases except those marked If we can't annotate For |
This sounds a lot like something I was talking with Mike & Leaf about. There are two different strictness levels we could choose for |
I think it would have to be an optional lint. There's plenty of people who would have no objection to this code: int width = foo.width(); // returns the pixel width
double textSize = text.measure(); // returns the width of the text, in fractional pixels
if (max(width, textSize) > maxWidth)
// ... |
ah yes, I should've been more clear. This will be an opt-in flag. |
I'm not sure to understand why I also don't understand why FWIW I'm testing a lint with a special case for methods having
With that rule min/max are not linted. You can see the changes on flutter consecutive to this lint in flutter/flutter#22096 ( I'm close to throw a TooMuchTypesError :) ) |
maybe a better lint to address my concern is one to ban num entirely (whether given explicitly or by inference). |
Created #57793 to track |
@Hixie Does it mean that with a |
With |
(The case that made me file this bug that I think we definitely want to be explicit for are |
@Hixie Are you trying to express the conditions under which type inference can validly infer the return type ( If so, then it seems like it ought to be sufficient to have at least one parameter of that type rather than requiring that all parameters are of that type, such as in |
The idea is that ideally in Flutter framework code we would never rely on inference. In some cases (pretty much only |
So, are you suggesting that any method where all of the parameters have the type parameter as their type are likely to be more like operators than methods? Like |
It also sounds like maybe you don't really want You could always make an Then there'd only be the three usages of From a philosophical perspective, though, I don't think |
@Hixie - Is there any way we could adjust/fix inference to make it work for you? Are there any particular problematic examples you're trying to solve by disabling it? Happy to chat offline too, if that'd be easier. We tried to tighten up the type checking in Dart 2, but we didn't get as far as we'd like. Since the language is pretty forgiving still (implicit casts, instantiate-to-bounds, dynamic, least upper bounds, etc.) type inference can find solutions that are valid in Dart 2, but they're too broad & result in implicit casts. This isn't necessarily a fault of type inference per se (you could write the exact same type explicitly), but rather the "loose" constraints/checking it's operating under. That's the motivation behind the |
Yes, this. Any time inference chose a "wrong" type, causing you a headache, that same error would have happened if you'd written it yourself:
moreover, specify the type can create problems:
Not to say inference is perfect. Notably, inference chooses "Null"/"Object" a lot more than a person would write that themselves. I think strict mode is planning on rejecting such cases. And implicit downcasts create problems but we are definitely planning on making it common to disallow them. So its like "bowl is hot, warn me if I use bowl!" instead of "bowl is hot, use oven mitts!" |
Would it be better to have a way to detect places where there is an implicit downcast than to have a lint like this? |
We definitely want to turn on the lints that ban implicit downcasts, but that's a separate issue. This issue is about being explicit with types everywhere. We already require that all arguments, fields, variables, etc, be typed, and that all generic classes be explicitly specialised. It's just that generic methods aren't caught by that lint today. The problem isn't that inference does the wrong thing, it's that I would like us (in Flutter framework code) to use the language (analyzer, compiler, inference, etc) to verify that the code does what we think it does, instead of having it write part of the code for us implicitly. Re |
I don't think this is a good metaphor. You aren't simply writing the code it would have written implictly, you are overriding what it knows. This can suppress type errors:
Here, if Now you may say its absurd to use Similarly,
In example A, if this is the case, you get a static warning. Example B fails at runtime. As long as you have implicit downcasts, writing types does more than just make implicit code explicit. Writing types asserts that values are of a certain type when there may be zero reason to believe they actually are. It introduces implicit code (downcasts) if you do it wrong. (nevermind the case of where adding types asserts something is not something it is, ie I'd highly recommend turning off implicit downcasts before considering this as a useful restriction....and maybe even giving it some time to settle, how inference feels in a safer environment, before going full in on explicit types everywhere. |
We don't want implicit downcasts either, and will be dropping them ASAP. That's blocked by various things currently. I'm confident that I want this lint. We already have a lint that would require every |
Actually I just got a lint about missing a type on |
Agree. |
Right now, there's no lint that catches (e.g.) the lack of a type argument on the
Iterable.map
method.It would be nice to be able to know where we're missing them.
Also, ideally such a lint would not trigger if the method in question is labeled with
@optionalTypeArgs
.cc @a14n
The text was updated successfully, but these errors were encountered: