kinko/utils.py
class VarsGen(object):

    def __init__(self):
        self.vars = {}

    def __getattr__(self, name):
        if name not in self.vars:
            self.vars[name] = TypeVar[None]
        return self.vars[name]
kinko/nodes.py
class Placeholder(Node):

    def __init__(self, name, **kw):
        self.name = name
        super(Placeholder, self).__init__(**kw)

    def __repr__(self):
        return '#{}'.format(self.name)

    def accept(self, visitor):
        return visitor.visit_placeholder(self)
kinko/errors.py
class UserError(Exception):
    pass
kinko/types.py
class TypeTransformer(object):

    def visit(self, type_):
        return type_.accept(self)

    def visit_bool(self, type_):
        return type_

    def visit_nothing(self, type_):
        return type_

    def visit_string(self, type_):
        return type_

    def visit_int(self, type_):
        return type_

    def visit_markup(self, type_):
        return type_

    def visit_typevar(self, type_):
        return TypeVar[self.visit(type_.__instance__)
                       if type_.__instance__ is not None else None]

    def visit_typeref(self, type_):
        return TypeRef[type_.__ref_name__]

    def visit_union(self, type_):
        return Union[(self.visit(t) for t in type_.__types__)]

    def visit_option(self, type_):
        t = (type_.__types__ - {Nothing}).pop()
        return Option[self.visit(t)]

    def visit_func(self, type_):
        return Func[[self.visit(t) for t in type_.__args__],
                    self.visit(type_.__result__)]

    def visit_varargs(self, type_):
        return VarArgs[self.visit(type_.__arg_type__)]

    def visit_namedarg(self, type_):
        return NamedArg[type_.__arg_name__, self.visit(type_.__arg_type__)]

    def visit_varnamedargs(self, type_):
        return VarNamedArgs[self.visit(type_.__arg_type__)]

    def visit_list(self, type_):
        return ListType[self.visit(type_.__item_type__)]

    def visit_dict(self, type_):
        return DictType[self.visit(type_.__key_type__),
                        self.visit(type_.__value_type__)]

    def visit_record(self, type_):
        return Record[{key: self.visit(value)
                       for key, value in type_.__items__.items()}]
kinko/types.py
class DictTypeMeta(TypingMeta):

    def __cls_init__(cls, params):
        cls.__key_type__, cls.__value_type__ = params

    def __repr__(cls):
        return '{{:{!r} {!r}}}'.format(cls.__key_type__, cls.__value_type__)

    def accept(cls, visitor):
        return visitor.visit_dict(cls)
kinko/types.py
class VarArgsMeta(TypingMeta):

    def __cls_init__(cls, arg_type):
        cls.__arg_type__ = arg_type

    def __repr__(cls):
        return '*{!r}'.format(cls.__arg_type__)

    def accept(cls, visitor):
        return visitor.visit_varargs(cls)
kinko/types.py
class ListTypeMeta(TypingMeta):

    def __cls_init__(cls, item_type):
        cls.__item_type__ = item_type

    def __repr__(cls):
        return '[{!r}]'.format(cls.__item_type__)

    def accept(cls, visitor):
        return visitor.visit_list(cls)
kinko/types.py
class Markup(with_metaclass(MarkupMeta, object)):
    pass
kinko/types.py
class Record(with_metaclass(RecordMeta, object)):
    pass
kinko/types.py
class Union(with_metaclass(UnionMeta, object)):
    pass
kinko/nodes.py
class Symbol(Node):

    def __init__(self, name, **kw):
        self.name = name
        head, sep, tail = name.partition('/')
        if sep:
            self.ns, self.rel = head, tail
        else:
            self.ns, self.rel = None, name
        super(Symbol, self).__init__(**kw)

    def __repr__(self):
        return self.name

    def accept(self, visitor):
        return visitor.visit_symbol(self)
kinko/types.py
class VarNamedArgs(with_metaclass(VarNamedArgsMeta, object)):
    pass
kinko/refs.py
class FieldRef(Reference):

    def __init__(self, backref, name):
        super(FieldRef, self).__init__(backref)
        self.name = name

    def __repr__(self):
        return '{!r} > [{!r}]'.format(self.backref, self.name)
kinko/types.py
class Option(with_metaclass(OptionMeta, object)):
    pass
kinko/nodes.py
class Number(Node):

    def __init__(self, value, **kw):
        self.value = value
        super(Number, self).__init__(**kw)

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

    def accept(self, visitor):
        return visitor.visit_number(self)
kinko/types.py
class IntType(with_metaclass(IntTypeMeta, object)):
    pass
kinko/nodes.py
class NodeTransformer(object):

    def visit(self, node):
        return node.accept(self)

    def visit_tuple(self, node):
        return node.clone_with(self.visit(i) for i in node.values)

    def visit_list(self, node):
        return node.clone_with(self.visit(i) for i in node.values)

    def visit_dict(self, node):
        return node.clone_with(self.visit(i) for i in node.values)

    def visit_symbol(self, node):
        return node.clone()

    def visit_keyword(self, node):
        return node.clone()

    def visit_placeholder(self, node):
        return node.clone()

    def visit_number(self, node):
        return node.clone()

    def visit_string(self, node):
        return node.clone()
kinko/types.py
class Func(with_metaclass(FuncMeta, object)):
    pass
kinko/types.py
class NamedArg(with_metaclass(NamedArgMeta, object)):
    pass
kinko/nodes.py
class String(Node):

    def __init__(self, value, **kw):
        self.value = text_type(value)
        super(String, self).__init__(**kw)

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

    def accept(self, visitor):
        return visitor.visit_string(self)
kinko/refs.py
class ArgRef(Reference):

    def __init__(self, name):
        super(ArgRef, self).__init__(None)
        self.name = name

    def __repr__(self):
        return "#{}".format(self.name)
kinko/types.py
class VarArgs(with_metaclass(VarArgsMeta, object)):
    pass
kinko/types.py
class StringType(with_metaclass(StringTypeMeta, object)):
    pass
kinko/types.py
class TypeVarMeta(TypingMeta):
    __backref__ = None

    def __cls_init__(cls, instance):
        cls.__instance__ = instance

    def __repr__(cls):
        return '<{}:{}>'.format(
            hex(id(cls))[-3:].upper(),
            repr(cls.__instance__) if cls.__instance__ is not None else '?',
        )

    def accept(cls, visitor):
        return visitor.visit_typevar(cls)
kinko/refs.py
class CtxRef(Reference):

    def __init__(self, name):
        super(CtxRef, self).__init__(None)
        self.name = name

    def __repr__(self):
        return 'ctx[{!r}]'.format(self.name)
kinko/types.py
class Nothing(with_metaclass(NothingMeta, object)):
    pass
kinko/types.py
class TypingMeta(GenericMeta):

    def __cls_init__(cls, *args):
        raise NotImplementedError

    def __getitem__(cls, parameters):
        type_ = cls.__class__(cls.__name__, cls.__bases__, dict(cls.__dict__))
        type_.__cls_init__(parameters)
        return type_
kinko/compat.py
PY3 = sys.version_info[0] == 3
PY35 = sys.version_info >= (3, 5)
def with_metaclass(meta, *bases):
        def __new__(cls, name, this_bases, d):
    def _exec_in(source, globals_dict):
    def _exec_in(source, globals_dict):
    class metaclass(meta):
kinko/types.py
class VarNamedArgsMeta(TypingMeta):

    def __cls_init__(cls, arg_type):
        cls.__arg_type__ = arg_type

    def __repr__(cls):
        return '**{!r}'.format(cls.__arg_type__)

    def accept(cls, visitor):
        return visitor.visit_varnamedargs(cls)
kinko/types.py
class UnionMeta(TypingMeta):

    def __cls_init__(cls, types):
        cls.__types__ = set(types)

    def __repr__(cls):
        return '|'.join(map(repr, cls.__types__))

    def accept(cls, visitor):
        return visitor.visit_union(cls)
kinko/types.py
class FuncMeta(TypingMeta):

    def __cls_init__(cls, params):
        cls.__args__, cls.__result__ = params

    def __repr__(cls):
        return '({} -> {!r})'.format(
            ' '.join(map(repr, cls.__args__)),
            cls.__result__,
        )

    def accept(cls, visitor):
        return visitor.visit_func(cls)
kinko/types.py
class TypeRefMeta(TypingMeta):
    __ref__ = None

    def __cls_init__(cls, name):
        cls.__ref_name__ = name

    def __repr__(cls):
        if cls.__ref__ is None:
            return '<{}[-]>'.format(cls.__ref_name__)
        else:
            return '<{}[*]>'.format(cls.__ref_name__)

    def accept(cls, visitor):
        return visitor.visit_typeref(cls)
kinko/refs.py
def is_from_arg(ref):
    return isinstance(get_origin(ref), ArgRef)
kinko/types.py
class ListType(with_metaclass(ListTypeMeta, object)):
    pass
kinko/nodes.py
class List(Node):

    def __init__(self, values, **kw):
        self.values = tuple(values)
        super(List, self).__init__(**kw)

    def __repr__(self):
        return '[{}]'.format(' '.join(map(repr, self.values)))

    def accept(self, visitor):
        return visitor.visit_list(self)
kinko/refs.py
class Reference(object):

    def __init__(self, backref):
        self.backref = backref
kinko/errors.py
class Errors(object):

    def __init__(self):
        self.list = []
        self._stack = [None]

    @contextmanager
    def module_ctx(self, module):
        self._stack.append(Func(module, None))
        try:
            yield
        finally:
            self._stack.pop()

    @contextmanager
    def func_ctx(self, module, name):
        self._stack.append(Func(module, name))
        try:
            yield
        finally:
            self._stack.pop()

    @contextmanager
    def location(self, location):
        try:
            yield
        except UserError as e:
            self.error(location, text_type(e))
            raise

    def warn(self, location, message):
        self.list.append(Error(self._stack[-1], location, message, WARNING))

    def error(self, location, message):
        self.list.append(Error(self._stack[-1], location, message, ERROR))
kinko/refs.py
class ItemRef(Reference):

    def __repr__(self):
        return '{!r} > []'.format(self.backref)
kinko/nodes.py
class Keyword(Node):

    def __init__(self, name, **kw):
        self.name = name
        super(Keyword, self).__init__(**kw)

    def __repr__(self):
        return ':{}'.format(self.name)

    def accept(self, visitor):
        return visitor.visit_keyword(self)
kinko/types.py
class BoolType(with_metaclass(BoolTypeMeta, object)):
    pass
kinko/types.py
class RecordMeta(TypingMeta):

    def __cls_init__(cls, items):
        cls.__items__ = dict(items)

    def __repr__(cls):
        return '{}{{{}}}'.format(
            cls.__name__,
            ' '.join(':{} {!r}'.format(*i) for i in cls.__items__.items()),
        )

    def accept(cls, visitor):
        return visitor.visit_record(cls)
kinko/types.py
class MarkupMeta(GenericMeta):

    def __repr__(cls):
        return 'markup'

    def accept(cls, visitor):
        return visitor.visit_markup(cls)
kinko/nodes.py
class NodeVisitor(object):

    def visit(self, node):
        node.accept(self)

    def visit_tuple(self, node):
        for value in node.values:
            self.visit(value)

    def visit_list(self, node):
        for value in node.values:
            self.visit(value)

    def visit_dict(self, node):
        for value in node.values:
            self.visit(value)

    def visit_symbol(self, node):
        pass

    def visit_keyword(self, node):
        pass

    def visit_placeholder(self, node):
        pass

    def visit_number(self, node):
        pass

    def visit_string(self, node):
        pass
kinko/types.py
class TypeVar(with_metaclass(TypeVarMeta, object)):
    pass
kinko/types.py
class NamedArgMeta(TypingMeta):

    def __cls_init__(cls, params):
        cls.__arg_name__, cls.__arg_type__ = params

    def __repr__(cls):
        return ':{} {!r}'.format(cls.__arg_name__, cls.__arg_type__)

    def accept(cls, visitor):
        return visitor.visit_namedarg(cls)
kinko/constant.py
HTML_ELEMENTS = frozenset((
    # root
    'html',

    # metadata
    'head', 'title', 'base', 'link', 'meta', 'style',

    # sections
    'body', 'article', 'section', 'nav', 'aside', 'h1', 'h2', 'h3', 'h4', 'h5',
    'h6', 'hgroup', 'headers', 'footer', 'address',

    # grouping content
    'p', 'hr', 'pre', 'blockquote', 'ol', 'ul', 'li', 'dl', 'dt', 'dd',
    'figure', 'figcaption', 'main', 'div',

    # text-level semantics
    'a', 'em', 'strong', 'small', 's', 'cite', 'q', 'dfn', 'abbr', 'ruby', 'rt',
    'rp', 'data', 'time', 'code', 'var', 'samp', 'kbd', 'sub', 'sup', 'i', 'b',
    'u', 'mark', 'bdi', 'bdo', 'span', 'br', 'wbr',

    # links
    'a', 'area',

    # edits
    'ins', 'del',

    # embedded content
    'picture', 'source', 'img', 'iframe', 'embed', 'object', 'param', 'video',
    'audio', 'track', 'map',

    # tabular data
    'table', 'caption', 'colgroup', 'col', 'tbody', 'thead', 'tfoot', 'tr',
    'td', 'th',

    # forms
    'form', 'label', 'input', 'button', 'select', 'datalist', 'optgroup',
    'option', 'textarea', 'keygen', 'output', 'progress', 'meter', 'fieldset',
    'legend',

    # interactive elements
    'details', 'summary', 'menu', 'menuitem', 'dialog',

    # scripting
    'script', 'noscript', 'template', 'canvas',
))
kinko/nodes.py
class Tuple(Node):

    def __init__(self, values, **kw):
        self.values = tuple(values)
        super(Tuple, self).__init__(**kw)

    def __repr__(self):
        return '({})'.format(' '.join(map(repr, self.values)))

    def accept(self, visitor):
        return visitor.visit_tuple(self)


Question: from itertools import chain
from contextlib import contextmanager
from collections import namedtuple, deque, defaultdict
from .refs import ArgRef, FieldRef, ItemRef, Reference, is_from_arg, CtxRef
from .nodes import Tuple, Number, Keyword, String, List, Symbol, Placeholder
from .nodes import NodeVisitor, NodeTransformer
from .types import IntType, NamedArgMeta, StringType, ListType, VarArgsMeta
from .types import TypeVarMeta, TypeVar, Func, NamedArg, Record, TypeRefMeta
from .types import RecordMeta, BoolType, Union, ListTypeMeta, DictTypeMeta
from .types import TypingMeta, UnionMeta, Nothing, Option, VarArgs, FuncMeta
from .types import TypeTransformer, Markup, VarNamedArgs, VarNamedArgsMeta
from .types import MarkupMeta
from .utils import VarsGen
from .errors import Errors, UserError
from .compat import zip_longest
from .constant import HTML_ELEMENTS


def split_defs(node):
    mapping = defaultdict(list)
    for defn in node.values:
        name_sym = defn.values[1]
        mapping[name_sym.ns].append(defn)
    return {ns: List(body)
            for ns, body in mapping.items()}


class _PlaceholdersExtractor(NodeVisitor):

    def __init__(self):
        # using list to preserve placeholders order for the tests
        self.placeholders = []

    def visit_placeholder(self, node):
        if node.name not in self.placeholders:
            self.placeholders.append(node.name)


class _FreshVars(TypeTransformer):

    def __init__(self):
        self._mapping = {}

    def visit_typevar(self, type_):
        if type_.__instance__ is None:
            if type_ not in self._mapping:
                self._mapping[type_] = TypeVar[None]
            return self._mapping[type_]
        return self.visit(type_.__instance__)


def get_type(node):
    t = node.__type__
    while isinstance(t, TypeVarMeta):
        t = t.__instance__
    return t


def contains_markup(type_):
    def recur_check(t):
        if isinstance(t, UnionMeta):
            return any(recur_check(st) for st in t.__types__)
        else:
            return isinstance(t, MarkupMeta)
    return recur_check(type_)


def returns_markup(node):
    type_ = get_type(node)
    return contains_markup(type_)


def item_ref(backref):
    v = TypeVar[None]
    v.__backref__ = ItemRef(backref)
    return v


def field_refs(backref, names):
    mapping = {}
    for name in names:
        v = TypeVar[None]
        v.__backref__ = FieldRef(backref, name)
        mapping[name] = v
    return mapping


def unify(t1, t2, backref=None):
    """Unify `t1` to match `t2`

    After unification `t1` should be equal to `t2` or `t1` would be
    a subtype of `t2`.

    `t1` may contain type variables. If `t1` comes from arguments and
    contains a record, this record will be extended. `t1`, also, may contain
    type references.

    `t2` can not contain bound type variables, only unbound type variables
    are allowed - they're represent polymorphic types in the function signature.
    `t2`, also, can not contain type references.
    """
    if isinstance(t1, TypeRefMeta):
        assert t1.__ref__ is not None, 'Unbound type reference {!r}'.format(t1)
        unify(t1.__ref__(), t2, backref)

    elif isinstance(t2, TypeRefMeta):
        assert False  # type references are not expected in t2

    elif isinstance(t2, TypeVarMeta) and t2.__instance__ is None:
        assert isinstance(backref, Reference) or backref is None, repr(backref)
        t2.__instance__ = t1
        t2.__backref__ = backref

    elif isinstance(t2, TypeVarMeta):
        assert False  # bound type-vars are not expected in t2

    elif isinstance(t1, TypeVarMeta) and t1.__instance__ is None:
        backref = t1 if t1.__backref__ else backref
        if backref and isinstance(t2, RecordMeta):
            t1.__instance__ = Record[field_refs(backref, t2.__items__)]
            unify(t1, t2, backref)
        elif backref and isinstance(t2, ListTypeMeta):
            t1.__instance__ = ListType[item_ref(backref)]
            unify(t1, t2, backref)
        elif backref and isinstance(t2, DictTypeMeta):
            raise NotImplementedError('TODO')
        else:
            t1.__instance__ = t2

    elif isinstance(t1, TypeVarMeta):
        backref = t1 if t1.__backref__ else backref
        try:
            unify(t1.__instance__, t2, backref)
        except TypeCheckError:
            if (
                backref and is_from_arg(backref) and

Answer:                 not isinstance(t1.__instance__, TypingMeta) and