Example #1
0
 def test_object_structural_subtyping(
     self, attributes, other_attributes
 ) -> None:
     x1, x2 = IndividualVariable(), IndividualVariable()
     object1 = ObjectType(x1, {**other_attributes, **attributes})
     object2 = ObjectType(x2, attributes)
     self.assertLessEqual(object1, object2)
Example #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])
Example #3
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])
Example #4
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])
Example #5
0
 def test_object_subtype_of_py_function(self, type1, type2) -> None:
     x = IndividualVariable()
     py_function = py_function_type[TypeSequence([type1]), type2]
     object = ObjectType(x, {'__call__': py_function})
     self.assertLessEqual(object, py_function)
Example #6
0
 def test_object_subtype_of_stack_effect(self, effect) -> None:
     x = IndividualVariable()
     object = ObjectType(x, {'__call__': effect})
     self.assertLessEqual(object, effect)
Example #7
0
 '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],
     ),
 ),
 'nip': ForAll(
     [_rest_var, _a_var],
     StackEffect([_rest_var, object_type, _a_var], [_rest_var, _a_var]),
 ),
 'nip_2': ObjectType(
     _a_var,
     {
         '__call__': StackEffect(
             [_rest_var, object_type, object_type, _b_var],
             [_rest_var, _b_var],
         )
     },
     [_rest_var, _b_var],
 ),
 'drop': ForAll(
     [_rest_var], StackEffect([_rest_var, object_type], [_rest_var])
 ),
 'dup': ForAll(
     [_rest_var, _a_var],
     StackEffect([_rest_var, _a_var], [_rest_var, _a_var, _a_var]),
 ),
 'open': ForAll(
     [_rest_var],
     StackEffect([_rest_var, dict_type, str_type], [_rest_var, file_type]),
 ),
Example #8
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