Skip to content
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

"if (... is int)" does not "promote to the tested type", as you have written here: #4037

Closed
kari538 opened this issue May 15, 2022 · 2 comments · Fixed by #4047
Closed

"if (... is int)" does not "promote to the tested type", as you have written here: #4037

kari538 opened this issue May 15, 2022 · 2 comments · Fixed by #4047
Labels
d.enhancement Improves docs with specific ask dev.null-safety Relates to transforming or migrating Dart code to sound null safety e1-hours Can complete in < 8 hours of normal, not dedicated, work p2-medium Necessary but not urgent concern. Resolve when possible.

Comments

@kari538
Copy link

kari538 commented May 15, 2022

Page URL

https://dart.dev/null-safety/understanding-null-safety

Page source

No response

Describe the problem

Before null-safety, I had a Map with "pings" that would count down for different players in a game. As players would come and go, their ping would be added and removed from the Map, but the countdown might try to count down after an entry was removed, so I added an "if" statement which worked fine.

But after upgrading to null-safety, I can't get the "--" operator to work! According to this page, https://dart.dev/null-safety/understanding-null-safety , I should be able to write "if (x is int) {}", and inside the body of that "if", x should be treated as an actual int. But it doesn't work in my case! Look:

    while (pingCountDownMap.containsKey(follower) && this.mounted) {
      await Future.delayed(Duration(seconds: 1));
      if (pingCountDownMap.containsKey(follower) && pingCountDownMap[follower] != null) {
        // Original:
        // pingCountDownMap[follower]--;
        
        // Monkey-patch:
        // int localPing = pingCountDownMap[follower] as int;
        // localPing--;
        // pingCountDownMap[follower] = localPing;
        
        // Should work, according to documents, but doesn't:
        if (pingCountDownMap[follower] is int) {
          pingCountDownMap[follower]--;
        }
    }

Shouldn't that last "if" statement work? Because it doesn't. I get the compile error that "--" can't be used on a value that might be null.

Is there any other check or correction that I could do so that I can still use the "--" operator, without having to go through that monkey-patch I show above?

Expected fix

Please tell me how to use the "--" operator on an int in a Map<String, int>, where the int in question may have been removed at the time the "--" operator is supposed to fire.

And please update the page in the provided link to include this situation.

Additional context

No response

@parlough
Copy link
Member

parlough commented May 16, 2022

Hi @kari538, thanks for opening an issue.

This is an issue because type promotion only works on local variables due to properties and other things like the map's subscript operators technically being able to return a different value each time. You can read more about this occurrence in Working with nullable fields and how to fix some of these errors in Fixing type promotion failures.

Currently, something like your patch is needed due to the compound assignment operators not handling nullable values. Another potential solution, if you know the key is present in the map, could be something like the following:

pingCountDownMap[follower] = pingCountDownMap[follower]! - 1;

Or using the update method. Note, if the key isn't present, the ifAbsent named argument must be specified.

pingCountDownMap.update(follower, (value) => value - 1);

Since your map doesn't support nullable values (it seems), another possible solution could be the following:

final followerPing = pingCountDownMap[follower];
if (followerPing != null) { // Or 'is int' if your map supports other types of values
  pingCountDownMap[follower] = followerPing - 1;
}

There are many potential solutions and improvements being discussed for these class of issues, such as
dart-lang/language#1113 which discusses better support compound assignment operators like in your situation and dart-lang/language#2020 which proposes a solution to promotion for fields.

I recommend looking through and adding to those issues or others on the language repository to discuss your use case. Let me know if you need any further help though :)


As for potential improvements to the documentation here, I think we can make it clearer in the Type promotion on null checks section this only works on local variables and direct to the resources discussing promotion for properties like Working with nullable fields. Perhaps even add another example to the fix article around bypassing compound assignment operator's lack of nullable handling.

Let me know if you have any further questions or have any other suggestions. Thanks again!

@parlough parlough added d.enhancement Improves docs with specific ask p2-medium Necessary but not urgent concern. Resolve when possible. e1-hours Can complete in < 8 hours of normal, not dedicated, work dev.null-safety Relates to transforming or migrating Dart code to sound null safety labels May 16, 2022
@kari538
Copy link
Author

kari538 commented May 21, 2022

It only works on local variables? How local?... The expression 'the variable is promoted within that block of code' made me think that it's promoted locally but can exist globally!...?

Anyway, thanks for the workarounds! :) I'm gonna go with:

map[x] = map[x]! - 1;

for now. It's a longer line than:

map[x]--;

but at least, it's still a one-liner! ;)


Edit:

But now, that I've thought and read about it a bit more...., is it really safe to do so?

I mean, the pingCountDownMap IS a global variable, and the element pingCountDownMap[follower] CAN be removed at any time... So just because I write an if(pingCountDownMap[follower] != null) statement, that doesn't 100% mean it's still non-null when we reach the next line of execution!... Does it? These are async functions, after all...

And my "monkey-patch" in the OP also doesn't solve this, as it too assumes the value stays non-null all through the block... So it seems the only truly null-safe way to do it would be:

int? localPing = pingCountDownMap[follower]; // Will return null if the element doesn't exist, right? (If not, I'll have to put an extra condition here.)

if(localPing != null) {
  localPing = localPing! - 1;   // Ok, coz localPing can't change from outside
}

if(pingCountDownMap.containsKey[follower]) {
  pingCountDownMap[follower] = localPing ?? 0;
}

That last "if()" is not absolutely necessary, it won't make anything crash if removed, but I don't want it to add an element again after it's been removed. If it's gone at that point, it should stay gone. According to earlier reasoning, this doesn't 100% guarantee that... but as I said, at least it won't crash anything. 😏 Even with maximum bad luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
d.enhancement Improves docs with specific ask dev.null-safety Relates to transforming or migrating Dart code to sound null safety e1-hours Can complete in < 8 hours of normal, not dedicated, work p2-medium Necessary but not urgent concern. Resolve when possible.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants