-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Crash type casting issues in Dart related to generic values and null checks, type '(String?) => void' is not a subtype of type '((dynamic) => void)?' #60288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
My understanding is that If you intend to allow the function to be nullable, it should be of type |
@RohitSaily |
For anyone working on this, I was able to create a complete and small Flutter project for this:
name: 'a'
publish_to: 'none'
version: '1.0.0+1'
environment:
sdk: ^3.8.0-149.0.dev
dependencies:
flutter:
sdk: 'flutter'
import 'package:flutter/material.dart';
void main()
{ final Widget tile=RadioListTile
( value: 'value',
activeColor: Colors.red,
toggleable: true,
onChanged:(value)=>debugPrint('Test'),
groupValue: null
);
if (tile is RadioListTile)
{ print(tile.onChanged);//Access of "onChanged" causes the crash
}
} @MikePendo I am able to reproduce the crash on macOS. I am still working to figure it out now |
The root of the problem is that the input parameter cannot be generalized. If a function operates on a specialized type, it may access specialized fields. Therefore the input type cannot be erased. I believe for The best solution I could come up with so far is to perform the check with the type i.e. void main()
{ final Widget tile=RadioListTile
( value: 'value',
activeColor: Colors.red,
toggleable: true,
onChanged:(value)=>debugPrint('Test'),
groupValue: null
);
f<String>();
}
void f<T>(final Widget widget)
{ if (tile is RadioListTile<T>)
{ print(tile.onChanged);//Should work as long as T is correct.
}
} This is not ideal, as |
Currently we do something similar: void getRadioListTileInfo(RadioListTile widget) {
_isContainer = true;
dynamic convertedWidget;
switch(widget.runtimeType) {
case const (RadioListTile<num>):
convertedWidget = widget as RadioListTile<num>;
_onChanged = convertedWidget.onChanged?.toString();
_isClickable = (convertedWidget as RadioListTile<num>).isCallbackClickExists();
case const (RadioListTile<int>):
case const (RadioListTile<double>):
etc .. We dont have the |
I was able to get it working by casting the widget to import 'package:flutter/material.dart';
void main()
{ final Widget tile=RadioListTile
( value: 'value',
activeColor: Colors.red,
toggleable: true,
onChanged:(value)=>debugPrint('Test'),
groupValue: null
);
if (tile is RadioListTile)
{ print((tile as dynamic).onChanged==null);//No crash :)
}
}
|
@RohitSaily void testWidgetOnNUllDynamic(dynamic clbk) {
if (clbk != null) {
print('still crashing');
}
}
void testWidgetOnNUllCallback(Widget widget) {
if (widget is RadioListTile) {
if ((widget as dynamic).onChanged != null) {
//type '(String?) => void' is not a subtype of type '((dynamic) => void)?'
print('wont crash any more');
}
//attempt to erase the type
testWidgetOnNUllDynamic((widget as dynamic).onChanged);
//attempt to erase the type will crash
testWidgetOnNUllDynamic(widget.onChanged);
}
} And although the @RohitSaily please feel free to close the issue, Thanks again |
@MikePendo happy to help! I am just another member of this community and do not have permissions to perform any management within this repository. I believe you will have to close this issue or an official maintainer will. Also I believe the type erasing method was not working because the |
There are few things which are important to understand here. First is that we want to be able to trust function types. If you have Second, function types are contravariant with respect to their parameter types. To put it in simple terms1: if you have a function that accepts an abstract class Animal {}
class Dog extends Animal {
}
class Cat extends Animal {
}
void handleADog(Dog d) { }
void handleAnAnimal(Animal a) {}
void processAnimals(void Function(Animal) f) {
f(Cat());
}
void processDogs(void Function(Dog) f) {
f(Dog());
}
processDogs(handleADog); // ok: handleADog can handle any Dog
processDogs(handleAnAnimal); // ok: handleAnAnimal can handle any animal, which means any Dog.
processAnimals(handleADog); // not ok: handleADog can handle any Dog, but not any Animal. The Finally, function type contravariance makes interaction between Dart's generics (which are unsoundly covariant) awkward: consider the following code: class A<T> {
void Function(T) f;
A(this.f);
}
final a = A<Dog>(handleADog);
final b = a as A<Animal>;
final f = b.f;
f(Cat()); Dart's subtyping rules say that However we hit a challenge with What Dart does here is the following: in places like this (where covariant type parameter occurs in a contravariant position) it inserts a check when you access field final f = b.f as void Function(Animal); // as ... inserted by compiler This will cause What can you do to avoid throw? Well, the usual trick is: if you know that class A<T> {
void Function(T)? f;
A(this.f);
bool get hasF => f != null;
}
final a = A<Dog>(handleADog);
final b = a as A<Animal>;
final hasF = b.f == null; // will throw because it is compiled as `(b.f as void Function(Animal)) == null`
final hasF2 = b.hasF; // ok. Footnotes
|
Agreeing with everything @mraleph said, I'd like to add a couple of extra remarks: If class A<X> {
void Function(X) f;
A(this.f);
void Function(X) method() => (X x) {};
}
void main() {
A<num> a = A<int>((int i) {});
a.f; // Throws!
a.method(); // Throws!
} You can enable the lint unsafe_variance in order to detect this kind of situation. @mraleph mentioned that one remedy is to ensure that the non-covariant member is never accessed on any other receiver than Another way to deal with this kind of run-time failure is to change the subtype relationship such that the unsafe situation can never occur. Statically checked variance is a proposal which will allow us to declare that a particular type variable is invariant rather than covariant (today, every type variable in Dart which is declared by a class is covariant, and soundness is maintained by means of run-time type checks, as with When a type parameter of a class If we assume that statically checked variance is available then we can do this: // Use statically checked variance to declare `X` as invariant: `inout X`.
class A<inout X> {
void Function(X) f;
A(this.f);
void Function(X) method() => (X x) {};
}
void dangerousSituationIsNowAnError() {
A<num> a = A<int>((int i) {}); // Compile-time error.
a.f; // Irrelevant, the compile-time error prevents this situation.
}
void correctedSituationWhichIsNotDangerous() {
A<int> a2 = A<int>((int i) {}); // This is OK!
a2.f; // Safe.
a2.method(); // Safe.
} By the way, votes (👍) for statically checked variance are highly appreciated! When features are prioritized, community support does matter. To put the original example into this context: The reason why there is a run-time type error is that This is also the reason why there is no run-time failure if we promote import 'package:flutter/material.dart';
void main() {
final tile = RadioListTile<String>(
value: 'value',
activeColor: Colors.red,
toggleable: true,
onChanged: (value) => debugPrint('Test'),
groupValue: null,
);
f<Object>(tile); // Oops, should have been `String`!
}
void f<T>(final Widget tile) {
if (tile is RadioListTile<T>) {
print(tile.onChanged); // Throws
}
} One of the techniques that you can use if you wish to access a non-covariant member is to make the access untyped. That is, cast the receiver to import 'package:flutter/material.dart';
// ignore_for_file: unused_local_variable
void main() {
final tile = RadioListTile<String>(
value: 'value',
activeColor: Colors.red,
toggleable: true,
onChanged: (value) => debugPrint('Test'),
groupValue: null,
);
f(tile);
}
void f(final Widget tile) {
if (tile is RadioListTile) {
print((tile as dynamic).onChanged); // Does not throw.
}
} This means that there will not be a covariance-related type check at run time, but in return for avoiding that you only have the type You may then need to perform other checks (e.g., This means that you will have to carry more of the type safety burden manually, but it may be convenient if you'd otherwise have to perform a major refactoring in order to enforce that |
HI
Dart Info:
Original question posted here StackOverflow
I am copying paste it here for better clarity:
I am trying to figure out if widget.onChanged of RadioListTile is not null, but it seems like dart is very strict when it comes to generics, any suggestion on how I can check it?
(I am a little new to Fluter/Dart )
I think that I understand the error I am not sure how to avoid it OR how to check of nallability without casting it to concrete type
Thanks
The text was updated successfully, but these errors were encountered: