-
Notifications
You must be signed in to change notification settings - Fork 208
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
Infer non-nullability from local boolean variables #1274
Comments
What is |
Also, is this a bug with the migration tool (it should do something different)? Or with the null safety feature as implemented in the VM or something? |
This isn't impossible, per se, but it's a pretty substantial lift. We'd need to track correlations between boolean valued variables and other pieces of our nullability state. I'm sympathetic to the request, but I don't think it's feasible in the time frame we have, and am not sure it would warrant the implementation complexity. |
@leafpetersen did we decide to not do this? Then perhaps it should be turned into a potential, future language issue? |
This is definitely not going to be in the feature as shipped. We could keep it as an option for future releases, but I'm pretty skeptical that this has the bang for the buck. This would require some pretty heavy machinery in the static analysis. I'll move it to the language repo for a round of discussion, but I think we may just want to close this out as unlikely to happen. |
@lrhn @eernstg @stereotype441 @munificent @natebosch @jakemac53 Is this something we see potentially doing? Is there are a generalization that would increase the bang/buck, given that I expect the denominator there to be large? |
The idea here is to abstract a test. That does make some sense. It's still a matter of "if this value is true, it implies something for some variables", we just don't branch on the boolean immediately. I do worry about making the promotion inference rules too opaque. We might be able to expand that to the implications being bound to boolean values stored in local variables, whether branched on immediately or not, we can still try to decide which code is dominated by the value being var hasFoo = foo != null;
if (hasFoo) {
foo.start();
}
// more code
if (hasFoo) {
foo.end();
} without needing to test twice. We can lose the information if anything assigns to either As long as we can explain to users which tests promote which variables in a short and clear way, I'm for it. |
To drill down a little further on the bang/buck question, I think the key implementation difficulty is that flow analysis models are snapshots of the state of the function at points in time; if we record a state at one time and then try to use it later, we need to somehow "rebase" that state to account for other promotions and demotions that have happened since we took the snapshot. Consider: int? getPossiblyNullValue() => ...;
void f(int? x, int? y, int? z) {
if (y == null) return;
bool b = x == null; // (1)
if (z == null) return;
y = getPossiblyNullValue(); // (2)
if (b) return; // (3)
} At the time of (1), I think we could fix the problem by doing some SSA-like bookkeeping: associate each variable with a unique integer representing its "SSA node id". Whenever SSA analysis would need to allocate a fresh node (either because of an assignment or because of a phi node at a join point), we allocate a fresh id. That way the rebase algorithm can distinguish demotions from promotions by seeing that the ids are different. The algorithm would be: if the ids match, we take the most promoted type; if they differ, we take the more recent information. So considering the example above again, we would be rebasing Another implementation difficulty is: at (3), when we recall the state we stored at (1), how do we make sure it hasn't been invalidated by intervening assignments to So that's the cost ("buck") part of the equation. What are the potential benefits ("bangs")? I can think of several:
foo(int? x);
int y;
if (x != null) {
y = x + 1;
// Flow analysis state is implicitly saved here, associated with `x != null`
}
...other logic...
if (x != null) { // SSA ids let us see that this is the same condition as above
// `x != null` state is restored here (and rebased over any promotions/demotions
// that have occurred in the meantime)
print(y + 1); // Ok; we know that `y` is definitely assigned.
}
} I'm not entirely sure whether the benefits justify the costs, but it sounds like a lot of fun to work on. I will try to set aside a few days after the Beta release ships to see if I can make these ideas pan out. |
One further thought. Extending my idea from yesterday, if we changed flow analysis so that it tracked promoted types via their SSA node id, then we could make promotion understand about reassigned variables, e.g.: void foo(int? x) {
var y = x;
if (x != null) {
print(y + 1); // Ok because y and x refer to the same value; and x != null
}
} Not a super compelling use case, but it would be a little extra "bang" that could potentially be unlocked if we wanted to go this direction. |
The FlowModel._freshVariableInfo field was an optimization with dubious benefit (it avoided a single allocation when seeing a variable for the first time). And it is getting in the way of changes I have planned for dart-lang/language#1274 (which will require each fresh variable to be allocated different information). Removing FlowModel._freshVariableInfo means that now we have a call to VariableModel.fresh() whenever we see a variable for the first time, so we can take advantage of this to elimiate the VariableModel.initialize() method, and simply allocate the VariableModel in the initialized state when it's appropriate to do so. Change-Id: I4b6f7d004fff72eaa4025fdf79e9ca29a81d7f96 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174566 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
…nfo. Previously, we used a single class hierarchy, ExpressionInfo, to store all the information that flow analysis needs to know about a variable, including: 1. What is known about the program state if the expression evaluates to true/false 2. Whether the expression is a `null` literal 3. Whether the expression is a reference to a variable. However, in order to address dart-lang/language#1274 (Infer non-nullability from local boolean variables), we'll need #3 to be tracked orthogonally from #1, so that when a local boolean is referred to later, we can track information of type #1 and #3 simultaneously. However, it makes sense to keep #1 and #2 in the same data structure, because future work is planned to represent them in a more uniform way, as part of addressing dart-lang/language#1224 (Using `if (foo?.bar == somethingNotNull)` should promote `foo`). Change-Id: I432f6e2e80543bb1d565b49403180c520eef66a5 Bug: dart-lang/language#1274 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175008 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
…motion info." This reverts commit fd2a6c6. Reason for revert: Broke pkg/dds/test/sse_smoke_test Original change's description: > Flow analysis: Track expression variables separately from promotion info. > > Previously, we used a single class hierarchy, ExpressionInfo, to store > all the information that flow analysis needs to know about a variable, > including: > > 1. What is known about the program state if the expression evaluates > to true/false > > 2. Whether the expression is a `null` literal > > 3. Whether the expression is a reference to a variable. > > However, in order to address > dart-lang/language#1274 (Infer > non-nullability from local boolean variables), we'll need #3 to be > tracked orthogonally from #1, so that when a local boolean is referred > to later, we can track information of type #1 and #3 simultaneously. > > However, it makes sense to keep #1 and #2 in the same data structure, > because future work is planned to represent them in a more uniform > way, as part of addressing > dart-lang/language#1224 (Using `if (foo?.bar > == somethingNotNull)` should promote `foo`). > > Change-Id: I432f6e2e80543bb1d565b49403180c520eef66a5 > Bug: dart-lang/language#1274 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175008 > Reviewed-by: Johnni Winther <[email protected]> > Commit-Queue: Paul Berry <[email protected]> [email protected],[email protected],[email protected] Change-Id: I70b4adaf13f412a42a8128b9c7b9583b4171158e No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: dart-lang/language#1274 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175321 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Paul Berry <[email protected]>
…motion info." This is a reland of fd2a6c6 Original change's description: > Flow analysis: Track expression variables separately from promotion info. > > Previously, we used a single class hierarchy, ExpressionInfo, to store > all the information that flow analysis needs to know about a variable, > including: > > 1. What is known about the program state if the expression evaluates > to true/false > > 2. Whether the expression is a `null` literal > > 3. Whether the expression is a reference to a variable. > > However, in order to address > dart-lang/language#1274 (Infer > non-nullability from local boolean variables), we'll need #3 to be > tracked orthogonally from #1, so that when a local boolean is referred > to later, we can track information of type #1 and #3 simultaneously. > > However, it makes sense to keep #1 and #2 in the same data structure, > because future work is planned to represent them in a more uniform > way, as part of addressing > dart-lang/language#1224 (Using `if (foo?.bar > == somethingNotNull)` should promote `foo`). > > Change-Id: I432f6e2e80543bb1d565b49403180c520eef66a5 > Bug: dart-lang/language#1274 > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175008 > Reviewed-by: Johnni Winther <[email protected]> > Commit-Queue: Paul Berry <[email protected]> Bug: dart-lang/language#1274 Change-Id: I002adbde782887def50dc80ab6673411b321c341 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175362 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Paul Berry <[email protected]>
This is the first step in a sequence of CLs to support type promotion based on local boolean variables (dart-lang/language#1274). To do this, we will need a way to reliably tell whether a variable's value has changed from one point in program execution to another, and to associate additional information with a particular value of a variable. We'll accomplish both of these tasks by associating each variable with a pointer to an "SSA node". This pointer is updated to point to a fresh node whenever the variable is written to, or two different possible values come together at a control flow join. Note that when a variable is write captured, it becomes impossible to track when/if its value might change Previously we tracked this using a `writeCaptured` boolean; now we track it by setting the SSA node pointer to `null`. This CL just lays the groundwork infrastructure and unit tests it; there is no user-visible change. Bug: dart-lang/language#1274 Change-Id: Id729390655c9371cba264816b418f6c0463e1758 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176180 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this is unreachable: bool hasArguments = false; ... if (hasArguments) { // Not reachable } To prepare for this improvement, we need to add an "ignore" comment to ignore the dead_code warning that will be generated.
When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool contextIsValid = focus != null && focus.context != null; ... if (contextIsValid) { ... focus! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled into flutter, I'll remove the null check entirely.
When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is fixed, this algorithm will replace the `restrict` algorithm is currently used at the bottom of a try/finally statement to combine the flow models from the `try` and `finally` blocks. The new algorithm has very similar behavior to the old one, however since it is based on SSA nodes it (a) is able to make slightly more promotions than the old one, and (b) will be able to properly update SSA nodes so that local boolean variables assigned inside a `try` block will be able to used for promotion after the `finally` block. The new functionality is hidden behind a flag for now, so there's no customer-visible change yet. Bug: dart-lang/language#1274 Change-Id: I1d37f981688f58da1e5c6c7eee48f2319c997cef Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174960 Reviewed-by: Johnni Winther <[email protected]>
This CL modifies flow analysis API so that when a variable is written or initialized, if the written expression has non-trivial flow analysis information, it is captured in the SsaNode associated with the variable. The stored information is not yet used; in a follow-up CL, I will add the ability to retrieve it on a read and use it for promotions. Bug: dart-lang/language#1274 Change-Id: I1e2590205d4a0c59f4400a119f3d6b380a11414c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176460 Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
This CL adds functionality to FlowAnalysis.variableRead so that if the truth value of the variable is known to be correlated with other flow analysis state, that state can be restored. This allows promotion based on boolean variables in addition to boolean expressions, e.g.: int? x = ...; var xIsNotNull = x != null; if (xIsNotNull) { print(x + 1); // Ok; x is known to be non-null. } This functionality is not enabled yet; it is hidden behind the flag `allowLocalBooleanVarsToPromoteByDefault`. Bug: dart-lang/language#1274 Change-Id: I2a0183b69d285193db7acb36cc6af5c34e165c2c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176500 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]>
In preparation for supporting inference of non-nullability from local boolean variables, we need to change a small amount of code to avoid test breakages. Bug: dart-lang/language#1274 Change-Id: Ia49e37f9ba2cd94967c7e1d411a3269593c0eeb9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176501 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
When analyzing code like this with the `allowLocalBooleanVarsToPromote` flag enabled, don't treat a variable containing `null` as equivalent to a literal `null`. For example: f(int? x) { var y = null; if (x != y) { print(x + 1); // ERROR: x must be null checked. } } The rationale is that flow analysis doesn't promote variables that are known to be `null` to the `Null` type, so it seems like it would be inconsistent if we used the fact that they're `null` to promote other variables. Bug: dart-lang/language#1274 Change-Id: Icfa14104936a26732353921e3a29a840c53d4d52 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176560 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
…lity from local boolean variables" (#72494) When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool contextIsValid = focus != null && focus.context != null; ... if (contextIsValid) { ... focus! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled into flutter, I'll remove the null check entirely.
…23131) When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool hasIdentityTransform = transform == null || isIdentityFloat32ListTransform(transform); ... if (!hasIdentityTransform) { ... transform! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled into flutter, I'll remove the null check entirely.
* c608b07 bump fuchsia toolchain to clang-12 (flutter/engine#23067) * 8a3e9a2 fix crash in FontCollection::init() when FontFamily is empty (flutter/engine#23019) * 95ba5ca Roll Skia from 6f31e27f1e29 to 85fa75616dfe (7 revisions) (flutter/engine#23118) * 50e7d89 [web] Switch web-render option default to auto (flutter/engine#23090) * 8854520 Roll Skia from 85fa75616dfe to d6f2338ab194 (3 revisions) (flutter/engine#23119) * acad21c [web] Tests for rich paragraph DOM (flutter/engine#23097) * 27ebbc4 Rename PointerState.isDown as per style guide (flutter/engine#23120) * 19950f5 [web] Rich paragraph getBoxesForRange (flutter/engine#23098) * 581acbe Roll Skia from d6f2338ab194 to 1d89532d5988 (1 revision) (flutter/engine#23122) * 540b191 Roll Fuchsia Mac SDK from acylwa3i4... to chLTYsKMR... (flutter/engine#23125) * 3b52edf Roll Skia from 1d89532d5988 to 7839f66540b6 (1 revision) (flutter/engine#23126) * 6b25350 Roll Fuchsia Linux SDK from TIKHoiQyP... to wu6yV-_BL... (flutter/engine#23127) * 9c72085 Roll Skia from 7839f66540b6 to 20f1b3462878 (1 revision) (flutter/engine#23129) * ee323d0 Roll Skia from 20f1b3462878 to 995f0366bd21 (2 revisions) (flutter/engine#23132) * 625aa69 Roll Skia from 995f0366bd21 to b64da3907f76 (1 revision) (flutter/engine#23135) * affc421 Roll Skia from b64da3907f76 to 81da68af2ecf (7 revisions) (flutter/engine#23142) * 9a6a31e Roll Fuchsia Mac SDK from chLTYsKMR... to RDUxjnng0... (flutter/engine#23143) * 78657ed Added golden test to make sure that spawn engines work. (flutter/engine#23066) * bb9cac2 Roll Fuchsia Linux SDK from wu6yV-_BL... to _l04etgVd... (flutter/engine#23145) * 51c9ae9 Add --strict_null_safety_checks to the Dart flag allowlist (flutter/engine#23144) * 76310c4 Add missing sdk constriant in pubspec.yaml files. (flutter/engine#23124) * 6e54f0d [fuchsia] Add wrapper for zx_clock_get_monotonic. (flutter/engine#23128) * ade75e0 [web] Rich paragraph getPositionForOffset (flutter/engine#23133) * 2ef2c86 Fix engine in preparation for implementing dart-lang/language#1274 (flutter/engine#23131) * 9384324 Update android_lint deps (flutter/engine#23151) * ea4bb2a Roll Skia from 81da68af2ecf to 7b920446a8fc (14 revisions) (flutter/engine#23152) * 4b5e4e6 Make it easier to turn on Xcode symlinks (flutter/engine#23150) * 325f069 Roll fuchsia toolchain (flutter/engine#23155) * 05704d3 Use include for C/C++ headers in darwin/macos (flutter/engine#23035) * 8d3d69b Turned no malloc scribble and randomized the tests. (flutter/engine#23014) * 418cc48 Fix macOS crash when modifier keys pressed. (flutter/engine#23154) * a8c360d Update FlutterPlatformViewsTests (flutter/engine#23158) * d941aef [web] Rich text painting on bitmap canvas (flutter/engine#23136) * 67cb0f3 Revert "[web] Switch web-render option default to auto (#23090)" (flutter/engine#23161) * c4b48c5 Roll Skia from 7b920446a8fc to dfc880bd9ba0 (14 revisions) (flutter/engine#23164) * de1de9d Disable FlutterPluginAppLifeCycleDelegateTest testWillResignActive (flutter/engine#23166)
…lutter#23131) When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool hasIdentityTransform = transform == null || isIdentityFloat32ListTransform(transform); ... if (!hasIdentityTransform) { ... transform! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled into flutter, I'll remove the null check entirely.
We discussed this at the language team meeting this morning and we're moving forward with the change. I'll send out https://dart-review.googlesource.com/c/sdk/+/175500 for code review shortly. |
The feature is not turned on yet; these tests will verify that it's working properly once it is enabled. Bug: dart-lang/language#1274 Change-Id: Ia48996d4525b84b523627a7b254cba18bdef48c7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177641 Reviewed-by: Leaf Petersen <[email protected]>
The implementation was completed in previous CLs, but hidden behind a flag. This CL switches on the feature and adds integration tests. Bug: dart-lang/language#1274 Change-Id: I0e2d7ac96d3a6d68d33d49f1eab5c341c9167f6f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/175500 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
Fully implemented in dart-lang/sdk@88cd473. |
…variables. This change removes a workaround that was introduced prior to landing Dart language feature dart-lang/language#1274, which allows type promotion in null safe code to account for local boolean variables. The workaround ensured that the code would analyze the same regardless of whether the feature was enabled, allowing for a smoother transition. Now that the feature has fully landed, the workaround isn't needed anymore.
… variables. (#23862) This change removes workarounds that were introduced prior to landing Dart language feature dart-lang/language#1274, which allows type promotion in null safe code to account for local boolean variables. The workarounds ensured that the code would analyze the same regardless of whether the feature was enabled, allowing for a smoother transition. Now that the feature has fully landed, the workarounds aren't needed anymore.
This flag was being kept around in case the corresponding feature (dart-lang/language#1274) wound up having bugs and needed to be switched off. It's now had sufficient bake time that we no longer need the flag. Removing the flag allows us to remove a bunch of flow analysis code that's no longer needed. Bug: dart-lang/language#1274 Change-Id: Ib91fa09a560bb0ebc6bff280ffc37d6037816ef9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/187981 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
… variables. (flutter#23862) This change removes workarounds that were introduced prior to landing Dart language feature dart-lang/language#1274, which allows type promotion in null safe code to account for local boolean variables. The workarounds ensured that the code would analyze the same regardless of whether the feature was enabled, allowing for a smoother transition. Now that the feature has fully landed, the workarounds aren't needed anymore.
…age#1274 When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool present = value != null && value.isValid; ... if (present) { ... value! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled in, I'll remove the null check entirely. Bug: dart-lang/language#1274 Change-Id: I212db32b71f381de3ec4f10cc2631ab9523b6f70 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/463601 Reviewed-by: Chase Latta <[email protected]> Reviewed-by: Jerry Belton <[email protected]> Commit-Queue: Auto-Submit <[email protected]> Fuchsia-Auto-Submit: Paul Berry <[email protected]>
…oolean variables. This change removes a workaround that was introduced prior to landing Dart language feature dart-lang/language#1274, which allows type promotion in null safe code to account for local boolean variables. The workaround ensured that the code would analyze the same regardless of whether the feature was enabled, allowing for a smoother transition. Now that the feature has fully landed, the workaround isn't needed anymore. Change-Id: I5338bd5e25c977452881239e5d5b843c80a59b31 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/474237 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Chase Latta <[email protected]>
When dart-lang/language#1274 (Infer non-nullability from local boolean variables) is implemented, flow analysis will detect that code like this no longer needs to perform a null check: final bool hasIdentityTransform = transform == null || isIdentityFloat32ListTransform(transform); ... if (!hasIdentityTransform) { ... transform! ... // Null check unnecessary } To avoid a build failure due to the unnecessary null check, we need to temporarily write it in a way that we can ignore it. Once the feature is complete and rolled into flutter, I'll remove the null check entirely.
… variables. This change removes workarounds that were introduced prior to landing Dart language feature dart-lang/language#1274, which allows type promotion in null safe code to account for local boolean variables. The workarounds ensured that the code would analyze the same regardless of whether the feature was enabled, allowing for a smoother transition. Now that the feature has fully landed, the workarounds aren't needed anymore.
It is frequently more readable to give a boolean expression a name for self-documentation. For example:
However, this does not promote
fields
to non-null inside theif
block. A workaround is to remove the variable:However, this makes the code less readable, because non-nullness of
fields
does not immediately imply that we're dealing with a "single line" mode of something.Can the nullability inference be enhanced to infer using local booleans?
/cc @nturgut @leafpetersen
The text was updated successfully, but these errors were encountered: