Skip to content

fix Incorrect type inference when overriding zip and other builtins #2385#2533

Closed
asukaminato0721 wants to merge 3 commits intofacebook:mainfrom
asukaminato0721:2385
Closed

fix Incorrect type inference when overriding zip and other builtins #2385#2533
asukaminato0721 wants to merge 3 commits intofacebook:mainfrom
asukaminato0721:2385

Conversation

@asukaminato0721
Copy link
Contributor

Summary

Fixes #2385

Prevented builtins wildcard imports from shadowing existing definitions during static scope setup, so local overrides (like zip) no longer get merged with builtins.

Test Plan

Added a regression test to cover shadowing zip and using reversed.

@meta-cla meta-cla bot added the cla signed label Feb 24, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 24, 2026 16:17
Copilot AI review requested due to automatic review settings February 24, 2026 16:17
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes incorrect type inference when user code overrides builtins (e.g., zip) by ensuring implicit builtins wildcard imports do not shadow existing definitions during static scope initialization.

Changes:

  • Adjust static scope setup to treat builtins / __builtins__.pyi wildcard imports as fallback-only when names already exist.
  • Add a regression test covering zip shadowing combined with reversed(...) type inference.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
pyrefly/lib/binding/scope.rs Prevents builtins/extra_builtins wildcard imports from overwriting existing static-scope bindings.
pyrefly/lib/test/simple.rs Adds a regression test validating correct inference when zip is locally overridden.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@rchen152 rchen152 self-assigned this Feb 25, 2026
@meta-codesync
Copy link

meta-codesync bot commented Feb 25, 2026

@rchen152 has imported this pull request. If you are a Meta employee, you can view this in D94325319.

@fangyi-zhou
Copy link
Contributor

Presumably this PR also resolves #1197?

@rchen152
Copy link
Contributor

It does, thanks!

Copy link
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

Passing along some feedback from @grievejia:

What would happen if we use conditional overrides like this?

if cond():  # or while, try, with, etc.
    def zip(*args) -> list[int]:
        return []
    # Would pyrefly completely disregard the built-in zip here?

I'm a little concerned that unconditionally overwriting any built-in names might be too aggressive. What if the user actually wants to reuse the types or names from the built-ins?

@asukaminato0721
Copy link
Contributor Author

def f(cond):
      if cond:
          def zip(*args) -> list[int]:
              return []
      return zip([1], [2])
f(True)
f(False)
> python tmp.py 
Traceback (most recent call last):
  File "/tmp/tmp.py", line 7, in <module>
    f(False)
    ~^^^^^^^
  File "/tmp/tmp.py", line 5, in f
    return zip([1], [2])
           ^^^
UnboundLocalError: cannot access local variable 'zip' where it is not associated with a value

@asukaminato0721
Copy link
Contributor Author

Would pyrefly completely disregard the built-in zip here?

yes
with this change, once a name is defined anywhere in the scope (even conditionally), pyrefly will treat it as a local and not fall back to the builtin. That matches Python’s runtime name resolution.

Why this is correct:

  • In Python, any assignment/def to a name in a scope makes it local for the whole scope.
  • If the conditional branch doesn’t execute, the name is uninitialized, and using it raises UnboundLocalError rather than falling back to the builtin.

Copy link
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

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

@asukaminato0721 I think the behavior you're observing is specific to function-local names, and Jia's concern was about conditionally overwriting a builtin at the module level. Try running this script a bunch of times:

import random
x = random.randint(0, 1)
if x:
    def zip(*args):
        return [42, 42, 42]
def f():
    print(list(zip([1], [2])))
f()

Sometimes it prints [(1, 2)], and other times it prints [42, 42, 42], showing that when the local zip isn't defined, the Python runtime indeed falls back to the zip builtin. So it would be wrong for pyrefly to treat zip as unconditionally overwritten.

@github-actions

This comment has been minimized.

@asukaminato0721
Copy link
Contributor Author

add a test to show it.

function scope vs module scope.

@github-actions

This comment has been minimized.

@asukaminato0721
Copy link
Contributor Author

did some check...

update track whether a definition is conditional; propagate this through control‑flow constructs.

only skip builtins wildcard injection when a name is unconditionally defined; conditional defs still merge with builtins.

@github-actions

This comment has been minimized.

@github-actions
Copy link

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

urllib3 (https://github.com/urllib3/urllib3)
- ERROR src/urllib3/connectionpool.py:479:32-33: `BaseSSLError | CertificateError | NewConnectionError | OSError | urllib3.exceptions.SSLError | ssl.SSLError | urllib3.exceptions.TimeoutError | builtins.TimeoutError` is not assignable to `Exception` [bad-assignment]
+ ERROR src/urllib3/connectionpool.py:479:32-33: `BaseSSLError | CertificateError | NewConnectionError | OSError | urllib3.exceptions.SSLError | ssl.SSLError | TimeoutError` is not assignable to `Exception` [bad-assignment]
- ERROR src/urllib3/connectionpool.py:824:32-33: `BaseSSLError | CertificateError | HTTPException | OSError | ProtocolError | ProxyError | urllib3.exceptions.SSLError | ssl.SSLError | urllib3.exceptions.TimeoutError | builtins.TimeoutError` is not assignable to `Exception` [bad-assignment]
+ ERROR src/urllib3/connectionpool.py:824:32-33: `BaseSSLError | CertificateError | HTTPException | OSError | ProtocolError | ProxyError | urllib3.exceptions.SSLError | ssl.SSLError | TimeoutError` is not assignable to `Exception` [bad-assignment]

psycopg (https://github.com/psycopg/psycopg)
- ERROR tests/scripts/dectest.py:40:14-34: Cannot index into `list[str]` [bad-index]

colour (https://github.com/colour-science/colour)
- ERROR colour/continuous/signal.py:1089:46-76: No matching overload found for function `pow` called with arguments: (ndarray[tuple[Any, ...], dtype[float64 | floating[_16Bit] | floating[_32Bit]]], ndarray[tuple[Any, ...], dtype[float64 | floating[_16Bit] | floating[_32Bit]]]) [no-matching-overload]
- ERROR colour/io/luts/lut.py:638:34-55: No matching overload found for function `pow` called with arguments: (ndarray[tuple[Any, ...], dtype[float64 | floating[_16Bit] | floating[_32Bit]]], ndarray[tuple[Any, ...], dtype[float64 | floating[_16Bit] | floating[_32Bit]]]) [no-matching-overload]

bandersnatch (https://github.com/pypa/bandersnatch)
- ERROR src/bandersnatch/tests/test_utils.py:47:35-43: Unexpected keyword argument `function` in function `hash` [unexpected-keyword]
- ERROR src/bandersnatch/tests/test_utils.py:48:35-43: Unexpected keyword argument `function` in function `hash` [unexpected-keyword]
- ERROR src/bandersnatch/verify.py:187:64-68: Argument `((obj: object, /) -> int) | ((path: Path, function: str = 'sha256') -> str)` is not assignable to parameter `func` with type `(object) -> int` in function `asyncio.events.AbstractEventLoop.run_in_executor` [bad-argument-type]
+ ERROR src/bandersnatch/verify.py:187:53-79: Unpacked argument `tuple[Path]` is not assignable to parameter `*args` with type `tuple[Path, str]` in function `asyncio.events.AbstractEventLoop.run_in_executor` [bad-argument-type]

Expression (https://github.com/cognitedata/Expression)
- ERROR expression/collections/array.py:373:16-25: Returned type `int | _TSourceSum` is not assignable to declared return type `int` [bad-return]
- ERROR expression/collections/asyncseq.py:18:20-39: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Self@AsyncSeq, ((AsyncIterable[TSource]) -> AsyncIterable[TResult]) | map[@_]) [no-matching-overload]
- ERROR expression/collections/asyncseq.py:18:30-38: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (TSource) -> TResult) [no-matching-overload]
- ERROR expression/collections/block.py:781:12-24: Returned type `((Block[tuple[*_P]]) -> Block[_TResult]) | map[@_]` is not assignable to declared return type `(Block[tuple[*_P]]) -> Block[_TResult]` [bad-return]
- ERROR expression/collections/block.py:781:15-24: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (args: tuple[*_P]) -> _TResult) [no-matching-overload]
- ERROR expression/collections/maptree.py:350:17-27: No matching overload found for function `iter` called with arguments: ((Key, Value) -> None, Option[MapTreeLeaf[Key, Value]]) [no-matching-overload]
- ERROR expression/collections/maptree.py:352:17-28: No matching overload found for function `iter` called with arguments: ((Key, Value) -> None, Option[MapTreeLeaf[Key, Value]]) [no-matching-overload]
- ERROR expression/collections/maptree.py:400:21-30: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (Key, Value) -> Result, Option[MapTreeLeaf[Key, Value]]) [no-matching-overload]
- ERROR expression/collections/maptree.py:402:21-31: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (Key, Value) -> Result, Option[MapTreeLeaf[Key, Value]]) [no-matching-overload]
- ERROR expression/collections/maptree.py:403:46-48: Argument `Option[MapTreeLeaf[Key, Result]] | map[@_]` is not assignable to parameter `left` with type `Option[MapTreeLeaf[Key, Result]]` in function `MapTreeNode.__init__` [bad-argument-type]
- ERROR expression/collections/maptree.py:403:50-52: Argument `Option[MapTreeLeaf[Key, Result]] | map[@_]` is not assignable to parameter `right` with type `Option[MapTreeLeaf[Key, Result]]` in function `MapTreeNode.__init__` [bad-argument-type]
- ERROR expression/collections/seq.py:93:20-37: Expected a callable, got `filter[@_]` [not-callable]
- ERROR expression/collections/seq.py:93:26-37: No matching overload found for function `filter.__new__` called with arguments: (type[filter[_T]], (_TSource) -> bool) [no-matching-overload]
- ERROR expression/collections/seq.py:196:24-43: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Self@Seq, ((Iterable[_TSource]) -> Iterable[_TResult]) | map[@_]) [no-matching-overload]
- ERROR expression/collections/seq.py:196:34-42: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (_TSource) -> _TResult) [no-matching-overload]
- ERROR expression/collections/seq.py:291:16-25: Returned type `Literal[0] | _TSupportsSum` is not assignable to declared return type `_TSupportsSum` [bad-return]
- ERROR expression/collections/seq.py:717:12-24: Returned type `((Iterable[Any]) -> Iterable[Any]) | map[@_]` is not assignable to declared return type `(Iterable[Any]) -> Iterable[Any]` [bad-return]
- ERROR expression/collections/seq.py:717:15-24: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (args: tuple[Any, ...]) -> Any) [no-matching-overload]
- ERROR expression/collections/seq.py:879:12-46: Returned type `Literal[0] | _TSupportsSum` is not assignable to declared return type `_TSupportsSum` [bad-return]
- ERROR expression/extra/parser.py:63:21-29: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (_A) -> _B) [no-matching-overload]
- ERROR expression/extra/parser.py:64:20-34: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Self@Parser, ((parser: Parser[_A]) -> Parser[_B]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:212:16-215:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[Any], ((parser: Parser[tuple[Any, ...]]) -> Parser[Any]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:214:12-21: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (values: tuple[Any, ...]) -> Any) [no-matching-overload]
- ERROR expression/extra/parser.py:237:16-242:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[(_A) -> _B], (p1: Parser[(_A) -> _B]) -> Parser[tuple[(_A) -> _B, _A]], ((parser: Parser[tuple[(_A) -> _B, _A]]) -> Parser[_B]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:241:12-20: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (fx: tuple[(_A) -> _B, _A]) -> _B) [no-matching-overload]
- ERROR expression/extra/parser.py:271:16-277:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (str, [_TSource](xs: Iterable[_TSource]) -> Block[_TSource], (Block[str]) -> Block[Parser[str]], [_A](parser_list: Block[Parser[_A]]) -> Parser[Block[_A]], ((parser: Parser[Unknown]) -> Parser[Unknown]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:276:12-34: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (x: Unknown) -> Unknown) [no-matching-overload]
- ERROR expression/extra/parser.py:348:36-52: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[_A], ((parser: Parser[_A]) -> Parser[Option[_A]]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:348:43-51: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (a: _A) -> Option[_A]) [no-matching-overload]
- ERROR expression/extra/parser.py:374:16-378:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[_A], (p1: Parser[_A]) -> Parser[tuple[_A, Any]], ((parser: Parser[tuple[_A, Any]]) -> Parser[_A]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:377:12-20: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (value: tuple[_A, Any]) -> _A) [no-matching-overload]
- ERROR expression/extra/parser.py:399:16-403:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[Any], (p1: Parser[Any]) -> Parser[tuple[Any, _B]], ((parser: Parser[tuple[Any, _B]]) -> Parser[_B]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:402:12-20: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (value: tuple[Any, _B]) -> _B) [no-matching-overload]
- ERROR expression/extra/parser.py:523:16-526:6: No matching overload found for function `expression.core.pipe.pipe` called with arguments: (Parser[Any], ((parser: Parser[Any]) -> Parser[None]) | map[@_]) [no-matching-overload]
- ERROR expression/extra/parser.py:525:12-20: No matching overload found for function `map.__new__` called with arguments: (type[map[_S]], (_: Any) -> None) [no-matching-overload]
- ERROR tests/test_map.py:11:24-33: Class `map` has no class attribute `empty` [missing-attribute]
- ERROR tests/test_map.py:12:12-24: Class `map` has no class attribute `is_empty` [missing-attribute]
- ERROR tests/test_map.py:18:24-33: Class `map` has no class attribute `empty` [missing-attribute]
- ERROR tests/test_map.py:19:16-28: Class `map` has no class attribute `is_empty` [missing-attribute]
- ERROR tests/test_map.py:27:9-19: Class `map` has no class attribute `create` [missing-attribute]
- ERROR tests/test_map.py:34:9-19: Class `map` has no class attribute `of_seq` [missing-attribute]
- ERROR tests/test_map.py:41:10-20: Class `map` has no class attribute `of_seq` [missing-attribute]
- ERROR tests/test_map.py:48:10-21: Class `map` has no class attribute `of_list` [missing-attribute]
- ERROR tests/test_map.py:49:19-29: Class `map` has no class attribute `to_seq` [missing-attribute]
- ERROR tests/test_map.py:74:21-31: Class `map` has no class attribute `remove` [missing-attribute]
- ERROR tests/test_map.py:82:10-20: Class `map` has no class attribute `of_seq` [missing-attribute]
- ERROR tests/test_map.py:90:10-22: Class `map` has no class attribute `of_block` [missing-attribute]
- ERROR tests/test_map.py:100:10-22: Class `map` has no class attribute `of_block` [missing-attribute]
- ERROR tests/test_map.py:107:10-16: Class `map` has no class attribute `of` [missing-attribute]
- ERROR tests/test_map.py:109:18-25: Class `map` has no class attribute `map` [missing-attribute]
- ERROR tests/test_map.py:111:18-24: Class `map` has no class attribute `of` [missing-attribute]
- ERROR tests/test_map.py:116:25-31: Class `map` has no class attribute `of` [missing-attribute]
- ERROR tests/test_map.py:118:34-43: Class `map` has no class attribute `count` [missing-attribute]
- ERROR tests/test_map.py:123:22-28: Class `map` has no class attribute `of` [missing-attribute]

pyodide (https://github.com/pyodide/pyodide)
- ERROR src/py/webbrowser.py:31:13-34: No matching overload found for function `open` called with arguments: (str, int, bool) [no-matching-overload]
- ERROR src/py/webbrowser.py:49:16-24: No matching overload found for function `open` called with arguments: (str, Literal[1]) [no-matching-overload]
- ERROR src/py/webbrowser.py:53:16-24: No matching overload found for function `open` called with arguments: (str, Literal[2]) [no-matching-overload]

zulip (https://github.com/zulip/zulip)
- ERROR zerver/tests/test_link_embed.py:681:45-62: Argument `requests.exceptions.ConnectionError | builtins.ConnectionError` is not assignable to parameter `body` with type `requests.exceptions.ConnectionError | str | None` in function `PreviewTestCase.create_mock_response` [bad-argument-type]
- ERROR zerver/tests/test_link_embed.py:896:45-62: Argument `requests.exceptions.ConnectionError | builtins.ConnectionError` is not assignable to parameter `body` with type `requests.exceptions.ConnectionError | str | None` in function `PreviewTestCase.create_mock_response` [bad-argument-type]

asynq (https://github.com/quora/asynq)
- ERROR asynq/tests/test_base.py:39:14-29: No matching overload found for function `set.__init__` called with arguments: (Unknown, Unknown) [no-matching-overload]
- ERROR asynq/tests/test_base.py:46:20-32: No matching overload found for function `set.__init__` called with arguments: (Literal[0], Literal['value']) [no-matching-overload]
- ERROR asynq/tests/test_performance.py:53:14-29: No matching overload found for function `set.__init__` called with arguments: (Unknown, Unknown) [no-matching-overload]
- ERROR asynq/tests/test_performance.py:62:14-20: No matching overload found for function `set.__init__` called with arguments: (Literal[0], Literal[0]) [no-matching-overload]

operator (https://github.com/canonical/operator)
- ERROR ops/pebble.py:2255:21-38: Class `Warning` has no class attribute `from_dict` [missing-attribute]
+ ERROR ops/pebble.py:2255:20-66: Returned type `list[builtins.Warning]` is not assignable to declared return type `list[ops.pebble.Warning]` [bad-return]

pandera (https://github.com/pandera-dev/pandera)
+  WARN pandera/__init__.py:15:15-18:6: `__all__` could not be statically analyzed; falling back to module-level definitions for star imports [unresolvable-dunder-all]

streamlit (https://github.com/streamlit/streamlit)
- ERROR lib/streamlit/testing/v1/element_tree.py:2151:32-61: `streamlit.testing.v1.element_tree.Warning | builtins.Warning` is not assignable to variable `new_node` with type `Block | Element` [bad-assignment]
- ERROR lib/streamlit/testing/v1/element_tree.py:2151:51-55: Unexpected keyword argument `root` in function `BaseException.__init__` [unexpected-keyword]
- ERROR lib/streamlit/testing/v1/element_tree.py:2181:28-63: `streamlit.testing.v1.element_tree.Exception | builtins.Exception` is not assignable to variable `new_node` with type `Block | Element` [bad-assignment]
- ERROR lib/streamlit/testing/v1/element_tree.py:2181:53-57: Unexpected keyword argument `root` in function `BaseException.__init__` [unexpected-keyword]

setuptools (https://github.com/pypa/setuptools)
- ERROR setuptools/_vendor/backports/tarfile/__init__.py:2829:21-35: No matching overload found for function `open` called with arguments: (fileobj=Unknown) [no-matching-overload]

strawberry (https://github.com/strawberry-graphql/strawberry)
- ERROR strawberry/experimental/pydantic/object_type.py:325:16-334:6: No matching overload found for function `type.__new__` called with arguments: (type[type], model=type[PydanticModel], name=str | None, is_input=Literal[True], is_interface=bool, description=str | None, directives=Sequence[object] | None, all_fields=bool, use_pydantic_alias=bool) [no-matching-overload]
- ERROR strawberry/experimental/pydantic/object_type.py:353:16-362:6: No matching overload found for function `type.__new__` called with arguments: (type[type], model=type[PydanticModel], name=str | None, is_input=bool, is_interface=Literal[True], description=str | None, directives=Sequence[object] | None, all_fields=bool, use_pydantic_alias=bool) [no-matching-overload]

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- ERROR mitmproxy/tools/console/window.py:61:18-31: Object of class `_Helper` has no attribute `HelpView` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:30:14-26: Class `zip` has no class attribute `prettify` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:40:12-32: Class `zip` has no class attribute `syntax_highlight` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:48:9-21: Class `zip` has no class attribute `prettify` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:53:12-31: Class `zip` has no class attribute `render_priority` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:54:12-31: Class `zip` has no class attribute `render_priority` [missing-attribute]
- ERROR test/mitmproxy/contentviews/test__view_zip.py:55:12-31: Class `zip` has no class attribute `render_priority` [missing-attribute]

bokeh (https://github.com/bokeh/bokeh)
- ERROR src/bokeh/core/validation/check.py:198:70-89: Class `Warning` has no class attribute `get_by_code` [missing-attribute]
- ERROR src/bokeh/core/validation/decorators.py:76:25-43: Class `Warning` has no class attribute `get_by_name` [missing-attribute]
- ERROR src/bokeh/core/validation/decorators.py:80:29-47: Class `Warning` has no class attribute `get_by_code` [missing-attribute]
- ERROR src/bokeh/core/validation/issue.py:52:9-26: Class `Warning` has no class attribute `_code_map` [missing-attribute]
- ERROR src/bokeh/core/validation/issue.py:53:9-26: Class `Warning` has no class attribute `_name_map` [missing-attribute]
+ ERROR src/bokeh/core/validation/check.py:198:70-101: Argument `builtins.Warning` is not assignable to parameter `warning` with type `bokeh.core.validation.issue.Warning` in function `is_silenced` [bad-argument-type]
+ ERROR src/bokeh/core/validation/decorators.py:76:25-50: `Error | Warning` is not assignable to variable `issue` with type `Issue` [bad-assignment]
+ ERROR src/bokeh/core/validation/decorators.py:80:29-61: `Error | Warning` is not assignable to variable `issue` with type `Issue` [bad-assignment]
+ ERROR src/bokeh/core/validation/issue.py:52:40-44: Cannot set item in `dict[int, Warning]` [unsupported-operation]
+ ERROR src/bokeh/core/validation/issue.py:53:40-44: Cannot set item in `dict[str, Warning]` [unsupported-operation]

aioredis (https://github.com/aio-libs/aioredis)
- ERROR aioredis/client.py:1105:34-46: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/client.py:1105:34-46: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/client.py:4047:34-46: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/client.py:4047:34-46: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/client.py:4452:34-46: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/client.py:4452:34-46: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/client.py:4461:24-25: Expression `e` has type `aioredis.exceptions.ConnectionError | builtins.ConnectionError | aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` or `None` [bad-raise]
+ ERROR aioredis/client.py:4461:24-25: Expression `e` has type `ConnectionError | TimeoutError`, expected `BaseException` or `None` [bad-raise]
- ERROR aioredis/client.py:4473:38-50: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/client.py:4473:38-50: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/client.py:4648:34-46: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/client.py:4648:34-46: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/client.py:4656:24-25: Expression `e` has type `aioredis.exceptions.ConnectionError | builtins.ConnectionError | aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` or `None` [bad-raise]
+ ERROR aioredis/client.py:4656:24-25: Expression `e` has type `ConnectionError | TimeoutError`, expected `BaseException` or `None` [bad-raise]
- ERROR aioredis/connection.py:281:23-66: Expression `TimeoutError("Timeout reading from socket")` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+ ERROR aioredis/connection.py:281:23-66: Expression `TimeoutError("Timeout reading from socket")` has type `TimeoutError`, expected `BaseException` [bad-raise]
- ERROR aioredis/connection.py:509:23-66: Expression `TimeoutError("Timeout reading from socket")` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+ ERROR aioredis/connection.py:509:23-66: Expression `TimeoutError("Timeout reading from socket")` has type `TimeoutError`, expected `BaseException` [bad-raise]
- ERROR aioredis/connection.py:696:19-63: Expression `TimeoutError("Timeout connecting to server")` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+ ERROR aioredis/connection.py:696:19-63: Expression `TimeoutError("Timeout connecting to server")` has type `TimeoutError`, expected `BaseException` [bad-raise]
-             )` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+             )` has type `TimeoutError`, expected `BaseException` [bad-raise]
- ERROR aioredis/connection.py:826:38-50: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/connection.py:826:38-50: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/connection.py:835:37-40: Expression `err` has type `aioredis.exceptions.ConnectionError | builtins.ConnectionError | aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` or `None` [bad-raise]
+ ERROR aioredis/connection.py:835:37-40: Expression `err` has type `ConnectionError | TimeoutError`, expected `BaseException` or `None` [bad-raise]
- ERROR aioredis/connection.py:866:19-60: Expression `TimeoutError("Timeout writing to socket")` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+ ERROR aioredis/connection.py:866:19-60: Expression `TimeoutError("Timeout writing to socket")` has type `TimeoutError`, expected `BaseException` [bad-raise]
- ERROR aioredis/connection.py:903:19-80: Expression `TimeoutError(f"Timeout reading from {self.host}:{self.port}")` has type `aioredis.exceptions.TimeoutError | builtins.TimeoutError`, expected `BaseException` [bad-raise]
+ ERROR aioredis/connection.py:903:19-80: Expression `TimeoutError(f"Timeout reading from {self.host}:{self.port}")` has type `TimeoutError`, expected `BaseException` [bad-raise]
- ERROR aioredis/sentinel.py:223:38-50: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/sentinel.py:223:38-50: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
- ERROR aioredis/sentinel.py:253:53-65: Invalid exception class: `aioredis.exceptions.TimeoutError | builtins.TimeoutError` does not inherit from `BaseException` [invalid-inheritance]
+ ERROR aioredis/sentinel.py:253:53-65: Invalid exception class: `TimeoutError` does not inherit from `BaseException` [invalid-inheritance]

prefect (https://github.com/PrefectHQ/prefect)
- ERROR src/prefect/task_worker.py:518:41-520:10: No matching overload found for function `BaseExceptionGroup.split` called with arguments: (tuple[type[StopTaskWorker], type[CancelledError], type[KeyboardInterrupt], type[TimeoutError]]) [no-matching-overload]

pip (https://github.com/pypa/pip)
- ERROR src/pip/_vendor/pygments/__init__.py:82:48-55: Expected 2 positional arguments, got 3 in function `format` [bad-argument-count]
- ERROR src/pip/_vendor/requests/adapters.py:659:40-47: Unexpected keyword argument `request` in function `BaseException.__init__` [unexpected-keyword]
- ERROR src/pip/_vendor/requests/adapters.py:677:38-45: Unexpected keyword argument `request` in function `BaseException.__init__` [unexpected-keyword]
- ERROR src/pip/_vendor/requests/adapters.py:680:38-45: Unexpected keyword argument `request` in function `BaseException.__init__` [unexpected-keyword]

jax (https://github.com/google/jax)
-   (a: _SupportsShape[Never]) -> tuple[Any, ...]
-   [_ShapeT: tuple[int, ...]](a: _SupportsShape[_ShapeT]) -> _ShapeT
-   (a: _PyScalar) -> tuple[()]
-   (a: _PyArray) -> tuple[int]
-   (a: _PyArray) -> tuple[int, int]
-   (a: bytearray | memoryview[int]) -> tuple[int]
-   (a: ArrayLike) -> tuple[Any, ...]
- ], dict_values[ErrorEffect, Bool]) [no-matching-overload]
- ERROR jax/_src/checkify.py:230:15-46: No matching overload found for function `map.__new__` called with arguments: (type[map[object]], Overload[
- ERROR jax/_src/interpreters/mlir.py:2148:65-74: Argument `list[Unknown] | map[Unknown]` is not assignable to parameter `avals_out` with type `Sequence[AbstractValue]` in function `_emit_lowering_rule_as_fun` [bad-argument-type]
- ERROR jax/_src/interpreters/remat.py:142:36-46: Argument `list[Unknown] | map[Unknown]` is not assignable to parameter `obj` with type `Sized` in function `len` [bad-argument-type]
- ERROR jax/experimental/jax2tf/call_tf.py:191:15-194:53: Argument `list[tuple[Any, ShapedArray]] | list[tuple[Any, None]] | zip[tuple[Any, ShapedArray]] | zip[tuple[Any, None]]` is not assignable to parameter `iterable` with type `Iterable[tuple[Any, ShapedArray]]` in function `enumerate.__new__` [bad-argument-type]
+ ERROR jax/experimental/jax2tf/call_tf.py:191:15-194:53: Argument `list[tuple[Any, ShapedArray]] | list[tuple[Any, None]]` is not assignable to parameter `iterable` with type `Iterable[tuple[Any, ShapedArray]]` in function `enumerate.__new__` [bad-argument-type]
- ERROR jax/experimental/roofline/roofline.py:221:22-43: Argument `list[AbstractValue] | map[AbstractValue]` is not assignable to parameter `avals_in` with type `Sequence[AbstractValue]` in function `RooflineRuleContext.__init__` [bad-argument-type]
- ERROR jax/experimental/roofline/roofline.py:222:23-45: Argument `list[AbstractValue] | map[AbstractValue]` is not assignable to parameter `avals_out` with type `Sequence[AbstractValue]` in function `RooflineRuleContext.__init__` [bad-argument-type]
- ERROR jax/experimental/roofline/roofline.py:234:38-51: Argument `list[RooflineShape] | map[RooflineShape]` is not assignable to parameter `shapes` with type `Sequence[RooflineShape]` in function `sum_bytes` [bad-argument-type]

materialize (https://github.com/MaterializeInc/materialize)
- ERROR test/aws/mzcompose.py:160:17-23: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:161:48-54: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:176:17-23: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:177:47-53: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:201:16-22: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:201:61-67: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:222:20-26: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:228:17-23: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:228:64-70: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:231:17-23: Object of class `SystemError` has no attribute `diag` [missing-attribute]
- ERROR test/aws/mzcompose.py:233:20-26: Object of class `SystemError` has no attribute `diag` [missing-attribute]

sphinx (https://github.com/sphinx-doc/sphinx)
- ERROR sphinx/directives/admonitions.py:100:34-41: Argument `type[sphinx.directives.admonitions.Warning] | type[builtins.Warning]` is not assignable to parameter `cls` with type `type[Directive]` in function `sphinx.application.Sphinx.add_directive` [bad-argument-type]

pywin32 (https://github.com/mhammond/pywin32)
- ERROR Pythonwin/pywin/Demos/ocx/msoffice.py:16:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/Demos/ocx/msoffice.py:53:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/framework/intpydde.py:25:9-22: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR Pythonwin/pywin/framework/intpydde.py:42:9-22: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/docview.py:56:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/docview.py:61:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/docview.py:85:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/docview.py:98:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/thread.py:12:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/mfc/window.py:10:9-25: Class `object` has no class attribute `CmdTarget` [missing-attribute]
- ERROR Pythonwin/pywin/tools/hierlist.py:75:20-33: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR win32/Demos/dde/ddeserver.py:10:9-22: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR win32/Demos/dde/ddeserver.py:18:9-22: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR win32/Demos/dde/ddeserver.py:28:9-22: Class `object` has no class attribute `Object` [missing-attribute]
- ERROR win32/Demos/desktopmanager.py:84:5-12: `+=` is not supported between `(obj: object, /) -> int` and `Literal[1]` [unsupported-operation]

@github-actions
Copy link

Primer Diff Classification

❌ 4 regression(s) | ✅ 16 improvement(s) | ➖ 2 neutral | 22 project(s) total

4 regression(s) across bandersnatch, pandera, bokeh, jax. error kinds: bad-argument-type, unexpected-keyword, unresolvable-dunder-all. caused by from_definitions(). 16 improvement(s) across psycopg, colour, Expression, pyodide, zulip, asynq, operator, streamlit, setuptools, strawberry, mitmproxy, prefect, pip, materialize, sphinx, pywin32.

Project Verdict Changes Error Kinds Root Cause
urllib3 ➖ Neutral +2, -2 bad-assignment
psycopg ✅ Improvement -1 bad-index pyrefly/lib/binding/scope.rs
colour ✅ Improvement -2 no-matching-overload pyrefly/lib/binding/scope.rs
bandersnatch ❌ Regression +1, -3 bad-argument-type, unexpected-keyword from_definitions()
Expression ✅ Improvement -54 bad-argument-type, bad-return overload_resolution()
pyodide ✅ Improvement -3 no-matching-overload pyrefly/lib/binding/scope.rs
zulip ✅ Improvement -2 bad-argument-type pyrefly/lib/binding/scope.rs
asynq ✅ Improvement -4 no-matching-overload pyrefly/lib/binding/scope.rs
operator ✅ Improvement +1, -1 bad-return, missing-attribute pyrefly/lib/binding/scope.rs
pandera ❌ Regression +1 unresolvable-dunder-all pyrefly/lib/export/definitions.rs
streamlit ✅ Improvement -4 bad-assignment, unexpected-keyword of_definitions()
setuptools ✅ Improvement -1 no-matching-overload from_definitions()
strawberry ✅ Improvement -2 no-matching-overload pyrefly/lib/binding/scope.rs
mitmproxy ✅ Improvement -7 missing-attribute from_definitions()
bokeh ❌ Regression +5, -5 bad-argument-type, bad-assignment pyrefly/lib/binding/scope.rs
aioredis ➖ Neutral +16, -16 bad-raise, invalid-inheritance
prefect ✅ Improvement -1 no-matching-overload pyrefly/lib/binding/scope.rs
pip ✅ Improvement -4 bad-argument-count, unexpected-keyword from_definitions()
jax ❌ Regression +1, -6 bad-argument-type pyrefly/lib/binding/scope.rs
materialize ✅ Improvement -11 missing-attribute pyrefly/lib/binding/scope.rs
sphinx ✅ Improvement -1 bad-argument-type pyrefly/lib/binding/scope.rs
pywin32 ✅ Improvement -15 missing-attribute, unsupported-operation pyrefly/lib/binding/scope.rs
Detailed analysis

❌ Regression (4)

bandersnatch (+1, -3)

This is a mixed case. The removed errors were false positives (improvement) - pyrefly was incorrectly merging builtin hash with the custom hash function, causing wrong 'unexpected-keyword' errors in tests that were actually correct. However, the new error appears to be a regression - pyrefly should now cleanly resolve to just the custom hash function, but it's still showing a union type that includes the builtin hash signature. The shadowing fix worked partially but not completely, leaving some type inference issues.
Attribution: The changes to pyrefly/lib/binding/scope.rs in the Static::[from_definitions()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/binding/scope.rs) method added logic to prevent builtins from shadowing unconditional local definitions. The skip_existing logic specifically prevents builtin imports from overriding local definitions when the module is builtins or extra_builtins.

pandera (+1)

This is a regression. The warning is technically correct - pyrefly cannot statically resolve __all__ due to the spread of *_pandas_deprecated_all from an imported module. However, this creates noise without practical value. The code is correct and works fine at runtime. Mypy and pyright handle this pattern silently by falling back to module-level definitions, which is the appropriate behavior. Pyrefly being more vocal about static analysis limitations than established tools creates false positive noise.
Attribution: The changes to conditional definition tracking in pyrefly/lib/export/definitions.rs now mark definitions inside try blocks as conditional, which affects how __all__ analysis works. The __all__ definition is inside a try block (lines 5-18), so it's now treated as conditional, making static analysis more conservative.

bokeh (+5, -5)

This is a regression. The PR fixed one name resolution issue (builtins shadowing local overrides) but introduced another - now pyrefly incorrectly resolves imported names to builtins when they shouldn't be. The removed errors were false positives (the Warning class does have get_by_code), but the new errors are also false positives (the code correctly uses the imported Warning, not builtins.Warning). The net effect is that pyrefly's name resolution got worse - it now fails to distinguish between imported classes and builtin classes with the same name.
Attribution: The change to Static::setup() in pyrefly/lib/binding/scope.rs now prevents builtins from shadowing local definitions, but this appears to have overcorrected - now pyrefly is failing to properly resolve the imported Warning class and is defaulting to builtins.Warning instead

jax (+1, -6)

This is a regression. The new error incorrectly flags a valid type pattern where a local zip function should override the builtin. In the source code at line 191, there's a local definition zip = util.safe_zip (line 56) that should take precedence over the builtin zip. The error claims the argument list[tuple[Any, ShapedArray]] | list[tuple[Any, None]] doesn't match Iterable[tuple[Any, ShapedArray]], but this is wrong - both list[tuple[Any, ShapedArray]] and list[tuple[Any, None]] are valid Iterable types. The removed errors were clearly false positives showing Unknown types, indicating inference failures that have been correctly fixed. The PR improved builtin shadowing logic, but introduced a new false positive where the type checker fails to recognize that the local zip override should be used instead of the builtin enumerate.__new__ signature.
Attribution: The change to Static::setup() in pyrefly/lib/binding/scope.rs modified how builtin wildcard imports are handled during static scope setup. The new logic prevents builtins from shadowing existing unconditional definitions by checking skip_existing for builtin modules and skipping names that already exist in unconditional_defs. This fixed the inference of locally overridden builtins like zip.

✅ Improvement (16)

psycopg (-1)

This was a false positive caused by incorrect scope resolution. The local assignment format = psycopg.pq.Format.BINARY on line 13 should shadow the builtin format() function, making format refer to the enum value (which is a valid integer index) rather than the builtin function. The PR correctly fixed this shadowing behavior, removing the false positive error.
Attribution: The changes to scope resolution in pyrefly/lib/binding/scope.rs (specifically the logic to prevent builtins from shadowing unconditional local definitions) and the conditional definition tracking in pyrefly/lib/export/definitions.rs fixed the variable shadowing issue. Now format = psycopg.pq.Format.BINARY properly shadows the builtin format() function.

colour (-2)

The removed errors were false positives. Looking at the source code, both files import pow from the operator module on line 18/23 respectively. The errors claimed no matching overload for pow(ndarray, ndarray), but this is incorrect - numpy arrays support element-wise power operations through their __pow__ method. The PR fixed builtin shadowing behavior that was incorrectly interfering with the imported pow function's type resolution. The pow function from operator module works correctly with numpy arrays, making these legitimate operations that should not produce errors.
Attribution: The change to builtin shadowing logic in pyrefly/lib/binding/scope.rs fixed the false positive no-matching-overload errors. The PR prevents builtins wildcard imports from shadowing existing unconditional definitions, which corrected the type resolution for the pow function imported from the operator module.

Expression (-54)

This is an improvement. The PR fixed a fundamental name resolution bug where pyrefly incorrectly allowed builtin imports to shadow local function definitions. All the removed errors were false positives caused by this bug - pyrefly was trying to type-check local functions against builtin signatures they were never meant to match. For example, a local sum method that calls sum(self) should resolve the call to builtin sum(), not recursively to itself. The fix correctly implements Python's scoping rules where local definitions take precedence over builtins.
Attribution: The change to overload_resolution() and scope handling in pyrefly/lib/binding/scope.rs fixed the name resolution bug where builtins were incorrectly shadowing unconditional local definitions, eliminating the false positive type errors.

pyodide (-3)

These were false positive errors caused by incorrect scope resolution. Pyrefly was merging the local open(url: str, new: int = 0, autoraise: bool = True) -> None function with the builtin open function (for file I/O), creating a confusing overload set. The local function signature perfectly matches all three call sites: open(url, new, autoraise) with (str, int, bool), open(url, 1) with (str, Literal[1]), and open(url, 2) with (str, Literal[2]). Standard Python scoping rules dictate that local definitions should shadow builtins, so these calls should resolve to the local function only. The fix correctly implements this behavior by preventing builtins from shadowing unconditional local definitions during static scope setup.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs now prevents builtins from shadowing unconditional local definitions. The skip_existing logic ensures that when a local open function is defined, the builtin open doesn't interfere with overload resolution.

zulip (-2)

The removed errors were false positives caused by incorrect type inference. The code explicitly imports requests.exceptions.ConnectionError on line 11, so ConnectionError() should resolve to that type, not a union with builtins.ConnectionError. The parameter type requests.exceptions.ConnectionError | str | None correctly accepts a requests.exceptions.ConnectionError instance. Pyrefly's scope resolution was incorrectly merging the explicit import with the builtin, creating a union type that didn't match the parameter annotation. The PR fixed this by ensuring explicit imports take precedence over builtins.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs that prevents builtins from shadowing explicit imports fixed the incorrect type inference that was creating the union type requests.exceptions.ConnectionError | builtins.ConnectionError

asynq (-4)

These were false positives caused by incorrect name resolution. The code defines a user function set(key, value) that takes two parameters, but pyrefly was incorrectly resolving calls to this function as calls to the builtin set() constructor (which takes at most one argument). The PR fixed the scope resolution to properly prioritize local definitions over builtins, so the calls now correctly resolve to the user-defined function. Removing these false positive overload errors is an improvement.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs that prevents builtins from shadowing unconditional local definitions. Specifically, the addition of skip_existing logic that skips builtin names when an unconditional definition already exists.

operator (+1, -1)

This is an improvement. The PR fixed a name resolution bug where pyrefly was incorrectly resolving Warning to the builtin class instead of the locally defined class. The new bad-return error is correct - the function returns list[builtins.Warning] (from the builtin) but should return list[ops.pebble.Warning] (the local class). However, this appears to be a cascade effect from the same underlying fix. The removed missing-attribute error was a false positive that occurred because pyrefly was looking for from_dict on the wrong Warning class. The local Warning class at line 603 does have a from_dict classmethod at line 623, so the missing-attribute error was incorrect.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs fixed how builtins wildcard imports interact with local definitions. The new logic prevents builtins from shadowing unconditional local definitions by checking unconditional_defs.contains(*name.key()) and skipping builtin names that conflict with local definitions.

streamlit (-4)

The removed errors were false positives caused by incorrect type inference. The PR fixed a bug where builtins wildcard imports were shadowing local class definitions. Before the fix, pyrefly was incorrectly merging the local Warning/Exception classes with built-in types, causing it to misunderstand their constructor signatures. The local Warning class (line 286) and Exception class (line 564) have proper constructors that accept 'root' parameters, but the type checker was incorrectly applying built-in BaseException constraints. After the fix, pyrefly correctly recognizes these as distinct local classes with their own valid constructor signatures.
Attribution: The change to pyrefly/lib/binding/scope.rs in the Static::[of_definitions()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/binding/scope.rs) method prevented builtins wildcard imports from shadowing existing definitions during static scope setup. This fixed the type inference for local overrides like the Warning and Exception classes, allowing pyrefly to correctly resolve their constructor signatures instead of incorrectly merging them with built-in types.

setuptools (-1)

This is an improvement. The error was incorrectly flagging a valid call to the tarfile module's open function. The code on line 2829 calls open(fileobj=name) where open refers to TarFile.open (assigned on line 2838), not the builtin open. The TarFile.open method accepts a fileobj parameter of type file-like object, which matches the name parameter that has a read() method (line 2827 checks hasattr(name, "read")). The PR fixed the type checker's scope resolution to properly handle cases where local definitions should shadow builtins, preventing this false positive.
Attribution: The change to Static::[from_definitions()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/binding/scope.rs) in pyrefly/lib/binding/scope.rs fixed how builtin wildcard imports interact with local definitions. The new logic prevents builtins from shadowing unconditional local definitions by checking unconditional_defs.contains(*name.key()) before adding builtin names from wildcard imports.

strawberry (-2)

These were false positive errors caused by incorrect name resolution. Pyrefly was resolving type to the builtin type constructor instead of the locally defined type() function at line 119. The local function accepts the parameters being passed (including optional include_computed), but the builtin type.__new__ does not. The PR fixed the scope resolution to correctly prioritize local definitions over builtins, eliminating these false positives.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs that prevents builtins from shadowing unconditional local definitions fixed the name resolution issue. Previously, the builtin type was incorrectly shadowing the locally defined type() function.

mitmproxy (-7)

This is an improvement. The PR fixed a type inference bug where pyrefly incorrectly merged local definitions with builtins during wildcard import processing. Before the fix, when code imported a module named help or zip, pyrefly would incorrectly treat references to these names as referring to the builtin help() function or zip() class instead of the imported modules. The removed errors were false positives claiming that builtin types like zip and _Helper were missing attributes that don't exist on builtins but do exist on the actual imported modules. The fix ensures that unconditional local definitions (like help=help.HelpView(master) and imports like from mitmproxy.contentviews._view_zip import zip) properly shadow builtins, which is the correct Python scoping behavior.
Attribution: The change to Static::[from_definitions()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/binding/scope.rs) in pyrefly/lib/binding/scope.rs fixed the builtin shadowing issue. The code now tracks unconditional definitions and prevents builtins wildcard imports from shadowing existing local definitions when skip_existing is true for builtins modules. This ensures that local imports like help and zip are not incorrectly merged with builtin names.

prefect (-1)

The removed error was a false positive. The code correctly calls BaseExceptionGroup.split() with a tuple of exception types (StopTaskWorker, asyncio.CancelledError, KeyboardInterrupt, TimeoutError), which is the standard usage pattern for this method. The PR's fix to prevent builtins from incorrectly shadowing imported definitions likely resolved incorrect type information that was causing the overload resolution to fail. This is an improvement - pyrefly now correctly understands the BaseExceptionGroup.split method signature.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs that prevents builtins from shadowing local definitions likely fixed incorrect type merging that was causing the overload resolution to fail

pip (-4)

These errors were false positives caused by pyrefly incorrectly merging builtin function signatures with local overrides. In Python, local definitions should completely shadow builtins. The format() function in pygments is intentionally overriding the builtin (note the pylint disable comment), and the ConnectionError in requests is a custom exception class that accepts different parameters than the builtin. Removing these false positives is an improvement - pyrefly now correctly handles builtin shadowing per Python's name resolution rules.
Attribution: The changes to Static::[from_definitions()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/binding/scope.rs) in pyrefly/lib/binding/scope.rs now prevent builtin wildcard imports from shadowing existing unconditional definitions. The conditional tracking in pyrefly/lib/export/definitions.rs ensures only truly unconditional local definitions can shadow builtins.

materialize (-11)

The removed errors were false positives caused by incorrect import resolution. The code imports SystemError from psycopg.errors on line 23, and this class does have a diag attribute with message_primary, message_detail, and message_hint fields as used throughout the code. Pyrefly was incorrectly merging this with the builtin SystemError exception class (which lacks these attributes), creating a union type that couldn't be properly resolved. The PR fixed the scope resolution to prevent builtins from shadowing explicit imports, allowing pyrefly to correctly identify that SystemError refers to the psycopg version with the required attributes.
Attribution: The changes to scope handling in pyrefly/lib/binding/scope.rs fixed the import resolution issue. Specifically, the logic that prevents builtin wildcard imports from shadowing existing definitions (lines with skip_existing check) ensures that the imported SystemError from psycopg.errors is not merged with the builtin SystemError.

sphinx (-1)

This was a false positive caused by incorrect name resolution. The local Warning class (lines 80-81) should shadow the builtin Warning exception, but pyrefly was incorrectly merging them into a union type. The local class inherits from SphinxDirective and is a valid Directive, so the error was wrong. The PR fixed the scope resolution to properly handle shadowing of builtins by local definitions.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs now prevents builtin wildcard imports from shadowing unconditional local definitions, fixing the incorrect type inference that was merging local and builtin symbols with the same name.

pywin32 (-15)

The removed errors were false positives caused by incorrect import resolution. The code imports object from pywin.mfc on line 10, which should define a CmdTarget class. However, pyrefly was incorrectly allowing the builtin object type to shadow this import, causing it to look for CmdTarget on the wrong object. The PR fixed this by preventing builtins wildcard imports from overriding explicit local definitions, which aligns with Python's standard scoping rules where explicit imports take precedence.
Attribution: The change to scope resolution in pyrefly/lib/binding/scope.rs where skip_existing logic was added to prevent builtins wildcard imports from shadowing unconditional local definitions. Specifically, the code now checks if skip_existing && unconditional_defs.contains(*name.key()) before adding wildcard imports, preventing builtins from overriding explicit imports like from pywin.mfc import object.

➖ Neutral (2)

urllib3 (+2, -2)

Same errors at same locations with same error kinds — message wording changed, no behavioral impact.

aioredis (+16, -16)

Same errors at same locations with same error kinds — message wording changed, no behavioral impact.

Suggested Fix

Summary: The PR fixed builtin shadowing issues but introduced regressions where imported names are incorrectly resolved to builtins instead of their proper imports.

1. In Static::from_definitions() in pyrefly/lib/binding/scope.rs, modify the skip_existing logic to only apply to wildcard imports from builtins modules, not to explicit imports. Change the condition from checking if the module is builtins to checking if it's a wildcard import AND the module is builtins. This prevents the fix from interfering with explicit imports like 'from bokeh.util.warnings import Warning'.

Files: pyrefly/lib/binding/scope.rs
Confidence: high
Affected projects: bokeh
Fixes: bad-assignment
The current logic prevents builtins from shadowing ANY unconditional definitions, but it should only prevent builtins from shadowing definitions when they come from wildcard imports. Explicit imports should always take precedence. This eliminates 5 bad-assignment errors in bokeh where imported Warning class is incorrectly resolved to builtins.Warning.

2. In Static::from_definitions() in pyrefly/lib/binding/scope.rs, add a guard to check if the local definition is actually a function override before applying the skip_existing logic for builtin functions. When a local function like 'zip = util.safe_zip' assigns a different function to override a builtin, the type checker should use the assigned function's signature, not treat it as a name conflict.

Files: pyrefly/lib/binding/scope.rs
Confidence: medium
Affected projects: jax
Fixes: no-matching-overload
The jax regression shows that when 'zip = util.safe_zip' creates a local override, the type checker incorrectly applies builtin zip constraints instead of using the safe_zip function signature. Adding logic to detect function assignments would fix this specific case.

3. In the conditional tracking logic in pyrefly/lib/export/definitions.rs, modify the all analysis to be less strict about conditional definitions inside try blocks. When all contains imported names that can be statically resolved (even if the import itself is in a try block), don't emit warnings about static analysis limitations.

Files: pyrefly/lib/export/definitions.rs
Confidence: low
Affected projects: pandera
Fixes: warning
The pandera regression shows a warning about all static analysis that other type checkers handle silently. However, this might be intentional behavior to be more explicit about analysis limitations, so confidence is low.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (2 heuristic, 20 LLM)

Copy link
Contributor

@kinto0 kinto0 left a comment

Choose a reason for hiding this comment

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

Review automatically exported from Phabricator review in Meta.

@meta-codesync
Copy link

meta-codesync bot commented Mar 4, 2026

@rchen152 merged this pull request in 95c42a1.

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.

Incorrect type inference when overriding zip and other builtins

6 participants