Skip to content

Commit

Permalink
Implement struct constructors, basic typechecking
Browse files Browse the repository at this point in the history
  • Loading branch information
charles-cooper committed Nov 28, 2018
1 parent fdacdf5 commit 9a732d8
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 2 deletions.
21 changes: 20 additions & 1 deletion vyper/parser/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,26 +648,45 @@ def call(self):

if isinstance(self.expr.func, ast.Name):
function_name = self.expr.func.id

if function_name in dispatch_table:
return dispatch_table[function_name](self.expr, self.context)

# Struct constructors do not need `self` prefix.
elif function_name in self.context.structs :
if not self.context.in_assignment :
raise StructureException("Struct constructor must be called in RHS of assignment.")
args = self.expr.args
if len(args) != 1 or not isinstance(args[0], ast.Dict) :
raise StructureException("Struct constructor is called with one argument, a dictionary of members")
sub = Expr.parse_value_expr(args[0], self.context)
typ = StructType(self.context.structs[function_name], function_name)
# This destructive update seems out of line with the rest of the codebase
sub.typ.name = function_name
return sub

else:
err_msg = "Not a top-level function: {}".format(function_name)
if function_name in [x.split('(')[0] for x, _ in self.context.sigs['self'].items()]:
err_msg += ". Did you mean self.{}?".format(function_name)
raise StructureException(err_msg, self.expr)

elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Name) and self.expr.func.value.id == "self":
return self_call.make_call(self.expr, self.context)

elif isinstance(self.expr.func, ast.Attribute) and isinstance(self.expr.func.value, ast.Call):
contract_name = self.expr.func.value.func.id
contract_address = Expr.parse_value_expr(self.expr.func.value.args[0], self.context)
value, gas = self._get_external_contract_keywords()
return external_contract_call(self.expr, self.context, contract_name, contract_address, pos=getpos(self.expr), value=value, gas=gas)

elif isinstance(self.expr.func.value, ast.Attribute) and self.expr.func.value.attr in self.context.sigs:
contract_name = self.expr.func.value.attr
var = self.context.globals[self.expr.func.value.attr]
contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.expr), annotation='self.' + self.expr.func.value.attr))
value, gas = self._get_external_contract_keywords()
return external_contract_call(self.expr, self.context, contract_name, contract_address, pos=getpos(self.expr), value=value, gas=gas)

elif isinstance(self.expr.func.value, ast.Attribute) and self.expr.func.value.attr in self.context.globals:
contract_name = self.context.globals[self.expr.func.value.attr].typ.unit
var = self.context.globals[self.expr.func.value.attr]
Expand Down Expand Up @@ -702,7 +721,7 @@ def struct_literals(self):
raise TypeMismatchException("Member variable duplicated: " + key.id, key)
o[key.id] = Expr(value, self.context).lll_node
members[key.id] = o[key.id].typ
return LLLnode.from_list(["multi"] + [o[key] for key in sorted(list(o.keys()))], typ=StructType(members), pos=getpos(self.expr))
return LLLnode.from_list(["multi"] + [o[key] for key in sorted(list(o.keys()))], typ=StructType(members, None), pos=getpos(self.expr))

def tuple_literals(self):
if not len(self.expr.elts):
Expand Down
2 changes: 2 additions & 0 deletions vyper/parser/global_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def get_global_context(cls, code):
if base_classes == [ '__VYPER_ANNOT_STRUCT__' ] :
global_ctx._structs[item.name] = GlobalContext.mkstruct(item.name, item.body)
elif base_classes == [ '__VYPER_ANNOT_CONTRACT__' ] :
if global_ctx._structs :
raise StructureException("External contract definitions must come before struct declarations", item)
global_ctx._contracts[item.name] = GlobalContext.mkcontract(item.body)

elif base_classes == [] : # revisit: This doesn't disallow a user from manually adding the base class.
Expand Down
2 changes: 2 additions & 0 deletions vyper/parser/parser_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ def make_setter(left, right, location, pos):
for k in right.typ.members:
if k not in left.typ.members:
raise TypeMismatchException("Keys don't match for structs, extra %s" % k, pos)
if left.typ.name != right.typ.name :
raise TypeMismatchException("Setter type mismatch: left side is %r, right side is %r" % (left.typ, right.typ), pos)
else:
if len(left.typ.members) != len(right.typ.members):
raise TypeMismatchException("Tuple lengths don't match, %d vs %d" % (len(left.typ.members), len(right.typ.members)), pos)
Expand Down
16 changes: 16 additions & 0 deletions vyper/parser/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,17 @@ def _check_valid_assign(self, sub):
if self.stmt.annotation.func.id != sub.typ.typ and not sub.typ.is_literal:
raise TypeMismatchException('Invalid type, expected: %s' % self.stmt.annotation.func.id, self.stmt)
elif isinstance(self.stmt.annotation, ast.Dict):
# Check for demotion?
if not isinstance(sub.typ, StructType):
raise TypeMismatchException('Invalid type, expected a struct')
elif isinstance(self.stmt.annotation, ast.Subscript):
if not isinstance(sub.typ, (ListType, ByteArrayType)): # check list assign.
raise TypeMismatchException('Invalid type, expected: %s' % self.stmt.annotation.value.id, self.stmt)
elif isinstance(sub.typ, StructType) :
# This needs to get more sophisticated in the presence of
# foreign structs.
if not sub.typ.name == self.stmt.annotation.id :
raise TypeMismatchException("Invalid type, expected %s" % self.stmt.annotation.id, self.stmt)
# Check that the integer literal, can be assigned to uint256 if necessary.
elif (self.stmt.annotation.id, sub.typ.typ) == ('uint256', 'int128') and sub.typ.is_literal:
if not SizeLimits.in_bounds('uint256', sub.value):
Expand Down Expand Up @@ -160,16 +166,20 @@ def assign(self):
raise StructureException("Assignment statement must have one target", self.stmt)
self.context.set_in_assignment(True)
sub = Expr(self.stmt.value, self.context).lll_node

# Determine if it's an RLPList assignment.
if isinstance(self.stmt.value, ast.Call) and getattr(self.stmt.value.func, 'id', '') is 'RLPList':
pos = self.context.new_variable(self.stmt.targets[0].id, sub.typ)
variable_loc = LLLnode.from_list(pos, typ=sub.typ, location='memory', pos=getpos(self.stmt), annotation=self.stmt.targets[0].id)
o = make_setter(variable_loc, sub, 'memory', pos=getpos(self.stmt))

# All other assignments are forbidden.
elif isinstance(self.stmt.targets[0], ast.Name) and self.stmt.targets[0].id not in self.context.vars:
raise VariableDeclarationException("Variable type not defined", self.stmt)

elif isinstance(self.stmt.targets[0], ast.Tuple) and isinstance(self.stmt.value, ast.Tuple):
raise VariableDeclarationException("Tuple to tuple assignment not supported", self.stmt)

else:
# Checks to see if assignment is valid
target = self.get_target(self.stmt.targets[0])
Expand Down Expand Up @@ -216,29 +226,35 @@ def call(self):
pack_logging_topics,
external_contract_call,
)

if isinstance(self.stmt.func, ast.Name):
if self.stmt.func.id in stmt_dispatch_table:
return stmt_dispatch_table[self.stmt.func.id](self.stmt, self.context)
elif self.stmt.func.id in dispatch_table:
raise StructureException("Function {} can not be called without being used.".format(self.stmt.func.id), self.stmt)
else:
raise StructureException("Unknown function: '{}'.".format(self.stmt.func.id), self.stmt)

elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Name) and self.stmt.func.value.id == "self":
return self_call.make_call(self.stmt, self.context)

elif isinstance(self.stmt.func, ast.Attribute) and isinstance(self.stmt.func.value, ast.Call):
contract_name = self.stmt.func.value.func.id
contract_address = Expr.parse_value_expr(self.stmt.func.value.args[0], self.context)
return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt))

elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.sigs:
contract_name = self.stmt.func.value.attr
var = self.context.globals[self.stmt.func.value.attr]
contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr))
return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt))

elif isinstance(self.stmt.func.value, ast.Attribute) and self.stmt.func.value.attr in self.context.globals:
contract_name = self.context.globals[self.stmt.func.value.attr].typ.unit
var = self.context.globals[self.stmt.func.value.attr]
contract_address = unwrap_location(LLLnode.from_list(var.pos, typ=var.typ, location='storage', pos=getpos(self.stmt), annotation='self.' + self.stmt.func.value.attr))
return external_contract_call(self.stmt, self.context, contract_name, contract_address, pos=getpos(self.stmt))

elif isinstance(self.stmt.func, ast.Attribute) and self.stmt.func.value.id == 'log':
if self.stmt.func.attr not in self.context.sigs['self']:
raise EventDeclarationException("Event not declared yet: %s" % self.stmt.func.attr)
Expand Down
5 changes: 4 additions & 1 deletion vyper/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def __repr__(self):
# Data structure for a struct, e.g. {a: <type>, b: <type>}
# struct can be named or anonymous. name=None indicates anonymous.
class StructType(NodeType):
def __init__(self, members, name=None):
def __init__(self, members, name):
self.members = copy.copy(members)
self.name = name

Expand Down Expand Up @@ -289,6 +289,9 @@ def parse_type(item, location, sigs={}, custom_units=[], custom_structs={}):
if item.func.id == 'address':
if sigs and item.args[0].id in sigs:
return ContractType(item.args[0].id)
# Struct types
if item.func.id in custom_structs :
return mkstruct(item.id, location, custom_structs[item.id], custom_units, custom_structs)
if not isinstance(item.func, ast.Name):
raise InvalidTypeException("Malformed unit type:", item)
base_type = item.func.id
Expand Down

0 comments on commit 9a732d8

Please sign in to comment.