Beispiel #1
0
    def visit_assignment(self, target, value, is_aug_assign=False):
        value_region  = self._region_of(value) if not is_aug_assign else self.youngest_region

        # If this is a variable, we might need to contract the live range.
        if isinstance(value_region, Region):
            for name in self._names_of(target):
                region = self._region_of(name)
                if isinstance(region, Region):
                    region.contract(value_region)

        # If we assign to an attribute of a quoted value, there will be no names
        # in the assignment lhs.
        target_names   = self._names_of(target) or []

        # The assigned value should outlive the assignee
        target_regions = [self._region_of(name) for name in target_names]
        for target_region in target_regions:
            if not Region.outlives(value_region, target_region):
                if is_aug_assign:
                    target_desc = "the assignment target, allocated here,"
                else:
                    target_desc = "the assignment target"
                note = diagnostic.Diagnostic("note",
                    "this expression has type {type}",
                    {"type": types.TypePrinter().name(value.type)},
                    value.loc)
                diag = diagnostic.Diagnostic("error",
                    "the assigned value does not outlive the assignment target", {},
                    value.loc, [target.loc],
                    notes=self._diagnostics_for(target_region, target.loc,
                                                target_desc) +
                          self._diagnostics_for(value_region, value.loc,
                                                "the assigned value"))
                self.engine.process(diag)
Beispiel #2
0
 def _diagnostics_for(self,
                      region,
                      loc,
                      descr="the value of the expression"):
     if isinstance(region, Region):
         return [
             diagnostic.Diagnostic("note",
                                   "{descr} is alive from this point...",
                                   {"descr": descr}, region.range.begin()),
             diagnostic.Diagnostic("note", "... to this point", {},
                                   region.range.end())
         ]
     elif isinstance(region, Global):
         return [
             diagnostic.Diagnostic("note", "{descr} is alive forever",
                                   {"descr": descr}, loc)
         ]
     elif isinstance(region, Argument):
         return [
             diagnostic.Diagnostic(
                 "note",
                 "{descr} is still alive after this function returns",
                 {"descr": descr}, loc),
             diagnostic.Diagnostic(
                 "note", "{descr} is introduced here as a formal argument",
                 {"descr": descr}, region.loc)
         ]
     else:
         assert False
Beispiel #3
0
    def visit_Name(self, node):
        typ = super()._try_find_name(node.id)
        if typ is not None:
            # Value from device environment.
            return asttyped.NameT(type=typ, id=node.id, ctx=node.ctx,
                                  loc=node.loc)
        else:
            # Try to find this value in the host environment and quote it.
            if node.id == "print":
                return self.quote(print, node.loc)
            elif node.id in self.host_environment:
                return self.quote(self.host_environment[node.id], node.loc)
            else:
                names = set()
                names.update(self.host_environment.keys())
                for typing_env in reversed(self.env_stack):
                    names.update(typing_env.keys())

                suggestion = suggest_identifier(node.id, names)
                if suggestion is not None:
                    diag = diagnostic.Diagnostic("fatal",
                        "name '{name}' is not bound to anything; did you mean '{suggestion}'?",
                        {"name": node.id, "suggestion": suggestion},
                        node.loc)
                    self.engine.process(diag)
                else:
                    diag = diagnostic.Diagnostic("fatal",
                        "name '{name}' is not bound to anything", {"name": node.id},
                        node.loc)
                    self.engine.process(diag)
    def visit_ClassDef(self, node):
        if any(node.bases) or any(node.keywords) or \
                node.starargs is not None or node.kwargs is not None:
            diag = diagnostic.Diagnostic("error",
                "inheritance is not supported", {},
                node.lparen_loc.join(node.rparen_loc))
            self.engine.process(diag)

        for child in node.body:
            if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)):
                continue

            diag = diagnostic.Diagnostic("fatal",
                "class body must contain only assignments and function definitions", {},
                child.loc)
            self.engine.process(diag)

        if node.name in self.env_stack[-1]:
            diag = diagnostic.Diagnostic("fatal",
                "variable '{name}' is already defined", {"name":node.name}, node.name_loc)
            self.engine.process(diag)

        extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
        extractor.visit(node)

        # Now we create two types.
        # The first type is the type of instances created by the constructor.
        # Its attributes are those of the class environment, but wrapped
        # appropriately so that they are linked to the class from which they
        # originate.
        instance_type = types.TInstance(node.name, OrderedDict())

        # The second type is the type of the constructor itself (in other words,
        # the class object): it is simply a singleton type that has the class
        # environment as attributes.
        constructor_type = types.TConstructor(instance_type)
        constructor_type.attributes = extractor.typing_env
        instance_type.constructor = constructor_type

        self.env_stack[-1][node.name] = constructor_type

        node = asttyped.ClassDefT(
            constructor_type=constructor_type,
            name=node.name,
            bases=self.visit(node.bases), keywords=self.visit(node.keywords),
            starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs),
            body=node.body,
            decorator_list=self.visit(node.decorator_list),
            keyword_loc=node.keyword_loc, name_loc=node.name_loc,
            lparen_loc=node.lparen_loc, star_loc=node.star_loc,
            dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc,
            colon_loc=node.colon_loc, at_locs=node.at_locs,
            loc=node.loc)

        try:
            old_in_class, self.in_class = self.in_class, node
            return self.generic_visit(node)
        finally:
            self.in_class = old_in_class
Beispiel #5
0
 def visit_Return(self, node):
     region = self._region_of(node.value)
     if isinstance(region, Region):
         note = diagnostic.Diagnostic("note",
             "this expression has type {type}",
             {"type": types.TypePrinter().name(node.value.type)},
             node.value.loc)
         diag = diagnostic.Diagnostic("error",
             "cannot return an allocated value that does not live forever", {},
             node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note])
         self.engine.process(diag)
Beispiel #6
0
    def _quote_function(self, function, loc):
        if isinstance(function, SpecializedFunction):
            host_function = function.host_function
        else:
            host_function = function

        if function in self.functions:
            pass
        elif not hasattr(host_function, "artiq_embedded") or \
                (host_function.artiq_embedded.core_name is None and
                 host_function.artiq_embedded.portable is False and
                 host_function.artiq_embedded.syscall is None and
                 host_function.artiq_embedded.forbidden is False):
            self._quote_rpc(function, loc)
        elif host_function.artiq_embedded.function is not None:
            if host_function.__name__ == "<lambda>":
                note = diagnostic.Diagnostic("note",
                    "lambda created here", {},
                    self._function_loc(host_function.artiq_embedded.function))
                diag = diagnostic.Diagnostic("fatal",
                    "lambdas cannot be used as kernel functions", {},
                    loc,
                    notes=[note])
                self.engine.process(diag)

            core_name = host_function.artiq_embedded.core_name
            if core_name is not None and self.dmgr.get(core_name) != self.core:
                note = diagnostic.Diagnostic("note",
                    "called from this function", {},
                    loc)
                diag = diagnostic.Diagnostic("fatal",
                    "this function runs on a different core device '{name}'",
                    {"name": host_function.artiq_embedded.core_name},
                    self._function_loc(host_function.artiq_embedded.function),
                    notes=[note])
                self.engine.process(diag)

            self._quote_embedded_function(function,
                                          flags=host_function.artiq_embedded.flags)
        elif host_function.artiq_embedded.syscall is not None:
            # Insert a storage-less global whose type instructs the compiler
            # to perform a system call instead of a regular call.
            self._quote_syscall(function, loc)
        elif host_function.artiq_embedded.forbidden is not None:
            diag = diagnostic.Diagnostic("fatal",
                "this function cannot be called as an RPC", {},
                self._function_loc(host_function),
                notes=self._call_site_note(loc, fn_kind='rpc'))
            self.engine.process(diag)
        else:
            assert False

        return self.functions[function]
Beispiel #7
0
 def _call_site_note(self, call_loc, is_syscall):
     if call_loc:
         if is_syscall:
             return [diagnostic.Diagnostic("note",
                 "in system call here", {},
                 call_loc)]
         else:
             return [diagnostic.Diagnostic("note",
                 "in function called remotely here", {},
                 call_loc)]
     else:
         return []
Beispiel #8
0
    def generic_visit(self, node):
        super().generic_visit(node)

        if isinstance(node, asttyped.commontyped):
            if types.is_polymorphic(node.type):
                note = diagnostic.Diagnostic("note",
                    "the expression has type {type}",
                    {"type": types.TypePrinter().name(node.type)},
                    node.loc)
                diag = diagnostic.Diagnostic("error",
                    "the type of this expression cannot be fully inferred", {},
                    node.loc, notes=[note])
                self.engine.process(diag)
Beispiel #9
0
    def visit_FunctionDefT(self, node):
        super().generic_visit(node)

        return_type = node.signature_type.find().ret
        if types.is_polymorphic(return_type):
            note = diagnostic.Diagnostic("note",
                "the function has return type {type}",
                {"type": types.TypePrinter().name(return_type)},
                node.name_loc)
            diag = diagnostic.Diagnostic("error",
                "the return type of this function cannot be fully inferred", {},
                node.name_loc, notes=[note])
            self.engine.process(diag)
Beispiel #10
0
    def _quote_syscall(self, function, loc):
        signature = inspect.signature(function)

        arg_types = OrderedDict()
        optarg_types = OrderedDict()
        for param in signature.parameters.values():
            if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
                diag = diagnostic.Diagnostic(
                    "error",
                    "system calls must only use positional arguments; '{argument}' isn't",
                    {"argument": param.name},
                    self._function_loc(function),
                    notes=self._call_site_note(loc, is_syscall=True))
                self.engine.process(diag)

            if param.default is inspect.Parameter.empty:
                arg_types[param.name] = self._type_of_param(function,
                                                            loc,
                                                            param,
                                                            is_syscall=True)
            else:
                diag = diagnostic.Diagnostic(
                    "error",
                    "system call argument '{argument}' must not have a default value",
                    {"argument": param.name},
                    self._function_loc(function),
                    notes=self._call_site_note(loc, is_syscall=True))
                self.engine.process(diag)

        if signature.return_annotation is not inspect.Signature.empty:
            ret_type = self._extract_annot(function,
                                           signature.return_annotation,
                                           "return type",
                                           loc,
                                           is_syscall=True)
        else:
            diag = diagnostic.Diagnostic(
                "error",
                "system call must have a return type annotation", {},
                self._function_loc(function),
                notes=self._call_site_note(loc, is_syscall=True))
            self.engine.process(diag)
            ret_type = types.TVar()

        function_type = types.TCFunction(arg_types,
                                         ret_type,
                                         name=function.artiq_embedded.syscall,
                                         flags=function.artiq_embedded.flags)
        self.functions[function] = function_type
        return function_type
Beispiel #11
0
    def _uninitialized_access(self, insn, var_name, pred_at_fault):
        if pred_at_fault is not None:
            visited = set()
            possible_preds = [pred_at_fault]

            uninitialized_loc = None
            while uninitialized_loc is None:
                possible_pred = possible_preds.pop(0)
                visited.add(possible_pred)

                for pred_insn in reversed(possible_pred.instructions):
                    if pred_insn.loc is not None:
                        uninitialized_loc = pred_insn.loc.begin()
                        break

                for block in possible_pred.predecessors():
                    if block not in visited:
                        possible_preds.append(block)

            assert uninitialized_loc is not None

            note = diagnostic.Diagnostic(
                "note",
                "variable is not initialized when control flows from this point",
                {}, uninitialized_loc)
        else:
            note = None

        if note is not None:
            notes = [note]
        else:
            notes = []

        if isinstance(insn, ir.Closure):
            diag = diagnostic.Diagnostic(
                "error",
                "variable '{name}' can be captured in a closure uninitialized here",
                {"name": var_name},
                insn.loc,
                notes=notes)
        else:
            diag = diagnostic.Diagnostic(
                "error",
                "variable '{name}' is not always initialized here",
                {"name": var_name},
                insn.loc,
                notes=notes)

        self.engine.process(diag)
Beispiel #12
0
    def visit_AugAssign(self, node):
        if builtins.is_list(node.target.type):
            note = diagnostic.Diagnostic("note",
                "try using `{lhs} = {lhs} {op} {rhs}` instead",
                {"lhs": node.target.loc.source(),
                 "rhs": node.value.loc.source(),
                 "op": node.op.loc.source()[:-1]},
                node.loc)
            diag = diagnostic.Diagnostic("error",
                "lists cannot be mutated in-place", {},
                node.op.loc, [node.target.loc],
                notes=[note])
            self.engine.process(diag)

        self.visit_assignment(node.target, node.value)
Beispiel #13
0
    def visit_assignment(self, target, value):
        value_region = self._region_of(value)

        # If we assign to an attribute of a quoted value, there will be no names
        # in the assignment lhs.
        target_names = self._names_of(target) or []

        # Adopt the value region for any variables declared on the lhs.
        for name in target_names:
            region = self._region_of(name)
            if isinstance(region, Region) and not region.present():
                # Find the name's environment to overwrite the region.
                for env in self.env_stack[::-1]:
                    if name.id in env:
                        env[name.id] = value_region
                        break

        # The assigned value should outlive the assignee
        target_regions = [self._region_of(name) for name in target_names]
        for target_region in target_regions:
            if not Region.outlives(value_region, target_region):
                diag = diagnostic.Diagnostic(
                    "error",
                    "the assigned value does not outlive the assignment target",
                    {},
                    value.loc, [target.loc],
                    notes=self._diagnostics_for(target_region, target.loc,
                                                "the assignment target") +
                    self._diagnostics_for(value_region, value.loc,
                                          "the assigned value"))
                self.engine.process(diag)
Beispiel #14
0
    def visit_assignment(self, target, value):
        value_region  = self._region_of(value)

        # If this is a variable, we might need to contract the live range.
        if isinstance(value_region, Region):
            for name in self._names_of(target):
                region = self._region_of(name)
                if isinstance(region, Region):
                    region.contract(value_region)

        # If we assign to an attribute of a quoted value, there will be no names
        # in the assignment lhs.
        target_names   = self._names_of(target) or []

        # The assigned value should outlive the assignee
        target_regions = [self._region_of(name) for name in target_names]
        for target_region in target_regions:
            if not Region.outlives(value_region, target_region):
                diag = diagnostic.Diagnostic("error",
                    "the assigned value does not outlive the assignment target", {},
                    value.loc, [target.loc],
                    notes=self._diagnostics_for(target_region, target.loc,
                                                "the assignment target") +
                          self._diagnostics_for(value_region, value.loc,
                                                "the assigned value"))
                self.engine.process(diag)
 def _check_not_in(self, name, names, curkind, newkind, loc):
     if name in names:
         diag = diagnostic.Diagnostic("error",
             "name '{name}' cannot be {curkind} and {newkind} simultaneously",
             {"name": name, "curkind": curkind, "newkind": newkind}, loc)
         self.engine.process(diag)
         return True
     return False
Beispiel #16
0
 def visit_Raise(self, node):
     node = self.generic_visit(node)
     if node.cause:
         diag = diagnostic.Diagnostic(
             "error", "'raise from' syntax is not supported", {},
             node.from_loc)
         self.engine.process(diag)
     return node
Beispiel #17
0
            def proxy_diagnostic(diag):
                note = diagnostic.Diagnostic(
                    "note",
                    "while inferring a type for an attribute '{attr}' of a host object",
                    {"attr": attr_name}, loc)
                diag.notes.append(note)

                self.engine.process(diag)
Beispiel #18
0
    def _find_name(self, name, loc):
        typ = self._try_find_name(name)
        if typ is not None:
            return typ

        diag = diagnostic.Diagnostic("fatal", "undefined variable '{name}'",
                                     {"name": name}, loc)
        self.engine.process(diag)
Beispiel #19
0
 def visit_arg(self, node):
     if node.arg in self.params:
         diag = diagnostic.Diagnostic("error",
                                      "duplicate parameter '{name}'",
                                      {"name": node.arg}, node.loc)
         self.engine.process(diag)
         return
     self._assignable(node.arg)
     self.params.add(node.arg)
Beispiel #20
0
 def _call_site_note(self, call_loc, fn_kind):
     if call_loc:
         if fn_kind == 'syscall':
             return [diagnostic.Diagnostic("note",
                 "in system call here", {},
                 call_loc)]
         elif fn_kind == 'rpc':
             return [diagnostic.Diagnostic("note",
                 "in function called remotely here", {},
                 call_loc)]
         elif fn_kind == 'kernel':
             return [diagnostic.Diagnostic("note",
                 "in kernel function here", {},
                 call_loc)]
         else:
             assert False
     else:
         return []
    def visit_arg(self, node):
        if node.annotation is not None:
            diag = diagnostic.Diagnostic("fatal",
                "type annotations are not supported here", {},
                node.annotation.loc)
            self.engine.process(diag)

        return asttyped.argT(type=self._find_name(node.arg, node.loc),
                             arg=node.arg, annotation=None,
                             arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
Beispiel #22
0
    def visit_SubscriptT(self, node):
        old_in_assign, self.in_assign = self.in_assign, False
        self.visit(node.value)
        self.visit(node.slice)
        self.in_assign = old_in_assign

        if self.in_assign and builtins.is_bytes(node.value.type):
            diag = diagnostic.Diagnostic("error", "type {typ} is not mutable",
                                         {"typ": "bytes"}, node.loc)
            self.engine.process(diag)
Beispiel #23
0
    def _quote_rpc(self, function, loc):
        if isinstance(function, SpecializedFunction):
            host_function = function.host_function
        else:
            host_function = function
        ret_type = builtins.TNone()

        if isinstance(host_function, pytypes.BuiltinFunctionType):
            pass
        elif (isinstance(host_function, pytypes.FunctionType) or \
              isinstance(host_function, pytypes.MethodType)):
            if isinstance(host_function, pytypes.FunctionType):
                signature = inspect.signature(host_function)
            else:
                # inspect bug?
                signature = inspect.signature(host_function.__func__)
            if signature.return_annotation is not inspect.Signature.empty:
                ret_type = self._extract_annot(host_function, signature.return_annotation,
                                               "return type", loc, fn_kind='rpc')
        else:
            assert False

        is_async = False
        if hasattr(host_function, "artiq_embedded") and \
                "async" in host_function.artiq_embedded.flags:
            is_async = True

        if not builtins.is_none(ret_type) and is_async:
            note = diagnostic.Diagnostic("note",
                "function called here", {},
                loc)
            diag = diagnostic.Diagnostic("fatal",
                "functions that return a value cannot be defined as async RPCs", {},
                self._function_loc(host_function.artiq_embedded.function),
                notes=[note])
            self.engine.process(diag)

        function_type = types.TRPC(ret_type,
                                   service=self.embedding_map.store_object(host_function),
                                   async=is_async)
        self.functions[function] = function_type
        return function_type
Beispiel #24
0
 def visit_Num(self, node):
     if isinstance(node.n, int):
         typ = builtins.TInt()
     elif isinstance(node.n, float):
         typ = builtins.TFloat()
     else:
         diag = diagnostic.Diagnostic(
             "fatal", "numeric type {type} is not supported",
             {"type": node.n.__class__.__name__}, node.loc)
         self.engine.process(diag)
     return asttyped.NumT(type=typ, n=node.n, loc=node.loc)
Beispiel #25
0
    def _extract_annot(self, function, annot, kind, call_loc, fn_kind):
        if not isinstance(annot, types.Type):
            diag = diagnostic.Diagnostic("error",
                "type annotation for {kind}, '{annot}', is not an ARTIQ type",
                {"kind": kind, "annot": repr(annot)},
                self._function_loc(function),
                notes=self._call_site_note(call_loc, fn_kind))
            self.engine.process(diag)

            return types.TVar()
        else:
            return annot
Beispiel #26
0
    def finalize(self):
        inferencer = StitchingInferencer(engine=self.engine,
                                         value_map=self.value_map,
                                         quote=self._quote)
        typedtree_hasher = TypedtreeHasher()

        # Iterate inference to fixed point.
        old_typedtree_hash = None
        old_attr_count = None
        while True:
            inferencer.visit(self.typedtree)
            typedtree_hash = typedtree_hasher.visit(self.typedtree)
            attr_count = self.embedding_map.attribute_count()

            if old_typedtree_hash == typedtree_hash and old_attr_count == attr_count:
                break
            old_typedtree_hash = typedtree_hash
            old_attr_count = attr_count

        # After we've discovered every referenced attribute, check if any kernel_invariant
        # specifications refers to ones we didn't encounter.
        for host_type in self.embedding_map.type_map:
            instance_type, constructor_type = self.embedding_map.type_map[host_type]
            if not hasattr(instance_type, "constant_attributes"):
                # Exceptions lack user-definable attributes.
                continue

            for attribute in instance_type.constant_attributes:
                if attribute in instance_type.attributes:
                    # Fast path; if the ARTIQ Python type has the attribute, then every observed
                    # value is guaranteed to have it too.
                    continue

                for value, loc in self.value_map[instance_type]:
                    if hasattr(value, attribute):
                        continue

                    diag = diagnostic.Diagnostic("warning",
                        "object {value} of type {typ} declares attribute '{attr}' as "
                        "kernel invariant, but the instance referenced here does not "
                        "have this attribute",
                        {"value": repr(value),
                         "typ": types.TypePrinter().name(instance_type, max_depth=0),
                         "attr": attribute},
                        loc)
                    self.engine.process(diag)

        # After we have found all functions, synthesize a module to hold them.
        source_buffer = source.Buffer("", "<synthesized>")
        self.typedtree = asttyped.ModuleT(
            typing_env=self.globals, globals_in_scope=set(),
            body=self.typedtree, loc=source.Range(source_buffer, 0, 0))
Beispiel #27
0
            def proxy_diagnostic(diag):
                note = diagnostic.Diagnostic(
                    "note",
                    "expanded from here while trying to infer a type for an"
                    " unannotated optional argument '{argument}' from its default value",
                    {"argument": param.name}, self._function_loc(function))
                diag.notes.append(note)

                note = self._call_site_note(loc, is_syscall)
                if note:
                    diag.notes += note

                self.engine.process(diag)
Beispiel #28
0
 def visit_AttributeT(self, node):
     self.generic_visit(node)
     if self.in_assign:
         typ = node.value.type.find()
         if types.is_instance(typ) and node.attr in typ.constant_attributes:
             diag = diagnostic.Diagnostic(
                 "error",
                 "cannot assign to constant attribute '{attr}' of class '{class}'",
                 {
                     "attr": node.attr,
                     "class": typ.name
                 }, node.loc)
             self.engine.process(diag)
             return
Beispiel #29
0
    def visit_AttributeT(self, node):
        old_in_assign, self.in_assign = self.in_assign, False
        self.visit(node.value)
        self.in_assign = old_in_assign

        if self.in_assign:
            typ = node.value.type.find()
            if types.is_instance(typ) and node.attr in typ.constant_attributes:
                diag = diagnostic.Diagnostic(
                    "error",
                    "cannot assign to constant attribute '{attr}' of class '{class}'",
                    {
                        "attr": node.attr,
                        "class": typ.name
                    }, node.loc)
                self.engine.process(diag)
                return
            if builtins.is_array(typ):
                diag = diagnostic.Diagnostic(
                    "error", "array attributes cannot be assigned to", {},
                    node.loc)
                self.engine.process(diag)
                return
Beispiel #30
0
    def _type_of_param(self, function, loc, param, fn_kind):
        if param.annotation is not inspect.Parameter.empty:
            # Type specified explicitly.
            return self._extract_annot(function, param.annotation,
                                       "argument '{}'".format(param.name), loc,
                                       fn_kind)
        elif fn_kind == 'syscall':
            # Syscalls must be entirely annotated.
            diag = diagnostic.Diagnostic("error",
                "system call argument '{argument}' must have a type annotation",
                {"argument": param.name},
                self._function_loc(function),
                notes=self._call_site_note(loc, fn_kind))
            self.engine.process(diag)
        elif fn_kind == 'rpc' and param.default is not inspect.Parameter.empty:
            notes = []
            notes.append(diagnostic.Diagnostic("note",
                "expanded from here while trying to infer a type for an"
                " unannotated optional argument '{argument}' from its default value",
                {"argument": param.name},
                self._function_loc(function)))
            if loc is not None:
                notes.append(self._call_site_note(loc, fn_kind))

            with self.engine.context(*notes):
                # Try and infer the type from the default value.
                # This is tricky, because the default value might not have
                # a well-defined type in APython.
                # In this case, we bail out, but mention why we do it.
                ast = self._quote(param.default, None)
                Inferencer(engine=self.engine).visit(ast)
                IntMonomorphizer(engine=self.engine).visit(ast)
                return ast.type
        else:
            # Let the rest of the program decide.
            return types.TVar()