Skip to content

fix in check does not narrow TypedDict Union #3079#3082

Open
asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
asukaminato0721:3079
Open

fix in check does not narrow TypedDict Union #3079#3082
asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
asukaminato0721:3079

Conversation

@asukaminato0721
Copy link
Copy Markdown
Contributor

@asukaminato0721 asukaminato0721 commented Apr 9, 2026

Summary

Fixes #3079

The solver now narrows the subject type itself for "key" in x / "key" not in x when x is a TypedDict union, instead of only recording a key facet, so Foo | Bar now becomes Foo in the positive branch and Bar in the negative branch.

Test Plan

add test

@meta-cla meta-cla bot added the cla signed label Apr 9, 2026
@github-actions github-actions bot added the size/m label Apr 9, 2026
@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review April 9, 2026 03:59
Copilot AI review requested due to automatic review settings April 9, 2026 03:59
@asukaminato0721 asukaminato0721 changed the title fix fix in check does not narrow TypedDict Union #3079 Apr 9, 2026
Copy link
Copy Markdown

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 TypedDict-union narrowing when guarding with a string-literal key membership check (e.g., if "a" in foo:), addressing #3079.

Changes:

  • Added a regression test asserting that "a" in (Foo | Bar) narrows the subject to Foo (and the else branch to Bar).
  • Updated the narrowing solver to narrow union members for HasKey / NotHasKey by filtering TypedDict union members based on key presence/requiredness, while preserving dict-like facet behavior.

Reviewed changes

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

File Description
pyrefly/lib/test/typed_dict.rs Adds regression test coverage for subject-type narrowing on TypedDict unions via "key" in obj.
pyrefly/lib/alt/narrow.rs Implements key-membership-based narrowing across union members (esp. TypedDict) and extends dict-like detection to unions for facet handling.

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

@github-actions

This comment has been minimized.

@rchen152
Copy link
Copy Markdown
Contributor

rchen152 commented Apr 9, 2026

There's some discussion about how to fix this in #2199. In particularly, we can't naively narrow the TypedDict union because a non-closed TypedDict can have extra keys we can't see via subtyping.

@github-actions github-actions bot added size/m and removed size/m labels Apr 9, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

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

openlibrary (https://github.com/internetarchive/openlibrary)
- ERROR openlibrary/plugins/ol_infobase.py:543:63-88: Cannot index into `list[dict[Unknown, dict[Unknown, Unknown] | list[Unknown] | Unknown] | list[dict[Unknown, Unknown] | list[Unknown] | Unknown] | Unknown]` [bad-index]
- ERROR openlibrary/plugins/openlibrary/lists.py:105:25-30: TypedDict `AnnotatedSeedDict` does not have key `key` [bad-typed-dict-key]
- ERROR openlibrary/plugins/openlibrary/lists.py:108:21-26: TypedDict `AnnotatedSeedDict` does not have key `key` [bad-typed-dict-key]

cloud-init (https://github.com/canonical/cloud-init)
- ERROR cloudinit/sources/DataSourceNoCloud.py:172:24-55: Cannot index into `str` [bad-index]
- ERROR cloudinit/sources/DataSourceNoCloud.py:172:24-55: `None` is not subscriptable [unsupported-operation]
- ERROR cloudinit/sources/DataSourceVMware.py:1001:26-27: Invalid key for TypedDict `Interface`, got `Literal[0]` [bad-typed-dict-key]
- ERROR cloudinit/sources/DataSourceVMware.py:1009:26-27: Invalid key for TypedDict `Interface`, got `Literal[0]` [bad-typed-dict-key]
- ERROR tests/unittests/sources/test_opennebula.py:159:39-73: `None` is not subscriptable [unsupported-operation]
- ERROR tests/unittests/sources/test_opennebula.py:214:37-74: `None` is not subscriptable [unsupported-operation]

mitmproxy (https://github.com/mitmproxy/mitmproxy)
- ERROR docs/src/examples/contrib/webscanner_helper/test_urldict.py:48:36-48: Cannot index into `islice[Any]` [bad-index]
- ERROR docs/src/examples/contrib/webscanner_helper/test_urldict.py:49:27-39: Cannot index into `islice[Any]` [bad-index]
- ERROR docs/src/examples/contrib/webscanner_helper/test_urldict.py:50:37-49: Cannot index into `islice[Any]` [bad-index]
- ERROR examples/contrib/webscanner_helper/test_urldict.py:48:36-48: Cannot index into `islice[Any]` [bad-index]
- ERROR examples/contrib/webscanner_helper/test_urldict.py:49:27-39: Cannot index into `islice[Any]` [bad-index]
- ERROR examples/contrib/webscanner_helper/test_urldict.py:50:37-49: Cannot index into `islice[Any]` [bad-index]

cwltool (https://github.com/common-workflow-language/cwltool)
- ERROR cwltool/command_line_tool.py:709:30-37: TypedDict `CWLDirectoryType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:709:30-37: TypedDict `CWLFileType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:710:27-38: TypedDict `CWLDirectoryType` does not have key `entryname` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:710:27-38: TypedDict `CWLFileType` does not have key `entryname` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:739:71-78: TypedDict `CWLDirectoryType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:739:71-78: TypedDict `CWLFileType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:741:50-61: TypedDict `CWLDirectoryType` does not have key `entryname` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:741:50-61: TypedDict `CWLFileType` does not have key `entryname` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:743:20-27: TypedDict `CWLDirectoryType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:743:20-27: TypedDict `CWLFileType` does not have key `entry` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:757:44-54: TypedDict `DirentType` does not have key `basename` [bad-typed-dict-key]
- ERROR cwltool/command_line_tool.py:758:16-26: TypedDict `DirentType` does not have key `basename` [bad-typed-dict-key]
- ERROR cwltool/main.py:232:32-41: TypedDict `CWLDirectoryType` does not have key `default` [bad-typed-dict-key]
- ERROR cwltool/main.py:232:32-41: TypedDict `CWLFileType` does not have key `default` [bad-typed-dict-key]
- ERROR cwltool/main.py:241:59-65: TypedDict `CWLDirectoryType` does not have key `name` [bad-typed-dict-key]
- ERROR cwltool/main.py:241:59-65: TypedDict `CWLFileType` does not have key `name` [bad-typed-dict-key]

scikit-learn (https://github.com/scikit-learn/scikit-learn)
- ERROR sklearn/tree/_export.py:270:13-24: Object of class `_BaseTreeExporter` has no attribute `colors` [missing-attribute]
- ERROR sklearn/tree/_export.py:278:17-28: Object of class `_BaseTreeExporter` has no attribute `colors` [missing-attribute]
- ERROR sklearn/tree/_export.py:281:17-28: Object of class `_BaseTreeExporter` has no attribute `colors` [missing-attribute]
- ERROR sklearn/tree/_export.py:287:21-32: Object of class `_BaseTreeExporter` has no attribute `colors` [missing-attribute]
+ ERROR sklearn/tree/_export.py:270:13-31: Cannot set item in `object` [unsupported-operation]
+ ERROR sklearn/tree/_export.py:278:17-38: Cannot set item in `object` [unsupported-operation]
+ ERROR sklearn/tree/_export.py:281:17-38: Cannot set item in `object` [unsupported-operation]
+ ERROR sklearn/tree/_export.py:287:21-42: Cannot index into `object` [bad-index]

pwndbg (https://github.com/pwndbg/pwndbg)
- ERROR pwndbg/emu/emulator.py:481:51-63: Object of class `NoneType` has no attribute `objfile` [missing-attribute]
+ ERROR pwndbg/emu/emulator.py:481:40-63: `in` is not supported between `Literal['[heap']` and `object` [not-iterable]
- ERROR pwndbg/emu/emulator.py:525:34-46: Object of class `NoneType` has no attribute `objfile` [missing-attribute]
+ ERROR pwndbg/emu/emulator.py:525:22-46: `in` is not supported between `Literal['[stack']` and `object` [not-iterable]
- ERROR pwndbg/enhance.py:91:47-59: Object of class `NoneType` has no attribute `objfile` [missing-attribute]
+ ERROR pwndbg/enhance.py:91:36-59: `in` is not supported between `Literal['[heap']` and `object` [not-iterable]
- ERROR pwndbg/enhance.py:124:30-42: Object of class `NoneType` has no attribute `objfile` [missing-attribute]
+ ERROR pwndbg/enhance.py:124:18-42: `in` is not supported between `Literal['[stack']` and `object` [not-iterable]

apprise (https://github.com/caronc/apprise)
- ERROR apprise/plugins/dbus.py:436:49-63: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:438:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:440:48-62: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:441:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:444:42-56: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:445:52-66: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:447:42-56: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/dbus.py:448:52-66: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:396:49-63: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:398:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:400:48-62: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:401:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:404:42-56: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:405:52-66: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:407:42-56: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/glib.py:408:52-66: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/gnome.py:267:49-63: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/gnome.py:270:17-31: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/gnome.py:273:48-62: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/gnome.py:274:54-68: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/macosx.py:245:46-60: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/macosx.py:246:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/macosx.py:249:46-60: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/macosx.py:250:53-67: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1169:48-62: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1170:34-48: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1174:43-57: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1176:17-31: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1183:44-58: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1184:30-44: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1187:46-60: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1188:32-46: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1190:49-63: `None` is not subscriptable [unsupported-operation]
- ERROR apprise/plugins/telegram.py:1191:32-46: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_config.py:1290:25-38: Object of class `object` has no attribute `tags` [missing-attribute]
- ERROR tests/test_apprise_config.py:1291:27-40: Object of class `object` has no attribute `tags` [missing-attribute]
+ ERROR tests/test_apprise_config.py:1290:12-38: `in` is not supported between `Literal['company']` and `object` [not-iterable]
+ ERROR tests/test_apprise_config.py:1291:12-40: `in` is not supported between `Literal['co-worker']` and `object` [not-iterable]
- ERROR tests/test_apprise_utils.py:383:20-33: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:385:20-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:401:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:417:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:434:22-35: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:435:24-37: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:437:21-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:439:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:441:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:442:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:443:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:443:37-51: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:444:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:444:37-51: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:460:22-35: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:461:24-37: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:463:21-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:465:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:467:12-26: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:468:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:469:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:469:37-51: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:470:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:470:37-51: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1030:20-33: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1040:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1050:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1063:22-35: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1064:24-37: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1065:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1066:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1067:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_apprise_utils.py:1068:12-25: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_config_base.py:972:28-38: Object of class `object` has no attribute `tags` [missing-attribute]
+ ERROR tests/test_config_base.py:972:16-38: `in` is not supported between `Literal['devops']` and `object` [not-iterable]
- ERROR tests/test_config_base.py:997:28-38: Object of class `object` has no attribute `tags` [missing-attribute]
+ ERROR tests/test_config_base.py:997:16-38: `in` is not supported between `Literal['devops']` and `object` [not-iterable]
- ERROR tests/test_config_base.py:1180:28-38: Object of class `object` has no attribute `tags` [missing-attribute]
+ ERROR tests/test_config_base.py:1180:16-38: `in` is not supported between `Literal['devops']` and `object` [not-iterable]
- ERROR tests/test_notify_base.py:296:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:300:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:305:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:312:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:319:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:326:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:334:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:341:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:344:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:347:12-29: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:353:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:356:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:359:12-31: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:366:12-27: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_notify_base.py:371:12-27: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:335:12-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:337:12-36: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:339:12-40: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:359:12-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:361:12-36: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_ses.py:363:12-40: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:219:12-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:221:12-36: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:223:12-40: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:242:12-34: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:244:12-36: `None` is not subscriptable [unsupported-operation]
- ERROR tests/test_plugin_sns.py:246:12-40: `None` is not subscriptable [unsupported-operation]

speedrun.com_global_scoreboard_webapp (https://github.com/Avasam/speedrun.com_global_scoreboard_webapp)
- ERROR backend/services/utils.py:71:16-25: Returned type `SrcErrorResultDto` is not assignable to declared return type `dict[Literal['data'], Any] | None` [bad-return]

bandersnatch (https://github.com/pypa/bandersnatch)
- ERROR src/bandersnatch/filter.py:46:25-54: `type[Any]` is not subscriptable [unsupported-operation]

pandas (https://github.com/pandas-dev/pandas)
- ERROR pandas/tests/io/test_stata.py:1866:30-44: Object of class `StataReader` has no attribute `columns` [missing-attribute]
+ ERROR pandas/tests/io/test_stata.py:1866:16-44: `in` is not supported between `Literal['date_col']` and `object` [not-iterable]

meson (https://github.com/mesonbuild/meson)
- ERROR mesonbuild/compilers/rust.py:293:40-54: Object of class `NoneType` has no attribute `id` [missing-attribute]

core (https://github.com/home-assistant/core)
- ERROR homeassistant/components/tts/__init__.py:292:53-72: Unpacked keyword argument `object | Unknown` is not assignable to parameter `engine` with type `str` in function `SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/__init__.py:292:53-72: Unpacked keyword argument `object | Unknown` is not assignable to parameter `use_file_cache` with type `bool | None` in function `SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/__init__.py:292:53-72: Unpacked keyword argument `object | Unknown` is not assignable to parameter `language` with type `str | None` in function `SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/__init__.py:292:53-72: Unpacked keyword argument `object | Unknown` is not assignable to parameter `options` with type `dict[Unknown, Unknown] | None` in function `SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/__init__.py:292:62-71: TypedDict `ParsedMediaSourceStreamId` does not have key `options` [bad-typed-dict-key]
- ERROR homeassistant/components/tts/__init__.py:293:41-50: TypedDict `ParsedMediaSourceStreamId` does not have key `message` [bad-typed-dict-key]
- ERROR homeassistant/components/tts/media_source.py:145:61-80: Unpacked keyword argument `object | Unknown` is not assignable to parameter `engine` with type `str` in function `homeassistant.components.tts.SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/media_source.py:145:61-80: Unpacked keyword argument `object | Unknown` is not assignable to parameter `use_file_cache` with type `bool | None` in function `homeassistant.components.tts.SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/media_source.py:145:61-80: Unpacked keyword argument `object | Unknown` is not assignable to parameter `language` with type `str | None` in function `homeassistant.components.tts.SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/media_source.py:145:61-80: Unpacked keyword argument `object | Unknown` is not assignable to parameter `options` with type `dict[Unknown, Unknown] | None` in function `homeassistant.components.tts.SpeechManager.async_create_result_stream` [bad-argument-type]
- ERROR homeassistant/components/tts/media_source.py:145:70-79: TypedDict `ParsedMediaSourceStreamId` does not have key `options` [bad-typed-dict-key]
- ERROR homeassistant/components/tts/media_source.py:146:49-58: TypedDict `ParsedMediaSourceStreamId` does not have key `message` [bad-typed-dict-key]
+ ERROR homeassistant/config_entries.py:486:17-34: Unpacked keyword argument `object | str` is not assignable to parameter `subentry_id` with type `str` in function `ConfigSubentry.__init__` [bad-argument-type]
- ERROR homeassistant/helpers/storage.py:437:17-38: Cannot index into `bool` [bad-index]
- ERROR homeassistant/helpers/storage.py:437:17-38: Cannot index into `float` [bad-index]
- ERROR homeassistant/helpers/storage.py:437:17-38: Cannot index into `int` [bad-index]
- ERROR homeassistant/helpers/storage.py:437:17-38: Cannot index into `list[JsonValueType]` [bad-index]
- ERROR homeassistant/helpers/storage.py:437:17-38: Cannot index into `str` [bad-index]
- ERROR homeassistant/helpers/storage.py:437:17-38: `None` is not subscriptable [unsupported-operation]
- ERROR homeassistant/helpers/storage.py:449:17-38: Cannot index into `bool` [bad-index]
- ERROR homeassistant/helpers/storage.py:449:17-38: Cannot index into `float` [bad-index]
- ERROR homeassistant/helpers/storage.py:449:17-38: Cannot index into `int` [bad-index]
- ERROR homeassistant/helpers/storage.py:449:17-38: Cannot index into `list[JsonValueType]` [bad-index]
- ERROR homeassistant/helpers/storage.py:449:17-38: Cannot index into `str` [bad-index]
- ERROR homeassistant/helpers/storage.py:449:17-38: `None` is not subscriptable [unsupported-operation]
- ERROR homeassistant/helpers/storage.py:458:42-63: Cannot index into `bool` [bad-index]
- ERROR homeassistant/helpers/storage.py:458:42-63: Cannot index into `float` [bad-index]
- ERROR homeassistant/helpers/storage.py:458:42-63: Cannot index into `int` [bad-index]
- ERROR homeassistant/helpers/storage.py:458:42-63: Cannot index into `list[JsonValueType]` [bad-index]
- ERROR homeassistant/helpers/storage.py:458:42-63: Cannot index into `str` [bad-index]
- ERROR homeassistant/helpers/storage.py:458:42-63: `None` is not subscriptable [unsupported-operation]

paasta (https://github.com/yelp/paasta)
- ERROR paasta_tools/kubernetes/application/controller_wrappers.py:186:47-74: Object of class `NoneType` has no attribute `config_dict` [missing-attribute]
+ ERROR paasta_tools/kubernetes/application/controller_wrappers.py:186:12-74: `in` is not supported between `Literal['unhealthy_pod_eviction_policy']` and `object` [not-iterable]

spack (https://github.com/spack/spack)
- ERROR lib/spack/spack/binary_distribution.py:1960:19-42: Object of class `NoneType` has no attribute `binary_formats` [missing-attribute]
+ ERROR lib/spack/spack/binary_distribution.py:1960:10-42: `in` is not supported between `Literal['elf']` and `object` [not-iterable]
- ERROR lib/spack/spack/mixins.py:64:41-49: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:66:21-29: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:67:42-50: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:69:25-33: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:70:41-49: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:71:42-50: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:104:37-45: Object of class `NoneType` has no attribute `spec` [missing-attribute]
- ERROR lib/spack/spack/mixins.py:106:28-36: Object of class `NoneType` has no attribute `spec` [missing-attribute]
+ ERROR lib/spack/spack/mixins.py:64:41-54: Cannot index into `object` [bad-index]
+ ERROR lib/spack/spack/mixins.py:66:12-29: `in` is not supported between `Literal['cxx']` and `object` [not-iterable]
+ ERROR lib/spack/spack/mixins.py:67:42-57: Cannot index into `object` [bad-index]
+ ERROR lib/spack/spack/mixins.py:69:12-33: `in` is not supported between `Literal['fortran']` and `object` [not-iterable]
+ ERROR lib/spack/spack/mixins.py:70:41-60: Cannot index into `object` [bad-index]
+ ERROR lib/spack/spack/mixins.py:71:42-61: Cannot index into `object` [bad-index]
+ ERROR lib/spack/spack/mixins.py:104:16-45: `not in` is not supported between `Literal['c']` and `object` [not-iterable]
+ ERROR lib/spack/spack/mixins.py:104:16-45: `not in` is not supported between `Literal['cxx']` and `object` [not-iterable]
+ ERROR lib/spack/spack/mixins.py:104:16-45: `not in` is not supported between `Literal['fortran']` and `object` [not-iterable]
+ ERROR lib/spack/spack/mixins.py:106:28-51: Cannot index into `object` [bad-index]

parso (https://github.com/davidhalter/parso)
- ERROR parso/python/pep8.py:657:41-67: Object of class `NoneType` has no attribute `prefix` [missing-attribute]
+ ERROR parso/python/pep8.py:657:29-67: `not in` is not supported between `Literal['\r']` and `object` [not-iterable]

pydantic (https://github.com/pydantic/pydantic)
- ERROR pydantic/_internal/_schema_gather.py:112:29-51: Argument `list[CoreSchema] | AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainValidatorFunctionSchema | SetSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapValidatorFunctionSchema | Unknown` is not assignable to parameter `schema` with type `AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | ComputedField | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FormatSerSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | ModelSerSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainSerializerFunctionSerSchema | PlainValidatorFunctionSchema | SetSchema | SimpleSerSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | ToStringSerSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapSerializerFunctionSerSchema | WrapValidatorFunctionSchema` in function `traverse_schema` [bad-argument-type]
+ ERROR pydantic/_internal/_schema_gather.py:112:29-51: Argument `list[CoreSchema] | object | AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainValidatorFunctionSchema | SetSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapValidatorFunctionSchema` is not assignable to parameter `schema` with type `AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | ComputedField | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FormatSerSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | ModelSerSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainSerializerFunctionSerSchema | PlainValidatorFunctionSchema | SetSchema | SimpleSerSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | ToStringSerSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapSerializerFunctionSerSchema | WrapValidatorFunctionSchema` in function `traverse_schema` [bad-argument-type]
+ ERROR pydantic/_internal/_schema_gather.py:115:22-44: Type `object` is not iterable [not-iterable]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `AfterValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `AnySchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ArgumentsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ArgumentsV3Schema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `BeforeValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `BoolSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `BytesSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `CallSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `CallableSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ChainSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ComplexSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ComputedField` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `CustomErrorSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DataclassArgsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DataclassSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DateSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DatetimeSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DecimalSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DefinitionReferenceSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DefinitionsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `DictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `EnumSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `FloatSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `FormatSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `IntSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `InvalidSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `IsInstanceSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `IsSubclassSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `JsonOrPythonSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `JsonSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `LaxOrStrictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `LiteralSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `MissingSentinelSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ModelFieldsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ModelSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ModelSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `MultiHostUrlSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `NoneSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `NullableSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `PlainSerializerFunctionSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `PlainValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `SimpleSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `StringSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `TaggedUnionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `TimeSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `TimedeltaSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `ToStringSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `TypedDictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `UnionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `UrlSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `UuidSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `WithDefaultSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `WrapSerializerFunctionSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:112:36-50: TypedDict `WrapValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `AfterValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `AnySchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ArgumentsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ArgumentsV3Schema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `BeforeValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `BoolSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `BytesSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `CallSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `CallableSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ChainSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ComplexSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ComputedField` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `CustomErrorSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DataclassArgsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DataclassSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DateSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DatetimeSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DecimalSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DefinitionReferenceSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DefinitionsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `DictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `EnumSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `FloatSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `FormatSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `IntSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `InvalidSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `IsInstanceSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `IsSubclassSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `JsonOrPythonSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `JsonSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `LaxOrStrictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `LiteralSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `MissingSentinelSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ModelFieldsSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ModelSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ModelSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `MultiHostUrlSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `NoneSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `NullableSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `PlainSerializerFunctionSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `PlainValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `SimpleSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `StringSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `TaggedUnionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `TimeSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `TimedeltaSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `ToStringSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `TypedDictSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `UnionSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `UrlSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `UuidSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `WithDefaultSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `WrapSerializerFunctionSerSchema` does not have key `items_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:115:29-43: TypedDict `WrapValidatorFunctionSchema` does not have key `items_schema` [bad-typed-dict-key]
+ ERROR pydantic/_internal/_schema_gather.py:119:29-50: Argument `object | AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainValidatorFunctionSchema | SetSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapValidatorFunctionSchema` is not assignable to parameter `schema` with type `AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | ComputedField | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FormatSerSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | ModelSerSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainSerializerFunctionSerSchema | PlainValidatorFunctionSchema | SetSchema | SimpleSerSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | ToStringSerSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapSerializerFunctionSerSchema | WrapValidatorFunctionSchema` in function `traverse_schema` [bad-argument-type]
+ ERROR pydantic/_internal/_schema_gather.py:121:29-52: Argument `object | AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainValidatorFunctionSchema | SetSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapValidatorFunctionSchema` is not assignable to parameter `schema` with type `AfterValidatorFunctionSchema | AnySchema | ArgumentsSchema | ArgumentsV3Schema | BeforeValidatorFunctionSchema | BoolSchema | BytesSchema | CallSchema | CallableSchema | ChainSchema | ComplexSchema | ComputedField | CustomErrorSchema | DataclassArgsSchema | DataclassSchema | DateSchema | DatetimeSchema | DecimalSchema | DefinitionReferenceSchema | DefinitionsSchema | DictSchema | EnumSchema | FloatSchema | FormatSerSchema | FrozenSetSchema | GeneratorSchema | IntSchema | InvalidSchema | IsInstanceSchema | IsSubclassSchema | JsonOrPythonSchema | JsonSchema | LaxOrStrictSchema | ListSchema | LiteralSchema | MissingSentinelSchema | ModelFieldsSchema | ModelSchema | ModelSerSchema | MultiHostUrlSchema | NoneSchema | NullableSchema | PlainSerializerFunctionSerSchema | PlainValidatorFunctionSchema | SetSchema | SimpleSerSchema | StringSchema | TaggedUnionSchema | TimeSchema | TimedeltaSchema | ToStringSerSchema | TupleSchema | TypedDictSchema | UnionSchema | UrlSchema | UuidSchema | WithDefaultSchema | WrapSerializerFunctionSerSchema | WrapValidatorFunctionSchema` in function `traverse_schema` [bad-argument-type]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `AfterValidatorFunctionSchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `AnySchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `ArgumentsSchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `ArgumentsV3Schema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `BeforeValidatorFunctionSchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `BoolSchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `BytesSchema` does not have key `keys_schema` [bad-typed-dict-key]
- ERROR pydantic/_internal/_schema_gather.py:119:36-49: TypedDict `CallSchema` does not have key `keys_schema` [bad-typed-dict-key]

... (truncated 892 lines) ...

PyGithub (https://github.com/PyGithub/PyGithub)
- ERROR github/AdvisoryVulnerability.py:141:51-88: Type `NoneType` is not iterable [not-iterable]

scrapy (https://github.com/scrapy/scrapy)
+ ERROR tests/test_crawl.py:326:16-53: `not in` is not supported between `Literal['failures']` and `object` [not-iterable]
+ ERROR tests/test_crawl.py:328:39-71: Cannot index into `object` [bad-index]
+ ERROR tests/test_crawl.py:331:39-71: Cannot index into `object` [bad-index]
+ ERROR tests/test_crawl.py:334:39-71: Cannot index into `object` [bad-index]
+ ERROR tests/test_crawl.py:337:39-71: Cannot index into `object` [bad-index]
- ERROR tests/test_crawl.py:326:34-53: Object of class `NoneType` has no attribute `meta`
- Object of class `Spider` has no attribute `meta` [missing-attribute]
- ERROR tests/test_crawl.py:328:39-58: Object of class `NoneType` has no attribute `meta`
- Object of class `Spider` has no attribute `meta` [missing-attribute]
- ERROR tests/test_crawl.py:331:39-58: Object of class `NoneType` has no attribute `meta`
- Object of class `Spider` has no attribute `meta` [missing-attribute]
- ERROR tests/test_crawl.py:334:39-58: Object of class `NoneType` has no attribute `meta`
- Object of class `Spider` has no attribute `meta` [missing-attribute]
- ERROR tests/test_crawl.py:337:39-58: Object of class `NoneType` has no attribute `meta`
- Object of class `Spider` has no attribute `meta` [missing-attribute]

spark (https://github.com/apache/spark)
- ERROR python/pyspark/tests/test_util.py:37:27-45: Object of class `Wrapped` has no attribute `_input_kwargs` [missing-attribute]
- ERROR python/pyspark/tests/test_util.py:38:23-41: Object of class `Wrapped` has no attribute `_input_kwargs` [missing-attribute]
- ERROR python/pyspark/tests/test_util.py:39:27-45: Object of class `Wrapped` has no attribute `_input_kwargs` [missing-attribute]
+ ERROR python/pyspark/tests/test_util.py:37:27-50: Cannot index into `object` [bad-index]
+ ERROR python/pyspark/tests/test_util.py:38:16-41: `in` is not supported between `Literal['y']` and `object` [not-iterable]
+ ERROR python/pyspark/tests/test_util.py:39:27-50: Cannot index into `object` [bad-index]
- ERROR python/pyspark/tests/test_util.py:60:21-39: Object of class `Setter` has no attribute `_input_kwargs` [missing-attribute]
- ERROR python/pyspark/tests/test_util.py:60:55-73: Object of class `Setter` has no attribute `_input_kwargs` [missing-attribute]
- ERROR python/pyspark/tests/test_util.py:61:27-45: Object of class `Setter` has no attribute `_input_kwargs` [missing-attribute]
+ ERROR python/pyspark/tests/test_util.py:60:21-48: Cannot index into `object` [bad-index]
+ ERROR python/pyspark/tests/test_util.py:60:55-84: Cannot index into `object` [bad-index]
+ ERROR python/pyspark/tests/test_util.py:61:27-50: Cannot index into `object` [bad-index]

prefect (https://github.com/PrefectHQ/prefect)
- ERROR src/integrations/prefect-dbt/tests/core/test_orchestrator_per_node.py:998:32-55: `None` is not subscriptable [unsupported-operation]

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Primer Diff Classification

❌ 5 regression(s) | ✅ 10 improvement(s) | ➖ 5 neutral | 20 project(s) total | +58, -187 errors

5 regression(s) across apprise, speedrun.com_global_scoreboard_webapp, meson, paasta, parso. error kinds: missing-attribute, not-iterable, New not-iterable errors on .tags. caused by narrow_key_membership(). 10 improvement(s) across openlibrary, cloud-init, mitmproxy, cwltool, bandersnatch, core, pydantic, PyGithub, scrapy, prefect.

Project Verdict Changes Error Kinds Root Cause
openlibrary ✅ Improvement -3 bad-index, bad-typed-dict-key narrow_key_membership()
cloud-init ✅ Improvement -6 bad-index, bad-typed-dict-key narrow_key_membership()
mitmproxy ✅ Improvement -6 bad-index narrow_key_membership()
cwltool ✅ Improvement -9 bad-typed-dict-key narrow_key_membership()
scikit-learn ➖ Neutral +4, -4 bad-index, missing-attribute narrow_key_membership()
pwndbg ➖ Neutral +4, -4 Removed missing-attribute on NoneType narrow_key_membership()
apprise ❌ Regression +5, -99 New not-iterable errors on .tags narrow_key_membership()
speedrun.com_global_scoreboard_webapp ❌ Regression -1 TypedDict key membership narrowing suppresses bad-return narrow_key_membership()
bandersnatch ✅ Improvement -1 unsupported-operation pyrefly/lib/alt/narrow.rs
pandas ➖ Neutral +1, -1 missing-attribute, not-iterable narrow_key_membership()
meson ❌ Regression -1 missing-attribute narrow_key_membership()
core ✅ Improvement +1, -12 Removed TypedDict union narrowing false positives narrow_key_membership()
paasta ❌ Regression +1, -1 missing-attribute, not-iterable narrow_key_membership()
spack ➖ Neutral +9, -9 bad-index on object (5 errors) narrow_key_membership()
parso ❌ Regression +1, -1 missing-attribute, not-iterable narrow_key_membership()
pydantic ✅ Improvement +21, -21 New bad-argument-type errors (17) narrow_key_membership()
PyGithub ✅ Improvement -1 not-iterable narrow_key_membership()
scrapy ✅ Improvement +5 bad-index, not-iterable narrow_key_membership()
spark ➖ Neutral +6, -6 New bad-index and not-iterable errors on decorator-injected attribute narrow_key_membership()
prefect ✅ Improvement -1 unsupported-operation narrow_key_membership()
Detailed analysis

❌ Regression (5)

apprise (+5, -99)

New not-iterable errors on .tags: False positives — pyrefly infers object for the result of double-indexing into AppriseConfig (e.g., ac[0][1]), then complains that in is not supported between a literal string and object. At runtime, ac[0][1] returns a notification plugin instance (a NotifyBase subclass) whose .tags attribute is a set[str], so the in check is valid. 0/5 confirmed by mypy/pyright. Minor regression from the narrowing changes.
Removed unsupported-operation (None not subscriptable): False positives removed — pyrefly previously reported None is not subscriptable on expressions like results["qsd"]["priority"]. The root cause is that NotifyBase.parse_url() has return type dict | None, and the old pyrefly was not narrowing past the implicit guard checks (e.g., "priority" in results["qsd"] implies results is not None). After the guards, results is always a dict, so subscripting is valid. 94 errors removed across 9 plugin files. Clear improvement.
Removed missing-attribute on object.tags: False positives removed — same root cause as the new not-iterable errors (type inferred as object for double-indexed config results), but the error form changed from "no attribute tags" to "in not supported on object". These were incorrect before and remain incorrect in the new form, representing a change in error presentation rather than a fix.

Overall: Net improvement: 99 false positives removed, 5 new false positives added. The removed errors were clearly wrong. The 5 new errors are also false positives (pyrefly infers object for .tags and then complains in isn't supported), but the net reduction of 94 errors is a significant improvement.

Per-category reasoning:

  • New not-iterable errors on .tags: False positives — pyrefly infers object for the result of double-indexing into AppriseConfig (e.g., ac[0][1]), then complains that in is not supported between a literal string and object. At runtime, ac[0][1] returns a notification plugin instance (a NotifyBase subclass) whose .tags attribute is a set[str], so the in check is valid. 0/5 confirmed by mypy/pyright. Minor regression from the narrowing changes.
  • Removed unsupported-operation (None not subscriptable): False positives removed — pyrefly previously reported None is not subscriptable on expressions like results["qsd"]["priority"]. The root cause is that NotifyBase.parse_url() has return type dict | None, and the old pyrefly was not narrowing past the implicit guard checks (e.g., "priority" in results["qsd"] implies results is not None). After the guards, results is always a dict, so subscripting is valid. 94 errors removed across 9 plugin files. Clear improvement.
  • Removed missing-attribute on object.tags: False positives removed — same root cause as the new not-iterable errors (type inferred as object for double-indexed config results), but the error form changed from "no attribute tags" to "in not supported on object". These were incorrect before and remain incorrect in the new form, representing a change in error presentation rather than a fix.

Attribution: The changes to narrow_key_membership() in pyrefly/lib/alt/narrow.rs improved TypedDict union narrowing for 'key' in x patterns. This fixed the 94 unsupported-operation false positives (likely by better handling dict-like subscript narrowing). However, the new narrowing logic in key_membership_value_type() and has_dict_like_member() appears to cause some expressions to be narrowed to object incorrectly, producing the 5 new not-iterable false positives.

speedrun.com_global_scoreboard_webapp (-1)

TypedDict key membership narrowing suppresses bad-return: The removed bad-return error was a true positive catching a function signature mismatch. However, the new narrowing correctly identifies that 'status' not in json_data is unreachable when json_data: SrcErrorResultDto (since status is required), narrowing to Never. This is type-theoretically sound but loses detection of the underlying annotation bug. This is a minor regression — a real (if indirect) error is no longer caught.

Overall: This is a nuanced case. The removed error was technically a true positive — the function's type annotation (SrcErrorResultDto) doesn't match how the function is used (it receives data that could be either an error or data result). However, the new narrowing behavior is type-theoretically correct: if json_data is truly SrcErrorResultDto with required key status, then 'status' not in json_data is unreachable, and returning from unreachable code doesn't produce type errors. The real bug is the function signature, not the return statement. The new behavior is a more precise analysis. While it happens to suppress a symptom of a real issue, the narrowing itself is correct per the typing spec's treatment of required TypedDict keys. This is a wash — the old error was catching a real issue indirectly, but the new analysis is more precise. On balance, the improved TypedDict narrowing is the primary goal and is correct; this is a minor side effect where a somewhat-useful error is lost due to more precise type narrowing. The error was not a false positive, but the new behavior is defensible.

Attribution: The change to narrow_key_membership() in pyrefly/lib/alt/narrow.rs introduced TypedDict subject narrowing for in/not in checks. For SrcErrorResultDto (which has status as a required field), checking 'status' not in json_data now narrows the type to Never (since a required key must always be present). Since Never is assignable to any type, the bad-return error is suppressed. The narrowing logic in lines 684-695 handles this: when present=false and the field exists and is required, it returns Never.

meson (-1)

The removed error was catching a real type issue: self.linker is Optional[DynamicLinker] and .id is accessed without a None guard on line 293. The has_verbatim method has no assertion or conditional check that self.linker is not None before accessing self.linker.id. While in practice the linker is likely always set when this method is called, from a static type checking perspective this is a genuine Optional access violation. Removing this error means pyrefly lost the ability to catch this potential None dereference. This is a regression.
Attribution: The PR changes narrow_key_membership() and related functions in pyrefly/lib/alt/narrow.rs. The change modifies how TypedDict union narrowing works with in checks. Looking at the diff more carefully, the changes are in the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) and NotHasKey branches. The new code calls self.[narrow_key_membership()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs) which uses distribute_over_union and then checks self.[has_dict_like_member()](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs). The change from self.is_dict_like(&base_ty) to self.has_dict_like_member(&narrowed_base) and the new narrowing logic could have side effects on how types are narrowed in non-TypedDict contexts. However, the connection to the self.linker.id error is unclear — this error is about Optional narrowing, not TypedDict narrowing. It's possible this is an indirect effect where the narrowing changes affected how Optional types are handled in some code path, but this seems unlikely. More likely, this is a pre-existing flaky error that happened to change due to some subtle ordering or caching effect.

paasta (+1, -1)

Both errors relate to self.soa_config.config_dict on line 186. The self.soa_config field is initialized as None on line 68 with a type comment # type: KubernetesDeploymentConfig. This means the declared type is KubernetesDeploymentConfig but the actual value is None until load_local_config is called.

The removed missing-attribute error flagged that self.soa_config could be None (since it's assigned None in __init__), and NoneType has no .config_dict attribute. This is a legitimate true positive — if ensure_pod_disruption_budget is called before load_local_config, this would raise AttributeError at runtime. Note the same pattern appears on line 179 with the same risk.

The new not-iterable error claims that in is not supported between a Literal string and object. This is a false positive. The type checker appears to be inferring self.soa_config.config_dict as object (possibly because of the None type issue propagating), but even if it were object, the real issue is that config_dict on KubernetesDeploymentConfig would typically be a dict, which fully supports the in operator. The in operator on dicts checks key membership and is completely valid Python.

Net effect: the old missing-attribute error was a true positive catching a real potential None dereference. The new not-iterable error is a false positive — it incorrectly flags a valid in check on what would be a dict. This is a regression: a valid error was replaced with an invalid one.

Attribution: The changes to narrow_key_membership() and the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) handling in pyrefly/lib/alt/narrow.rs caused this. The new narrowing logic appears to incorrectly produce a not-iterable error when processing in checks on TypedDicts, likely because the narrowed type doesn't properly resolve as dict-like in this context.

parso (+1, -1)

Both errors are false positives caused by missing type stubs for the parso library. The old error (NoneType has no attribute prefix) has genuine justification: self._previous_leaf is initialized to None (line 156), so the type checker correctly identifies it as potentially None. While at runtime this code path may never execute with self._previous_leaf being None, the type-level concern is valid. The new error (not in not supported between Literal['\r'] and object) arises because without parso type stubs, pyrefly cannot determine the actual type of .prefix on the leaf object. Even if pyrefly narrows past the None case, it likely infers the leaf as an unknown or under-specified type, causing .prefix to resolve to object. At runtime, .prefix on parso leaf nodes is indeed a str, so the in check is perfectly valid. The new error is more confusing and less actionable than the old one — a developer seeing 'NoneType has no attribute prefix' can understand the nullability concern and add a guard, while 'not in not supported between Literal and object' is opaque and harder to diagnose. This represents a slight regression: the old error pointed at a real (if unlikely) issue, while the new error is a more confusing false positive about a valid string containment check.
Attribution: The change to narrow_key_membership() and related functions in pyrefly/lib/alt/narrow.rs modified how in/not in narrowing works. The new narrowing logic for not in (around line 1619-1640) now narrows the subject type differently, which appears to have changed how pyrefly resolves the type of self._previous_leaf (or its .prefix attribute) in the not in expression on line 657, causing it to resolve to object instead of properly handling the None | leaf_type union.

✅ Improvement (10)

openlibrary (-3)

The two errors in lists.py (lines 105 and 108) are false positives caused by pyrefly's inability to narrow TypedDict unions based on "key" in seed checks. The parameter seed has type ThingReferenceDict | AnnotatedSeedDict, and after the elif "key" in seed: guard on line 103, pyrefly should narrow the type to ThingReferenceDict (which has a key field), but previously it did not, causing it to complain that AnnotatedSeedDict does not have key key. The PR adds proper narrowing logic so these are correctly resolved.

The error in ol_infobase.py (line 543) is a separate issue. It involves data['table_of_contents'] where data is the result of _process_data(json.loads(json_str)). The _process_data function recursively processes data and returns complex union types. The fix_table_of_contents function expects a list argument, and the error [bad-index] relates to pyrefly's inference of the type flowing through _process_data. This is not a TypedDict narrowing issue but rather an improvement in how pyrefly handles the return type of recursive data processing functions or json.loads results.

All three were false positives — the code was correct — but they stem from different type inference limitations in pyrefly.

Attribution: The changes in pyrefly/lib/alt/narrow.rs added the narrow_key_membership() method which narrows TypedDict union types based on "key" in x checks. Previously, pyrefly only recorded a key facet but didn't narrow the subject type itself. The new code in the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) and NotHasKey branches now calls self.narrow_key_membership(&base_ty, key, true/false) to filter out TypedDict members that don't have the checked key, then uses has_dict_like_member(&narrowed_base) instead of is_dict_like(&base_ty) to determine whether to apply facet narrowing. This correctly removes TypedDict variants from unions when a key membership check eliminates them.

cloud-init (-6)

All 6 removed errors are false positives that arose from pyrefly's inability to properly narrow types when using key in x membership tests. The PR adds proper subject-type narrowing for this pattern. For the DataSourceNoCloud.py errors specifically, mydata is initialized as a dict with values of type dict | str | None (line 70-75: values include {}, "", and None). Thus mydata["meta-data"] has type dict | str | None. At line 171, the check "seedfrom" in mydata["meta-data"] should narrow the type of mydata["meta-data"] to exclude None (which doesn't support in) and effectively indicate it's a dict (since in on a string checks for substrings, not dict-style key access). Without this narrowing, pyrefly reported bad-index (Cannot index into str) and unsupported-operation (None is not subscriptable) at line 172 when accessing mydata["meta-data"]["seedfrom"]. The code follows standard Python idioms (check if key in dict then access dict[key]), which are valid at runtime and correctly handled by other type checkers. The bad-typed-dict-key errors in DataSourceVMware.py and the remaining unsupported-operation error in test_opennebula.py similarly involve narrowing after membership tests. Removing these false positives is an improvement.
Attribution: The changes to narrow_key_membership() in pyrefly/lib/alt/narrow.rs are directly responsible. Previously, when pyrefly encountered "key" in x where x was a dict or TypedDict (or union thereof), it only recorded a key facet but did not narrow the subject type itself. The new narrow_key_membership() function (lines 682-698) filters union members based on whether they could contain the key, and the updated NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) handler (around line 1589) now applies this narrowing to the base type before computing the value type. This means that after if "seedfrom" in mydata["meta-data"], pyrefly now correctly knows the type of mydata["meta-data"] is the dict-like member that supports subscripting, rather than incorrectly considering str or None alternatives from sibling keys.

mitmproxy (-6)

The 6 removed bad-index errors ('Cannot index into islice[Any]') in test files are no longer reported after this change. Without access to the source code, we cannot definitively determine whether these are false positives being correctly removed or true positives being incorrectly suppressed. Notably, itertools.islice objects do NOT support indexing (__getitem__) in Python - they are iterators that only support __next__ and __iter__. So if the code is genuinely indexing into an islice object, these errors would be legitimate. However, it's also possible that the type checker was previously inferring islice[Any] incorrectly where the actual runtime type would be something indexable (e.g., a list or dict), in which case these would be false positives. The fact that these appear in test files for a 'urldict' module suggests the actual objects might be dict-like, and the islice[Any] inference may have been incorrect. Without the source code, the correctness of this change remains uncertain.
Attribution: The restructuring of the HasKey narrowing logic in pyrefly/lib/alt/narrow.rs (around lines 1586-1610 in the new code) changed how base types are narrowed and how facet chains are applied. Specifically, the new narrow_key_membership() function and the change from is_dict_like(&base_ty) to has_dict_like_member(&narrowed_base) altered the type inference flow, which as a side effect fixed the incorrect islice[Any] type inference that was producing the bad-index false positives.

cwltool (-9)

These were false positives caused by pyrefly's inability to narrow TypedDict unions via in checks. The variable t2 iterates over ls which is typed as MutableSequence[CWLFileType | CWLDirectoryType | DirentType]. The DirentType TypedDict has keys entry and entryname, while CWLFileType and CWLDirectoryType do not have the key entry. At line 705, the code checks if "entry" not in t2: continue, which should narrow the union type from CWLFileType | CWLDirectoryType | DirentType to just DirentType (the only member that has the entry key). Without this narrowing, pyrefly sees the full union type at lines 709-710 when accessing t2["entry"] and t2["entryname"], and reports bad-typed-dict-key errors because CWLDirectoryType (and CWLFileType) do not have these keys. With the PR's new narrow_key_membership() function, pyrefly now correctly narrows the union type after the in check, eliminating CWLFileType and CWLDirectoryType from the union and leaving only DirentType, which resolves the spurious errors.
Attribution: The change to narrow_key_membership() in pyrefly/lib/alt/narrow.rs implements TypedDict union narrowing for "key" in x patterns. The NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) and NotHasKey branches now call narrow_key_membership() to filter union members based on whether they have the checked key, eliminating members that cannot have that key (closed TypedDicts without the key). This directly fixes the false positive bad-typed-dict-key errors.

bandersnatch (-1)

This is a removal of a false positive. BandersnatchConfig is a ConfigParser subclass, and ConfigParser supports __getitem__ (subscript) access, so self.configuration['plugins']['enabled'] is valid Python — self.configuration['plugins'] returns a SectionProxy, and indexing that with 'enabled' returns a string. The error type[Any] is not subscriptable indicates the type checker was incorrectly resolving the type of self.configuration to type[Any] (a class/type object) rather than an instance of BandersnatchConfig. This could happen if BandersnatchConfig uses a singleton pattern or unusual metaclass that confused the type checker's inference. The PR's changes fixed the type resolution so that self.configuration is correctly understood as an instance supporting subscript access, eliminating this false positive.
Attribution: The changes in pyrefly/lib/alt/narrow.rs refactored how in narrowing works for dict-like types. The old code at the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) branch directly checked self.is_dict_like(&base_ty) and applied facet narrowing. The new code first calls self.narrow_key_membership(&base_ty, key, true) to narrow the base type, then checks self.has_dict_like_member(&narrowed_base). This restructuring, along with the new key_membership_value_type method that replaces get_facet_chain_type, likely fixed the incorrect type resolution that was producing type[Any] for the subscript result of BandersnatchConfig after in narrowing.

core (+1, -12)

Removed TypedDict union narrowing false positives: 12 errors removed across bad-typed-dict-key, bad-index, unsupported-operation, and bad-argument-type categories. All were caused by pyrefly's inability to narrow TypedDict unions via 'key' in x checks. The code correctly guards key access with membership tests.
New false positive from dict value type inference: 1 new pyrefly-only error where subentry_kwargs dict value type is inferred as object | str instead of str. The dict is conditionally populated with a str value from a narrowed TypedDict, but pyrefly loses precision through the intermediate dict.

Overall: Net result: 12 false positives removed, 1 false positive added. The removed errors are clearly false positives from the inability to narrow TypedDict unions. The new error is a pyrefly-only false positive where dict value inference is too broad. Overall this is a significant improvement (11 net fewer false positives).

Per-category reasoning:

  • Removed TypedDict union narrowing false positives: 12 errors removed across bad-typed-dict-key, bad-index, unsupported-operation, and bad-argument-type categories. All were caused by pyrefly's inability to narrow TypedDict unions via 'key' in x checks. The code correctly guards key access with membership tests.
  • New false positive from dict value type inference: 1 new pyrefly-only error where subentry_kwargs dict value type is inferred as object | str instead of str. The dict is conditionally populated with a str value from a narrowed TypedDict, but pyrefly loses precision through the intermediate dict.

Attribution: The changes to narrow_key_membership() and related functions in pyrefly/lib/alt/narrow.rs now narrow TypedDict union types when 'key' in x is used. This correctly removes 12 false positives where pyrefly couldn't narrow the union. The 1 new error is a side effect where the narrowed type flows into a dict that gets unpacked, and pyrefly infers the dict value type too broadly as object | str instead of just str.

pydantic (+21, -21)

New bad-argument-type errors (17): These are false positives. The narrowing introduces object into the type union for TypedDict values accessed after in checks. The object comes from open TypedDict extra items. Since traverse_schema expects AllSchemas (a union of specific TypedDicts), the object component makes the argument incompatible. Neither mypy nor pyright flags these.
New not-iterable errors (3): False positives caused by the same root issue. After narrowing, schema['items_schema'] includes object in its type (from open TypedDict extra items), and object is not iterable. The code is correct because the in check ensures only TypedDicts with items_schema reach this point.
New missing-attribute error (1): False positive. schema.get('metadata') fails because after narrowing, schema includes object which doesn't have .get(). Same root cause as above.
Removed bad-typed-dict-key errors (19): These were false positives. The code guards TypedDict key access with if 'key' in schema: checks, but pyrefly previously didn't narrow the TypedDict union, so it complained about keys missing from some union members. Removing these is correct.
Removed bad-argument-type errors (2): These were false positives (included Unknown in the type union). Removing them is correct.

Overall: This is a mixed result but net negative. The PR correctly removes 19 false positive bad-typed-dict-key errors by implementing TypedDict union narrowing via in checks — that's a genuine improvement. However, it introduces 21 new false positive errors (all pyrefly-only) because the narrowing produces types that include object in the union. Looking at the new bad-argument-type errors, the argument type includes object (e.g., list[CoreSchema] | object | AfterValidatorFunctionSchema | ...), which doesn't match the parameter type. The not-iterable errors say Type 'object' is not iterable and the missing-attribute error says Object of class 'object' has no attribute 'get'. This object type is being introduced by the narrowing — when 'items_schema' in schema narrows the TypedDict union, some members that don't have items_schema as a known key but are open TypedDicts get narrowed to include object as the value type. The file explicitly has # pyright: reportTypedDictNotRequiredAccess=false, reportGeneralTypeIssues=false, reportArgumentType=false, reportAttributeAccessIssue=false at the top, indicating the authors know this code is hard to type-check. The net effect is 21 new false positives replacing 21 old false positives — a lateral move at best, but the new errors are arguably worse because they include confusing object types.

Attribution: The changes in pyrefly/lib/alt/narrow.rs added narrow_key_membership(), has_dict_like_member(), and key_membership_value_type() methods, and modified the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) and NotHasKey handling. The new narrowing logic correctly narrows TypedDict unions when 'key' in typed_dict is used. However, it appears that the narrowing is also being triggered by schema_type in {'list', ...} patterns (where schema_type is a string extracted from the TypedDict, not a key membership check on the TypedDict itself). This causes the TypedDict union schema to be incorrectly narrowed, producing object types where specific TypedDict types should remain. The 'items_schema' in schema checks on lines 111, 114, etc. DO correctly trigger TypedDict narrowing, but the narrowing appears to be too aggressive — it narrows to types that include object because the in check doesn't fully discriminate the union. The new errors show object appearing in the type (e.g., list[CoreSchema] | object | ...), suggesting the narrowing produces an object fallback for some union members.

PyGithub (-1)

The removed error was a false positive. The code at line 141 uses a conditional expression (X if condition else Y) where the iteration (for vf in vulnerability["vulnerable_functions"]) in X only executes when the condition vulnerability["vulnerable_functions"] is not None on line 142 is true. At runtime this is safe because Python evaluates the condition first. However, the static type checker was analyzing the expression vulnerability["vulnerable_functions"] on line 141, which has the declared type list[str] | None from the SimpleAdvisoryVulnerability TypedDict (line 59). Pyrefly was not properly narrowing the type of vulnerability["vulnerable_functions"] to list[str] in the truthy branch of the conditional expression based on the is not None check on line 142. The PR's improvements (likely to type narrowing in conditional expressions or TypedDict handling) allowed pyrefly to correctly understand that in the branch where the iteration occurs, the value has been narrowed to list[str] and is therefore iterable, eliminating this false positive.
Attribution: The changes in pyrefly/lib/alt/narrow.rs to narrow_key_membership() and the modified HasKey/NotHasKey handling in the narrowing logic. Specifically, the new key_membership_value_type() function and the restructured narrowing in the HasKey branch now properly compute the value type after narrowing. The has_dict_like_member() check and the new narrowed_base computation likely result in better type resolution for the vulnerable_functions field access after the "vulnerable_functions" in vulnerability assertion on line 139, which may have improved the type information available when analyzing line 141.

scrapy (+5)

After the assert "responses" in crawler.spider.meta check on line 325, pyrefly's type narrowing incorrectly changes the inferred type of crawler.spider.meta — which is typed as dict[str, Any] in Scrapy's Spider class — causing it to be treated as object rather than maintaining its dict type. This causes the not-iterable error on line 326 (where not in is used) and bad-index errors on lines 328, 331, 334, and 337 (where dict indexing with ["responses"] is used). These are all false positives — meta is a dict[str, Any] that fully supports in/not in containment checks and [] indexing. The narrowing logic in pyrefly appears to incorrectly handle the in containment check on dict types, widening the type to object instead of preserving the dict type. All 5 errors are pyrefly-only false positives.
Attribution: The changes to narrow_key_membership() and the NarrowOp::Atomic(subject, AtomicNarrowOp::HasKey(key)) / NotHasKey(key) handling in pyrefly/lib/alt/narrow.rs changed how the narrowed type is constructed. Previously, for non-dict-like types, the code returned type_info.clone() directly. Now it goes through narrow_key_membership and constructs a new narrowed type via type_info.with_narrow(chain.facets(), narrowed_base.clone()) or type_info.clone().with_ty(narrowed_base). This appears to incorrectly modify the type information for meta (which is dict[str, Any] or possibly object due to inference issues), causing it to be treated as object after the in check on line 325, which then cascades to all subsequent accesses.

prefect (-1)

This is a false positive removal. The code assert "invocation" in result["model.test.m1"] is valid — result is the return value of test_flow(), which calls orch.run_build() and returns a dict mapping node IDs to result dicts. The in operator on line 998 checks for key membership in the inner dict obtained via result["model.test.m1"].

The error occurs because test_flow is decorated with Prefect's @flow decorator, and pyrefly infers the return type of the decorated function as potentially including None (the flow decorator's type signature may wrap the return type in a way that includes None). This means result could be None or the dict could contain None values from pyrefly's perspective, making result["model.test.m1"] potentially None, which would make the in operator on it invalid.

However, this is a false positive because at runtime the flow returns a proper dict with dict values. The prior lines (996-997) already perform similar subscript operations (result["model.test.m1"]["status"] and "timing" in result["model.test.m1"]) which should have narrowed the type, but pyrefly wasn't propagating that narrowing to line 998. The fix in the PR improved type narrowing so that after the successful subscript operations on lines 996-997, pyrefly correctly understands that result["model.test.m1"] is not None on line 998, eliminating the spurious error.

Attribution: The changes to narrow_key_membership() and related functions in pyrefly/lib/alt/narrow.rs improved how pyrefly narrows types through "key" in x patterns. The new narrow_key_membership method and the refactored HasKey/NotHasKey handling in the NarrowOp::Atomic match arms now properly narrow the base type before applying facet narrows, which likely improved the inferred type of result["model.test.m1"] so it's no longer seen as potentially None.

➖ Neutral (5)

scikit-learn (+4, -4)

This is a net wash in error count (4 added, 4 removed) but represents a shift in error detection focus. The 4 removed errors were false positives where pyrefly claimed _BaseTreeExporter has no attribute colors — this was incorrect because colors is defined in both subclasses (_DOTTreeExporter and _MPLTreeExporter) as self.colors = {"bounds": None}, and the methods accessing self.colors (get_color, get_fill_color) are only ever called on subclass instances. The 4 new errors are also false positives, but of a different kind. The error messages say 'Cannot set item in object' and 'Cannot index into object', which are confirmed by pyright (3/3 and 1/1). These arise because pyrefly is now recognizing that self.colors exists (via the subclass assignments) but is inferring its type too narrowly — likely as dict[str, None] from the initialization {"bounds": None}, or possibly as object. When the code later assigns self.colors["rgb"] = _color_brew(...) (a list) or self.colors["bounds"] = (tuple), the type checker flags these as incompatible with the inferred value type. At runtime, this code works perfectly fine since Python dicts accept heterogeneous values. So pyrefly went from reporting one kind of false positive (missing attribute) to a different kind (overly narrow type inference on dict values). Neither set of errors represents actual bugs in the code. The pyright cross-check confirmation (3/3 and 1/1) shows this is a common limitation of static type analysis on dynamically-typed dict patterns, not unique to pyrefly.
Attribution: The change to narrow_key_membership() in pyrefly/lib/alt/narrow.rs now narrows TypedDict union types on "key" in x checks. This changed how self.colors is resolved — previously pyrefly couldn't resolve colors at all (missing-attribute), now it resolves it but correctly identifies that item assignment/indexing on the narrowed type is invalid. The has_dict_like_member() and key_membership_value_type() functions enable better type tracking through containment checks.

pwndbg (+4, -4)

Removed missing-attribute on NoneType: These were false positives. The code checks if not page and returns early before accessing page.objfile, so page is guaranteed non-None at the access points. Removing these is an improvement.
New not-iterable errors: These are false positives. "[stack" in page.objfile is valid Python — strings support __contains__. The error message says the RHS is object, suggesting pyrefly fails to resolve the type of objfile properly. Neither mypy nor pyright flag these.

Overall: Net neutral: 4 false positives removed (missing-attribute on NoneType after proper None guard), 4 false positives added (not-iterable on valid string containment checks). The old errors were wrong because the code guards against None. The new errors are wrong because in is valid on strings. The error quality shifted but didn't improve overall — it traded one class of false positive for another.

Per-category reasoning:

  • Removed missing-attribute on NoneType: These were false positives. The code checks if not page and returns early before accessing page.objfile, so page is guaranteed non-None at the access points. Removing these is an improvement.
  • New not-iterable errors: These are false positives. "[stack" in page.objfile is valid Python — strings support __contains__. The error message says the RHS is object, suggesting pyrefly fails to resolve the type of objfile properly. Neither mypy nor pyright flag these.

Attribution: The changes to narrow_key_membership() and the NarrowOp::Atomic handling in pyrefly/lib/alt/narrow.rs changed how "key" in x narrowing works. This fixed the None-narrowing (removing the missing-attribute false positives) but introduced a new issue where the in operator's type checking now incorrectly reports not-iterable when the RHS type resolves to object (likely because page.objfile's type isn't fully resolved after the new narrowing path).

pandas (+1, -1)

This is a swap of one false positive for another. The old error incorrectly inferred that result (assigned from self.read_dta(path), which calls read_stata and returns a DataFrame) was a StataReader, and thus claimed it had no columns attribute. In reality, result is a DataFrame which does have columns. The new error incorrectly claims in is not supported between Literal['date_col'] and object — the type checker is seeing result.columns as having type object rather than recognizing it as a pandas Index, which does implement __contains__ and supports the in operator at runtime. The removal of the old error is an improvement (pyrefly now correctly recognizes result as a DataFrame), but the addition of the new error is a regression (pyrefly doesn't recognize that DataFrame.columns returns an Index that supports __contains__). Net effect is roughly neutral — one false positive replaced by another false positive of similar severity.
Attribution: The changes in pyrefly/lib/alt/narrow.rs modified how HasKey/NotHasKey narrowing works, adding narrow_key_membership() and has_dict_like_member(). The new not-iterable error likely stems from the changed code path in the HasKey branch (around line 1586-1613) where the narrowed type or the in check validation now produces a different error. The old code path produced a missing-attribute error on StataReader.columns; the new code path correctly resolves the DataFrame type but then incorrectly flags the in operator on the object-typed columns attribute.

spack (+9, -9)

bad-index on object (5 errors): False positives. After the in narrowing change, pyrefly infers pkg.spec as object in the positive branch of "c" in pkg.spec, then reports Cannot index into object when pkg.spec["c"] is accessed. The Spec class supports subscript access. These are pyrefly-only (0/5 mypy, 0/5 pyright).
not-iterable on object (4 errors): False positives. After narrowing, pyrefly infers platform.binary_formats as object in the elif branch, then reports in is not supported between Literal['elf'] and object. The binary_formats attribute is iterable. 0/4 mypy, 1/4 pyright.
Removed missing-attribute on NoneType (9 errors): These were false positives — pyrefly incorrectly inferred platform or pkg.spec as potentially NoneType. Removing them is an improvement.

Overall: This is a net-neutral to slightly regressive change. The 9 removed errors were false positives (improvement), but the 9 new errors are also false positives (regression). The new narrowing logic for TypedDict unions is spilling over to non-TypedDict types, causing object type inference where concrete types should be preserved. The old errors said NoneType has no attribute binary_formats; the new errors say Cannot index into object and in is not supported between Literal['elf'] and object. Both sets are wrong, just wrong in different ways.

Per-category reasoning:

  • bad-index on object (5 errors): False positives. After the in narrowing change, pyrefly infers pkg.spec as object in the positive branch of "c" in pkg.spec, then reports Cannot index into object when pkg.spec["c"] is accessed. The Spec class supports subscript access. These are pyrefly-only (0/5 mypy, 0/5 pyright).
  • not-iterable on object (4 errors): False positives. After narrowing, pyrefly infers platform.binary_formats as object in the elif branch, then reports in is not supported between Literal['elf'] and object. The binary_formats attribute is iterable. 0/4 mypy, 1/4 pyright.
  • Removed missing-attribute on NoneType (9 errors): These were false positives — pyrefly incorrectly inferred platform or pkg.spec as potentially NoneType. Removing them is an improvement.

Attribution: The change to narrow_key_membership() and the modified narrowing logic in pyrefly/lib/alt/narrow.rs (around lines 1586-1640) now narrows the subject type for in checks. This correctly removes the old false positive missing-attribute errors but introduces new false positive bad-index and not-iterable errors because the narrowing appears to degrade non-TypedDict types to object in the negative branch of in checks.

spark (+6, -6)

New bad-index and not-iterable errors on decorator-injected attribute: The @keyword_only decorator dynamically injects self._input_kwargs as a dict. Pyrefly can't see through this decorator, so it types _input_kwargs as object. The new narrowing code now validates in and [] operations on this object type, producing false positive bad-index and not-iterable errors. These are pyrefly-only (0/6 co-reported) and the code works correctly at runtime.
Removed missing-attribute errors on decorator-injected attribute: The old missing-attribute errors on _input_kwargs were also false positives (the attribute exists at runtime via the decorator). Their removal is good, but they've been replaced by equally-wrong errors of a different kind.

Overall: This is a net-neutral-to-slightly-worse change. The old errors (6 missing-attribute on _input_kwargs) were false positives because _input_kwargs is dynamically injected by the @keyword_only decorator. The new errors (5 bad-index + 1 not-iterable on object) are also false positives — they're downstream consequences of pyrefly not understanding the decorator. The type of self._input_kwargs is inferred as object (since pyrefly can't see through the decorator), and the new narrowing code now tries to validate in and [] operations on object, producing these errors. The old errors were more informative (pointing to the root cause: missing attribute), while the new errors are more confusing (claiming you can't index into object when the real issue is the decorator). Both old and new errors are false positives, so this is essentially swapping one set of false positives for another. Neither mypy nor pyright flag any of these lines.

Per-category reasoning:

  • New bad-index and not-iterable errors on decorator-injected attribute: The @keyword_only decorator dynamically injects self._input_kwargs as a dict. Pyrefly can't see through this decorator, so it types _input_kwargs as object. The new narrowing code now validates in and [] operations on this object type, producing false positive bad-index and not-iterable errors. These are pyrefly-only (0/6 co-reported) and the code works correctly at runtime.
  • Removed missing-attribute errors on decorator-injected attribute: The old missing-attribute errors on _input_kwargs were also false positives (the attribute exists at runtime via the decorator). Their removal is good, but they've been replaced by equally-wrong errors of a different kind.

Attribution: The changes to narrow_key_membership() and the NarrowOp::Atomic handling in pyrefly/lib/alt/narrow.rs changed how "key" in x narrowing works. Previously, when pyrefly saw if "x" in self._input_kwargs, it didn't narrow the type of self._input_kwargs (which it inferred as object due to the missing-attribute issue). Now, with the new narrowing logic, pyrefly attempts to narrow self._input_kwargs (typed as object) through the in check. Since object doesn't support __contains__ or __getitem__, the narrowing produces errors like Cannot index into object and in is not supported between Literal['y'] and object. The old code path returned type_info.clone() for non-dict-like types, while the new code applies narrow_key_membership which still returns the original type but then proceeds to check indexing on it.

Suggested fixes

Summary: The new narrow_key_membership() correctly narrows TypedDict unions on 'key in x' checks, but the HasKey/NotHasKey branches unconditionally replace type_info with a reconstructed 'narrowed' value even for non-dict-like types, losing type precision and producing 'object'-typed results that cause false positive not-iterable and bad-index errors.

**1. In the HasKey branch of narrow() in pyrefly/lib/alt/narrow.rs (around line 1586-1613), when the base type has no dict-like member after narrowing (the else branch), return type_info.clone() instead of the reconstructed narrowed value. The issue is that for non-dict-like types (custom classes, strings, object), constructing narrowed via type_info.with_narrow(chain.facets(), narrowed_base.clone()) or type_info.clone().with_ty(narrowed_base) can lose type information. The fix: in the else branch at line ~1612, change narrowed back to type_info.clone(). Similarly, only apply narrow_key_membership to the base type when has_dict_like_member(&base_ty) is true in the first place. Pseudo-code:

let narrowed_base = self.narrow_key_membership(&base_ty, key, true);
if self.has_dict_like_member(&narrowed_base) {
    let mut narrowed = match &resolved_chain {
        Some(chain) => type_info.with_narrow(chain.facets(), narrowed_base.clone()),
        None => type_info.clone().with_ty(narrowed_base.clone()),
    };
    // ... facet narrowing logic ...
    narrowed
} else {
    type_info.clone()  // <-- preserve original for non-dict-like types
}
```**
> Files: [`pyrefly/lib/alt/narrow.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs)
> Confidence: high
> Affected projects: apprise, pwndbg, spack, spark, scrapy, pydantic, pandas, paasta, parso, core
> Fixes: `not-iterable`, `bad-index`, `missing-attribute`, `bad-argument-type`
> Across apprise (5 errors), pwndbg (4 errors), spack (9 errors), spark (6 errors), scrapy (5 errors), pydantic (21 errors), pandas (1 error), paasta (1 error), parso (1 error), and core (1 error), the new false positives all share the same pattern: after an `in` check on a non-TypedDict type, pyrefly infers `object` instead of the actual type, then reports not-iterable or bad-index. The root cause is that the else branch (non-dict-like types) now returns `narrowed` (reconstructed from narrow_key_membership) instead of `type_info.clone()`. For non-dict-like types, narrow_key_membership returns the type unchanged, but wrapping it in with_narrow/with_ty loses existing type context. Restoring `type_info.clone()` for the non-dict-like case preserves the pre-PR behavior for these types while keeping the TypedDict union narrowing improvement.

**2. Apply the same fix to the NotHasKey branch in [`pyrefly/lib/alt/narrow.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs) (around line 1619-1640): only construct the `narrowed` variable and return it when `has_dict_like_member(&base_ty)` is true. In the else branch, return `type_info.clone()` instead of `narrowed`. Pseudo-code:

```rust
let narrowed_base = self.narrow_key_membership(&base_ty, key, false);
if self.has_dict_like_member(&base_ty) {
    let mut narrowed = match &resolved_chain {
        Some(chain) => type_info.with_narrow(chain.facets(), narrowed_base),
        None => type_info.clone().with_ty(narrowed_base),
    };
    // ... facet invalidation logic ...
    narrowed
} else {
    type_info.clone()  // <-- preserve original for non-dict-like types
}
```**
> Files: [`pyrefly/lib/alt/narrow.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs)
> Confidence: high
> Affected projects: parso, spack, pwndbg
> Fixes: `not-iterable`, `bad-index`
> The NotHasKey branch has the same structural issue as HasKey. For non-dict-like types, the reconstructed `narrowed` value replaces the original type_info, potentially losing type precision. This affects the parso regression (where `not in` on a string-typed `.prefix` attribute produces an object-typed result) and spack's elif branches.

**3. In [`key_membership_value_type()`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs) in [`pyrefly/lib/alt/narrow.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs) (around line 710-725), for open TypedDicts where the key is not a known field, the extra_item type defaults to `object`. When this `object` type flows into union members, it produces false positives. Consider returning the subscript inference result for open TypedDicts with unknown keys (similar to the non-TypedDict branch) rather than the raw extra_item type, or at minimum ensure the extra_item type is properly resolved. This would fix the pydantic regression where accessing keys on open TypedDicts after narrowing produces `object` in the value type union.**
> Files: [`pyrefly/lib/alt/narrow.rs`](https://github.com/facebook/pyrefly/blob/main/pyrefly/lib/alt/narrow.rs)
> Confidence: medium
> Affected projects: pydantic, core
> Fixes: `bad-argument-type`, `not-iterable`, `missing-attribute`
> In pydantic, after narrowing a union of TypedDicts via 'items_schema' in schema, some open TypedDict members that don't have 'items_schema' as a declared field contribute `object` (from ExtraItems::Default) to the value type union. This causes 21 new false positives where argument types include `object`. The fix would make key_membership_value_type return a more precise type for open TypedDicts.

</details>

---
Was this helpful? React with 👍 or 👎

<sub>Classification by primer-classifier (20 LLM)</sub>

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.

in check does not narrow TypedDict Union

3 participants