Commit d3ef6b9c authored by Robert Bradshaw's avatar Robert Bradshaw

Type inference methods for expression nodes.

parent cf6abbcf
...@@ -307,6 +307,27 @@ class ExprNode(Node): ...@@ -307,6 +307,27 @@ class ExprNode(Node):
temp_bool = bool.coerce_to_temp(env) temp_bool = bool.coerce_to_temp(env)
return temp_bool return temp_bool
# --------------- Type Inference -----------------
def type_dependencies(self):
# Returns the list of entries whose types must be determined
# before the type of self can be infered.
if hasattr(self, 'type') and self.type is not None:
return ()
return sum([node.type_dependencies() for node in self.subexpr_nodes()], ())
def infer_type(self, env):
# Attempt to deduce the type of self.
# Differs from analyse_types as it avoids unnecessary
# analysis of subexpressions, but can assume everything
# in self.type_dependencies() has been resolved.
if hasattr(self, 'type') and self.type is not None:
return self.type
elif hasattr(self, 'entry') and self.entry is not None:
return self.entry.type
else:
self.not_implemented("infer_type")
# --------------- Type Analysis ------------------ # --------------- Type Analysis ------------------
def analyse_as_module(self, env): def analyse_as_module(self, env):
...@@ -858,6 +879,8 @@ class LongNode(AtomicExprNode): ...@@ -858,6 +879,8 @@ class LongNode(AtomicExprNode):
# #
# value string # value string
type = py_object_type
def calculate_constant_result(self): def calculate_constant_result(self):
self.constant_result = long(self.value) self.constant_result = long(self.value)
...@@ -865,7 +888,6 @@ class LongNode(AtomicExprNode): ...@@ -865,7 +888,6 @@ class LongNode(AtomicExprNode):
return long(self.value) return long(self.value)
def analyse_types(self, env): def analyse_types(self, env):
self.type = py_object_type
self.is_temp = 1 self.is_temp = 1
gil_message = "Constructing Python long int" gil_message = "Constructing Python long int"
...@@ -954,6 +976,9 @@ class NameNode(AtomicExprNode): ...@@ -954,6 +976,9 @@ class NameNode(AtomicExprNode):
create_analysed_rvalue = staticmethod(create_analysed_rvalue) create_analysed_rvalue = staticmethod(create_analysed_rvalue)
def type_dependencies(self):
return self.entry
def compile_time_value(self, denv): def compile_time_value(self, denv):
try: try:
return denv.lookup(self.name) return denv.lookup(self.name)
...@@ -1298,12 +1323,13 @@ class BackquoteNode(ExprNode): ...@@ -1298,12 +1323,13 @@ class BackquoteNode(ExprNode):
# #
# arg ExprNode # arg ExprNode
type = py_object_type
subexprs = ['arg'] subexprs = ['arg']
def analyse_types(self, env): def analyse_types(self, env):
self.arg.analyse_types(env) self.arg.analyse_types(env)
self.arg = self.arg.coerce_to_pyobject(env) self.arg = self.arg.coerce_to_pyobject(env)
self.type = py_object_type
self.is_temp = 1 self.is_temp = 1
gil_message = "Backquote expression" gil_message = "Backquote expression"
...@@ -1329,15 +1355,16 @@ class ImportNode(ExprNode): ...@@ -1329,15 +1355,16 @@ class ImportNode(ExprNode):
# module_name IdentifierStringNode dotted name of module # module_name IdentifierStringNode dotted name of module
# name_list ListNode or None list of names to be imported # name_list ListNode or None list of names to be imported
type = py_object_type
subexprs = ['module_name', 'name_list'] subexprs = ['module_name', 'name_list']
def analyse_types(self, env): def analyse_types(self, env):
self.module_name.analyse_types(env) self.module_name.analyse_types(env)
self.module_name = self.module_name.coerce_to_pyobject(env) self.module_name = self.module_name.coerce_to_pyobject(env)
if self.name_list: if self.name_list:
self.name_list.analyse_types(env) self.name_list.analyse_types(env)
self.name_list.coerce_to_pyobject(env) self.name_list.coerce_to_pyobject(env)
self.type = py_object_type
self.is_temp = 1 self.is_temp = 1
env.use_utility_code(import_utility_code) env.use_utility_code(import_utility_code)
...@@ -1367,12 +1394,13 @@ class IteratorNode(ExprNode): ...@@ -1367,12 +1394,13 @@ class IteratorNode(ExprNode):
# #
# sequence ExprNode # sequence ExprNode
type = py_object_type
subexprs = ['sequence'] subexprs = ['sequence']
def analyse_types(self, env): def analyse_types(self, env):
self.sequence.analyse_types(env) self.sequence.analyse_types(env)
self.sequence = self.sequence.coerce_to_pyobject(env) self.sequence = self.sequence.coerce_to_pyobject(env)
self.type = py_object_type
self.is_temp = 1 self.is_temp = 1
gil_message = "Iterating over Python object" gil_message = "Iterating over Python object"
...@@ -1424,10 +1452,11 @@ class NextNode(AtomicExprNode): ...@@ -1424,10 +1452,11 @@ class NextNode(AtomicExprNode):
# #
# iterator ExprNode # iterator ExprNode
type = py_object_type
def __init__(self, iterator, env): def __init__(self, iterator, env):
self.pos = iterator.pos self.pos = iterator.pos
self.iterator = iterator self.iterator = iterator
self.type = py_object_type
self.is_temp = 1 self.is_temp = 1
def generate_result_code(self, code): def generate_result_code(self, code):
...@@ -1480,9 +1509,10 @@ class ExcValueNode(AtomicExprNode): ...@@ -1480,9 +1509,10 @@ class ExcValueNode(AtomicExprNode):
# of an ExceptClauseNode to fetch the current # of an ExceptClauseNode to fetch the current
# exception value. # exception value.
type = py_object_type
def __init__(self, pos, env): def __init__(self, pos, env):
ExprNode.__init__(self, pos) ExprNode.__init__(self, pos)
self.type = py_object_type
def set_var(self, var): def set_var(self, var):
self.var = var self.var = var
...@@ -1598,6 +1628,19 @@ class IndexNode(ExprNode): ...@@ -1598,6 +1628,19 @@ class IndexNode(ExprNode):
return PyrexTypes.CArrayType(base_type, int(self.index.compile_time_value(env))) return PyrexTypes.CArrayType(base_type, int(self.index.compile_time_value(env)))
return None return None
def type_dependencies(self):
return self.base.type_dependencies()
def infer_type(self, env):
if isinstance(self.base, StringNode):
return py_object_type
base_type = self.base.infer_type(env)
if base_type.is_ptr or base_type.is_array:
return base_type.base_type
else:
# TODO: Handle buffers (hopefully without too much redundancy).
return py_object_type
def analyse_types(self, env): def analyse_types(self, env):
self.analyse_base_and_index_types(env, getting = 1) self.analyse_base_and_index_types(env, getting = 1)
...@@ -2106,6 +2149,9 @@ class SliceNode(ExprNode): ...@@ -2106,6 +2149,9 @@ class SliceNode(ExprNode):
# start ExprNode # start ExprNode
# stop ExprNode # stop ExprNode
# step ExprNode # step ExprNode
type = py_object_type
is_temp = 1
def calculate_constant_result(self): def calculate_constant_result(self):
self.constant_result = self.base.constant_result[ self.constant_result = self.base.constant_result[
...@@ -2137,8 +2183,6 @@ class SliceNode(ExprNode): ...@@ -2137,8 +2183,6 @@ class SliceNode(ExprNode):
self.start = self.start.coerce_to_pyobject(env) self.start = self.start.coerce_to_pyobject(env)
self.stop = self.stop.coerce_to_pyobject(env) self.stop = self.stop.coerce_to_pyobject(env)
self.step = self.step.coerce_to_pyobject(env) self.step = self.step.coerce_to_pyobject(env)
self.type = py_object_type
self.is_temp = 1
gil_message = "Constructing Python slice object" gil_message = "Constructing Python slice object"
...@@ -2154,6 +2198,7 @@ class SliceNode(ExprNode): ...@@ -2154,6 +2198,7 @@ class SliceNode(ExprNode):
class CallNode(ExprNode): class CallNode(ExprNode):
def analyse_as_type_constructor(self, env): def analyse_as_type_constructor(self, env):
type = self.function.analyse_as_type(env) type = self.function.analyse_as_type(env)
if type and type.is_struct_or_union: if type and type.is_struct_or_union:
...@@ -2206,6 +2251,20 @@ class SimpleCallNode(CallNode): ...@@ -2206,6 +2251,20 @@ class SimpleCallNode(CallNode):
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
def type_dependencies(self):
# TODO: Update when Danilo's C++ code merged in to handle the
# the case of function overloading.
return self.function.type_dependencies()
def infer_type(self, env):
func_type = self.function.infer_type(env)
if func_type.is_ptr:
func_type = func_type.base_type
if func_type.is_cfunction:
return func_type.return_type
else:
return py_object_type
def analyse_as_type(self, env): def analyse_as_type(self, env):
attr = self.function.as_cython_attribute() attr = self.function.as_cython_attribute()
if attr == 'pointer': if attr == 'pointer':
...@@ -2466,6 +2525,8 @@ class GeneralCallNode(CallNode): ...@@ -2466,6 +2525,8 @@ class GeneralCallNode(CallNode):
# keyword_args ExprNode or None Dict of keyword arguments # keyword_args ExprNode or None Dict of keyword arguments
# starstar_arg ExprNode or None Dict of extra keyword args # starstar_arg ExprNode or None Dict of extra keyword args
type = py_object_type
subexprs = ['function', 'positional_args', 'keyword_args', 'starstar_arg'] subexprs = ['function', 'positional_args', 'keyword_args', 'starstar_arg']
nogil_check = Node.gil_error nogil_check = Node.gil_error
...@@ -2643,6 +2704,15 @@ class AttributeNode(ExprNode): ...@@ -2643,6 +2704,15 @@ class AttributeNode(ExprNode):
return getattr(obj, attr) return getattr(obj, attr)
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
def infer_type(self, env):
if self.analyse_as_cimported_attribute(env, 0):
return self.entry.type
elif self.analyse_as_unbound_cmethod(env):
return self.entry.type
else:
self.analyse_attribute(env)
return self.type
def analyse_target_declaration(self, env): def analyse_target_declaration(self, env):
pass pass
...@@ -3207,6 +3277,8 @@ class SequenceNode(ExprNode): ...@@ -3207,6 +3277,8 @@ class SequenceNode(ExprNode):
class TupleNode(SequenceNode): class TupleNode(SequenceNode):
# Tuple constructor. # Tuple constructor.
type = tuple_type
gil_message = "Constructing Python tuple" gil_message = "Constructing Python tuple"
...@@ -3216,7 +3288,6 @@ class TupleNode(SequenceNode): ...@@ -3216,7 +3288,6 @@ class TupleNode(SequenceNode):
self.is_literal = 1 self.is_literal = 1
else: else:
SequenceNode.analyse_types(self, env, skip_children) SequenceNode.analyse_types(self, env, skip_children)
self.type = tuple_type
def calculate_result_code(self): def calculate_result_code(self):
if len(self.args) > 0: if len(self.args) > 0:
...@@ -3275,6 +3346,13 @@ class ListNode(SequenceNode): ...@@ -3275,6 +3346,13 @@ class ListNode(SequenceNode):
obj_conversion_errors = [] obj_conversion_errors = []
gil_message = "Constructing Python list" gil_message = "Constructing Python list"
def type_dependencies(self):
return ()
def infer_type(self, env):
# TOOD: Infer non-object list arrays.
return list_type
def analyse_expressions(self, env): def analyse_expressions(self, env):
SequenceNode.analyse_expressions(self, env) SequenceNode.analyse_expressions(self, env)
...@@ -3382,6 +3460,9 @@ class ComprehensionNode(ExprNode): ...@@ -3382,6 +3460,9 @@ class ComprehensionNode(ExprNode):
subexprs = ["target"] subexprs = ["target"]
child_attrs = ["loop", "append"] child_attrs = ["loop", "append"]
def infer_type(self, env):
return self.target.infer_type(env)
def analyse_types(self, env): def analyse_types(self, env):
self.target.analyse_expressions(env) self.target.analyse_expressions(env)
self.type = self.target.type self.type = self.target.type
...@@ -3458,10 +3539,12 @@ class DictComprehensionAppendNode(ComprehensionAppendNode): ...@@ -3458,10 +3539,12 @@ class DictComprehensionAppendNode(ComprehensionAppendNode):
class SetNode(ExprNode): class SetNode(ExprNode):
# Set constructor. # Set constructor.
type = set_type
subexprs = ['args'] subexprs = ['args']
gil_message = "Constructing Python set" gil_message = "Constructing Python set"
def analyse_types(self, env): def analyse_types(self, env):
for i in range(len(self.args)): for i in range(len(self.args)):
arg = self.args[i] arg = self.args[i]
...@@ -3525,6 +3608,13 @@ class DictNode(ExprNode): ...@@ -3525,6 +3608,13 @@ class DictNode(ExprNode):
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
def type_dependencies(self):
return ()
def infer_type(self, env):
# TOOD: Infer struct constructors.
return dict_type
def analyse_types(self, env): def analyse_types(self, env):
hold_errors() hold_errors()
for item in self.key_value_pairs: for item in self.key_value_pairs:
...@@ -3688,12 +3778,13 @@ class UnboundMethodNode(ExprNode): ...@@ -3688,12 +3778,13 @@ class UnboundMethodNode(ExprNode):
# #
# function ExprNode Function object # function ExprNode Function object
type = py_object_type
is_temp = 1
subexprs = ['function'] subexprs = ['function']
def analyse_types(self, env): def analyse_types(self, env):
self.function.analyse_types(env) self.function.analyse_types(env)
self.type = py_object_type
self.is_temp = 1
gil_message = "Constructing an unbound method" gil_message = "Constructing an unbound method"
...@@ -3714,10 +3805,12 @@ class PyCFunctionNode(AtomicExprNode): ...@@ -3714,10 +3805,12 @@ class PyCFunctionNode(AtomicExprNode):
# #
# pymethdef_cname string PyMethodDef structure # pymethdef_cname string PyMethodDef structure
type = py_object_type
is_temp = 1
def analyse_types(self, env): def analyse_types(self, env):
self.type = py_object_type pass
self.is_temp = 1
gil_message = "Constructing Python function" gil_message = "Constructing Python function"
def generate_result_code(self, code): def generate_result_code(self, code):
...@@ -3771,6 +3864,9 @@ class UnopNode(ExprNode): ...@@ -3771,6 +3864,9 @@ class UnopNode(ExprNode):
return func(operand) return func(operand)
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
def infer_type(self, env):
return self.operand.infer_type(env)
def analyse_types(self, env): def analyse_types(self, env):
self.operand.analyse_types(env) self.operand.analyse_types(env)
...@@ -3819,7 +3915,11 @@ class NotNode(ExprNode): ...@@ -3819,7 +3915,11 @@ class NotNode(ExprNode):
# 'not' operator # 'not' operator
# #
# operand ExprNode # operand ExprNode
type = PyrexTypes.c_bint_type
subexprs = ['operand']
def calculate_constant_result(self): def calculate_constant_result(self):
self.constant_result = not self.operand.constant_result self.constant_result = not self.operand.constant_result
...@@ -3830,12 +3930,12 @@ class NotNode(ExprNode): ...@@ -3830,12 +3930,12 @@ class NotNode(ExprNode):
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
subexprs = ['operand'] def infer_type(self, env):
return PyrexTypes.c_bint_type
def analyse_types(self, env): def analyse_types(self, env):
self.operand.analyse_types(env) self.operand.analyse_types(env)
self.operand = self.operand.coerce_to_boolean(env) self.operand = self.operand.coerce_to_boolean(env)
self.type = PyrexTypes.c_bint_type
def calculate_result_code(self): def calculate_result_code(self):
return "(!%s)" % self.operand.result() return "(!%s)" % self.operand.result()
...@@ -3903,6 +4003,9 @@ class AmpersandNode(ExprNode): ...@@ -3903,6 +4003,9 @@ class AmpersandNode(ExprNode):
# operand ExprNode # operand ExprNode
subexprs = ['operand'] subexprs = ['operand']
def infer_type(self, env):
return PyrexTypes.c_ptr_type(self.operand.infer_type(env))
def analyse_types(self, env): def analyse_types(self, env):
self.operand.analyse_types(env) self.operand.analyse_types(env)
...@@ -3961,6 +4064,15 @@ class TypecastNode(ExprNode): ...@@ -3961,6 +4064,15 @@ class TypecastNode(ExprNode):
subexprs = ['operand'] subexprs = ['operand']
base_type = declarator = type = None base_type = declarator = type = None
def type_dependencies(self):
return ()
def infer_types(self, env):
if self.type is None:
base_type = self.base_type.analyse(env)
_, self.type = self.declarator.analyse(base_type, env)
return self.type
def analyse_types(self, env): def analyse_types(self, env):
if self.type is None: if self.type is None:
base_type = self.base_type.analyse(env) base_type = self.base_type.analyse(env)
...@@ -4182,6 +4294,10 @@ class BinopNode(ExprNode): ...@@ -4182,6 +4294,10 @@ class BinopNode(ExprNode):
return func(operand1, operand2) return func(operand1, operand2)
except Exception, e: except Exception, e:
self.compile_time_value_error(e) self.compile_time_value_error(e)
def infer_type(self, env):
return self.result_type(self.operand1.infer_type(env),
self.operand1.infer_type(env))
def analyse_types(self, env): def analyse_types(self, env):
self.operand1.analyse_types(env) self.operand1.analyse_types(env)
...@@ -4196,13 +4312,21 @@ class BinopNode(ExprNode): ...@@ -4196,13 +4312,21 @@ class BinopNode(ExprNode):
self.analyse_c_operation(env) self.analyse_c_operation(env)
def is_py_operation(self): def is_py_operation(self):
return (self.operand1.type.is_pyobject return self.is_py_operation_types(self.operand1.type, self.operand2.type)
or self.operand2.type.is_pyobject)
def is_py_operation_types(self, type1, type2):
return type1.is_pyobject or type2.is_pyobject
def result_type(self, type1, type2):
if self.is_py_operation_types(type1, type2):
return py_object_type
else:
return self.compute_c_result_type(type1, type2)
def nogil_check(self, env): def nogil_check(self, env):
if self.is_py_operation(): if self.is_py_operation():
self.gil_error() self.gil_error()
def coerce_operands_to_pyobjects(self, env): def coerce_operands_to_pyobjects(self, env):
self.operand1 = self.operand1.coerce_to_pyobject(env) self.operand1 = self.operand1.coerce_to_pyobject(env)
self.operand2 = self.operand2.coerce_to_pyobject(env) self.operand2 = self.operand2.coerce_to_pyobject(env)
...@@ -4321,12 +4445,11 @@ class IntBinopNode(NumBinopNode): ...@@ -4321,12 +4445,11 @@ class IntBinopNode(NumBinopNode):
class AddNode(NumBinopNode): class AddNode(NumBinopNode):
# '+' operator. # '+' operator.
def is_py_operation(self): def is_py_operation_types(self, type1, type2):
if self.operand1.type.is_string \ if type1.is_string and type2.is_string:
and self.operand2.type.is_string: return 1
return 1
else: else:
return NumBinopNode.is_py_operation(self) return NumBinopNode.is_py_operation_types(self, type1, type2)
def compute_c_result_type(self, type1, type2): def compute_c_result_type(self, type1, type2):
#print "AddNode.compute_c_result_type:", type1, self.operator, type2 ### #print "AddNode.compute_c_result_type:", type1, self.operator, type2 ###
...@@ -4355,14 +4478,12 @@ class SubNode(NumBinopNode): ...@@ -4355,14 +4478,12 @@ class SubNode(NumBinopNode):
class MulNode(NumBinopNode): class MulNode(NumBinopNode):
# '*' operator. # '*' operator.
def is_py_operation(self): def is_py_operation_types(self, type1, type2):
type1 = self.operand1.type
type2 = self.operand2.type
if (type1.is_string and type2.is_int) \ if (type1.is_string and type2.is_int) \
or (type2.is_string and type1.is_int): or (type2.is_string and type1.is_int):
return 1 return 1
else: else:
return NumBinopNode.is_py_operation(self) return NumBinopNode.is_py_operation_types(self, type1, type2)
class DivNode(NumBinopNode): class DivNode(NumBinopNode):
...@@ -4499,10 +4620,10 @@ class DivNode(NumBinopNode): ...@@ -4499,10 +4620,10 @@ class DivNode(NumBinopNode):
class ModNode(DivNode): class ModNode(DivNode):
# '%' operator. # '%' operator.
def is_py_operation(self): def is_py_operation_types(self, type1, type2):
return (self.operand1.type.is_string return (type1.is_string
or self.operand2.type.is_string or type2.is_string
or NumBinopNode.is_py_operation(self)) or NumBinopNode.is_py_operation_types(self, type1, type2))
def zero_division_message(self): def zero_division_message(self):
if self.type.is_int: if self.type.is_int:
...@@ -4578,6 +4699,13 @@ class BoolBinopNode(ExprNode): ...@@ -4578,6 +4699,13 @@ class BoolBinopNode(ExprNode):
# operand2 ExprNode # operand2 ExprNode
subexprs = ['operand1', 'operand2'] subexprs = ['operand1', 'operand2']
def infer_type(self, env):
if (self.operand1.infer_type(env).is_pyobject or
self.operand2.infer_type(env).is_pyobject):
return py_object_type
else:
return PyrexTypes.c_bint_type
def calculate_constant_result(self): def calculate_constant_result(self):
if self.operator == 'and': if self.operator == 'and':
...@@ -4692,6 +4820,13 @@ class CondExprNode(ExprNode): ...@@ -4692,6 +4820,13 @@ class CondExprNode(ExprNode):
false_val = None false_val = None
subexprs = ['test', 'true_val', 'false_val'] subexprs = ['test', 'true_val', 'false_val']
def type_dependencies(self):
return self.true_val.type_dependencies() + self.false_val.type_dependencies()
def infer_types(self, env):
return self.compute_result_type(self.true_val.infer_types(env),
self.false_val.infer_types(env))
def calculate_constant_result(self): def calculate_constant_result(self):
if self.test.constant_result: if self.test.constant_result:
...@@ -4776,6 +4911,10 @@ richcmp_constants = { ...@@ -4776,6 +4911,10 @@ richcmp_constants = {
class CmpNode(object): class CmpNode(object):
# Mixin class containing code common to PrimaryCmpNodes # Mixin class containing code common to PrimaryCmpNodes
# and CascadedCmpNodes. # and CascadedCmpNodes.
def infer_types(self, env):
# TODO: Actually implement this (after merging with -unstable).
return py_object_type
def calculate_cascaded_constant_result(self, operand1_result): def calculate_cascaded_constant_result(self, operand1_result):
func = compile_time_binary_operators[self.operator] func = compile_time_binary_operators[self.operator]
...@@ -5294,6 +5433,8 @@ class NoneCheckNode(CoercionNode): ...@@ -5294,6 +5433,8 @@ class NoneCheckNode(CoercionNode):
class CoerceToPyTypeNode(CoercionNode): class CoerceToPyTypeNode(CoercionNode):
# This node is used to convert a C data type # This node is used to convert a C data type
# to a Python object. # to a Python object.
type = py_object_type
def __init__(self, arg, env): def __init__(self, arg, env):
CoercionNode.__init__(self, arg) CoercionNode.__init__(self, arg)
...@@ -5366,9 +5507,10 @@ class CoerceToBooleanNode(CoercionNode): ...@@ -5366,9 +5507,10 @@ class CoerceToBooleanNode(CoercionNode):
# This node is used when a result needs to be used # This node is used when a result needs to be used
# in a boolean context. # in a boolean context.
type = PyrexTypes.c_bint_type
def __init__(self, arg, env): def __init__(self, arg, env):
CoercionNode.__init__(self, arg) CoercionNode.__init__(self, arg)
self.type = PyrexTypes.c_bint_type
if arg.type.is_pyobject: if arg.type.is_pyobject:
self.is_temp = 1 self.is_temp = 1
...@@ -5472,6 +5614,12 @@ class CloneNode(CoercionNode): ...@@ -5472,6 +5614,12 @@ class CloneNode(CoercionNode):
def result(self): def result(self):
return self.arg.result() return self.arg.result()
def type_dependencies(self):
return self.arg.type_dependencies()
def infer_type(self, env):
return self.arg.infer_type(env)
def analyse_types(self, env): def analyse_types(self, env):
self.type = self.arg.type self.type = self.arg.type
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment