Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Change invariant list of subclassed AST in ast fields, to covariant Sequence #13942

Copy link
Copy link
Closed
@hunterhogan

Description

@hunterhogan
Issue body actions

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:

  1. Classes as of Python 3.13 without differentiating other versions.
  2. No deprecated classes.
  3. Includes list if the type is subclassed, such as list[ast.expr] but not list[ast.arg].
  4. 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.
  5. Includes list[str] because str 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.