Generalize record literal inference to handle union types - #4714
Conversation
lrhn
left a comment
There was a problem hiding this comment.
I assume all non-record types are handled elsewhere.
(And patterns are not an issue because this is only about expression contexts.)
|
@eernst and I discussed this today, and I'm concerned because I don't think this is particularly a record specific thing. Poking at the implementations, we're currently fairly inconsistent with our treatment of context types here. I think this should be pulled out to a general issue, and we should figure out what is currently being done (and if possible why, I think we discussed something like this in at least some places). @stereotype441 may have more context. Here are some tests showing inconsistent treatments - we seem to strip import 'dart:async';
class A {
A.mk();
}
void t<T>(FutureOr<T> x) {}
T foo<T>() {throw "nope";}
void test() {
t<A>(.mk());
t<(A,)>((.mk(),)); // error
t<List<A>>([.mk()]);
t<Set<A>>({.mk()});
t<Map<A, A>>({.mk() : .mk()});
t<A Function()>(() => .mk()); // error
t<int>(foo()..isEven); // error
{
FutureOr<(int, int)> x = (3,4);
switch(x) {
case (var a, _): a.isEven; // error
}
}
{
FutureOr<List<int>> x = [3];
switch(x) {
case [var a]: a.isEven; // error
}
}
} |
|
I do think we have an explicit rule for many of these cases: With
With
In other words, this is explicitly specified to not erase the outermost union types, so it fails. For
So we're again specifying explicitly that no union types are erased, and we could again add a rule that erases the outermost union types from the context type before we start looking into the function type signature. For The remaining cases are collection literals, and they are able to erase the union types in order to obtain a relevant context type schema. However, this seems to be achieved as follows: Subtype constraint generation includes the following rule:
The collection literals are recognized as having types The same case does not succeed for a record literal, even in the case where the context type is In the implementation, With This subtype check is what triggers 'type_analyzer_operations.dart', which strips off Record literals are not generic classes with type parameters to infer. So the analyzer never initializes a Instead of solving constraints, when analyzing a record literal, the analyzer simply inspects the downward context type to see if it can extract individual field context types to pass down to each expression in the record. In 'record_literal_resolver.dart', the analyzer directly checks if RecordTypeImpl? _matchContextType(
RecordLiteralImpl node,
DartType contextType,
) {
if (contextType is! RecordTypeImpl) return null;
...The front end (CFE) does the exact same thing in 'inference_visitor.dart': It looks like record literals bypass the generic solver where The fix might be in the implementation: On the other hand, if " |
0ca8f10 to
aff121a
Compare
|
@leafpetersen, what do you think about the approach taken in this PR? It introduces a specific rule about the context type provided to the components of a record literal. We may need to do a similar thing for function literals, but that can be done separately. |
This PR adds the notion of 'union-free type schema' derived from a given type schema (as a slight generalization of the 'union-free type' in the language specification) to the feature specification of records, and uses it to provide a better context type to each component of a record literal during inference.
This is, in principle, a breaking change. It should be language versioned. The first action will be to use the specification update to guide an implementation and assess the breakage. The PR may then be landed if the breakage is negligible; in case of significant breakage, the way ahead will need to be discussed further.