How to debug the Null Safety default value type error on double question mark '??' - flutter

I recently run into some strange behavior when using double question mark.
Here is the example code:
void main() {
String strA;
String strB;
print('start');
strB = strA ?? 5; // wrong use of default value type. It should be a String here.
print('end');
}
The program never run to the end and even not throwing out any error message.
Is the behavior expected?

How you're running this code?
When I run it, I get this output:
▶ dart test.dart
start
Unhandled exception:
type 'int' is not a subtype of type 'String'
#0 main (file:///Users/renato/programming/projects/test.dart:6:3)
As I expected.
What Dart version are you using (run dart --version)? Make sure you have an up-to-date version like 2.10.
If you still have issues, you might have a local analysis_options.yaml file which overrides the Dart compiler checks to be more lenient, in which case it might not check the types (I am not sure which option would enable this, but I suppose it's possible).
Check how analysis options work here.
I suggest you always enable "strong mode" by adding this to your analysis options file:
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
By the way, if you want to try the new Dart NNBD (Not-Null-By-Default) experimental feature, you need to use the Dart dev channel releases and run your program with dart --enable-experiment=non-nullable file.dart.
To learn how to enable NNBD, check the null-safety tech preview 2 blog post about it.

Related

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

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.

Flutter compile error: non-null value must be returned since the return type 'String' doesn't allow null - displayString

My tests are failing to compile and run in a Dart only project that is referenced by my Flutter project. I'm receiving the following error message
Failed to precompile test:test:
../../../../../../../../../.pub-cache/hosted/pub.dartlang.org/analyzer-1.0.0/lib/src/error/best_practices_verifier.dart:1952:14: Error: A non-null value must be returned since the return type 'String' doesn't allow null.
String get displayString {
Any ideas please?
Flutter 2.2.1 (current stable channel)
Tools • Dart 2.13.1
(I've asked the question in Flutter's github here also)
https://github.com/flutter/flutter/issues/83683
There was a component using analyzer version 1.0.0
Upgrading this component to use analyzer version 1.7 or above seemed to fix the problem for me.
go to this file: flutter/.pub-cache/hosted/pub.dartlang.org/analyzer-1.5.0/lib/src/error/best_practices_verifier.dart:1978:14.
and add below:-
default:
return '';
Please check the function displayString to make sure that it returns non-null string variable. There are some use cases that I guess you might face with
Use "required" if you get the variable from parameters (ex: String displayString(required String var)).
If the variable is optional parameter, then you need to check if it is null or not. Then, you could "return var!;" to let the function knows that you already confirmed the variable content.
If you want to return nullable String, then you should change the function to "String? displayString".

How do I use Type Aliases / Typedefs (also non-function) in Dart?

I have known function typedefs in Dart for a long time. They are also explained in answers to this question.
Now, I have heard about non-function type aliases (or non-function typedefs) coming to Dart.
I am wondering two things:
What exactly are (non-function) typedefs in Dart?
How do I use them (in my Flutter project)?
Generalized type aliases / typedefs in Dart
You can view the feature specification for Generalized type alisases for the full design document.
I want to preface this by pointing out that Dart used to only support typedefs for functions. The new generalized feature supports typedefs for any type.
typedef JsonMap = Map<String, dynamic>;
JsonMap parseJsonMap(String input) => json.decode(input) as JsonMap;
This is especially useful when you have multiple generic types (type parameters) that cause long type names that are tedious to type, for example Map<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>, SnackBar>. This can now be simplified using a type alias:
typedef ScaffoldSnackBarMap = Map<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>, SnackBar>;
Syntax
If not clear from the above examples, this is the syntax for type alisases / typedefs:
'typedef' identifier typeParameters? '=' type ';'
This means that you always need to start with the typedef keyword followed by your desired identifier, e.g. FooTypeDef. After that, you can add type parameters, e.g. Foo<K, V>. The last step is adding the = symbol followed by the actual type you want to create an alias for. This can be any type, i.e. a class, primitive type, function type, or w/e. Do not forget the ; at the end ;)
// Type parameters / generic types in typedef.
typedef Foo<K, V> = Map<K, V>;
// Type alias for regular types.
typedef Bar = Widget;
// As well as primitive types.
typedef Baz = int;
// Function types are also supported.
typedef FooFunction<T, R> = R Function(T param);
Deprecating names
Additionally, you can use typedefs for any class names. Say you want to rename your class from Provider to Pod because you think the former is too verbose. If you are maintaining a package, this would be a breaking change. With the new generalized type aliases, you can simply rename your class and create a type alias that you deprecate:
class NewClassName<T> {}
#Deprecated("Use NewClassName instead")
typedef OldClassName<T> = NewClassName<T>;
Note that this example and the one above are taken from the proposed CHANGELOG entry for the feature.
How to use them
The feature will be shipped by default with Dart 2.13 but is currently still experimental. I will cover how to use it in both ways; the experimental method can be removed later on.
Dart 2.13
As I mentioned previously, the feature will be enabled by default starting with Dart 2.13. If you currently have Dart 2.13 installed already (you can use dart --version to check it for example), you can use this method. Otherwise, you should refer to the Experimental support section below.
In your pubspec.yaml, you need to define the lower bound on your Dart SDK constraint to be greater than or equal to 2.13.0:
environment:
dart: '>=2.13.0 <3.0.0'
Experimental support
In your Flutter project (or any other Dart project), you currently need to enable them as an experiment. It means that they are hidden behind a feature flag.
Experimental Dart features can be configured using analysis_options.yaml. You can simply create an analysis_options.yaml file in the root of your project directory and add the following lines:
analyzer:
enable-experiment:
- nonfunction-type-aliases
Now, you need to also enable the experiment when you run (or build) your app:
flutter run --enable-experiment=nonfunction-type-aliases
To make sure that you can use this feature, use the master channel (flutter channel master when using Flutter).

Does Dart have an equivalent of TypeScript's --noImplicitAny, enforcing static typing unless explicitly disabled?

I'm getting to grips with Flutter and the Dart language, but keep occasionally making silly mistakes because the language lets me. In the docs on type inference, it says:
The analyzer can infer types for fields, methods, local variables, and most generic type arguments. When the analyzer doesn’t have enough information to infer a specific type, it uses the dynamic type.
This seems to happen a fair amount, and there's no way to see what the analyzer was and wasn't able to infer. For example:
void doThings(List<int> integers) {
print(integers);
}
main() {
doThings([1, 2, 3].map((i) => i + 1));
}
There's no warning in the IDE (IntelliJ) or during compilation, but at runtime we get an ugly TypeError: Instance of 'MappedListIterable<int, int>': type 'MappedListIterable<int, int>' is not a subtype of type 'List<int>' because I needed a .toList() call.
To catch mistakes like that at compile time instead of runtime, TypeScript has an option --noImplicitAny which makes any un-inferrable type into an error, rather than silently substituting a variant type (dynamic in Dart, any in TypeScript).
Is there a way to make the Dart compiler this helpful too?
You can customise the static analyzer. You need to add analysis_options.yaml to your root.
To forbid implicit types use the following:
analyzer:
strong-mode:
implicit-casts: false
implicit-dynamic: false
More information: https://dart.dev/guides/language/analysis-options

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