-
Notifications
You must be signed in to change notification settings - Fork 205
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
Inference should flow information between arguments in a generic function call #731
Comments
@kevmoo This is the bug for integrating inference further to make things work for fold. Here's an example pulled over from dart-lang/sdk#25491 . Currently we give no error on this since we fail to infer a type for void main() {
List<int> o;
var x = o.fold(0, (x, y) => x + y);
List<String> y = x;
} |
@leafpetersen That doesn't quite hit the same thing I'm seeing void main() {
List<String> o;
var x = o.fold({}, (x, y) {
x[y] = 'thing';
return x;
}) as Map<String, String>;
_printInt(x);
}
_printInt(Map<String, String> things) => print(things); on Dart VM version: 1.16.0-dev.0.0 (Wed Mar 9 15:04:40 2016) on "macos_x64" |
I think it does, but maybe I misunderstand. The redundant cast hint is a different issue - this is just about why it's not enough to put a type on the map literal. In your example, the cast gives inference enough context to infer the type of fold (and then you get the redundant cast warning). We ought to be able to infer the type just from the type of the receiver and the first argument - so putting a type annotation on the map literal should be enough here. It's not though - hence this bug. Workarounds should be either typing both the map literal and the first argument to the lambda, or putting a type annotation |
@leafpetersen asked me to add this example: /// Creates a new map from [map] with new keys and values.
///
/// The return values of [keyFn] are used as the keys and the return values of
/// [valueFn] are used as the values for the new map.
Map/*<K2, V2>*/ mapMap/*<K1, V1, K2, V2>*/(Map/*<K1, V1>*/ map,
/*=K2*/ keyFn(/*=K1*/ key, /*=V1*/ value),
/*=V2*/ valueFn(/*=K1*/ key, /*=V1*/ value)) =>
new Map.fromIterable(map.keys,
key: (key) => keyFn(key as dynamic/*=K1*/, map[key]),
value: (key) => valueFn(key as dynamic/*=K1*/, map[key]));
/// Creates a new map from [map] with the same keys.
///
/// The return values of [fn] are used as the values for the new map.
Map/*<K, V2>*/ mapMapValues/*<K, V1, V2>*/(Map/*<K, V1>*/ map,
/*=V2*/ fn(/*=K*/ key, /*=V1*/ value)) =>
// [warning] Unsound implicit cast from dynamic to K.
mapMap(map, (key, _) => key, fn); in |
Actually, let me repurpose this bug with a new subject line :) We did integrate upwards and downwards inference. However, it is not able to flow information between arguments on an argument list, as required for several of the examples here such as EDIT: also to fix this, we'll need to spec this for a future version Dart and implement it in the new front end |
The |
|
This will also become a lot more relevant if we start seriously pushing for |
Yeah it's area-language. We can't do big changes to the type system in Analyzer, because it's all migrated to the new front end (thanks to @stereotype441), and we'll need to spec out (at least informally) what the new inference algorithm should be and how the rules work. The current technique is insufficiently powerful to handle |
Has anything changed here since the last update? |
This inference improvement (if it happens, which I hope it does) is post 2.0, so there won't be any changes until after that. edit: tagged this as "feature request" |
Yeah, let's keep this open for now until we re-organize language requests. This is something I'd still like to try to do in a future inference revision. |
Also reported here: https://youtrack.jetbrains.com/issue/WEB-34819 |
One more example where an issue in this area was reported, again using |
Just want to chime in here that I've ran into this unexpected behavior. I've been migrating a large code base to Dart 2 with |
Here is another example: dart-lang/sdk#37247. |
Dart type inference does not propagate type sideways in the argument lists[1], so we have to explicitly provide fold with a type argument, otherwise it is inferred as `Object?`, which does not work in this case. [1] dart-lang/language#731 Change-Id: I2ee0bda2cd6c37c64018efd48ea14c469f76ba3a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/152003 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
@leafpetersen Is this more of an issue now with nnbd? I'm not sure if this is applicable to this particular issue, but somebody posted the following example in Gitter, complaining of the lack of inference. I tried with nnbd and it results in a static error: Iterable<R> map<T, R>(
Iterable<T> iterable,
R Function(T) transform,
) sync* {
for (var item in iterable) {
yield transform(item);
}
}
void main() {
var list = [1, 2, 3];
var mappedList = map(list, (x) => x + 1); // `x` is inferred as `Object?`
print(mappedList);
}
|
Yes, or no, depending how you look at it. nnbd will make this more statically visible, which has both upsides and downsides. Your example used to work because void main() {
var list = [1, 2, 3];
var mappedList = map(list, (x) => x.foo); // `x` is inferred as `Object?`
print(mappedList);
} This variant of your But I do expect that nnbd will raise the profile of this issue, yes. |
I actually love that nnbd raises an error. But you will get more people complaining 😅. If something blows up at runtime, the programmer get blamed by their manager. When something doesnt compile that theoretically should, the language designers gets blamed. |
Sure, and I'd love inferring the anonymous function parameter even more, :) |
In order to support dart-lang/language#731 (improved inference for fold etc.) I'm going to need to add logic to _inferInvocation to postpone type analysis of arguments that are function expressions. To avoid having to code up this logic twice, it will be helpful to have both named and unnamed arguments handled by the same chunk of code. In particular, this change unifies the computation of inferredFormalType, the recursive call to inferExpression, the logic for hoisting, and the update of the local variables identicalInfo, formalTypes, and actualTypes. We pay a small price by having to have multiple `if (isExpression)` checks, but these should be very fast. Change-Id: I095a7eac84237eeb878cc3dd86e76a6a871f31d5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241041 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
In order to implement dart-lang/language#731 (improved inference for fold etc.), we'll need to gradually accumulate type constraints as we evaluate arguments, and periodically re-do inference; we can't just accumulate all the formal types and actual types and run them through an inference process at the end. This change moves us a step toward that eventuality, by accumulating type constraints after type inference visits each argument, rather than all at once after all arguments have been visited. The total amount of work done is unchanged. Change-Id: I91ed0529cd3142afe4153cac8c25bce3c20f137d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241800 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
These tests exercise the "deferred type inference of function literals" part of dart-lang/language#731 (improved inference for fold etc.) for super-constructor invocations and redirecting constructor invocations, both of which have their own code paths in the analyzer. Change-Id: I6877ac3c07a3cca31550ba74d941d250c8410cfd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241987 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
…led. In order to address dart-lang/language#731 (improved type inference for `fold` etc.) we're going to need to sometimes defer analysis of invocation arguments that are closures, so that closure parameters can have their types inferred based on other parameters. To avoid annoying the user with inconsistent behaviors, we defer analysis of closures in all circumstances, even if it's not necessary to do so for type inference purposes. This has a minor user-visible effect: if an invocation contains some closures and some non-closures, any demotions that happen due to write captures in the closures are postponed until the end of the invocation; this means that the write-captured variables remain promoted for other invocation arguments, even if those arguments appear after the closure. This is safe because there is no way for the closure to be called until after all of the other invocation arguments are evaluated. See the language tests in tests/language/inference_update_1/write_capture_deferral_enabled_test.dart for details. Note that this change only has an effect when the experimental feature `inference-update-1` is enabled. Change-Id: If7bb38e361755180c033ecb2108fc4fffa7570b1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241864 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
In the definition of `_computeExplicitlyTypedParameterSet`, I accidentally nested the declaration of `unnamedParameterIndex` inside the `for` loop, defeating the increment and causing all parameters to be considered to have index 0. I've included a test case that would have caught the mistake. Bug: dart-lang/language#731 Change-Id: I0cd0e1e5b481313150e495d370af2477253d6637 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/242741 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com>
When a function literal is wrapped in parentheses, it shouldn't affect how it interacts with type inference. This change ensures that parenthesized function literals are treated the same as unparenthesized ones by the logic that supports dart-lang/language#731 (improved inference for fold etc.) Change-Id: I672787a31addbfe3f3282b6e638e00b693eea46f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243000 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This change allows function literals in invocations to be inferred in dependency order. For example, given the following code: U f<T, U>(T Function() g, U Function(T) h) => h(g()); test() { var x = f(() => 0, (y) => [y]); } The function literal `() => 0` is inferred first, causing the type parameter `T` to be assigned the type `int`. Then, `(y) => [x]` is inferred with the benefit of this type assignment, so `y` gets the type `int`, and consequently, `U` gets assigned the type `List<int>`. This completes the support for dart-lang/language#731 (improved inference for fold etc.) Change-Id: I48c22693720a1cc8bbf435643e863834e07f02b1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243002 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
The function passed to Stream.handleError needs to accept either a single argument of type `Object`, or an `Object` and a `StackTrace`. Since this can't be expressed by the type system, it's checked at runtime. As a result, the `check` function (as previously written) would fail if ever instantiated with a type `T` that was narrower than `Object`. Prior to implementation of dart-lang/language#731 (improved inference for fold etc.), this worked fine, because type inference always supplied an argument type of `dynamic` or `Object` for the function literals passed to `check`. However, with the inference improvement enabled, it starts to infer other types, causing runtime failures. The correct fix is to give the `event` argument of `onError` an appropriate type.
The function passed to Stream.handleError needs to accept either a single argument of type `Object`, or an `Object` and a `StackTrace`. Since this can't be expressed by the type system, it's checked at runtime. As a result, the `check` function (as previously written) would fail if ever instantiated with a type `T` that was narrower than `Object`. Prior to implementation of dart-lang/language#731 (improved inference for fold etc.), this worked fine, because type inference always supplied an argument type of `dynamic` or `Object` for the function literals passed to `check`. However, with the inference improvement enabled, it starts to infer other types, causing runtime failures. The correct fix is to give the `event` argument of `onError` an appropriate type.
The front end desugars extension methods by inserting a synthetic `this` argument before all other arguments. But this synthetic argument isn't included in the `formalTypes` and `actualTypes` arrays. So when recording a value into `_DeferredParamInfo.evaluationOrderIndex` we may need to subtract 1 in order to ensure that later logic will find the correct argument. Fixes a corner case of dart-lang/language#731. Change-Id: Idbf136195e40555199f7c5b61a575a430f6ec6bd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243854 Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
… inference stages. As part of the implementation of dart-lang/language#731 (improved inference for fold etc.), I expanded the front end's type inference logic so that instead of just having a downward phase and an upward phase, it could have 3 or more phases. The function that previously did downward inference became repurposed to do "partial inference" (which could either be the first, downward stage, or a later, horizontal stage). However, I failed to generalize the logic that prevents types assigned by one inference stage from being refined by later stages--previously this logic was only needed for upward inference, but now it's needed for horizontal inference stages as well. (This logic is needed because of Dart's "runtime checked covariance" behavior--it means that we want to stick with the type from downward inference, even if a later horizontal inference stage is able to find a more precise type, because that more precise type may lead to runtime failures). As part of this change I've re-architected the inference methods so that they are responsible for creating and returning the list of inferred types. This makes the inference logic more similar between the front end and analyzer, and is easier to read IMHO. The total number of list allocations is the same as before. Change-Id: I19bfcede9c2968e50f110b571164549f16495217 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243707 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
Fixes dart-lang/language#731. Change-Id: I5fee1470efe7b891b79dcfecd33bc3670590efb3 Tested: trybots, and global presubmit in the internal mono repo Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243530 Reviewed-by: Bob Nystrom <rnystrom@google.com> Commit-Queue: Paul Berry <paulberry@google.com> Reviewed-by: Liam Appelbe <liama@google.com> Reviewed-by: Michael Thomsen <mit@google.com>
This is fully implemented and enabled as of 0a92b0c3aa2e32924a04c7a12a544f98b611fea4. It was released to the dev channel in 2.18.0-150.0.dev. |
@stereotype441 Thanks for this amazing update ! However I don't know if this is expected but the following does not work: List<num> numList;
...
List<int> intList = [1, 2, 3];
numList = [
intList.fold(0, (x, y) => x.bitLength + y), // Error: The getter 'bitLength' isn't defined for the type 'num'.
]; While the following does work: List<int> l1 = [1, 2, 3];
List<double> l2 = [1.0, 2.0, 3.0];
final a = [
l1.fold(0, (x, y) => x.bitLength + y),
l2.fold(0.0, (x, y) => x + y),
];
print(a is List<num>); // true |
@felangel can this prevent the need to specify the state when declaring a BlocBuilder widget? From (current): |
I think it would be useful to clarify the situation around this comment. Here is a simpler example showing the same phenomenon as the example given there: X f<X>(X x, X Function(X) g) => g(x);
void main() {
var i = 1;
num n = f(i, (j) => j.bitLength); // 'The getter `bitLength` isn't defined for the type `num`'.
} In this case (and in the original example) there is no direct connection to the support for horizontal inference (which is the new feature that was introduced to resolve this issue). The reason why the inferred actual type argument to the invocation of This means that there is no reasonable way we could change horizontal inference such that these examples would infer It is a separate discussion that Dart type inference gives priority to the context type, rather than choosing a type which is obtained by plain bottom-up inference. Check out the issues about inference in general, which currently shows at least four issues about how to handle context types. The standard example showing that the context type cannot be ignored in Dart could be this one: // Assume that Dart would give priority to bottom-up inference,
// rather than giving priority to the context type.
void main() {
List<num> nums = []; // Inferred as `<Never>[]`.
nums.add(1); // Throws.
} |
EDIT by @jmesserly
Inference should flow information up/down between arguments in a generic function call. This will help inference common methods like
Iterable.fold
andFuture.forEach
.(original description)
We currently do downwards and upwards inference independently. This gets us a fair bit, but we should eventually integrate the two, probably following the colored local type inference approach.
The text was updated successfully, but these errors were encountered: