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.
annotation-mismatch
This error indicates a mismatch between multiple annotations for a single variable. This is relatively uncommon, but it can happen in if-statements:
if some_cond:
x: int = 1
else:
x: str = "two" # Inconsistent type annotations for x: str, int [annotation-mismatch]
It can be helpful to annotate the variable before branch, especially if there is a useful default value for it. For example:
x: str = "default"
if some_cond:
x = "actual value"
assert-type
An assert-type
error is raised when a typing.assert_type()
call fails.
This error kind should never be suppressed, since that rather defeats the point
of of typing.assert_type()
.
async-error
async-error
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 [async-error]
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.
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:
...
take_three(3, 2) # Expected 1 more positional argument [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['two']` 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_args(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-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-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-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.
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-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]
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()
delete-error
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.
import-error
An error related to the import mechanism, such as when a module cannot 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.
index-error
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]
Pyrefly also knows the keys of TypedDict
s, but those have their own error kind.
internal-error
Ideally you'll never see this one. If you do, please consider filing a bug.
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.
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 *
# The first arg must match the name!
Mismatch = NewType("Wrong Name", int)
# NewTypes cannot be used in isinstance.
UserId = NewType("UserId", int)
if isinstance(1, UserId):
...
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
NewType
or aFinal
class. - Attempting to mix
Protocol
s with non-Protocol
base classes. - Trying to make a generic enum.
- Trying to give a
TypedDict
a 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.0]
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-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-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-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
match-error
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.
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-module-attribute
Arises when attempting to import a name that does not exist from a module.
This is distinct from import-error
, 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 # import-error
import os.bacarat # import-error
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 import-error
.
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
.
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-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)
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-callable
A straightforward error: something that is not a function was used as if it were a function.
One interesting place this error may occur is with decorators:
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
...
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.
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
reveal-type
Pyrefly uses this error to communicate the output of the reveal_type
function.
type-alias-error
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
typed-dict-key-error
This error arises when TypedDict
s 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]
unbound-name
This error corresponds to the runtime NameError
, indicating that a variable is referenced but does not exist.
def do_things(stuff: list[int]) -> str:
...
do_thing([1, 2]) # typo! Or, unbound-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)
unknown-name
unknown-name
occurs when attempting to load a name from another scope, but the name cannot be found.
def where() -> None:
# There is no spoon: unknown-name
global spoon
unsupported-operand
This error arises when attempting to perform an operation between values of two incompatible types.
Since many Python operators are actually syntactic sugar for function calls, many operations will result in bad-argument-type
errors.
An exception is the in
operator, which fails when looking for a value in something that does not support in
:
if "hello" in 1: # int doesn't support `in`!
...
unsupported
This error indicates that pyrefly does not currently support a typing feature.