kinko/types.py
class VarArgs(with_metaclass(VarArgsMeta, object)):
    pass
kinko/types.py
class Nothing(with_metaclass(NothingMeta, 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/types.py
class MarkupMeta(GenericMeta):

    def __repr__(cls):
        return 'markup'

    def accept(cls, visitor):
        return visitor.visit_markup(cls)
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 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 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/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/types.py
class NamedArg(with_metaclass(NamedArgMeta, object)):
    pass
kinko/types.py
class Option(with_metaclass(OptionMeta, object)):
    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/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/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/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/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/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)
kinko/types.py
class Record(with_metaclass(RecordMeta, object)):
    pass
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 Markup(with_metaclass(MarkupMeta, 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 BoolType(with_metaclass(BoolTypeMeta, object)):
    pass
kinko/refs.py
def is_from_arg(ref):
    return isinstance(get_origin(ref), ArgRef)
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/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 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/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/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
class Reference(object):

    def __init__(self, backref):
        self.backref = backref
kinko/types.py
class Union(with_metaclass(UnionMeta, object)):
    pass
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/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 VarNamedArgs(with_metaclass(VarNamedArgsMeta, object)):
    pass
kinko/refs.py
class ItemRef(Reference):

    def __repr__(self):
        return '{!r} > []'.format(self.backref)
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/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/types.py
class StringType(with_metaclass(StringTypeMeta, object)):
    pass
kinko/types.py
class IntType(with_metaclass(IntTypeMeta, object)):
    pass
kinko/types.py
class ListType(with_metaclass(ListTypeMeta, 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/types.py
class TypeVar(with_metaclass(TypeVarMeta, object)):
    pass
kinko/errors.py
class UserError(Exception):
    pass
kinko/types.py
class Func(with_metaclass(FuncMeta, 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/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 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 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 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)


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
                if not isinstance(t1, TypingMeta):
                    # means simple type with nullary constructor
                    return

        raise TypeCheckError('Unexpected type: {!r}, instead of: {!r}'
                             .format(t1, t2))


def split_args(args):
    pos_args, kw_args = [], {}
    i = enumerate(args)
    try:
        while True:
            arg_pos, arg = next(i)
            if isinstance(arg, Keyword):
                try:
                    value_pos, _ = next(i)
                except StopIteration:
                    raise TypeError('Missing named argument value')
                else:
                    kw_args[arg.name] = value_pos
            else:
                pos_args.append(arg_pos)
    except StopIteration:
        return pos_args, kw_args


def normalize_args(fn_type, args, pos_args, kw_args):
    pos_args, kw_args = list(pos_args), dict(kw_args)
    norm_args = []
    norm_args_pos = []
    missing_arg = False
    for arg_type in fn_type.__args__:
        if isinstance(arg_type, NamedArgMeta):
            try:
                value_pos = kw_args.pop(arg_type.__arg_name__)
            except KeyError:
                missing_arg = True
                break
            else:
                norm_args.append(args[value_pos])
                norm_args_pos.append(value_pos)
        elif isinstance(arg_type, VarArgsMeta):
            norm_args.append([args[pos] for pos in pos_args])
            norm_args_pos.append(list(pos_args))
            del pos_args[:]
        elif isinstance(arg_type, VarNamedArgsMeta):
            norm_args.append({k: args[v] for k, v in kw_args.items()})
            norm_args_pos.append(kw_args.copy())
            kw_args.clear()
        else:
            try:
                value_pos = pos_args.pop(0)
            except IndexError:
                missing_arg = True
                break
            else:
                norm_args.append(args[value_pos])
                norm_args_pos.append(value_pos)
    if pos_args or kw_args or missing_arg:
        raise SignatureMismatch
    else:
        return norm_args, norm_args_pos


def match_fn(fn_types, args):
    pos_args, kw_args = split_args(args)
    for fn_type in fn_types:
        try:
            norm_args, norm_args_pos = \
                normalize_args(fn_type, args, pos_args, kw_args)
        except SignatureMismatch:
            continue
        else:
            return fn_type, norm_args, norm_args_pos
    else:
        raise TypeCheckError('Function signature mismatch')


def restore_args(fn_type, args, norm_args, norm_args_pos):
    args_map = {}
    _norm_args = list(norm_args)
    _norm_args_pos = list(norm_args_pos)
    for arg_type in fn_type.__args__:
        if isinstance(arg_type, NamedArgMeta):
            value = _norm_args.pop(0)
            value_pos = _norm_args_pos.pop(0)
            args_map[value_pos - 1] = args[value_pos - 1]  # keyword
            args_map[value_pos] = value
        elif isinstance(arg_type, VarArgsMeta):
            values = _norm_args.pop(0)
            values_pos = _norm_args_pos.pop(0)
            for v, v_pos in zip(values, values_pos):
                args_map[v_pos] = v
        elif isinstance(arg_type, VarNamedArgsMeta):
            values_map = _norm_args.pop(0)
            values_pos = _norm_args_pos.pop(0)
            for key in values_map.keys():
                value = values_map[key]
                value_pos = values_pos[key]
                args_map[value_pos - 1] = args[value_pos - 1]  # keyword
                args_map[value_pos] = value
        else:
            value = _norm_args.pop(0)
            value_pos = _norm_args_pos.pop(0)
            args_map[value_pos] = value
    assert not _norm_args
    assert len(args_map) == len(args)
    return [args_map[i] for i in range(len(args))]


def check_arg(arg, type_, env):
    arg = check(arg, env)
    with env.errors.location(arg.location):
        unify(arg.__type__, type_)
    return arg


_StringLike = Union[Nothing, IntType, StringType]


Answer: _MarkupLike = Union[_StringLike, Markup]