Pyrefly v1.2.0-dev.1
Pre-releaseRelease date: July 1, 2026
About dev releases
Dev releases (versions likeX.Y.Z-dev.N) are non-stable snapshots cut periodically from trunk. They give early adopters a chance to try in-progress features and surface issues before the next stable release, but they don't carry the same stability or compatibility guarantees as a stable release — don't pin production projects to a dev version.
Pyrefly v1.2.0-dev.1 bundles 235 commits from 38 contributors.
✨ New & Improved
Type Checking
- Attrs classes are now fully supported with comprehensive field synthesis, validation, and special-method generation. Pyrefly recognizes
@attr.s,@define,@frozen, and their variants, handling field specifiers (attr.ib(),field()), converters, validators, defaults, and private-field aliasing. See the new attrs documentation for details. isinstancenarrowing now consumes dynamic uncertainty before intersecting with the target type, sox: Anynarrowed byisinstance(x, C)refines toCinstead of preserving theAny. This closes a type-safety hole while sacrificing the gradual guarantee in exchange for more precise runtime-evidence-based narrowing.- TypedDict
.get()and.pop()methods with literal defaults now preserve the field type when the default is assignable to it, so.get("x", "b")on aLiteral["a", "b"]field returnsLiteral["a", "b"]instead ofLiteral["a", "b"] | str. - Unannotated class attributes are now inferred by unioning the types of all assignments in the constructor, instead of taking the type of the first assignment.
Language Server
- Baselined errors (stored in a separate baseline file) now appear as hints in the IDE instead of errors, making it easier to distinguish new issues from known technical debt.
- Document symbol search now works in editors like Helix that don't support hierarchical symbols, returning a flat
SymbolInformationlist with dotted container names. - Semantic tokens are now emitted for names bound by
with ... asandexcept ... as, so those variables are syntax-highlighted consistently with other local bindings. - Go-to-definition on non-Python source files (e.g.,
.thrift) now navigates directly to the symbol definition via text search instead of the top of the file, and works for nested attribute/enum member access. - The unused-type-ignore rule detects
# type: ignorecomments that suppress no errors, helping clean up stale suppressions (off by default; enable withunused-type-ignore = "error"inpyrefly.toml). - Cross-file diagnostics now refresh on save in strict-spec LSP clients like Zed, which previously needed a language-server restart to pick up changes in imported files.
Stubgen
- Multi-line parenthesized annotations, values, and return types are now re-wrapped in
(...)when emitted, preventingIndentationErrorin the generated stubs. - Callable-typed values with differing return types across overloads are now rendered as
Callable[..., Incomplete]instead of bareIncomplete, preserving callability information. - Implicit class variables (bare assignments in a class body) are now annotated as
ClassVar[...]in emitted stubs, correctly distinguishing them from instance attributes. __all__is now preserved verbatim when it's a static list/tuple literal, so re-exports survive in the stub.- Async generators are emitted as plain
defwithAsyncGeneratorreturn annotations instead ofasync def, following the typeshed convention and fixing coroutine-vs-generator confusion.
Tensor Shape Types
- The
pyrefly-shape-extensionsandpyrefly-torch-stubspackages are now published to PyPI alongside Pyrefly releases, providing runtime helpers and shape-aware PyTorch stubs for tensor shape type checking. - The shape DSL now supports
D[N]orD(N)runtime wrappers for dimension expressions, allowing evaluated annotations to survive Python runtime while preserving symbolic arithmetic. assert_shaperuntime helper validates tensor shapes at runtime, falling back to rank-only checks for symbolic shapes.
🐛 Bug fixes
We closed 20 bug issues this release 👏
- #3867: Fixed a regression where
isinstancenarrowing silently stopped working after a sibling branch in the same if/elif/else chain narrowed a different variable and terminated. The lazy-builtins optimization incorrectly liftedisinstanceinto the fork base, producing a degenerate Phi that resolved toNeverand broke downstream narrowing. - #3893: Fixed a stack overflow crash when checking self-referential protocols with overloaded methods whose
self:annotations reference the protocol itself. A new per-thread cycle guard prevents infinite recursion during overload filtering. - #3841: Unpacking a TypeVar bounded by a tuple (e.g.,
Z: tuple[str, int]) now preserves positional element types instead of collapsing to a union.u, v = xwherex: Znow revealsu: str, v: intinstead ofu: int | str, v: int | str. - #3787: TypedDict
.get(key, "literal")on a non-required key now returns the field type when the literal default is assignable to it, instead of widening tofield_type | str. - #3561: Functions decorated with
@no_type_checkno longer emitunannotated-returndiagnostics, since the decorator explicitly opts the function out of type checking. - #3900: Dataclass and Pydantic BaseModel fields named
"self"are no longer incorrectly flagged withbad-keyword-argumentandbad-argument-typeon construction. The instance receiver is now named__dataclass_self__when a field namedselfexists. - #3881: SQLAlchemy declarative base classes with
kw_only=Truenow correctly apply the keyword-only constraint to subclass fields, fixing false "field without default may not follow field with default" errors. - #2858: Match subjects without a real narrowing name (e.g.,
match f(x):) now carry direct fallthrough narrows across cases, and the final capture forwards to the already-evaluated subject narrowed by previous cases. - #3445: Packages with a PEP 561
py.typedmarker no longer triggeruntyped-importrecommendations, even when imported as submodules. - #3954: The
typings/directory under the config root is now auto-discovered on the CLI path (not just in the IDE), fixing a divergence where CLI and IDE had different stub search paths for the same config. - And more! #3879, #3891, #3293, #3928, #3688, #3890, #3912, #3945, #3926, #3924
Thank-you to all our contributors who found these bugs and reported them! Did you know this is one of the most helpful contributions you can make to an open-source project? If you find any bugs in Pyrefly we want to know about them! Please open a bug report issue here.
📦 Upgrade
pip install --upgrade pyrefly==1.2.0-dev.1How to safely upgrade your codebase
Upgrading the version of Pyrefly you're using or a third-party library you depend on can reveal new type errors in your code. Fixing them all at once is often unrealistic. We've written scripts to help you temporarily silence them. After upgrading, follow these steps:
pyrefly check --suppress-errors- Run your code formatter of choice
pyrefly check --remove-unused-ignores- Repeat until you achieve a clean formatting run and a clean type check.
This will add # pyrefly: ignore comments to your code, enabling you to silence errors and return to fix them later. This can make the process of upgrading a large codebase much more manageable.
Read more about error suppressions in the Pyrefly documentation.
🖊️ Contributors this release
@stroxler, @shobhitmehro, @yangdanny97, @jorenham, @grievejia, generatedunixname2066905484085733, @connernilsen, @asukaminato0721, @samwgoldman, generatedunixname949130641157030, @kinto0, @NathanTempest, @ericzhonghou, @rchen152, @javabster, @QEDady, @mikeleppane, @markselby9, @aodihis, @arthaud, @DibbayajyotiRoy, Myles Matz, @ndmitchell, David Tolnay, @Kartikey077, pratved64, @maggiemoss, @goutamadwant, @kz357, @nitishagar, @hrolfurgylfa, @olekuhlmann, @JakobDegen, @lesbass, @fangyi-zhou, @ibraheemshaikh5, @alexander-beedie, @xaskii
Please note: These release notes summarize major updates and features. For brevity, not all individual commits are listed.