Skip to content

Suppress unknown-name for global declarations the function itself defines - #4797

Open
nitishagar wants to merge 1 commit into
facebook:mainfrom
nitishagar:fix/global-assign-unknown-name-4785
Open

Suppress unknown-name for global declarations the function itself defines#4797
nitishagar wants to merge 1 commit into
facebook:mainfrom
nitishagar:fix/global-assign-unknown-name-4785

Conversation

@nitishagar

Copy link
Copy Markdown
Contributor

Fixes #4785

Problem

A global declaration inside a function errored with unknown-name ("Could not find name x") whenever the module had no module-level definition of x — even when the declaring function assigns x itself:

def set_workload_id(value: str) -> None:
    global workload_id  # ERROR: Could not find name `workload_id`
    workload_id = value

global x; x = value is a legal way to create the module-level name at runtime, so the declaration should not error.

Why it happened

The mutable-capture lookup for global names consults module-scope statics (plus a builtin fallback) only, and a function's global-assign intentionally never publishes a module-scope static — so the lookup cannot find the very name the function is about to define.

Change

The definitions phase already merges every definition site in a scope, so it knows whether the scope gives the name a value beyond the capture declaration:

  • Definition records whether any definition site is a value definition (assignment or import; del and other capture declarations do not count).
  • The flag is threaded through the MutableCapture static.
  • declare_mutable_capture suppresses only the not-found error for global declarations in function/method scopes whose own body defines the name.

Everything else is unchanged: undefined reads (global a; print(a) with no definition anywhere), assignments textually before the declaration (x = 1; global x), module top-level and class-body declarations, and all nonlocal behavior keep erroring exactly as before.

Test plan

  • New testcase!s in pyrefly/lib/test/scope.rs: the issue repro, undefined-read still errors, nested-if assignments (both orders), del doesn't define, assignment-before-declaration keeps its error, module top-level and class-body keep erroring
  • cargo test -p pyrefly --lib green (the two pre-existing missing-source LSP interaction failures also fail on unmodified main)
  • Formatting/linting clean

…f defines

A `global x` declaration inside a function errored with unknown-name whenever
the module had no module-level `x`, even when the declaring function assigns
`x` itself (`global x; x = value`) — a legal way to create the module-level
name at runtime, and the exact pattern from issue facebook#4785.

The name-resolution failure is expected there: a function's global-assign
intentionally never publishes a module-scope static, so the mutable-capture
lookup that only consults module statics cannot find what the function is
about to define. The definitions phase already knows better — it merges every
definition site in the scope — so this change records whether any site gives
the name a value (assignment or import, not `del` or another capture
declaration) on the scope's `Definition`, threads it through the
`MutableCapture` static, and suppresses only the not-found error for
`global` declarations in function/method scopes whose own body defines the
name. Undefined reads (`global a; print(a)` with no definition anywhere),
assignments before the declaration, and module/class-scope declarations keep
erroring exactly as before.

Fixes facebook#4785
@meta-cla meta-cla Bot added the cla signed label Sep 3, 2026
@github-actions github-actions Bot added the size/m label Sep 3, 2026
@meta-codesync

meta-codesync Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has been imported. If you are a Meta employee, you can view this in D118601767. (Because this pull request was imported automatically, there will not be any future comments.)

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

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

============================================================
SUMMARY
============================================================
Total: +0 new errors, -41 fixed errors
By preset: +0/-41 (default), +0/-41 (strict)

Projects with changes (9):
  mitmproxy: +0 -1
  spark: +0 -2
  pycryptodome: +0 -12
  setuptools: +0 -2
  asynq: +0 -1
  stone: +0 -3
  dd-trace-py: +0 -2
  pandas: +0 -17
  pyodide: +0 -1
============================================================

FULL DIFF DETAILS
------------------------------------------------------------

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- ERROR test/helper_tools/memoryleak.py:32:18-21: Could not find name `ssl` [unknown-name]

spark (https://github.com/apache/spark)
- ERROR python/pyspark/serializers.py:369:16-31: Could not find name `_old_namedtuple` [unknown-name]
- ERROR python/pyspark/serializers.py:370:16-42: Could not find name `_old_namedtuple_kwdefaults` [unknown-name]

pycryptodome (https://github.com/Legrandin/pycryptodome)
- ERROR lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:329:20-25: Could not find name `asked` [unknown-name]
- ERROR lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:332:24-29: Could not find name `asked` [unknown-name]
- ERROR lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:357:20-28: Could not find name `mgfcalls` [unknown-name]
- ERROR lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:361:24-32: Could not find name `mgfcalls` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_DSA.py:71:16-19: Could not find name `DSA` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_DSA.py:71:21-27: Could not find name `Random` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_DSA.py:71:29-42: Could not find name `bytes_to_long` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_DSA.py:71:44-48: Could not find name `size` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_RSA.py:90:16-19: Could not find name `RSA` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_RSA.py:90:21-27: Could not find name `Random` [unknown-name]
- ERROR lib/Crypto/SelfTest/PublicKey/test_RSA.py:90:29-42: Could not find name `bytes_to_long` [unknown-name]
- ERROR lib/Crypto/SelfTest/Util/test_Counter.py:33:16-23: Could not find name `Counter` [unknown-name]

setuptools (https://github.com/pypa/setuptools)
- ERROR setuptools/tests/test_setuptools.py:54:20-21: Could not find name `x` [unknown-name]
- ERROR setuptools/tests/test_setuptools.py:54:23-24: Could not find name `y` [unknown-name]

asynq (https://github.com/quora/asynq)
- ERROR asynq/tests/test_contexts.py:104:12-39: Could not find name `expected_change_amount_base` [unknown-name]

stone (https://github.com/dropbox/stone)
- ERROR stone/_vendor/ply/lex.py:868:12-17: Could not find name `lexer` [unknown-name]
- ERROR stone/_vendor/ply/lex.py:874:12-17: Could not find name `token` [unknown-name]
- ERROR stone/_vendor/ply/yacc.py:3168:12-17: Could not find name `parse` [unknown-name]

dd-trace-py (https://github.com/DataDog/dd-trace-py)
- ERROR ddtrace/internal/process_tags/__init__.py:140:12-24: Could not find name `process_tags` [unknown-name]
- ERROR ddtrace/internal/process_tags/__init__.py:141:12-29: Could not find name `process_tags_list` [unknown-name]

pandas (https://github.com/pandas-dev/pandas)
- ERROR pandas/io/clipboard/__init__.py:140:12-24: Could not find name `QApplication` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:348:12-19: Could not find name `HGLOBAL` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:348:21-27: Could not find name `LPVOID` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:348:29-34: Could not find name `DWORD` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:348:36-42: Could not find name `LPCSTR` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:348:44-47: Could not find name `INT` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:12-16: Could not find name `HWND` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:18-27: Could not find name `HINSTANCE` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:29-34: Could not find name `HMENU` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:36-40: Could not find name `BOOL` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:42-46: Could not find name `UINT` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:349:48-54: Could not find name `HANDLE` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:540:12-22: Could not find name `Foundation` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:540:24-30: Could not find name `AppKit` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:540:32-36: Could not find name `qtpy` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:540:38-43: Could not find name `PyQt4` [unknown-name]
- ERROR pandas/io/clipboard/__init__.py:540:45-50: Could not find name `PyQt5` [unknown-name]

pyodide (https://github.com/pyodide/pyodide)
- ERROR src/tests/test_jsproxy.py:2956:12-13: Could not find name `A` [unknown-name]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positive unknown-name for global assign w/o declaration

2 participants