Beispiel #1
0
    def __missing__(self, key) -> TypeScheme:
        from xotl.fl.utils import tvarsupply
        from xotl.fl.match import MatchLiteral, Extract
        from xotl.fl.ast.types import TypeVariable

        # Constructors of tuples are not fixed, since now you can have (1, 2,
        # 3..., 10000); that's a long tuple with a single constructor
        # (,,...,,); i.e 9999 commas.
        if isinstance(key, str) and TUPLE_CONS.match(key):
            items = len(key) + 1
            names = list(tvarsupply(limit=items))
            type: Type = TypeCons(key, names)
            for name in reversed(names):
                type = name >> type
            return TypeScheme.from_typeexpr(type)
        elif isinstance(key, Extract) and TUPLE_CONS.match(key.name):
            # The type of 'extract' for tuple varies with the number of
            # components of the tuple.  Example, for a triple, Extract(',,',
            # 2) -- i.e. extracting the second element; has the type scheme
            # 'forall a b c r. (a, b, c) -> (b -> r) -> r'.
            type_ = self[key.name].type_
            assert isinstance(type_, TypeCons)
            res = TypeVariable(".r", check=False)
            return TypeScheme.from_typeexpr(type_ >> (
                (type_.subtypes[key.arg - 1] >> res) >> res))
        elif isinstance(key, MatchLiteral):
            # The match has type 'a -> (a -> r) -> r'; where a is the type of
            # the literal.  We must ensure to generate a new variable not free
            # in type a.  Everywhere else we generate types '.a0', '.a1'.
            # Let's use '.r' as the result type.
            a = key.value.type_
            res = TypeVariable(".r", check=False)
            return TypeScheme.from_typeexpr(a >> ((a >> res) >> res))
        else:
            raise KeyError(key)  # pragma: no cover
def test_local_definitions():
    # Taken from the paper 'Practical type inference for arbitrary-rank types'
    # by Peyton Jones, Simon et al.
    assert (parse("""
            foo :: ([Bool], [Char])
            foo = let f :: (forall a. [a] -> [a]) -> ([Bool], [Char])
                      f x = (x [True, False], x ['a', 'b'])
            in f reverse
            """) == [
        {
            "foo": TypeScheme.from_str("([Bool], [Char])")
        },
        Equation(
            "foo",
            [],
            Let(
                {
                    "f":
                    build_lambda(
                        ["x"],
                        parse_expression("(x [True, False], x ['a', 'b'])"))
                },
                build_application("f", Identifier("reverse")),
                {
                    "f":
                    TypeScheme.from_str(
                        "(forall a. [a] -> [a]) -> ([Bool], [Char])")
                },
            ),
        ),
    ])
Beispiel #3
0
def test_hidden_paradox_omega_let():
    code = r"""
    let prxI c  = c free id
        p1 x y  = x
        p2 x y  = y
    in prxI p2 (prxI p2)
    """
    env = BuiltinEnvDict({
        "free": TypeScheme.from_str("f", generics=[]),
        "id": TypeScheme.from_str("a -> a"),
    })
    typecheck(parse_expression(code), env)
def test_typedecls():
    assert (parse("""
            id :: a -> a
            const :: a -> b -> a
            """) == [
        {
            "id": TypeScheme.from_str("a -> a")
        },
        {
            "const": TypeScheme.from_str("a -> b -> a")
        },
    ])
Beispiel #5
0
 def genbar(unknowns, names, type_, name):
     if not _generalize_over or name in _generalize_over:
         schvars = list(
             {tv.name for tv in find_tvars(type_) if tv.name not in unknowns}
         )
         alist: List[Tuple[str, TypeVariable]] = list(zip(schvars, ns))
         restype = subtype(build_substitution(alist), type_)
         return TypeScheme([v.name for _, v in alist], restype)
     else:
         # This correspond to the No Qualification rule explained in
         # [OutsideInX], section 4.2.4; which means that let-bound
         # variables are not generalized but left as unknown.
         return TypeScheme([], type_)
Beispiel #6
0
def typecheck_letrec(env: TypeEnvironment, ns, exp: Letrec) -> TCResult:
    # This algorithm is quite elaborate.
    #
    # We expected that at least one of exprs is defined in terms of a name.
    # So, we must type-check all of exprs in a type environment where there's
    # non-generic type associated to each of the names defined in the 'let'.
    #
    #     let x1 = e1
    #         x2 = e2
    #        ...
    #     in body
    #
    # We make a new type scheme for each 'x' ; x1 :: Tx1, x2 :: Tx2, etc...
    # and type-check of the 'exprs' in this extended environment.
    #
    exprs: Sequence[AST] = tuple(exp.values())
    names: Sequence[Symbolic] = tuple(exp.keys())
    nbvs = {
        name: TypeScheme.from_typeexpr(var, generics=[]) for name, var in zip(names, ns)
    }
    phi, ts = tcl(ChainMap(nbvs, env), ns, exprs)

    # At this point `phi` is the substitution that makes all the bindings in
    # the letrec type-check; and `ts` is the list of the types inferred for
    # each expr.
    #
    # We must now see if the inferred types match the annotations
    # (if any).
    #
    # Then, we must unify the types inferred with the types of the names in
    # the non-extended environment, but taking the `phi` into account.
    #
    local = exp.localenv or {}
    if local:
        typepairs = [
            (newinstance(ns, local[name]), ts[i])
            for i, name in enumerate(names)
            if name in local
        ]
        phi = unify_exprs(typepairs, p=phi)
        ts = [subtype(phi, t) for t in ts]
        gamma = sub_typeenv(phi, ChainMap(local, env))
    else:
        gamma = sub_typeenv(phi, env)
    nbvs1 = sub_typeenv(phi, nbvs)
    ts1 = [sch.type_ for _, sch in nbvs1.items()]
    psi = unify_exprs(zip(ts, ts1), p=phi)

    # Now we have type-checked the definitions, so we can now typecheck the
    # body in the **proper** environment.
    nbvs1 = sub_typeenv(psi, nbvs)
    ts = [sch.type_ for _, sch in nbvs1.items()]
    psi1, t = typecheck(
        exp.body,
        _add_decls(
            sub_typeenv(psi, gamma), ns, names, ts, _generalize_over=local.keys()
        ),
        ns,
    )
    return scompose(psi1, psi), t
def test_simple_if_program():
    assert parse(
        """
        if :: Bool -> a -> a -> a
        if True x _  = x
        if False _ x = x
        """,
        debug=True,
    ) == [
        {
            "id": TypeScheme.from_str("Bool -> a -> a -> a")
        },
        Equation(
            "if",
            [Identifier("True"),
             Identifier("x"),
             Identifier("_")],
            Identifier("x"),
        ),
        Equation(
            "if",
            [Identifier("False"),
             Identifier("_"),
             Identifier("x")],
            Identifier("x"),
        ),
    ]
Beispiel #8
0
def subscheme(phi: Substitution, ts: TypeScheme) -> TypeScheme:
    """Apply a substitution to a type scheme.

    .. warning:: You must ensure that the type scheme's generic variables are
       distinct from the variables occurring in the result of applying the
       substitution `phi` to any of the non-generic variables of `ts`.

       The way in which we ensure this (in the algorithm) is to guarantee that
       the names of the generic variables in the type scheme are always
       distinct from those which can occur in the range of the substitution
       (which are always non-generic).

    """
    # From Damas1982:
    #
    # If S is a substitution of types for type variables, often written
    # [τ1/α1, ..., τn/αn ] or [τi/αi], and σ is a type-scheme, then Sσ is the
    # type-scheme obtained by replacing each free occurrence of αi in σ by τi,
    # renaming the generic variables of σ if necessary.  Then Sσ is called an
    # instance of σ; the notions of substitution and instance extend naturally
    # to larger syntactic constructs containing type-schemes.
    #
    assert all(not bool(scvs & set(tv.name for tv in find_tvars(phi(unk))))
               for scvs in (set(ts.generics), ) for unk in ts.nongenerics)
    return TypeScheme(ts.generics, subtype(Exclude(phi, ts), ts.type_))
Beispiel #9
0
def test_typecheck_recursion():
    then = TypeScheme.from_str("a -> Then a")
    else_ = TypeScheme.from_str("a -> Else a")
    if_then_else = TypeScheme.from_str("Bool -> Then a -> Else a -> a")
    Nil = TypeScheme.from_str("[a]")
    tail = TypeScheme.from_str("[a] -> [a]")
    matches = TypeScheme.from_str("a -> b -> Bool")
    add = TypeScheme.from_str("a -> a -> a")
    env = BuiltinEnvDict({
        "if": if_then_else,
        "then": then,
        "else": else_,
        "matches": matches,
        "Nil": Nil,
        "+": add,
        "tail": tail,
    })
    phi, t = typecheck(
        # I need to put parenthesis because of the failure of precedence we
        # have; otherwise we could use $ to connect if then and else (they are
        # still functions): 'if cond $ then result $ else other_result'.
        # `matches` would be a simple pattern matching function.  The real
        # function would have to operate on values and patterns (which are no
        # representable here.)
        parse_expression("""
            let count xs = if (xs `matches` Nil) \
                              (then 0) \
                              (else let ts = tail xs in 1 + (count ts))
            in count
            """),
        env,
    )
    # The count functions counts the number of elements.
    unify(Type.from_str("[a] -> Number"), t)
Beispiel #10
0
def typecheck_lambda(env: TypeEnvironment, ns, exp: Lambda) -> TCResult:
    # \x -> ...; the type of 'x' can be anything.  Thus, create a type
    # scheme with a new non-generic type variable Tx.  We extend the
    # environment to say 'x :: Tx' and typecheck the body of the lambda in
    # this new environment.
    newvar = next(ns)
    argtype = TypeScheme.from_typeexpr(newvar, generics=[])
    phi, type_ = typecheck(exp.body, ChainMap({exp.varname: argtype}, env), ns)
    return phi, FuncCons(phi(newvar.name), type_)
Beispiel #11
0
def test_hidden_paradox_omega_2():
    code = """
    let id x    = x
        prxI c  = c x id
        p1 x y  = x
        p2 x y  = y
    in prxI p1 (prxI p1)
    """
    env = BuiltinEnvDict({"x": TypeScheme.from_str("a", generics=[])})
    with pytest.raises(TypeError):
        typecheck(parse_expression(code), env)
Beispiel #12
0
def test_hidden_paradox_omega_letrec():
    code = r"""
    let id x = x
        prxI :: forall b c. (free -> (b -> b) -> c) -> c
        prxI c  = c free id
        p1 x y  = x
        p2 :: x -> y -> y
        p2 x y  = y
    in prxI p2 (prxI p2)
    """
    env = BuiltinEnvDict({"free": TypeScheme.from_str("free", generics=[])})
    typecheck(parse_expression(code), env)
Beispiel #13
0
def test_local_type_annotation_letrec():
    phi, t = typecheck(
        parse_expression("""let count :: Number -> [Number]
                   count x = x:count (x + 1)
                   g :: [a]
                   g = count 1
                   g2 = reverse g
                in g2"""),
        BuiltinEnvDict({
            "reverse": TypeScheme.from_str("[a] -> [a]"),
            ":": TypeScheme.from_str("a -> [a] -> [a]"),
            "+": TypeScheme.from_str("a -> a -> a"),
        }),
    )
    assert t == Type.from_str("[Number]")

    with pytest.raises(TypeError):
        typecheck(
            parse_expression("""let count :: Number -> [Number]
                       count x = x:count (x + 1)
                       g :: [Char]
                       g = count 1
                    in reverse g"""),
            BuiltinEnvDict({
                "reverse": TypeScheme.from_str("[a] -> [a]"),
                ":": TypeScheme.from_str("a -> [a] -> [a]"),
                "+": TypeScheme.from_str("a -> a -> a"),
            }),
        )
Beispiel #14
0
    def __init__(self, d=None, **kw):
        from xotl.fl.ast.types import TypeScheme
        from xotl.fl.match import NO_MATCH_ERROR, MATCH_OPERATOR
        from xotl.fl.match import Match, Extract

        if not d:
            d = {}
        init = {
            # These can't be parsed (yet) and are really builtin -- their
            # values cannot be directly expressed in the language, even
            # though isomorphic types can be expressed, i.e 'data List a =
            # Nil | Cons a (List a)'.
            "[]":
            TypeScheme.from_str("[a]"),
            # There are special identifiers provided for translation of
            # pattern matching: The FATBAR (MATCH) operator; notice that
            # type-wise this is operator takes two arguments or equal type and
            # returns the first if it matches or the second.
            MATCH_OPERATOR.name:
            TypeScheme.from_str("a -> a -> a"),
            NO_MATCH_ERROR.name:
            TypeScheme.from_str("a"),
            # These are 'match' and 'extract' for lists pattern matching.
            Match("[]"):
            TypeScheme.from_str("[a] -> b -> b"),
            Extract(":", 1):
            TypeScheme.from_str("[a] -> ([a] -> b) -> b"),
            Extract(":", 2):
            TypeScheme.from_str("[a] -> ([a] -> b) -> b"),
            # Pattern matching requires 'extracting' the type from the Pattern
            # Cons.  These are dynamic and require knowledge from the locally
            # (program) defined types; we cannot provide the types here.
        }
        init.update(d)
        super().__init__(init, **kw)
Beispiel #15
0
def test_composition():
    phi, t = typecheck(parse_expression("let id x = x in id . id"),
                       builtins_env)
    unify(Type.from_str("a -> a"), t)
    unify(Type.from_str("(a -> a) -> (a -> a)"), t)

    phi, t = typecheck(parse_expression("Left . Right"), builtins_env)
    unify(Type.from_str("a -> Either (Either b a) c"), t)

    # In our case, (+) is not polymorphic (Number is not a type-class), so it
    # can't be composed with Either.
    with pytest.raises(TypeError):
        typecheck(parse_expression("(+) . Left"), builtins_env)
    # If we had a polymorphic (+), it would be composable
    phi, t = typecheck(
        parse_expression("(+) . Left"),
        dict(builtins_env, **{"+": TypeScheme.from_str("a -> a -> a")}),
    )
    unify(Type.from_str("a -> Either a b -> Either a b"), t)
    phi, t = typecheck(
        parse_expression("(+) . Right"),
        dict(builtins_env, **{"+": TypeScheme.from_str("a -> a -> a")}),
    )
    unify(Type.from_str("b -> Either a b -> Either a b"), t)
Beispiel #16
0
def test_basic_builtin_types():
    with pytest.raises(TypeError):
        # not :: Bool -> Bool, but passed a Number
        typecheck(parse_expression("not 0"), builtins_env)

    phi, t = typecheck(parse_expression("not True"), builtins_env)
    assert t == BoolType
    phi, t = typecheck(parse_expression("not False"), builtins_env)
    assert t == BoolType

    userfuncs = {"toString": TypeScheme.from_str("a -> [Char]")}
    phi, t = typecheck(parse_expression("either toString id"),
                       dict(builtins_env, **userfuncs))
    assert len(find_tvars(t)) == 1
    unify(Type.from_str("Either a [Char] -> [Char]"), t)
Beispiel #17
0
def test_local_type_annotation_let():
    phi, t = typecheck(
        parse_expression("""let g = [1, 2, 3]
               in reverse g"""),
        BuiltinEnvDict({
            "reverse": TypeScheme.from_str("[a] -> [a]"),
            ":": TypeScheme.from_str("a -> [a] -> [a]"),
        }),
    )
    assert t == Type.from_str("[Number]")

    phi, t = typecheck(
        parse_expression("""let g :: [Number]
                   g = []
               in reverse g"""),
        BuiltinEnvDict({
            "reverse": TypeScheme.from_str("[a] -> [a]"),
            ":": TypeScheme.from_str("a -> [a] -> [a]"),
        }),
    )
    assert t == Type.from_str("[Number]")

    phi, t = typecheck(
        parse_expression("""let g :: [a]
                   g = [1, 2, 3]
                in reverse g"""),
        BuiltinEnvDict({
            "reverse": TypeScheme.from_str("[a] -> [a]"),
            ":": TypeScheme.from_str("a -> [a] -> [a]"),
        }),
    )
    assert t == Type.from_str("[Number]")

    with pytest.raises(TypeError):
        typecheck(
            parse_expression("""let g :: [Char]
                       g = [1, 2, 3]
                   in reverse g"""),
            BuiltinEnvDict({
                "reverse": TypeScheme.from_str("[a] -> [a]"),
                ":": TypeScheme.from_str("a -> [a] -> [a]"),
            }),
        )
Beispiel #18
0
def subtype(phi: Substitution, t: Type) -> Type:
    """Get the sub-type of `t` by applying the substitution `phi`.

    """
    # 'subtype(sidentity, t) == t'; and since Type, TypeVariables and TypeCons
    # are treated immutably we should be safe to return the same type.
    if phi is sidentity:
        return t
    elif isinstance(t, TypeVariable):
        return phi(t.name)
    elif isinstance(t, TypeCons):
        return TypeCons(t.cons, [subtype(phi, subt) for subt in t.subtypes],
                        binary=t.binary)
    elif isinstance(t, TypeScheme):
        psi = Exclude(phi, t)
        return TypeScheme(t.generics, subtype(psi, t.type_))
    else:
        assert False, f"Node of unknown type {t!r}"
Beispiel #19
0
    def implied_env(self) -> TypeEnvironment:
        """The implied type environment by the data type.

        Each data constructor is a function (or value) of type of the data
        type.

        A simple example is the Bool data type:

            >>> from xotl.fl import parse
            >>> datatype = parse('data Bool = True | False')[0]
            >>> datatype.implied_env
            {'True': <TypeScheme: Bool>, 'False': <TypeScheme: Bool>}

        Both True and False are just values of type Bool.

        The Either data type shows data constructors with parameters:

        .. doctest::
           :options: +NORMALIZE_WHITESPACE

            >>> datatype = parse('data Either a b = Left a | Right b')[0]
            >>> datatype.implied_env
            {'Left': <TypeScheme: forall a b. a -> (Either a b)>,
             'Right': <TypeScheme: forall a b. b -> (Either a b)>}

        Right takes any value of type `a` and returns a value of type `Either
        a b` (for any type `b`).

        """
        from xotl.fl.ast.types import FunctionTypeCons

        def _implied_type(dc: DataCons) -> Type:
            result = self.type_
            for arg in reversed(dc.args):
                result = FunctionTypeCons(arg, result)
            return result

        return {
            dc.name: TypeScheme.from_typeexpr(_implied_type(dc))
            for dc in self.dataconses
        }