-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Issue with generic function definition ((String?) => Future<String>' is not a subtype of type '((dynamic) => FutureOr<String?>)?') #52168
Comments
You should probably never use an expression of the form (value) async {
return 'test';
} as FunctionProto<String> The problem is that there is no syntax that allows you to declare the return type of a function literal, and the placement of the function literal as the left operand of In order to ensure that the function literal gets a useful context type you'd need to put the function literal directly in the location where that type is expected: TestClass testClass = TestClass<String>(function: (value) async => 'test'); It is still possible that type inference cannot select the desired types (of the parameter I would assume that the bad typing of function literals which is caused by using
Please mark that location in the code using a comment or something like that. Most people won't count to 68 in this situation. ;-)
That's not particularly easy to understand. Perhaps you could create a minimal example? One thing that I noticed is that you could use a conditional invocation: if (testClass.function != null) {
print('Call function');
String? err = await testClass.function!('handover some value');
print('err: $err');
}
// OK, assuming you don't really care about the `print` statements, this could be:
String? err = await testClass.function?.call('handover some value'); The point is that you don't have to use the unsafe I'm not sure I get the part about 'not feasible' (which might be the most important part), but I hope this helps anyway. ;-) |
Thank you very much for your feedback! test('Main issue', () async {
TestClass testClass = TestClass<String>(
function: (value) async {
return 'test';
},
);
if (testClass.function != null) {
print('Call function');
String? err = await testClass.function!('handover some value');
print('err: $err');
}
}); and there at the snippet
Pls find attached an example of how we would use the generic type approach: import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
typedef FunctionProto<T> = FutureOr<String?> Function(T? value);
@immutable
class TestClass<T> {
final T? value;
final FunctionProto<T>? function;
const TestClass({
this.value,
this.function,
});
}
@immutable
class ForExampleSomeApiModel {
final String? id;
const ForExampleSomeApiModel({
this.id,
});
}
List<TestClass> testClassList = [
TestClass<String>(
value: 'test',
function: (value) async {
if (value == 'test') {
return 'return some good value';
}
return 'return some other value';
},
),
TestClass<String>(
value: 'test2',
function: (value) async {
if (value == 'test') {
return 'return some good value';
}
return 'return some other value';
},
),
TestClass<ForExampleSomeApiModel>(
value: const ForExampleSomeApiModel(id: 'test'),
function: (value) async {
if (value?.id == 'test') {
return 'return some good value';
}
return 'return some other value';
},
),
TestClass<ForExampleSomeApiModel>(
value: const ForExampleSomeApiModel(id: 'test2'),
function: (value) async {
if (value?.id == 'test') {
return 'return some good value';
}
return 'return some other value';
},
),
];
void main() {
group('Generic function subtype problem', () {
test('Main issue', () async {
for (TestClass testClass in testClassList) {
if (testClass.function != null) {
print('Call function');
String? err = await testClass.function?.call(testClass.value);
print('err: $err');
}
}
});
});
} The code would run with the workarounds ( |
Taking another look, I noticed that there was a raw type, analyzer:
language:
strict-raw-types: true
strict-inference: true
strict-casts: true
linter:
rules:
- avoid_dynamic_calls The 'strict-raw-types' setting will give you a diagnostic message when a generic type has been specified with no actual type arguments (that's a raw type), and the meaning of that type includes the type In the example, The general rules about how a raw type Here is a small reproduction of the failure which is caused by the combination of the raw type and the "contravariant member": import 'dart:async';
class TestClass<T> {
final FutureOr<String?> Function(T? value) function;
const TestClass(this.function);
}
void main() {
TestClass testClass = TestClass<String>((value) async => 'test');
testClass.function; // Throws! We don't even have to call it, just look at it.
} If you use The instance variable However, when a type variable like In those cases there is a 'caller-side check' on the operation where the instance variable is evaluated, and that caller-side check can fail at run time. This is the reason why The simple fix is to stop using contravariant members. For instance, the type of The genuine fix is to have statically checked declaration-site variance, cf. dart-lang/language#524. You could vote for that if you wish to support that feature. There is an example here illustrating how you could make a class like import 'dart:async';
typedef _Inv<X> = X Function(X);
typedef TestClass<X> = _TestClass<X, _Inv<X>>;
class _TestClass<X, Invariance> {
final FutureOr<String?> Function(X? value) function;
const _TestClass(this.function);
}
void main() {
TestClass testClass = TestClass<String>((value) async => 'test'); // Compile-time error.
testClass.function; // Won't throw when there is no covariance.
} With this modified version of If you change |
I think we can close this issue: The described behaviors are well-known, and 'strict-raw-types' deals with the raw type issue. Moreover, dart-lang/language#524 proposes a mechanism that would make "contravariant members" safe. |
Hi,
I run into a runtime exception with Dart 2.19.6 (used with Flutter 3.7.12) when running the following code as a test:
The mentioned code will throw a runtime exception at line 68 at the snippet testClass.validator with the message type '(String?) => Future' is not a subtype of type '((dynamic) => FutureOr<String?>)?'. In my production code, I have a List where the specific type of each entry could vary, that's why the first workaround in my sample code is not feasible. The other two workarounds are helping for now but on the one side unnecessary code and additionally are marked by the linter as unnecessary.
The Flutter team forwarded my issue to the Dart Repository, as it seems to be a Dart-related issue. The original issue is here issue 125387.
Thank you very much!
The text was updated successfully, but these errors were encountered: