Skip to content

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

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

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 <noreply@anthropic.com>
@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 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…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 <noreply@anthropic.com>
…, LiteralString, Type[Any], Never, NoReturn

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moved to a gist for PR reviewers instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@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
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

2 participants