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

Cannot use field twice after an is not None check? #145

Open
bennn opened this issue Sep 26, 2024 · 4 comments
Open

Cannot use field twice after an is not None check? #145

bennn opened this issue Sep 26, 2024 · 4 comments

Comments

@bennn
Copy link
Contributor

bennn commented Sep 26, 2024

On Python 3.10.5+cinder, Cinder commit 1aeff3259dc

Example program:

class Node:
    def __init__(self) -> None:
        self.wins: int = 0
        self.losses: int = 0
        self.parent: None | Node = None

    def score1(self) -> int:
        n = 0
        if self.parent is not None:
            n += self.parent.wins
        if self.parent is not None:
            n += self.parent.losses
        return n

    def score2(self) -> int:
        n = 0
        if self.parent is not None:
            n += self.parent.losses
            n += self.parent.wins # type error !?
        return n

Expected no type error.

But got a type error inside score2 accessing self.parent.wins:

  File "foo.py", line 20
    n += self.parent.wins
        ^
cinderx.compiler.errors.TypedSyntaxError: Optional[__main__.Node]: 'NoneType' object has no attribute 'wins'

If you swap that line and the previous one, the error is on self.parent.losses instead!

@bennn
Copy link
Contributor Author

bennn commented Sep 26, 2024

Also, it's strange that the test must be self.parent is not None. I'd rather write self.parent by itself.

@carljm
Copy link
Contributor

carljm commented Sep 26, 2024

I think this is Static Python reflecting the idea that losses could be a dynamic property which could execute arbitrary code. So once you've accessed it, any guarantees from the narrowing go out the window. That explains the seemingly odd "second access is an error" behavior.

It might be possible for SP to know that this isn't actually the case for an attribute of a given static type, though, since we can see the definition of the type. The trick here is subclassing, though -- it depends what our rules are on subclasses overriding a normal attribute with a dynamic property.

Respecting if x (and not just if x is not None) as eliminating the possibility of None in an optional type should definitely be possible, I think it just hasn't been implemented.

@bennn
Copy link
Contributor Author

bennn commented Sep 26, 2024

ah, that makes sense, thanks!

@bennn
Copy link
Contributor Author

bennn commented Oct 22, 2024

This seems like a related issue: if self.parent is optional, then I can't call a method on it (after a guard) because SP thinks I'm passing an optional value as the self argument to the method call:

import __static__
from typing import Optional

class Node:
  def __init__(self, val: int):
    self.parent: Optional[Node] = None
    self.val: int = val

  def add(self, pp: Node):
    self.parent = pp

  def getval(self):
    return self.val

  def pval(self):
    if self.parent is not None:
      self.parent.getval() # ERROR
      # it's fine if I define `pp = self.parent` and do the call on `pp`

def main():
  n0: Node = Node(0)
  n1: Node = Node(1)
  n0.add(n1)
  n0.pval()
  return

main()
 ....
    raise exception
  File "test.py", line 18
    self.parent.getval()
cinderx.compiler.errors.TypedSyntaxError: type mismatch: Optional[__main__.Node] received for positional arg 'self', expected __main__.Node

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants