Skip to content

Improve handling of global scope for partially defined check #14213

Closed

Description

@ilinum

Currently, partially defined check (responsible for two error codes: partially-defined and use-before-def) doesn't handle variables defined in outer scope very consistently.

Here's an example that generates no error right now:

def foo() -> None:
  global x
  print(x)  # No error here right now.

foo()  # This will result in a runtime error.
x = 1

However, in some cases, the check also generates use-before-def errors:

def foo() -> None:
  global x
  print(x)  # E: Name "x" is used before definition[use-before-def]
  x = 1

x = 1

Note that the same applies to inner functions.

The question is: what should the behavior be here? Should we generate a may be defined error in both cases? Or, perhaps, we should generate no error in either case (because it's a pretty common pattern).

@JukkaL @ilevkivskyi do you have thoughts?

Activity

  1. ilevkivskyi commented on Nov 28, 2022

    @ilevkivskyi
    Member

    I think it should be "may be undefined" in both cases. IMO the general rule is roughly like this: if mypy can prove it will always fail, it is "use before definition", and if mypy cannot prove it will not fail, it is "may be undefined". So it is fine to have some false negatives for the first, and some false positives for the second. But not vice versa.

  2. ilinum commented on Nov 29, 2022

    @ilinum
    CollaboratorAuthor

    I agree with the thought behind this (when to use which error code).

    However, I'm a bit worried that it will generate too many false positives and I'm thinking that maybe we should explicitly assume that everything from outer scope is always defined, even if definition comes after.

    I recently stumbled upon an interesting example:

    class C:
        def f0(self) -> None:
            self.f1()  # If we report cross-scope errors, this will be a `partially-defined` error.
        def f1(self) -> None:
            pass

    So for the example with classes above, we will definitely need some special handling (make class scope special). I'm not convinced that the noise is worth it.

  3. ilevkivskyi commented on Nov 29, 2022

    @ilevkivskyi
    Member

    The example with method is obviously safe, but attribute access is totally different story and will require separate handling independently of what we do here.

    Coming back to variables, the partially-defined error code is going to stay off by default, so people who will opt in to it, will likely prefer false positives over false negatives (especially if there is a simple workaround like put variables at top of module). As a random data point, I have fixed a bad bug caused by use of a forward global ~year ago.

    Note however, code like this should not generate any errors:

    def foo() -> None:
        bar()
    
    def bar() -> None:
        foo()

    Because this is actually a very common pattern, and a totally fine one. If it is hard to implement the global variable error without also causing an error in this last example, it is fine to leave it as a follow up issue. (But we definitely should not give the use-before-def error as we currently do.)

  4. ilinum commented on Nov 29, 2022

    @ilinum
    CollaboratorAuthor

    The check is a bit non-trivial to implement (since we need to handle var vs def declaration in scope differently). For now, #14222 will make it not generate an error.

  5. ilinum commented on Feb 8, 2023

    @ilinum
    CollaboratorAuthor

    After #14517 is merged, this issue will largely be resolved.
    We still don't detect the following case:

    def foo() -> None:
      global x
      print(x)  # No error here right now.
    
    foo()  # This will result in a runtime error.
    x = 1

    But I don't see how we can handle this in a reasonable way. We either need to flag all such uses as possibly-undefined (which will cause a ton of errors -- any time you're using a global var). Or we'll need to track the order in which functions are called (which seems very difficult to implement).

  6. ilevkivskyi commented on Feb 11, 2023

    @ilevkivskyi
    Member

    Yes, I think this issue can be closed by #14517. You can either open a separate (low-priority) issue to track the remaining tricky case, or just wait for someone to complain about this :-)

  7. added a commit that references this issue on Mar 1, 2023
    a618110
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions