diff --git a/Makefile.posix b/Makefile.posix index fb67fc86..568490fd 100644 --- a/Makefile.posix +++ b/Makefile.posix @@ -54,7 +54,7 @@ jou_stage2: jou_stage1 config.jou rm -rf compiler/jou_compiled && ./jou_stage1 -o jou_stage2 --linker-flags "$(LDFLAGS)" compiler/main.jou # Stage 3 of bootstrapping: Compile the Jou compiler with the Jou compiler. -jou: jou_stage2 config.jou $(wildcard compiler/*.jou) +jou: jou_stage2 config.jou $(wildcard compiler/*.jou compiler/*/*.jou) rm -rf compiler/jou_compiled && ./jou_stage2 -o jou --linker-flags "$(LDFLAGS)" compiler/main.jou .PHONY: clean diff --git a/Makefile.windows b/Makefile.windows index b1e487b2..4b45035b 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -37,7 +37,7 @@ jou_stage2.exe: jou_stage1.exe config.jou rm -rf compiler/jou_compiled && ./jou_stage1.exe -o jou_stage2.exe --linker-flags "$(LDFLAGS)" compiler/main.jou # Stage 3 of bootstrapping: Compile the Jou compiler with the Jou compiler. -jou.exe: jou_stage2.exe config.jou $(wildcard compiler/*.jou) +jou.exe: jou_stage2.exe config.jou $(wildcard compiler/*.jou compiler/*/*.jou) rm -rf compiler/jou_compiled && ./jou_stage2.exe -o jou.exe --linker-flags "$(LDFLAGS)" compiler/main.jou .PHONY: clean diff --git a/compiler/build_cf_graph.jou b/compiler/build_cf_graph.jou index 0ab88bc4..4b1e7811 100644 --- a/compiler/build_cf_graph.jou +++ b/compiler/build_cf_graph.jou @@ -5,6 +5,7 @@ import "./cf_graph.jou" import "./structs.jou" import "./evaluate.jou" import "./types.jou" +import "./typecheck/common.jou" import "./errors_and_warnings.jou" import "./ast.jou" @@ -534,18 +535,9 @@ def build_function_or_method_call( assert selfclass->kind == TypeKind::Pointer selfclass = selfclass->value_type assert selfclass->kind == TypeKind::Class - - for s = selfclass->classdata.methods; s < &selfclass->classdata.methods[selfclass->classdata.nmethods]; s++: - assert s->get_self_class() == selfclass - if strcmp(s->name, call->name) == 0: - sig = s - break - + sig = selfclass->find_method(call->name) else: - for f = st->filetypes->functions; f < &st->filetypes->functions[st->filetypes->nfunctions]; f++: - if strcmp(f->signature.name, call->name) == 0: - sig = &f->signature - break + sig = st->filetypes->find_function(call->name) assert sig != NULL diff --git a/compiler/cf_graph.jou b/compiler/cf_graph.jou index af9ed5ad..fc30b96f 100644 --- a/compiler/cf_graph.jou +++ b/compiler/cf_graph.jou @@ -6,6 +6,7 @@ import "stdlib/io.jou" import "./errors_and_warnings.jou" import "./structs.jou" +import "./typecheck/common.jou" import "./print.jou" import "./free.jou" import "./types.jou" diff --git a/compiler/codegen.jou b/compiler/codegen.jou index 3052e841..a2f1305f 100644 --- a/compiler/codegen.jou +++ b/compiler/codegen.jou @@ -3,6 +3,7 @@ import "stdlib/mem.jou" import "stdlib/str.jou" import "./evaluate.jou" +import "./typecheck/common.jou" import "./cf_graph.jou" import "./llvm.jou" import "./target.jou" diff --git a/compiler/evaluate.jou b/compiler/evaluate.jou index 336596f9..1a362952 100644 --- a/compiler/evaluate.jou +++ b/compiler/evaluate.jou @@ -5,6 +5,12 @@ import "./ast.jou" import "./errors_and_warnings.jou" +def evaluate_array_length(expr: AstExpression*) -> int: + if expr->kind == AstExpressionKind::Int: + return expr->int_value + fail(expr->location, "cannot evaluate array length at compile time") + + def get_special_constant(name: byte*) -> int: if strcmp(name, "WINDOWS") == 0: return WINDOWS as int diff --git a/compiler/free.jou b/compiler/free.jou index 84cc6e62..bf697508 100644 --- a/compiler/free.jou +++ b/compiler/free.jou @@ -1,9 +1,11 @@ # Boring boilerplate code to free up data structures used in compilation. +import "stdlib/mem.jou" + import "./structs.jou" import "./types.jou" import "./token.jou" -import "stdlib/mem.jou" +import "./typecheck/common.jou" def free_tokens(tokenlist: Token*) -> None: for t = tokenlist; t->kind != TokenKind::EndOfFile; t++: diff --git a/compiler/main.jou b/compiler/main.jou index d14b3ae8..3e7a1393 100644 --- a/compiler/main.jou +++ b/compiler/main.jou @@ -12,7 +12,10 @@ import "./codegen.jou" import "./print.jou" import "./llvm.jou" import "./output.jou" -import "./typecheck.jou" +import "./typecheck/common.jou" +import "./typecheck/step1_create_types.jou" +import "./typecheck/step2_populate_types.jou" +import "./typecheck/step3_function_and_method_bodies.jou" import "./target.jou" import "./types.jou" import "./free.jou" @@ -344,7 +347,7 @@ class CompileState: self->parse(path, NULL) free(path) - # Each stage of type checking produces exported symbols that other files can import. + # Each step of type checking produces exported symbols that other files can import. # This method hands them over to the importing files. def exports_to_imports(self, pending_exports: ExportSymbol**) -> None: for to = self->files; to < &self->files[self->nfiles]; to++: @@ -399,22 +402,22 @@ class CompileState: for i = 0; i < self->nfiles; i++: if command_line_args.verbosity >= 1: - printf(" stage 1: %s\n", self->files[i].path) - pending_exports[i] = typecheck_stage1_create_types(&self->files[i].types, &self->files[i].ast) + printf(" step 1: %s\n", self->files[i].path) + pending_exports[i] = typecheck_step1_create_types(&self->files[i].types, &self->files[i].ast) self->exports_to_imports(pending_exports) for i = 0; i < self->nfiles; i++: if command_line_args.verbosity >= 1: - printf(" stage 2: %s\n", self->files[i].path) - pending_exports[i] = typecheck_stage2_populate_types(&self->files[i].types, &self->files[i].ast) + printf(" step 2: %s\n", self->files[i].path) + pending_exports[i] = typecheck_step2_populate_types(&self->files[i].types, &self->files[i].ast) self->exports_to_imports(pending_exports) for i = 0; i < self->nfiles; i++: if command_line_args.verbosity >= 1: - printf(" stage 3: %s\n", self->files[i].path) - typecheck_stage3_function_and_method_bodies(&self->files[i].types, &self->files[i].ast) + printf(" step 3: %s\n", self->files[i].path) + typecheck_step3_function_and_method_bodies(&self->files[i].types, &self->files[i].ast) free(pending_exports) diff --git a/compiler/structs.jou b/compiler/structs.jou index b7845501..9a36e662 100644 --- a/compiler/structs.jou +++ b/compiler/structs.jou @@ -1,10 +1,7 @@ # TODO: delete this file, merge into others import "stdlib/str.jou" -import "stdlib/math.jou" -import "stdlib/io.jou" -import "./ast.jou" import "./types.jou" @@ -85,78 +82,3 @@ def int_constant(type: Type*, value: long) -> Constant: value = value } } - - -class GlobalVariable: - name: byte[100] # Same as in user's code, never empty - type: Type* - defined_in_current_file: bool # not declare-only (e.g. stdout) or imported - usedptr: bool* # If non-NULL, set to true when the variable is used. This is how we detect unused imports. - - -class LocalVariable: - id: int # Unique, but you can also compare pointers to LocalVariable. - name: byte[100] # Same name as in user's code, empty for temporary variables created by compiler - type: Type* - is_argument: bool # First n variables are always the arguments - - def print_to_width(self, width: int) -> None: - if self->name[0] != '\0': - printf("%-*s", width, self->name) - else: - printf("$%-*d", max(width-1, 0), self->id) - - def print(self) -> None: - self->print_to_width(0) - - -class ExpressionTypes: - expr: AstExpression* # not owned - type: Type* - implicit_cast_type: Type* # NULL for no implicit cast - - # Flags to indicate whether special kinds of implicit casts happened - implicit_array_to_pointer_cast: bool # Foo[N] to Foo* - implicit_string_to_array_cast: bool # "..." to byte[N] - -enum ExportSymbolKind: - Function - Type - GlobalVar - -class ExportSymbol: - kind: ExportSymbolKind - name: byte[100] # TODO: maybe this should be 200 because it can be ClassName.method_name? or something else? - union: - funcsignature: Signature - type: Type* # ExportSymbolKind::Type and ExportSymbolKind::GlobalVar - -# Type information about a function or method defined in the current file. -class FunctionOrMethodTypes: - signature: Signature - expr_types: ExpressionTypes** - n_expr_types: int - locals: LocalVariable** - nlocals: int - -class TypeAndUsedPtr: - type: Type* - usedptr: bool* - -class SignatureAndUsedPtr: - signature: Signature - usedptr: bool* - -# Type information about a file. -class FileTypes: - current_fom_types: FunctionOrMethodTypes* # conceptually this is internal to typecheck.c - fomtypes: FunctionOrMethodTypes* - nfomtypes: int - globals: GlobalVariable* - nglobals: int - owned_types: Type** # These will be freed later - n_owned_types: int - types: TypeAndUsedPtr* - ntypes: int - functions: SignatureAndUsedPtr* - nfunctions: int diff --git a/compiler/typecheck/common.jou b/compiler/typecheck/common.jou new file mode 100644 index 00000000..4663b135 --- /dev/null +++ b/compiler/typecheck/common.jou @@ -0,0 +1,225 @@ +# Contains data structures and functions that are shared among multiple type +# checking steps. + +import "stdlib/io.jou" +import "stdlib/math.jou" +import "stdlib/str.jou" +import "stdlib/mem.jou" + +import "../ast.jou" +import "../evaluate.jou" +import "../errors_and_warnings.jou" +import "../types.jou" + + +class GlobalVariable: + name: byte[100] # Same as in user's code, never empty + type: Type* + defined_in_current_file: bool # not declare-only (e.g. stdout) or imported + usedptr: bool* # If non-NULL, set to true when the variable is used. This is how we detect unused imports. + + +class LocalVariable: + id: int # Unique, but you can also compare pointers to LocalVariable. + name: byte[100] # Same name as in user's code, empty for temporary variables created by compiler + type: Type* + is_argument: bool # First n variables are always the arguments + + def print_to_width(self, width: int) -> None: + if self->name[0] != '\0': + printf("%-*s", width, self->name) + else: + printf("$%-*d", max(width-1, 0), self->id) + + def print(self) -> None: + self->print_to_width(0) + + +class ExpressionTypes: + expr: AstExpression* # not owned + type: Type* + implicit_cast_type: Type* # NULL for no implicit cast + + # Flags to indicate whether special kinds of implicit casts happened + implicit_array_to_pointer_cast: bool # Foo[N] to Foo* + implicit_string_to_array_cast: bool # "..." to byte[N] + + +# Type checking steps 1 and 2 return export symbols to be passed on to the next +# step. That's how the next step accesses the results of the previous step. +enum ExportSymbolKind: + Function + Type + GlobalVar + +class ExportSymbol: + kind: ExportSymbolKind + name: byte[100] # TODO: maybe this should be 200 because it can be ClassName.method_name? or something else? + union: + funcsignature: Signature + type: Type* # ExportSymbolKind::Type and ExportSymbolKind::GlobalVar + + +# Type information about a function or method defined in the current file. +# Not created for anything imported from another file. +class FunctionOrMethodTypes: + signature: Signature + expr_types: ExpressionTypes** + n_expr_types: int + locals: LocalVariable** + nlocals: int + + def find_local_var(self, name: byte*) -> LocalVariable*: + for var = self->locals; var < &self->locals[self->nlocals]; var++: + if strcmp((*var)->name, name) == 0: + return *var + return NULL + + def add_variable(self, t: Type*, name: byte*) -> LocalVariable*: + var: LocalVariable* = calloc(1, sizeof *var) + var->id = self->nlocals + var->type = t + + assert name != NULL + assert self->find_local_var(name) == NULL + assert strlen(name) < sizeof(var->name) + strcpy(var->name, name) + + self->locals = realloc(self->locals, sizeof(self->locals[0]) * (self->nlocals + 1)) + assert self->locals != NULL + self->locals[self->nlocals++] = var + + return var + + +class TypeAndUsedPtr: + type: Type* + usedptr: bool* # used to detect unused imports + +class SignatureAndUsedPtr: + signature: Signature + usedptr: bool* # used to detect unused imports + + +class FileTypes: + current_fom_types: FunctionOrMethodTypes* # conceptually this is internal to typecheck.c + fomtypes: FunctionOrMethodTypes* + nfomtypes: int + globals: GlobalVariable* + nglobals: int + owned_types: Type** # These will be freed later + n_owned_types: int + types: TypeAndUsedPtr* + ntypes: int + functions: SignatureAndUsedPtr* + nfunctions: int + + def find_type(self, name: byte*) -> Type*: + for t = self->types; t < &self->types[self->ntypes]; t++: + if strcmp(t->type->name, name) == 0: + if t->usedptr != NULL: + *t->usedptr = True + return t->type + return NULL + + def find_function(self, name: byte*) -> Signature*: + for f = self->functions; f < &self->functions[self->nfunctions]; f++: + if strcmp(f->signature.name, name) == 0: + if f->usedptr != NULL: + *f->usedptr = True + return &f->signature + return NULL + + def find_function_or_method(self, selfclass: Type*, name: byte*) -> Signature*: + if selfclass != NULL: + return selfclass->find_method(name) + else: + return self->find_function(name) + + def find_global_var(self, name: byte*) -> Type*: + for var = self->globals; var < &self->globals[self->nglobals]; var++: + if strcmp(var->name, name) == 0: + if var->usedptr != NULL: + *var->usedptr = True + return var->type + return NULL + + def find_local_var(self, name: byte*) -> LocalVariable*: + if self->current_fom_types == NULL: + return NULL + return self->current_fom_types->find_local_var(name) + + def find_any_var(self, name: byte*) -> Type*: + if get_special_constant(name) != -1: + return boolType + + local = self->find_local_var(name) + if local != NULL: + return local->type + + return self->find_global_var(name) + + +def type_from_ast(ft: FileTypes*, asttype: AstType*) -> Type*: + msg: byte[500] + + if asttype->is_void() or asttype->is_none() or asttype->is_noreturn(): + snprintf(msg, sizeof(msg), "'%s' cannot be used here because it is not a type", asttype->name) + fail(asttype->location, msg) + + if asttype->kind == AstTypeKind::Named: + if strcmp(asttype->name, "short") == 0: + return shortType + if strcmp(asttype->name, "int") == 0: + return intType + if strcmp(asttype->name, "long") == 0: + return longType + if strcmp(asttype->name, "byte") == 0: + return byteType + if strcmp(asttype->name, "bool") == 0: + return boolType + if strcmp(asttype->name, "float") == 0: + return floatType + if strcmp(asttype->name, "double") == 0: + return doubleType + + found = ft->find_type(asttype->name) + if found != NULL: + return found + + snprintf(msg, sizeof(msg), "there is no type named '%s'", asttype->name) + fail(asttype->location, msg) + + if asttype->kind == AstTypeKind::Pointer: + if asttype->value_type->is_void(): + return voidPtrType + return type_from_ast(ft, asttype->value_type)->pointer_type() + + if asttype->kind == AstTypeKind::Array: + tmp = type_from_ast(ft, asttype->value_type) + len = evaluate_array_length(asttype->array.length) + if len <= 0: + fail(asttype->array.length->location, "array length must be positive") + return tmp->array_type(len) + + assert False + + +def short_type_description(t: Type*) -> byte*: + if t->kind == TypeKind::OpaqueClass or t->kind == TypeKind::Class: + return "a class" + if t->kind == TypeKind::Enum: + return "an enum" + if t->kind == TypeKind::VoidPointer or t->kind == TypeKind::Pointer: + return "a pointer type" + if ( + t->kind == TypeKind::SignedInteger + or t->kind == TypeKind::UnsignedInteger + or t->kind == TypeKind::FloatingPoint + ): + return "a number type" + if t->kind == TypeKind::Array: + return "an array type" + if t->kind == TypeKind::Bool: + return "the built-in bool type" + assert False diff --git a/compiler/typecheck/step1_create_types.jou b/compiler/typecheck/step1_create_types.jou new file mode 100644 index 00000000..0c8e4795 --- /dev/null +++ b/compiler/typecheck/step1_create_types.jou @@ -0,0 +1,65 @@ +# First step of type checking is to create types, so that they exist when later +# stages reference the types. +# +# After the first step, classes defined in Jou exist, but they are all opaque +# and contain no members. This way, when step 2 checks function signatures, +# they can refer to classes that are defined anywhere in the project, e.g. +# later in the same file. +# +# Enums are simple. The first step creates them and also fills in their members, +# although it doesn't really matter whether that's done in step 1 or 2. + +import "stdlib/str.jou" +import "stdlib/mem.jou" + +import "../ast.jou" +import "../errors_and_warnings.jou" +import "../types.jou" +import "./common.jou" + + +def typecheck_step1_create_types(ft: FileTypes*, ast: AstFile*) -> ExportSymbol*: + exports: ExportSymbol* = NULL + nexports = 0 + + for i = 0; i < ast->body.nstatements; i++: + stmt = &ast->body.statements[i] + + name: byte[100] + if stmt->kind == AstStatementKind::Class: + assert sizeof(name) == sizeof(stmt->classdef.name) + strcpy(name, stmt->classdef.name) + t = create_opaque_class(name) + elif stmt->kind == AstStatementKind::Enum: + assert sizeof(name) == sizeof(stmt->enumdef.name) + strcpy(name, stmt->enumdef.name) + t = create_enum(name, stmt->enumdef.member_count, stmt->enumdef.member_names) + else: + continue + + existing = ft->find_type(name) + if existing != NULL: + msg: byte[500] + snprintf(msg, sizeof(msg), "%s named '%s' already exists", short_type_description(existing), name) + fail(stmt->location, msg) + + ft->types = realloc(ft->types, sizeof(ft->types[0]) * (ft->ntypes + 1)) + assert ft->types != NULL + ft->types[ft->ntypes++] = TypeAndUsedPtr{type=t, usedptr=NULL} + + ft->owned_types = realloc(ft->owned_types, sizeof(ft->owned_types[0]) * (ft->n_owned_types + 1)) + assert ft->owned_types != NULL + ft->owned_types[ft->n_owned_types++] = t + + es = ExportSymbol{kind = ExportSymbolKind::Type, type = t} + assert sizeof(es.name) == sizeof(name) + strcpy(es.name, name) + + exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) + assert exports != NULL + exports[nexports++] = es + + exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) + assert exports != NULL + exports[nexports] = ExportSymbol{} # list terminator + return exports diff --git a/compiler/typecheck/step2_populate_types.jou b/compiler/typecheck/step2_populate_types.jou new file mode 100644 index 00000000..a1f73b6b --- /dev/null +++ b/compiler/typecheck/step2_populate_types.jou @@ -0,0 +1,206 @@ +# Second step of type checking is to check function/method signatures, global +# variables, and bodies of classes. However, we don't look into function or +# method bodies yet, because they reference other functions and methods which +# might not exist. +# +# After the second step, all types, functions, and global variables are ready. +# +# This step assumes that all types exist, but doesn't need to know what fields +# and methods each class has. + +import "stdlib/mem.jou" +import "stdlib/str.jou" + +import "../ast.jou" +import "../types.jou" +import "../errors_and_warnings.jou" +import "./common.jou" + + +def handle_global_var(ft: FileTypes*, vardecl: AstNameTypeValue*, defined_here: bool) -> ExportSymbol: + assert ft->current_fom_types == NULL # find_any_var() only finds global vars + if ft->find_global_var(vardecl->name) != NULL: + msg: byte[500] + snprintf(msg, sizeof(msg), "a global variable named '%s' already exists", vardecl->name) + fail(vardecl->name_location, msg) + + assert vardecl->value == NULL + g = GlobalVariable{ + type = type_from_ast(ft, &vardecl->type), + defined_in_current_file = defined_here, + } + + assert sizeof(g.name) == sizeof(vardecl->name) + strcpy(g.name, vardecl->name) + + ft->globals = realloc(ft->globals, sizeof(ft->globals[0]) * (ft->nglobals + 1)) + assert ft->globals != NULL + ft->globals[ft->nglobals++] = g + + es = ExportSymbol{kind = ExportSymbolKind::GlobalVar, type = g.type} + assert sizeof(es.name) == sizeof(g.name) + strcpy(es.name, g.name) + return es + + +def handle_signature(ft: FileTypes*, astsig: AstSignature*, self_class: Type*) -> Signature: + msg: byte[500] + + if ft->find_function_or_method(self_class, astsig->name) != NULL: + if self_class != NULL: + snprintf(msg, sizeof(msg), "a method named '%s' already exists", astsig->name) + else: + snprintf(msg, sizeof(msg), "a function named '%s' already exists", astsig->name) + fail(astsig->name_location, msg) + + sig = Signature{nargs = astsig->nargs, takes_varargs = astsig->takes_varargs} + assert sizeof(sig.name) == sizeof(astsig->name) + strcpy(sig.name, astsig->name) + + size = sizeof(sig.argnames[0]) * sig.nargs + sig.argnames = malloc(size) + for i = 0; i < sig.nargs; i++: + assert sizeof(sig.argnames[i]) == sizeof(astsig->args[i].name) + strcpy(sig.argnames[i], astsig->args[i].name) + + sig.argtypes = malloc(sizeof(sig.argtypes[0]) * sig.nargs) + for i = 0; i < sig.nargs; i++: + if ( + strcmp(sig.argnames[i], "self") == 0 + and astsig->args[i].type.kind == AstTypeKind::Named + and astsig->args[i].type.name[0] == '\0' + ): + # just "self" without a type after it --> default to "self: Foo*" in class Foo + argtype = self_class->pointer_type() + else: + argtype = type_from_ast(ft, &astsig->args[i].type) + + if strcmp(sig.argnames[i], "self") == 0 and argtype != self_class and argtype != self_class->pointer_type(): + snprintf(msg, sizeof(msg), "type of self must be %s* (default) or %s", self_class->name, self_class->name) + fail(astsig->args[i].type.location, msg) + + sig.argtypes[i] = argtype + + sig.is_noreturn = astsig->return_type.is_noreturn() + if astsig->return_type.is_none() or astsig->return_type.is_noreturn(): + sig.returntype = NULL + elif astsig->return_type.is_void(): + fail(astsig->return_type.location, "void is not a valid return type, use '-> None' if the function does not return a value") + else: + sig.returntype = type_from_ast(ft, &astsig->return_type) + + if self_class == NULL and strcmp(sig.name, "main") == 0: + # special main() function checks + if sig.returntype != intType: + fail(astsig->return_type.location, "the main() function must return int") + if ( + sig.nargs != 0 + and not ( + sig.nargs == 2 + and sig.argtypes[0] == intType + and sig.argtypes[1] == byteType->pointer_type()->pointer_type() + ) + ): + fail( + astsig->args[0].type.location, + "if the main() function takes parameters, it should be defined like this: def main(argc: int, argv: byte**) -> int" + ) + + sig.returntype_location = astsig->return_type.location + + if self_class == NULL: + ft->functions = realloc(ft->functions, sizeof(ft->functions[0]) * (ft->nfunctions + 1)) + assert ft->functions != NULL + ft->functions[ft->nfunctions++] = SignatureAndUsedPtr{ + signature = sig.copy(), + usedptr = NULL, + } + + return sig + + +def handle_class_members_step2(ft: FileTypes*, classdef: AstClassDef*) -> None: + # Previous type-checking step created an opaque struct. + type: Type* = NULL + for s = ft->owned_types; s < &ft->owned_types[ft->n_owned_types]; s++: + if strcmp((*s)->name, classdef->name) == 0: + type = *s + break + + assert type != NULL + assert type->kind == TypeKind::OpaqueClass + type->kind = TypeKind::Class + + memset(&type->classdata, 0, sizeof type->classdata) + + union_id = 0 + for m = classdef->members; m < &classdef->members[classdef->nmembers]; m++: + if m->kind == AstClassMemberKind::Field: + f = ClassField{ + type = type_from_ast(ft, &m->field.type), + union_id = union_id++, + } + assert sizeof(f.name) == sizeof(m->field.name) + strcpy(f.name, m->field.name) + + type->classdata.fields = realloc(type->classdata.fields, sizeof(type->classdata.fields[0]) * (type->classdata.nfields + 1)) + assert type->classdata.fields != NULL + type->classdata.fields[type->classdata.nfields++] = f + + elif m->kind == AstClassMemberKind::Union: + uid = union_id++ + for ntv = m->union_fields.fields; ntv < &m->union_fields.fields[m->union_fields.nfields]; ntv++: + f = ClassField{ + type = type_from_ast(ft, &ntv->type), + union_id = uid, + } + assert sizeof(f.name) == sizeof(ntv->name) + strcpy(f.name, ntv->name) + + type->classdata.fields = realloc(type->classdata.fields, sizeof(type->classdata.fields[0]) * (type->classdata.nfields + 1)) + assert type->classdata.fields != NULL + type->classdata.fields[type->classdata.nfields++] = f + + elif m->kind == AstClassMemberKind::Method: + # Don't handle the method body yet: that is a part of step 3, not step 2 + sig = handle_signature(ft, &m->method.signature, type) + + type->classdata.methods = realloc(type->classdata.methods, sizeof(type->classdata.methods[0]) * (type->classdata.nmethods + 1)) + assert type->classdata.methods != NULL + type->classdata.methods[type->classdata.nmethods++] = sig + + else: + assert False + + +def typecheck_step2_populate_types(ft: FileTypes*, ast: AstFile*) -> ExportSymbol*: + exports: ExportSymbol* = NULL + nexports = 0 + + for i = 0; i < ast->body.nstatements; i++: + stmt = &ast->body.statements[i] + + exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) + assert exports != NULL + + if stmt->kind == AstStatementKind::GlobalVariableDeclaration: + exports[nexports++] = handle_global_var(ft, &stmt->var_declaration, False) + elif stmt->kind == AstStatementKind::GlobalVariableDefinition: + exports[nexports++] = handle_global_var(ft, &stmt->var_declaration, True) + elif stmt->kind == AstStatementKind::Function: + sig = handle_signature(ft, &stmt->function.signature, NULL) + es = ExportSymbol{kind = ExportSymbolKind::Function, funcsignature = sig} + assert sizeof(es.name) == sizeof(sig.name) + strcpy(es.name, sig.name) + exports[nexports++] = es + elif stmt->kind == AstStatementKind::Class: + handle_class_members_step2(ft, &stmt->classdef) + elif stmt->kind == AstStatementKind::Enum: + pass # Everything done in previous type-checking steps. + else: + assert False + + exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) + assert exports != NULL + exports[nexports] = ExportSymbol{} + return exports diff --git a/compiler/typecheck.jou b/compiler/typecheck/step3_function_and_method_bodies.jou similarity index 70% rename from compiler/typecheck.jou rename to compiler/typecheck/step3_function_and_method_bodies.jou index b14c2554..236c194a 100644 --- a/compiler/typecheck.jou +++ b/compiler/typecheck/step3_function_and_method_bodies.jou @@ -1,387 +1,28 @@ +# The third and final step of type checking. Checks the code inside functions +# and methods. +# +# After the third step, local variables exist, and the types of all values in +# the program are known. +# +# This step assumes that all classes, enums, global variables and functions +# already exist and are fully populated, so that we e.g. know what methods each +# class has and what parameter types they take. + import "stdlib/str.jou" import "stdlib/io.jou" import "stdlib/math.jou" import "stdlib/mem.jou" -import "./structs.jou" -import "./evaluate.jou" -import "./types.jou" -import "./ast.jou" -import "./errors_and_warnings.jou" - -def find_type(ft: FileTypes*, name: byte*) -> Type*: - for t = ft->types; t < &ft->types[ft->ntypes]; t++: - if strcmp(t->type->name, name) == 0: - if t->usedptr != NULL: - *t->usedptr = True - return t->type - return NULL - -def find_function(ft: FileTypes*, name: byte*) -> Signature*: - for f = ft->functions; f < &ft->functions[ft->nfunctions]; f++: - if strcmp(f->signature.name, name) == 0: - if f->usedptr != NULL: - *f->usedptr = True - return &f->signature - return NULL - -def find_method(selfclass: Type*, name: byte*) -> Signature*: - if selfclass->kind != TypeKind::Class: - return NULL - for m = selfclass->classdata.methods; m < &selfclass->classdata.methods[selfclass->classdata.nmethods]; m++: - if strcmp(m->name, name) == 0: - return m - return NULL - -def find_function_or_method(ft: FileTypes*, selfclass: Type*, name: byte*) -> Signature*: - if selfclass != NULL: - return find_method(selfclass, name) - else: - return find_function(ft, name) - -def find_local_var(ft: FileTypes*, name: byte*) -> LocalVariable*: - if ft->current_fom_types != NULL: - for var = ft->current_fom_types->locals; var < &ft->current_fom_types->locals[ft->current_fom_types->nlocals]; var++: - if strcmp((*var)->name, name) == 0: - return *var - return NULL - -def find_any_var(ft: FileTypes*, name: byte*) -> Type*: - if get_special_constant(name) != -1: - return boolType - if ft->current_fom_types != NULL: - for lvar = ft->current_fom_types->locals; lvar < &ft->current_fom_types->locals[ft->current_fom_types->nlocals]; lvar++: - if strcmp((*lvar)->name, name) == 0: - return (*lvar)->type - for gvar = ft->globals; gvar < &ft->globals[ft->nglobals]; gvar++: - if strcmp(gvar->name, name) == 0: - if gvar->usedptr != NULL: - *gvar->usedptr = True - return gvar->type - return NULL - -def short_type_description(t: Type*) -> byte*: - if t->kind == TypeKind::OpaqueClass or t->kind == TypeKind::Class: - return "a class" - if t->kind == TypeKind::Enum: - return "an enum" - if t->kind == TypeKind::VoidPointer or t->kind == TypeKind::Pointer: - return "a pointer type" - if ( - t->kind == TypeKind::SignedInteger - or t->kind == TypeKind::UnsignedInteger - or t->kind == TypeKind::FloatingPoint - ): - return "a number type" - if t->kind == TypeKind::Array: - return "an array type" - if t->kind == TypeKind::Bool: - return "the built-in bool type" - assert False - -def typecheck_stage1_create_types(ft: FileTypes*, ast: AstFile*) -> ExportSymbol*: - exports: ExportSymbol* = NULL - nexports = 0 - - for i = 0; i < ast->body.nstatements; i++: - stmt = &ast->body.statements[i] - - name: byte[100] - if stmt->kind == AstStatementKind::Class: - assert sizeof(name) == sizeof(stmt->classdef.name) - strcpy(name, stmt->classdef.name) - t = create_opaque_class(name) - elif stmt->kind == AstStatementKind::Enum: - assert sizeof(name) == sizeof(stmt->enumdef.name) - strcpy(name, stmt->enumdef.name) - t = create_enum(name, stmt->enumdef.member_count, stmt->enumdef.member_names) - else: - continue - - existing = find_type(ft, name) - if existing != NULL: - msg: byte[500] - snprintf(msg, sizeof(msg), "%s named '%s' already exists", short_type_description(existing), name) - fail(stmt->location, msg) - - ft->types = realloc(ft->types, sizeof(ft->types[0]) * (ft->ntypes + 1)) - assert ft->types != NULL - ft->types[ft->ntypes++] = TypeAndUsedPtr{type=t, usedptr=NULL} - - ft->owned_types = realloc(ft->owned_types, sizeof(ft->owned_types[0]) * (ft->n_owned_types + 1)) - assert ft->owned_types != NULL - ft->owned_types[ft->n_owned_types++] = t - - es = ExportSymbol{kind = ExportSymbolKind::Type, type = t} - assert sizeof(es.name) == sizeof(name) - strcpy(es.name, name) - - exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) - assert exports != NULL - exports[nexports++] = es - - exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) - assert exports != NULL - exports[nexports] = ExportSymbol{} # list terminator - return exports +import "../ast.jou" +import "../errors_and_warnings.jou" +import "../evaluate.jou" +import "../types.jou" +import "./common.jou" -def evaluate_array_length(expr: AstExpression*) -> int: - if expr->kind == AstExpressionKind::Int: - return expr->int_value - fail(expr->location, "cannot evaluate array length at compile time") - - -def type_from_ast(ft: FileTypes*, asttype: AstType*) -> Type*: - msg: byte[500] - - if asttype->is_void() or asttype->is_none() or asttype->is_noreturn(): - snprintf(msg, sizeof(msg), "'%s' cannot be used here because it is not a type", asttype->name) - fail(asttype->location, msg) - - if asttype->kind == AstTypeKind::Named: - if strcmp(asttype->name, "short") == 0: - return shortType - if strcmp(asttype->name, "int") == 0: - return intType - if strcmp(asttype->name, "long") == 0: - return longType - if strcmp(asttype->name, "byte") == 0: - return byteType - if strcmp(asttype->name, "bool") == 0: - return boolType - if strcmp(asttype->name, "float") == 0: - return floatType - if strcmp(asttype->name, "double") == 0: - return doubleType - - found = find_type(ft, asttype->name) - if found != NULL: - return found - - snprintf(msg, sizeof(msg), "there is no type named '%s'", asttype->name) - fail(asttype->location, msg) - - if asttype->kind == AstTypeKind::Pointer: - if asttype->value_type->is_void(): - return voidPtrType - return type_from_ast(ft, asttype->value_type)->pointer_type() - - if asttype->kind == AstTypeKind::Array: - tmp = type_from_ast(ft, asttype->value_type) - len = evaluate_array_length(asttype->array.length) - if len <= 0: - fail(asttype->array.length->location, "array length must be positive") - return tmp->array_type(len) - - assert False - -def handle_global_var(ft: FileTypes*, vardecl: AstNameTypeValue*, defined_here: bool) -> ExportSymbol: - assert ft->current_fom_types == NULL # find_any_var() only finds global vars - if find_any_var(ft, vardecl->name) != NULL: - msg: byte[500] - snprintf(msg, sizeof(msg), "a global variable named '%s' already exists", vardecl->name) - fail(vardecl->name_location, msg) - - assert vardecl->value == NULL - g = GlobalVariable{ - type = type_from_ast(ft, &vardecl->type), - defined_in_current_file = defined_here, - } - - assert sizeof(g.name) == sizeof(vardecl->name) - strcpy(g.name, vardecl->name) - - ft->globals = realloc(ft->globals, sizeof(ft->globals[0]) * (ft->nglobals + 1)) - assert ft->globals != NULL - ft->globals[ft->nglobals++] = g - - es = ExportSymbol{kind = ExportSymbolKind::GlobalVar, type = g.type} - assert sizeof(es.name) == sizeof(g.name) - strcpy(es.name, g.name) - return es - -def handle_signature(ft: FileTypes*, astsig: AstSignature*, self_class: Type*) -> Signature: - msg: byte[500] - - if find_function_or_method(ft, self_class, astsig->name) != NULL: - if self_class != NULL: - snprintf(msg, sizeof(msg), "a method named '%s' already exists", astsig->name) - else: - snprintf(msg, sizeof(msg), "a function named '%s' already exists", astsig->name) - fail(astsig->name_location, msg) - - sig = Signature{nargs = astsig->nargs, takes_varargs = astsig->takes_varargs} - assert sizeof(sig.name) == sizeof(astsig->name) - strcpy(sig.name, astsig->name) - - size = sizeof(sig.argnames[0]) * sig.nargs - sig.argnames = malloc(size) - for i = 0; i < sig.nargs; i++: - assert sizeof(sig.argnames[i]) == sizeof(astsig->args[i].name) - strcpy(sig.argnames[i], astsig->args[i].name) - - sig.argtypes = malloc(sizeof(sig.argtypes[0]) * sig.nargs) - for i = 0; i < sig.nargs; i++: - if ( - strcmp(sig.argnames[i], "self") == 0 - and astsig->args[i].type.kind == AstTypeKind::Named - and astsig->args[i].type.name[0] == '\0' - ): - # just "self" without a type after it --> default to "self: Foo*" in class Foo - argtype = self_class->pointer_type() - else: - argtype = type_from_ast(ft, &astsig->args[i].type) - - if strcmp(sig.argnames[i], "self") == 0 and argtype != self_class and argtype != self_class->pointer_type(): - snprintf(msg, sizeof(msg), "type of self must be %s* (default) or %s", self_class->name, self_class->name) - fail(astsig->args[i].type.location, msg) - - sig.argtypes[i] = argtype - - sig.is_noreturn = astsig->return_type.is_noreturn() - if astsig->return_type.is_none() or astsig->return_type.is_noreturn(): - sig.returntype = NULL - elif astsig->return_type.is_void(): - fail(astsig->return_type.location, "void is not a valid return type, use '-> None' if the function does not return a value") - else: - sig.returntype = type_from_ast(ft, &astsig->return_type) - - if self_class == NULL and strcmp(sig.name, "main") == 0: - # special main() function checks - if sig.returntype != intType: - fail(astsig->return_type.location, "the main() function must return int") - if ( - sig.nargs != 0 - and not ( - sig.nargs == 2 - and sig.argtypes[0] == intType - and sig.argtypes[1] == byteType->pointer_type()->pointer_type() - ) - ): - fail( - astsig->args[0].type.location, - "if the main() function takes parameters, it should be defined like this: def main(argc: int, argv: byte**) -> int" - ) - - sig.returntype_location = astsig->return_type.location - - if self_class == NULL: - ft->functions = realloc(ft->functions, sizeof(ft->functions[0]) * (ft->nfunctions + 1)) - assert ft->functions != NULL - ft->functions[ft->nfunctions++] = SignatureAndUsedPtr{ - signature = sig.copy(), - usedptr = NULL, - } - - return sig - -def handle_class_members_stage2(ft: FileTypes*, classdef: AstClassDef*) -> None: - # Previous type-checking stage created an opaque struct. - type: Type* = NULL - for s = ft->owned_types; s < &ft->owned_types[ft->n_owned_types]; s++: - if strcmp((*s)->name, classdef->name) == 0: - type = *s - break - - assert type != NULL - assert type->kind == TypeKind::OpaqueClass - type->kind = TypeKind::Class - - memset(&type->classdata, 0, sizeof type->classdata) - - union_id = 0 - for m = classdef->members; m < &classdef->members[classdef->nmembers]; m++: - if m->kind == AstClassMemberKind::Field: - f = ClassField{ - type = type_from_ast(ft, &m->field.type), - union_id = union_id++, - } - assert sizeof(f.name) == sizeof(m->field.name) - strcpy(f.name, m->field.name) - - type->classdata.fields = realloc(type->classdata.fields, sizeof(type->classdata.fields[0]) * (type->classdata.nfields + 1)) - assert type->classdata.fields != NULL - type->classdata.fields[type->classdata.nfields++] = f - - elif m->kind == AstClassMemberKind::Union: - uid = union_id++ - for ntv = m->union_fields.fields; ntv < &m->union_fields.fields[m->union_fields.nfields]; ntv++: - f = ClassField{ - type = type_from_ast(ft, &ntv->type), - union_id = uid, - } - assert sizeof(f.name) == sizeof(ntv->name) - strcpy(f.name, ntv->name) - - type->classdata.fields = realloc(type->classdata.fields, sizeof(type->classdata.fields[0]) * (type->classdata.nfields + 1)) - assert type->classdata.fields != NULL - type->classdata.fields[type->classdata.nfields++] = f - - elif m->kind == AstClassMemberKind::Method: - # Don't handle the method body yet: that is a part of stage 3, not stage 2 - sig = handle_signature(ft, &m->method.signature, type) - - type->classdata.methods = realloc(type->classdata.methods, sizeof(type->classdata.methods[0]) * (type->classdata.nmethods + 1)) - assert type->classdata.methods != NULL - type->classdata.methods[type->classdata.nmethods++] = sig - - else: - assert False - - -def typecheck_stage2_populate_types(ft: FileTypes*, ast: AstFile*) -> ExportSymbol*: - exports: ExportSymbol* = NULL - nexports = 0 - - for i = 0; i < ast->body.nstatements; i++: - stmt = &ast->body.statements[i] - - exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) - assert exports != NULL - - if stmt->kind == AstStatementKind::GlobalVariableDeclaration: - exports[nexports++] = handle_global_var(ft, &stmt->var_declaration, False) - elif stmt->kind == AstStatementKind::GlobalVariableDefinition: - exports[nexports++] = handle_global_var(ft, &stmt->var_declaration, True) - elif stmt->kind == AstStatementKind::Function: - sig = handle_signature(ft, &stmt->function.signature, NULL) - es = ExportSymbol{kind = ExportSymbolKind::Function, funcsignature = sig} - assert sizeof(es.name) == sizeof(sig.name) - strcpy(es.name, sig.name) - exports[nexports++] = es - elif stmt->kind == AstStatementKind::Class: - handle_class_members_stage2(ft, &stmt->classdef) - elif stmt->kind == AstStatementKind::Enum: - pass # Everything done in previous type-checking steps. - else: - assert False - - exports = realloc(exports, sizeof(exports[0]) * (nexports + 1)) - assert exports != NULL - exports[nexports] = ExportSymbol{} - return exports - -def add_variable(ft: FileTypes*, t: Type*, name: byte*) -> LocalVariable*: - var: LocalVariable* = calloc(1, sizeof *var) - var->id = ft->current_fom_types->nlocals - var->type = t - - assert name != NULL - assert find_local_var(ft, name) == NULL - assert strlen(name) < sizeof(var->name) - strcpy(var->name, name) - - ft->current_fom_types->locals = realloc(ft->current_fom_types->locals, sizeof(ft->current_fom_types->locals[0]) * (ft->current_fom_types->nlocals + 1)) - assert ft->current_fom_types->locals != NULL - ft->current_fom_types->locals[ft->current_fom_types->nlocals++] = var - - return var - -global short_expr_desc_result: byte[200] # Intended for errors. Returned string can be overwritten in next call. # Imagine "cannot assign to" in front of these, e.g. "cannot assign to a constant" -def short_expression_description(expr: AstExpression*) -> byte*: +def short_expression_description(expr: AstExpression*) -> byte[200]: if ( expr->kind == AstExpressionKind::String or expr->kind == AstExpressionKind::Int @@ -462,13 +103,16 @@ def short_expression_description(expr: AstExpression*) -> byte*: ): return "the result of decrementing a value" + result: byte[200] + if expr->kind == AstExpressionKind::AddressOf: - snprintf(short_expr_desc_result, sizeof short_expr_desc_result, "address of %s", short_expression_description(&expr->operands[0])) - return short_expr_desc_result + inner = short_expression_description(&expr->operands[0]) + snprintf(result, sizeof result, "address of %s", inner) + return result if expr->kind == AstExpressionKind::GetClassField: - snprintf(short_expr_desc_result, sizeof short_expr_desc_result, "field '%s'", expr->class_field.field_name) - return short_expr_desc_result + snprintf(result, sizeof result, "field '%s'", expr->class_field.field_name) + return result assert False @@ -528,9 +172,11 @@ def ensure_can_take_address(fom: FunctionOrMethodTypes*, expr: AstExpression*, e return msg: byte[500] - snprintf(msg, sizeof(msg), errmsg_template, short_expression_description(expr)) + desc = short_expression_description(expr) + snprintf(msg, sizeof(msg), errmsg_template, desc) fail(expr->location, msg) + # Implicit casts are used in many places, e.g. function arguments. # # When you pass an argument of the wrong type, it's best to give an error message @@ -585,6 +231,7 @@ def can_cast_implicitly(from: Type*, to: Type*) -> bool: or (from->is_pointer_type() and to->is_pointer_type() and (from == voidPtrType or to == voidPtrType)) ) + def can_cast_explicitly(from: Type*, to: Type*) -> bool: return ( from == to @@ -599,6 +246,7 @@ def can_cast_explicitly(from: Type*, to: Type*) -> bool: or (from == longType and to->is_pointer_type()) ) + def do_implicit_cast( fom: FunctionOrMethodTypes*, types: ExpressionTypes*, @@ -638,10 +286,12 @@ def do_implicit_cast( "cannot create a pointer into an array that comes from %s (try storing it to a local variable first)" ) + def cast_array_to_pointer(fom: FunctionOrMethodTypes*, types: ExpressionTypes*) -> None: assert types->type->kind == TypeKind::Array do_implicit_cast(fom, types, types->type->array.item_type->pointer_type(), Location{}, NULL) + def do_explicit_cast(fom: FunctionOrMethodTypes*, types: ExpressionTypes*, to: Type*, location: Location) -> None: assert types->implicit_cast_type == NULL from = types->type @@ -659,6 +309,7 @@ def do_explicit_cast(fom: FunctionOrMethodTypes*, types: ExpressionTypes*, to: T if from->kind == TypeKind::Array and to->is_pointer_type(): cast_array_to_pointer(fom, types) + def typecheck_expression_not_void(ft: FileTypes*, expr: AstExpression*) -> ExpressionTypes*: types: ExpressionTypes* = typecheck_expression(ft, expr) if types != NULL: @@ -674,6 +325,7 @@ def typecheck_expression_not_void(ft: FileTypes*, expr: AstExpression*) -> Expre snprintf(msg, sizeof(msg), "method '%s' does not return a value", expr->call.name) fail(expr->location, msg) + def typecheck_expression_with_implicit_cast( ft: FileTypes*, expr: AstExpression*, @@ -683,6 +335,7 @@ def typecheck_expression_with_implicit_cast( types = typecheck_expression_not_void(ft, expr) do_implicit_cast(ft->current_fom_types, types, casttype, expr->location, errormsg_template) + def check_binop( fom: FunctionOrMethodTypes*, op: AstExpressionKind, @@ -817,6 +470,7 @@ def check_increment_or_decrement(ft: FileTypes*, expr: AstExpression*) -> Type*: fail(expr->location, msg) return t + def typecheck_dereferenced_pointer(location: Location, t: Type*) -> None: # TODO: improved error message for dereferencing void* if t->kind != TypeKind::Pointer: @@ -824,6 +478,7 @@ def typecheck_dereferenced_pointer(location: Location, t: Type*) -> None: snprintf(msg, sizeof(msg), "the dereference operator '*' is only for pointers, not for %s", t->name) fail(location, msg) + # ptr[index] def typecheck_indexing( ft: FileTypes*, @@ -873,19 +528,17 @@ def typecheck_and_or( typecheck_expression_with_implicit_cast(ft, rhsexpr, boolType, errormsg) -global nth_result_buffer: byte[100] +def nth(n: int) -> byte[100]: + result: byte[100] - -# Be aware that return value may change as you call this many times. -def nth(n: int) -> byte*: assert n >= 1 - first_few = [NULL, "first", "second", "third", "fourth", "fifth", "sixth"] if n < sizeof(first_few) / sizeof(first_few[0]): - return first_few[n] + strcpy(result, first_few[n]) + else: + sprintf(result, "%dth", n) - sprintf(nth_result_buffer, "%dth", n) - return nth_result_buffer + return result def plural_s(n: int) -> byte*: @@ -901,7 +554,7 @@ def plural_s(n: int) -> byte*: def typecheck_function_or_method_call(ft: FileTypes*, call: AstCall*, self_type: Type*, location: Location) -> Type*: msg: byte[500] - sig = find_function_or_method(ft, self_type, call->name) + sig = ft->find_function_or_method(self_type, call->name) if sig == NULL: if self_type == NULL: snprintf(msg, sizeof(msg), "function '%s' not found", call->name) @@ -910,7 +563,7 @@ def typecheck_function_or_method_call(ft: FileTypes*, call: AstCall*, self_type: msg, sizeof(msg), "class %s does not have a method named '%s'", self_type->name, call->name) - elif self_type->kind == TypeKind::Pointer and find_method(self_type->value_type, call->name) != NULL: + elif self_type->kind == TypeKind::Pointer and self_type->value_type->find_method(call->name) != NULL: snprintf( msg, sizeof(msg), "the method '%s' is defined on class %s, not on the pointer type %s, so you need to dereference the pointer first (e.g. by using '->' instead of '.')", @@ -952,7 +605,11 @@ def typecheck_function_or_method_call(ft: FileTypes*, call: AstCall*, self_type: if strcmp(sig->argnames[i], "self") == 0: continue # This is a common error, so worth spending some effort to get a good error message. - snprintf(msg, sizeof msg, "%s argument of %s %s should have type , not ", nth(i+1), function_or_method, sigstr) + which_arg = nth(i+1) + snprintf( + msg, sizeof msg, + "%s argument of %s %s should have type , not ", which_arg, function_or_method, sigstr + ) typecheck_expression_with_implicit_cast(ft, &call->args[k++], sig->argtypes[i], msg) for i = k; i < call->nargs; i++: @@ -1123,7 +780,7 @@ def typecheck_expression(ft: FileTypes*, expr: AstExpression*) -> ExpressionType result = byteType->pointer_type() elif expr->kind == AstExpressionKind::GetEnumMember: - result = find_type(ft, expr->enum_member.enum_name) + result = ft->find_type(expr->enum_member.enum_name) if result == NULL: snprintf(msg, sizeof(msg), "there is no type named '%s'", expr->enum_member.enum_name) fail(expr->location, msg) @@ -1195,24 +852,16 @@ def typecheck_expression(ft: FileTypes*, expr: AstExpression*) -> ExpressionType result = typecheck_function_or_method_call(ft, &expr->call, temptype, expr->location) # If self argument is passed by pointer, make sure we can create that pointer - found = False assert temptype->kind == TypeKind::Class - for m = temptype->classdata.methods; m < &temptype->classdata.methods[temptype->classdata.nmethods]; m++: - if strcmp(m->name, expr->call.name) != 0: - continue - - if m->argtypes[0]->is_pointer_type(): - assert strstr(expr->call.name, "%") == NULL - snprintf( - msg, sizeof msg, - "cannot take address of %%s, needed for calling the %s() method", - expr->call.name) - ensure_can_take_address(ft->current_fom_types, expr->call.method_call_self, msg) - - found = True - break - - assert found + signature = temptype->find_method(expr->call.name) + assert signature != NULL + if signature->argtypes[0]->is_pointer_type(): + assert strstr(expr->call.name, "%") == NULL + snprintf( + msg, sizeof msg, + "cannot take address of %%s, needed for calling the %s() method", + expr->call.name) + ensure_can_take_address(ft->current_fom_types, expr->call.method_call_self, msg) if result == NULL: # no return value produced @@ -1227,13 +876,13 @@ def typecheck_expression(ft: FileTypes*, expr: AstExpression*) -> ExpressionType result = temptype->pointer_type() elif expr->kind == AstExpressionKind::GetVariable: - result = find_any_var(ft, expr->varname) + result = ft->find_any_var(expr->varname) if result == NULL: snprintf(msg, sizeof(msg), "no variable named '%s'", expr->varname) fail(expr->location, msg) elif expr->kind == AstExpressionKind::Self: - selfvar = find_local_var(ft, "self") + selfvar = ft->find_local_var("self") assert selfvar != NULL result = selfvar->type @@ -1329,6 +978,7 @@ def typecheck_if_statement(ft: FileTypes*, ifstmt: AstIfStatement*) -> None: typecheck_body(ft, &ifstmt->else_body) + def typecheck_statement(ft: FileTypes*, stmt: AstStatement*) -> None: msg: byte[500] @@ -1362,11 +1012,11 @@ def typecheck_statement(ft: FileTypes*, stmt: AstStatement*) -> None: if ( targetexpr->kind == AstExpressionKind::GetVariable - and find_any_var(ft, targetexpr->varname) == NULL + and ft->find_any_var(targetexpr->varname) == NULL ): # Making a new variable. Use the type of the value being assigned. types = typecheck_expression_not_void(ft, valueexpr) - add_variable(ft, types->type, targetexpr->varname) + ft->current_fom_types->add_variable(types->type, targetexpr->varname) else: # Convert value to the type of an existing variable or other assignment target. ensure_can_take_address(ft->current_fom_types, targetexpr, "cannot assign to %s") @@ -1374,9 +1024,8 @@ def typecheck_statement(ft: FileTypes*, stmt: AstStatement*) -> None: if targetexpr->kind == AstExpressionKind::Dereference: strcpy(msg, "cannot place a value of type into a pointer of type *") else: - snprintf(msg, sizeof msg, - "cannot assign a value of type to %s of type ", - short_expression_description(targetexpr)) + desc = short_expression_description(targetexpr) + snprintf(msg, sizeof msg, "cannot assign a value of type to %s of type ", desc) targettypes = typecheck_expression_not_void(ft, targetexpr) typecheck_expression_with_implicit_cast(ft, valueexpr, targettypes->type, msg) @@ -1449,15 +1098,15 @@ def typecheck_statement(ft: FileTypes*, stmt: AstStatement*) -> None: "attempting to return a value of type from function '%s' defined with '-> '", ft->current_fom_types->signature.name) typecheck_expression_with_implicit_cast( - ft, stmt->return_value, find_local_var(ft, "return")->type, msg) + ft, stmt->return_value, ft->find_local_var("return")->type, msg) elif stmt->kind == AstStatementKind::DeclareLocalVar: - if find_any_var(ft, stmt->var_declaration.name) != NULL: + if ft->find_any_var(stmt->var_declaration.name) != NULL: snprintf(msg, sizeof(msg), "a variable named '%s' already exists", stmt->var_declaration.name) fail(stmt->location, msg) type = type_from_ast(ft, &stmt->var_declaration.type) - add_variable(ft, type, stmt->var_declaration.name) + ft->current_fom_types->add_variable(type, stmt->var_declaration.name) if stmt->var_declaration.value != NULL: typecheck_expression_with_implicit_cast( @@ -1485,27 +1134,22 @@ def typecheck_function_or_method_body(ft: FileTypes*, sig: Signature*, body: Ast ft->current_fom_types->signature = sig->copy() for i = 0; i < sig->nargs; i++: - v = add_variable(ft, sig->argtypes[i], sig->argnames[i]) + v = ft->current_fom_types->add_variable(sig->argtypes[i], sig->argnames[i]) v->is_argument = True if sig->returntype != NULL: - add_variable(ft, sig->returntype, "return") + ft->current_fom_types->add_variable(sig->returntype, "return") typecheck_body(ft, body) ft->current_fom_types = NULL -def typecheck_stage3_function_and_method_bodies(ft: FileTypes*, ast: AstFile*) -> None: +def typecheck_step3_function_and_method_bodies(ft: FileTypes*, ast: AstFile*) -> None: for i = 0; i < ast->body.nstatements; i++: stmt = &ast->body.statements[i] if stmt->kind == AstStatementKind::Function and stmt->function.body.nstatements > 0: - sig: Signature* = NULL - for f = ft->functions; f < &ft->functions[ft->nfunctions]; f++: - if strcmp(f->signature.name, stmt->function.signature.name) == 0: - sig = &f->signature - break + sig = ft->find_function(stmt->function.signature.name) assert sig != NULL - typecheck_function_or_method_body(ft, sig, &stmt->function.body) if stmt->kind == AstStatementKind::Class: @@ -1521,10 +1165,6 @@ def typecheck_stage3_function_and_method_bodies(ft: FileTypes*, ast: AstFile*) - continue method = &m->method - sig = NULL - for s = classtype->classdata.methods; s < &classtype->classdata.methods[classtype->classdata.nmethods]; s++: - if strcmp(s->name, method->signature.name) == 0: - sig = s - break + sig = classtype->find_method(method->signature.name) assert sig != NULL typecheck_function_or_method_body(ft, sig, &method->body) diff --git a/compiler/types.jou b/compiler/types.jou index 4daff9ab..0e1d7735 100644 --- a/compiler/types.jou +++ b/compiler/types.jou @@ -89,6 +89,15 @@ class Type: info->arrays[info->narrays++] = arr return &arr->type + def find_method(self, name: byte*) -> Signature*: + if self->kind != TypeKind::Class: + return NULL + + for m = self->classdata.methods; m < &self->classdata.methods[self->classdata.nmethods]; m++: + if strcmp(m->name, name) == 0: + return m + return NULL + # The TypeInfo for type T contains the type T* (if it has been used) # and all array and pointer types with element type T.