Pyrefly Error Kinds
An error kind categorizes an error by the part of the typing specification that an error is related to. Every error has exactly one kind.
The main use of error kinds is as short names ("slugs") that can be used in error suppression comments.
Coming from another type checker? See the mypy error-code mapping and the Pyright diagnostic mapping for how those diagnostics correspond to the kinds below.
Diagnostics have several possible severity levels, which can be configured in pyrefly.toml or via CLI:
- ignore: the diagnostic is not emitted
- hint: in the IDE, unused code is greyed out and deprecated names are struck through, and any other diagnostic set to
hintshows up blue likeinfo.pyrefly checkdoesn't mention it unless--min-severity hintis passed - info: the diagnostic shows up blue in the IDE
- warn: the diagnostic shows up yellow in the IDE
- error: the diagnostic shows up red in the IDE
The default severity for diagnostics is error unless otherwise noted. Diagnostics with default severity ignore must be explicitly enabled to be emitted.
By default, pyrefly check only displays diagnostics with severity error or higher.
Use the min-severity config option or --min-severity CLI flag to also show lower-severity diagnostics (e.g. --min-severity warn).
abstract-method-call
This error is raised when code attempts to invoke a method decorated with
@abstractmethod. Abstract methods have no implementation, so calling them is
always invalid, even if the signature would otherwise match.
from abc import ABC, abstractmethod
class Base(ABC):
@classmethod
@abstractmethod
def build(cls) -> "Base": ...
Base.build()
# Cannot call abstract method `Base.build` [abstract-method-call]
abstract-method-with-implementation
Default severity: ignore
Pyrefly emits this error when a method decorated with @abstractmethod contains a
non-placeholder implementation. Abstract methods may provide an implementation at
runtime, so this diagnostic is disabled by default.
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def run(self) -> None:
print("run") # abstract-method-with-implementation
@abstractmethod
def stop(self) -> None:
raise NotImplementedError # OK: canonical abstract placeholder
assert-type
An assert-type error is raised when a typing.assert_type() call fails.
assert-type-unspellable-subtype
An assert-type-unspellable-subtype error is raised when a typing.assert_type() call fails
because Pyrefly inferred a more precise type than any type annotation can express. The inferred
type is a subtype of the asserted type, but the two are not equivalent. For example, an
isinstance check can narrow a value to an intersection, which has no annotation syntax:
from typing import assert_type
class A: ...
class B: ...
def f(x: A):
if isinstance(x, B):
# The inferred type is `A & B`.
assert_type(x, B) # assert-type-unspellable-subtype
This is a sub-kind of assert-type, so configuring or suppressing the parent kind
also applies to this diagnostic. Suppressing only assert-type-unspellable-subtype keeps other
assert_type() failures visible.
attribute-assign-on-never
Default severity: warn
An attribute assignment on an object with type Never always succeeds. Such an attribute
assignment may indicate a bug, such as an object being accidentally narrowed to Never:
class A:
x: int
def f(a: A):
# Since `A.x` is annotated as `int`, narrowing to `None` makes the type of `a` `Never`.
# A possible fix is to change the annotation to `int | None`.
if a.x is None:
a.x = 0 # attribute-assign-on-never
bad-argument-count
This error arises when a function is called with the wrong number of arguments.
def takes_three(one: int, two: int, three: int) -> complex:
...
takes_three(1, 2, 3, 4) # Expected 3 positional arguments, got 4 [bad-argument-count]
Note that missing-argument will be raised if pyrefly can identify that
specific arguments are missing. As such, this error is more likely to appear
when too many args are supplied, rather than too few.
This example shows both kinds of errors:
from typing import Callable
def apply(f: Callable[[int, int], int]) -> int:
return f(1) # Expected 1 more positional argument [bad-argument-count]
apply() # Missing argument `f` in function `apply` [missing-argument]
bad-argument-type
This error indicates that the function was called with an argument of the wrong type.
def example(x: int) -> None:
...
example("one") # Argument `Literal['one']` is not assignable to parameter `x` with type `int` in function `example` [bad-argument-type]
This can also happen with *args and **kwargs:
def bad_args(*args: int) -> None:
...
bad_args(1, "two") # Argument `Literal['two']` is not assignable to parameter with type `int` in function `bad_args` [bad-argument-type]
def bad_kwargs(**kwargs: int) -> None:
...
bad_kwargs(x=1, y="two") # Keyword argument `y` with type `Literal['two']` is not assignable to kwargs type `int` in function `bad_kwargs` [bad-argument-type]
bad-assignment
The most common cause of this error is attempting to assign a value that conflicts with the variable's type annotation.
x: str = 1 # `Literal[1]` is not assignable to `str` [bad-assignment]
However, it can occur in several other situations.
Here, x is marked as Final, so assigning a new value to it is an error.
from typing import Final
x: Final = 1
x = 2 # `x` is marked final [bad-assignment]
In another case, attempting to annotate an assignment to an instance attribute raises this error.
class A:
x: int
a = A()
a.x: int = 2 # Type cannot be declared in assignment to non-self attribute `a.x` [bad-assignment]
bad-class-definition
This error indicates that there is something wrong with the class definition.
It tends to be a bit rarer, since most issues would be tagged with other error kinds, such as
annotation-mismatch or one of the function errors.
Inheritance has its own complexities, so it has its own error kind called invalid-inheritance.
One place you may see it is dynamic class generation:
from enum import Enum
Ex = Enum("Ex", [("Red", 1), ("Blue", 2), ("Red", 3)]) # Duplicate field `Red` [bad-class-definition]
However, it is best practice to use the class syntax if possible, which doesn't treat duplicate names as an error.
bad-context-manager
This error occurs when a type that cannot be used as a context manager appears in a with statement.
class A:
def __enter__(self): ...
with A(): ... # `A` is missing an `__exit__` method!
bad-dataclass-descriptor
A dataclass field is typed as a descriptor whose read-back type does not match what the
synthesized __init__ writes.
A data descriptor defines __set__ and/or __delete__.
If it also defines __get__, reading the field yields the __get__ return
type while writing it goes through __set__, so the two must agree:
from dataclasses import dataclass
class Desc:
def __get__(self, obj, cls) -> int: ...
def __set__(self, obj, value: str) -> None: ...
@dataclass
class C:
x: Desc = Desc() # `__get__` returns `int`, but `__set__` accepts `str` [bad-dataclass-descriptor]
A non-data descriptor defines __get__ but neither __set__ nor __delete__. It does not
take priority over the instance dictionary, so the synthesized __init__ writes straight into
that dictionary and shadows the class-level descriptor. Every later read returns the raw value
rather than calling __get__:
from dataclasses import dataclass
class Desc:
def __get__(self, obj, cls) -> int: ...
@dataclass
class C:
x: Desc = Desc() # instance attribute shadows the descriptor [bad-dataclass-descriptor]
bad-dunder-all
This error occurs when __all__ is explicitly defined for a module but contains an entry that cannot be found in the module's definitions, wildcard imports, or submodules (for __init__.py files).
__all__ = ["x", "y"] # Name `y` is listed in `__all__` but is not defined in the module
x = 5
To fix this error, either define the missing name or remove it from __all__:
__all__ = ["x", "y"]
x = 5
y = 10 # Now `y` is defined
bad-function-definition
Like bad-class-definition, this error kind is uncommon because other error kinds are used for more specific issues.
For example, argument order is enforced by the parser, so def f(x: int = 1, y: str) is a parse-error.
bad-index
Attempting to access a container with an incorrect index. This only occurs when Pyrefly can statically verify that the index is incorrect, such as with a fixed-length tuple.
def add_three(x: tuple[int, int]) -> int:
return x[0] + x[1] + x[2] # Error: index 2 is out of range.
Pyrefly also knows the keys of TypedDicts, but those have their own error kind.
bad-instantiation
This error occurs when attempting to instantiate a class that cannot be instantiated, such as a protocol:
from typing import Protocol
class C(Protocol): ...
C() # bad-instantiation
bad-keyword-argument
bad-keyword-argument pops up when a keyword argument is given multiple values:
def f(x: int) -> None:
pass
f(x=1, x=2)
However, this is often accompanied by a parse-error for the same issue.
bad-match
This error is used in two cases.
The first is when there is an issue with a match statement. For example, Ex only has 2 fields but the case lists 3:
class Ex:
__match_args__ = ('a', 'b')
def __init__(self, a: int, b: str) -> None:
self.a = a
self.b = b
def do(x: Ex) -> None:
match x:
case Ex(a, b, c):
print("This is an error")
It is also used when __match_args__ is defined incorrectly. It must be a tuple of the names of the class's attributes as literal strings.
For class Ex in the previous example, __match_args__ = ('a', 'c') would be an error because Ex.c does not exist.
bad-override
When a subclass overrides a field or method of its base class, care must be taken that the override won't cause problems.
Some of these are obvious:
class Base:
def f(self, a: int) -> None:
pass
class NoArg(Base):
def f(self) -> None:
pass
class WrongType(Base):
def f(self, a: str) -> None:
pass
def uses_f(b: Base) -> None:
b.f(1)
These errors are rather obvious: uses_f will fail if given a NoArg or WrongType instance, because those methods don't expect an int argument!
The guiding idea here is the Liskov Substitution Principle, the idea that a subclass can stand in for a base class at any point without breaking the program.
This can be a little subtle at first blush. Consider:
class Base:
def f(self, a: int) -> None:
pass
class Sub(Base):
def f(self, a: float) -> None:
pass
Is this OK? Yes! int is treated as a subclass of float, or to put it another way, a function that accepts float can accept every int.
That means everywhere that we call Base.f can safely call Sub.f.
The opposite case, where Base.f takes float and Sub.f takes int, is an error because Sub.f cannot accept every float value.
Note that bad overrides caused by inconsistent parameter names are separately reported as bad-override-param-name, and some overrides involving mutable attributes are reported as bad-override-mutable-attribute.
bad-override-mutable-attribute
Arises when a subclass narrows the type of a mutable (read-write) attribute inherited from a parent class. Mutable attributes require invariant types — the child's type must be exactly compatible with the parent's — because the attribute can be both read and written through a reference to the parent class.
class Base:
x: int | str
class Sub(Base):
x: int # Error: narrows the type
This is unsafe because code that holds a Base reference can write any int | str value to x, but Sub only expects int:
def f(b: Base) -> None:
b.x = "hello" # valid for Base, but breaks Sub's invariant
f(Sub())
This is a sub-kind of bad-override: suppressing bad-override also suppresses this error.
Other type checkers like mypy do not enforce this check by default, so this error kind can be selectively disabled if desired.
bad-override-param-name
Arises when a subclass overrides a method of its base class while changing the name of a positional parameter.
This is a sub-kind of bad-override: suppressing bad-override also suppresses this error.
Changing the name of a parameter breaks callers that pass in an argument by name:
class Base:
def f(self, a: int) -> None:
pass
class Sub(Base):
def f(self, b: int) -> None:
pass
def f(base: Base):
base.f(a=0)
f(Sub()) # oops!
bad-param-name-override
Deprecated: this error code has been renamed to bad-override-param-name. The old name is still accepted in suppression comments and configuration for backwards compatibility.
bad-raise
In a raise statement of the form raise x from y, x must be an exception, and y must be an exception or None.
def bad_raise() -> None:
raise Exception() # ok
raise 1 # error
raise Exception() from None # ok
raise Exception() from Exception() # ok
raise Exception() from 1 # error
bad-return
Arises when a function does not return a value that is compatible with the function's return type annotation.
def bad_return() -> None:
return 1
Real-world examples are often less obvious, of course, due to complex control flow and type relationships.
This error is also raised for generator functions:
from typing import Generator
# Generator has 3 types: the yield, send, and return types.
def bad_gen() -> Generator[int, None, str]:
yield 1
return 2 # should be a str!
bad-singledispatch-register
A functools.singledispatch implementation is registered with a dispatch type that is not a subtype of the fallback function's first parameter. Since that parameter defines the function's declared domain, the registration is outside it and can never be dispatched to through type-correct calls (often meaning the fallback was annotated too narrowly).
from functools import singledispatch
@singledispatch
def f(arg: int) -> None: ...
@f.register
def _(arg: str) -> None: ... # bad-singledispatch-register: `str` is not a subtype of `int`
bad-specialization
"Specialization" refers to instantiating a generic type with a concrete type.
For example, list is a generic type, and list[int] is that type specialized with int.
Each generic type has an expected number of type vars, and each type var can be bound or constrained.
Attempting to use specialize a generic type in a way that violates these specifications will result in a bad-specialization error:
x: list[int, str] # Error: expected 1 type argument, got 2.
class A[T: str]: ...
y: A[int] # Error: `int` is not assignable to `str`.
bad-typed-dict
This error is reported when a TypedDict definition includes an unsupported keyword argument.
from typing import TypedDict
# This is an error because `foo` is not a valid keyword.
class InvalidTD(TypedDict, foo=1):
x: int
# This is valid.
class ValidTD(TypedDict, total=False):
x: int
bad-typed-dict-key
This error arises when TypedDicts are used with incorrect keys, such as a key that does not exist in the TypedDict.
from typing import TypedDict
class Ex(TypedDict):
a: int
b: str
def test(x: Ex) -> None:
# These two keys don't exist
x.nope
x["wrong"]
# TypedDict keys must be strings!
x[1]
bad-unpacking
An error caused by unpacking, such as attempting to unpack a list, tuple, or iterable into the wrong number of variables.
def two_elems() -> tuple[int, str]:
return (1, "two")
a, b, c = two_elems()
Note that pyrefly can only report this error if it knows how many elements the thing being unpacked has.
# A bare `tuple` could have any number of elements
def two_elems() -> tuple:
return (1, "two")
a, b, c = two_elems()
column-schema-mismatch
A Polars DataFrame's data columns do not name exactly the columns declared by its schema=. A declared schema is the authoritative column set, so a data column that is missing from it or absent from the data raises ValueError or KeyError at runtime.
import polars as pl
pl.DataFrame({"x": [1]}, schema={"a": pl.Int64}) # data column `x` is not in the schema, and `a` has no data
column-type-mismatch
A Polars DataFrame column literal has an element whose type does not fit the column's dtype. Polars infers the dtype from the first element and requires every later element to fit it, so a mismatch raises TypeError at runtime.
import polars as pl
pl.DataFrame({"a": [1, 2.0]}) # column `a` is `int`, so the `float` does not fit
deprecated
Default severity: warn
This warning occurs on usage of a deprecated class or function.
from warnings import deprecated
@deprecated("deprecated")
def f(): ...
f() # deprecated!
direct-abstract-base-instantiation
Default severity: warn
This diagnostic is raised when code instantiates a class that directly extends
abc.ABC or directly uses abc.ABCMeta, even if the class has no abstract
methods. Python permits such classes to be instantiated, and some libraries use
ABC without prohibiting construction.
division-by-zero
Default severity: warn
Division, floor division, or modulo by a literal zero value. This catches cases where the divisor is the literal 0 or a variable with type Literal[0], which would always raise ZeroDivisionError at runtime.
x = 10 / 0 # error: division by zero
y = 10 // 0 # error: division by zero
z = 10 % 0 # error: division by zero
duplicate-column
A Polars operation produces more than one column with the same name. Pyrefly reports this for projections with duplicate output names and join suffix collisions that Polars rejects at execution.
import polars as pl
df = pl.DataFrame({"a": [1]})
df.select("a", "a")
empty-body
Default severity: ignore
A function body consists only of ... even though the function declares a
return type that None is not assignable to.
Empty ellipsis bodies are allowed in stub files, protocol methods, abstract
methods, overload declarations, and if TYPE_CHECKING blocks.
def f() -> int: ... # error: empty body
explicit-any
Default severity: ignore
typing.Any was written explicitly in an annotation. This diagnostic is useful for codebases that want to prevent Any from silently allowing arbitrary operations.
from typing import Any
def f(x: Any) -> Any: # explicit-any on both annotations
return x
# Fix:
def f(x: object) -> object:
return x
explicit-any-use
Default severity: ignore
A value's type is an explicit Any, which allows arbitrary operations on it without any checking. The Any usually comes from an annotation, either here or in a library: a parameter annotated Any, or a function like json.loads that returns Any.
The annotation itself is reported as explicit-any.
import json
from typing import Any
class Event:
name: str
def handle(event: Any) -> None:
name = event.name # `name`, and attribute `name`, are each an explicit `Any`
print(event) # the type of this argument is an explicit `Any`
config = json.loads(open("config.json").read()) # the type of `config` is an explicit `Any`
# Fix: give the values precise types.
def handle_typed(event: Event) -> None:
name = event.name
fallback-search-path-relative-import
Default severity: warn
This error is emitted when an unqualified import is not found on the module search path or site package path and resolves only through Pyrefly's fallback search path heuristic. The import may then fail at runtime. This commonly indicates a missing or misconfigured search path.
Add the module to the search path or site package path. If the module belongs to the same package, use an explicit relative or fully qualified import:
# pkg/main.py
import helper # fallback-search-path-relative-import
# Fix when helper.py belongs to pkg:
from . import helper
# or: import pkg.helper
implicit-abstract-class
Default severity: ignore
Pyrefly emits this error when a class that inherits from an abstract class but is not itself explicitly abstract (for example, it does not directly inherit from abc.ABC or use abc.ABCMeta) has unimplemented abstract members. Such classes cannot be instantiated at runtime. To resolve the issue, explicitly declare the class as abstract or provide concrete implementations.
from abc import ABC, abstractmethod
class A(ABC):
@abstractmethod
def f(self) -> int: ...
class B(A): ... # Error: `B` cannot be instantiated due to unimplemented abstract method `f`.
Two possible fixes:
# 1. If `B` is not meant to be instantiable, explicitly declare it as abstract.
class B(A, ABC): ...
# 2. Or, implement `f` to make `B` instantiable.
class B(A):
def f(self) -> int:
return 0
implicit-any
Default severity: ignore
Umbrella error code for cases where Pyrefly infers an implicit Any. Suppressing or enabling implicit-any cascades to every sub-kind below — useful when you want to surface (or silence) all implicit-Any cases without listing them individually.
Most concrete diagnostics are emitted under one of the more specific sub-kinds:
implicit-any-attributeimplicit-any-empty-containerimplicit-any-lambdaimplicit-any-parameterimplicit-any-type-argument
implicit-any-attribute
Default severity: ignore
An attribute is implicitly inferred to be Any | None (or tuple[Any, ...]) because it was assigned None or () in a method without an explicit type annotation. Add an annotation so the attribute's type is determined at the assignment site rather than leaking Any into downstream uses.
This is a sub-kind of implicit-any: suppressing implicit-any also suppresses this error.
class C:
def __init__(self):
self.a = None # implicit-any-attribute (type: None | Any)
self.b = () # implicit-any-attribute (type: tuple[Any, ...])
# Fix:
class C:
a: int | None
b: tuple[int, ...]
def __init__(self):
self.a = None
self.b = ()
implicit-any-empty-container
Default severity: ignore
An empty container literal ([], {}) couldn't be inferred from context and was pinned to a container of Any. Provide a type annotation, initialize with a non-empty value, or use the value in a way that lets Pyrefly infer its element type.
This is a sub-kind of implicit-any: suppressing implicit-any also suppresses this error.
x = [] # implicit-any-empty-container — type: list[Any]
# Fix:
x: list[int] = []
# or
x = [1, 2, 3]
implicit-any-lambda
Default severity: ignore
A lambda parameter whose type cannot be inferred is treated as an implicit Any, hiding type errors in the lambda body and at its call sites.
When the lambda is used in a typed context (e.g. assigned to a Callable or passed to a function that expects one), the parameters are inferred from that context and no error is reported.
f = lambda x: x # error: implicit-any-lambda
from typing import Callable
g: Callable[[int], int] = lambda x: x # OK: `x` is inferred as `int` from context
implicit-any-parameter
Default severity: ignore
A function parameter has no type annotation, so Pyrefly treats it as Any. Add an annotation. The self and cls parameters of methods are excluded from this check.
def f(x):
return x + 1
# Fix:
def f(x: int) -> int:
return x + 1
implicit-any-type-argument
Default severity: ignore
A generic class, type alias, or special form (tuple, Callable, type) was used without explicit type arguments, so Pyrefly defaulted the missing type parameters to Any. Provide explicit arguments, or declare a default for the relevant type variable.
This is a sub-kind of implicit-any: suppressing implicit-any also suppresses this error.
def f(xs: list) -> tuple: # implicit-any-type-argument on both `list` and `tuple`
return tuple(xs)
# Fix:
def f(xs: list[int]) -> tuple[int, ...]:
return tuple(xs)
implicit-bool
Default severity: ignore
This error is emitted when a non-bool value is used in a boolean context. Enable it to require
explicit checks for values whose truthiness could conflate distinct cases, such as None and zero
or None and an empty container. An explicit bool(...) conversion is allowed.
def get_temperature() -> int | None:
return 0
temperature = get_temperature()
if not temperature: # implicit-bool
raise RuntimeError("Temperature sensor unavailable")
# Fix:
if temperature is None:
raise RuntimeError("Temperature sensor unavailable")
implicit-import
Default severity: warn
This error is emitted when a submodule is accessed through a parent package without being explicitly imported in the current file.
While Python’s global module cache (sys.modules) might allow this to work if another part of your program performed the import earlier, relying on this side effect is fragile. If that external code is refactored or removed, your code will crash at runtime with an AttributeError.
To fix this, always use an explicit import for the submodule you want to access.
import urllib
# error: 'urllib' has no attribute 'request' (unless imported elsewhere)
urllib.request.urlopen(...)
# Fix:
import urllib.request
urllib.request.urlopen(...)
implicit-reexport
Default severity: ignore
This error is emitted when you import a name from a module that only made it
available through a plain import or from ... import ... statement. Per the
typing spec,
such a name is not part of the module's public interface, so relying on it is
fragile: the intermediate module may stop importing it at any time.
A module explicitly re-exports a name when it is redundantly aliased
(from x import y as y or import x as x), listed in __all__, or brought in
via a wildcard (from x import *).
# foo.py
a = 1
# bar.py
from foo import a # `a` is not re-exported from `bar`
# baz.py
from bar import a # error: `a` is not exported from module `bar`
# Fix in bar.py:
from foo import a as a # or add `a` to `__all__`
implicitly-defined-attribute
Default severity: ignore
An attribute was implicitly defined by assignment to self in a method that we
do not recognize as always executing. We recognize constructors and some test
setup methods; we will emit an error for any attributes defined by assignment
in other methods.
class C:
def __init__(self):
self.x = 0 # no error, `__init__` always executes
def f(self):
self.y = 0 # error, `y` may be undefined if `f` does not execute
inactive-code
Default severity: hint
A block of code that is inactive in the current type-checking configuration. Pyrefly does not check inactive code, and the IDE greys it out.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from collections.abc import Sequence
else:
Sequence = list # inactive-code: This code is inactive for the current type-checking configuration
incompatible-comparison
Default severity: ignore
This error is raised when Pyrefly can prove that an equality (==) or inequality
(!=) comparison is made between incompatible built-in types (for example, int
versus str).
x: int = 1
y: str = ""
if x == y: ... # Comparison `==` between incompatible types `int` and `str` [incompatible-comparison]
incompatible-overload-argument
This error is raised when we match an overloaded function against a type containing a type variable and cannot find a consistent type for that variable.
This usually indicates a higher-order call where each overload branch implies incompatible constraints for the same type variable.
from typing import Callable, overload
@overload
def f(x: int) -> float: ...
@overload
def f(x: str) -> str: ...
def f(x): ...
def project[S](func: Callable[[S], S], y: S) -> Callable[[], S]:
return lambda: y
project(f, 1) # Overload type was not compatible with solved type variables: S = int
incompatible-overload-residual
Deprecated: this error code has been renamed to incompatible-overload-argument. The old name is still accepted in suppression comments and configuration for backwards compatibility.
inconsistent-inheritance
When a class inherits from multiple base classes, the inherited fields must be consistent.
Example:
class A:
f: str
class B:
f: int
class C(A, B): ... # error, the field `f` is inconsistent
inconsistent-overload
The signature of a function overload is inconsistent with the implementation. See the typing specification for details on the consistency checks Pyrefly performs.
Example:
from typing import overload
@overload
def f(x: int) -> int: ...
@overload
def f(x: str) -> str: ... # error, overload accepts `str` but implementation only accepts `int`
def f(x: int) -> int | str:
return x
inconsistent-overload-default
In an overloaded function, the type of a parameter in an overload signature is inconsistent with its default value in the implementation.
Example:
from typing import Literal, overload
@overload
def f(x: Literal[True] = ...) -> None: ... # error, `x` has default `False`, which is inconsistent with type `Literal[True]`
@overload
def f(x: Literal[False]) -> int: ...
def f(x: bool = False) -> int | None:
return 0 if x else None
instance-var-assign
Default severity: ignore
Assigning to an instance variable via the class object — e.g. C.instance_var = ...
where the class body declares instance_var: int without ClassVar — misuses the
declaration: instance variables should be assigned on instances. Pyrefly flags such
assignments when this kind is enabled (it is on in strict mode).
class C:
instance_var: int
C.instance_var = 3 # Cannot set instance variable `instance_var` from the class [instance-var-assign]
Unannotated assignments and ClassVar declarations are class variables and may be
assigned via the class.
internal-error
Ideally you'll never see this one. If you do, please consider filing a bug.
invalid-abstract-method
Default severity: ignore
Pyrefly emits this error when a class that is not abstract defines a method decorated with @abstractmethod. Such a class is directly instantiable at runtime, yet contains an unimplemented method — a likely programming mistake.
from abc import abstractmethod
class Foo:
@abstractmethod # Error: `Foo.fn` is decorated with `@abstractmethod` but `Foo` is not an abstract class
def fn(self) -> int: ...
To fix the issue, either inherit from abc.ABC to make the class abstract, define a Protocol if the class is an interface, or remove the @abstractmethod decorator and provide a concrete implementation.
invalid-annotation
There are several reasons why an annotation may be invalid. The most common case is misusing a typing special form, such as typing.Final, typing.ClassVar, typing.ParamSpec, and so on.
Even when no configuration file is present, this diagnostic is shown as a warning in the IDE.
from typing import *
# Final must have a value
a: Final
# ClassVar can only be used in a class body
b: ClassVar[int] = 1
The error messages will explain how the special form is being misused. Consult the typing docs and typing spec for more information.
invalid-argument
This error is used to indicate an issue with an argument to special typing-related functions.
For example, typing.NewType is a handy special form for creating types that are distinct from a base type.
from typing import *
# Invalid argument to `NewType`: the first arg must match the name.
Mismatch = NewType("Wrong Name", int)
# Invalid argument to `isinstance`: `NewType`s cannot be used in `isinstance`.
UserId = NewType("UserId", int)
if isinstance(1, UserId):
...
invalid-cast
Default severity: ignore
This warning is raised when typing.cast or typing_extensions.cast converts
between types that Pyrefly can prove have disjoint runtime classes. Generic
arguments do not make two class instances disjoint, so casting list[int] to
list[str] does not produce this warning. Two open nominal classes are not
considered disjoint unless their disjoint-base representatives conflict, because
a multiple-inheritance subclass could satisfy both.
from typing import cast
x: int = 1
cast(str, x) # Cast from `int` to `str` is invalid because the types are disjoint [invalid-cast]
invalid-decorator
Default severity: warn
This error indicates that a method-only decorator (@final, @override, etc.) was applied to a top-level function. Such usage is harmless at runtime and is sometimes intentional, so the default severity is warn.
from typing import final
@final
def f() -> None:
pass
Decorator misuse that violates the typing spec — for example, @dataclass on a
Protocol, @disjoint_base on a TypedDict or Protocol,
@runtime_checkable on a non-Protocol class, or @disjoint_base on a
function — is reported under bad-class-definition or
bad-function-definition instead, both of which default to error.
invalid-format-spec
Default severity: ignore
An f-string format specification is incompatible with the value's static type or with the known builtin formatting rules. This optional check validates literal, nonempty specifications, including unsupported formatting, incompatible presentation types, invalid flags, and unrecognized specifications.
f"{None:>10}" # invalid-format-spec, if enabled
f"{'text':.2f}" # invalid-format-spec, if enabled
f"{1:.2d}" # invalid-format-spec, if enabled
invalid-inheritance
An error caused by incorrect inheritance in a class or type definition. This can pop up in quite a few cases:
- Trying to subclass something that isn't a class.
- Subclassing a type that does not support it, such as a
NewTypeor aFinalclass. - Attempting to mix
Protocols with non-Protocolbase classes. - Trying to make a generic enum.
- Trying to give a
TypedDicta metaclass.
And so on!
invalid-literal
typing.Literal only allows a limited set of types as parameters.
Attempting to use Literal with anything else is an error.
from typing import Literal
# These are legal
Literal[1]
Literal['a', 'b', 'c']
# This is not
class A:
...
Literal[A()]
invalid-overload
The @overload decorator requires that the decorated function has at least two overloaded signatures and a base implementation.
from typing import *
@overload
def no_base(x: int) -> None:
pass
@overload
def no_base(x: str) -> int:
pass
@overload
def just_one(x: int) -> None:
pass
def just_one(x: str) -> None:
...
invalid-param-spec
This error is reported when typing.ParamSpec is defined incorrectly or misused. For example:
from typing import *
P = ParamSpec("Name Must Match!")
P1 = ParamSpec("P1")
P2 = ParamSpec("P2")
def f(x, *args: P1.args, **kwargs: P2.kwargs) -> None:
pass
Here, P1.args and P2.kwargs can't be used together; *args and **kwargs must come from the same ParamSpec.
invalid-pattern
This error is reported when a pattern is invalid at runtime. For example, enum members are values,
so they must be matched as value patterns (without ()), not class patterns:
from enum import Enum
class Color(Enum):
RED = "red"
def describe(color: Color) -> None:
match color:
case Color.RED(): # Invalid pattern: use `Color.RED` (without parentheses)
pass
invalid-self-type
This error occurs when Self is used in a context where it is not allowed.
For example, Self is not allowed inside a TypedDict, so the following code
errors:
from typing import Optional, Self, TypedDict
class TD(TypedDict):
x: Optional[Self] # error: `Self` is not allowed in a `TypedDict`
invalid-sentinel
An error caused by incorrect definition of a Sentinel. A few examples:
from typing_extensions import Sentinel
# First argument passed to sentinel constructor isn't a string literal
my_str: str = "MISSING"
A = Sentinel(my_str)
# Invalid arguments passed to sentinel constructor
MISSING = Sentinel("MISSING", non_existent="")
invalid-super-call
super() has a few restrictions on how it is called.
super() can be called without arguments, but only when used inside a method of a class:
class Legal(Base1, Base2):
def f(self) -> None:
super().f()
def illegal(arg: SomeType) -> None:
super().f()
When the function is called with two arguments, like super(T, x), then T must be a type, and the second argument is either an object where isinstance(x, T) is true
or a type where issubclass(x, T) is true.
invalid-syntax
This error covers syntactical edge cases that are not flagged by the parser.
For example:
x: list[int] = [0, 2, 3]
x[0]: int = 1
It's not a parse error for an assignment to have an annotation, but it is forbidden by the type checker to annotate assignment to a subscript like x[0].
invalid-type-alias
An error related to the definition or usage of a typing.TypeAlias. Many of these cases are covered by invalid-annotation, so this error
specifically handles illegal type alias values:
from typing import TypeAlias
x = 2
Bad: TypeAlias = x
invalid-type-checking-constant
A module-level TYPE_CHECKING constant that is not typed as bool. Type checkers treat
TYPE_CHECKING as True, while at runtime it is False, so a user-defined one must be a bool
(conventionally TYPE_CHECKING = False).
TYPE_CHECKING: str = "" # error: not a bool
invalid-type-var
An error caused by incorrect usage or definition of a TypeVar. A few examples:
from typing import TypeVar
# Old-style TypeVars must be assigned to a matching variable.
Wrong = TypeVar("Name")
# PEP 695-style TypeVars can be constrained, but there must be at least two:
def only_one_constraint[T: (int,)](x: T) -> T:
...
# It's also illegal to mix the two styles together.
T = TypeVar("T")
def mixed[S](a: S, b: T) -> None:
...
invalid-type-var-tuple
An error caused by incorrect usage or definition of a TypeVarTuple.
TypeVarTuple has similar error cases to TypeVar, but also a few of its own. For example:
from typing import TypeVarTuple
Ts = TypeVarTuple("Ts")
# TypeVarTuples must always be unpacked:
bad: tuple[Ts] = (...)
good: tuple[*Ts] = (...)
# Only one TypeVarTuple is allowed in a list of type arguments:
def two_tups[*Xs, *Ys](xs: tuple[*Xs], ys: tuple[*Ys]) -> None:
...
invalid-variance
An error caused by a type variable being used in a position incompatible with its declared variance.
For example, a covariant type variable cannot be used in a contravariant position (such as a method parameter), and a contravariant type variable cannot be used in a covariant position (such as a return type).
from typing import TypeVar, Generic
T_co = TypeVar("T_co", covariant=True)
T_contra = TypeVar("T_contra", contravariant=True)
class BadCovariant(Generic[T_co]):
# Error: covariant type variable used in contravariant position
def set_value(self, value: T_co) -> None: ...
class BadContravariant(Generic[T_contra]):
# Error: contravariant type variable used in covariant position
def get_value(self) -> T_contra: ...
invalid-yield
This error arises when yield is used in a way that is not allowed. For example:
from typing import Generator
for _ in range(1, 10):
yield "can't yield outside of a function!"
def bad_yield_from() -> Generator[int, None, None]:
# `yield from` can only be used with iterables.
yield from 1
method-assign
Default severity: ignore
Assigning to a method on a class object or instance is ambiguous because Python's
type system cannot distinguish the bound and unbound callable types that the
replacement will produce. This check is enabled by the strict preset.
class Service:
def run(self) -> None: ...
def replacement(self: Service) -> None: ...
Service.run = replacement # method-assign
misplaced-ignore
Default severity: warn
A file-level # pyrefly: ignore-errors (or # pyrefly: ignore-errors[code]) directive
only takes effect when it appears at the top of the file, before any code. Placed after
the first line of code it is silently inert — it suppresses nothing — so Pyrefly flags it.
Move the directive to the top of the file, or use a line-level # pyrefly: ignore[code]
to suppress a single line.
import os
# pyrefly: ignore-errors # inert here — flagged as misplaced-ignore
x: int = "not an int" # this error is still reported
missing-argument
An error caused by calling a function without all the required arguments.
def takes_two(x: int, y: int) -> int:
return x + y
takes_two(1)
missing-attribute
This error is raised when attempting to access an attribute that does not exist on the given object or module.
In the case of modules, attempting to import an nonexistent name will raise `missing-module-attribute instead.
import os
from os import bacarat # missing-module-attribute
os.jongleur() # missing-attribute
Note that objects with type Any will never raise this error.
missing-attribute-patch-target
Default severity: warn
The attribute targeted by a unittest.mock.patch call does not exist. Example:
from unittest import mock
@mock.patch("dep.nonexistent_attr") # missing-attribute-patch-target
def f(): ...
This is a sub-kind of missing-attribute: suppressing missing-attribute also suppresses this error.
missing-import
A module could not be found.
The error message will include which paths were searched, such as the site package paths. You may be missing a dependency, or you may need to inform Pyrefly where the module lives. See Configuration for further information.
Even when no configuration file is present, this diagnostic is shown as a warning in the IDE.
missing-module-attribute
Arises when attempting to import a name that does not exist from a module.
This is distinct from missing-import, which is used when the module being imported does not exist, and missing-attribute, when access attributes of the module.
import this_does_not_exist # missing-import
import os.bacarat # missing-import
from os import joker # missing-module-attribute
os.perkeo # missing-attribute
In this example, os.bacarat is treated as a module name, so failing to find it results in an missing-import.
from os import joker does not tell us if joker is a module, class, function, etc., so it is treated as the more general missing-module-attribute.
missing-override-decorator
Default severity: ignore
A method overrides a parent class method but does not have the @override decorator. We do not emit this error for dunder methods that are inherited from object (e.g., __repr__, __eq__, __str__), but we do emit it for dunder methods inherited from other classes.
This error supports strict override enforcement as specified in the typing spec. When enabled, it requires all overriding methods to be explicitly marked with @typing.override.
class Base:
def foo(self) -> None: ...
class Derived(Base):
def foo(self) -> None: ... # missing-override-decorator
missing-source
Default severity: ignore
Pyrefly was able to find a stubs package but no corresponding source package. For example, this can
happen if you install the types-requests package but forget to install requests.
missing-source-for-stubs
Pyrefly has bundled stubs for a package, but no corresponding source package was found.
missing-super-call
Default severity: ignore
A constructor-like method overrides a parent class method but does not call super().
class Base:
def __init__(self) -> None:
self.value: int = 1
class Child(Base):
def __init__(self) -> None: # missing-super-call
pass
name-mismatch
Default severity: warn
This warning indicates that the first string argument to a functional type definition does not match the name it is assigned to.
from collections import namedtuple
from enum import Enum
RepoDetails = namedtuple("repo_details", ["source_dir", "age"])
DviState = Enum("_dvistate", "pre outer inpage")
no-access
The no-access error indicates that an attribute exists, but it cannot be used in this way.
For example, classes do not have access to their instances' attributes:
class Ex:
def __init__(self) -> None:
self.meaning: int = 42
del Ex.meaning # no-access
no-any-return
Default severity: ignore
Umbrella error code for cases where a returned expression is Any in a function declared to return a concrete type other than object. Enabling this catches places where Any silently bypasses the function's declared return type. Returning Any as object is allowed because every runtime value is an object, so this does not weaken the return type's guarantee.
Concrete diagnostics are emitted under one of the more specific sub-kinds:
no-any-return-explicit
Default severity: ignore
The returned expression has type Any, but the function is declared to return a concrete type other than object. The returned Any originates from an explicit annotation or other introduction into the type system.
This is a sub-kind of no-any-return: suppressing no-any-return also suppresses this error.
from typing import Any
def get_port(config: dict[str, Any]) -> int:
return config["port"] # no-any-return-explicit: Returning Any from function declared to return "int"
# Fix: validate the value at the boundary where untyped data enters typed code.
def get_typed_port(config: dict[str, object]) -> int:
port = config["port"]
if isinstance(port, int):
return port
raise ValueError("port must be an int")
no-any-return-implicit
Default severity: ignore
The returned expression was inferred as Any (e.g., from an untyped import or missing stub), but the function's return annotation is a concrete type other than object. This usually indicates that adding type information at an untyped boundary would restore type precision.
This is a sub-kind of no-any-return: suppressing no-any-return also suppresses this error.
def get_port(config) -> int:
return config["port"] # no-any-return-implicit: Returning implicit Any from function declared to return "int"
# Fix: annotate the parameter and validate the value before returning it.
def get_typed_port(config: dict[str, object]) -> int:
port = config["port"]
if isinstance(port, int):
return port
raise ValueError("port must be an int")
no-matching-overload
This error is similar to the other bad function call errors, but specifically for cases where a function decorated with @overload is called with arguments that do not match any of the overloaded variations.
For example, neither of the signatures of f can take an argument of type float:
from typing import overload
@overload
def f(x: int) -> int:
...
@overload
def f(x: str) -> str:
...
def f(x: int | str) -> int | str:
return x
f(1.0)
non-convergent-recursion
Default severity: warn
Some Python code has type analysis that is recursive. Consider, for example:
def f(): return g()
def g(): return [f()]
Here, return-type inference for f depends on the return type of g, and vice versa
so we get a cycle.
Pyrefly resolves such cycles by repeatedly analyzing the entities involved, up to a
limited number of iterations. If the inferred types still have not converged, Pyrefly
reports a non-convergent-recursion error and uses the result of the last iteration.
Adding annotations can break the cycle. If you're filing a bug report for this error,
set the environment variable PYREFLY_FIXPOINT_DETAILS=1 and include the extra
diagnostic information in the issue.
non-exhaustive-match
Default severity: warn
Pyrefly warns when a match over a closed subject type, such as an enum, literal
union, None, bool, or non-subclassable class, may fall through without matching
a case. Add the missing cases or a default arm.
Non-exhaustive matches over open subject types are reported under a sub-kind, non-exhaustive-match-open-type.
from enum import Enum
class Color(Enum):
RED = "red"
BLUE = "blue"
def describe(color: Color) -> str:
match color: # non-exhaustive-match
case Color.RED:
return "danger"
non-exhaustive-match-open-type
Default severity: ignore
Enable this error kind to have Pyrefly report when a match over an open
subject type such as int, str, or object is non-exhaustive.
This is a sub-kind of non-exhaustive-match, so configuring or suppressing the parent kind also applies to this diagnostic.
not-a-type
This indicates an attempt to use something that isn't a type where a type is expected. In most cases, a more specific error kind is used.
You may see this error around incorrect type aliases:
class A:
...
# Not an alias, just a string!
X = "A"
x: X = ... # X is not a type alias, so this is illegal
not-async
not-async is reported when attempting to await on something that is not
awaitable. This may indicate that a function should have been marked async but
wasn't.
def some_func() -> None:
...
await some_func() # Expression is not awaitable [not-async]
This will also arise if the context manager used in an async with statement
has __aenter__ and __aexit__ methods that are not marked async.
The fix is to use an async function in the await. This may mean making the
function async or finding an existing async function to use instead.
not-callable
A value that is not callable is called or used as a decorator.
x = 1
@x # not-callable
def foo() -> None:
...
not-iterable
This is most likely to be seen in a for loop:
x = 1 # Or some other value
for val in x: # not-iterable
...
not-required-key-access
Default severity: ignore
This warning indicates that a TypedDict
key marked NotRequired (or inherited from a total=False TypedDict) is being accessed without
first ensuring that the key is present. Even if the value type is non-optional, the key itself may
not exist at runtime, so Pyrefly encourages guarding the access with an in check or a .get()
call.
from typing import NotRequired, TypedDict
class Movie(TypedDict):
title: str
year: NotRequired[int]
def describe(movie: Movie) -> int:
return movie["year"] # not-required-key-access
def safe_describe(movie: Movie) -> int:
if "year" in movie:
return movie["year"] # OK: key presence established
raise ValueError("Missing year")
open-unpacking
Default severity: ignore
This error is reported on an attempt to unpack an open TypedDict that potentially has items incompatible with the TypedDict it is being unpacked into.
Example:
from typing import TypedDict
class OpenTypedDict(TypedDict):
x: int
class UnpackingTarget(TypedDict):
x: int
y: str
def f(o: OpenTypedDict) -> UnpackingTarget:
# Error: `o` could be an instance of a subclass of `OpenTypedDict` with an
# item `y` with an incompatible type.
return {"y": "", **o}
The same error is reported when an open TypedDict is unpacked into a call whose target cannot accept the unknown items:
def g(x: int) -> None: ...
def h(o: OpenTypedDict) -> None:
# Error: `o` could carry items under keys that `g` does not accept.
g(**o)
To fix this error, close the open TypedDict to indicate it does not contain any unknown items:
class OpenTypedDict(TypedDict, closed=True): ...
Note: In Python versions before 3.15, import TypedDict from typing_extensions rather than
typing to use the closed feature.
ordered-dataclass-inheritance
Default severity: warn
This warning is reported when a class inherits from a stdlib dataclass with order=True. The class
definition is valid Python, but mixed comparisons between parent and subclass instances can raise
TypeError at runtime because the generated ordering methods only compare instances of the exact
same class.
It is not reported when every mixed comparison reaches a user-defined
__lt__, __le__, __gt__, or __ge__ instead, for example when the subclass, or a mixin listed
before the dataclass, defines all four. Overriding only some of them is not enough, because the
remaining generated methods still reject instances of the other class.
from dataclasses import dataclass
@dataclass(order=True)
class Parent:
value: int
class Child(Parent): # ordered-dataclass-inheritance
pass
Child(1) < Parent(2) # TypeError
parse-error
An error related to parsing or syntax. This covers a variety of cases, such as function calls with duplicate keyword args, some poorly defined functions, and so on.
partially-unknown-type
Default severity: ignore
A value whose type is not itself Any but contains an implicit Any, such as list[Unknown] or Unknown | None. A fully unknown value is reported by the unknown-* checks; this kind covers a type that is only partially unknown, which Pyright reports under its reportUnknown* rules. Only an implicit Any counts: an explicit Any the user wrote in a nested annotation is reported at the annotation by explicit-any.
def untyped(p): # the parameter is an implicit `Any`
return p
v = [untyped(1)] # `v` has type `list[Unknown]`: partially unknown
possibly-uninitialized-attribute
Default severity: ignore
The strict preset enables this check as an error.
Pyrefly reports an attribute that the class body or an initialization method assigns on some paths but not others, so an instance may be created without it.
class C:
if enabled():
flag = True # possibly-uninitialized-attribute
def __init__(self, value: int | None) -> None:
if value is not None:
self.value = value # possibly-uninitialized-attribute
Every path that completes normally must assign the attribute, including one that ends in an early return.
A path that raises, or ends in a call to a Never or NoReturn function, does not need to.
A bare class-body annotation is checked against the methods that assign it.
If no initialization method assigns it, uninitialized-instance-variable reports it instead.
potential-bad-keyword-argument
Default severity: ignore
A potential conflict between an explicit keyword argument and a NotRequired TypedDict field. The field may be absent at runtime, so the conflict is not guaranteed. This is a separate error code from bad-keyword-argument to allow users to opt-in to this stricter check.
from typing import TypedDict, NotRequired
class Options(TypedDict, total=False):
name: str
def f(name: str, **kwargs) -> None: ...
opts: Options = {}
# Potential conflict: if 'name' is in opts at runtime, this will crash
f(name="test", **opts) # E: Multiple values for argument `name`
protected-access
Default severity: ignore
The protected-access error is reported when a protected class member, whose name starts with a
single underscore, is accessed from outside its defining class or a subclass.
class Service:
def _reset(self) -> None: ...
Service()._reset() # protected-access
Double-underscore private attributes are checked separately under no-access.
protocol-implicitly-defined-attribute
Protocols must declare the attributes they require directly in the class body. Assigning to a new self attribute inside a protocol method introduces a member that implementations of the protocol would never be required to provide.
Add an annotated attribute (or property) to the protocol, or remove the assignment.
from typing import Protocol
class Template(Protocol):
name: str
def method(self) -> None:
self.temp: list[int] = [] # protocol-implicitly-defined-attribute
pytorch-efficiency-lint-cuda-call
Default severity: ignore
Calling .cuda() on a torch.Tensor hard-codes the target device to CUDA. Use .to(device) instead so your code works on any accelerator (CUDA, XPU, MPS, etc.).
This is a sub-kind of pytorch-efficiency-lints: enabling pytorch-efficiency-lints also enables this error.
import torch
def f(x: torch.Tensor) -> None:
y = x.cuda() # pytorch-efficiency-lint-cuda-call
# Fix: y = x.to(device)
pytorch-efficiency-lint-item-call
Default severity: ignore
Calling .item() on a torch.Tensor forces GPU-to-CPU synchronization, blocking the training loop until all pending GPU operations complete. Prefer tensor[0] for scalar tensors, accumulate values on the GPU with torch.sum(), or defer .item() to outside the training loop.
This is a sub-kind of pytorch-efficiency-lints: enabling pytorch-efficiency-lints also enables this error.
import torch
def f(x: torch.Tensor) -> None:
v = x.item() # pytorch-efficiency-lint-item-call
pytorch-efficiency-lint-print-tensor
Default severity: ignore
Passing a torch.Tensor to print() triggers Tensor.__repr__(), which forces GPU-to-CPU synchronization and blocks until all pending GPU operations complete. Use print(tensor.shape) to inspect metadata without synchronizing, or guard with if DEBUG: print(tensor).
This is a sub-kind of pytorch-efficiency-lints: enabling pytorch-efficiency-lints also enables this error.
import torch
def f(x: torch.Tensor) -> None:
print(x) # pytorch-efficiency-lint-print-tensor
# Fix: print(x.shape) or print(x.dtype)
pytorch-efficiency-lint-redundant-to-call
Default severity: ignore
Calling .to(device) on a tensor returned by a factory function like torch.zeros() allocates the tensor on CPU first, then copies it to the target device. Pass device= directly to the factory function instead to avoid the redundant allocation and copy.
This is a sub-kind of pytorch-efficiency-lints: enabling pytorch-efficiency-lints also enables this error.
import torch
device = torch.device("cuda")
x = torch.zeros(3, 4).to(device) # pytorch-efficiency-lint-redundant-to-call
# Fix: x = torch.zeros(3, 4, device=device)
pytorch-efficiency-lints
Default severity: ignore
Umbrella error code for PyTorch GPU performance anti-patterns. Suppressing or enabling pytorch-efficiency-lints cascades to every sub-kind — useful when you want to surface (or silence) all of them without listing them individually.
All concrete diagnostics are emitted under one of the more specific sub-kinds:
pytorch-efficiency-lint-cuda-callpytorch-efficiency-lint-item-callpytorch-efficiency-lint-print-tensorpytorch-efficiency-lint-redundant-to-call
read-only
This error indicates that the attribute being accessed does exist but cannot be modified.
For example, a @property with no setter cannot be assigned to:
class Ex:
@property
def meaning(self) -> int:
return 42
x = Ex()
x.meaning = 0
redefinition
Pyrefly reports this error when a name that already has an annotation in the current scope is annotated again with a different type. Re-annotating the same variable can lead to confusing types; prefer introducing a new name instead.
def f(x: int) -> None:
x: str = str(x) # redefinition
redundant-cast
Default severity: warn
This warning is raised when typing.cast() is used to cast a value to a type it is already compatible with. Such casts are unnecessary and can be removed to improve code clarity.
import typing
x: int = 42
# This cast is redundant since x is already an int
y = typing.cast(int, x) # redundant-cast
# This is a valid cast since we're casting from a more general type
obj: object = "hello"
s = typing.cast(str, obj) # No warning - this is a valid cast
redundant-condition
Default severity: warn
This error is used to indicate a type that's equivalent to True or False is used as a boolean condition (e.g. an uncalled function)
def f() -> bool:
...
# This is likely a mistake, as it's likely that the function needs to be invoked.
if f:
...
# This is likely a mistake, as it's equivalent to `if True`.
if "abc":
...
regex
This error is raised when Pyrefly can statically detect an invalid regular expression pattern.
import re
re.compile("(") # missing ), unterminated subpattern [regex]
reveal-type
Default severity: info
Pyrefly uses this diagnostic to communicate the output of the reveal_type function.
reveal_type is a directive — it is always shown in CLI output regardless of the min-severity threshold, and is never subject to suppression or baseline exclusion. To hide it, set reveal-type = "ignore" in the errors table.
Pass --reveal-type-qualified-names to display revealed types with their fully qualified names. Builtins are not qualified.
import numpy as np
reveal_type(np.timedelta64(1, "D"))
# default: timedelta64[timedelta]
# with --reveal-type-qualified-names: numpy.timedelta64[datetime.timedelta]
string-as-iterable
Default severity: ignore
This warning is raised when a string is passed to a parameter expecting an Iterable[str] or
Sequence[str]. While str is technically iterable, it iterates by individual characters, which
is often not what you intended.
from typing import Iterable
def takes_items(xs: Iterable[str]) -> None:
...
takes_items("hello") # Passing `str` treats it as an iterable of characters
unannotated-attribute
Default severity: ignore
Deprecated: this error code has been renamed to implicit-any-attribute. The old name is still accepted in suppression comments and configuration for backwards compatibility.
unannotated-parameter
Default severity: ignore
Deprecated: this error code has been renamed to implicit-any-parameter. The old name is still accepted in suppression comments and configuration for backwards compatibility.
unannotated-protocol-member
This error is raised when a protocol member is assigned a value in the class body without an explicit type annotation. Protocol members must have explicitly declared types so that implementations know exactly what type to provide.
from typing import Protocol
class MyProto(Protocol):
x = None # error: Protocol member `x` must have an explicit type annotation
# Fixed version:
class MyProto(Protocol):
x: int | None = None
unannotated-return
Default severity: ignore
This error is raised when a function is missing a return type annotation. This helps enforce fully-typed codebases by ensuring all functions declare their return types explicitly. To fix it, add a return type annotation to the function.
def calculate_sum(x: int, y: int): # error: `calculate_sum` is missing a return annotation
return x + y
# Fixed version:
def calculate_sum(x: int, y: int) -> int:
return x + y
unbound-name
Pyrefly found a conditional definition for the given name, so it may be undefined in some flows. For example:
def f(check: bool):
if check:
x = 1
return x # unbound-name
Compare this with unknown-name, which is reported when no definition at all is found for a name.
unexpected-keyword
A function was called with an extra keyword argument.
def two_args(a: int, b: int) -> int:
...
two_args(a=1, b=2, c=3)
unexpected-never
Default severity: warn
Pyrefly emits this warning when a function can never return, contrary to its annotation.
def f() -> int: # Function declared to return `int` but can never return [unexpected-never]
while True:
pass
unexpected-positional-argument
A positional argument was passed for a keyword-only parameter.
def takes_kwonly(*, x: int) -> int:
...
takes_kwonly(1) # should be `takes_kwonly(x=1)`!
unimported-directive
Using a type checker directive without importing it from typing first will result in a runtime error.
reveal_type(1) # error
assert_type(1, int) # error
uninitialized-instance-variable
Default severity: ignore
An instance attribute is declared with a type annotation in the class body but is never assigned a value there or in a recognized method such as __init__, so reading it raises AttributeError at runtime. Enable this check to require that annotated instance attributes are initialized. This mirrors pyright's reportUninitializedInstanceVariable.
class C:
x: int # error: Instance attribute `x` is declared but never initialized
def __init__(self) -> None:
pass
unknown-argument-type
Default severity: ignore
A call argument whose type is an implicit Any (unknown) is passed without the type checker being able to verify it against the parameter, hiding potential bugs. This mirrors pyright's reportUnknownArgumentType.
def untyped(x): # unannotated: returns an implicit `Any`
return x
def f(n: int) -> None: ...
f(untyped(1)) # the argument's type is unknown [unknown-argument-type]
unknown-attribute-access
Default severity: ignore
An attribute is accessed whose type is an implicit Any (unknown), so uses of the result are not checked. This mirrors pyright's reportUnknownMemberType for member accesses. Accesses on an explicit Any are treated as intentional and are not reported.
Compare this with unknown-attribute-type, which is reported where an unannotated attribute is assigned a value of unknown type.
def untyped(x): # unannotated: returns an implicit `Any`
return x
class C:
def __init__(self) -> None:
self.value = untyped(1)
def use(c: C, obj) -> None:
c.value # the type of attribute `value` is unknown [unknown-attribute-access]
obj.attr.deeper # both `attr` and `deeper` are unknown [unknown-attribute-access]
unknown-attribute-type
Default severity: ignore
An unannotated attribute is assigned a value whose type is unknown, so the attribute is inferred as Any. Add an explicit attribute annotation or fix the right-hand side so Pyrefly can infer a concrete type.
This is not a sub-kind of implicit-any: suppressing implicit-any does not suppress this error.
def untyped(x): # unannotated: returns an implicit `Any`
return x
class C:
def __init__(self) -> None:
self.x = untyped(1) # downstream users don't know what type `self.x` is
self.y: int = untyped(1) # downstream users treat `self.y` as `int`
unknown-base-class
Default severity: ignore
A class whose base class has an implicit Any (unknown) type, for example from a missing
annotation. Pyrefly can't check attribute accesses on such a class. A bare type
annotation counts as unknown, because it means type[Any] with an implicit Any.
To mark a base as intentionally dynamic, annotate it as type[Any]
instead.
from typing import Any
def fn(cls: type):
class Foo(cls): # Type of base class is Unknown [unknown-base-class]
...
DynamicBase: type[Any] = object
class Baz(DynamicBase): # OK
...
unknown-column
Accessing a DataFrame column that does not exist in the inferred schema.
unknown-from-error
Default severity: ignore
A value's type is unknown because it comes from an error, such as an unresolved import or an annotation that refers to a name that does not exist. That error is reported once where it happens, and if it is suppressed, for example for a dependency with missing types, nothing else shows that the value is unknown. This kind reports such values at the same kinds of places as the other unknown-type checks: variables, arguments, attribute accesses, class attributes, returned values, and lambdas.
import untyped_dependency # pyrefly: ignore[missing-import]
class Config:
timeout: Timeout # unknown-name, if `Timeout` is not defined
def wait(seconds: int) -> None: ...
wait(Config().timeout) # the type of attribute `timeout` is unknown because it comes from an error
handler = untyped_dependency.make_handler() # the type of attribute `make_handler` is unknown because it comes from an error
unknown-name
A name is referenced but does not exist.
Compare this with unbound-name, which is reported when the name is conditionally defined.
Even when no configuration file is present, this diagnostic is shown as a warning in the IDE.
def where() -> None:
# There is no spoon: unknown-name
global spoon
unknown-return-type
Default severity: ignore
A function without a return annotation, or a lambda, returns a value of unknown type. For a function, or a lambda without an expected type, this makes the inferred return type unknown, so callers get an implicit Any. Annotating the function's parameters or its return type fixes this. A function with a return annotation is covered by no-any-return-implicit instead.
def get(obj): # `obj` is unannotated, so `obj.value` is unknown
return obj.value # the type of the returned value is unknown
f = lambda: get(config) # the return type of this lambda is unknown
# Fix:
def get(obj: Config) -> int:
return obj.value
unknown-variable-type
Default severity: ignore
A variable assigned a value of unknown type without an explicit annotation is inferred as Any, which hides type errors wherever it is used. Adding an annotation gives the variable a concrete type.
def untyped(x): # unannotated: returns an implicit `Any`
return x
y = untyped(1) # downstream users don't know what type `y` is
y2: int = untyped(1) # downstream users treat `y2` as `int`
for item in untyped(1): # the type of `item` is unknown
pass
a, b = untyped(1) # the types of `a` and `b` are unknown
unnecessary-comparison
Default severity: warn
This warning is raised when an identity comparison (is or is not) is made between
literals whose comparison result is statically known.
def test0() -> None:
# Different literals are always different objects
if 1 is 2: # unnecessary-comparison: always False
pass
# Same singletons are always the same object
if True is not False: # unnecessary-comparison: always True
pass
class User: ...
class Admin(User): ...
def test1(user: User) -> None:
# Comparing an instance to a class is always False
if user is Admin: # unnecessary-comparison: did you mean isinstance(user, Admin)?
pass
This check is relatively conservative and only warns on limited cases where the comparison is highly likely to be redundant.
unnecessary-type-conversion
Default severity: warn
This warning is raised when a builtin type constructor (str, int, float, bool, or bytes) is called on a value that is already of that type, making the conversion redundant.
def f(x: str) -> None:
y = str(x) # unnecessary-type-conversion: `x` is already of type `str`
unreachable
Default severity: hint
This diagnostic is reported when code can never execute because it comes after a statement that
always exits the current flow, such as return, raise, break, or continue.
The all preset
reports it as an error.
def example():
return 1
print("dead") # This code is unreachable [unreachable]
print("also dead, but covered by the same diagnostic")
Code after a call that never returns, such as sys.exit() or a function annotated NoReturn,
is reported too, as is code after an if or match whose every branch ends that way.
It is also raised for the body of an if or while whose condition is statically known,
and for the branches that a chosen branch makes dead.
A condition that consults the runtime environment — sys.version_info, sys.platform,
os.name, or TYPE_CHECKING — is not reported. Such a branch is dead only under the
configuration being checked and is live under another, so flagging it would be a false
positive on code that deliberately supports several environments.
Unreachable regions are grayed out in the editor.
unreachable-except-clause
Default severity: warn
This warning is raised when an except clause can never be entered, because earlier
clauses in the same try statement already catch every exception it matches. An
exception is delivered to the first clause that matches it, so a clause listing a
subclass of something caught above it never runs.
try:
pass
except BaseException:
pass
except Exception: # This `except` clause is unreachable, because an earlier clause already catches `BaseException` [unreachable-except-clause]
pass
unreachable-match-case
Default severity: warn
This warning is raised when a case pattern in a match statement can never match
the subject because the subject's type is disjoint from the pattern's type.
def example(x: list[int]) -> None:
match x:
case 1: # Case pattern can never match subject of type `list[int]` [unreachable-match-case]
pass
unresolvable-dunder-all
Default severity: warn
This warning is raised when __all__ is defined but its value cannot be
statically analyzed. This can happen when __all__ is assigned a function call,
a variable, a list comprehension, or any expression that Pyrefly cannot resolve
at analysis time.
When this occurs, Pyrefly falls back to inferring public exports from module-level definitions (all names that do not start with an underscore).
def generate_all():
return ["x", "y"]
__all__ = generate_all()
# `__all__` could not be statically analyzed [unresolvable-dunder-all]
unsafe-overlap
Protocols decorated with @runtime_checkable may be used in isinstance and issubclass checks, but the runtime will only check that all the required attributes are present, without looking at their types.
This error occurs when the object you're checking against the protocol has all the required attributes, but their types are not compatible.
In the example below, C should not match with P, but the isinstance check will succeed at runtime.
from typing import Protocol, runtime_checkable
@runtime_checkable
class P(Protocol):
x: int
class C:
x: str
c = C()
if isinstance(c, P):
pass
unsupported
This error indicates that pyrefly does not currently support a typing feature.
unsupported-delete
This error occurs when attempting to del something that cannot be deleted.
Besides obvious things like built-in values (you can't del True!), some object attributes are protected from deletion.
For example, read-only and required TypedDict fields cannot be deleted.
unsupported-dynamic-base
Default severity: ignore
This error is raised for dynamic class definitions created with type() when
the base classes are not statically known class literals.
class Base: ...
def factory(base: type[Base]) -> type:
return type("Dynamic", (base,), {})
# Base class `type[Base]` in `type()` call is not a statically known class [unsupported-dynamic-base]
unsupported-operation
This error arises when attempting to perform an operation between values of two incompatible types.
x = 1 + "oops" # `+` is not supported between `int` and `str` [unsupported-operation]
untyped-class-decorator
Default severity: ignore
A class decorator may modify the class in unexpected ways, either because the decorator's own type is Any or because it returns an unknown type. This mirrors pyright's reportUntypedClassDecorator, except that fully unannotated decorators are also reported. A decorator annotated to return an explicit Any is treated as intentional and is not reported.
from typing import Any
my_decorator: Any = lambda cls: cls
@my_decorator # `my_decorator` is `Any`, so the type of `A` is lost [untyped-class-decorator]
class A: ...
def unannotated(cls): # returns an implicit `Any`
return cls
@unannotated # the result is unknown [untyped-class-decorator]
class B: ...
untyped-function-decorator
Default severity: ignore
A function or method decorator obscures the decorated function's type, either because the decorator's own type is Any or because applying it produces an unknown type. This mirrors pyright's reportUntypedFunctionDecorator, except that fully unannotated decorators are also reported. A decorator annotated to return an explicit Any is treated as intentional and is not reported.
from typing import Any
my_decorator: Any = lambda f: f
@my_decorator # `my_decorator` is `Any`, so the type of `g` is lost [untyped-function-decorator]
def g() -> int:
return 1
def unannotated(f): # returns an implicit `Any`
return f
@unannotated # the result is unknown, so the type of `h` is lost [untyped-function-decorator]
def h() -> int:
return 1
untyped-import
Default severity: warn
Type information for some third-party libraries is shipped in a stubs package separate from the
library's source code. This error is emitted when we detect that a library is being used without
the recommended stubs package being installed. It only applies to libraries found on the site package
path that do not ship a py.typed file; modules found on the search path, such as local stubs in a
typings/ directory or your own code, are never reported.
untyped-namedtuple
Default severity: ignore
A collections.namedtuple declares no field types, so every field has an unknown type, and code that reads the fields is not checked. Use typing.NamedTuple, which declares a type for each field. This mirrors pyright's reportUntypedNamedTuple.
from collections import namedtuple
from typing import NamedTuple
Point = namedtuple("Point", ["x", "y"]) # untyped-namedtuple
# Fix:
class Point(NamedTuple):
x: int
y: int
unused-call-result
Default severity: ignore
This rule reports when the result of a call expression is discarded and the result type is informative (not None, Any, or
Never). This is often a sign of a mistake, such as forgetting to use the return value.
def combine(a: list[int], b: list[int]) -> list[int]:
return a + b
items = [1, 2, 3]
combine(items, [4, 5]) # warning: result is discarded
print(items) # ok: print returns None
x = combine(items, [4, 5]) # ok: result is used
unused-coroutine
Pyrefly reports an error if the result of an async function call is not awaited or used.
It also reports an error if a coroutine is used as a Boolean condition, such as in an
if statement. Await the coroutine to test its result.
async def foo():
return 1
async def bar():
foo() # error
await foo() # ok
x = foo() # ok
if foo(): # error: the coroutine is not awaited
pass
if await foo(): # ok: tests the result
pass
unused-ignore
Default severity: ignore
This error is raised when a # pyrefly: ignore comment is not used to suppress an error, and can be safely removed.
unused-import
Default severity: hint
A module-level import whose name is never used. The IDE greys it out. Star imports, __future__ imports, re-exports written as import x as x, and names listed in __all__ are not reported.
import os # unused-import: Import `os` may be unused
unused-parameter
Default severity: hint
A function parameter that is never used in the function body. The IDE greys it out. Parameters whose names start with _, self, cls, *args and **kwargs are not reported.
def handler(request: str, context: str) -> str: # unused-parameter: Parameter `context` is unused
return request
unused-type-ignore
Default severity: ignore
This error is raised when a # type: ignore comment is not used to suppress any error, and can be safely removed. This rule is distinct from unused-ignore so that projects using multiple type checkers can leave # type: ignore comments for other tools (e.g. mypy) without pyrefly flagging them. Enable this rule if your project uses pyrefly exclusively.
unused-variable
Default severity: hint
A local variable that is assigned but never used. The IDE greys it out. Variables with a leading underscore, such as _ or _unused, are not reported.
def f() -> None:
result = compute() # unused-variable: Variable `result` is unused
useless-overload-body
Default severity: warn
This warning is raised when an @overload function contains executable body logic.
Overload bodies are never executed at runtime, so only placeholder bodies like pass, ...,
a docstring-only body, raise NotImplementedError(...), or return NotImplemented are useful.
from typing import overload
@overload
def parse(x: int) -> int:
return x + 1 # warning: executable logic in an overload body
variance-mismatch
Default severity: warn
This warning suggests covariance or contravariance for an invariant protocol
TypeVar when its structural usage permits it. For example, if a protocol
only returns T, the warning suggests declaring T as covariant. Incorrect usage
of an explicitly covariant or contravariant TypeVar is reported as
invalid-variance instead.
Constrained TypeVars, such as TypeVar("T", str, bytes), are exempt from this
warning. Constraints commonly describe a fixed set of unrelated alternatives, so
invariance is often sufficient and narrower-variance suggestions are noisy.
Bounded TypeVars, such as TypeVar("T", bound=float), are still checked.
from typing import Protocol, TypeVar
T = TypeVar("T")
class A(Protocol[T]): # variance-mismatch: Type variable `T` in class `A` is declared as invariant, but could be covariant based on its usage
def f(self) -> T: ...