Reproduced in
Here's a gist you can run to reproduce the error: https://dartpad.dartlang.org/396f0f9b95e4bee49fb13fab97fbe7d3
The default of list in the following example is inferred as a plain List, rather than List<String>.
void Function([List<String>]) foo() {
return ([list = const []]) {
/* ... */
}
}
So long as you pass a list to the closure returned by foo(), everything is fine. You can even pass an untyped list, foo([]) and rely on the inference of the return type to fill in the generic type argument as String.
However, if you omit the argument, the default value is incorrectly instantiated without the correct generic type argument of String and yields a TypeError.
TypeError: Instance of 'JSArray': type 'JSArray' is not a subtype of type 'List
'
Here are two workarounds for this bug.
return ([list = const <String>[]]) {
return ([List<String> list = const []]) {
Considering that inference works for an untyped list passed into the closure, I'd suspect it's possible for the untyped default argument value to be correctly inferred.
I'm not sure this is a failure/bug versus WAI (though I think the inconsistency hurts in any case).
/cc @jmesserly @leafpetersen thoughts?
This is definitely not WAI. The default value should be inferred using the parameter type as the inference context, even if the parameter type is inferred via downwards inference.
This seems to work correctly on DDC, so I think this is just a CFE bug.
Thanks @leafpetersen!
I'm guessing this is working as intended
([List<dynamic> l]) {} is void Function([List<String]); // true
The inference won't make the argument more specific because it doesn't need to in order to satisfy the return type.
<dynamic>[] is List<String>; // false
Here the inference makes the List type more specific because it needs to in order to satisfy the return type.
I originally expected downwards inference to work here as @leafpetersen suggests, but @natebosch's point seems valid too. If the argument does satisfy the return type, why is it then a runtime error?
It doesn't need to in order to satisfy the return type, but in general it may need to in order to statically type uses of the argument in the body. We don't consider parameter uses when doing inference, so we just go ahead and infer the more specific type.
void Function([List<String>]) foo() {
return ([list = const []]) {
if (list.length > 0) {
print(list[0] + "hello"); // If I don't infer List<String> for the argument, this has to be a dynamic call, and I can't give you an error if you do list[0] + 3
}
}
in analyzer this happens in two stages:
(both of those stages are actually the same pass, BTW, resolver in analyzer uses static_type_analyzer and element_resolver as helper classes, conceptually they're all the same pass.)
@jmesserly so is step 2 not getting the inferred type, and thus using the declared?
@jmesserly so is step 2 not getting the inferred type, and thus using the declared?
oh no sorry, I was describing the behavior for the Analyzer (used by DDC and IDEs) ... this appears to be working correctly in Analyzer. I wrote that in case it would help the CFE folks track down the bug.
Most helpful comment
It doesn't need to in order to satisfy the return type, but in general it may need to in order to statically type uses of the argument in the body. We don't consider parameter uses when doing inference, so we just go ahead and infer the more specific type.