Skip to content

Commit 5e37ec0

Browse files
feat(typecheck): Finish typechecker (add expressions)
1 parent fff6268 commit 5e37ec0

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

parser/typecheck/typecheck.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ class TypecheckError(BaseLocatedError):
203203
_typecheck_dispatch: dict[type[AstNode], NodeTypecheckFn] = {}
204204

205205

206+
# TODO: output typed AST
206207
class Typechecker:
207208
_curr_scope: Scope
208209

@@ -320,6 +321,83 @@ def _typecheck_define(self, n: AstDefine):
320321
finally:
321322
self._curr_scope = old_scope
322323

324+
@_node_typechecker(AstNumber)
325+
def _typecheck_number(self, _n: AstNumber):
326+
return ValType()
327+
328+
@_node_typechecker(AstString)
329+
def _typecheck_string(self, _n: AstString):
330+
return ValType()
331+
332+
@_node_typechecker(AstListLiteral)
333+
def _typecheck_list(self, n: AstListLiteral):
334+
for item in n.items:
335+
if self._typecheck(item) != ValType():
336+
raise self.err("Can only have ValType()s in list", item)
337+
return ListType()
338+
339+
@_node_typechecker(AstIdent)
340+
def _typecheck_ident(self, n: AstIdent):
341+
return self._curr_scope.used[n.id].tp_info
342+
343+
@_node_typechecker(AstAttrName)
344+
def _typecheck_attr_name(self, _n: AstAttrName):
345+
assert 0, "AstAttrName has no type, cannot be checked on its own"
346+
347+
@_node_typechecker(AstAttribute)
348+
def _typecheck_attribute(self, n: AstAttribute):
349+
# TODO: implement this properly, with better types and stuff
350+
raise self.err("Attributes are not implemented yet", n)
351+
352+
@_node_typechecker(AstItem)
353+
def _typecheck_item(self, n: AstItem):
354+
# TODO: this will require different intrinsics for string vs list getitem
355+
container_tp = self._typecheck(n.obj)
356+
if container_tp not in (ListType(), ValType()):
357+
raise self.err(f"Cannot get item of {container_tp}", n)
358+
self.expect_type(self._typecheck(n.index), ValType(), n.index)
359+
360+
@_node_typechecker(AstCall)
361+
def _typecheck_call(self, n: AstCall):
362+
called_tp = self._typecheck(n.obj)
363+
if not isinstance(called_tp, FunctionType):
364+
raise self.err(f"Cannot call {called_tp}", n.obj)
365+
if len(called_tp.arg_types) != len(n.args):
366+
if n.args and len(n.args) > len(called_tp.arg_types):
367+
region = n.args[-1].region # Highlight extraneous arg
368+
else:
369+
region = n.region
370+
raise self.err(f"Incorrect number of arguments, expected "
371+
f"{len(called_tp.arg_types)}, got {len(n.args)}",
372+
region)
373+
for decl_t, arg_node in zip(called_tp.arg_types, n.args):
374+
self.expect_type(self._typecheck(arg_node), decl_t, arg_node)
375+
return called_tp.ret_type
376+
377+
_BINARY_OP_TYPES = dict.fromkeys([
378+
*'+-*/%', '**', '..', '==', '!=', '<', '>', '<=', '>='
379+
], ValType()) | dict.fromkeys([
380+
'&&', '||'
381+
], BoolType())
382+
383+
_UNARY_OP_TYPES = dict.fromkeys([
384+
*'+-'
385+
], ValType()) | dict.fromkeys([
386+
'!'
387+
], BoolType())
388+
389+
# TODO: allow casting bool to val? - auto-cast or explicit?
390+
@_node_typechecker(AstBinOp)
391+
def _typecheck_bin_op(self, n: AstBinOp):
392+
expect_tp = self._BINARY_OP_TYPES[n.op]
393+
self.expect_type(self._typecheck(n.left), expect_tp, n.left)
394+
self.expect_type(self._typecheck(n.right), expect_tp, n.right)
395+
396+
@_node_typechecker(AstUnaryOp)
397+
def _typecheck_unary_op(self, n: AstUnaryOp):
398+
expect_tp = self._UNARY_OP_TYPES[n.op]
399+
self.expect_type(self._typecheck(n.operand), expect_tp, n.operand)
400+
323401
def _resolve_scope(self, scope_tp: VarDeclScope):
324402
return self.top_scope if scope_tp == VarDeclScope.GLOBAL else self._curr_scope
325403

0 commit comments

Comments
 (0)