Skip to content

Change flow analysis to assume sound null safety once unsound null safety support is fully removed #3100

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

Open
stereotype441 opened this issue May 22, 2023 · 5 comments
Assignees
Labels
flow-analysis Discussions about possible future improvements to flow analysis

Comments

@stereotype441
Copy link
Member

stereotype441 commented May 22, 2023

Currently, flow analysis conservatively assumes that a variable with a non-nullable type might still be null because the program might be running with unsound null safety, and we don't want null-related unsoundness to be able to be escalated into full unsoundness (see #1143 for details).

Although we don't officially support running with unsound null safety anymore, support has not yet been fully removed. Once we remove it, we should remove this conservative assumption from flow analysis.

Note that this is a significant enough change that it probably should be tied to a language version.

@stereotype441 stereotype441 added the flow-analysis Discussions about possible future improvements to flow analysis label May 22, 2023
@lrhn
Copy link
Member

lrhn commented May 23, 2023

I'm actually worried that we assume some syntactically allowed control flow cannot happen because of type analysis.
We went back on that because of unsound null safety, but I also think it was the right idea in general.

If code contains a branch, I think we should assume that all branches can be taken, if at all possible.
We currently don't in some cases, predicting that a particular branch will never get executed.
That matters for things like definite assignment, where a variable assigned on every reachable branch is considered definitely assigned after the branches has been joined.

The problem with that is that an unreachable branch is dead code. Dead code is likely a mistake, or at least a temporary thing. At some point that code will either be deleted, or the branch will be enabled.
If type inference and definite assignment changes when you remove a temporary && false added to an if condition, then you might have to go back and do a larger rewrite.

If type analysis and definite assignment has always considered the branch to be executable, then twiddling with the conditions won't suddenly make your code have type errors.

The one issue I see with such branches, is that it's not always clear which types promotable variables have on entry, since the reason the branch is unreachable might be a type test that rules out everything.

Say (probably not a correct example, but maybe there are valid examples):

// ignore_for_file: prefer_void_to_null
import "dart:async";

void main() {
  FutureOr<Null> x = null as dynamic;
  int y;
  int? z = 0;
  if (x is Future<Null>) {
    // x promoted to `Future<Nul>`
    y = 0;
  } else {
    // x promoted to `Null`
    if (x != null) {
      // x promoted to `Never` ? (Actually not, it's still `Null`)
      z = null;
    } else {
      y = 2;
    }
  }
  print(y);
  z.toRadixString(10); // Allowed because `z=null` doesn't demote.
}

Here y is considered definitely assigned, and z is not demoted to nullable,
because the type inference/definite assignment decides that the x != null cannot be true.

We do analyze the "unreacable" branch (can be seen by introducing type errors there), we just don't merge it back into the result afterwards

That's "very clever" from an analysis which cannot determine that const f= false; if (f) { reachable? } is not reachable.

Which brings me back to the argument that unreachable code should probably not be considered to be deliberately unreachable, because nobody needs unreachable code in their final production code.
If you don't need the code right now, you can comment it out, or delete it.

If the code is still there, we should, IMO, assume that it's intended to be reachable, and analyze the program as if it was.
So that when it does become reachable, by flipping a single false to true, we don't get sudden non-local changes in analysis results.

It should even be easier, just assume that all branches in the code, and all merges after them, are (intended to be) possible.

@stereotype441
Copy link
Member Author

Now that dart-lang/sdk@0060b0f has removed support for running in unsound null safety mode, I'm going to try prototyping this to see how breaking of a change it would be. Once I have that information, I'll report my findings and we can decide whether we want to go ahead with this change or not.

@stereotype441 stereotype441 self-assigned this Mar 4, 2025
stereotype441 added a commit to stereotype441/language that referenced this issue Mar 4, 2025
In fully sound null safety mode, flow analysis should know that a test
like `expr == null` is guaranteed to evaluate to `false` if the static
type of `expr` is non-nullable. But in unsound null safety mode, no
such guarantee can be made.

Since support for unsound null safety was only recently removed from
the CFE (see
dart-lang/sdk@0060b0f),
flow analysis still conservatively assumes that an expression with a
non-nullable static type might, nonetheless, still be `null`.

This change updates the spec to match the implementation in this
regard, and adds a reference to
dart-lang#3100, where we are
discussing the possibility of changing the behavior.
stereotype441 added a commit that referenced this issue Mar 5, 2025
In fully sound null safety mode, flow analysis should know that a test
like `expr == null` is guaranteed to evaluate to `false` if the static
type of `expr` is non-nullable. But in unsound null safety mode, no
such guarantee can be made.

Since support for unsound null safety was only recently removed from
the CFE (see
dart-lang/sdk@0060b0f),
flow analysis still conservatively assumes that an expression with a
non-nullable static type might, nonetheless, still be `null`.

This change updates the spec to match the implementation in this
regard, and adds a reference to
#3100, where we are
discussing the possibility of changing the behavior.
@eernstg
Copy link
Member

eernstg commented Mar 6, 2025

At the meeting Wednesday Mar 5 2025 the language team decided to proceed with this proposal.

copybara-service bot pushed a commit to dart-lang/sdk that referenced this issue Mar 27, 2025
All work on dart-lang/language#3100 (Change
flow analysis to assume sound null safety once unsound null safety
support is fully removed) will be guarded by this flag.

Bug: dart-lang/language#3100
Change-Id: I0a6e7b732520ed70944141b4bb02832b7d0d969b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/417906
Reviewed-by: Kevin Moore <[email protected]>
Reviewed-by: Konstantin Shcheglov <[email protected]>
Commit-Queue: Paul Berry <[email protected]>
@eernstg
Copy link
Member

eernstg commented Mar 31, 2025

@stereotype441, I created dart-lang/sdk#60438 in order to make it possible to see the existence and status of the ongoing implementation effort as usual. However, the old approach (one sub-issue for the analyzer and another one for the CFE, and at times lots of other issues for other things) doesn't fit the reality any more. Seems like it would suffice to just have dart-lang/sdk#60438?

@stereotype441
Copy link
Member Author

@eernstg

@stereotype441, I created dart-lang/sdk#60438 in order to make it possible to see the existence and status of the ongoing implementation effort as usual. However, the old approach (one sub-issue for the analyzer and another one for the CFE, and at times lots of other issues for other things) doesn't fit the reality any more. Seems like it would suffice to just have dart-lang/sdk#60438?

Thanks, I'll attach all of my CLs to dart-lang/sdk#60438. I think a single implementation issue is sufficient.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flow-analysis Discussions about possible future improvements to flow analysis
Projects
None yet
Development

No branches or pull requests

3 participants