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
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?
CodePudding user response:
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.
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.