Skip to content

Forward references to NamedTuples don't behave correctly in some instances #2762

Closed
@axch

Description

@axch

Please forgive me if this is an ignorant question, but I've looked around a bit and can't seem to find an answer.

I'd like to use mypy to type check a recursive-descent programming language interpreter, and I am running into what seems like a pretty basic problem: defining the type for the expression tree. The textbook approach from, e.g., Haskell, is to define an algebraic data type of the possible syntactic forms in the language, which will presumably be recursive because the type of a general subexpression of something is "expression".

I was able to define the type in what seems to be the idiomatic mypy way, but when I tried to do isinstance case analysis on it, I got None types in the branches, even though the program runs. Am I doing something wrong?

Here is a small example of the kind of program I am trying to write:

from typing import List
from typing import NamedTuple
from typing import Union

Exp = Union['App', 'Lit']

# Make classes so that forward references work.
class App(NamedTuple('App', [('subs', List[Exp])])): pass
class Lit(NamedTuple('Lit', [('val', object)])): pass

def my_eval(exp):
  # type: (Exp) -> int
  reveal_type(exp)
  if isinstance(exp, App):
    reveal_type(exp)
    return sum([my_eval(e) for e in exp.subs])
  if isinstance(exp, Lit):
    reveal_type(exp)
    return exp.val

print(my_eval(App([Lit(1), Lit(2)])))

When run in Python 2.7 (removing the reveal_type, of course), this prints 3 as expected. Type checking in mypy, however, (both default and with --python-version 2.7) produces this output:

$ mypy test.py
test.py:13: error: Revealed type is 'Union[regen.App, regen.Lit]'
test.py:15: error: Revealed type is 'builtins.None'
test.py:16: error: None has no attribute "subs"
test.py:18: error: Revealed type is 'builtins.None'
test.py:19: error: None has no attribute "val"

Or, with --strict-optional, this:

$ mypy --strict-optional test.py
test.py:13: error: Revealed type is 'Union[regen.App, regen.Lit]'
test.py:15: error: Revealed type is '<uninhabited>'
test.py:16: error: object has no attribute "subs"
test.py:18: error: Revealed type is '<uninhabited>'
test.py:19: error: object has no attribute "val"

Is there a way to do this in mypy? Since this is a new project, I am willing to reorganize the (largely non-existent) code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions