Skip to content

fix False negative: Pyrefly does not emit an unpacking error in for k, v in dict #3352#3353

Open
asukaminato0721 wants to merge 3 commits into
facebook:mainfrom
asukaminato0721:3352
Open

fix False negative: Pyrefly does not emit an unpacking error in for k, v in dict #3352#3353
asukaminato0721 wants to merge 3 commits into
facebook:mainfrom
asukaminato0721:3352

Conversation

@asukaminato0721
Copy link
Copy Markdown
Contributor

Summary

Fixes #3352

unpack-length checking rejects string-like unpack sources (str, string literals, LiteralString) with bad-unpacking, which catches for k, v in {"x": 1} because dict iteration yields keys.

Test Plan

update && add test

@meta-cla meta-cla Bot added the cla signed label May 9, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review May 9, 2026 20:38
Copilot AI review requested due to automatic review settings May 9, 2026 20:38
@github-actions github-actions Bot added the size/s label May 9, 2026
@asukaminato0721 asukaminato0721 marked this pull request as draft May 9, 2026 20:39
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 a false-negative where tuple-unpacking in for loop targets did not report an unpacking error when the iterated element type was string-like (e.g., dict key iteration yielding str), by adding a string-like guard to unpack-length checking and updating/adding regression tests.

Changes:

  • Add is_string_like helper and emit bad-unpacking when unpack-length checking is applied to string-like types.
  • Update existing string-literal unpacking tests to assert the expected error.
  • Add a new regression test covering for k, v in {"x": 1} and for a, b in list[str].

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pyrefly/lib/alt/solve.rs Add string-like detection and produce BadUnpacking in BindingExpect::UnpackedLength for string-like types.
pyrefly/lib/test/assign.rs Update existing unpack-string tests and add for-loop unpacking regression coverage.

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

Comment thread pyrefly/lib/alt/solve.rs
@github-actions github-actions Bot added size/m and removed size/s labels May 9, 2026
@asukaminato0721 asukaminato0721 marked this pull request as ready for review May 9, 2026 20:44
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@yangdanny97
Copy link
Copy Markdown
Contributor

@asukaminato0721
Copy link
Copy Markdown
Contributor Author

from __future__ import annotations
x: list[ (str, str) ]= []
Expected 1 type argument for `list`, got 2 [[bad-specialization](https://pyrefly.org/en/docs/error-kinds/#bad-specialization)]

@github-actions github-actions Bot added size/m and removed size/m labels May 10, 2026
@github-actions
Copy link
Copy Markdown

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

apprise (https://github.com/caronc/apprise)
- ERROR apprise/plugins/xmpp/adapter.py:161:31-47: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/adapter.py:350:18-34: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/adapter.py:863:18-34: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/adapter.py:911:27-43: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/base.py:175:27-43: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/base.py:202:23-39: Expected 1 type argument for `list`, got 2 [bad-specialization]
- ERROR apprise/plugins/xmpp/base.py:218:33-45: Argument `tuple[Literal['chat', 'groupchat'], str]` is not assignable to parameter `object` with type `str` in function `list.append` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:280:17-76: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:303:17-51: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:315:17-34: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:363:17-28: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:445:17-24: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:762:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:784:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:794:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:847:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:875:17-71: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:933:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1068:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1169:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1259:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1353:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1392:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1446:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1512:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:1569:17-34: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:1611:17-34: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:1630:17-34: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:1654:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:1669:35-40: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.send_message` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:2530:45-50: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:2575:45-50: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:3484:37-42: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:3579:17-47: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:3617:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:3649:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:3849:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:3879:17-44: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:3947:45-62: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:4366:17-69: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
+ ERROR tests/test_plugin_xmpp.py:4433:17-52: Argument `list[str]` is not assignable to parameter `targets` with type `list[tuple[str, str]] | None` in function `apprise.plugins.xmpp.base.NotifyXMPP.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4575:17-63: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4612:21-67: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4644:17-63: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4682:21-67: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4714:17-63: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4767:17-63: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter.__init__` [bad-argument-type]
- ERROR tests/test_plugin_xmpp.py:4803:21-67: Argument `list[tuple[str, str]]` is not assignable to parameter `targets` with type `list[str]` in function `apprise.plugins.xmpp.adapter.SlixmppAdapter._send_keepalive_async` [bad-argument-type]

pywin32 (https://github.com/mhammond/pywin32)
+ ERROR win32/Demos/getfilever.py:28:5-19: Cannot unpack str into 2 values [bad-unpacking]

@github-actions
Copy link
Copy Markdown

Primer Diff Classification

❌ 1 regression(s) | ✅ 1 improvement(s) | 2 project(s) total | +16, -33 errors

1 regression(s) across pywin32. error kinds: bad-unpacking. caused by string_unpack_source(). 1 improvement(s) across apprise.

Project Verdict Changes Error Kinds Root Cause
apprise ✅ Improvement +15, -33 Removed bad-specialization errors type_arguments_for_class_subscript()
pywin32 ❌ Regression +1 bad-unpacking string_unpack_source()
Detailed analysis

❌ Regression (1)

pywin32 (+1)

The win32api module is a C extension from pywin32 that likely lacks complete type stubs. GetFileVersionInfo with the '\VarFileInfo\Translation' parameter returns a list of tuples at runtime, but pyrefly appears to be inferring the return type as str (or the iteration type as str). The new string-unpacking check then fires incorrectly. This is a false positive — the code is correct and works at runtime. The PR's intent (catching for k, v in dict) is valid, but it's producing a false positive here because of incomplete type information for a C extension module. Neither mypy nor pyright flag this, confirming it's a pyrefly-specific false positive.
Attribution: The new string_unpack_source() method in pyrefly/lib/alt/solve.rs and the string-unpacking check added around line 2243 of solve.rs are responsible. The PR adds logic that rejects unpacking when the iterable type is str, LiteralString, or a string literal. The issue is that win32api.GetFileVersionInfo likely returns an untyped or poorly-typed result (possibly Any or str due to missing/incomplete stubs for the win32api C extension), and pyrefly is treating it as str and then flagging the unpacking. The error message says 'Cannot unpack str into 2 values', confirming pyrefly thinks pairs iterates over str characters.

✅ Improvement (1)

apprise (+15, -33)

Removed bad-specialization errors: 6 errors like 'Expected 1 type argument for list, got 2' were false positives from pyrefly misinterpreting list[(str, str)] as having 2 type args instead of 1 tuple type arg. Removing these is a clear improvement.
Removed bad-argument-type errors (27): These were downstream false positives from the same root cause — misinterpreting the annotation led to incorrect type expectations. Removing these is an improvement.
New bad-argument-type errors (15): Now that pyrefly correctly parses list[(str, str)] as list[tuple[str, str]], it flags list[str] arguments as incompatible. However, the constructor is designed to accept list[str] at runtime (converting internally), 0/15 are confirmed by mypy/pyright, and all tests pass. These are likely false positives caused by an imprecise annotation in the library — the parameter should probably be typed as list[str] | list[tuple[str, str]] | None. These new errors are a minor regression.

Overall: Net change: -33 removed (all false positives from misinterpreting parenthesized tuple annotations) and +15 added (all pyrefly-only). The removed errors are clearly improvements — pyrefly was incorrectly parsing list[(str, str)] as having 2 type arguments. The 15 new errors are a mixed case: pyrefly now correctly understands the annotation as list[tuple[str, str]] | None, but the constructor is designed to accept list[str] at runtime (it converts internally). Since 0/15 are confirmed by mypy/pyright, and the code works correctly at runtime, these new errors are likely false positives caused by an imprecise annotation in the library code that other type checkers handle more leniently. Overall, the net reduction of 18 errors (33 removed - 15 added) with the removed errors being clear false positives makes this a net improvement.

Attribution: The change to type_arguments_for_class_subscript() in pyrefly/lib/alt/expr.rs fixed the parsing of parenthesized tuple annotations like list[(str, str)], which removed the 6 bad-specialization and 27 downstream bad-argument-type errors. However, now that pyrefly correctly understands the type as list[tuple[str, str]] | None, it flags list[str] arguments as incompatible, producing 15 new errors. The string unpacking changes in pyrefly/lib/alt/solve.rs are unrelated to these new errors.

Suggested fixes

Summary: The pywin32 regression is a single pyrefly-only bad-unpacking error caused by incomplete type stubs for the win32api C extension (GetFileVersionInfo returns Any/str instead of list[tuple]), triggering the new string-unpacking check; the check itself is working as designed.

1. In string_unpack_source() in pyrefly/lib/alt/solve.rs, add a guard to skip the string unpacking check when the type originates from an Any context. Specifically, before the string_unpack_source check around line 2243, add a condition: if the iterable type was produced by iterating over a type that flowed from Any (e.g., the iterate() call on the container returned str because the container was Any), skip the check. Alternatively, a simpler fix: in the UnknownLen arm of the match, only emit the error when the SizeExpectation is Eq or Ge (which it already does), but also check that the type is not str obtained from iterating Any. This could be done by checking if the original iterable (before iteration) is Any, and if so, not applying the string unpack check.

Files: pyrefly/lib/alt/solve.rs
Confidence: medium
Affected projects: pywin32
Fixes: bad-unpacking
The win32api.GetFileVersionInfo function likely has no proper type stubs, so its return type flows as Any or gets inferred as str somewhere in the chain. The string_unpack_source check then fires on str with UnknownLen, producing a false positive. However, this is really a type stubs issue — the check is correct for properly-typed code. The fix would prevent false positives when type information is incomplete, but the root cause is missing stubs. Only 1 error in 1 project is affected, so this is low priority.


Was this helpful? React with 👍 or 👎

Classification by primer-classifier (2 LLM)

@asukaminato0721
Copy link
Copy Markdown
Contributor Author

Looks like the problem with tuple handling.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False negative: Pyrefly does not emit an unpacking error in for k, v in dict

3 participants