Hello. I just spend a lot of time on one simple thing that I didn't know about dart and linters might be helpful here.
Problem is that if you don't specify the generic type, it will be cast to dynamic.
See example here: https://dartpad.dev/d7ba98df97906bbd768ea13195580776
Now I had 10 times more complex code with multiple types inside types. And you can see how error-prone this is. I forgot to add <T> at one place, the compiler shows everything right, but it would crash at runtime.
Now that I know why this happens. Looking at the docs:
In most places in Dart, a type annotation can be omitted, in which case the type will automatically be dynamic
I didn't know this obviously. This is really weird, I don't know for other languages that work like this.
Maybe a lint rule that warns you about this? What do you think?
Someone pointed out that you can have this as a part of the static analyzer: https://dart.dev/guides/language/analysis-options.
You need to configure it for your project.
Still think it's weird that type-safe language works this way. I feel like many beginners gonna stumble here. Feel free to close if there's nothing to add here.
Note that dynamic is not used because you use a raw type (like MyGenericClass rather than MyGenericClass<int, String>), it is used because the type is raw _and_ there is no bound on the type parameter in the declaration. The general rule is that a raw type denotes the result of "instantiation to bound".
In any case, I agree that it would be helpful to call out specific situations in this area:
class A<X extends num> { X x; }
void fooA(A a) { // OK: `A` just means `A<num>`, which is a precise static type.
// Typed: `a.x.isEven` is an error.
}
class B<X> { X x; }
void fooB(B b) { // Lint raw `B`: it introduces `dynamic`.
// Untyped: `b.x.whatEverYouWant` is accepted silently.
}
It might even be helpful to lint _every_ situation where a superinterface is a raw class (it is quite likely to be a mistake, especially if there is a type parameter like X above which isn't used anywhere in the superinterfaces).
Hi @itsJoKr, I created #2181 to directly aim at the situation where instantiation to bound implicitly introduces dynamic into various kinds of types. This issue may be about a broader topic (for example, instantiation to bound may take place in situations where there is no type cast at all). You may wish to update your description in this issue in order to point out situations that are not covered by #2181.
FYI: Turns out that there is a way to request a very similar kind of check as that of #2181 already: Put strict-raw-types: true in analysis_options.yaml. Thanks to @srawlins for the heads up about this feature!
I think what would help with my problem is also implicit-dynamic: false.
It's just the question do we want this as a part of lint since it's error-prone (at least for people not familiar with dart). Not everyone uses analysis. We can close this if that's the decision for now.
/fyi @leafpetersen
I thought you had implicit-dynamic: false already. The question about whether this should be a lint or a hint often goes as follows: 'It should be a hint, because they are shown to everybody who doesn't actively disable them' vs. 'It should be a lint because this allows developers to tailor their own level of strictness in various ways'. In this case the hint seems to be a better fit, so maybe the real question is whether it should be on by default.
In this case the hint seems to be a better fit, so maybe the real question is whether it should be on by default.
I don't understand this statement, probably because I have a different view of hints vs lints. Both are used to catch possible errors that are not specifically called out in the language specification. The only difference from a user's perspective is that hints are enabled by default while lints are disabled by default.
While we don't generally refer to them as such (because of implementation differences, I suspect), the strong-mode and language options are effectively lints in that they are disabled by default. (It's unfortunate that they are enabled differently, because I think users find it confusing.)
Because they are enabled by default, hints are required to (a) indicate an actual problem in the code (such as "this will always fail at runtime") and (b) have zero false positives. While these checks for dynamic have no false positives that I'm aware of, they aren't guaranteed to represent actual problems in code. As a result, I would not be comfortable making them be enabled by default.
The only difference from a user's perspective is that hints are enabled by default while lints are disabled by default.
OK — but the behavior I see is that the hint is emitted (when hints in general are enabled and) there is an analysis_options.yaml or an --options=... pointing to a file that contains the strict-raw-types: true, and it is otherwise not emitted.
The fact that we have to ask for it in analysis_options.yaml is what I was thinking about when I implied that it wasn't enabled by default. We could also have a setup where we could ask for disabling it in analysis_options.yaml.
I don't think the strict-raw-types will pass the test about "will always fail", because raw types are well-defined and may well be exactly what is intended (and they don't necessarily involve dynamic). But it is error prone when they do introduce the type dynamic implicitly somewhere in a type.
Right. We're getting kind of far afield from what this issue is about, but that's why I said
While we don't generally refer to them as such (because of implementation differences, I suspect), the
strong-modeandlanguageoptions are effectively lints in that they are disabled by default.
Even in the user interface we refer to the diagnostics introduced by strict-raw-types (a strong-mode option) as hints because of how they are implemented, but I think that's the wrong choice and we should label them as lints. Or do away with the difference in the UI. (I was kind of hoping the spec would stop containing warnings and we could use "warning" as the label for hints and lints. But we didn't.) Either way, I think we've created confusion in our user's minds, which needs to be fixed.
The language team just agreed that we should clean up the mechanisms used to control these things (in particular: dynamism), so there's hope. ;)
@itsJoKr, I think we can close this now, please create a new issue if any of the topics that you raised haven't been resolved.