Skip to content

fix [feature] warn when passing str to something that accepts iterable[str] or sequence[str] #673#2293

Open
asukaminato0721 wants to merge 4 commits intofacebook:mainfrom
asukaminato0721:673
Open

fix [feature] warn when passing str to something that accepts iterable[str] or sequence[str] #673#2293
asukaminato0721 wants to merge 4 commits intofacebook:mainfrom
asukaminato0721:673

Conversation

@asukaminato0721
Copy link
Copy Markdown
Contributor

Summary

Fixes #673

Implemented the new string-as-iterable warning and documented it, with checks that fire only when the argument is definitely str and the parameter is Iterable[str]/Sequence[str] without explicitly allowing str.

Test Plan

add test

@meta-cla meta-cla Bot added the cla signed label Feb 3, 2026
@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as draft February 3, 2026 09:38
@github-actions

This comment has been minimized.

@asukaminato0721 asukaminato0721 marked this pull request as ready for review February 3, 2026 13:06
@yangdanny97 yangdanny97 self-assigned this Feb 3, 2026
Copy link
Copy Markdown
Contributor

@yangdanny97 yangdanny97 left a comment

Choose a reason for hiding this comment

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

This seems to change the positions of a bunch of unrelated errors.

I think the change could be smaller and more self-contained.

For now, what if we didn't consider unions or assignability at all, and just made it error if the expected type is exactly Iterable[str] or Sequence[str] and the actual type is exactly str?

For the initial impl we probably don't even need to consider unions.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 3, 2026

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

packaging (https://github.com/pypa/packaging)
+  WARN src/packaging/_musllinux.py:84:28-72: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=src/packaging/_musllinux.py,line=84,col=28,endLine=84,endColumn=72,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

scikit-build-core (https://github.com/scikit-build/scikit-build-core)
+  WARN src/scikit_build_core/builder/_load_provider.py:115:17-20: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN src/scikit_build_core/builder/_load_provider.py:118:59-62: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=src/scikit_build_core/builder/_load_provider.py,line=115,col=17,endLine=115,endColumn=20,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=src/scikit_build_core/builder/_load_provider.py,line=118,col=59,endLine=118,endColumn=62,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

pwndbg (https://github.com/pwndbg/pwndbg)
+  WARN pwndbg/commands/context.py:1508:23-32: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=pwndbg/commands/context.py,line=1508,col=23,endLine=1508,endColumn=32,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

meson (https://github.com/mesonbuild/meson)
+  WARN mesonbuild/backend/vs2010backend.py:1049:13-117: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ERROR mesonbuild/cmake/common.py:179:55-69: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+  WARN mesonbuild/cmdline.py:121:26-40: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN mesonbuild/cmdline.py:141:26-40: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN mesonbuild/interpreter/interpreter.py:2786:34-44: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN mesonbuild/mtest.py:2247:32-33: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:66:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:89:43-85: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:111:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:119:37-40: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:120:43-46: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:126:43-84: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:148:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:153:43-85: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:158:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:163:37-78: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:164:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:184:37-79: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:185:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:210:37-78: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:211:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:216:37-79: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:217:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:225:43-51: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:226:43-51: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:275:43-85: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:276:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:285:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:297:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:314:37-76: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:315:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:327:37-76: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:328:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:340:37-79: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:341:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:353:37-85: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:354:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:366:37-79: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:367:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:379:37-84: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:380:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:392:37-87: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:393:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:405:43-92: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:424:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:429:43-91: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:448:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:453:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:463:43-85: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN unittests/rewritetests.py:478:43-83: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=mesonbuild/backend/vs2010backend.py,line=1049,col=13,endLine=1049,endColumn=117,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::error file=mesonbuild/cmake/common.py,line=179,col=55,endLine=179,endColumn=69,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str
+ ::warning file=mesonbuild/cmdline.py,line=121,col=26,endLine=121,endColumn=40,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=mesonbuild/cmdline.py,line=141,col=26,endLine=141,endColumn=40,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=mesonbuild/interpreter/interpreter.py,line=2786,col=34,endLine=2786,endColumn=44,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=mesonbuild/mtest.py,line=2247,col=32,endLine=2247,endColumn=33,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=66,col=43,endLine=66,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=89,col=43,endLine=89,endColumn=85,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=111,col=43,endLine=111,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=119,col=37,endLine=119,endColumn=40,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=120,col=43,endLine=120,endColumn=46,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=126,col=43,endLine=126,endColumn=84,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=148,col=43,endLine=148,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=153,col=43,endLine=153,endColumn=85,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=158,col=43,endLine=158,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=163,col=37,endLine=163,endColumn=78,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=164,col=43,endLine=164,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=184,col=37,endLine=184,endColumn=79,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=185,col=43,endLine=185,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=210,col=37,endLine=210,endColumn=78,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=211,col=43,endLine=211,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=216,col=37,endLine=216,endColumn=79,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=217,col=43,endLine=217,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=225,col=43,endLine=225,endColumn=51,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=226,col=43,endLine=226,endColumn=51,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=275,col=43,endLine=275,endColumn=85,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=276,col=43,endLine=276,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=285,col=43,endLine=285,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=297,col=43,endLine=297,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=314,col=37,endLine=314,endColumn=76,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=315,col=43,endLine=315,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=327,col=37,endLine=327,endColumn=76,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=328,col=43,endLine=328,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=340,col=37,endLine=340,endColumn=79,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=341,col=43,endLine=341,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=353,col=37,endLine=353,endColumn=85,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=unittests/rewritetests.py,line=354,col=43,endLine=354,endColumn=83,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

... (truncated 13 lines) ...

zulip (https://github.com/zulip/zulip)
+  WARN zerver/lib/markdown/api_arguments_table_generator.py:234:17-66: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN zerver/lib/markdown/api_arguments_table_generator.py:241:17-87: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN zerver/lib/markdown/api_arguments_table_generator.py:246:17-248:18: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN zerver/management/commands/send_test_email.py:53:64-79: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN zerver/management/commands/send_test_email.py:57:80-95: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+ ERROR zerver/management/commands/send_test_email.py:65:67-84: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ::warning file=zerver/lib/markdown/api_arguments_table_generator.py,line=234,col=17,endLine=234,endColumn=66,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=zerver/lib/markdown/api_arguments_table_generator.py,line=241,col=17,endLine=241,endColumn=87,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=zerver/lib/markdown/api_arguments_table_generator.py,line=246,col=17,endLine=248,endColumn=18,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=zerver/management/commands/send_test_email.py,line=53,col=64,endLine=53,endColumn=79,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=zerver/management/commands/send_test_email.py,line=57,col=80,endLine=57,endColumn=95,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::error file=zerver/management/commands/send_test_email.py,line=65,col=67,endLine=65,endColumn=84,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str

schemathesis (https://github.com/schemathesis/schemathesis)
+ ERROR src/schemathesis/pytest/plugin.py:318:38-76: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ERROR src/schemathesis/pytest/plugin.py:335:42-88: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ERROR src/schemathesis/pytest/plugin.py:350:38-352:14: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ::error file=src/schemathesis/pytest/plugin.py,line=318,col=38,endLine=318,endColumn=76,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str
+ ::error file=src/schemathesis/pytest/plugin.py,line=335,col=42,endLine=335,endColumn=88,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str
+ ::error file=src/schemathesis/pytest/plugin.py,line=350,col=38,endLine=352,endColumn=14,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str

mkosi (https://github.com/systemd/mkosi)
+  WARN mkosi/config.py:4701:21-37: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN mkosi/config.py:4722:25-72: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN mkosi/config.py:4728:17-28: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=mkosi/config.py,line=4701,col=21,endLine=4701,endColumn=37,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=mkosi/config.py,line=4722,col=25,endLine=4722,endColumn=72,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=mkosi/config.py,line=4728,col=17,endLine=4728,endColumn=28,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

cwltool (https://github.com/common-workflow-language/cwltool)
+  WARN tests/test_toolargparse.py:218:26-45: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=tests/test_toolargparse.py,line=218,col=26,endLine=218,endColumn=45,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

dulwich (https://github.com/dulwich/dulwich)
+  WARN dulwich/contrib/swift.py:184:24-28: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=dulwich/contrib/swift.py,line=184,col=24,endLine=184,endColumn=28,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

vision (https://github.com/pytorch/vision)
+  WARN test/test_datasets.py:2479:29-85: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=test/test_datasets.py,line=2479,col=29,endLine=2479,endColumn=85,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

core (https://github.com/home-assistant/core)
+ ERROR homeassistant/components/androidtv_remote/media_player.py:186:45-55: No matching overload found for function `list.__init__` called with arguments: (str) [no-matching-overload]
+ ::error file=homeassistant/components/androidtv_remote/media_player.py,line=186,col=45,endLine=186,endColumn=55,title=Pyrefly no-matching-overload::No matching overload found for function `list.__init__` called with arguments: (str)%0A  Possible overloads:%0A  () -> None%0A  (iterable: Iterable[str], /) -> None [closest match]

setuptools (https://github.com/pypa/setuptools)
+  WARN setuptools/_vendor/packaging/_musllinux.py:84:28-72: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN setuptools/_vendor/tomli/_parser.py:342:32-48: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=setuptools/_vendor/packaging/_musllinux.py,line=84,col=28,endLine=84,endColumn=72,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=setuptools/_vendor/tomli/_parser.py,line=342,col=32,endLine=342,endColumn=48,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

werkzeug (https://github.com/pallets/werkzeug)
+ ERROR src/werkzeug/debug/__init__.py:382:49-77: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ::error file=src/werkzeug/debug/__init__.py,line=382,col=49,endLine=382,endColumn=77,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str

isort (https://github.com/pycqa/isort)
+  WARN isort/_vendored/tomli/_parser.py:256:32-48: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN isort/core.py:369:21-41: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=isort/_vendored/tomli/_parser.py,line=256,col=32,endLine=256,endColumn=48,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=isort/core.py,line=369,col=21,endLine=369,endColumn=41,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

mitmproxy (https://github.com/mitmproxy/mitmproxy)
+ ERROR mitmproxy/tools/console/master.py:186:73-76: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ::error file=mitmproxy/tools/console/master.py,line=186,col=73,endLine=186,endColumn=76,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+  WARN ddtrace/debugging/_encoding.py:166:32-36: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN tests/appsec/integrations/flask_tests/test_gunicorn_handlers.py:29:26-78: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=ddtrace/debugging/_encoding.py,line=166,col=32,endLine=166,endColumn=36,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=tests/appsec/integrations/flask_tests/test_gunicorn_handlers.py,line=29,col=26,endLine=29,endColumn=78,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

CPython (Argument Clinic) (https://github.com/python/cpython)
+ ERROR Tools/clinic/libclinic/codegen.py:161:24-37: No matching overload found for function `str.join` called with arguments: (str) [no-matching-overload]
+ ::error file=Tools/clinic/libclinic/codegen.py,line=161,col=24,endLine=161,endColumn=37,title=Pyrefly no-matching-overload::No matching overload found for function `str.join` called with arguments: (str)%0A  Possible overloads:%0A  (iterable: Iterable[LiteralString], /) -> LiteralString [closest match]%0A  (iterable: Iterable[str], /) -> str

pip (https://github.com/pypa/pip)
+  WARN src/pip/_internal/cli/spinners.py:54:44-54: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN src/pip/_vendor/packaging/_musllinux.py:84:28-72: Passing `str` to `Sequence[str]` treats the string as an iterable of characters [string-as-iterable]
+  WARN src/pip/_vendor/tomli/_parser.py:341:32-48: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=src/pip/_internal/cli/spinners.py,line=54,col=44,endLine=54,endColumn=54,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=src/pip/_vendor/packaging/_musllinux.py,line=84,col=28,endLine=84,endColumn=72,title=Pyrefly string-as-iterable::Passing `str` to `Sequence[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?
+ ::warning file=src/pip/_vendor/tomli/_parser.py,line=341,col=32,endLine=341,endColumn=48,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

sphinx (https://github.com/sphinx-doc/sphinx)
+  WARN sphinx/writers/latex.py:1974:17-65: Passing `str` to `Iterable[str]` treats the string as an iterable of characters [string-as-iterable]
+ ::warning file=sphinx/writers/latex.py,line=1974,col=17,endLine=1974,endColumn=65,title=Pyrefly string-as-iterable::Passing `str` to `Iterable[str]` treats the string as an iterable of characters%0A  Did you mean to pass an iterable of strings?

@meta-codesync
Copy link
Copy Markdown
Contributor

meta-codesync Bot commented Feb 4, 2026

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

@yangdanny97
Copy link
Copy Markdown
Contributor

This is a lot of new errors, I'll need to think about how to restrict this so we don't give so many false positives. Additionally, it should probably be off by default

@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

@github-actions github-actions Bot added the stale label Feb 23, 2026
@yangdanny97 yangdanny97 reopened this Mar 11, 2026
@github-actions github-actions Bot removed the stale label Mar 16, 2026
@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

@github-actions github-actions Bot added the stale label Mar 31, 2026
@yangdanny97 yangdanny97 removed their assignment Mar 31, 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

Adds a new diagnostic to Pyrefly that warns when a str is passed to parameters typed as Iterable[str] or Sequence[str], aiming to catch the common footgun where strings are treated as iterables of characters.

Changes:

  • Introduces ErrorKind::StringAsIterable (default severity warn) and emits it during argument type-checking when the pattern is detected.
  • Documents the new error kind and adds a regression test covering Iterable[str] / Sequence[str] and an explicit | str allowlist case.
  • Refactors overload calling to optionally retry without a hint when hint-driven checking produces call errors.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/docs/error-kinds.mdx Adds documentation for the new string-as-iterable diagnostic.
pyrefly/lib/test/calls.rs Adds a test case asserting the new warning message for Iterable[str] and Sequence[str].
pyrefly/lib/alt/overload.rs Updates overload call flow to retry without hint when hint-driven call errors occur.
pyrefly/lib/alt/callable.rs Implements the string-as-iterable warning and threads it through argument checking.
crates/pyrefly_config/src/error_kind.rs Adds StringAsIterable to ErrorKind and assigns default severity warn.

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

Comment on lines +490 to +513
fn check_expr_argument(
&self,
expr: &Expr,
hint: &Type,
range: TextRange,
arg_errors: &ErrorCollector,
call_errors: &ErrorCollector,
tcc: &dyn Fn() -> TypeCheckContext,
) -> (Type, bool) {
if hint.is_any() {
return (
self.expr_infer_type_info_with_hint(expr, None, arg_errors)
.into_ty(),
false,
);
}
let got = self.expr_infer_type_info_with_hint(
expr,
Some(HintRef::new(hint, Some(call_errors))),
arg_errors,
);
let ok = self.check_type(got.ty(), hint, range, call_errors, tcc);
(got.into_ty(), ok)
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

check_expr_argument always returns the inferred type even when check_type fails. Previously expr_with_separate_check_errors would return want on failure (via check_and_return_type_info), which helps prevent cascading type errors and keeps downstream inference consistent. Consider restoring that behavior by returning hint (or got.with_ty(hint.clone())) when ok is false.

Copilot uses AI. Check for mistakes.
Comment on lines +522 to +528
if got.is_error() || got.is_any() || want.is_any() {
return;
}
let got_is_str = matches!(got, Type::ClassType(cls) if cls.is_builtin("str"));
if !got_is_str {
return;
}
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The new warning only triggers when got is exactly Type::ClassType(str), so common cases like passing a string literal ("hello") or a LiteralString-typed value (both represented as literal-string types) won't warn even though they are definitely str at runtime. Consider treating got.is_literal_string() as str for this check (and add a test for the literal-literal case).

Copilot uses AI. Check for mistakes.
Comment on lines +886 to +895
// We want to use our hint to contextually type the arguments, but errors resulting
// from the hint should not influence overload selection. If there are call errors, we
// try again without a hint in case we can still match this overload.
let (call_errors, res, expected_types) = try_call(hint);
let (call_errors, res, expected_types) =
if tparams.is_some() && hint.is_some() && !call_errors.is_empty() {
try_call(None)
} else {
(call_errors, res, expected_types)
};
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

This overload-calling logic now may run callable_infer twice (first with a hint, then without) to avoid hint-driven errors affecting overload matching. Since this is a behavior/perf change beyond the new string-as-iterable warning, it would be good to mention it in the PR description (or split it) so the diagnostic/selection change is reviewed intentionally.

Copilot uses AI. Check for mistakes.
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.

[feature] warn when passing str to something that accepts iterable[str] or sequence[str]

3 participants