"""
Our own implementation of an abstract syntax tree (AST).

The convert function recursively converts a Python AST (from the module `ast`)
to our own AST (of the class `Node`).
"""

import ast
from logging import debug

from typy.builtin import data_types
from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable
from typy import types


class Node:
    def __init__(self, type_map, ast_node):
        self.type_map = type_map
        self._ast_fields = ast_node._fields

    def check(self):
        """Must be overriden in subtype."""
        raise NotYetSupported('check call to', self)

    def iter_fields(self):
        for field in self._ast_fields:
            try:
                yield field, getattr(self, field)
            except AttributeError:
                pass

    def iter_child_nodes(self):
        for _name, field in self.iter_fields():
            if isinstance(field, Node):
                yield field
            elif isinstance(field, list):
                for item in field:
                    if isinstance(item, Node):
                        yield item


class FunctionDef(Node):
    def __init__(self, type_map, ast_node):
        if (ast_node.args.vararg is not None or
                len(ast_node.args.kwonlyargs) > 0 or
                len(ast_node.args.kw_defaults) > 0 or
                ast_node.args.kwarg is not None or
                len(ast_node.args.defaults) > 0):
            raise NotYetSupported('default arguments and keyword arguments')

        super().__init__(type_map, ast_node)
        self.name = ast_node.name
        self.params = [arg.arg for arg in ast_node.args.args]
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]
        self._ast_fields = ('name', 'params', 'body')

    def check(self):
        debug('checking func def %s', self.name)
        function = types.Function(self, self.type_map)
        self.type_map.add_variable(self.name, function)
        return data_types.None_()

    def __repr__(self):
        return 'def ' + self.name + '()'


class ClassDef(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.name = ast_node.name
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]

    def check(self):
        debug('checking class def %s', self.name)

        class_namespace = self.type_map.enter_namespace(self.name)

        for stmt in self.body:
            stmt.check()

        self.type_map.exit_namespace()

        class_ = types.Class(self, self.type_map, class_namespace)
        self.type_map.add_variable(self.name, class_)

        return data_types.None_()

    def __repr__(self):
        return 'def ' + self.name


class Attribute(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.value = convert(type_map, ast_node.value)
        self.attr = ast_node.attr
        self.ctx = ast_node.ctx

    def check(self):
        debug('checking attr %s', self)

        value_type = self.value.check()
        debug('attr %r = %r', self, value_type)

        if isinstance(self.ctx, ast.Load):
            return value_type.get_attribute(self.attr)
        elif isinstance(self.ctx, ast.Store):
            return (value_type, self.attr)
        else:
            # TODO implement for Del, AugLoad, AugStore, Param
            raise NotYetSupported('name context', self.ctx)

    def __repr__(self):
        return repr(self.value) + '.' + self.attr


class Name(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.id = ast_node.id
        self.ctx = ast_node.ctx

    def check(self):
        debug('checking name %s', self.id)

        if isinstance(self.ctx, ast.Load):
            return self.type_map.find(self.id)
        elif isinstance(self.ctx, ast.Store):
            return self
        else:
            # TODO implement for Del, AugLoad, AugStore, Param
            raise NotYetSupported('name context', self.ctx)

    def __repr__(self):
        return self.id


class Call(Node):
    def __init__(self, type_map, ast_node):
        if (len(ast_node.keywords) > 0 or
                ast_node.starargs is not None or
                ast_node.kwargs is not None):
            raise NotYetSupported('keyword arguments and star arguments')

        super().__init__(type_map, ast_node)
        self.func = convert(type_map, ast_node.func)
        self.args = [convert(type_map, expr) for expr in ast_node.args]

    def check(self):
        debug('checking call')
        func = self.func.check()
        args = [arg.check() for arg in self.args]
        return func.check_call(args)

    def __repr__(self):
        return repr(self.func) + \
               '(' + ', '.join(repr(x) for x in self.args) + ')'


class Expr(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.value = convert(type_map, ast_node.value)

    def check(self):
        debug('checking expr')
        self.value.check()
        return data_types.None_()

    def __repr__(self):
        return repr(self.value)


class Return(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.value = convert(type_map, ast_node.value)

    def check(self):
        debug('checking return')
        return self.value.check()

    def __repr__(self):
        return 'return ' + repr(self.value)


class Module(Node, types.Type):
    def __init__(self, type_map, ast_node):
        Node.__init__(self, type_map, ast_node)
        types.Type.__init__(self, type_map)
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]

    def check(self):
        debug('checking module')

        self.module_namespace = self.type_map.enter_namespace('__main__')

        debug('entering %r', self.type_map.current_namespace)

        for stmt in self.body:
            debug('still in %r', self.type_map.current_namespace)
            stmt.check()

        debug('leaving %r', self.type_map.current_namespace)

        self.type_map.exit_namespace()

    def get_attribute(self, name):
        try:
            return self.module_namespace[name]
        except KeyError:
            types.Type.get_attribute(self, name)


class Assign(Node):
    def __init__(self, type_map, ast_node):
        # TODO handle multiple targets
        if len(ast_node.targets) > 1:
            raise NotYetSupported('assignment with multiple targets')

        super().__init__(type_map, ast_node)
        self.target = convert(type_map, ast_node.targets[0])
        self.value = convert(type_map, ast_node.value)
        self._ast_fields = ('target', 'value')

    def check(self):
        debug('checking assign %r', self.target)
        _assign(self.target, self.value, self.type_map)
        return data_types.None_()

    def __repr__(self):
        return repr(self.target) + ' = ' + repr(self.value)


class Pass(Node):
    def check(self):
        debug('checking pass')
        return data_types.None_()

    def __repr__(self):
        return 'pass'


class Not(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.value = convert(type_map, ast_node.value)

    def check(self):
        debug('checking not')
        self.value.check()
        return data_types.Bool()

    def __repr__(self):
        return 'not ' + repr(self.value)


class BoolOp(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.op = ast_node.op
        self.values = [convert(type_map, value) for value in ast_node.values]

    def check(self):
        debug('checking boolop')

        for value in self.values:
            value.check()

        # TODO return intersection van types?
        return data_types.Bool()

    def __repr__(self):
        op_name = ' {} '.format(self.op)
        return '(' + op_name.join(repr(val) for val in self.values) + ')'


class In(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.element = convert(type_map, ast_node.element)
        self.container = convert(type_map, ast_node.container)

    def check(self):
        debug('checking in')

        element = self.element.check()
        container = self.container.check()

        try:
            container.call_magic_method('__contains__', element)
        except NoSuchAttribute:
            if not container.is_iterable():
                raise NotIterable(container)

        return data_types.Bool()

    def __repr__(self):
        return '{!r} in {!r}'.format(self.element, self.container)


class For(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.target = convert(type_map, ast_node.target)
        self.iter = convert(type_map, ast_node.iter)
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]
        self.orelse = [convert(type_map, clause) for clause in ast_node.orelse]

    def check(self):
        debug('checking for')

        iterator = self.iter.check()
        enclosed_type = iterator.get_enclosed_type()
        _assign(self.target, enclosed_type, self.type_map)

        for stmt in self.body:
            stmt.check()
        for stmt in self.orelse:
            stmt.check()

        # TODO return intersection of values of both branches
        return data_types.None_()

    def __repr__(self):
        s = 'for {!r} in {!r}:\n    '.format(self.target, self.iter)
        s += '\n    '.join(repr(stmt) for stmt in self.body)
        if self.orelse:
            s += 'else:\n    '
            s += '\n    '.join(repr(stmt) for stmt in self.orelse)
        return s


class If(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.test = convert(type_map, ast_node.test)
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]
        self.orelse = [convert(type_map, stmt) for stmt in ast_node.orelse]

    def check(self):
        debug('checking if')

        # TODO take isinstance into account (?)
        # TODO real branching?
        self.test.check()
        for stmt in self.body:
            stmt.check()
        for stmt in self.orelse:
            stmt.check()

        # TODO return intersection of values of both branches
        return data_types.None_()

    def __repr__(self):
        s = 'if {!r}:\n    '.format(self.test)
        s += '\n    '.join(repr(stmt) for stmt in self.body)
        if self.orelse:
            s += 'else:\n    '
            s += '\n    '.join(repr(stmt) for stmt in self.orelse)
        return s


class IfExp(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.test = convert(type_map, ast_node.test)
        self.body = convert(type_map, ast_node.body)
        self.orelse = convert(type_map, ast_node.orelse)

    def check(self):
        debug('checking ifexp')

        # TODO take isinstance into account (?)
        self.test.check()
        value1 = self.body.check()
        value2 = self.orelse.check()
        return types.Intersection(value1, value2)

    def __repr__(self):
        template = '{!r} if {!r} else {!r}'
        return template.format(self.test, self.body, self.orelse)


class NameConstant(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.value = ast_node.value

    def check(self):
        debug('checking name constant %r', self.value)
        if self.value is None:
            return data_types.None_()
        elif self.value is True or self.value is False:
            return data_types.Bool()
        else:
            raise NotYetSupported('name constant', self.value)

    def __repr__(self):
        return repr(self.value)


class While(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.test = convert(type_map, ast_node.test)
        self.body = [convert(type_map, stmt) for stmt in ast_node.body]
        self.orelse = [convert(type_map, stmt) for stmt in ast_node.orelse]

    def check(self):
        debug('checking while')

        # TODO take isinstance into account (?)
        # TODO real branching?
        self.test.check()
        for stmt in self.body:
            stmt.check()
        for stmt in self.orelse:
            stmt.check()

        # TODO return intersection of values of both branches
        return data_types.None_()

    def __repr__(self):
        s = 'while {!r}:\n    '.format(self.test)
        s += '\n    '.join(repr(stmt) for stmt in self.body)
        if self.orelse:
            s += 'else:\n    '
            s += '\n    '.join(repr(stmt) for stmt in self.orelse)
        return s


class Break(Node):
    def check(self):
        debug('checking break')
        return data_types.None_()

    def __repr__(self):
        return 'break'


class Continue(Node):
    def check(self):
        debug('checking continue')
        return data_types.None_()

    def __repr__(self):
        return 'continue'


class Num(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.number_type = {
            int: data_types.Int,
            # float: data_types.Float,
            # complex: data_types.Complex,
        }[type(ast_node.n)]

    def check(self):
        debug('checking num')
        return self.number_type()


class Tuple(Node):
    def __init__(self, type_map, ast_node):
        super().__init__(type_map, ast_node)
        self.elts = [convert(type_map, el) for el in ast_node.elts]
        self.ctx = ast_node.ctx

    def check(self):
        debug('checking tuple %r', self)

        if isinstance(self.ctx, ast.Load):
            el_types = (el.check() for el in self.elts)
            return types.Tuple(self.type_map, *el_types)
        elif isinstance(self.ctx, ast.Store):
            return self
        else:
            # TODO implement for Del, AugLoad, AugStore, Param
            raise NotYetSupported('name context', self.ctx)

    def __repr__(self):
        return '(' + ', '.join(repr(el) for el in self.elts) + ')'


def _assign(target, value, type_map):
    value_type = value.check()

    if isinstance(target, Name):
        target_type = target.check()
        type_map.add_variable(target_type.id, value_type)
    elif isinstance(target, Attribute):
        target_type, attr = target.check()
        target_type.set_attribute(attr, value_type)
    else:
        raise NotYetSupported('assignment to', target)


def convert(type_map, node):
    class_name = node.__class__.__name__
    try:
        # Try to convert to a node
        class_ = globals()[class_name]
        return class_(type_map, node)
    except KeyError:
        try:
            # Try to convert to a builtin type
            class_ = getattr(data_types, class_name)
            return class_()
        except AttributeError:
            raise NotYetSupported('node', node)

[((570, 608), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""check call to"""', 'self'], {}), "('check call to', self)\n", (585, 608), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((1809, 1849), 'logging.debug', 'debug', (['"""checking func def %s"""', 'self.name'], {}), "('checking func def %s', self.name)\n", (1814, 1849), False, 'from logging import debug\n'), ((1869, 1904), 'typy.types.Function', 'types.Function', (['self', 'self.type_map'], {}), '(self, self.type_map)\n', (1883, 1904), False, 'from typy import types\n'), ((1976, 1994), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (1992, 1994), False, 'from typy.builtin import data_types\n'), ((2310, 2351), 'logging.debug', 'debug', (['"""checking class def %s"""', 'self.name'], {}), "('checking class def %s', self.name)\n", (2315, 2351), False, 'from logging import debug\n'), ((2535, 2584), 'typy.types.Class', 'types.Class', (['self', 'self.type_map', 'class_namespace'], {}), '(self, self.type_map, class_namespace)\n', (2546, 2584), False, 'from typy import types\n'), ((2655, 2673), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (2671, 2673), False, 'from typy.builtin import data_types\n'), ((2998, 3029), 'logging.debug', 'debug', (['"""checking attr %s"""', 'self'], {}), "('checking attr %s', self)\n", (3003, 3029), False, 'from logging import debug\n'), ((3079, 3118), 'logging.debug', 'debug', (['"""attr %r = %r"""', 'self', 'value_type'], {}), "('attr %r = %r', self, value_type)\n", (3084, 3118), False, 'from logging import debug\n'), ((3720, 3754), 'logging.debug', 'debug', (['"""checking name %s"""', 'self.id'], {}), "('checking name %s', self.id)\n", (3725, 3754), False, 'from logging import debug\n'), ((4577, 4599), 'logging.debug', 'debug', (['"""checking call"""'], {}), "('checking call')\n", (4582, 4599), False, 'from logging import debug\n'), ((5039, 5061), 'logging.debug', 'debug', (['"""checking expr"""'], {}), "('checking expr')\n", (5044, 5061), False, 'from logging import debug\n'), ((5104, 5122), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (5120, 5122), False, 'from typy.builtin import data_types\n'), ((5376, 5400), 'logging.debug', 'debug', (['"""checking return"""'], {}), "('checking return')\n", (5381, 5400), False, 'from logging import debug\n'), ((5638, 5673), 'typy.types.Type.__init__', 'types.Type.__init__', (['self', 'type_map'], {}), '(self, type_map)\n', (5657, 5673), False, 'from typy import types\n'), ((5776, 5800), 'logging.debug', 'debug', (['"""checking module"""'], {}), "('checking module')\n", (5781, 5800), False, 'from logging import debug\n'), ((5885, 5938), 'logging.debug', 'debug', (['"""entering %r"""', 'self.type_map.current_namespace'], {}), "('entering %r', self.type_map.current_namespace)\n", (5890, 5938), False, 'from logging import debug\n'), ((6071, 6123), 'logging.debug', 'debug', (['"""leaving %r"""', 'self.type_map.current_namespace'], {}), "('leaving %r', self.type_map.current_namespace)\n", (6076, 6123), False, 'from logging import debug\n'), ((6786, 6826), 'logging.debug', 'debug', (['"""checking assign %r"""', 'self.target'], {}), "('checking assign %r', self.target)\n", (6791, 6826), False, 'from logging import debug\n'), ((6898, 6916), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (6914, 6916), False, 'from typy.builtin import data_types\n'), ((7051, 7073), 'logging.debug', 'debug', (['"""checking pass"""'], {}), "('checking pass')\n", (7056, 7073), False, 'from logging import debug\n'), ((7089, 7107), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (7105, 7107), False, 'from typy.builtin import data_types\n'), ((7348, 7369), 'logging.debug', 'debug', (['"""checking not"""'], {}), "('checking not')\n", (7353, 7369), False, 'from logging import debug\n'), ((7412, 7429), 'typy.builtin.data_types.Bool', 'data_types.Bool', ([], {}), '()\n', (7427, 7429), False, 'from typy.builtin import data_types\n'), ((7745, 7769), 'logging.debug', 'debug', (['"""checking boolop"""'], {}), "('checking boolop')\n", (7750, 7769), False, 'from logging import debug\n'), ((7893, 7910), 'typy.builtin.data_types.Bool', 'data_types.Bool', ([], {}), '()\n', (7908, 7910), False, 'from typy.builtin import data_types\n'), ((8310, 8330), 'logging.debug', 'debug', (['"""checking in"""'], {}), "('checking in')\n", (8315, 8330), False, 'from logging import debug\n'), ((8630, 8647), 'typy.builtin.data_types.Bool', 'data_types.Bool', ([], {}), '()\n', (8645, 8647), False, 'from typy.builtin import data_types\n'), ((9140, 9161), 'logging.debug', 'debug', (['"""checking for"""'], {}), "('checking for')\n", (9145, 9161), False, 'from logging import debug\n'), ((9505, 9523), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (9521, 9523), False, 'from typy.builtin import data_types\n'), ((10156, 10176), 'logging.debug', 'debug', (['"""checking if"""'], {}), "('checking if')\n", (10161, 10176), False, 'from logging import debug\n'), ((10475, 10493), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (10491, 10493), False, 'from typy.builtin import data_types\n'), ((11069, 11092), 'logging.debug', 'debug', (['"""checking ifexp"""'], {}), "('checking ifexp')\n", (11074, 11092), False, 'from logging import debug\n'), ((11255, 11289), 'typy.types.Intersection', 'types.Intersection', (['value1', 'value2'], {}), '(value1, value2)\n', (11273, 11289), False, 'from typy import types\n'), ((11608, 11654), 'logging.debug', 'debug', (['"""checking name constant %r"""', 'self.value'], {}), "('checking name constant %r', self.value)\n", (11613, 11654), False, 'from logging import debug\n'), ((12292, 12315), 'logging.debug', 'debug', (['"""checking while"""'], {}), "('checking while')\n", (12297, 12315), False, 'from logging import debug\n'), ((12614, 12632), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (12630, 12632), False, 'from typy.builtin import data_types\n'), ((12958, 12981), 'logging.debug', 'debug', (['"""checking break"""'], {}), "('checking break')\n", (12963, 12981), False, 'from logging import debug\n'), ((12997, 13015), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (13013, 13015), False, 'from typy.builtin import data_types\n'), ((13117, 13143), 'logging.debug', 'debug', (['"""checking continue"""'], {}), "('checking continue')\n", (13122, 13143), False, 'from logging import debug\n'), ((13159, 13177), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (13175, 13177), False, 'from typy.builtin import data_types\n'), ((13539, 13560), 'logging.debug', 'debug', (['"""checking num"""'], {}), "('checking num')\n", (13544, 13560), False, 'from logging import debug\n'), ((13835, 13867), 'logging.debug', 'debug', (['"""checking tuple %r"""', 'self'], {}), "('checking tuple %r', self)\n", (13840, 13867), False, 'from logging import debug\n'), ((1452, 1510), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""default arguments and keyword arguments"""'], {}), "('default arguments and keyword arguments')\n", (1467, 1510), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((4320, 4375), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""keyword arguments and star arguments"""'], {}), "('keyword arguments and star arguments')\n", (4335, 4375), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((5983, 6036), 'logging.debug', 'debug', (['"""still in %r"""', 'self.type_map.current_namespace'], {}), "('still in %r', self.type_map.current_namespace)\n", (5988, 6036), False, 'from logging import debug\n'), ((6495, 6546), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""assignment with multiple targets"""'], {}), "('assignment with multiple targets')\n", (6510, 6546), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((11705, 11723), 'typy.builtin.data_types.None_', 'data_types.None_', ([], {}), '()\n', (11721, 11723), False, 'from typy.builtin import data_types\n'), ((13987, 14024), 'typy.types.Tuple', 'types.Tuple', (['self.type_map', '*el_types'], {}), '(self.type_map, *el_types)\n', (13998, 14024), False, 'from typy import types\n'), ((14683, 14723), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""assignment to"""', 'target'], {}), "('assignment to', target)\n", (14698, 14723), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((3402, 3443), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""name context"""', 'self.ctx'], {}), "('name context', self.ctx)\n", (3417, 3443), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((4011, 4052), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""name context"""', 'self.ctx'], {}), "('name context', self.ctx)\n", (4026, 4052), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((6297, 6333), 'typy.types.Type.get_attribute', 'types.Type.get_attribute', (['self', 'name'], {}), '(self, name)\n', (6321, 6333), False, 'from typy import types\n'), ((11799, 11816), 'typy.builtin.data_types.Bool', 'data_types.Bool', ([], {}), '()\n', (11814, 11816), False, 'from typy.builtin import data_types\n'), ((11849, 11893), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""name constant"""', 'self.value'], {}), "('name constant', self.value)\n", (11864, 11893), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((14190, 14231), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""name context"""', 'self.ctx'], {}), "('name context', self.ctx)\n", (14205, 14231), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((8591, 8613), 'typy.exceptions.NotIterable', 'NotIterable', (['container'], {}), '(container)\n', (8602, 8613), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n'), ((15128, 15157), 'typy.exceptions.NotYetSupported', 'NotYetSupported', (['"""node"""', 'node'], {}), "('node', node)\n", (15143, 15157), False, 'from typy.exceptions import NotYetSupported, NoSuchAttribute, NotIterable\n')]