Skip to content

Latest commit

 

History

History
2467 lines (1806 loc) · 82.4 KB

errors.md

File metadata and controls

2467 lines (1806 loc) · 82.4 KB
id title sidebar_label
errors
Type Errors
Type Errors

import Internal from './fb/errors.md';

Common Issues

Covariance and Contravariance

Variance is tricky and a common source of confusion for people new to Python's type system.

Pyre will error when, for instance, a List[int] is passed in when a List[float] is expected, as in the following example:

def to_seconds(milliseconds: List[float]) -> List[int]:
  return [int(x/1000.0) for x in milliseconds]

my_list: List[int] = [1]
my_list = to_seconds(my_list) # Pyre errors here!
$ pyre
Unbound name [10]: Name `List` is used but not defined in the current scope.

This code works perfectly fine at runtime, and we may think that since int is a subtype of float this should not be a problem for the type checker either. However, consider the following code:

def halve_first_element(list: List[float]) -> None:
  list[0] /= 2

def function_taking_int(int: int) -> None:
    return None

my_list: List[int] = [1]
halve_first_element(my_list)
function_taking_int(my_list[0]) # Oh no, my_list[0] is 0.5!
$ pyre
Incompatible parameter type [6]: In call `list.__setitem__`, for 2nd positional argument, expected `int` but got `float`.

If we allowed passing in my_list to the halve_first_element function here, the above code would type check. It's perfectly valid from the perspective of the callee to modify the list's element to be a float, as it was annotated as taking a list of floats, but because this list escapes the scope of the callee, we can't allow this in the type checker.

To work around this, we can signal to the type checker that the parameter can't be modified. Here's how you can tell the type checker that you won't change the container in your function:

from typing import *
# I can't modify milliseconds here, so it's safe to pass a Iterable[int].
def to_seconds(milliseconds: Iterable[float]) -> List[int]:
  return [int(x/1000.0) for x in milliseconds]

my_list: List[int] = [1]
my_list = to_seconds(my_list) # Type checks!

Most commonly used generic containers have immutable variants, and I would encourage you to use them for function parameters whenever you don't need to modify a container in your function. Here are some immutable variants for commonly used containers:

typing.Listtyping.Sequence (if you need random access via my_list[id])
typing.Listtyping.Iterable (if you're just iterating over the list in a loop and want to support sets as well)
typing.Dicttyping.Mapping
typing.Settyping.AbstractSet

Invariance, combined with type inference, comes with a few gotchas. When you write an expression, Pyre infers the most precise type possible. For instance, Pyre infers the List[int] type for [1, 2], even though List[float] would be a perfectly valid type here. This can cause issues, as in the following example:

def zeroes(number_of_elements: int) -> List[float]:
  a = [0] * number_of_elements
  return a # Pyre errors here!
$ pyre
Incompatible return type [7]: Expected `List[float]` but got `List[int]`.

What happened above is that Pyre inferred a type of List[int] for a, and invariance kicked in. You can work around this by adding an explicit annotation when declaring a:

def zeroes(number_of_elements: int) -> List[float]:
  a: List[float] = [0.0] * number_of_elements
  return a # Type checks!

Contravariance

Callable, on the other hand, is contravariant in its parameter types. This means that, when checking if Callable[[A], None] is compatible with Callable[[B], None], we check if B is compatible with A, not the other way around. This is because the former should be capable of accepting any arguments accepted by the latter.

For example, a function of type Callable[[Base], int] may be given an argument of type Child2. But if we passed in a function of type Callable[[Child1], int], this could fail at runtime:

class Base: pass

class Child1(Base):
    size: int = 42

# No size field.
class Child2(Base): pass

def print_child2_size(get_size: Callable[[Base], int]) -> None:
    child2 = Child2()
    size = get_size(child2)
    print(size)

def size_of_child1(child1: Child1) -> int:
    return child1.size

print_child2_size(size_of_child1) # BAD!

# At runtime:
# AttributeError: 'Child2' object has no attribute 'size'

To prevent such errors, Pyre raises a type error when violating contravariance:

$ pyre
Incompatible parameter type [6]: Expected `typing.Callable[[Base], int]` for 1st positional only parameter to call `print_child2_size` but got `typing.Callable(size_of_child1)[[Named(child1, Child1)], int]`.

Optional Attributes

A common pattern in Python is to check whether an attribute is None before accessing its value. E.g.

from typing import Optional

class Data:
  field: Optional[int]

def process_field(input: int) -> None:
  ...

def process_data(data: Data) -> None:
  if data.field is not None:
    # ... interleaving logic
    process_field(data.field)
$ pyre
Incompatible parameter type [6]: expected int but got Optional[int]

The above fails to type-check because Pyre cannot guarantee that data.field remains not None if the interleaving logic between the explicit check and the later reference contains anything that may have side effects, like function calls.

An interleaving call could set field back to None, since it's a non local variable and is mutable. Therefore any calls between the None check and the access will invalidate the "not None" refinement. If data.field is defined as a class property or if the parent class has overridden __getattr__, then all bets are off even if there are no interleaving calls.

The preferred way to make this code type-check is to either move the check closer to the access, or to mark the attribute Final if it is not meant to be reassigned to, and you can guarantee to the type checker that no interleaving side effects can modify this attribute.

from typing import Final, Optional

class Data:
  # Needs to be assigned in the constructor and cannot be changed afterwards.
  field: Final[Optional[int]] = 1

It is always safe to refine attributes when their types are Final.

Alternatively, it is also safe to assign the attribute to a local variable before accessing its value:

def process_data(data: Data) -> None:
  field = data.field
  if field is not None:
    # ... interleaving logic
    process_field(field)

or using Python 3.8's assignment expressions:

def process_data(data: Data) -> None:
    if (field := data.field) is not None:
        # ... interleaving logic
        process_field(field)

Third-Party Libraries

Not all third-party libraries come with Python code that Pyre can analyze (e.g. Cython modules), and some libraries contain source code without annotations. This will often show up in the form of undefined attribute errors:

Undefined attribute [16]: Module <library> has no attribute <some attribute>.

Since it is not always possible to annotate code, PEP 484 specifies a format for stub files with a .pyi extension. Pyre will look for stub files in typeshed, or next to your source code. You can also provide additional paths to Pyre to look for stubs (see Configuration).

Error Codes

Different errors raised by Pyre have different error codes. E.g. in

(venv) $ pyre
 ƛ Found 1 type error!
test.py:1:0 Incompatible variable type [9]: a is declared to have type `int` but is used as type `str`.

The 9 in the brackets indicates that we raised an error with code 9.

0: Unused Ignore

Pyre fixmes and ignores allow you to ignore specific type errors by their code until you are able to fix them. In order to avoid outdated fixme comments in your project, Pyre will also error when a fixme is no longer needed. Removing the fixme comment will resolve the error.

# pyre-fixme[7] # unused ignore
def foo() -> int:
  return 1
$ pyre
Unused ignore [0]: The `pyre-ignore[7]` or `pyre-fixme[7]` comment is not suppressing type errors, please remove it.

2: Missing Parameter Annotation

If strict mode is turned on, Pyre will error when a function parameter is either annotated with a type that contains typing.Any or not annotated with any type at all (in which case Pyre will treat it as typing.Any by default). It will also error when a method parameter is not annotated, unless that parameter is the first parameter of a bound or static method (i.e. self, whose type pyre can infer).

We enforce typed argument because typing.Any can hide type errors that will happen at runtime:

from typing import Any

def say_hello(name) -> None:
    print("Hello " + name)


# This line will raise at runtime, but no type error since `say_hello`s `name` has type `Any`.
say_hello(42)

You can silence this by adding a non-Any annotation to all parameters of functions and methods (other than self and cls for bound and class methods, which you may omit).

3: Missing Return Annotation

If strict mode is turned on, Pyre will error when a function is either annotated with a return type that contains typing.Any, or is not annotated with any return type at all (in which case Pyre will treat it as returning typing.Any by default).

This is bad because a return type of typing.Any may potentially hiding legitimate type errors that may happen at runtime:

from typing import Any

def f():
  return 42

print("a" + f())
$ pyre
Missing return annotation [3]: Returning `int` but no return type is specified.

The best way to silence this error is to add non-Any return annotation to every function.

4: Missing Attribute Annotation

In strict mode, Pyre will error when an attribute does not have an annotation.

def foo() -> str:
    return "Hello, World!"
class A:
    b = foo() # Missing attribute annotation
$ pyre
Missing attribute annotation [4]: Attribute `b` of class `A` has no type specified.

Adding a type annotation will resolve this error:

def foo() -> str:
    return "Hello, World!"
class A:
    b: str = foo()

This error can also occur when pyre is inferring attribute types from constructors. For example, here we know that b is an int based on the parameter annotation in __init__:

class A:
    def __init__(self, b: int) -> None:
        self.b = b

But here we need a annotations because we can't just propagate an argument annotation:

class A:
    def __init__(self, arg: int) -> None:
        self.a = arg + 5
        self.b = arg + 5
$ pyre
Missing attribute annotation [4]: Attribute `a` of class `A` has type `int` but no type is specified.
Missing attribute annotation [4]: Attribute `b` of class `A` has type `int` but no type is specified.

We can fix this by making the annotation explicit, either in the class body or in __init__:

class A:
    a: int
    def __init__(self, arg: int) -> None:
        self.a = arg + 5
        self.b: int = arg + 5

5: Missing Global Annotation

If strict mode is turned on, Pyre will error when a globally accessible variable is not annotated. If pyre was able to infer a type for the variable, it will emit this type in the error message. The fix is usually to add an annotation to the variable.

Note: This error has also arisen when there is some ambiguity of whether a declaration is a global expression or a type alias, in these cases pyre assumes it is an expression. Adding a : TypeAlias annotation lets pyre know that it is a type alias and solves the problem.

from typing_extensions import TypeAlias

# This declaration would result in an error
MyTypeAlias = Dict[str, "AnotherTypeAlias"]
$ pyre
Missing global annotation [5]: Globally accessible variable `MyTypeAlias` has no type specified.
from typing_extensions import TypeAlias
# This declaration ensures that pyre knows MyTypeAlias is a type alias
MyTypeAlias: TypeAlias = Dict[str, "AnotherTypeAlias"]

6: Incompatible Parameter Type

Pyre will error if an argument passed into a function call does not match the expected parameter type of that function.

def takes_int(x: int) -> None:
  pass

def f(x: Optional[int]) -> None:
  takes_int(x) # Incompatible parameter type error
$ pyre
Incompatible parameter type [6]: In call `takes_int`, for 1st positional argument, expected `int` but got `Optional[int]`.

If you are seeing errors with invariant containers where some Container[T] is expected but you are passing Container[S] where S < T, please see Covariance and Contravariance.

7: Incompatible Return Type

Pyre will error when the value returned from a function does not match the annotation.

def foo() -> int:
  return "" # incompatible return type
$ pyre
Incompatible return type [7]: Expected `int` but got `str`.

Updating the return annotation, or the value returned from the function will resolve this error.

def foo() -> str:
  return "" # compatible: No error

8: Incompatible Attribute Type

Pyre will error if a value is assigned to an attribute that does not match the annotated type of that attribute.

class Foo:
  x: int = 0

def f(foo: Foo) -> None:
  foo.x = "abc" # Incompatible attribute type error
$ pyre
Incompatible attribute type [8]: Attribute `x` declared in class `Foo` has type `int` but is used as type `str`.

If you are seeing errors with invariant containers where some Container[T] is expected but you are passing Container[S] where S < T, please see Covariance and Contravariance.

9: Incompatible Variable Type

Pyre will error when assigning incompatible types to local variables and parameters that were explicitly annotated.

That is, the following will error:

def f(x: int) -> None:
  x = "" # Incompatible variable type error
  y: int = 1
  y = "" # Incompatible variable type error
$ pyre
Incompatible variable type [9]: x is declared to have type `int` but is used as type `str`.
Incompatible variable type [9]: y is declared to have type `int` but is used as type `str`.

The rationale here is that it's surprising for an explicitly annotated variable to have an incompatible type later on in the same function.

If you are constructing an object that is generic over an invariant type, you may run into an error:

from typing import TypeVar

_T = TypeVar('_T')

class Foo(Generic[_T]):
    def __init__(self, x: _T) -> None: ...

def f() -> None:
    foo: Foo[Optional[int]] = Foo(x=1) # Incompatible variable type error
$ pyre
Incompatible variable type [9]: foo is declared to have type `Foo[Optional[int]]` but is used as type `Foo[int]`.

This is due to the fact that Foo[X] is not less than Foo[Y] even if X < Y when the type variable is invariant. You can declare your intention to initialize the object with a wider type than is given to fix this error:

from typing import TypeVar

_T = TypeVar('_T')

class Foo(Generic[_T]):
    def __init__(self, x: _T) -> None: ...

def f() -> None:
    foo: Foo[Optional[int]] = Foo[Optional[int]](x=1)

10: Unbound Name

Pyre produces an Unbound Name error when your code access a variable (local or global) that pyre believes is not defined.

In most cases code that does this is invalid and will always fail. For example this code will always fail at runtime:

def f() -> int:
    return x  # use of an unbound name x
$ pyre
Unbound name [10]: Name `x` is used but not defined in the current scope.

There are some cases where python code that works fine at runtime could produce this error, for example if a function implicitly sets a module-level global variable that is not declared in the toplevel. Pyre will not accept this because module-level globals require type annotations, and if they have no declaration there is nowhere to put the annotation:

def set_x() -> None:
    global x
    x = 42

def use_x() -> None:
    print(x)


# this code will run fine, but pyre cannot analyze the type or use of the
# implicitly-defined global x and will complain about an unbound name.
set_x()
use_x()
$ pyre
Unbound name [10]: Name `x` is used but not defined in the current scope.

You can fix this by explicitly adding a declaration of the top-level variable x, for example:

x : Optional[int] = None

def set_x() -> None:
    global x
    x = 42

def use_x() -> None:
    print(x)


# this code will run fine
set_x()
use_x()

11, 31: Undefined or Invalid Type

Pyre recognizes class names as valid annotations. Most basic types are imported from the typing module or are already available from builtins like str, int, bool, etc. You can also define your own type alias on the global scope, which can be used as annotations:

from typing_extensions import TypeAlias

INT_OR_STR: TypeAlias = Union[int, str]

If you use a name as an annotation that is not a valid type or valid alias, you will see this error:

from typing import Callable, List
from typing_extensions import Final, Literal

GLOBAL_VALUE = "string"

def f0() -> GLOBAL_VALUE: ... # Error! `GLOBAL_VALUE` is a value, not a type.

def f1() -> type(GLOBAL_VALUE): ...   # Error! Static type annotations cannot be dynamically computed.

def f2() -> [int]: ...  # Error! `[int]` is not a valid type. If you mean a list of int, use `typing.List[int]`.

def f3() -> (int, str): ...  # Error! `(int, str)` is not a valid type. If you mean a pair of int and str, use `typing.Tuple[int, str]`.

def f4() -> Callable[[int]]: ...  # Error! `Callable[[int]]` is not a valid type because the return type of the callable is missing. Good example: `Callable[[int], int]`.

def f5() -> Callable[int, int]: ...  # Error! `Callable[int, int]` is not a valid type. The parameter types of the callable must be enclosed in square brackets. Good example: `Callable[[int], int]`.

def f6() -> List[Final[int]]: ...  # Error! `Final` may only be used as the outermost type in annotations. See PEP 591.

def f7() -> Literal[GLOBAL_VALUE]: ...  # Error! Only literals are allowed as parameters for `Literal`. See PEP586. Good example: `Literal[42]` or `Literal["string"]`.
$ pyre
Undefined or invalid type [11]: Annotation `GLOBAL_VALUE` is not defined as a type.
Invalid type [31]: Expression `type(GLOBAL_VALUE)` is not a valid type.
Invalid type [31]: Expression `[int]` is not a valid type.
Invalid type [31]: Expression `(int, str)` is not a valid type.
Invalid type [31]: Expression `typing.Callable[[int]]` is not a valid type.
Invalid type [31]: Expression `typing.Callable[(int, int)]` is not a valid type.
Invalid type [31]: Expression `GLOBAL_VALUE` is not a literal value.

You can fix this error by verifying that your annotation is

  1. statically determined.
  2. properly imported from typing if applicable.
  3. properly defined in the module you are importing from. If the module you are importing from has a stub file, you should check the definition there.
  4. properly adhere to the additional rules of special types (e.g. Callable, Final, and Literal).

Type Aliases

For type aliases, check that your type alias is defined

  1. with a valid type on the RHS. If you provide an annotation for the TypeAlias assignment, it must be typing_extensions.TypeAlias.
  2. on the global scope, not nested inside a function or class.

ParamSpec

For ParamSpec, check that you have used both *args: P.args and **kwargs: P.kwargs in your function's parameters:

from typing import Callable

from pyre_extensions import ParameterSpecification

P = ParameterSpecification("P")

# Error because `**kwargs: P.kwargs` is missing.
def bad1(f: Callable[P, int], *args: P.args) -> int:
    return f(*args)

# Error because `*args: P.args` is missing.
def bad2(f: Callable[P, int], **kwargs: P.kwargs) -> int:
    return f(**kwargs)
$ pyre
Undefined or invalid type [11]: Annotation `P.args` is not defined as a type.
Call error [29]: `typing.Callable[P, int]` cannot be safely called because the types and kinds of its parameters depend on a type variable.
Undefined or invalid type [11]: Annotation `P.kwargs` is not defined as a type.

No type error if you have used both *args: P.args and **kwargs: P.kwargs

from typing import Callable

from pyre_extensions import ParameterSpecification

P = ParameterSpecification("P")

# OK
def good(f: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int:
    return f(*args, **kwargs)

12: Incompatible Awaitable Type

In strict mode, pyre will verify that all calls to await are on awaitable values, to ensure that you cannot get a runtime error awaiting an object that is not a coroutine.

A common situation where working code will produce this error is when pyre cannot statically verify that an awaitable is non-Null, for example:

import asyncio

async def f(flag: bool) -> None:
    if flag:
        task = asyncio.create_task(asyncio.sleep(1))
    else:
        task = None
    await task  # would throw a ValueError if flag were false

asyncio.run(f(True))

In this example, task will always be a valid awaitable unless some other module overwrites the global flag, but pyre cannot prove that this does not happen. The error we get has the message

$ pyre
Expected an awaitable but got `typing.Optional[asyncio.tasks.Task[None]]`

You can fix this error by ensuring that the awaited object has an awaitable type. In the case of optional values, you can use refinement to rule out None. The example above can be fixed by tweaking the definition of main:

async def f(flag: bool) -> None:
    if flag:
        task = asyncio.create_task(asyncio.sleep(1))
    else:
        task = None
    if task is not None:
      await task

13: Uninitialized Attribute

In strict mode, pyre will throw an error for class attributes which are declared without default values if they are not initialized in a constructor, for example:

class A:
    x : int

    def __init__(self) -> None:
        pass
$ pyre
Uninitialized attribute [13]: Attribute `x` is declared in class `A` to have type `int` but is never initialized.

For a case like this, you can fix the error either by setting a default value like x : int = 0 at the class level, or by setting x in the constructor e.g. self.x = 0.

class A:
    x: int = 0
    def __init__(self) -> None:
        pass
class A:
    x: int

    def __init__(self, x: int = 0) -> None:
        self.x = x

Dataclass-like classes

One case where this can occur is when using a library providing a "dataclass-like" decorator that, for example, autogenerates a constructor setting attributes.

from dataclasses import dataclass

@dataclass
class A:
    x: int

Pyre currently knows that that uninitialized attributes of classes wrapped in dataclass and attrs decorators will generate constructors that set the attributes. But it does not understand many custom libraries that do similar things, for example test frameworks, or new decorators that wrap the dataclass decorator and add more logic.

There is not currently a way to fix this other than via pyre-ignore or pyre-fixme directives. The python typing community is aware of this problem but has not yet settled on a solution, you can see discussion here.

14,15: Behavioral Subtyping

Method overrides should follow Liskov's substitution principle. In short, parameter types can't be more restrictive and return types can't be more permissive in overridden methods. To see why, consider the following example:

def width(image: Image) -> float:
    return image.width()

Say we now have different implementations of our Image class, one of which violates the substitution principle:

class Image:
    def width() -> float: pass

class JpegImage(Image):
    def width() -> int: return 10  # this is fine

class ComplexImage(Image):
    def width() -> complex: return 1j

def foo() -> None:
    image: Image = ComplexImage()
    print(int(image.width()))

The above code fails at runtime with TypeError: can't convert complex to int. The case for parameters follows analogously.

Common Reasons

  • Could not find parameter y in overriding signature.: Check if the overriding function can accept all arguments that the overridden function can.

    class Base:
        def foo(self, x: int, y: str) -> None:
            pass
    
    class Child(Base):
        def foo(self, x: int) -> None:
            pass
  • Type Foo is not a subtype of the overridden attribute type Bar:

    class Base:
        a: int = 0
    
    class Child(Base):
        a: str = ""
    
    def foo() -> None:
        base: Base = Child()
        base.a + 1

    This would fail at runtime with TypeError: can only concatenate str (not "int") to str.

  • Returned type Foo is not a subtype of the overridden return Bar.: Check for reasons like invariance.

16: Missing Attributes

Your code is most likely trying to access an attribute that Pyre does not know about. Pyre has various ways of inferring what is an attribute of an object:

Explicitly Declare the Attribute

class Derp:
    my_attribute: int = 1

    @property
    def my_property(self) -> int: ...

Implicitly Declare the Attribute

class Derp:
    def __init__(self, foo: str) -> None:
        self.my_attribute: int = 1

        # The `foo` attribute is inferred to have type `str` because the
        # parameter `foo` has type `str`.
        self.foo = foo

Pyre does one level of inlining to infer implicit parameters We suggest you do not heavily rely on this feature as it is not sound and makes our code brittle. Support for this is temporary.

Common Reasons

  • Optional type has no attribute foo.: See Optional attributes.

  • Foo has no attribute bar.: Check if you have explicitly provided the type for bar either in the constructor or as a class attribute.

  • Module foo has no attribute bar: Check if the library has stubs. If so, you may need to add the function, class, or global variable to the stub.

  • A library class has an attribute but it is not recognized by Pyre: Check if the library has stubs. If so, you may need to add the attribute to the class in the stub.

  • Your class has dynamic attributes: Consider using __getattr__ in a stub so that Pyre doesn't complain about those attributes.

17: Incompatible Constructor Annotation

PEP 484 specifies that __init__ method of any class must be annotated to return None. Pyre will emit an error if the user's annotation does not conform to the specification.

# pyre-strict

class A:
    def __init__(self) -> "A":  # Error 17: Invalid return annotation of `__init__`.
        ...

class B:
    def __init__(self) -> None:  # OK
        ...
$ pyre
Incompatible constructor annotation [17]: `__init__` is annotated as returning `A`, but it should return `None`.

19: Too Many Arguments

Pyre verifies that you pass a legal number of arguments to functions.

The most obvious way to encounter this error is to just pass too many arguments to a function:

def f(x: int) -> int:
    return x

f(5, 6)  # this would throw a TypeError at runtime, and pyre complains
$ pyre
Too many arguments [19]: Call `f` expects 1 positional argument, 2 were provided.

To fix this, make sure you pass the correct number of parameters. In some cases you may encounter this error if you intended to use a variadic argument (*args) or to set a default value.

Pyre will also throw this error if you pass too many positional arguments to a function that uses python's ability restrict arguments to be keyword-only specified by PEP 3102:

def f(*, x: int) -> int:
    return x

f(5)  # As before, this throws a TypeError because x is positional-only
f(x=5)  # this line will typecheck and run without error
$ pyre
Too many arguments [19]: Call `f` expects 0 positional arguments, 1 was provided.

20: Missing Argument

Pyre verifies that function calls provide all the expected arguments, so it will complain about code like this:

def f(x: int) -> ing:
    return x

f()
$ pyre
Missing argument [20]: Call `f` expects argument `x`.

To fix this, make sure all required arguments are provided.

21: Undefined Name, Undefined Import

This is usually caused by failing to import the proper module.

from my_module import my_function

my_function()
$ pyre
Undefined import [21]: Could not find a module corresponding to import `my_module`.

Pyre will raise error 21 instead ("Undefined import") when the import statement is present, but the module to be imported could not be found in the search path. If the module provides stub files, please provide their location via the --search-path commandline parameter.

Namespace Package Modules

One case where you may run into undefined imports on code that works at runtime is when importing namespace modules. The CPython runtime allows you to import a directory that is on your PYTHONPATH, even if it contains no __init__.py; this behavior is defined in PEP 420 and the module is called a namespace package. In order to make Pyre both fast and consistent on incremental updates, in Pyre we only allow importing namespace packages that have at least one python file as a direct child.

So, for example, if I have a directory tree with just a/b/c.py then Pyre will allow import a.b.c and import a.b but not import a. A namespace package module can never contain useful types or code so it is rare to directly import it, but in special cases it might be useful (for example to access the __name__ attribute). In these cases, you'll need to suppress Pyre errors.

22: Redundant Cast

Pyre will warn when you attempt to use typing.cast to cast a variable to a type that the type checker already knows that variable has. This is because typing.cast is purely a tool for communicating with the static type checker, and will not provide any runtime guarantees. Therefore a redundant cast provides no value and is likely a mistake.

from typing import cast
def foo(x: int) -> None:
    y = cast(int, x)
$ pyre
Redundant cast [22]: The value being cast is already of type `int`.

If you are trying to document the type of the variable, you can provide an explicit annotation where it is declared. If you are trying to add a sanity check at runtime that the type of a variable is what you already believe it must be, use isinstance.

23: Unable to Unpack

Pyre will warn you when trying to assign a value to a tuple with the wrong number of items.

def foo() -> None:
    a, b = (1, 2, 3)
    x, y = 42
$ pyre
Unable to unpack [23]: Unable to unpack 3 values, 2 were expected.
Unable to unpack [23]: Unable to unpack `int` into 2 values.

Common reasons:

  • Trying to assign an Optional value to a tuple:

    def bar() -> None:
        x = None
        if 2 + 2 == 4:
            x = ("a", "b")
    
        a, b = x
    $ pyre
    Unable to unpack [23]: Unable to unpack `typing.Optional[typing.Tuple[str, str]]` into 2 values.
    
  • Unpacking an incorrect number of elements when looping over a list:

    for a, b in [1, 2, 3]:
        print(a, b)
    
    $ pyre
    Unable to unpack [23]: Unable to unpack `int` into 2 values.

24: Invalid Type Parameters

Pyre will error if a generic type annotation is given with unexpected type parameters.

"Generic type expects X type parameters ..."

Either too few or too many type parameters were provided for the container type. For example,

x: List[int, str] = [] # Invalid type parameters error
$ pyre
Invalid type parameters [24]: Generic type `list` expects 1 type parameter, received 2, use `typing.List[<element type>]` to avoid runtime subscripting errors.

In this case, typing.List is a generic type taking exactly one type parameter. If we pass a single parameter, this resolves the issue.

x: List[Union[int, str]] = [] # No error

If you do not know or do not want to specify the type parameters, use typing.Any but still ensure the arity is correct.

x: List = [] # Invalid type parameters error
x: List[Any] = [] # No error

Note: You may see a suggestion to use typing.List instead of builtins list as the type annotation when providing type parameters. This is to avoid runtime errors, because the builtin list does not support subscripting and list[int] is therefore not runtime-friendly.

"Non-generic type cannot take type parameters ..."

Type parameters are only meaningful if the container type is generic. Passing in the type parameter binds the provided parameter type to the generic in the container class. For example,

class Container:
    def add(self,element: int) -> None: ...
    def get_element(self) -> int: ...

x: Container[int] = Container() # Invalid type parameter error
$ pyre
Invalid type parameters [24]: Non-generic type `Container` cannot take parameters.
from typing import TypeVar, Generic

T = TypeVar('T')

class Container(Generic[T]):
    def add(self,element: T) -> None: ...
    def get_element(self) -> T: ...

x: Container[int] = Container()
x.get_element() # returns int

y: Container[str] = Container()
y.get_element() # returns str

"Type parameter violates constraints ..."

If a container class is generic over a type variable with given type bounds, any type parameter used must comply with those type bounds. For example,

from typing import TypeVar, Union, Generic

T = TypeVar('T', bound=Union[int, bool])

class Container(Generic[T]):
    def add(self, element: T) -> None: ...
    def get_element(self) -> T: ...

x: Container[int] = Container() # No error

y: Container[str] = Container() # Invalid type parameter error
$ pyre
Invalid type parameters [24]: Type parameter `str` violates constraints on `Variable[T (bound to typing.Union[bool, int])]` in generic type `Container`.

26: Typed Dictionary Access With Non-Literal

In python, typed dictionaries can only be accessed using literal strings that can be statically verified as valid. As a result, code like this will not typecheck even though it works at runtime, because we cannot statically verify that key in print_value is a valid Shape key:

from typing import TypedDict

class Shape(TypedDict):
    sides: int
    color: str

shape: Shape = {"sides": 4, "color": "blue"}

print(shape["sides"])  # this is fine because "sides" is a literal

for key in ["sides", "color"]:
    print(key, shape[key])  # pyre will complain here because it can't prove `key` is valid
$ pyre
TypedDict accessed with a non-literal [26]: TypedDict key must be a string literal. Expected one of ('color', 'sides').

The example above shows a situation where you might hit this error: when you want to iterate over the fields of a typed dict. A suggested fix is to use type-safe operations like dictionary.items instead. For example the following code produces the same results but type checks:

class Shape(TypedDict):
    sides: int
    color: str

shape: Shape = {"sides": 4, "color": "blue"}

print(shape["sides"])  # this is fine because "sides" is a literal

for key, value  in shape.items():
    print(key, value) # no error

In other cases where you need to access a TypedDict using a variable as a key, you can use dictionary.get(key).

from typing import TypedDict

class Shape(TypedDict):
    sides: int
    color: str

shape: Shape = {"sides": 4, "color": "blue"}

print(shape["sides"])  # this is fine because "sides" is a literal

for key in ["sides", "color"]:
    print(key, shape.get(key)) # no error

27: Typed Dictionary Key Not Found

If you try to access a typed dictionary with a string literal that pyre knows is not a valid key, pyre will emit an error:

from typing import TypedDict

class Shape(TypedDict):
    sides: int
    color: str

def f(shape: Shape) -> None:
    print(shape["location"])  # error here
$ pyre
TypedDict accessed with a missing key [27]: TypedDict `Shape` has no key `location`.

A possible fix: pyre considers instances of any TypedDict with additional fields to be a subtype of Shape, so in many cases you could handle the need for a location field in some Shape dicts by creating a new type as follows:

from typing import TypedDict

class Shape(TypedDict):
    sides: int
    color: str

class ShapeWithLocation(TypedDict):
    sides: int
    color: str
    location: str

def f(shape: ShapeWithLocation) -> None:
    print(shape["location"])  # okay
    g(shape)  # also okay: ShapeWithLocation is a subtype of Shape

def g(shape: Shape) -> None:
    print(shape)

28: Unexpected Keyword

Pyre will error if attempting to pass an argument by name and there are no parameters with a matching name. For example,

def foo(integer: int, string: str) -> None: ...

foo(1, "one")  # no error
foo(string="one", integer=1)  # no error
foo(integer=1, undefined="one")  # type error
$ pyre
Unexpected keyword [28]: Unexpected keyword argument `undefined` to call `foo`.

29: Call Error

Pyre will emit an error on seeing a call of one of the following types:

  1. The called object is not a function. This means that its inferred type is not Callable and it is not an instance of a class which implements a __call__ method. This could happen due to user error (the object is indeed not a function) or due to an incorrect or incomplete type stub for the object's class causing pyre to infer the wrong type.

  2. The call cannot be safely typed since the types and kinds of its parameters depend on a type variable. This is seen when the callable is typed using a ParameterSpecification type variable and the *args and **kwargs are not passed into the call correctly, i.e. together and in order. (For more details see PEP 612)

from pyre_extensions import ParameterSpecification
from typing import Callable

P = ParameterSpecification("P")

def decorator(f: Callable[P, int]) -> Callable[P, None]:

    def foo(*args: P.args, **kwargs: P.kwargs) -> None:
        f(*args, **kwargs)    # Accepted, should resolve to int
        f(*args)              # Rejected : error here
        f(*kwargs, **args)    # Rejected : error here
        f(1, *args, **kwargs) # Accepted

    return foo
$ pyre
Call error [29]: `typing.Callable[sandbox.P, int]` cannot be safely called because the types and kinds of its parameters depend on a type variable.
Call error [29]: `typing.Callable[sandbox.P, int]` cannot be safely called because the types and kinds of its parameters depend on a type variable.

30, 36: Terminating Analysis, Mutually Recursive Type Variables

Overly-complex Functions

In very rare cases where a function has a lot of if branches or for-loops, Pyre may raise an error saying that is unable to analyze the function fully. Analyzing extremely complex functions in depth can be costly, so Pyre only does so up to a limit. This means that it won't infer precise types for some variables and won't catch errors related to their usage. For example:

def my_function() -> None:
    u = 42

    if foo():
        x1 = bar()
        if x1:
            x2 = baz()
            if x2:
                # <more branches of code>
        else:
            # <more branches>

        if foo2():
            # <code>

        if foo3():
            # <even more branches>
        # <and even more branches>

$ pyre
Analysis failure [30]: Pyre gave up inferring types for some variables because function `foo` was too complex.
Please simplify the function by factoring out some if-statements or for-loops.

To remedy this, factor out some of the branching code into separate functions out that each function has a limited amount of branching logic:

def do_stuff() -> None:
    if foo():
        # <code>

    if foo():
        # <even more branches>

    # <and even more branches>

def bar() -> None:
    u = 42

    if foo():
        x1 = bar()
        if x1:
            x2 = baz()
            if x2:
                # <more branches of code>
        else:
            # <more branches>

        do_stuff()

Other Analysis Failures

These errors usually indicates a bug in Pyre. Please open an issue on Github.

31: Invalid Type

This indicates that you are using some expression that pyre does not understand as a type.

Some situations where you might run into this:

  • Using a list of types rather than List[type]:
x: [str] = ["a string"]

You can fix this by using the List type:

x: List[str] = ["a string"]
  • Using a constructor call rather than a bare class name:
    class A:
        ...
    
    a: A() = A()
    You can fix this by using a bare type name:
    a: A = A()

32: Invalid Argument

This error usually means you are using a variable in a way that is incompatible with its structure, either as an argument to a function call or as part of a data structure.

This could be from using an invalid variadic parameter (informally a "splat"):

x: int = 5

print(*x)   # invalid use of x, which is not iterable
$ pyre
Invalid argument [32]: Unpacked argument `x` must have an unpackable type but has type `typing_extensions.Literal[5]`.

or using an invalid keyword parameter (informally a "double-splat"):

from typing import Dict

x: int = 5

d: Dict[int, int] = {**x}  # invalid use of x, which is not a mapping

dict(**d)  # invalid use of d; function kwargs must be a mapping with string keys
$ pyre
Invalid argument [32]: Keyword argument `x` has type `typing_extensions.Literal[5]` but must be a mapping.
Invalid argument [32]: Keyword argument `d` has type `Dict[int, int]` but must be a mapping with string keys.

It's also possible to hit this error code on constraint mismatches when using tuple variadic variables as specified in PEP 646, which are an advanced feature of pyre.

33: Prohibited Any

Pyre will warn on any usage of typing.Any when run in strict mode. Any is an escape hatch that hides type errors and introduces potential type inconsistencies which Pyre strict is designed to make explicit.

import typing
from typing import Any, Dict

def foo() -> None:
    x: typing.Any = 1
$ pyre
Prohibited any [33]: Expression `x` has type `int`; given explicit type cannot be `Any`.

To resolve this error, replace Any with any other annotation. Using builtins object is acceptable if you are looking for a supertype of all classes.

34: Invalid Type Variable

Type variables can only be used as types when they have already been placed "in scope". A type variable can be placed into scope via:

  • Generic class declarations
    • for example, class C(Generic[T]): puts T into scope for the body of the class
  • The parameter types of a generic function
    • for example, def foo(x: T) puts T into scope for the body and return type annotation of the function

For example:

from typing import List

class Base:
    foo: List[T] = []

$ pyre
Invalid type variable [34]: The current class isn't generic with respect to the type variable `Variable[T]`.
def foo(x: int) -> List[T]:
    return [x, x]

$ pyre
Invalid type variable [34]: The type variable `Variable[T]` isn't present in the function's parameters.

Suggested fix:

from typing import Generic, List

class Base(Generic[T]):
    foo: List[T] = []

base: Base[int]

def foo(x: T) -> List[T]:
    return [x, x]

Decorator Factories

One common error is when defining a generic decorator factory. The Python type system doesn't currently place T into scope within a Callable type. So, it considers T to be a type variable from the outer scope. This can lead to errors for apparently valid code:

from typing import Callable, TypeVar

T = TypeVar("T")
R = TypeVar("R")

def my_decorator_factory(message: str) -> Callable[[Callable[[T], R]], Callable[[T], R]]:

    def _decorator(f: Callable[[T], R]) -> Callable[[T], R]:

        def _inner(x: T) -> R:
            print(message)
            return f(x)

        return _inner

    return _decorator

$ pyre
Invalid type variable [34]: The type variable `Variable[R]` isn't present in the function's parameters.
Invalid type variable [34]: The type variable `Variable[T]` isn't present in the function's parameters.

Suggested fix: Use a callback protocol to define the return type.

from typing import Callable, Protocol, TypeVar

T = TypeVar("T")
R = TypeVar("R")

class MyCallableProtocol(Protocol):
    def __call__(self, f: Callable[[T], R]) -> Callable[[T], R]: ...

def my_decorator_factory(message: str) -> MyCallableProtocol:

    def _decorator(f: Callable[[T], R]) -> Callable[[T], R]:

        def _inner(x: T) -> R:
            print(message)
            return f(x)

        return _inner

    return _decorator

If you are using a ParamSpec in your decorator, use the following:

from typing import Any, Callable, Coroutine, Protocol, TypeVar
from pyre_extensions import ParameterSpecification
import asyncio

R = TypeVar("R")
P = ParameterSpecification("P")

class MyCallableProtocol(Protocol):
    def __call__(self, f: Callable[P, Coroutine[object, object, R]]) -> Callable[P, Coroutine[object, object, R]]: ...

def my_decorator_factory(message: str) -> MyCallableProtocol:
    def _decorator(f: Callable[P, Coroutine[object, object, R]]) -> Callable[P, Coroutine[object, object, R]]:

        async def _inner(*args: P.args, **kwargs: P.kwargs) -> R:
            print(message)
            return await f(*args, **kwargs)

        return _inner

    return _decorator

@my_decorator_factory("hello!")
async def foo() -> int:
    return 1

asyncio.run(foo())

Note: Support for such callables is currently experimental and varies from one typechecker to another. This behavior may change in the future.

35: Illegal Annotation Target

Pyre will error when a type annotation is applied to something that can't be annotated. This could happen when:

  1. A variable is re-annotated after first declaration or an explicity annotated function parameter is re-annotated within the function body. This is not allowed as re-annotating variables reduces readability and causes the annotation of a variable to depend on the position in control flow.
def transformation(p: int) -> str:
    return str(p + 1)

def foo(x: int) -> None:
    y: int = x + 2
    z = x + 3

    # Each of the following will produce an error
    x: str = transformation(x)
    y: str = transformation(y)
    z: int = 4
$ pyre
Illegal annotation target [35]: Target `x` cannot be annotated after it is first declared.
Illegal annotation target [35]: Target `y` cannot be annotated after it is first declared.
Illegal annotation target [35]: Target `z` cannot be annotated after it is first declared.

An easy fix for the first two errors is to use a new variable rather than re-annotating the old variable so it can hold a new type. For the third error, z should have been annotated at first declaration.

  1. Trying to annotate non-self attributes, i.e annotating the attributes of a different class than the one whose scope you are in:
class Foo:
    attribute: int = 1

class Bar:
    def __init__(self) -> None:
        Foo.attribute: str = "hello"

def some_method() -> None:
    Foo.attribute: int = 5
$ pyre
Illegal annotation target [35]: Target `sandbox.Foo.attribute` cannot be annotated.
Illegal annotation target [35]: Target `sandbox.Foo.attribute` cannot be annotated.

This is not allowed as Pyre needs to be able to statically determine the type of globally accessible values, including class attributes. Even if Pyre followed control flow across functions to determine class attribute annotations, such re-annotations imply very dynamic behavior that makes the code difficult to work with.

The fix for this situation, similar to the case above, is to annotate the class attribute at its definition in the class that owns it and remove any annotations elsewhere. If this attribute is from a third party library, then you can add a stub for the class and annotate the attribute there.

39: Invalid Inheritance

When defining a new class, Pyre will error if the base class given is not a valid parent class. This may be caused by various conditions:

  1. The parent class is marked as final which means it explicitly is annotated as not supporting child classes.
@final
class Base:
    ...

class Derived(Base): # Invalid inheritance error
    ...
$ pyre
Invalid inheritance [39]: Cannot inherit from final class `Base`.
  1. The expression given in the base class field is not a class at all.
MY_GLOBAL: str = "string"

class Foo(MY_GLOBAL): # Invalid inheritance error
    ...

Pyre does not support dynamic expressions as base classes, even if they may evaluate to a valid class at runtime. This is because the type checker relies on building up a valid class hierarchy before it can resolve types in the Python it is analyzing. On the other hand, type aliases are equivalent to types and are acceptable as base classes.

  1. You are defining a typed dictionary that does not inherit from another typed dictionary.
from typing import TypedDict

class NonTypedDict:
    ...

class Movie(TypedDict):
    name: str
    year: int

class BookBasedMovie(Movie): # No error
    based_on: str

class BookBasedMovie(NonTypedDict): # Invalid inheritance error
    based_on: str

If inheriting from another typed dictionary, fields need to have a consistent type between child and parent, in order for subclassing to be sound. Similarly, a required field in the child must also be required for the parent.

40: Invalid Override

Pyre will error when methods in a child class override those in a parent class inconsistently. Static methods cannot be overwritten by non-static methods, and final methods cannot be overwritten.

class A:
    @staticmethod
    def foo() -> int:
        pass

class B(A):
    @classmethod # Non-static method `B.foo` cannot override a static method defined in `A`.
    def foo(cls) -> int:
        pass
from typing import final

class Foo:
    @final
    def bar(self) -> None:
        pass

class Bar(Foo):
    def bar(self) -> None: # Invalid override, because Foo.bar is final
        pass

41: Invalid Assignment

Pyre will error on assignments to final attributes, read-only properties, and class variables from a class instance. For example,

from typing import Final, Optional

class Foo:
  field: Final[Optional[int]] = 1

  def foo() -> None:
    self.field = 2 # Invalid assignment

class Bar:
    _x = 1
    @property
    def x(self) -> int:
        return self._x

def bar(b: Bar) -> None:
    b.x = 1 # Invalid assignment

To fix this error, change the definition of this attribute to something that is mutable, if it is not intended to be read-only.

42: Missing Overload Implementation

Pyre will throw this error if a source module specifies one or more overloads via typing.overload but fails to provide an implementation, for example:

from typing import overload

@overload
def f(x: int) -> float:
    ...

@overload
def f(x: str) -> str:
    ...

Missing implementations are allowed in .pyi stub files.

To fix it, provide exactly one implementation (a function of the same name without the typing.overload decorator). For example above we could implement f as follows:

from typing import overload, Union

@overload
def f(x: int) -> float:
    ...

@overload
def f(x: str) -> str:
    ...

@overload
def f(x: str) -> str:
    ...

def f(x: Union[int, str]) -> Union[float, str]:
    if isinstance(x, int):
        return float(x)
    else:
        return x

43: Incompatible Overload Implementation

Pyre will error if you define one or more overloads using typing.overload, and your concrete implementation has an incompatible type signature.

For example, this code will produce an incompatible overload implementation error

# pyre-strict

from typing import overload, Union

@overload
def f(x: int) -> float:
    ...

@overload
def f(x: float) -> int:
    ...

@overload
def f(x: str) -> str:
    ...

def f(x: Union[int, float, str]) -> Union[int, str]:
    if isinstance(x, float):
        return int(x)
    elif isinstance(x, int):
        return float(x)
    else:
        return x

The problem here is that the return type Union[int, str] is too narrow to permit f to return float when called on an int argument.

You can fix this by either removing incorrect overload delcarations or making sure all parameters and return type annotations on the concrete implementation are general enough to be consistent with the overloads:

def f(x: Union[int, float, str]) -> Union[int, float, str]:
    <same implementation>

45: Invalid Class Instantiation

In typed Python, some classes that represent abstract interfaces may not be directly instantiated. Pyre considers a class C abstract, and will error on invalid instantiation if you try to construct an instance directly in either of the following cases:

  1. C contains one or more abstract methods that are left not overridden. Abstract methods are defined as methods that are decorated with @abc.abstractmethod.

For example, here Derived0 is abstract because it does not override bar, but Derived1 may be instantiated:

import abc
from typing import Protocol

class Base(abc.ABC):
    @abc.abstractmethod
    def foo(self) -> None:
        raise NotImplementedError
    @abc.abstractmethod
    def bar(self) -> str:
        raise NotImplementedError

class Derived0(Base):
    def foo(self) -> None:
        print(self.bar())

class Derived1(Derived0):
    def bar(self) -> str:
        return "bar"

def test0() -> None:
    base = Base()  # Error! Class `Base` contains 2 abstract methods and therefore cannot be instantiated.
    derived0 = Derived0()  # Error! Class `Derived0` contains 1 abstract method `bar` and therefore cannot be instantiated.
    derived1 = Derived1()  # OK
  1. C directly inherits from typing.Protocol.

For example, here MyProtocol is abstract because it inherits directly from typing.Protocol, but MyClass (which implements the protocol interface) may be instantiated:

class MyProtocol(Protocol):
    def baz(self, x: int) -> int:
        ...

class MyClass:
    def baz(self, x: int) -> int:
        return x

def test1() -> None:
    object0 = MyProtocol()  # Error! Class `MyProtocol` cannot be instantiated.
    object1 = MyClass()  # OK

46: Invalid Type Variance

In brief, read-only data types can be covariant, write-only data types can be contravariant, and data types that support both reads and writes must be invariant. If a data type implements any functions accepting parameters of that type, we cannot guarantee that writes are not happening. If a data type implements any functions returning values of that type, we cannot guarantee that reads are not happening. For example (note: int is a subclass of float in the type system and in these examples): Writes taking covariants:

from typing import TypeVar, Generic

_T_co = TypeVar("_T_co", covariant=True)

class MyList(Generic[_T_co]):
    def write(self, element: _T_co) -> None:
        ... # adds element to list

def takes_float_list(float_list: MyList[float]) -> None:
    float_list.write(1.0)

int_list: MyList[int] = ...
takes_float_list(int_list)  # this call is OK because MyList is covariant: MyList[int] < MyList[float]
# int_list contains floats

Reads returning contravariants:

from typing import TypeVar, Generic

_T_cont = TypeVar("_T_cont", contravariant=True)

class MyList(Generic[_T_cont]):
    def read(self) -> _T_cont:
        ... # returns first element from list

def takes_int_list(int_list: MyList[int]) -> int:
    return int_list.read()

float_list: MyList[float] = ...
takes_int_list(float_list)  # this call is OK because MyList is contravariant: MyList[float] < MyList[int]
# problem with return above is clear

47: Invalid Method Signature

Pyre will error if a non-static method fails to specify an expected implicit parameter like self for an instance method or cls for a class method, as this argument is always implicitly passed in a call and will cause a runtime crash if not specified. Additionally, Pyre will warn if this parameter is specified but typed as something incompatible with the type of the parent class.

Often times, the method may not need a self or cls and should be decorated with @staticmethod to resolve this error. For example,

class Foo:
    def foo() -> None: ...  # type error

class Foo:
    @staticmethod
    def foo() -> None: ... # no type error

class Foo:
    def foo(self) -> None: ... # no type error

Only type variables with compatible bounds can be used to annotate the self or cls parameter. For example,

from typing import TypeVar

P = TypeVar("T", bound="Parent")
A = TypeVar("S", bound="ChildA")
B = TypeVar("S", bound="ChildB")

class Parent: ...

class ChildA(Parent):
    @classmethod
    def foo(cls: Type[A]) -> A: ...  # no type error

class ChildB(Parent):
    def foo(self: A) -> A: ...  # type error
    def bar(self: B) -> B: ...  # no type error
    def baz(self: P) -> P: ...  # no type error

48: Invalid Exception

In python, you can only raise objects that derive from BaseException (it's more common to subtype Exception or one of the standard library-defined errors like ValueError). Attempting to raise another object such as a bare string will result in a TypeError. As a result, pyre will flag code like this:

def f(x: int) -> None:
    if x > 1:
        raise "x is too big"

To fix this, wrap the information you are trying to raise (usually an error message) in some exception type, for example:

def f(x: int) -> None:
    if x > 1:
        raise ValueError("x is too big")

49: Unsafe Cast

To allow "safe" casts that preserve type soundness, you can use pyre_extensions.safe_cast. This will verify that the type you are casting to is broader than the type of the expression. In cases where this is not the case, pyre will produce an Unsafe Cast error. For example:

from pyre_extensions import safe_cast

def foo(x: int) -> str:
    y = safe_cast(str, x) # Unsafe cast error
    z = safe_cast(Union[int, str], x) # No error
    return z # Invalid return type error

Some context on this: pyre_extensions.safe_cast is a type-safe alternative to typing.cast. The typing.cast function forces type checkers to accept a type for an expression that otherwise would not be valid, which is sometimes useful but also can hide clear type errors, for example:

from typing import cast

def foo(x: int) -> str:
    y = cast(str, x)
    return y # No type error, even though this is unsound.

51: Unused Local Mode

This error will be thrown if you specify more than one local mode, by having multiple line comments of the form # pyre-strict or # pyre-unsafe in the header. Pyre will ask you to remove all but one local mode declaration if you have more than one because the mode needs to be unambiguous.

Context: Pyre supports two modes of type checking, unsafe and strict.

  • By default, every file runs in unsafe mode, but you can change this default to strict in your configuration file.
  • In addition, you can set the type checking mode of a module to differ from the default for the project by adding a comment in the form # pyre-strict or # pyre-unsafe comment on its own line to the file header.

52: Private Protocol Property

Python Protocols provide a way to statically check "duck typing", what many languages would refer to as interfaces.

Because protocols specify only an interface, they should not include private fields and methods, which cannot not be accessed outside of the class where they are defined (including in subclasses). Pyre will complain about the following:

from typing import Protocol

class Duck(Protocol):

    def __quack(self) -> str:
        ...

class SomeDuck:

    def __quack(self) -> str:
        return "quack"

To signal a non-public part of an interface, use a protected field or method (single leading underscore), which is accessible by classes implementing the interface:

from typing import Protocol

class Duck(Protocol):

    def _quack(self) -> str:
        ...

class SomeDuck:

    def _quack(self) -> str:
        return "quack"

53: Missing Annotation For Captured Variables

Pyre makes no attempt at trying to infer the types across function boundaries. The statement holds for nested functions as well. From a nested function's perspective, a variable defined in an nesting function behaves similarly to a global variable. As with global variables, an explicit annotation is required if strict mode is turned on:

def outer_function0() -> int:
    x = foo()
    def inner_function() -> int:
        return x  # Due to the lack of explicit annotation, Pyre will treat this variable as having type `Any`.
    return inner_function()

def outer_function1() -> int:
    x: int = foo()
    def inner_function() -> int:
        return x  # This is ok: the type of `x` is known to be `int`.
    return inner_function()

def outer_function2() -> int:
    x = foo()
    def inner_function(x: int) -> int:
        return x  # This is also ok: even though the outer `x` is not annotated, the `x` parameter of the inner function is.
    return inner_function(x)

54: Invalid TypedDict Operation

In accordance with PEP 598, code that tries to assign a value of the wrong type to a field of a TypedDict will not typecheck:

from typing import TypedDict

class MyDict(TypedDict):
    value: str

d: MyDict = {"value": "hello"}
d["value"] = 5  # Invalid TypedDict operation

To fix this you may need to change your field type to a Union, if variable types are actually needed for a field.

55: TypedDict Initialization Error

Pyre will warn you when initializing a TypedDict with:

  • Missing required fields

    from typing import TypedDict
    
    class Movie(TypedDict):
        name: str
        year: int
    
    movie: Movie = {"name": "The Matrix"}
    
    $ pyre
    TypedDict initialization error [55]: Missing required field `year` for TypedDict `Movie`.
  • Incorrect field type

    movie: Movie = {"name": "The Matrix", "year": "1999"}
    
    $ pyre
    TypedDict initialization error [55]: Expected type `int` for `Movie` field `year` but got `str`.
  • Undefined fields

    movie: Movie = {"name": "The Matrix", "year": 1999, "extra_field": "hello"}
    
    $ pyre
    TypedDict initialization error [55]: TypedDict `Movie` has no field `extra_field`.

56: Invalid Decoration

This error code is a catch-all for a variety of problems that can arise in the course of resolving the type of a decorated function. In all of these cases, these decoration failures will lead to the function being registered with type Any to avoid any spurious downstream errors.

"Pyre was not able to infer the type of the decorator ..."

This should only happen when the decorator access itself is invalid, e.g. when you use a decorator which isn't declared in the stubs for a third-party library.

"Pyre was not able to infer the type of argument ..."

When using the "decorator factory" pattern, we need to resolve the type of both the decorator factory itself as well as the arguments passed to the decorator factory. This is because the types of these arguments can alter the behavior of the returned decorator via overloads or type variables. However, this resolution has to happen early in the environment-building pipeline, when we don't yet have all of the context we need in order to resolve the types of arbitrary expressions. We support resolving literals and simple globals as arguments, but using anything else will result in this error.

To work around this, you can statically type your arguments to the decorator factory as separate globals, which can be validated later in the type-checking pipeline.

from typing import TypeVar

T = TypeVar("T")

def decorator_factory(x: T) -> Callable[[Callable[[int], str]], Callable[[str], T]]:
    ...

# pyre-fixme[56]: Pyre was not able to infer the type of argument
#  `complex_expression()` to decorator factory `decorator_factory`.
@decorator_factory(complex_expression())
def foo(x: int) -> str:
    ...

argument: float = complex_expression()

@decorator_factory(argument) # Accepted!  bar resolves to Callable[[str], float]
def bar(x: int) -> str:
    ...

"Decorator factory `X` could not be called"

This corresponds to when the decorator factory access resolves to a type that is not callable (i.e. has no __call__ method).

not_a_factory: int = 5

# pyre-fixme[56]: Decorator factory `not_a_factory` could not be called, because its
# type `int` is not callable
@not_a_factory(1)
def bar() -> None:
    pass

"Decorator `X` could not be called"

Similarly, these errors correspond to when the entire decorator expression (potentially including arguments to a decorator factory), resolves to a non-callable type.

def foo() -> int:
    return 42

# pyre-fixme[56]: Decorator `foo()` could not be called, because its
# type `int` is not callable
@foo()
def bar() -> None:
    pass

"While applying decorator factory ..."

These errors are emitted from attempting to pass the resolved factory arguments to the factory, as with any other function call.

from typing import Callable

def factory(x: str) -> Callable[[object], object]:
    ...

# pyre-fixme[56]: While applying decorator factory `factory`:
# Expected `str` for 1st param but got `int`.
@factory(1)
def foo() -> None:
    pass

"While applying decorator ..."

Correspondingly, these errors are emitted from trying to pass the decorated function as an argument to the resolved decorator type.

from typing import Callable

def decorator(f: Callable[[int], str]) -> int:
    ...

# pyre-fixme[56]: While applying decorator `decorator`:
# Expected `Callable[[int], str]` for 1st param but got `Callable[[str], int]`.
@decorator
def foo(x: str) -> int:
    return 5

57: Incompatible Async Generator Return Type

An async generator function is an async function that contains at least one yield statement. The Python runtime ensures that all async generator would return an async generator object. Therefore, the return type of async generator functions should always be typing.AsyncGenerator or one of its supertypes.

from typing import AsyncGenerator

async def f() -> int:  # Error
    yield 0

async def g() -> AsyncGenerator[int, None]:  # OK
    if False:
        yield 1

58: Unsupported Operand

Pyre will warn if an infix operator is not supported for the right or left operands provided.

In Python, an infix operator is converted to a method call on either of the operands - for example, a < b is equivalent to a.__lt__(b). Therefore, this type error can also be considered sugar for an error that method a.__lt__ does not accept the type of b as an argument.

For example,

from typing import Optional

def foo(x: Optional[int]) -> bool:
    return x < 0  # type error: Optional[int] is not a supported operand

def bar(x: Optional[int]) -> bool:
    if x:
        return x < 0  # no type error
    return False

59: Duplicate Type Variables

This occurs when the same type variable is provided more than once to a Generic or Protocol. A type variable needs to be bound to a single value. Thus, if one wants two independent type variables with perhaps the same bounds or same properties, they have to be different variables.

from typing import TypeVar, Generic

T0 = TypeVar("T0")
T1 = TypeVar("T1")
T2 = TypeVar("T2")

class A(Generic[T0, T1, T0]):  # Error
    pass

class B(Generic[T0, T1, T2]):  # OK
    pass

60: Unable to Concatenate Tuple

"Expected to unpack an iterable ..."

This can occur if during concatenation of a tuple one tries to unpack a non-iterable since non-iterables can't be unpacked. Either try to unpack an iterable, or concatenate without unpacking.

def foo(x: int, not_iterable: int, iterable: list[int]) -> None:
    y = (x, *not_iterable)  # Error
    z = (x, not_iterable) # OK
    w = (x, *iterable)  # OK

"Concatenation not yet supported for multiple variadic tuples ..."

This can occur if during concatenation one tries to use multiple variadic tuples. This is due the limitations of the current type system and there is no workaround currently. One may use # pyre-ignore[60] to suppress.

from typing import Tuple
from pyre_extensions import TypeVarTuple

Ts = TypeVarTuple("Ts")

def foo(xs: Tuple[*Ts]) -> None:
    y = (*xs, *xs)  # Error

61: Uninitialized Local

This indicates that there are code paths along which a local variable may not be initialized. Below are some common code patterns that may cause this error:

Not initialized in all branches of condition

def f(x: int) -> None:
    z = None
    if x > 5:
        y = 2
        z = 2
    print(y)  # Error
    print(z)  # OK

y is not defined when the if condition is not met. For instance, f(4) will result in a runtime error. Possible ways to address this:

  • initialize y to a default value, outside the conditional or in the else branch
  • refactor so that initialization and access are in the same conditional

Pyre static analysis does not reason about runtime values or potential side effects of interleaving calls, so for instance, in the example below we cannot guarantee that the two if statements will always be consistent and, hence, throw the same error:

def f(x: int) -> None:
    if x > 5:
        y = 2
    # ...some operations...
    if x > 5:
        print(y)    # Error

Initialized only inside a for loop

def f(xs: List[int]) -> None
    for x in xs:
        y = "yes"
    print("Last element is: ", x)  # Error
    print("Did we enter the loop?", y)  # Error

Here, if one calls f([]), it will result in errors.

One way to remediate is to initialize outside the loop. For instance,

def f(xs: List[int]) -> None:
    x = None
    y = "no"
    for x in xs:
        y = "yes"
    print("Last element is: ", x)  # OK
    print("Did we enter the loop?", y)  # OK

Initialized in try block

def f(divisor: int) -> None:
    answer_good = None
    try:
        answer_bad = 5 / divisor
        answer_good = 5 / divisor
        answer_also_good = 5 / divisor
        print(f"5 divided by {divisor} is {answer_also_good}")   # OK
    except ZeroDivisionError:
        pass
    print(f"5 divided by {divisor} is {answer_bad}")  # Error
    print(f"5 divided by {divisor} is {answer_good}")  # OK

Here, f(0) leads to an error on access of answer_bad. Suggested approaches to address this:

  • Initialize any variables needed after the try block to a default value before entering the try block.
  • Keep the access to variables initialized inside the try block within the try block.
  • Consider if pulling the initialization as-is before the try block is possible. It is generally considered a good practice to minimize the code inside a try block, and keep it to exception throwing code. This also helps with Pyre, as it does not reason about which operations might throw exceptions.
def bad(divisor: int) -> Optional[int]:
    try:
        dividend = 5
        return dividend // divisor
    except ZeroDivisionError:
        print(f"Cannot divide {dividend} by 0")  # Error (according to Pyre)

def good(divisor: int) -> Optional[int]:
    dividend = 5
    try:
        return dividend // divisor
    except ZeroDivisionError:
        print(f"Cannot divide {dividend} by 0")  # OK

62: Non-literal string

Pyre will error if you passs in a non-literal string into a function call that expects a LiteralString.

def query_user(conn: Connection, user_id: str) -> User:
    query = f"SELECT * FROM data WHERE user_id = {user_id}"
    conn.execute(query)  # Error: Expected LiteralString, got str.
    ...
def query_user(conn: Connection, user_id: str) -> User:
    query = "SELECT * FROM data WHERE user_id = ?"
    conn.execute(query, (user_id,))  # OK.

LiteralStrings are created either from an explicit string literal like "foo" or from combining multiple string literals or LiteralStrings. This is a security focused typing feature for safely calling powerful APIs. See PEP 675 for more details.

63: Suppression Comment Without Error Code

When running Pyre in strict mode, suppression comments like pyre-ignore and pyre-fixme need to be associated with a specific error code or set of error codes.

OK:

def foo() -> int:
    # pyre-ignore[7]
    return ""

Not OK:

def foo() -> int:
    # pyre-ignore
    return ""

For more details on how to write suppression comments, refer to the section on Suppression later in this doc.

64: Inconsistent Method Resolution Order

Python supports multiple inheritance via multiple base classes. In order to decide the order that methods from base classes are overridden, there needs to be a consistent order among the base classes of a particular class. When this order cannot be created, for example because there is a cycle, then Pyre raises this error.

class A(B):  # Error
    pass

class B(A):  # Error
    pass

For more details and examples for Python's Method Resolution Order (MRO), please refer to the official guide.

65: Duplicate Parameter

Functions may not have multiple named parameters that share the same name.

def add(a: int, a: int) -> None:  # Error
    pass

66: Invalid Exception Handler

Exception types listed in an except clause must extend BaseException.

try:
    pass
except (int, bool):  # Error
    pass

67: Invalid Exception Group Handler

Exception types listed in an except* clause (Python 3.12 exception group handler) must extend BaseException and may not extend BaseExceptionGroup.

try:
    pass
except* ExceptionGroup as e:  # Error
    pass
try:
    pass
except* int as e:  # Error
    pass

68: Invalid Type Guard

User defined type-guards are functions that return TypeIs[X] or TypeGuard[X]; at runtime these return a bool, but the static type indicates to the type checker that they narrow the type of the first positional argument, for example:

from typing import TypeIs, reveal_type

def is_int_or_str(val: object) -> TypeIs[int | str]: ...

def f(x: object):
    if is_int_or_str(x):
        reveal_type(x)  # int | str

The difference between TypeGuard and TypeIs is that TypeGuard only narrows in the positive case and is covariant in its type parameter, whereas TypeIs also narrows in the negative case and is invariant in its type parameter; for example:

# (extending the previous example)

def is_int_or_str_typeguard(val: object) -> TypeGuard[int | str]: ...

def g(x: int | str | bytes):
    if is_int_or_str(x):
        reveal_type(x)  # int | str
    else:
        reveal_type(x)  # bytes

    if is_int_or_str_typeguard(x):
        reveal_type(x)  # int | str
    else:
        reveal_type(x)  # int | str | bytes (no narrowing here)

It is a type error if such a type guard function does not accept at least one positional argument; the type guard cannot actually be invoked correctly in this case. For example:

from typing import TypeIs, TypeGuard

# Error: the argument is keyword-only, this is not a valid type guard.
def bad_type_guard(/, val: objec) -> TypeGuard[float]: ...

class CustomTypeGuard:
    # Error: this is a non-static method and `self` is bound, so it does not
    #  accept a positional argument.
    def guard(self) -> TypeIs[str]: ...

In addition, for TypeIs it is an error if the narrowed type (the type inside the TypeIs) is not assignable to the type of the first positional parameter.

from typing import TypeIs

# Error: `int` is not a subtype of `str`
def inconsistent_type_is(x: str) -> TypeIs[int]: ...

You can read more about type guards and narrowing in the specification: https://typing.readthedocs.io/en/latest/spec/narrowing.html.

69: Invalid Positional-Only Parameter

Python has positional-only parameters which may not be passed by name. Positional-only parameters cannot appear after parameters that may be passed by name.

Typically, all the parameters that proceed / in the parameter list are considered positional-only, but in functions without this syntax we treat parameters that are prefixed but not suffixed with __ as positional-only for backwards-compatibility purposes. Refer to https://typing.readthedocs.io/en/latest/spec/historical.html#positional-only-parameters for more details.

In the following example, __y is a positional-only that follows the regular parameter x, which is an error.

def foo(x: int, __y: int) -> None:  # Error
    pass

70: Assert Type

assert_type is a static assertion that has no effect at runtime. Pyre will emit an error if the inferred type for the first argument does not match the type provided as the second argument.

x: int = 1
assert_type(x, str) # Error

71: Typed Dictionary Isinstance

Typed dictionaries are structurally typed. This means that it is invalid to use isinstance to check whether something is an instance of a typed dictionary.

class Coord(TypedDict):
    x: int
    y: int

isinstance({}, Coord) # Error

72: Tuple Delete

Tuples are immutable, so deleting elements is not allowed.

x = (1, 2, 3)
del x[0] # Error

73: Tuple Out of Bounds

When a tuple with a concrete/known length is indexed with a literal, Pyre will emit an error if the index is out of bounds.

x = (1, 2, 3)
y: int = ...
x[0] # OK
x[-1] # OK
x[5] # Error
x[y] # OK

74: Named Tuple Default Fields

Since named tuple constructors are generated based on the order fields are declared in, fields without defaults may not follow fields with defaults in a named tuple declaration.

# This is OK
class MyTuple(NamedTuple):
    x: int
    y: int = 1

# This is not allowed
class MyTuple2(NamedTuple):
    x: int = 1
    y: int

Suppression

It is not always possible to address all errors immediately – some code is too dynamic and should be refactored, other times it's just not the right time to deal with a type error. We do encourage people to keep their type check results clean at all times and provide mechanisms to suppress errors that cannot be immediately fixed.

Suppressing Individual Errors

Pyre supports error suppression of individual errors with comments that can be placed on the line of the error or on the line preceding the error.

  • # pyre-fixme indicates there is an issue in the code that will be revisited later.
  • # pyre-ignore indicates there's an issue with the type checker or the code is too dynamic and we have decided to not fix this. If this is a Pyre bug, make sure you open an issue on our tracker.

Both comment styles allow you to suppress individual error codes as well as adding additional context.

def foo() -> int:
    # pyre-fixme[7]: only suppresses return mismatches
    return ""

Pyre also supports # type: ignore comments for backwards-compatibility with MyPy.

Suppressing Errors within Format Strings

If you want to suppress an error within an f-string, you can add a fixme comment on the line before the string. This will suppress all errors within the f-string matching that fixme:

def print_profile(name: str, age: Optional[int]) -> None:
    # pyre-fixme[58]: `-` is not supported for operand types `Optional[int]` and `int`.
    s = f"""
    Your personal details!!

    Name: {name}
    Age: {age - 3}
    """
    print(s)

Suppressing All Errors

You can use the Pyre upgrade tool to add inline error suppressions for all errors in your project.

Suppressing Errors Across Files

You can suppress all errors in entire sections of your code by adding the path to the ignore_all_errors section of your configuration.

Furthermore Pyre supports suppressing all errors in an individual file if you add a # pyre-ignore-all-errors to your file. Like the other suppression comments, you can use square brackets to chose to only ignore one or more particular error types. For example, you can suppress all incompatible return type errors by adding:

# pyre-ignore-all-errors[7]

def foo(x: int) -> str:
    return x  # pyre will not error here

Debugging