Skip to content

Complex Forward-reference NamedTuples cause crash #3990

Closed
@Raelifin

Description

@Raelifin
from typing import NamedTuple, Union

class Person(NamedTuple):
    name: Union[str, "NamePair"]

class NamePair(NamedTuple):
    first: str
    last: str

print(Person(name=NamePair(first="John", last="Doe")))
# Person(name=NamePair(first='John', last='Doe'))

Works fine in Python 3.6, but in mypy:

mypy_breaks.py:10: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.521
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.6/bin/mypy", line 11, in <module>
    sys.exit(console_entry())
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/__main__.py", line 7, in console_entry
    main(None)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/main.py", line 50, in main
    res = type_check_only(sources, bin_dir, options)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/main.py", line 97, in type_check_only
    options=options)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/build.py", line 196, in build
    graph = dispatch(sources, manager)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/build.py", line 1801, in dispatch
    process_graph(graph, manager)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/build.py", line 2044, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/build.py", line 2147, in process_stale_scc
    graph[id].type_check_first_pass()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/build.py", line 1716, in type_check_first_pass
    self.type_checker.check_first_pass()
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checker.py", line 185, in check_first_pass
    self.accept(d)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checker.py", line 273, in accept
    stmt.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/nodes.py", line 829, in accept
    return visitor.visit_expression_stmt(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checker.py", line 1883, in visit_expression_stmt
    self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 2209, in accept
    typ = self.visit_call_expr(node, allow_none_return=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 247, in visit_call_expr
    ret_type = self.check_call_expr_with_callee_type(callee_type, e, fullname, object_type)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 459, in check_call_expr_with_callee_type
    object_type=object_type)[0]
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 519, in check_call
    callee, args, arg_kinds, formal_to_actual)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 675, in infer_arg_types_in_context2
    res[ai] = self.accept(args[ai], callee.arg_types[i])
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 2213, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/nodes.py", line 1307, in accept
    return visitor.visit_call_expr(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 215, in visit_call_expr
    callee_type = self.accept(e.callee, always_allow_any=True)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 2213, in accept
    typ = node.accept(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/nodes.py", line 1219, in accept
    return visitor.visit_name_expr(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 128, in visit_name_expr
    result = self.analyze_ref_expr(e)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkexpr.py", line 156, in analyze_ref_expr
    result = type_object_type(node, self.named_type)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkmember.py", line 525, in type_object_type
    return type_object_type_from_function(init_method, info, fallback)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkmember.py", line 530, in type_object_type_from_function
    signature = bind_self(function_type(init_or_new, fallback))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkmember.py", line 664, in bind_self
    arg_types = [expand(x) for x in func.arg_types[1:]]
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkmember.py", line 664, in <listcomp>
    arg_types = [expand(x) for x in func.arg_types[1:]]
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/checkmember.py", line 662, in expand
    return expand_type(target, {func.variables[0].id: typearg})
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/expandtype.py", line 16, in expand_type
    return typ.accept(ExpandTypeVisitor(env))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/types.py", line 1114, in accept
    return visitor.visit_union_type(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/expandtype.py", line 117, in visit_union_type
    return UnionType.make_simplified_union(self.expand_types(t.items), t.line, t.column)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/types.py", line 1096, in make_simplified_union
    if (i != j and is_proper_subtype(tj, ti)):
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/subtypes.py", line 569, in is_proper_subtype
    return left.accept(ProperSubtypeVisitor(right))
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/types.py", line 435, in accept
    return visitor.visit_instance(self)
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/mypy/subtypes.py", line 605, in visit_instance
    for base in left.type.mro:
TypeError: 'NoneType' object is not iterable

Interestingly not an issue if I break the forward-reference

from typing import NamedTuple, Union

class NamePair(NamedTuple):
    first: str
    last: str

class Person(NamedTuple):
    name: Union[str, "NamePair"]

print(Person(name=NamePair(first="John", last="Doe")))  # mypy works fine

or if I switch to using classes

from typing import NamedTuple, Union

class Person(NamedTuple):
    name: Union[str, "NamePair"]

class NamePair:
    def __init__(self, first: str, last: str) -> None:
        self.first = first
        self.last = last

print(Person(name=NamePair(first="John", last="Doe")))  # mypy works fine

or if I drop the union typing

from typing import NamedTuple, Union

class Person(NamedTuple):
    name: "NamePair"

class NamePair(NamedTuple):
    first: str
    last: str

print(Person(name=NamePair(first="John", last="Doe")))  # mypy works fine

including by using Optional

from typing import NamedTuple, Optional

class Person(NamedTuple):
    name: Optional["NamePair"]

class NamePair(NamedTuple):
    first: str
    last: str

print(Person(name=NamePair(first="John", last="Doe")))  # mypy works fine

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions