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

[used before def] rework builtin handling #14483

Merged
merged 3 commits into from
Jan 24, 2023

Conversation

ilinum
Copy link
Collaborator

@ilinum ilinum commented Jan 21, 2023

When doing multiple passes, in the example below, range will refer to current's module range. When doing a single pass, range will refer to builtins.range:

_range = range
_C = C  # error: Name "C" is used before definition
class range: pass
class C: pass

Instead of looking at the output of semanal to check if a variable is resolving to a builtins package, we can just check if it's part of builtins module.

Fixes #14476.

@github-actions

This comment has been minimized.

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just a few minor comments. The root cause is that the semantic analyzer behaves inconsistently, but fixing that would likely be harder, so only fixing the regression seems reasonable for now.

builtins_mod = names.get("__builtins__", None)
if builtins_mod:
assert isinstance(builtins_mod.node, MypyFile)
self.builtins = set(builtins_mod.node.names.keys())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a minor optimization, we could store builtins_mod.node.names in self.builtins (i.e. a SymbolTable instance). This would avoid having to create a set with all the items in builtins.

# flags: --enable-error-code used-before-def

# When doing multiple passes, mypy resolves references slightly differently.
# In this case, it would refer the earlier `range` call to the range class defined below.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change range in the comment to type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! I realized that range wasn't included in fixtures by default, so switched to type but didn't update the comment. Thanks!

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

spark (https://github.com/apache/spark)
- python/pyspark/pandas/namespace.py:159: error: Name "range" is used before definition  [used-before-def]

@@ -597,7 +603,7 @@ def visit_starred_pattern(self, o: StarredPattern) -> None:
super().visit_starred_pattern(o)

def visit_name_expr(self, o: NameExpr) -> None:
if refers_to_builtin(o):
if o.name in self.builtins:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm this should only affect references to global definitions. Can you add a test case to ensure that a local variable named type is still processed? A potential fix would be to check if fullname starts with builtins..

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It think that you can also check that the kind of SymbolTableNode is GDEF.

Copy link
Collaborator

@JukkaL JukkaL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to fix the local case in a separate PR.

@ilinum ilinum merged commit 8b30913 into python:master Jan 24, 2023
@ilinum ilinum deleted the used-before-def-multipass branch January 24, 2023 15:28
JukkaL pushed a commit that referenced this pull request Mar 1, 2023
…pes (#14517)

While working on #14483, we discovered that variable inheritance didn't
work quite right. In particular, functions would inherit variables from
outer scope. On the surface, this is what you want but actually, they
only inherit the scope if there isn't a colliding definition within that
scope.

Here's an example:
```python
class c: pass

def f0() -> None:
    s = c()  # UnboundLocalError is raised when this code is executed.
    class c: pass

def f1() -> None:
    s = c()  # No error.
```
This PR also fixes issues with builtins (exactly the same example as
above but instead of `c` we have a builtin).

Fixes #14213 (as much as is reasonable to do)
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

Successfully merging this pull request may close these issues.

Used before def false positive when aliasing builtin definition
2 participants