Description
In stdlib/ast.pyi, if a subclass of ast.AST
has a field with a "collection" type, then the type is builtins.list
. In some cases, because list
is invariant, the feedback from type checkers is not helpful. Consider the following simple code.
from ast import FunctionDef, Pass, arguments
list_stmt_subclasses = [Pass()]
FunctionDef("chicken", arguments([], [], None, [], [], None, []), list_stmt_subclasses, [], None, None, [])
Mypy reports:
Argument 3 to "FunctionDef" has incompatible type "list[Pass]"; expected "list[stmt]" Mypy(arg-type)
"List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
Consider using "Sequence" instead, which is covariant
Pylance reports:
Argument of type "list[Pass]" cannot be assigned to parameter "body" of type "list[stmt]" in function "__init__"
"list[Pass]" is not assignable to "list[stmt]"
Type parameter "_T@list" is invariant, but "Pass" is not the same as "stmt"
Consider switching from "list" to "Sequence" which is covariant Pylance(reportArgumentType)
When I use a stub file that defines body: Sequence[stmt]
in all the right places, the above code does not lead to diagnostic reports.
Change list
to Sequence
for subclassed types
If I understand correctly, even though ast.Pass
is a subclass of ast.stmt
, the invariance of list
requires ast.stmt
, not a subclass. In contrast, the covariance of Sequence
permits substituting subclasses for the base class. I'm not a trained programmer, and I don't have professional programming experience. In the past, when I have encountered this issue and got help from an AI assistant, the "solution" was always
list_stmt_subclasses = [cast(ast.stmt, element) for element in list_stmt_subclasses]
Those kinds of statements have no value, increase complexity, and increase the risk of bugs.
If the solution is to change some list
to Sequence
in the stub file, then I happen to have already a list (no pun intended) of subclasses and fields.
About the list contents:
- Classes as of Python 3.13 without differentiating other versions.
- No deprecated classes.
- Includes
list
if the type is subclassed, such aslist[ast.expr]
but notlist[ast.arg]
. - All typing.TypeAlias from the stub file were replaced with the, uh, true name (what is that called?). That might not have affected this list, but I'm not sure.
- Includes
list[str]
becausestr
could be subclassed, but I don't really have the knowledge to analyze its inclusion or exclusion.
Class | Field | Type |
---|---|---|
Assign | targets | list[ast.expr] |
AsyncFor | body | list[ast.stmt] |
AsyncFor | orelse | list[ast.stmt] |
AsyncFunctionDef | body | list[ast.stmt] |
AsyncFunctionDef | decorator_list | list[ast.expr] |
AsyncFunctionDef | type_params | list[ast.type_param] |
AsyncWith | body | list[ast.stmt] |
BoolOp | values | list[ast.expr] |
Call | args | list[ast.expr] |
ClassDef | bases | list[ast.expr] |
ClassDef | body | list[ast.stmt] |
ClassDef | decorator_list | list[ast.expr] |
ClassDef | type_params | list[ast.type_param] |
Compare | comparators | list[ast.expr] |
Compare | ops | list[ast.cmpop] |
Delete | targets | list[ast.expr] |
Dict | keys | list[ast.expr] |
Dict | values | list[ast.expr] |
ExceptHandler | body | list[ast.stmt] |
For | body | list[ast.stmt] |
For | orelse | list[ast.stmt] |
FunctionDef | body | list[ast.stmt] |
FunctionDef | decorator_list | list[ast.expr] |
FunctionDef | type_params | list[ast.type_param] |
FunctionType | argtypes | list[ast.expr] |
Global | names | list[str] |
If | body | list[ast.stmt] |
If | orelse | list[ast.stmt] |
Interactive | body | list[ast.stmt] |
JoinedStr | values | list[ast.expr] |
List | elts | list[ast.expr] |
MatchClass | kwd_attrs | list[str] |
MatchClass | kwd_patterns | list[ast.pattern] |
MatchClass | patterns | list[ast.pattern] |
MatchMapping | keys | list[ast.expr] |
MatchMapping | patterns | list[ast.pattern] |
MatchOr | patterns | list[ast.pattern] |
MatchSequence | patterns | list[ast.pattern] |
Module | body | list[ast.stmt] |
Module | type_ignores | list[ast.type_ignore] |
Nonlocal | names | list[str] |
Set | elts | list[ast.expr] |
Try | body | list[ast.stmt] |
Try | finalbody | list[ast.stmt] |
Try | handlers | list[ast.excepthandler] |
Try | orelse | list[ast.stmt] |
TryStar | body | list[ast.stmt] |
TryStar | finalbody | list[ast.stmt] |
TryStar | handlers | list[ast.excepthandler] |
TryStar | orelse | list[ast.stmt] |
Tuple | elts | list[ast.expr] |
TypeAlias | type_params | list[ast.type_param] |
While | body | list[ast.stmt] |
While | orelse | list[ast.stmt] |
With | body | list[ast.stmt] |
arguments | defaults | list[ast.expr] |
arguments | kw_defaults | list[ast.expr] |
comprehension | ifs | list[ast.expr] |
match_case | body | list[ast.stmt] |
Coda
I replaced list
with Sequence
in an ast-related project, but my inexpert code might not be relevant to this conversation.