Why does the following code even compile (Dart VM version: 2.0.0-dev.62.0):
int f<T>(T q) {
return q.hashCode;
}
void main() {
print(f<int>(23));
print(f<int>("wow"));
}
I thought f<A>(..) selects the A version of f?
The Dart VM does not use Dart 2 semantics by default when invoked directly yet (it does via Flutter, and is coming soon for Dart v2 dev), so you need to run with --preview-dart-2. If you do, you'll get an error:
Dannys-MacBook:lib danny$ dart --preview-dart-2 test.dart
test.dart:7:22: Error: A value of type 'dart.core::String' can't be assigned to a variable of type 'dart.core::int'.
Try changing the type of the left hand side, or casting the right hand side to 'dart.core::int'.
print(f<int>("wow"));
Related
I switched to sound null safety and started getting runtime error in a simple assignment, that should never happen with sound null safety:
final widgetOnPressed = widget.onPressed;
Error:
type '(LogData) => void' is not a subtype of type '((LogData?) => void)?'
I can repro it for Flutter versions 2.12.0-4.1.pre and 2.13.0-0.0.pre.505.
PR: https://github.com/flutter/devtools/pull/3971
Failing line: https://github.com/flutter/devtools/blob/9fc560ff2e6749459e2ca6a1dc00bf6fb16ed93b/packages/devtools_app/lib/src/shared/table.dart#L1184
To repro, start DevTools at this PR for macos, connect to an app and click the tab 'Logging'. DevTools will show red screen and error in console.
Is it dart bug or the app bug? If it is the app bug, how can I debug it?
It's a bug in your code.
You didn't say which kind of error you got - a compile-time error or a runtime error. I'm guessing runtime error. (Well, you did say to launch it in the debugger, so that is a good hint too.)
The line final widgetOnPressed = widget.onPressed; looks like it can't possibly fail. After all, the type of the local variable is inferred from the expression assigned to it, and the runtime value of that expression will surely be a subtype of the static type because the type system is sound!
Isn't it? ISN'T IT?
It's not, sorry. Dart 2's type system is mostly sound, even more so with null safety, but class generics is covariant, which can still be unsound. It's fairly hard to hit one of the cases where that unsoundness shows its ugly head, but returning a function where the argument type is the class's type variable is one.
Your state class extends State<TableRow<T?>>, so the widget getter returns a TableRow<T?>. The onPressed of that type has type ItemCallback<T?>?, aka, void Function(T?)?.
You create a _TableRowState<LogData>, with its widget which has static type TableRow<LogData?>, but you somehow manage to pass it a TableRow<LogData> instead. That's fine. Class generics are covariant, so all is apparently fine at compile-time.
Then you do final widgetOnPressed = widget.onPressed;.
The static type of widgetOnPressed is void Function(LogData?) here.
The actual runtime type of onPressed is void Function(LogData) because it's from a TableRow<LogData>.
A void Function(LogData) is-not-a void Function(LogData?) because the former cannot be used in all places where the latter can (in particular, it can't be used in a place where it's called with null).
This assignment is potentially unsound, and actually unsound in this case. The compiler knows this and inserts an extra check to ensure that you don't assign a value to the variable which isn't actually valid. That check triggers and throws the error you see.
How do you avoid that?
Don't create a TableRow<LogData> where a TableRow<LogData?> is required.
Or type the variable as:
final ItemCallback<T>? widgetOnPressed = widget.onPressed;
(no ? on the T).
Or rewrite everything to avoid returning a function with a covariant type parameter (from the class) occurring contra-variantly (as an argument type).
Which solution fits you depends on what you want to be able to do.
I have the following Dart code:
void main() {
provide(1, 'A');
}
void provide<A>(A one, A two) {
print('one $one two $two');
}
In java the call to provide will give a compile time error as the two parameters should be both of type A. With java as soon as you pass an argument to a typed parameter that defines the type.
My understanding is that with Dart if I don't follow 'provide' with a type then the type is dynamic.
To get the above code to work correctly in dart I have to write:
void main() {
provide<int>(1, 'A');
}
void provide<A>(A one, A two) {
print('one $one two $two');
}
This will now give a compile type error as 'A' is not an int.
This however is error prone as the user is likely to forget to add the type when callng provide.
Is there anyway I can make calls to the provide method type safe without having to write provide<int>(....
I've trivialised the example, the aim is to make a call to a method such as :
void provide(Token<T> token, T value);
If T is not the correct type then the user should get a compile error.
Consider following dart code:
main.dart
import 'module.dart';
main() {
foo();
}
module.dart
foo() {
print('foo');
}
bar() {
print('bar');
}
I expect bar to be eliminated from the final build by dart compiler with tree-shaking since it's statically analyzable that the function was not used in main.
I tried the method answered in related S/O question.
Using devtools by running dart run --observe main.dart, I still see bar was included.
How can I know whether bar was eliminated from the final build or not?
Alternatively, I want someone with an understanding of dart compiler to convince me whether the function was eliminated or not, so I don't need to worry about checking it myself.
I've enabled the dart 2.8 nullsaftey experiment.
I've converted my app to nullsaftey but its using an old pre-nullsafety package.
The problem is that the old package has a method which can return null:
/// Returns the environment variable with [name] or null if it doesn't
/// exist
String env(String name);
Which is used as follows:
var home = env('HOME');
If the HOME environment variable is missing, env returns null.
The problem is that env is declared as returning a String.
So when I write
var home = env('HOME');
home ??= '/home';
I get an error:
The operand can't be null, so the condition is always false.
Try removing the condition, an enclosing condition, or the whole conditional statement.
Given that all the nullsaftey release announcements say you can use nullsaftey with older packages, I'm guessing there is some way to declare an imported packages as non-nullsafe.
Problem is that I can't find any documentation on how to do this.
null safety has not been released yet! that is why you need to provide the experiment flag.
Language versioning
By default, whether or not null safety is supported in a library is determined by its language version. Any language version 2.8 or lower counts as opted out of null safety, and 2.9 or higher (subject to change) is opted in. The language version itself can come from one of two places:
The minimum bound of the package's declared SDK constraint. The following package will have a language version of 2.8.
name: foo
env:
sdk:
">=2.8.0 <3.0.0"
A language override comment at the top level of the file, before any other declarations. The following library will have a language version of 2.8.
// #dart=2.8
class Foo {}
The language override comment will take precedence over the SDK constraint, but only within the single library where it is declared.
Interaction between null safe and non-null safe code
The problem you are having is reproducible without different packages or incorrect language versions though, and has to do with the interaction between null-safe and non-null-safe code. Consider the following example:
// #dart=2.8
String foo() {
return null;
}
// #dart=2.9
import 'a.dart';
void main() {
var value = foo();
value ??= 'asd';
}
The return type of foo doesn't become String?, instead it gets tagged as String* - this is known as a legacy type. A legacy type is treated as a non-null type in opted in libraries. The goal of legacy types is to make it easier to migrate to null-safety through an in-order migration
Consider the example below:
// #dart=2.9
void foo(String value) {
// do something with non-null String.
}
// #dart=2.8
import 'a.dart';
void main() {
foo(getStringFromAPI());
}
While foo requires a non-null string, it isn't possible for the entry-point to actually pass it one - since it has not opted in yet. Without the treatment of legacy types as non-nullable types, it would not be possible to gradually migrate - because all libraries would need to be updated at once, or only updated to accept nullable types.
Out of order migration
By calling code that has not been migrated to null-safety from a null safe library, you are increasing the risk that you will be broken when that dependency eventually migrates. In you example, if home was treated as non-nullable then updating to a version of the dependency with an updated return value of String? would cause a compilation error.
For your specific case, I would recommend specifically annotating the type of home as String?. This is a perfectly valid type annotation, since in general T and T* are always assignable to T?. It also more correct, since you know the API can return null.
String? home = env('HOME');
home ??= '/home';
EDIT June 2021:
Null safety has released, yay! The first version of Dart with null safety enabled by default ended up being 2.12 and not 2.9 as documented in the question above.
In dart, when developing a web application, if I invoke a method with a wrong number of arguments, the editor shows a warning message, the javascript compilation however runs successfully, and an error is only raised runtime. This is also the case for example if I refer and unexistent variable, or I pass a method argument of the wrong type.
I ask, if the editor already know that things won't work, why is the compilation successful? Why do we have types if they are not checked at compile time? I guess this behaviour has a reason, but I couldn't find it explained anywhere.
In Dart, many programming errors are warnings.
This is for two reasons.
The primary reason is that it allows you to run your program while you are developing it. If some of your code isn't complete yet, or it's only half refactored and still uses the old variable names, you can still test the other half. If you weren't allowed to run the program before it was perfect, that would not be possible.
The other reason is that warnings represent only static type checking, which doesn't know everything about your program, It might be that your program will work, it's just impossible for the analyser to determine.
Example:
class C {
int foo(int x) => x;
}
class D implements C {
num foo(num x, [num defaultValue]) => x == null ? defaultValue : x;
}
void bar(C c) => print(c.foo(4.1, 42)); // Static warning: wrong argument count, bad type.
main() { bar(new D()); } // Program runs fine.
If your program works, it shouldn't be stopped by a pedantic analyser that only knows half the truth. You should still look at the warnings, and consider whether there is something to worry about, but it is perfectly fine to decide that you actually know better than the compiler.
There is no compilation stage. What you see is warning based on type. For example:
This code will have warning:
void main() {
var foo = "";
foo.baz();
}
but this one won't:
void main() {
var foo;
foo.baz();
}
because code analyzer cant deduct the type of foo