Skip to content

Fix missing error for "str" | None union with string literals#2828

Closed
fangyi-zhou wants to merge 8 commits intofacebook:mainfrom
fangyi-zhou:fix-issue-1005
Closed

Fix missing error for "str" | None union with string literals#2828
fangyi-zhou wants to merge 8 commits intofacebook:mainfrom
fangyi-zhou:fix-issue-1005

Conversation

@fangyi-zhou
Copy link
Contributor

@fangyi-zhou fangyi-zhou commented Mar 18, 2026

Summary

At runtime, "Foo" | None raises TypeError because NoneType doesn't support __or__ with string operands. Pyrefly's is_plain_type check was missing Type::None, so this case wasn't flagged.

Fix: Add Type::None => true to is_plain_type in the ForwardRefUnion check.

Tests: Add cases covering None, Any, TypedDict, NamedTuple, Protocol, Literal[1], Callable, LiteralString, and builtin parameterized generics (list[int], dict[str, int]).

Known gap: Builtin parameterized generics like list[int] (types.GenericAlias) error at runtime but List[int] (typing._GenericAlias) does not. Pyrefly represents both identically, so it can't distinguish them. This is documented with a bug marker.

Runtime validation script: https://gist.github.com/fangyi-zhou/ce153247db2e6b71e985f27e9e31c03b

Fixes #1005

Test plan

  • cargo test -p pyrefly test_union — all 44 tests pass
  • python3 test.py --no-test --no-conformance — formatting and linting clean

🤖 Generated with Claude Code

`NoneType` is a plain type at runtime that doesn't support `__or__`
with string arguments, so `"Foo" | None` raises a TypeError just like
`"Foo" | int`. The `is_plain_type` check was missing the `Type::None`
case.

Fixes facebook#1005

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@meta-cla meta-cla bot added the cla signed label Mar 18, 2026
@github-actions

This comment has been minimized.

fangyi-zhou and others added 7 commits March 19, 2026 21:46
Add test cases covering more Type variants (Any, builtin parameterized
generics) and a runtime validation script for reviewers. The Any case
was already handled correctly. Builtin parameterized generics like
list[int] are a known false negative documented with a bug marker.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ble, LiteralString

TypedDict, NamedTuple, and Protocol are class definitions that correctly
get flagged. Literal[1], Callable[[int], str], and LiteralString are
typing special forms that correctly pass through.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…, LiteralString, Type[Any], Never, NoReturn

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Moved to a gist for PR reviewers instead.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@github-actions
Copy link

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@meta-codesync
Copy link

meta-codesync bot commented Mar 21, 2026

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

@yangdanny97 yangdanny97 self-assigned this Mar 21, 2026
Copy link
Contributor

@migeed-z migeed-z left a comment

Choose a reason for hiding this comment

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

Review automatically exported from Phabricator review in Meta.

@meta-codesync
Copy link

meta-codesync bot commented Mar 24, 2026

@yangdanny97 merged this pull request in e91f099.

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.

Don't allow | with stringified annotations unless module has from __future__ import annotations

3 participants