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:
This code is 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:
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:
typing.Iterable is an immutable variant for lists that allows accessing the list without modifying it. 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:
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:
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:
A common pattern in Python is to check whether an attribute is
None before accessing its value. E.g.
The above fails to type-check because Pyre cannot guarantee that
data.field is not
None even after checking explicitly in the line before:
field could be set to
None by another thread or it could be a property that returns something different the next time we access it.
The preferred way to make this code type-check is to mark the attribute
Final, i.e. to specify that it can't be reassigned.
It is always safe to refine attributes when their types are
Alternatively, it is also safe to assign the attribute to a local variable before accessing its value.
Different errors raised by Pyre have different error codes. E.g. in
9 in the brackets indicates that we raised an error with code 9.
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:
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 intended to change the type of the variable, you can explicitly annotate it with the new type:
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:
Say we now have different implementations of our
Image class, one of which
violates the substitution principle:
width function above breaks when used with a
The case for parameters follows analogously.
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:
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.
18,21: Undefined Name, Undefined Import
Error 18 ("Undefined name") is raised when your code tries to access a variable or function that Pyre could not resolve. This is usually caused by failing to import the proper 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.
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,
Tinto scope for the body of the class
- for example,
- The parameter types of a generic function
- for example,
def foo(x: T)puts
Tinto scope for the body and return type annotation of the function
- for example,
Something notably absent from this list is "inside of a
This means that
Callable[[T], T] does not spell the type of a generic function, but rather a specific identity function, with the
T defined by an outer scope.
Therefore, if you want to spell the signature of a function that takes/returns a generic function, you will need to declare it separately via a callback protocol:
35: 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:
Reads returning contravariants:
53: Missing annotation for captured variables
Pyre makes no attempt at trying to infer the types across function boundaries. The statement holds for nested funtions as well. From a nested function's perspective, a variable defined in an nesting function behaves not too differently from a global variable. Therefore, Pyre treats such variables in the same way as it treats global variable: an explicit annotation is required if strict mode is turned on.
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.
"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
"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.
"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.
"While applying decorator ..."
Correspondingly, these errors are emitted from trying to pass the decorated function as an argument to the resolved decorator type.
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 preceeding the error.
# pyre-fixmeindicates there is an issue in the code that will be revisited later.
# pyre-ignoreindicates 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.
Pyre also supports
# type: ignore comments for backwards-compatibility with mypy.
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.