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

mypy reports no issues on access of conditionally available fields #18708

Open
jgogstad opened this issue Feb 19, 2025 · 7 comments
Open

mypy reports no issues on access of conditionally available fields #18708

jgogstad opened this issue Feb 19, 2025 · 7 comments
Labels
bug mypy got something wrong topic-possibly-undefined possibly-undefined error code

Comments

@jgogstad
Copy link

Bug Report

To Reproduce

# foo.py
def print_foo():
    print(foo)

if __name__ == '__main__':
    foo = "foobar"

https://mypy-play.net/?mypy=latest&python=3.12&gist=d8a24e474f61a151f549728e14173383

Expected Behavior

mypy should report an error

the name foo is not guaranteed to be part of the scope in print_foo. E.g. the following code will break

import foo
foo.print_foo()

Actual Behavior

mypy outputs "Success: no issues found in 1 source file"

Your Environment

see playground link

  • Mypy version used: 1.15
  • Mypy command-line flags: none (but happens with --strict also)
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.11, 3.12, 3.13
@jgogstad jgogstad added the bug mypy got something wrong label Feb 19, 2025
@tusharsadhwani
Copy link
Contributor

tusharsadhwani commented Feb 19, 2025

It's hard for a type checker to catch such things as these are runtime behaviour dependent.

Here's another even simpler example:

https://mypy-play.net/?mypy=latest&python=3.12&gist=f2e5e3b988e9a6cac9f9c8d54d26d75e&enable-error-code=possibly-undefined

class C:
    x = 1
    del x

print(C.x)

Deducing that x doesn't exist on C anymore in the general case is a fairly hard problem, and just solving it for this one case, or just for the if __name__ == '__main__' case won't really be a good solution.


My way of working around this is to always use a main function to avoid having optionally defined globals in my python file:

def main():
    ...

if __name__ == '__main__':
    main()

@A5rocks
Copy link
Collaborator

A5rocks commented Feb 20, 2025

At the very least possibly-undefined should warn on this (and doesn't, see https://mypy-play.net/?mypy=latest&python=3.12&gist=d8a24e474f61a151f549728e14173383&enable-error-code=possibly-undefined).

... well at least maybe it should, I don't know if the fallout from this wouldn't make it worth it.

@A5rocks A5rocks added the topic-possibly-undefined possibly-undefined error code label Feb 20, 2025
@jgogstad
Copy link
Author

It's hard for a type checker to catch such things as these are runtime behaviour dependent.

I think there is a crucial difference between the examples. In the class C example, the statically determined scope of the module contains C.x, so it's not outrageous that print(C.x) typechecks—anything else feels like it's approaching the halting problem to me, but this is not my area of expertise. In the test_foo/foo example, the statically determined scope of test_foo does not contain foo.

I understand that the typechecker attempts to determine the dynamic scope in both cases and fails in both cases (and it's great that it attempts to determine this!). In the first example neither the dynamic nor the static scope contains the attribute, yet the code typechecks, so that feels definitely like a bug.

Does that make sense?

My way of working around this is to always use a main function to avoid having optionally defined globals in my python file:

That's a good workaround, just never write to the module namespace, thanks

@tusharsadhwani
Copy link
Contributor

In the test_foo/foo example, the statically determined scope of test_foo does not contain foo.

I don't think that's true. The last line in the file creates foo in the module scope of test_foo. Optionally, but it still may exist.

@tusharsadhwani
Copy link
Contributor

If you mean to say that conditionally instantiated variables should not be assumed to exist in a scope, it'll break a lot more stuff.

For example:

if TYPE_CHECKING:
  from module import MyClass
def foo(x: int):
  if x < 0:
    abs_x = abs(x)
  else:
    return x

  return abs_x

@jgogstad
Copy link
Author

If you mean to say that conditionally instantiated variables should not be assumed to exist in a scope, it'll break a lot more stuff.

For example:

if TYPE_CHECKING:
  from module import MyClass

def foo(x: int):
  if x < 0:
    abs_x = abs(x)
  else:
    return x

  return abs_x

definitely not! There is a difference though. In the quoted example above, the type is inferred through type narrowing and mypy's support for conditional types. In principle there's no reason for type narrowing to not support del statements of course, but you got to draw the line somewhere. Mypy can't generally narrow types to arbitrary precision—it would need to run the actual program then.

Your examples and arguments has made it clear (to me at least) that the original problem phrasing is imprecise. It's more precise to say that since mypy supports type narrowing via conditionals, then I expect that it fails when fields are conditionally available. I've updated the title and description to reflect this.

@jgogstad jgogstad changed the title mypy reports no issues on access of undefined variable mypy reports no issues on access of conditionally available fields Feb 24, 2025
@jgogstad
Copy link
Author

after reading the documentation again, https://mypy.readthedocs.io/en/stable/type_narrowing.html#type-narrowing-expressions, I see the issue. There's no type narrowing in effect on this conditional of course.

I think it would be nice if type narrowing were extended to detect fields that are only present on one side of a conditional, but I see now that this is a feature request and probably not a bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-possibly-undefined possibly-undefined error code
Projects
None yet
Development

No branches or pull requests

3 participants