Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complex Forward-reference NamedTuples cause crash #3990

Closed
Raelifin opened this issue Sep 23, 2017 · 3 comments · Fixed by #3952
Closed

Complex Forward-reference NamedTuples cause crash #3990

Raelifin opened this issue Sep 23, 2017 · 3 comments · Fixed by #3952
Labels
bug mypy got something wrong topic-named-tuple

Comments

@Raelifin
Copy link

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
@ethanhs
Copy link
Collaborator

ethanhs commented Sep 23, 2017

NamedTuples and forward references can be problematic. See eg #3952 fixes a lot of these cases. I'm not sure if this case will be solved by that particular PR however.

@ilevkivskyi
Copy link
Member

Yes, this will be fixed by #3952, I updated the PR description, will also now add this example to the tests.

ilevkivskyi added a commit to ilevkivskyi/mypy that referenced this issue Sep 23, 2017
@ilevkivskyi
Copy link
Member

OK, added, thanks for the idea for an additional test! If everything will be fine then this PR will be merged before the next release that will happen very soon.

JukkaL pushed a commit that referenced this issue Sep 27, 2017
Forward references didn't work with anything apart from classes, for example 
this didn't work:

```
x: A
A = NamedTuple('A', [('x', int)])
```

The same situation was with `TypedDict`, `NewType`, and type aliases. The 
root problem is that these synthetic types are neither detected in first pass, 
nor fixed in third pass. In certain cases this can lead to crashes (first six issues 
below are various crash scenarios). This fixes these crashes by applying some 
additional patches after third pass.

Here is the summary of the PR:

* New simple wrapper type `ForwardRef` with only one field `link` is introduced 
  (with updates to type visitors)
* When an unknown type is found in second pass, the corresponding 
  `UnboundType` is wrapped in `ForwardRef`, it is given a "second chance" in 
  third pass.
* After third pass I record the "suspicious" nodes, where forward references and 
  synthetic types have been encountered and append patches (callbacks) to fix 
  them after third pass. Patches use the new visitor `TypeReplacer` (which is the 
  core of this PR).

Fixes #3340
Fixes #3419
Fixes #3674
Fixes #3685
Fixes #3799
Fixes #3836
Fixes #3881
Fixes #867
Fixes #2241
Fixes #2399
Fixes #1701
Fixes #3016
Fixes #3054
Fixes #2762
Fixes #3575
Fixes #3990
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-named-tuple
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants