Dart/Flutter linter rule: the type to index a map should be the key type of map? - flutter

For example, I have Map<int, int> m;. Then I can write down m['hello'] without any compile-time error, but of course, cannot find any element at runtime. I hope it will produce an error (or warning) at compile-time or lint time.
This is a big problem in many cases. For example, when I refactor Map<A, int> m into Map<B, int> m, I want to have compile-time errors for all accesses like m[some_var_of_type_A], instead of no compile-time errors and suddenly it explodes at runtime. As another example, the de-serialized JSON is of type Map<String, ...> but the key is actually a int. So it is tempting to do var userId=42; deserializedJson[userId] but only to find errors. Actually need to do deserializedJson[userId.toString()].
You know, dart's type system is so strong (even null safe!), and I really enjoy it since it catchs a LOT of bugs at compile-time. So I hope this problem can also be addressed at compile-time.
Thanks for any suggestions!

There currently is no lint to warn about doing lookups on a Map with arguments of the wrong type. This has been requested in https://github.com/dart-lang/linter/issues/1307.
Also see https://github.com/dart-lang/sdk/issues/37392, which requests a type-checked alternative to Map.operator []. In the meantime, Dart's extension mechanism allows anyone to easily add such an alternative themselves. For example, package:basics provides a type-checked Map.get extension.

NOTE:
The original answer was wrong and has been edited to:
point out the right/better answer
explain why the original answer was wrong
Thank you #jamesdlin for pointing this out.
Better answer
As pointed by #jamesdlin in his answer, the lint rule mentioned in the question has been requested in the flutter Github issues, and not in production yet.
Original Answer (wrong but kind of related to the question)
Why it is wrong:
The question was asking about the lint rule when using an index of Map. The answer however gave the lint rule about initializing a map using the wrong index (By the wrong index, I mean different data type).
Below is the answer:
There is a lint rule for this.
For example, if you define a Map like this ->
final Map<String, String> m = {
1: 'some random value',
};
It shows an error right away and this won't compile. This is the error ->
Error: A value of type 'int' can't be assigned to a variable of type 'String'.
1: 'error because index is of type String but assigned value is of type int',
^
Error: Compilation failed.
See the official docs where this lint rule, map_key_type_not_assignable is defined.
I have tested this in dartpad and vs code. Both IDEs show this error.
There could be some issues in your IDE configuration if you're not seeing this lint error.
As for your question, there is already a lint rule for this as explained above.

Related

Dart: Null-safety and retrieving values from maps

there is one thing I can't quite wrap my head around concerning null-safety in Dart, and that concerns how to safely retrieve values from Map<String,dynamic> (I've read the FAQ from the Dart docs).
Basically, the following code in the DartPad with null-safty enabled is valid:
void main() {
int i;
Map<String, dynamic> map = {"key": 1};
i = map["key"];
print(i);
}
Which I do not understand. Why can I assign map["key"] to i without the compiler shouting at me? From the docs:
Code should be safe by default. If you write new Dart code and don’t
use any explicitly unsafe features, it never throws a null reference
error at runtime.
But exactly this is happening. If, in the code above, the key is not in the map, or contains some random type, the program will crash on runtime, which I though is what should never happen with null safety.
I'm particular interested in this since I'm writing a Flutter app and don't understand how to properly deserialize the JSON data I fetch from the DB (try..catch? Special syntax like ??= ?). Even though I don't have the 'non-nullable' language feature enabled (I can't even write int? val without getting a warning), the compiler does not seem to mind that I assign nullable values to non-nullable variables, and will happily crash on runtime if they are null.
Basically, my question does not only concern null-safety, but the type system in general, since from my understanding it shouldn't be possible to assign a dynamic value to an int variable, but obviously this works with Map. Any explanation is greatly appreciated!
A dynamic variable can contain any data type, "dynamic" keyword is used when you don't know the specific data type that might be returned.
and what you're actually doing here:
i = map["key"];
is assigning a "dynamic" variable to an "int" variable and since dynamic in this case the value in the key/value pair of your Map is an integer which matches the data type "int" of variable "i" it won't crash because of type inference done at runtime and not compile time. if the "dynamic" variable was a String it would crash at runtime because of a type mismatch. Hope this is explanatory.

MapEntry - specify type annotation

I'm using a linter package to practice strict coding, however, I can't figure out how to solve this warning. The code is working but I just want to understand this warning and how to solve this. I'm new to flutter by the way. Hope someone can answer my question. TIA!
Actual Code
PS: sorry, unable to display image because of restriction
The lint you're getting is from pedantic's always_specify_types property. It means that you have to specify the type of the variable. In your case, you need to specify the type of your MapEntry. By default, MapEntry's key and value's type will be dynamic. You'd have to specify it as MapEntry<int,Tab>.
In simple terms, instead of:
var number = 12; // dynamic means that the type can be anything (Eg: String, int, etc.)
do:
int number = 12;

Is Map really a type in Flutter? Unfinished Map declarations break other Map declarations

I understand that in Flutter, I can declare a Map using a map constructor':
eg.
var map_name = new Map();
and then use it:
map_name[key] = value
or using Map literals:
var details = {'Username':'Fede','Password':'pass#123'};
However, I have seen perfectly valid code in Dart such as:
Map<String, int> phoneBook ={
'Fede': 12345678,
'Juli': 5467899,
'Pablo' : 56788654,
};
This kind of declaration can be accepted by the compiler in normal cases:
code accepted by compiler
but (after hours of debugging) I have seen that not finishing the declaration of one map in this way by not assigning a name for it, the compiler (in Android Studio) will yield an error telling that "Map isn't a type" in other valid declarations, even in other files calling that file where the Map declaration was not finished! That is, the error is quite spread.
crashed code
map isnt't a type
In other words, the unfinished declaration of one Map breaks the possibility to declare any other Maps in this way, anywhere linked to that unfinished sentence giving a 'map isn't a type' error. The problem dissappears when you just put a name to the unfinished Map declaration and Maps are treated as types again. So my question is: Are Maps a type for Flutter, or is it just a minor bug?
In your "crashed code" picture, i seen that you have not provide name for second map.Your error is kind of syntax, please provide correct declaration to make its work. I tried your code in my IDE and its working perfectly.The syntax you tried is totally valid and it should working.

Instantiation in Minizinc

I am reading through "A Minizinc Tutorial" by Kim Marriott and it says that
the combination of variable instantiation and type is called type-inst. As you start to use Minizinc, you will undoubtedly see examples of type-inst errors.
What exactly are type-inst errors?
I believe the terminology is not often used in the MiniZinc literature these days, but for every value in MiniZinc the compiler keeps track of two things: it's type (int, bool, float, etc.) and if it is a decision variable (not known at solve time) or a problem parameter (must be known when rewriting the model for the solver). Together these two things are called the Type Instantiation or type-inst.
A type-inst error is an error given by the type checker of the compiler. These error can occur in many places, such as when in a declaration the declared type instantiation doesn't match it's right hand side, or when two side of an if-then-else have a different type-instantiation, or when the arguments of a call do not match the declared type-instantiation of the function-declaration.
The mismatch that causes these errors can come from either side of the type-inst: either the types are incompatible (e.g. used float instead of bool), or you used a decision variable where only a problem parameter was allowed. These issues are usually caused by mistakes in the model and are usually resolved easily by changing the value used or using different language constructs.
Note that MiniZinc does allow sub-typing: You are allowed to use bool instead of int and it is converted to a 0/1 value. Similarly you can use a integer value instead of a float, and you can use a parameter in place of a variable.
The newest version of the MiniZinc Tutorial can be found with its documentation: https://www.minizinc.org/doc-latest/en/part_2_tutorial.html

dart + flutter: why doesn't the compiler recognize type mismatches

I'm encapsulating the data of my flutter app inside a class called AppData. It looks a bit like this:
class AppData with ChangeNotifier{
List<word.Word> _words;
UnmodifiableListView<word.Word> words;
AppData(){
// at some point this will be loaded from disk
// for now I'm just using a hardcoded list of words
_words = word.words;
// the following code would work:
// words = UnmodifiableListView(_words);
// this doesn't, but it's also not a compiler error !?
words = _words;
}
// ...
}
The AppData class keeps track of a list of words. For the consumers of AppData, this is visible as an UnModifiableListView.
My code had a very obvious bug: I assigned _words to words without encapsulating the List in the UnModifiableListView properly.
Why is this not found by the compiler?
The types should be an obvious mismatch. From the dart docs (emphasis mine):
Dart’s type system, like the type systems in Java and C#, is sound. It
enforces that soundness using a combination of static checking
(compile-time errors) and runtime checks. For example, assigning a
String to int is a compile-time error. Casting an Object to a string
using as String fails with a runtime error if the object isn’t a
string.
Update, to respond to Rémi's answer:
The error message is:
The following assertion was thrown building MultiProvider:
type 'List<Word>' is not a subtype of type 'UnmodifiableListView<Word>'
This seems like an issue of Covariance vs Contravariance.
If I know that my reference to List actually contains an UnmodifiableListView, then I could do the cast myself.
Why would the compiler add an implicit cast for me?
It seems to me as if this bypasses a lot of the type soundness mentioned in the docs above. Especially when I change my type hierarchies and do extensive refactoring, I rely on the compiler to tell me: You've got the types mixed up.
It's very possible that their inheritance tree still reaches a common ancestor at some point. But they are definitely not the same.
At least to me, it is even more surprising, since this is not the way other "typical" OOP languages work (Java, C#, ...).
So I still wonder: Why doesn't the compiler find this, what is the reason behind this design?
What happens, in reality, is an implicit cast.
Since UnmodifiableListView is a List, assigning List to UnmodifiableListView does an automatic cast.
You can disable that on the analysis_options.yaml like so:
analyzer:
strong-mode:
implicit-casts: false