Skip to content

Flow analysis. Variable can be promoted to the type which is not subtype of its current type. #60646

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
sgrekhov opened this issue Apr 30, 2025 · 5 comments
Labels
area-dart-model For issues related to conformance to the language spec in the parser, compilers or the CLI analyzer. model-flow Implementation of flow analysis in analyzer/cfe

Comments

@sgrekhov
Copy link
Contributor

main() {
  Object? s = "Some string";
  s as String;      // `s` promoted to `String`
  if (s is int) {}  // Make `int` a type of interest for `s`.
  s = 42;           // `s` demoted to `Object?`
  s.isEven;         // This error is not reported. `s` promoted to `int`
//  ^^^^^^
// [analyzer] unspecified
// [cfe] unspecified
  s.substring(0);
//  ^^^^^^^^^
// [analyzer] unspecified
// [cfe] unspecified
}

According to the dart-lang/co19#3160 (comment) after s = 42 assignment s should be demoted to Object? and both s.isEven and s.substring(0) should be errors.

cc @stereotype441

Dart SDK version: 3.9.0-63.0.dev (dev) (Sun Apr 27 17:07:04 2025 -0700) on "windows_x64"

@eernstg
Copy link
Member

eernstg commented Apr 30, 2025

I think s should be demoted to Object (not Object? and not int). In any case, it is possible that the behavior which is actually implemented and which causes s to be "demoted to" int is also the target of #60622.

@sgrekhov sgrekhov added the model-flow Implementation of flow analysis in analyzer/cfe label May 1, 2025
@stereotype441
Copy link
Member

stereotype441 commented May 2, 2025

Flow analysis is behaving in the way that's intended, but not in a way that's consistent with the spec. I consider this a bug in the spec.

I'll need to come back later (hopefully with @eernstg's help) and specify this more precisely, but in essence, the rule is that an assignment first performs demotion (discarding any promotions that aren't compatible with the written type), followed by promotion to a type of interest (if possible).

Here's a first attempt at specifying that a little more precisely:

  • If the variable has been write captured, then there is no further promotion or demotion (a write capture always fully demotes the variable, and it can never be promoted again thereafter).
  • Otherwise:
    • Any type in the variable model's promoted list that is not a supertype of the written type is removed from the promoted list. This is the "demotion" step.
    • Then, if the assignment is a kind that can promote to a type of interest*:
      • We create a list of the types of interest. This list contains:
        • NonNull(declared), if it's not the same type as declared (declared is the declared type of the variable).
        • For each type T in the variable model's tested list:
          • T.
          • NonNull(T), if it's not the same type as T.
      • If any type in that list is the same type as the written type, then the variable is promoted to that type.
      • Otherwise, the list of types of interest is narrowed down as follows:
        • Any type in the list of types of interest that is not a supertype of the written type is discarded.
        • Any type in the list of types of interest that is not a subtype of the variable's current promoted type (the result of the "demotion" step) is discarded.
        • Any duplicate types in the list of types of interest is discarded.
        • If, after the above discards, the resulting list contains exactly one type that is a subtype of all the others, then the variable is promoted to that type.
        • Otherwise no promotion is performed.

*most assignments can promote to a type of interest; the only exception is the implicit assignment performed by the initialization of a final variable at its declaration site; this ensures that final int? x = 0; doesn't promote x to int; we assume that when a user initializes a final variable at its declaration site, and they provide an explicit type, they really want the variable to have that type.

So under these rules, s = 42; first demotes s to Object?, and then promotes it to int. Which is why no error is reported for s.isEven;.

Edit: So under these rules, s = 42; first demotes s to Object, and then promotes it to int. Which is why no error is reported for s.isEven;.

@eernstg
Copy link
Member

eernstg commented May 5, 2025

Thanks, @stereotype441!

Just one thing: Why wouldn't the demotion step demote s to Object rather than Object?? I thought that promoted would contain Object because it is a type of interest after the initialization of s and s is actually promoted to have type Object at that point. I don't know if we can find a situation where it matters, but it seems to be a difference that could filter out some non-nullable types of interest (and perhaps help satisfying that "unique minimal type" requirement).

@stereotype441
Copy link
Member

@eernstg

Thanks, @stereotype441!

Just one thing: Why wouldn't the demotion step demote s to Object rather than Object?? I thought that promoted would contain Object because it is a type of interest after the initialization of s and s is actually promoted to have type Object at that point. I don't know if we can find a situation where it matters, but it seems to be a difference that could filter out some non-nullable types of interest (and perhaps help satisfying that "unique minimal type" requirement).

You're absolutely right, thank you! I've fixed my comment above.

@lrhn lrhn added the area-dart-model For issues related to conformance to the language spec in the parser, compilers or the CLI analyzer. label May 7, 2025
@lrhn
Copy link
Member

lrhn commented May 7, 2025

For each type T in the variable model's tested list:

  • T.
  • NonNull(T), if it's not the same type as T.

Should we add "... if T is a subtype of declared" to the first item? Or do we filter those away later before trying to promote/demote to them?

Should we add "T? if T is not nullable (and T? is a subtype of declared)" to the second item?

For the second, if I have Object? x;, I do if (x is int) {} to make int a type of interest. This should then also make int? a type of interest, so that x = (null as int?) would promote x to int?. The int? is a nullability-variant of int, and it's a subtype of Object?, and you cared about int, so you probably care about int? too.

(The underlying rule would be that if T is a type of interest, so is NonNull(T) and T?, and then maybe filtered by not actually caring about types that are not subtypes of declared.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-dart-model For issues related to conformance to the language spec in the parser, compilers or the CLI analyzer. model-flow Implementation of flow analysis in analyzer/cfe
Projects
None yet
Development

No branches or pull requests

4 participants