Exemple #1
0
 def test_completeness_test(self) -> None:
     in_var = SequenceVariable()
     f = StackEffect(
         [in_var, object_type, object_type],
         [in_var, object_type, object_type],
     )
     self.assertFalse(f.can_be_complete_program())
Exemple #2
0
def _generate_module_type(components: Sequence[str],
                          _full_name: Optional[str] = None,
                          source_dir='.') -> ObjectType:
    if _full_name is None:
        _full_name = '.'.join(components)
    if len(components) > 1:
        module_t = ObjectType(
            IndividualVariable(),
            {
                components[1]:
                _generate_module_type(components[1:], _full_name,
                                      source_dir)[_seq_var, ],
            },
            nominal_supertypes=[module_type],
        )
        effect = StackEffect([_seq_var], [_seq_var, module_type])
        return ObjectType(IndividualVariable(), {
            '__call__': effect,
        }, [_seq_var])
    else:
        innermost_type = _generate_type_of_innermost_module(
            _full_name, source_dir)
        return ObjectType(IndividualVariable(), {
            '__call__': innermost_type,
        }, [_seq_var])
Exemple #3
0
    def to_type(self, env: Environment) -> Tuple[StackEffect, Environment]:
        a_bar = SequenceVariable()
        b_bar = a_bar
        new_env = env.copy()
        if self.input_sequence_variable is not None:
            if self.input_sequence_variable in new_env:
                a_bar = cast(
                    SequenceVariable,
                    new_env[self.input_sequence_variable],
                )
            new_env[self.input_sequence_variable] = a_bar
        if self.output_sequence_variable is not None:
            if self.output_sequence_variable in new_env:
                b_bar = cast(
                    SequenceVariable,
                    new_env[self.output_sequence_variable],
                )
            else:
                b_bar = SequenceVariable()
                new_env[self.output_sequence_variable] = b_bar

        in_types = []
        for item in self.input:
            type, new_env = _ensure_type(item[1], new_env, item[0])
            in_types.append(type)
        out_types = []
        for item in self.output:
            type, new_env = _ensure_type(item[1], new_env, item[0])
            out_types.append(type)

        return StackEffect([a_bar, *in_types], [b_bar, *out_types]), new_env
Exemple #4
0
def _generate_type_of_innermost_module(qualified_name: str,
                                       source_dir) -> StackEffect:
    # We resolve imports as if we are the source file.
    sys.path, old_path = [source_dir, *sys.path], sys.path
    try:
        module = importlib.import_module(qualified_name)
    except ModuleNotFoundError:
        raise TypeError(
            'module {} not found during type checking'.format(qualified_name))
    finally:
        sys.path = old_path
    module_attributes = {}
    for name in dir(module):
        attribute_type = object_type
        if isinstance(getattr(module, name), int):
            attribute_type = int_type
        elif callable(getattr(module, name)):
            attribute_type = py_function_type
        module_attributes[name] = attribute_type
    module_t = ObjectType(
        IndividualVariable(),
        module_attributes,
        nominal_supertypes=[module_type],
    )
    return StackEffect([_seq_var], [_seq_var, module_type])
Exemple #5
0
 def test_add_operator_inference(self, a: int, b: int) -> None:
     try_prog = '{!r} {!r} +\n'.format(a, b)
     tree = parse(try_prog)
     _, type = concat.typecheck.infer(
         concat.typecheck.Environment(), tree.children, is_top_level=True,
     )
     note(str(type))
     self.assertEqual(type, StackEffect([], [int_type]))
Exemple #6
0
 def test_cast_word(self) -> None:
     """Test that the type checker properly checks casts."""
     tree = parse('"str" cast (int)')
     _, type = concat.typecheck.infer(
         Environment(concat.typecheck.preamble_types.types),
         tree.children,
         is_top_level=True,
     )
     self.assertEqual(type, StackEffect([], [int_type]))
Exemple #7
0
 def test_slice_inference(self) -> None:
     slice_prog = '[1, 2, 3, 4, 5, 6, 7, 8] $[1:None:2]\n'
     tree = parse(slice_prog)
     _, type = concat.typecheck.infer(
         Environment(concat.typecheck.preamble_types.types),
         tree.children,
         is_top_level=True,
     )
     self.assertEqual(type, StackEffect([], [list_type[int_type,]]))
Exemple #8
0
 def test_call_inference(self) -> None:
     try_prog = '$(42) call\n'
     tree = parse(try_prog)
     _, type = concat.typecheck.infer(
         concat.typecheck.Environment(
             concat.typecheck.preamble_types.types
         ),
         tree.children,
         is_top_level=True,
     )
     self.assertEqual(type, StackEffect([], [int_type]))
Exemple #9
0
 def test_read_quot(self) -> None:
     stack = []
     seq_var = SequenceVariable()
     # Like in Factor, read_quot will search its caller's scope for objects.
     some, words, here = object(), object(), object()
     with replace_stdin(io.StringIO('some words here')):
         concat.stdlib.repl.read_quot(
             stack,
             [],
             extra_env=concat.typecheck.Environment(
                 {
                     'some': StackEffect([seq_var], []),
                     'words': StackEffect([], []),
                     'here': StackEffect([], []),
                 }
             ),
         )
     self.assertEqual(
         stack,
         [concat.stdlib.types.Quotation([some, words, here])],
         msg='read_quot has incorrect stack effect',
     )
Exemple #10
0
 def test_with_word_and_funcdef_inference(self) -> None:
     wth = 'def fn(f:object -- n:int): drop 0 ~\n$fn {"file": "a_file"} open with'
     tree = parse(wth)
     in_var = SequenceVariable()
     _, type = concat.typecheck.infer(
         Environment(
             {
                 **concat.typecheck.preamble_types.types,
                 'drop': concat.typecheck.types.ForAll(
                     [in_var], StackEffect([in_var, object_type], [in_var])
                 ),
                 'open': concat.typecheck.types.ForAll(
                     [in_var],
                     # FIXME: dict_type should be a type constructor
                     StackEffect([in_var, dict_type], [in_var, file_type]),
                 ),
             }
         ),
         tree.children,
         initial_stack=TypeSequence([in_var]),
     )
     self.assertEqual(type, StackEffect([in_var], [in_var, int_type]))
Exemple #11
0
 def test_attribute_word(self, attr_word) -> None:
     _, type = concat.typecheck.infer(
         concat.typecheck.Environment(),
         [attr_word],
         initial_stack=TypeSequence(
             [
                 ObjectType(
                     IndividualVariable(),
                     {attr_word.value: StackEffect([], [int_type]),},
                 ),
             ]
         ),
     )
     self.assertEqual(list(type.output), [int_type])
Exemple #12
0
 def test_with_word(self) -> None:
     wth = '$() ctxmgr with\n'
     tree = parse(wth)
     a_bar = SequenceVariable()
     self.assertRaises(
         concat.typecheck.TypeError,
         concat.typecheck.infer,
         concat.typecheck.Environment(
             {
                 'ctxmgr': concat.typecheck.types.ForAll(
                     [a_bar], StackEffect([a_bar], [a_bar, object_type])
                 )
             }
         ),
         tree.children,
     )
Exemple #13
0
class TestStackEffectParser(unittest.TestCase):
    _a_bar = concat.typecheck.SequenceVariable()
    _d_bar = concat.typecheck.SequenceVariable()
    _b = concat.typecheck.IndividualVariable()
    _c = concat.typecheck.IndividualVariable()
    examples: Dict[str, StackEffect] = {
        'a b -- b a': StackEffect([_a_bar, _b, _c], [_a_bar, _c, _b]),
        'a -- a a': StackEffect([_a_bar, _b], [_a_bar, _b, _b]),
        'a --': StackEffect([_a_bar, _b], [_a_bar]),
        'a:object b:object -- b a': StackEffect(
            [_a_bar, object_type, object_type,], [_a_bar, *[object_type] * 2],
        ),
        'a:`t -- a a': StackEffect([_a_bar, _b], [_a_bar, _b, _b]),
        '*i -- *i a': StackEffect([_a_bar], [_a_bar, _b]),
        '*i fun:(*i -- *o) -- *o': StackEffect(
            [_a_bar, StackEffect([_a_bar], [_d_bar])], [_d_bar],
        ),
    }

    def test_examples(self) -> None:
        for example in self.examples:
            with self.subTest(example=example):
                tokens = lex_string(example)
                # exclude ENCODING, NEWLINE and ENDMARKER
                tokens = tokens[1:-2]
                try:
                    effect = build_parsers()['stack-effect-type'].parse(tokens)
                except parsy.ParseError as e:
                    self.fail('could not parse {}\n{}'.format(example, e))
                env = Environment(concat.typecheck.preamble_types.types)
                actual = effect.to_type(env)[0].generalized_wrt(env)
                expected = self.examples[example].generalized_wrt(env)
                print(actual)
                print(expected)
                self.assertEqual(
                    actual, expected,
                )
Exemple #14
0
 def test_class_subtype_of_stack_effect(self, effect) -> None:
     x = IndividualVariable()
     # NOTE: self-last convention is modelled after Factor.
     unbound_effect = StackEffect([*effect.input, x], effect.output)
     cls = ClassType(x, {'__init__': unbound_effect})
     self.assertLessEqual(cls, effect)
Exemple #15
0
 def test_stack_effect_subtyping(self, type1, type2) -> None:
     fun1 = StackEffect([type1], [type2])
     fun2 = StackEffect([no_return_type], [object_type])
     self.assertLessEqual(fun1, fun2)
Exemple #16
0
def infer(
    gamma: Environment,
    e: 'concat.astutils.WordsOrStatements',
    extensions: Optional[Tuple[Callable]] = None,
    is_top_level=False,
    source_dir='.',
    initial_stack: Optional[TypeSequence] = None,
) -> Tuple[Substitutions, StackEffect]:
    """The infer function described by Kleffner."""
    e = list(e)
    current_subs = Substitutions()
    if initial_stack is None:
        initial_stack = TypeSequence(
            [] if is_top_level else [SequenceVariable()])
    current_effect = StackEffect(initial_stack, initial_stack)

    for node in e:
        try:
            S, (i, o) = current_subs, current_effect

            if isinstance(node, concat.operators.AddWordNode):
                # rules:
                # require object_type because the methods should return
                # NotImplemented for most types
                # FIXME: Make the rules safer... somehow

                # ... a b => (... {__add__(object) -> s} t)
                # ---
                # a b + => (... s)

                # ... a b => (... t {__radd__(object) -> s})
                # ---
                # a b + => (... s)
                *rest, type1, type2 = current_effect.output
                try_radd = False
                try:
                    add_type = type1.get_type_of_attribute('__add__')
                except AttributeError:
                    try_radd = True
                else:
                    if not isinstance(add_type, ObjectType):
                        raise TypeError(
                            '__add__ method of type {} is not of an object type, instead has type {}'
                            .format(type1, add_type))
                    if add_type.head != py_function_type:
                        raise TypeError(
                            '__add__ method of type {} is not a Python function, instead it has type {}'
                            .format(type1, add_type))
                    if [*add_type.type_arguments[0]] != [object_type]:
                        raise TypeError(
                            '__add__ method of type {} does not have type (object) -> `t, instead it has type {}'
                            .format(type1, add_type))
                    current_effect = StackEffect(
                        current_effect.input,
                        [*rest, add_type.output],
                    )
                if try_radd:
                    radd_type = type2.get_type_of_attribute('__radd__')
                    if (not isinstance(radd_type, ObjectType)
                            or radd_type.head != py_function_type or
                        [*radd_type.type_arguments[0]] != [object_type]):
                        raise TypeError(
                            '__radd__ method of type {} does not have type (object) -> `t, instead it has type {} (left operand is of type {})'
                            .format(type2, radd_type, type1))
                    current_effect = StackEffect(
                        current_effect.input,
                        [*rest, radd_type.output],
                    )
            elif isinstance(node, concat.parse.PushWordNode):
                S1, (i1, o1) = S, (i, o)
                # special case for pushing an attribute accessor
                child = node.children[0]
                if isinstance(child, concat.parse.AttributeWordNode):
                    top = o1[-1]
                    attr_type = top.get_type_of_attribute(child.value)
                    rest_types = o1[:-1]
                    current_subs, current_effect = (
                        S1,
                        StackEffect(i1, [*rest_types, attr_type]),
                    )
                # special case for name words
                elif isinstance(child, concat.parse.NameWordNode):
                    if child.value not in gamma:
                        raise NameError(child)
                    name_type = gamma[child.value].instantiate()
                    current_effect = StackEffect(
                        current_effect.input,
                        [*current_effect.output,
                         current_subs(name_type)],
                    )
                elif isinstance(child, concat.parse.SliceWordNode):
                    sliceable_object_type = o[-1]
                    # This doesn't match the evaluation order used by the
                    # transpiler.
                    # FIXME: Change the transpiler to fit the type checker.
                    sub1, start_effect = infer(
                        gamma,
                        list(child.start_children),
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=TypeSequence(o[:-1]),
                    )
                    start_type = start_effect.output[-1]
                    o = tuple(start_effect.output[:-1])
                    sub2, stop_effect = infer(
                        sub1(gamma),
                        list(child.stop_children),
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=TypeSequence(o),
                    )
                    stop_type = stop_effect.output[-1]
                    o = tuple(stop_effect.output[:-1])
                    sub3, step_effect = infer(
                        sub2(sub1(gamma)),
                        list(child.step_children),
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=TypeSequence(o),
                    )
                    step_type = step_effect.output[-1]
                    o = tuple(step_effect.output[:-1])
                    this_slice_type = slice_type[start_type, stop_type,
                                                 step_type]
                    getitem_type = sliceable_object_type.get_type_of_attribute(
                        '__getitem__')
                    getitem_type = getitem_type.get_type_of_attribute(
                        '__call__')
                    getitem_type = getitem_type.instantiate()
                    if (not isinstance(getitem_type, PythonFunctionType)
                            or len(getitem_type.input) != 1):
                        raise TypeError(
                            '__getitem__ method of {} has incorrect type {}'.
                            format(node, getitem_type))
                    getitem_type, overload_subs = getitem_type.select_overload(
                        (this_slice_type, ))
                    result_type = getitem_type.output
                    current_subs = overload_subs(sub3(sub2(
                        sub1(current_subs))))
                    current_effect = current_subs(
                        StackEffect(i, [*o, result_type]))
                # special case for subscription words
                elif isinstance(child, concat.parse.SubscriptionWordNode):
                    S2, (i2, o2) = infer(
                        current_subs(gamma),
                        child.children,
                        extensions=extensions,
                        is_top_level=False,
                        source_dir=source_dir,
                        initial_stack=current_effect.output,
                    )
                    # FIXME: Should be generic
                    subscriptable_interface = subscriptable_type[
                        int_type, IndividualVariable(), ]

                    rest_var = SequenceVariable()
                    expected_o2 = TypeSequence([
                        rest_var,
                        subscriptable_interface,
                        int_type,
                    ])
                    o2[-1].constrain(int_type)
                    getitem_type = (o2[-2].get_type_of_attribute(
                        '__getitem__').instantiate().get_type_of_attribute(
                            '__call__').instantiate())
                    if not isinstance(getitem_type, PythonFunctionType):
                        raise TypeError(
                            '__getitem__ of type {} is not a Python function (has type {})'
                            .format(o2[-2], getitem_type))
                    getitem_type, overload_subs = getitem_type.select_overload(
                        [int_type])
                    current_subs = overload_subs(S2(current_subs))
                    current_effect = current_subs(
                        StackEffect(
                            current_effect.input,
                            [*o2[:-2], getitem_type.output],
                        ))
                else:
                    if (isinstance(child, concat.parse.QuoteWordNode)
                            and child.input_stack_type is not None):
                        input_stack, _ = child.input_stack_type.to_type(gamma)
                    else:
                        # The majority of quotations I've written don't comsume
                        # anything on the stack, so make that the default.
                        input_stack = TypeSequence([SequenceVariable()])
                    S2, fun_type = infer(
                        S1(gamma),
                        child.children,
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=input_stack,
                    )
                    current_subs, current_effect = (
                        S2(S1),
                        StackEffect(
                            S2(TypeSequence(i1)),
                            [*S2(TypeSequence(o1)),
                             QuotationType(fun_type)],
                        ),
                    )
            elif isinstance(node, concat.parse.WithWordNode):
                a_bar, b_bar = SequenceVariable(), SequenceVariable()
                body_type = StackEffect([a_bar, object_type], [b_bar])
                phi = current_effect.output.constrain_and_bind_supertype_variables(
                    TypeSequence([a_bar, body_type, context_manager_type]),
                    set(),
                )
                assert b_bar in phi
                current_subs, current_effect = (
                    phi(current_subs),
                    phi(
                        StackEffect(current_effect.input,
                                    TypeSequence([b_bar]))),
                )
            elif isinstance(node, concat.parse.TryWordNode):
                a_bar, b_bar = SequenceVariable(), SequenceVariable()
                phi = TypeSequence(o).constrain_and_bind_supertype_variables(
                    TypeSequence([
                        a_bar,
                        iterable_type[StackEffect([a_bar], [b_bar]), ],
                        StackEffect([a_bar], [b_bar]),
                    ]),
                    set(),
                )
                assert b_bar in phi
                current_subs, current_effect = (
                    phi(S),
                    phi(StackEffect(i, [b_bar])),
                )
            elif isinstance(node, concat.parse.DictWordNode):
                phi = current_subs
                collected_type = current_effect.output
                for key, value in node.dict_children:
                    phi1, (i1, o1) = infer(
                        phi(gamma),
                        key,
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=collected_type,
                    )
                    phi = phi1(phi)
                    collected_type = phi(o1)
                    # drop the top of the stack to use as the key
                    collected_type, key_type = (
                        collected_type[:-1],
                        collected_type.as_sequence()[-1],
                    )
                    phi2, (i2, o2) = infer(
                        phi(gamma),
                        value,
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=collected_type,
                    )
                    phi = phi2(phi)
                    collected_type = phi(o2)
                    # drop the top of the stack to use as the value
                    collected_type, value_type = (
                        collected_type[:-1],
                        collected_type.as_sequence()[-1],
                    )
                current_subs, current_effect = (
                    phi,
                    phi(
                        StackEffect(current_effect.input,
                                    [*collected_type, dict_type])),
                )
            elif isinstance(node, concat.parse.ListWordNode):
                phi = S
                collected_type = TypeSequence(o)
                element_type: IndividualType = object_type
                for item in node.list_children:
                    phi1, fun_type = infer(
                        phi(gamma),
                        item,
                        extensions=extensions,
                        source_dir=source_dir,
                        initial_stack=collected_type,
                    )
                    collected_type = fun_type.output
                    # FIXME: Infer the type of elements in the list based on
                    # ALL the elements.
                    if element_type == object_type:
                        assert isinstance(collected_type[-1], IndividualType)
                        element_type = collected_type[-1]
                    # drop the top of the stack to use as the item
                    collected_type = collected_type[:-1]
                    phi = phi1(phi)
                current_subs, current_effect = (
                    phi,
                    phi(
                        StackEffect(
                            i, [*collected_type, list_type[element_type, ]])),
                )
            elif isinstance(node, concat.operators.InvertWordNode):
                out_types = current_effect.output[:-1]
                invert_attr_type = current_effect.output[
                    -1].get_type_of_attribute('__invert__')
                if not isinstance(invert_attr_type, PythonFunctionType):
                    raise TypeError(
                        '__invert__ of type {} must be a Python function'.
                        format(current_effect.output[-1]))
                result_type = invert_attr_type.output
                current_effect = StackEffect(current_effect.input,
                                             [*out_types, result_type])
            elif isinstance(node, concat.parse.NoneWordNode):
                current_effect = StackEffect(i, [*o, none_type])
            elif isinstance(node, concat.parse.NotImplWordNode):
                current_effect = StackEffect(i, [*o, not_implemented_type])
            elif isinstance(node, concat.parse.EllipsisWordNode):
                current_effect = StackEffect(i, [*o, ellipsis_type])
            elif isinstance(node, concat.parse.SliceWordNode):
                sliceable_object_type = o[-1]
                # This doesn't match the evaluation order used by the
                # transpiler.
                # FIXME: Change the transpiler to fit the type checker.
                sub1, start_effect = infer(
                    gamma,
                    list(node.start_children),
                    source_dir=source_dir,
                    initial_stack=TypeSequence(o[:-1]),
                )
                start_type = start_effect.output[-1]
                o = tuple(start_effect.output[:-1])
                sub2, stop_effect = infer(
                    sub1(gamma),
                    list(node.stop_children),
                    source_dir=source_dir,
                    initial_stack=TypeSequence(o),
                )
                stop_type = stop_effect.output[-1]
                o = tuple(stop_effect.output[:-1])
                sub3, step_effect = infer(
                    sub2(sub1(gamma)),
                    list(node.step_children),
                    source_dir=source_dir,
                    initial_stack=TypeSequence(o),
                )
                step_type = step_effect.output[-1]
                o = tuple(step_effect.output[:-1])
                this_slice_type = slice_type[start_type, stop_type, step_type]
                getitem_type = sliceable_object_type.get_type_of_attribute(
                    '__getitem__')
                getitem_type = getitem_type.get_type_of_attribute('__call__')
                getitem_type = getitem_type.instantiate()
                if (not isinstance(getitem_type, PythonFunctionType)
                        or len(getitem_type.input) != 1):
                    raise TypeError(
                        '__getitem__ method of {} has incorrect type {}'.
                        format(node, getitem_type))
                getitem_type, overload_subs = getitem_type.select_overload(
                    (this_slice_type, ))
                result_type = getitem_type.output
                current_subs = overload_subs(sub3(sub2(sub1(current_subs))))
                current_effect = overload_subs(
                    StackEffect(i, [*o, result_type]))
            elif isinstance(node, concat.parse.FromImportStatementNode):
                imported_name = node.asname or node.imported_name
                # mutate type environment
                gamma[imported_name] = object_type
                # We will try to find a more specific type.
                sys.path, old_path = [source_dir, *sys.path], sys.path
                module = importlib.import_module(node.value)
                sys.path = old_path
                # For now, assume the module's written in Python.
                try:
                    gamma[imported_name] = current_subs(
                        getattr(module, '@@types')[node.imported_name])
                except (KeyError, builtins.AttributeError):
                    # attempt introspection to get a more specific type
                    if callable(getattr(module, node.imported_name)):
                        args_var = SequenceVariable()
                        gamma[imported_name] = ObjectType(
                            IndividualVariable(),
                            {
                                '__call__':
                                py_function_type[TypeSequence([args_var]),
                                                 object_type],
                            },
                            type_parameters=[args_var],
                            nominal=True,
                        )
            elif isinstance(node, concat.parse.ImportStatementNode):
                # TODO: Support all types of import correctly.
                if node.asname is not None:
                    gamma[node.asname] = current_subs(
                        _generate_type_of_innermost_module(
                            node.value,
                            source_dir).generalized_wrt(current_subs(gamma)))
                else:
                    imported_name = node.value
                    # mutate type environment
                    components = node.value.split('.')
                    # FIXME: This replaces whatever was previously imported. I really
                    # should implement namespaces properly.
                    gamma[components[0]] = current_subs(
                        _generate_module_type(components,
                                              source_dir=source_dir))
            elif isinstance(node, concat.parse.SubscriptionWordNode):
                seq = current_effect.output[:-1]
                index_type_var = IndividualVariable()
                result_type_var = IndividualVariable()
                subscriptable_interface = subscriptable_type[index_type_var,
                                                             result_type_var]
                (
                    index_subs,
                    (index_input, index_output),
                ) = infer(
                    gamma,
                    node.children,
                    initial_stack=current_effect.output,
                    extensions=extensions,
                )
                index_output_typeseq = index_output
                subs = index_output_typeseq[
                    -2].constrain_and_bind_supertype_variables(
                        subscriptable_interface,
                        set(),
                    )(index_subs(current_subs))
                subs(index_output_typeseq[-1]).constrain(subs(index_type_var))

                result_type = subs(result_type_var).get_type_of_attribute(
                    '__call__')
                if not isinstance(result_type, StackEffect):
                    raise TypeError(
                        'result of subscription is not callable as a Concat function (has type {})'
                        .format(result_type))
                subs = index_output_typeseq[:
                                            -2].constrain_and_bind_supertype_variables(
                                                result_type.input, set())(subs)

                current_subs, current_effect = (
                    subs,
                    subs(StackEffect(current_effect.input,
                                     result_type.output)),
                )
            elif isinstance(node, concat.operators.SubtractWordNode):
                # FIXME: We should check if the other operand supports __rsub__ if the
                # first operand doesn't support __sub__.
                other_operand_type_var = IndividualVariable()
                result_type_var = IndividualVariable()
                subtractable_interface = subtractable_type[
                    other_operand_type_var, result_type_var]
                seq_var = SequenceVariable()
                final_subs = current_effect.output.constrain_and_bind_supertype_variables(
                    TypeSequence([
                        seq_var,
                        subtractable_interface,
                        other_operand_type_var,
                    ]),
                    set(),
                )
                assert seq_var in final_subs
                current_subs, current_effect = (
                    final_subs(current_subs),
                    final_subs(
                        StackEffect(current_effect.input,
                                    [seq_var, result_type_var])),
                )
            elif isinstance(node, concat.parse.FuncdefStatementNode):
                S = current_subs
                f = current_effect
                name = node.name
                declared_type: Optional[StackEffect]
                if node.stack_effect:
                    declared_type, _ = node.stack_effect.to_type(S(gamma))
                    declared_type = S(declared_type)
                else:
                    # NOTE: To continue the "bidirectional" bent, we will require a
                    # type annotation.
                    # TODO: Make the return types optional?
                    # FIXME: Should be a parse error.
                    raise TypeError(
                        'must have type annotation on function definition')
                recursion_env = gamma.copy()
                recursion_env[name] = declared_type.generalized_wrt(S(gamma))
                phi1, inferred_type = infer(
                    S(recursion_env),
                    node.body,
                    is_top_level=False,
                    extensions=extensions,
                    initial_stack=declared_type.input,
                )
                # We want to check that the inferred outputs are subtypes of
                # the declared outputs. Thus, inferred_type.output should be a subtype
                # declared_type.output.
                try:
                    inferred_type.output.constrain(declared_type.output)
                except TypeError:
                    message = (
                        'declared function type {} is not compatible with '
                        'inferred type {}')
                    raise TypeError(
                        message.format(declared_type, inferred_type))
                effect = declared_type
                # we *mutate* the type environment
                gamma[name] = effect.generalized_wrt(S(gamma))
            elif isinstance(node,
                            concat.operators.GreaterThanOrEqualToWordNode):
                a_type, b_type = current_effect.output[-2:]
                try:
                    ge_type = a_type.get_type_of_attribute('__ge__')
                    if not isinstance(ge_type, PythonFunctionType):
                        raise TypeError(
                            'method __ge__ of type {} should be a Python function'
                            .format(ge_type))
                    _, current_subs = ge_type.select_overload([b_type])
                except TypeError:
                    le_type = b_type.get_type_of_attribute('__le__')
                    if not isinstance(le_type, PythonFunctionType):
                        raise TypeError(
                            'method __le__ of type {} should be a Python function'
                            .format(le_type))
                    _, current_subs = le_type.select_overload([a_type])
                current_subs, current_effect = (
                    current_subs,
                    StackEffect(
                        current_effect.input,
                        TypeSequence([*current_effect.output[:-2], bool_type]),
                    ),
                )
            elif isinstance(
                    node,
                (
                    concat.operators.IsWordNode,
                    concat.operators.AndWordNode,
                    concat.operators.OrWordNode,
                    concat.operators.EqualToWordNode,
                ),
            ):
                # TODO: I should be more careful here, since at least __eq__ can be
                # deleted, if I remember correctly.
                if not isinstance(current_effect.output[-1],
                                  IndividualType) or not isinstance(
                                      current_effect.output[-2],
                                      IndividualType):
                    raise StackMismatchError(
                        TypeSequence(current_effect.output),
                        TypeSequence([object_type, object_type]),
                    )
                current_effect = StackEffect(
                    current_effect.input,
                    TypeSequence([*current_effect.output[:-2], bool_type]),
                )
            elif isinstance(node, concat.parse.NumberWordNode):
                if isinstance(node.value, int):
                    current_effect = StackEffect(i, [*o, int_type])
                else:
                    raise UnhandledNodeTypeError
            elif isinstance(node, concat.parse.NameWordNode):
                (i1, o1) = current_effect
                if node.value not in current_subs(gamma):
                    raise NameError(node)
                type_of_name = current_subs(gamma)[node.value].instantiate()
                type_of_name = type_of_name.get_type_of_attribute('__call__')
                if not isinstance(type_of_name, StackEffect):
                    raise UnhandledNodeTypeError(
                        'name {} of type {} (repr {!r})'.format(
                            node.value, type_of_name, type_of_name))
                constraint_subs = o1.constrain_and_bind_supertype_variables(
                    type_of_name.input, set())
                current_subs = constraint_subs(current_subs)
                current_effect = current_subs(
                    StackEffect(i1, type_of_name.output))
            elif isinstance(node, concat.parse.QuoteWordNode):
                quotation = cast(concat.parse.QuoteWordNode, node)
                # make sure any annotation matches the current stack
                if quotation.input_stack_type is not None:
                    input_stack, _ = quotation.input_stack_type.to_type(gamma)
                    S = TypeSequence(o).constrain_and_bind_supertype_variables(
                        input_stack,
                        set(),
                    )(S)
                else:
                    input_stack = TypeSequence(o)
                S1, (i1, o1) = infer(
                    gamma,
                    [*quotation.children],
                    extensions=extensions,
                    source_dir=source_dir,
                    initial_stack=input_stack,
                )
                current_subs, current_effect = (
                    S1(S),
                    S1(StackEffect(i, o1)),
                )
            elif isinstance(node, concat.parse.StringWordNode):
                current_subs, current_effect = (
                    S,
                    StackEffect(
                        current_effect.input,
                        [*current_effect.output, str_type],
                    ),
                )
            elif isinstance(node, concat.parse.AttributeWordNode):
                stack_top_type = o[-1]
                out_types = o[:-1]
                attr_function_type = stack_top_type.get_type_of_attribute(
                    node.value).instantiate()
                if not isinstance(attr_function_type, StackEffect):
                    raise UnhandledNodeTypeError(
                        'attribute {} of type {} (repr {!r})'.format(
                            node.value, attr_function_type,
                            attr_function_type))
                R = TypeSequence(
                    out_types).constrain_and_bind_supertype_variables(
                        attr_function_type.input,
                        set(),
                    )
                current_subs, current_effect = (
                    R(S),
                    R(StackEffect(i, attr_function_type.output)),
                )
            elif isinstance(node, concat.parse.CastWordNode):
                new_type, _ = node.type.to_type(gamma)
                rest = current_effect.output[:-1]
                current_effect = current_subs(
                    StackEffect(current_effect.input, [*rest, new_type]))
            else:
                raise UnhandledNodeTypeError(
                    "don't know how to handle '{}'".format(node))
        except TypeError as e:
            e.set_location_if_missing(node.location)
            raise
    return current_subs, current_effect
Exemple #17
0
    Mapping,
    Optional,
    Sequence,
    Sized,
    Union,
    cast,
)


_stack_type_var = SequenceVariable()
_rest_var = SequenceVariable()
_rest_var_2 = SequenceVariable()
_rest_var_3 = SequenceVariable()
globals()['@@types'] = {
    'to_str': StackEffect(
        TypeSequence([_stack_type_var, object_type, object_type, object_type]),
        TypeSequence([_stack_type_var, str_type]),
    ),
    'to_py_function': ForAll(
        [_rest_var, _rest_var_2, _rest_var_3],
        StackEffect(
            [_rest_var, StackEffect([_rest_var_2], [_rest_var_3])],
            [_rest_var, py_function_type],
        ),
    ),
}


def to_py_function(stack: List[object], stash: List[object]) -> None:
    func = cast(Callable[[List[object], List[object]], None], stack.pop())

    def py_func(*args: object) -> object:
Exemple #18
0
_seq_var = SequenceVariable()
_stack_var = SequenceVariable()
_stack_type_var = SequenceVariable()
_a_var = IndividualVariable()
_b_var = IndividualVariable()
_c_var = IndividualVariable()
_x = IndividualVariable()

types = {
    'py_call': ForAll(
        [_rest_var, _seq_var, _a_var],
        StackEffect(
            [
                _rest_var,
                iterable_type[object_type,],
                iterable_type[object_type,],
                py_function_type[TypeSequence([_seq_var]), _a_var],
            ],
            [_rest_var, _a_var],
        ),
    ),
    'swap': ForAll(
        [_rest_var, _a_var, _b_var],
        StackEffect([_rest_var, _a_var, _b_var], [_rest_var, _b_var, _a_var]),
    ),
    'pick': ForAll(
        [_rest_var, _a_var, _b_var, _c_var],
        StackEffect(
            [_rest_var, _a_var, _b_var, _c_var],
            [_rest_var, _a_var, _b_var, _c_var, _a_var],
        ),