#include #include #include #include #include #include #include #include #include "ast.h" #include "check.h" #include "eval.h" #include "expr.h" #include "identifier.h" #include "mod.h" #include "scope.h" #include "type_store.h" #include "typedef.h" #include "types.h" #include "util.h" struct ident * mkident(struct context *ctx, struct ident *in, const char *symbol) { if (symbol) { return intern_name(ctx->itbl, symbol); } else if (ctx->ns && in->ns == NULL) { return intern_ident(ctx->itbl, in->name, ctx->ns); } else { return in; } } static struct ident * intern_generated(struct context *ctx, const char *template) { const char *s = intern_owned(ctx->itbl, gen_name(&ctx->id, template)); return intern_name(ctx->itbl, s); } void mkstrliteral(struct expression *expr, const char *fmt, ...) { va_list ap; va_start(ap, fmt); size_t n = vsnprintf(NULL, 0, fmt, ap); va_end(ap); char *s = xcalloc(n + 1, n); va_start(ap, fmt); vsnprintf(s, n + 1, fmt, ap); va_end(ap); *expr = (struct expression) { .type = EXPR_LITERAL, .result = &builtin_type_str, }; expr->literal.string.value = s; expr->literal.string.len = n; } char * gen_typename(const struct type *type) { size_t sz = 0; char *ptr = NULL; FILE *f = open_memstream(&ptr, &sz); if (f == NULL) { xfprintf(stderr, "Unable to open memstream: %s\n", strerror(errno)); exit(EXIT_ABNORMAL); } emit_type(type, f); fclose(f); return ptr; } static void handle_errors(struct errors *errors) { struct errors *error = errors; while (error) { xfprintf(stderr, "%s:%d:%d: error: %s\n", sources[error->loc.file], error->loc.lineno, error->loc.colno, error->msg); errline(error->loc); free(error->msg); struct errors *next = error->next; free(error); error = next; } if (errors) { exit(EXIT_CHECK); } } static void mkerror(struct expression *expr) { expr->type = EXPR_LITERAL; expr->result = &builtin_type_invalid; expr->literal.uval = 0; expr->loc = (struct location){0}; } static void verror(struct context *ctx, const struct location loc, const char *fmt, va_list ap) { va_list copy; va_copy(copy, ap); size_t sz = vsnprintf(NULL, 0, fmt, copy); va_end(copy); char *msg = xcalloc(sz + 1, 1); vsnprintf(msg, sz + 1, fmt, ap); struct errors *next = *ctx->next = xcalloc(1, sizeof(struct errors)); next->loc = loc; next->msg = msg; ctx->next = &next->next; } void error(struct context *ctx, struct location loc, struct expression *expr, const char *fmt, ...) { if (expr) { mkerror(expr); expr->loc = loc; } va_list ap; va_start(ap, fmt); verror(ctx, loc, fmt, ap); va_end(ap); } FORMAT(3, 4) static noreturn void error_norec(struct context *ctx, struct location loc, const char *fmt, ...) { va_list ap; va_start(ap, fmt); verror(ctx, loc, fmt, ap); va_end(ap); handle_errors(ctx->errors); abort(); } struct expression * lower_implicit_cast(struct context *ctx, const struct type *to, struct expression *expr) { if (to == expr->result || expr->result->storage == STORAGE_NEVER) { return expr; } if (type_dealias(ctx, to)->storage == STORAGE_TAGGED) { const struct type *interim = tagged_select_subtype(ctx, to, expr->result, true); if (interim) { expr = lower_implicit_cast(ctx, interim, expr); } } struct expression *cast = xcalloc(1, sizeof(struct expression)); cast->type = EXPR_CAST; cast->loc = expr->loc; cast->result = cast->cast.secondary = to; cast->cast.kind = C_CAST; cast->cast.value = expr; cast->cast.lowered = true; return cast; } static void resolve_decl(struct context *ctx, struct scope_object *obj); static const struct type * check_autodereference(struct context *ctx, struct location loc, const struct type *type) { const struct type *dtype = type_dereference(ctx, type, false); if (dtype == NULL) { error(ctx, loc, NULL, "Cannot autodereference a nullable pointer"); } return type_dereference(ctx, type, true); } struct match_context { struct context *ctx; // Type of the object being matched const struct type *otype; // Type the object type refers to, in the case of a pointer const struct type *ref_type; // Derived from otype bool is_tagged, is_nullable_ptr, is_tagged_ptr; }; // Returns false if an error occured static bool begin_check_match(struct context *ctx, struct match_context *mctx, struct expression *expr, const struct type *otype, struct location oloc) { mctx->ctx = ctx; mctx->otype = otype; const struct type *type = type_dealias(ctx, otype); if (type->storage == STORAGE_INVALID) { mkerror(expr); return false; } mctx->is_tagged = type->storage == STORAGE_TAGGED; mctx->is_nullable_ptr = false; mctx->is_tagged_ptr = false; mctx->ref_type = NULL; if (type->storage == STORAGE_POINTER) { mctx->is_nullable_ptr = type->pointer.nullable; mctx->ref_type = type_dealias(ctx, type->pointer.referent); if (mctx->ref_type->storage == STORAGE_INVALID) { mkerror(expr); return false; } mctx->is_tagged_ptr = mctx->ref_type->storage == STORAGE_TAGGED; } if (!mctx->is_tagged && !mctx->is_nullable_ptr && !mctx->is_tagged_ptr) { error(ctx, oloc, expr, "Value is not tagged union, pointer to tagged union, or nullable pointer type"); return false; } return true; } static const char * check_match_case_nullable_ptr(struct match_context *mctx, const struct type *ctype) { // match (e: nullable *ref_type) { // case ctype => // Null has already been handled. if (ctype->storage != STORAGE_POINTER) { return "Match on nullable pointer: case is not null or pointer type"; } else if (mctx->ref_type != type_dealias(mctx->ctx, ctype->pointer.referent)) { return "Match on nullable pointer: case has invalid pointer type"; } return NULL; } static const char * check_match_case_tagged(struct match_context *mctx, const struct type *ctype) { const struct type *type = type_dealias(mctx->ctx, mctx->otype); // match (e: type) { // case ctype => // TODO: Assign a score to tagged compatibility // and choose the branch with the highest score. if (!type_is_assignable(mctx->ctx, type, ctype)) { return "Match on tagged union: case is not assignable to match type"; } return NULL; } static const char * check_match_case_tagged_ptr(struct match_context *mctx, const struct type *ctype) { // match (e: *ref_type) { // case ctype => if (ctype->size == 0) { if (!type_is_assignable(mctx->ctx, mctx->ref_type, ctype)) { return "Match on pointer to tagged union: zero-sized case type is not assignable to match type"; } } else if (ctype->storage == STORAGE_NULL) { // XXX: The purpose of this branch is to prevent `x as null`. // Should we allow that, though? return "Cannot match with null in this context"; } else if (ctype->storage != STORAGE_POINTER) { return "Match on pointer to tagged union: finite-sized case type is not a pointer"; } else if (!type_is_assignable(mctx->ctx, mctx->ref_type, ctype->pointer.referent)) { return "Match on pointer to tagged union: case is not assignable to match type"; } return NULL; } static bool check_match_case(struct match_context *mctx, const struct type *ctype, struct expression *expr, struct location loc) { const char *err_msg = NULL; if (ctype->storage == STORAGE_NULL && mctx->is_nullable_ptr) { // Ok in all cases. } else if (mctx->is_nullable_ptr && !mctx->is_tagged_ptr) { err_msg = check_match_case_nullable_ptr(mctx, ctype); } else if (mctx->is_tagged_ptr) { err_msg = check_match_case_tagged_ptr(mctx, ctype); } else { assert(mctx->is_tagged); err_msg = check_match_case_tagged(mctx, ctype); } if (err_msg) { error(mctx->ctx, loc, expr, "%s", err_msg); return false; } return true; } static void check_expr_access(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_ACCESS; expr->access.type = aexpr->access.type; struct scope_object *obj = NULL; switch (expr->access.type) { case ACCESS_IDENTIFIER: obj = scope_lookup(ctx->scope, aexpr->access.ident); if (!obj) { char buf[IDENT_BUFSIZ]; ident_unparse_static(aexpr->access.ident, buf); error(ctx, aexpr->loc, expr, "Unknown object '%s'", buf); return; } wrap_resolver(ctx, obj, resolve_decl); switch (obj->otype) { case O_CONST: // Lower flexible types *expr = *obj->value; expr->loc = aexpr->loc; flexible_reset_refs(expr->result); break; case O_BIND: case O_DECL: expr->result = obj->type; expr->access.object = obj; break; case O_TYPE: if (type_dealias(ctx, obj->type)->storage != STORAGE_VOID && type_dealias(ctx, obj->type)->storage != STORAGE_DONE) { char *ident = ident_unparse(obj->type->alias.ident); error(ctx, aexpr->loc, expr, "Cannot use non void or done type alias '%s' as literal", ident); free(ident); return; } expr->type = EXPR_LITERAL; expr->result = obj->type; break; case O_SCAN: assert(0); // handled above } break; case ACCESS_INDEX: expr->access.array = xcalloc(1, sizeof(struct expression)); expr->access.index = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->access.array, expr->access.array, NULL); check_expression(ctx, aexpr->access.index, expr->access.index, &builtin_type_size); const struct type *atype = check_autodereference(ctx, aexpr->access.array->loc, expr->access.array->result); atype = type_dealias(ctx, atype); if (atype->storage == STORAGE_INVALID) { mkerror(expr); return; } const struct type *itype = type_dealias(ctx, expr->access.index->result); if (atype->storage != STORAGE_ARRAY && atype->storage != STORAGE_SLICE) { error(ctx, aexpr->access.array->loc, expr, "Can only index into array or slice object, but got %s", type_storage_unparse(atype->storage)); return; } if (atype->storage == STORAGE_SLICE && atype->array.members->size == SIZE_UNDEFINED) { error(ctx, aexpr->access.array->loc, expr, "Cannot use index into slice whose member type has undefined size"); return; } if (!type_is_integer(ctx, itype)) { error(ctx, aexpr->access.index->loc, expr, "Cannot use non-integer %s type as slice/array index", type_storage_unparse(itype->storage)); return; } expr->access.index = lower_implicit_cast(ctx, &builtin_type_size, expr->access.index); expr->result = atype->array.members; // Compile-time bounds check if (atype->storage == STORAGE_ARRAY && atype->array.length != SIZE_UNDEFINED) { struct expression *evaled = xcalloc(1, sizeof(struct expression)); if (eval_expr(ctx, expr->access.index, evaled)) { if (evaled->literal.uval >= atype->array.length) { error(ctx, aexpr->loc, expr, "Index must be less than array length"); free(evaled); return; } expr->access.bounds_checked = true; } free(evaled); } break; case ACCESS_FIELD: expr->access._struct = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->access._struct, expr->access._struct, NULL); const struct type *stype = check_autodereference(ctx, aexpr->access._struct->loc, expr->access._struct->result); stype = type_dealias(ctx, stype); if (stype->storage == STORAGE_INVALID) { mkerror(expr); return; } if (stype->storage != STORAGE_STRUCT && stype->storage != STORAGE_UNION) { error(ctx, aexpr->access._struct->loc, expr, "Cannot select field from non-struct, non-union object"); return; } expr->access.field = type_get_field(ctx, stype, aexpr->access.field); if (!expr->access.field) { error(ctx, aexpr->access._struct->loc, expr, "No such struct field '%s'", aexpr->access.field); return; } expr->result = expr->access.field->type; break; case ACCESS_TUPLE: expr->access.tuple = xcalloc(1, sizeof(struct expression)); struct expression *value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->access.tuple, expr->access.tuple, NULL); check_expression(ctx, aexpr->access.value, value, NULL); assert(value->type == EXPR_LITERAL); const struct type *ttype = check_autodereference(ctx, aexpr->access.tuple->loc, expr->access.tuple->result); ttype = type_dealias(ctx, ttype); if (ttype->storage == STORAGE_INVALID) { mkerror(expr); return; } if (ttype->storage != STORAGE_TUPLE) { error(ctx, aexpr->access.tuple->loc, expr, "Cannot select value from non-tuple object"); return; } if (!type_is_integer(ctx, value->result)) { error(ctx, aexpr->access.tuple->loc, expr, "Cannot use non-integer literal to select tuple value"); return; } expr->access.tvalue = type_get_value(ttype, aexpr->access.value->literal.uval); if (!expr->access.tvalue) { error(ctx, aexpr->access.tuple->loc, expr, "No such tuple value '%" PRIu64 "'", aexpr->access.value->literal.uval); return; } expr->access.tindex = aexpr->access.value->literal.uval; expr->result = expr->access.tvalue->type; break; } } static void check_expr_alloc_init(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *inithint, bool nullable) { // alloc(initializer) case check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint); if (expr->alloc.init->result->storage == STORAGE_INVALID) { mkerror(expr); return; } const struct type *objtype = expr->alloc.init->result; if (objtype->storage == STORAGE_UNDEFINED) { if (!inithint) { error(ctx, aexpr->loc, expr, "Cannot infer @undefined type without type hint"); return; } objtype = inithint; } if (type_dealias(ctx, objtype)->storage == STORAGE_ARRAY && type_dealias(ctx, objtype)->array.expandable) { const struct type *atype = type_dealias(ctx, objtype); if (!inithint) { error(ctx, aexpr->loc, expr, "Cannot infer expandable array length without type hint"); return; } const struct type *htype = type_dealias(ctx, inithint); if (htype->storage != STORAGE_ARRAY) { error(ctx, aexpr->loc, expr, "Cannot assign expandable array from non-array type"); return; } assert(htype->array.members == atype->array.members); objtype = inithint; } if (type_is_flexible(objtype) && inithint) { const struct type *promoted = promote_flexible(ctx, objtype, inithint); if (promoted) { objtype = promoted; } } else if (inithint) { uint32_t objtype_id = type_dealias(ctx, objtype)->id; uint32_t inithint_id = type_dealias(ctx, inithint)->id; if (objtype_id == inithint_id) { objtype = inithint; } } expr->alloc.allocation_result = type_store_lookup_pointer(ctx, aexpr->loc, objtype, nullable); const struct type *initresult = expr->alloc.init->result; if (initresult->storage != STORAGE_UNDEFINED && initresult->size == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "Cannot allocate object of undefined size"); return; } } static void check_expr_alloc_cap(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *inithint) { // alloc(init, length/capacity) case check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint); if (expr->alloc.init->result->storage == STORAGE_INVALID) { mkerror(expr); return; } const struct type *objtype = expr->alloc.init->result; if (type_dealias(ctx, objtype)->storage == STORAGE_ARRAY) { if (type_dealias(ctx, objtype)->array.length == SIZE_UNDEFINED) { error(ctx, aexpr->alloc.init->loc, expr, "Slice initializer must have defined length"); return; } } else if (type_dealias(ctx, objtype)->storage != STORAGE_SLICE) { error(ctx, aexpr->alloc.init->loc, expr, "Slice initializer must be of slice or array type, not %s", type_storage_unparse(type_dealias(ctx, objtype)->storage)); return; } const struct type *caphint = &builtin_type_size; expr->alloc.cap = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->alloc.cap, expr->alloc.cap, caphint); const struct type *captype = expr->alloc.cap->result; if (!type_is_assignable(ctx, &builtin_type_size, captype)) { error(ctx, aexpr->alloc.cap->loc, expr, "Slice capacity must be assignable to size"); return; } expr->alloc.cap = lower_implicit_cast(ctx, &builtin_type_size, expr->alloc.cap); struct expression cap = {0}; if (expr->alloc.init->type == EXPR_LITERAL && expr->alloc.cap->type == EXPR_LITERAL && eval_expr(ctx, expr->alloc.cap, &cap)) { uint64_t len = 0; for (struct array_literal *c = expr->alloc.init->literal.array; c != NULL; c = c->next) { len++; } if (cap.literal.uval < len) { error(ctx, aexpr->alloc.cap->loc, expr, "Slice capacity cannot be smaller than length of initializer"); return; } } const struct type *membtype = type_dealias(ctx, objtype)->array.members; expr->alloc.allocation_result = type_store_lookup_slice(ctx, aexpr->alloc.init->loc, membtype); if (objtype->storage == STORAGE_ARRAY && objtype->array.expandable) { expr->alloc.kind = ALLOC_LEN; } } static void check_expr_alloc_copy(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *inithint) { // alloc(init...) case check_expression(ctx, aexpr->alloc.init, expr->alloc.init, inithint); if (expr->alloc.init->result->storage == STORAGE_INVALID) { mkerror(expr); return; } const struct type *result = type_dealias(ctx, expr->alloc.init->result); if (result->storage != STORAGE_ARRAY && result->storage != STORAGE_SLICE) { error(ctx, aexpr->alloc.init->loc, expr, "Slice initializer must be of slice or array type, not %s", type_storage_unparse(result->storage)); return; } if (result->storage == STORAGE_ARRAY) { if (result->array.expandable) { error(ctx, aexpr->alloc.init->loc, NULL, "Slice initializer can't be an expandable array"); } else if (result->array.length == SIZE_UNDEFINED) { error(ctx, aexpr->alloc.init->loc, NULL, "Slice initializer can't be an unbounded array"); } // can recover from error } result = type_dealias(ctx, expr->alloc.init->result); expr->alloc.allocation_result = type_store_lookup_slice(ctx, aexpr->alloc.init->loc, result->array.members); } static void alloc_inithint(struct context *ctx, const struct type *hint, enum alloc_kind kind, const struct type **inithint, bool *nullable) { const struct type *htype = NULL; hint = type_dealias(ctx, hint); switch (hint->storage) { case STORAGE_TAGGED: if (hint->tagged.len != 2) { *inithint = NULL; return; } if (hint->tagged.types[0] == &builtin_type_nomem) { htype = hint->tagged.types[1]; } else if (hint->tagged.types[1] == &builtin_type_nomem) { htype = hint->tagged.types[0]; } else { *inithint = NULL; return; } break; case STORAGE_POINTER: case STORAGE_SLICE: // handle cases such as // let a: alloc(0) as *u8; // let b: []u8 = alloc([0])!; htype = hint; break; default: *inithint = NULL; return; } switch (htype->storage) { case STORAGE_POINTER: if (kind == ALLOC_OBJECT) { *inithint = htype->pointer.referent; *nullable = htype->pointer.nullable; } break; case STORAGE_SLICE: *inithint = hint; break; default: *inithint = NULL; return; }; } static void check_expr_alloc(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { assert(aexpr->type == EXPR_ALLOC); expr->type = EXPR_ALLOC; expr->result = &builtin_type_void; expr->alloc.init = xcalloc(1, sizeof(struct expression)); expr->alloc.kind = aexpr->alloc.kind; const struct type *inithint = NULL; bool nullable = false; if (hint != NULL) { alloc_inithint(ctx, hint, expr->alloc.kind, &inithint, &nullable); } switch (aexpr->alloc.kind) { case ALLOC_OBJECT: check_expr_alloc_init(ctx, aexpr, expr, inithint, nullable); break; case ALLOC_CAP: check_expr_alloc_cap(ctx, aexpr, expr, inithint); break; case ALLOC_COPY: check_expr_alloc_copy(ctx, aexpr, expr, inithint); break; case ALLOC_LEN: abort(); // Not determined by parse } if (expr->result == &builtin_type_invalid) { return; } const struct type *tags[] = { &builtin_type_nomem, expr->alloc.allocation_result }; struct type_tagged_union tagged = { .types = tags, .len = 2 }; expr->result = type_store_lookup_tagged(ctx, aexpr->loc, &tagged); } static void check_expr_append_insert(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { assert(aexpr->type == EXPR_APPEND || aexpr->type == EXPR_INSERT); expr->type = aexpr->type; const struct type *tags[] = { &builtin_type_nomem, &builtin_type_void }; struct type_tagged_union tagged = { .types = tags, .len = 2 }; expr->result = type_store_lookup_tagged(ctx, aexpr->loc, &tagged); expr->append.is_static = aexpr->append.is_static; expr->append.is_multi = aexpr->append.is_multi; expr->append.object = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->append.object, expr->append.object, NULL); if (expr->append.object->result->storage == STORAGE_INVALID) { mkerror(expr); return; } if (expr->append.object->type != EXPR_ACCESS) { error(ctx, aexpr->append.object->loc, expr, "Expression must operate on an object"); return; } const struct type *sltype; const struct type *sltypename; const char *exprtype_name; struct expression *object = NULL; switch (expr->type) { case EXPR_APPEND: sltypename = expr->append.object->result; exprtype_name = "append"; object = expr->append.object; break; case EXPR_INSERT: assert(expr->append.object->type == EXPR_ACCESS); assert(expr->append.object->access.type == ACCESS_INDEX); sltypename = expr->append.object->access.array->result; exprtype_name = "insert"; object = expr->append.object->access.array; break; default: abort(); // Invariant } if (object->type == EXPR_ACCESS && object->access.type == ACCESS_IDENTIFIER && object->access.object->flags & SO_FOR_EACH_SUBJECT) { error(ctx, aexpr->append.object->loc, expr, "cannot %s the subject of for-each loop", exprtype_name); } sltype = check_autodereference(ctx, aexpr->append.object->loc, sltypename); sltype = type_dealias(ctx, sltype); if (sltype->storage != STORAGE_SLICE) { char *typename = gen_typename(sltypename); error(ctx, aexpr->append.object->loc, expr, "%s expression must operate on a slice, but got %s", exprtype_name, typename); free(typename); return; } if (sltype->array.members->size == SIZE_UNDEFINED) { error(ctx, aexpr->append.object->loc, expr, "Cannot %s %sto slice whose member type has undefined size", exprtype_name, expr->type == EXPR_APPEND ? "" : "in"); return; } expr->append.value = xcalloc(1, sizeof(struct expression)); if (!expr->append.is_multi && !aexpr->append.length) { check_expression(ctx, aexpr->append.value, expr->append.value, sltype->array.members); if (!type_is_assignable(ctx, sltype->array.members, expr->append.value->result)) { error(ctx, aexpr->append.value->loc, expr, "Value type must be assignable to object member type"); return; } expr->append.value = lower_implicit_cast(ctx, sltype->array.members, expr->append.value); return; } check_expression(ctx, aexpr->append.value, expr->append.value, sltype); const struct type *valtype = type_dealias(ctx, expr->append.value->result); if (aexpr->append.length) { if (valtype->storage != STORAGE_ARRAY || !valtype->array.expandable) { error(ctx, aexpr->append.value->loc, expr, "Value must be an expandable array in append with length"); return; } struct expression *len = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->append.length, len, &builtin_type_size); if (!type_is_assignable(ctx, &builtin_type_size, len->result)) { error(ctx, aexpr->append.length->loc, expr, "Length parameter must be assignable to size"); return; } len = lower_implicit_cast(ctx, &builtin_type_size, len); expr->append.length = len; } else if (valtype->storage != STORAGE_SLICE && valtype->storage != STORAGE_ARRAY) { error(ctx, aexpr->append.value->loc, expr, "Value must be an array or a slice in multi-valued %s", exprtype_name); return; } else if (valtype->size == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "Value array must be bounded"); return; } if (sltype->array.members != valtype->array.members) { error(ctx, aexpr->loc, expr, "Value member type must match object member type"); return; } } static void check_assert(struct context *ctx, struct ast_expression_assert e, struct location loc, struct expression *expr) { expr->result = &builtin_type_void; expr->type = EXPR_ASSERT; if (e.cond != NULL) { expr->assert.cond = xcalloc(1, sizeof(struct expression)); check_expression(ctx, e.cond, expr->assert.cond, &builtin_type_bool); loc = e.cond->loc; if (expr->assert.cond->result->storage == STORAGE_INVALID) { mkerror(expr); return; } if (type_dealias(ctx, expr->assert.cond->result)->storage != STORAGE_BOOL) { error(ctx, loc, expr, "Assertion condition must be boolean"); return; } } else { if (!e.is_static) { expr->result = &builtin_type_never; } } if (e.message == NULL) { expr->assert.fixed_reason = ABORT_ANON_ASSERTION_FAILED; } else { expr->assert.message = xcalloc(1, sizeof(struct expression)); check_expression(ctx, e.message, expr->assert.message, &builtin_type_str); if (type_dealias(ctx, expr->assert.message->result)->storage != STORAGE_STRING) { error(ctx, e.message->loc, expr, "Assertion message must be string"); return; } } if (e.is_static) { expr->type = EXPR_LITERAL; bool cond = false; if (expr->assert.cond != NULL) { struct expression out = {0}, msgout = {0}; if (!eval_expr(ctx, expr->assert.cond, &out)) { error(ctx, e.cond->loc, expr, "Unable to evaluate static assertion condition at compile time"); return; } if (expr->assert.message) { if (!eval_expr(ctx, expr->assert.message, &msgout)) { error(ctx, e.message->loc, expr, "Unable to evaluate static assertion message at compile time"); return; } } assert(type_dealias(ctx, out.result)->storage == STORAGE_BOOL); cond = out.literal.bval; } // XXX: Should these abort immediately? if (!cond) { if (e.message != NULL) { error(ctx, loc, expr, "Static assertion failed: %.*s", (int)expr->assert.message->literal.string.len, expr->assert.message->literal.string.value); } else { error(ctx, loc, expr, "Static assertion failed"); } } } } static void check_expr_assert(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { check_assert(ctx, aexpr->assert, aexpr->loc, expr); } static void check_binarithm_op(struct context *ctx, struct expression *expr, enum binarithm_operator op) { const struct type *dealiased = type_dealias(ctx, expr->result); switch (op) { // Numeric arithmetic case BIN_DIV: case BIN_MINUS: case BIN_PLUS: case BIN_TIMES: if (!type_is_numeric(ctx, dealiased)) { error(ctx, expr->loc, expr, "Cannot perform arithmetic on non-numeric %s type", type_storage_unparse(dealiased->storage)); } return; // Integer artithmetic case BIN_BAND: case BIN_BOR: case BIN_LSHIFT: case BIN_MODULO: case BIN_RSHIFT: case BIN_BXOR: if (!type_is_integer(ctx, dealiased)) { error(ctx, expr->loc, expr, "Cannot perform operation on non-integer %s type", type_storage_unparse(dealiased->storage)); } return; // Logical arithmetic case BIN_LAND: case BIN_LOR: case BIN_LXOR: expr->result = &builtin_type_bool; if (dealiased->storage != STORAGE_BOOL) { error(ctx, expr->loc, expr, "Cannot perform logical arithmetic on non-bool %s type", type_storage_unparse(dealiased->storage)); } return; case BIN_GREATER: case BIN_GREATEREQ: case BIN_LESS: case BIN_LESSEQ: expr->result = &builtin_type_bool; if (!type_is_numeric(ctx, dealiased)) { error(ctx, expr->loc, expr, "Cannot perform comparison on non-numeric %s type", type_storage_unparse(dealiased->storage)); } return; case BIN_LEQUAL: case BIN_NEQUAL: expr->result = &builtin_type_bool; if (!type_is_numeric(ctx, dealiased) && dealiased->storage != STORAGE_POINTER && dealiased->storage != STORAGE_STRING && dealiased->storage != STORAGE_BOOL && dealiased->storage != STORAGE_RCONST && dealiased->storage != STORAGE_RUNE) { error(ctx, expr->loc, expr, "Cannot perform equality test on %s type", type_storage_unparse(dealiased->storage)); } return; } } static void check_expr_assign(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_ASSIGN; expr->result = &builtin_type_void; expr->assign.op = aexpr->assign.op; struct expression *value = xcalloc(1, sizeof(struct expression)); if (aexpr->assign.object == NULL) { assert(expr->assign.op == BIN_LEQUAL); check_expression(ctx, aexpr->assign.value, value, NULL); expr->assign.value = value; return; } struct expression *object = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->assign.object, object, NULL); check_expression(ctx, aexpr->assign.value, value, object->result); if (object->type == EXPR_LITERAL && object->result != &builtin_type_invalid) { error(ctx, aexpr->assign.object->loc, expr, "Cannot assign to constant"); return; } if (object->result->size == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "Cannot assign to object with undefined size"); return; } if (!type_is_assignable(ctx, object->result, value->result)) { char *valtypename = gen_typename(value->result); char *objtypename = gen_typename(object->result); error(ctx, aexpr->loc, expr, "rvalue type (%s) is not assignable to lvalue (%s)", valtypename, objtypename); free(valtypename); free(objtypename); return; } if (expr->assign.op != BIN_LEQUAL) { check_binarithm_op(ctx, object, expr->assign.op); } if (object->type == EXPR_SLICE && value->result->storage == STORAGE_ARRAY && value->result->array.expandable) { expr->assign.value = value; } else { expr->assign.value = lower_implicit_cast(ctx, object->result, value); } expr->assign.object = object; } static const struct type * type_promote(struct context *ctx, const struct type *a, const struct type *b) { // Note: we must return either a, b, or NULL if (a == b) { return a; } if (a->storage == STORAGE_ALIAS && b->storage == STORAGE_ALIAS) { return NULL; } // TODO: There are likely some improperly handled edge cases around type // flags, both here and in the spec const struct type *da = type_dealias(ctx, a); const struct type *db = type_dealias(ctx, b); if (da == db) { if (a->storage == STORAGE_ALIAS) { return a; } else if (b->storage == STORAGE_ALIAS) { return b; } else if (a->storage == STORAGE_ERROR) { return a; } else { return b; } } if (type_is_flexible(da) || type_is_flexible(db)) { return promote_flexible(ctx, a, b); } if (db->storage == STORAGE_ENUM && da->storage == db->alias.type->storage) { return b; } if (db->storage == STORAGE_INVALID) { return a; } switch (da->storage) { case STORAGE_ENUM: if (da->alias.type->storage == db->storage) { return a; } return NULL; case STORAGE_I8: case STORAGE_I16: case STORAGE_I32: case STORAGE_I64: case STORAGE_INT: if (!type_is_integer(ctx, db) || !type_is_signed(ctx, db) || db->size == da->size) { return NULL; } return da->size > db->size ? a : b; case STORAGE_U32: case STORAGE_U16: case STORAGE_U64: case STORAGE_UINT: case STORAGE_SIZE: case STORAGE_U8: if (da->storage == STORAGE_SIZE && db->storage == STORAGE_UINTPTR) { return db; } if (!type_is_integer(ctx, db) || type_is_signed(ctx, db) || db->size == da->size) { return NULL; } return da->size > db->size ? a : b; case STORAGE_F32: case STORAGE_F64: if (!type_is_float(ctx, db) || db->size == da->size) { return NULL; } return da->size > db->size ? a : b; case STORAGE_POINTER: if (db->storage == STORAGE_NULL) { return a; } if (db->storage == STORAGE_UINTPTR) { return a; } if (db->storage != STORAGE_POINTER) { return NULL; } if (da->pointer.referent->storage == STORAGE_OPAQUE || db->pointer.referent->storage == STORAGE_OPAQUE) { return a; } const struct type *r = type_promote(ctx, da->pointer.referent, db->pointer.referent); if (r == da->pointer.referent) { return a; } if (r == db->pointer.referent) { return b; } assert(r == NULL); return NULL; case STORAGE_NULL: if (db->storage == STORAGE_POINTER || db->storage == STORAGE_UINTPTR) { return b; } return NULL; case STORAGE_INVALID: case STORAGE_NEVER: return b; case STORAGE_UINTPTR: if (db->storage == STORAGE_SIZE || db->storage == STORAGE_NULL) { return a; } if (db->storage == STORAGE_POINTER) { return b; } return NULL; // Cannot be promoted case STORAGE_ARRAY: case STORAGE_BOOL: case STORAGE_DONE: case STORAGE_FUNCTION: case STORAGE_NOMEM: case STORAGE_OPAQUE: case STORAGE_RUNE: case STORAGE_SLICE: case STORAGE_STRING: case STORAGE_STRUCT: case STORAGE_TAGGED: case STORAGE_TUPLE: case STORAGE_UNION: case STORAGE_VALIST: case STORAGE_VOID: case STORAGE_UNDEFINED: return NULL; // Handled above case STORAGE_ALIAS: case STORAGE_ERROR: case STORAGE_FCONST: case STORAGE_ICONST: case STORAGE_RCONST: assert(0); } assert(0); } static void resolve_enum_field(struct context *ctx, struct scope_object *obj); static bool type_has_default(struct context *ctx, const struct type *type) { switch (type->storage) { case STORAGE_BOOL: case STORAGE_DONE: case STORAGE_INVALID: case STORAGE_F32: case STORAGE_F64: case STORAGE_I16: case STORAGE_I32: case STORAGE_I64: case STORAGE_I8: case STORAGE_INT: case STORAGE_NOMEM: case STORAGE_RUNE: case STORAGE_SIZE: case STORAGE_SLICE: case STORAGE_STRING: case STORAGE_U16: case STORAGE_U32: case STORAGE_U64: case STORAGE_U8: case STORAGE_UINT: case STORAGE_UINTPTR: case STORAGE_VOID: case STORAGE_UNDEFINED: return true; case STORAGE_FUNCTION: case STORAGE_NEVER: case STORAGE_OPAQUE: case STORAGE_TAGGED: case STORAGE_VALIST: return false; case STORAGE_ARRAY: return type->array.length != SIZE_UNDEFINED && type_has_default(ctx, type->array.members); case STORAGE_ENUM: for (struct scope_object *obj = type->_enum.values->objects; obj != NULL; obj = obj->lnext) { if (obj->otype == O_DECL) { continue; } if (obj->otype == O_SCAN) { wrap_resolver(ctx, obj, resolve_enum_field); } assert(obj->otype == O_CONST); if (obj->value->literal.uval == 0) { return true; } } return false; case STORAGE_POINTER: return type->pointer.nullable; case STORAGE_STRUCT: for (struct struct_field *sf = type->struct_union.fields; sf != NULL; sf = sf->next) { if (!type_has_default(ctx, sf->type)) { return false; } } return true; case STORAGE_UNION: for (struct struct_field *sf = type->struct_union.fields; sf != NULL; sf = sf->next) { if (type_has_default(ctx, sf->type)) { return true; } } return false; case STORAGE_TUPLE: for (const struct type_tuple *t = &type->tuple; t != NULL; t = t->next) { if (!type_has_default(ctx, t->type)) { return false; } } return true; case STORAGE_ALIAS: case STORAGE_ERROR: return type_has_default(ctx, type_dealias(ctx, type)); case STORAGE_FCONST: case STORAGE_ICONST: case STORAGE_NULL: case STORAGE_RCONST: abort(); // unreachable } abort(); // Unreachable } static void check_expr_binarithm(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_BINARITHM; expr->binarithm.op = aexpr->binarithm.op; struct expression *lvalue = xcalloc(1, sizeof(struct expression)), *rvalue = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->binarithm.lvalue, lvalue, NULL); check_expression(ctx, aexpr->binarithm.rvalue, rvalue, NULL); if (lvalue->result->storage == STORAGE_INVALID || rvalue->result->storage == STORAGE_INVALID) { mkerror(expr); return; } expr->result = type_promote(ctx, lvalue->result, rvalue->result); if (expr->result == NULL) { char *ltypename = gen_typename(lvalue->result); char *rtypename = gen_typename(rvalue->result); error(ctx, aexpr->loc, expr, "Cannot promote %s and %s", ltypename, rtypename); free(ltypename); free(rtypename); return; } expr->binarithm.lvalue = lower_implicit_cast(ctx, expr->result, lvalue); expr->binarithm.rvalue = lower_implicit_cast(ctx, expr->result, rvalue); check_binarithm_op(ctx, expr, expr->binarithm.op); } static bool create_unpack_bindings(struct context *ctx, const struct type *type, const struct location loc, const struct ast_binding_names *names, bool is_static, struct expression_binding *binding) { type = type_dealias(ctx, type); if (type->storage != STORAGE_TUPLE) { error(ctx, loc, NULL, "Cannot unpack non-tuple type"); return false; } binding->unpack = xcalloc(1, sizeof(struct binding_unpack)); struct binding_unpack *unpack = binding->unpack; const struct type_tuple *type_tuple = &type->tuple; while (names != NULL && type_tuple != NULL) { if (type_tuple->type->size == SIZE_UNDEFINED) { error(ctx, loc, NULL, "Cannot create binding of undefined size"); return false; } if (names->name != NULL) { if (unpack->object != NULL) { unpack->next = xcalloc(1, sizeof(struct binding_unpack)); unpack = unpack->next; } if (is_static) { // Generate a static declaration ident unpack->object = scope_insert(ctx->scope, O_DECL, intern_generated(ctx, "static.%d"), names->name, type_tuple->type, NULL); } else { unpack->object = scope_insert(ctx->scope, O_BIND, names->name, names->name, type_tuple->type, NULL); } unpack->offset = type_tuple->offset; } names = names->next; type_tuple = type_tuple->next; } if (binding->unpack->object == NULL) { error(ctx, loc, NULL, "Must have at least one non-underscore value when unpacking tuples"); return false; } if (type_tuple != NULL) { error(ctx, loc, NULL, "Fewer bindings than tuple elements were provided when unpacking"); return false; } if (names != NULL) { error(ctx, loc, NULL, "More bindings than tuple elements were provided when unpacking"); return false; } return true; } static void check_expr_binding(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { assert(aexpr->type == EXPR_BINDING || aexpr->type == EXPR_DEFINE); expr->type = aexpr->type; expr->result = &builtin_type_void; struct expression_binding *binding = &expr->binding; const struct ast_expression_binding *abinding = &aexpr->binding; while (abinding) { const struct type *type = NULL; if (abinding->type) { type = type_store_lookup_atype(ctx, abinding->type); } struct expression *initializer = xcalloc(1, sizeof(struct expression)); check_expression(ctx, abinding->initializer, initializer, type); if (abinding->type && abinding->type->storage == STORAGE_ARRAY && abinding->type->array.contextual) { if (initializer->result->storage == STORAGE_INVALID) { // no-op } else if (initializer->result->storage != STORAGE_ARRAY) { error(ctx, aexpr->loc, expr, "Cannot infer array length from non-array type"); return; } else if (initializer->result->array.members != type->array.members) { char *inittype = gen_typename(initializer->result); char *bindingtype = gen_typename(type); error(ctx, aexpr->loc, expr, "Initializer of type %s is not assignable to binding type %s", inittype, bindingtype); free(inittype); free(bindingtype); return; } type = initializer->result; } if (expr->type == EXPR_DEFINE) { if (type) { initializer = lower_implicit_cast( ctx, type, initializer); } struct expression *value = xcalloc(1, sizeof(struct expression)); if (!eval_expr(ctx, initializer, value)) { error(ctx, initializer->loc, value, "Unable to evaluate constant initializer at compile time"); type = &builtin_type_invalid; } binding->initializer = value; assert(abinding->names.name != NULL); assert(abinding->names.next == NULL); binding->object = scope_insert(ctx->scope, O_CONST, abinding->names.name, abinding->names.name, NULL, value); goto done; } if (!type) { type = initializer->result; } if (abinding->names.next != NULL) { if (!create_unpack_bindings(ctx, type, abinding->initializer->loc, &abinding->names, abinding->is_static, binding)) { mkerror(expr); } } else if (abinding->names.name != NULL) { if (abinding->is_static) { // Generate a static declaration ident binding->object = scope_insert(ctx->scope, O_DECL, intern_generated(ctx, "static.%d"), abinding->names.name, type, NULL); } else { binding->object = scope_insert(ctx->scope, O_BIND, abinding->names.name, abinding->names.name, type, NULL); } } if (type->storage == STORAGE_NULL) { error(ctx, aexpr->loc, expr, "Null is not a valid type for a binding"); return; } if (!type_is_assignable(ctx, type, initializer->result)) { char *inittype = gen_typename(initializer->result); char *bindingtype = gen_typename(type); error(ctx, aexpr->loc, expr, "Initializer of type %s is not assignable to binding type %s", inittype, bindingtype); free(inittype); free(bindingtype); return; } type = lower_flexible(ctx, type, NULL); if (type->size == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "Cannot create binding for type of undefined size"); return; } binding->initializer = lower_implicit_cast(ctx, type, initializer); if (abinding->is_static) { struct expression *value = xcalloc(1, sizeof(struct expression)); if (!eval_expr(ctx, binding->initializer, value)) { error(ctx, abinding->initializer->loc, expr, "Unable to evaluate static initializer at compile time"); return; } // TODO: Free initializer binding->initializer = value; } done: if (abinding->next) { binding->next = xcalloc(1, sizeof(struct expression_binding)); binding = binding->next; } abinding = abinding->next; } } static void check_expr_call(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_CALL; struct expression *lvalue = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->call.lvalue, lvalue, NULL); expr->call.lvalue = lvalue; const struct type *fntype = check_autodereference(ctx, aexpr->loc, lvalue->result); fntype = type_dealias(ctx, fntype); if (fntype->storage == STORAGE_INVALID) { mkerror(expr); return; } if (fntype->storage != STORAGE_FUNCTION) { error(ctx, aexpr->loc, expr, "Cannot call non-function type"); return; } if (fntype->func.variadism != VARIADISM_HARE && aexpr->call.variadic) { error(ctx, aexpr->loc, NULL, "Function type does not permit variadic argument list"); } expr->result = fntype->func.result; struct call_argument *arg, **next = &expr->call.args; struct ast_expression_list *aarg = aexpr->call.args; struct type_func_param *param = fntype->func.params; while (param && aarg) { arg = *next = xcalloc(1, sizeof(struct call_argument)); arg->value = xcalloc(1, sizeof(struct expression)); struct ast_expression val; if (!param->next && fntype->func.variadism == VARIADISM_HARE && !aexpr->call.variadic) { // lower the rest to an array val = (struct ast_expression){ .loc = aarg->expr->loc, .type = EXPR_LITERAL, .literal = { .storage = STORAGE_ARRAY, .array.exprs = aarg, }, }; } else { val = *aarg->expr; } check_expression(ctx, &val, arg->value, param->type); if (!type_is_assignable(ctx, param->type, arg->value->result)) { char *argtypename = gen_typename(arg->value->result); char *paramtypename = gen_typename(param->type); error(ctx, val.loc, NULL, "Argument type %s is not assignable to parameter type %s", argtypename, paramtypename); free(argtypename); free(paramtypename); return; } arg->value = lower_implicit_cast(ctx, param->type, arg->value); if (!param->next && fntype->func.variadism == VARIADISM_HARE) { return; } aarg = aarg->next; next = &arg->next; param = param->next; } while (param && param->default_value) { arg = *next = xcalloc(1, sizeof(struct call_argument)); arg->value = param->default_value; next = &arg->next; param = param->next; } if (param) { if (fntype->func.variadism == VARIADISM_HARE && !param->next) { // No variadic arguments, lower to empty slice arg = *next = xcalloc(1, sizeof(struct call_argument)); arg->value = xcalloc(1, sizeof(struct expression)); *arg->value = (struct expression){ .type = EXPR_LITERAL, .result = param->type, .literal = { .object = NULL, .slice.array = NULL, .slice.len = 0, }, }; return; } else if (param->default_value == NULL) { error(ctx, aexpr->loc, NULL, "Not enough arguments for function call"); return; } } else if (aarg) { if (fntype->func.variadism != VARIADISM_C) { error(ctx, aexpr->loc, NULL, "Too many arguments for function call"); return; } while (aarg) { arg = *next = xcalloc(1, sizeof(struct call_argument)); arg->value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aarg->expr, arg->value, NULL); aarg = aarg->next; next = &arg->next; } } } static void check_expr_cast(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_CAST; expr->cast.kind = aexpr->cast.kind; struct expression *value = expr->cast.value = xcalloc(1, sizeof(struct expression)); const struct type *secondary = expr->cast.secondary = type_store_lookup_atype(ctx, aexpr->cast.type); check_expression(ctx, aexpr->cast.value, value, secondary); const struct type *primary = type_dealias(ctx, expr->cast.value->result); if (primary->storage == STORAGE_INVALID || secondary->storage == STORAGE_INVALID) { mkerror(expr); return; } struct match_context mctx = {0}; switch (aexpr->cast.kind) { case C_ASSERTION: case C_TEST: if (!begin_check_match(ctx, &mctx, expr, expr->cast.value->result, aexpr->cast.value->loc)) { return; } if (!check_match_case(&mctx, secondary, expr, aexpr->cast.type->loc)) { return; } break; case C_CAST:; const struct type *intermediary = type_is_castable(ctx, secondary, value->result); if (intermediary == NULL) { char *primarytypename = gen_typename(value->result); char *secondarytypename = gen_typename(secondary); error(ctx, aexpr->cast.type->loc, expr, "Invalid cast from %s to %s", primarytypename, secondarytypename); free(primarytypename); free(secondarytypename); return; } // intermediary type is required when casting to tagged union // whose member is an alias of primary type, since gen.c asserts // that the primary type is a direct member of the tagged union. // The value is first cast to an intermediary type which is a // direct member of the tagged union, before being cast to the // tagged union itself. expr->cast.value = lower_implicit_cast(ctx, intermediary, value); break; } expr->result = aexpr->cast.kind == C_TEST? &builtin_type_bool : secondary; } static void check_expr_array_literal(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { size_t len = 0; struct ast_expression_list *item = aexpr->literal.array.exprs; struct array_literal *cur, **next = &expr->literal.array; const struct type *type = NULL; if (hint) { hint = type_dealias(ctx, hint); size_t narray = 0; switch (hint->storage) { case STORAGE_ARRAY: case STORAGE_SLICE: type = hint->array.members; break; case STORAGE_TAGGED:; const struct type_tagged_union *htagged = &hint->tagged; for (size_t i = 0; i < htagged->len; i++) { const struct type *t = type_dealias(ctx, htagged->types[i]); if (t->storage == STORAGE_ARRAY || t->storage == STORAGE_SLICE) { hint = t; type = hint->array.members; ++narray; } } if (narray != 1) { type = hint = NULL; } break; default: hint = NULL; break; } } while (item) { struct expression *value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, item->expr, value, type); cur = *next = xcalloc(1, sizeof(struct array_literal)); cur->value = value; if (!type) { type = value->result; } else { if (!type_is_assignable(ctx, type, value->result)) { char *typename1 = gen_typename(type); char *typename2 = gen_typename(value->result); error(ctx, item->expr->loc, expr, "Array members must be of a uniform type, previously seen %s, but now see %s", typename1, typename2); free(typename1); free(typename2); return; } if (!hint) { // The promote_flexible in // type_is_assignable might've caused the // type to change out from under our feet type = expr->literal.array->value->result; } cur->value = lower_implicit_cast(ctx, type, cur->value); } item = item->next; next = &cur->next; ++len; } if (type == NULL) { error(ctx, aexpr->loc, expr, "Cannot infer array type from context, try casting it to the desired type"); return; } expr->result = type_store_lookup_array(ctx, aexpr->loc, type, len, aexpr->literal.array.expand); } static void check_expr_compound(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_COMPOUND; struct scope *scope = scope_push(&ctx->scope, SCOPE_COMPOUND); scope->hint = hint; expr->compound.scope = scope; if (aexpr->compound.label) { expr->compound.label = aexpr->compound.label; scope->label = aexpr->compound.label; } struct expressions *list = &expr->compound.exprs; struct expressions **next = &list->next; const struct ast_expression_list *alist = &aexpr->compound.list; struct expression *lexpr = NULL; while (alist) { lexpr = xcalloc(1, sizeof(struct expression)); check_expression(ctx, alist->expr, lexpr, NULL); if (type_has_error(ctx, lexpr->result)) { error(ctx, alist->expr->loc, lexpr, "Cannot ignore error here"); } list->expr = lexpr; alist = alist->next; if (alist) { *next = xcalloc(1, sizeof(struct expressions)); list = *next; next = &list->next; } if (alist && lexpr->result->storage == STORAGE_NEVER) { error(ctx, alist->expr->loc, expr, "Expression with result 'never' may not be followed by additional expressions"); } } if (lexpr->result->storage != STORAGE_NEVER) { // Add implicit `yield void` if control reaches end of compound // expression. tagged_append(&scope->results, &builtin_type_void); list->next = xcalloc(1, sizeof(struct expressions)); struct ast_expression *yexpr = xcalloc(1, sizeof(struct ast_expression)); yexpr->type = EXPR_YIELD; lexpr = xcalloc(1, sizeof(struct expression)); check_expression(ctx, yexpr, lexpr, NULL); list->next->expr = lexpr; } expr->result = type_store_reduce_result(ctx, aexpr->loc, &scope->results); for (struct yield *yield = scope->yields; yield;) { *yield->expression = lower_implicit_cast(ctx, expr->result, *yield->expression); struct yield *next = yield->next; free(yield); yield = next; } assert(expr->result); scope_pop(&ctx->scope); } static void check_expr_literal(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_LITERAL; enum type_storage storage = aexpr->literal.storage; expr->result = builtin_type_for_storage(storage); switch (aexpr->literal.storage) { case STORAGE_ICONST: expr->result = type_create_flexible(storage, aexpr->literal.ival, aexpr->literal.ival); /* fallthrough */ case STORAGE_I8: case STORAGE_I16: case STORAGE_I32: case STORAGE_I64: case STORAGE_INT: expr->literal.ival = aexpr->literal.ival; break; case STORAGE_U8: case STORAGE_U16: case STORAGE_U32: case STORAGE_U64: case STORAGE_UINT: case STORAGE_SIZE: expr->literal.uval = aexpr->literal.uval; break; case STORAGE_RCONST: expr->result = type_create_flexible(storage, aexpr->literal.rune, aexpr->literal.rune); expr->literal.rune = aexpr->literal.rune; break; case STORAGE_BOOL: expr->literal.bval = aexpr->literal.bval; break; case STORAGE_DONE: case STORAGE_NOMEM: case STORAGE_NULL: case STORAGE_VOID: // No storage break; case STORAGE_ARRAY: check_expr_array_literal(ctx, aexpr, expr, hint); break; case STORAGE_STRING: expr->literal.string.len = aexpr->literal.string.len; expr->literal.string.value = xcalloc(1, aexpr->literal.string.len); memcpy(expr->literal.string.value, aexpr->literal.string.value, aexpr->literal.string.len); break; case STORAGE_FCONST: expr->result = type_create_flexible(storage, aexpr->literal.fval, aexpr->literal.fval); // fallthrough case STORAGE_F32: case STORAGE_F64: expr->literal.fval = aexpr->literal.fval; break; case STORAGE_ENUM: case STORAGE_INVALID: case STORAGE_UINTPTR: case STORAGE_ALIAS: case STORAGE_ERROR: case STORAGE_FUNCTION: case STORAGE_NEVER: case STORAGE_OPAQUE: case STORAGE_POINTER: case STORAGE_RUNE: case STORAGE_SLICE: case STORAGE_TAGGED: case STORAGE_TUPLE: case STORAGE_STRUCT: case STORAGE_UNION: case STORAGE_UNDEFINED: case STORAGE_VALIST: assert(0); // Invariant } } static void check_expr_defer(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_DEFER; expr->result = &builtin_type_void; expr->defer.deferred = xcalloc(1, sizeof(struct expression)); expr->defer.scope = scope_push(&ctx->scope, SCOPE_DEFER); check_expression(ctx, aexpr->defer.deferred, expr->defer.deferred, NULL); if (type_has_error(ctx, expr->defer.deferred->result)) { error(ctx, aexpr->defer.deferred->loc, expr->defer.deferred, "Cannot ignore error here"); } scope_pop(&ctx->scope); } static void check_expr_delete(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_DELETE; expr->delete.is_static = aexpr->delete.is_static; expr->result = &builtin_type_void; struct expression *dexpr = expr->delete.expr = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->delete.expr, expr->delete.expr, NULL); const struct type *otype = NULL; switch (dexpr->type) { case EXPR_SLICE: otype = dexpr->slice.object->result; break; case EXPR_ACCESS: if (dexpr->access.type != ACCESS_INDEX) { error(ctx, aexpr->delete.expr->loc, expr, "Deleted expression must be slicing or indexing expression"); return; } struct expression *array = dexpr->access.array; if (array->type == EXPR_ACCESS && array->access.type == ACCESS_IDENTIFIER && array->access.object->flags & SO_FOR_EACH_SUBJECT) { error(ctx, aexpr->delete.expr->loc, expr, "cannot delete the subject of for-each loop"); } otype = dexpr->access.array->result; break; default: if (dexpr->result->storage != STORAGE_INVALID) { error(ctx, aexpr->delete.expr->loc, expr, "Deleted expression must be slicing or indexing expression"); } return; } otype = check_autodereference(ctx, aexpr->loc, otype); otype = type_dealias(ctx, otype); if (otype->storage != STORAGE_SLICE) { error(ctx, aexpr->delete.expr->loc, expr, "delete must operate on a slice"); return; } } static void check_expr_control(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = aexpr->type; expr->result = &builtin_type_never; expr->control.label = aexpr->control.label; enum scope_class want; switch (expr->type) { case EXPR_BREAK: case EXPR_CONTINUE: want = SCOPE_LOOP; break; case EXPR_YIELD: want = SCOPE_COMPOUND; break; default: abort(); // Invariant } struct scope *scope = NULL; if (aexpr->control.label) { scope = scope_lookup_label(ctx->scope, aexpr->control.label); if (scope && scope->class != want) { error(ctx, aexpr->loc, NULL, "Selected expression must%s be a loop", want == SCOPE_COMPOUND ? " not" : ""); } } else { scope = scope_lookup_class(ctx->scope, want); } if (scope) { struct scope *defer_scope = scope_lookup_class(ctx->scope, SCOPE_DEFER); if (defer_scope) { defer_scope = aexpr->control.label ? scope_lookup_label(defer_scope, aexpr->control.label) : scope_lookup_class(defer_scope, want); if (scope == defer_scope) { error(ctx, aexpr->loc, NULL, "Cannot jump out of defer expression"); // continue checking so other errors can be reported } } } else { const char *msg; switch (expr->type) { case EXPR_BREAK: msg = "No eligible loop to break from"; break; case EXPR_CONTINUE: msg = "No eligible loop to continue to"; break; case EXPR_YIELD: msg = "No eligible expression to yield from"; break; default: assert(0); // Invariant } error(ctx, aexpr->loc, NULL, "%s", msg); // No need to continue checking, because we won't have the right // hint for the value without a scope. return; } expr->control.scope = scope; if (expr->type == EXPR_CONTINUE) { return; } expr->control.value = xcalloc(1, sizeof(struct expression)); if (aexpr->control.value) { check_expression(ctx, aexpr->control.value, expr->control.value, scope->hint); } else { expr->control.value->type = EXPR_LITERAL; expr->control.value->result = &builtin_type_void; } tagged_append(&scope->results, expr->control.value->result); struct yield *yield = xcalloc(1, sizeof(struct yield)); yield->expression = &expr->control.value; yield->next = scope->yields; scope->yields = yield; } static void check_expr_for_accumulator(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr) { struct expression *bindings = NULL, *cond = NULL, *afterthought = NULL; if (aexpr->_for.bindings) { bindings = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_for.bindings, bindings, NULL); if (bindings->result->storage == STORAGE_INVALID) { // It won't be fruitful to continue checking if the // bindings fail. return; } assert(bindings->type == EXPR_BINDING); expr->_for.bindings = bindings; } cond = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_for.cond, cond, &builtin_type_bool); expr->_for.cond = cond; if (type_dealias(ctx, cond->result)->storage != STORAGE_BOOL && cond->result->storage != STORAGE_INVALID) { error(ctx, aexpr->_for.cond->loc, expr, "Expected for condition to be boolean"); } if (aexpr->_for.afterthought) { afterthought = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_for.afterthought, afterthought, NULL); if (type_has_error(ctx, afterthought->result)) { error(ctx, aexpr->_for.afterthought->loc, afterthought, "Cannot ignore error here"); } expr->_for.afterthought = afterthought; } struct expression *body = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_for.body, body, NULL); if (type_has_error(ctx, body->result)) { error(ctx, aexpr->_for.body->loc, body, "Cannot ignore error here"); } expr->_for.body = body; struct expression evaled; if (eval_expr(ctx, expr->_for.cond, &evaled) && evaled.literal.bval) { expr->result = &builtin_type_never; } } static void check_expr_for_each(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr) { struct expression *binding = xcalloc(1, sizeof(struct expression)); struct expression *initializer = xcalloc(1, sizeof(struct expression)); expr->_for.bindings = binding; binding->type = EXPR_BINDING; binding->result = &builtin_type_void; binding->binding.initializer = initializer; struct ast_expression_binding *abinding = &aexpr->_for.bindings->binding; const struct type *binding_type = NULL, *init_type_hint = NULL; if (abinding->type != NULL) { binding_type = type_store_lookup_atype(ctx, abinding->type); // Construct a type hint for the init expression. For example, // if the type hint is *int and we are in a &.., we would have // to do: *int -> int -> [_]int init_type_hint = binding_type; switch (expr->_for.kind) { case FOR_EACH_POINTER: init_type_hint = type_dealias(ctx, init_type_hint); if (init_type_hint->storage != STORAGE_POINTER) { error(ctx, aexpr->loc, expr, "Expected pointer type"); return; } init_type_hint = init_type_hint->pointer.referent; // fallthrough case FOR_EACH_VALUE: init_type_hint = type_store_lookup_array(ctx, aexpr->loc, init_type_hint, SIZE_UNDEFINED, false); break; case FOR_EACH_ITERATOR: { struct type_tagged_union tags = { .types = NULL }; if (init_type_hint->storage == STORAGE_TAGGED) { tags = tagged_dup_tags(&init_type_hint->tagged); } else { tagged_append(&tags, binding_type); } tagged_append(&tags, &builtin_type_done); init_type_hint = type_store_lookup_tagged(ctx, aexpr->loc, &tags); break; } default: abort(); // unreachable } } check_expression(ctx, abinding->initializer, initializer, init_type_hint); const struct type *initializer_type = type_dealias(ctx, initializer->result); const struct type *var_type = binding_type; const struct type *initializer_result; switch (expr->_for.kind) { case FOR_EACH_POINTER: if (abinding->names.next != NULL) { error(ctx, abinding->initializer->loc, expr, "Cannot unpack tuple by pointer in for-each loop"); return; } // fallthrough case FOR_EACH_VALUE: initializer_type = type_dealias(ctx, check_autodereference(ctx, abinding->initializer->loc, initializer_type)); if (initializer_type->storage != STORAGE_ARRAY && initializer_type->storage != STORAGE_SLICE) { error(ctx, abinding->initializer->loc, initializer, "Expected array or slice"); return; } if (initializer_type->storage == STORAGE_ARRAY && initializer_type->size == SIZE_UNDEFINED) { error(ctx, abinding->initializer->loc, initializer, "Cannot iterate over array of type with undefined size"); return; } if (expr->_for.kind == FOR_EACH_VALUE) { initializer_result = initializer_type->array.members; } else { initializer_result = type_store_lookup_pointer(ctx, aexpr->loc, initializer_type->array.members, false); } break; case FOR_EACH_ITERATOR: if (initializer_type->storage == STORAGE_INVALID) { initializer_result = &builtin_type_invalid; break; } else if (initializer_type->storage != STORAGE_TAGGED) { error(ctx, abinding->initializer->loc, initializer, "Expected tagged union"); return; } // Remove all done tags and aliases of it from the tagged union struct type_tagged_union tags = tagged_dup_tags(&initializer_type->tagged); int done_tags_found = 0; size_t new_len = 0; for (size_t i = 0; i < tags.len; i++) { if (type_dealias(ctx, tags.types[i])->storage == STORAGE_DONE) { done_tags_found++; continue; } tags.types[new_len++] = tags.types[i]; } tags.len = new_len; if (done_tags_found != 1) { error(ctx, abinding->initializer->loc, initializer, "Tagged union must contain exactly one done type"); return; } initializer_result = type_store_reduce_result(ctx, abinding->initializer->loc, &tags); break; default: abort(); } if (var_type == NULL) { var_type = initializer_result; } if (var_type->size == SIZE_UNDEFINED) { error(ctx, abinding->initializer->loc, binding, "Cannot create binding of undefined size"); // error is recoverable } if (abinding->names.next != NULL) { if (!create_unpack_bindings(ctx, var_type, initializer->loc, &abinding->names, abinding->is_static, &binding->binding)) { mkerror(binding); }; } else if (abinding->names.name != NULL) { binding->binding.object = scope_insert(ctx->scope, O_BIND, abinding->names.name, abinding->names.name, var_type, NULL); } if (binding_type != NULL && !type_is_assignable(ctx, var_type, initializer_result)) { char *init = gen_typename(initializer_result); char *bind = gen_typename(var_type); error(ctx, aexpr->loc, expr, "Initializer of type %s is not assignable to binding of type %s", init, bind); free(init); free(bind); return; } struct expression *body = xcalloc(1, sizeof(struct expression)); expr->_for.body = body; if (expr->_for.kind != FOR_EACH_ITERATOR && initializer->type == EXPR_ACCESS && initializer->access.type == ACCESS_IDENTIFIER) { initializer->access.object->flags |= SO_FOR_EACH_SUBJECT; check_expression(ctx, aexpr->_for.body, body, NULL); initializer->access.object->flags &= ~(SO_FOR_EACH_SUBJECT); } else { check_expression(ctx, aexpr->_for.body, body, NULL); } if (type_has_error(ctx, body->result)) { error(ctx, aexpr->_for.body->loc, body, "Cannot ignore error here"); } } static void check_expr_for(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_FOR; expr->result = &builtin_type_void; expr->_for.kind = aexpr->_for.kind; struct scope *scope = scope_push(&ctx->scope, SCOPE_LOOP); scope->hint = hint; expr->_for.scope = scope; if (aexpr->_for.label) { expr->_for.label = aexpr->_for.label; scope->label = aexpr->_for.label; } switch (expr->_for.kind) { case FOR_ACCUMULATOR: check_expr_for_accumulator(ctx, aexpr, expr); break; case FOR_EACH_VALUE: case FOR_EACH_POINTER: case FOR_EACH_ITERATOR: check_expr_for_each(ctx, aexpr, expr); break; } scope_pop(&ctx->scope); // The else branch is not evaluated in the loop scope. expr->_for.else_branch = xcalloc(1, sizeof(struct expression)); if (aexpr->_for.else_branch) { check_expression(ctx, aexpr->_for.else_branch, expr->_for.else_branch, hint); } else { expr->_for.else_branch->type = EXPR_LITERAL; expr->_for.else_branch->result = &builtin_type_void; } // Check this later, because we should unconditionally typecheck the // else branch if (expr->result != &builtin_type_never) { expr->result = expr->_for.else_branch->result; } else { expr->_for.else_branch = NULL; }; // If every possible result type is assignable to the hint, just set the // hint as the result type. bool assignable_to_hint = true; if (hint && type_is_assignable(ctx, hint, expr->result)) { for (size_t i = 0; i < scope->results.len; i++) { if (!type_is_assignable(ctx, hint, scope->results.types[i])) { assignable_to_hint = false; break; } } } else { assignable_to_hint = false; } if (assignable_to_hint) { // If we were going to end up with `never` as our result, keep // it regardless of the hint if (scope->results.len != 0 || expr->_for.else_branch) { expr->result = hint; } } else { tagged_append(&scope->results, expr->result); expr->result = type_store_reduce_result(ctx, aexpr->loc, &scope->results); } // Lower the break values to the result type. for (struct yield *yield = scope->yields; yield;) { *yield->expression = lower_implicit_cast(ctx, expr->result, *yield->expression); struct yield *next = yield->next; free(yield); yield = next; } if (expr->_for.else_branch) { expr->_for.else_branch = lower_implicit_cast(ctx, expr->result, expr->_for.else_branch); } } static void check_expr_free(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { assert(aexpr->type == EXPR_FREE); expr->type = EXPR_FREE; expr->free.expr = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->free.expr, expr->free.expr, NULL); if (expr->free.expr->type == EXPR_ACCESS && expr->free.expr->access.type == ACCESS_IDENTIFIER && expr->free.expr->access.object->flags & SO_FOR_EACH_SUBJECT) { error(ctx, aexpr->free.expr->loc, expr, "cannot free the subject of for-each loop"); } enum type_storage storage = type_dealias(ctx, expr->free.expr->result)->storage; if (storage == STORAGE_INVALID) { mkerror(expr); return; } if (storage != STORAGE_SLICE && storage != STORAGE_STRING && storage != STORAGE_POINTER && storage != STORAGE_NULL) { error(ctx, aexpr->free.expr->loc, expr, "free must operate on slice, string, pointer, or null"); return; } expr->result = &builtin_type_void; } static void check_expr_if(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_IF; struct expression *cond, *true_branch, *false_branch; cond = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_if.cond, cond, &builtin_type_bool); true_branch = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_if.true_branch, true_branch, hint); false_branch = xcalloc(1, sizeof(struct expression)); if (aexpr->_if.false_branch) { check_expression(ctx, aexpr->_if.false_branch, false_branch, hint); } else { false_branch->type = EXPR_LITERAL; false_branch->result = &builtin_type_void; } const struct type *fresult = false_branch->result; if (hint && type_is_assignable(ctx, hint, true_branch->result) && type_is_assignable(ctx, hint, fresult)) { expr->result = hint; } else { const struct type *tags[] = { fresult, true_branch->result }; struct type_tagged_union tagged = { .types = tags, .len = 2 }; expr->result = type_store_reduce_result(ctx, aexpr->loc, &tagged); } true_branch = lower_implicit_cast(ctx, expr->result, true_branch); false_branch = lower_implicit_cast(ctx, expr->result, false_branch); if (cond->result->storage == STORAGE_INVALID) { mkerror(expr); return; } if (type_dealias(ctx, cond->result)->storage != STORAGE_BOOL) { error(ctx, aexpr->_if.cond->loc, expr, "Expected if condition to be boolean"); return; } expr->_if.cond = cond; expr->_if.true_branch = true_branch; expr->_if.false_branch = false_branch; } static void check_expr_match(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_MATCH; struct expression *value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->match.value, value, NULL); expr->match.value = value; struct match_context mctx = {0}; if (!begin_check_match(ctx, &mctx, expr, value->result, aexpr->match.value->loc)) { return; } struct type_tagged_union result_type = { .types = NULL }; struct match_case **next = &expr->match.cases, *_case = NULL; for (struct ast_match_case *acase = aexpr->match.cases; acase; acase = acase->next) { _case = *next = xcalloc(1, sizeof(struct match_case)); next = &_case->next; const struct type *ctype = NULL; if (acase->type) { ctype = type_store_lookup_atype(ctx, acase->type); if (!check_match_case(&mctx, ctype, expr, acase->type->loc)) { return; } if (ctype->size == SIZE_UNDEFINED) { error(ctx, acase->type->loc, expr, "Can't use type of undefined size in match case"); return; } } if (acase->name != NULL) { assert(ctype); if (ctype->storage == STORAGE_NULL) { error(ctx, aexpr->loc, expr, "Null is not a valid type for a binding"); return; } struct scope *scope = scope_push(&ctx->scope, SCOPE_MATCH); _case->object = scope_insert(scope, O_BIND, acase->name, acase->name, ctype, NULL); } _case->value = xcalloc(1, sizeof(struct expression)); _case->type = ctype; // Lower to compound // TODO: This should probably be done in a more first-class way struct ast_expression compound = { .type = EXPR_COMPOUND, .loc = acase->exprs.expr->loc, .compound = { .label = aexpr->match.label, .list = acase->exprs, }, }; check_expression(ctx, &compound, _case->value, hint); if (acase->name != NULL) { scope_pop(&ctx->scope); } if (expr->result == NULL) { expr->result = _case->value->result; tagged_append(&result_type, _case->value->result); } else if (expr->result != _case->value->result) { tagged_append(&result_type, _case->value->result); } } if (result_type.len > 1) { if (hint) { expr->result = hint; } else { expr->result = type_store_reduce_result( ctx, aexpr->loc, &result_type); } struct match_case *_case = expr->match.cases; struct ast_match_case *acase = aexpr->match.cases; while (_case) { if (hint && !type_is_assignable(ctx, hint, _case->value->result)) { error(ctx, acase->exprs.expr->loc, expr, "Match case is not assignable to result type"); return; } _case->value = lower_implicit_cast(ctx, expr->result, _case->value); _case = _case->next; acase = acase->next; } free(result_type.types); } } static void check_expr_measure(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->result = &builtin_type_size; switch (aexpr->measure.op) { case M_ALIGN: case M_SIZE: break; case M_LEN: expr->len.value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->measure.value, expr->len.value, NULL); const struct type *type = check_autodereference(ctx, aexpr->measure.value->loc, expr->len.value->result); type = type_dealias(ctx, type); enum type_storage vstor = type->storage; bool valid = vstor == STORAGE_ARRAY || vstor == STORAGE_SLICE || vstor == STORAGE_STRING || vstor == STORAGE_INVALID; if (!valid) { char *typename = gen_typename(expr->len.value->result); error(ctx, aexpr->measure.value->loc, expr, "len argument must be of an array, slice, or str type, but got %s", typename); free(typename); return; } if (vstor == STORAGE_ARRAY) { if (type->array.length == SIZE_UNDEFINED) { error(ctx, aexpr->measure.value->loc, expr, "Cannot take length of unbounded array type"); return; } expr->type = EXPR_LITERAL; expr->result = &builtin_type_size; expr->literal.object = NULL; expr->literal.uval = type->array.length; return; } expr->type = EXPR_LEN; return; case M_OFFSET: expr->type = EXPR_LITERAL; if (aexpr->measure.value->type != EXPR_ACCESS) { error(ctx, aexpr->measure.value->loc, expr, "offset argument must be a field or tuple access"); return; } if (aexpr->measure.value->access.type != ACCESS_FIELD && aexpr->measure.value->access.type != ACCESS_TUPLE) { error(ctx, aexpr->measure.value->loc, expr, "offset argument must be a field or tuple access"); return; } struct expression *value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->measure.value, value, NULL); if (value->result->storage == STORAGE_INVALID) { return; } if (value->access.type == ACCESS_FIELD) { expr->literal.uval = value->access.field->offset; } else { assert(value->access.type == ACCESS_TUPLE); expr->literal.uval = value->access.tvalue->offset; } return; } expr->type = EXPR_LITERAL; struct errors **cur_err = ctx->next; struct dimensions dim = type_store_lookup_dimensions( ctx, aexpr->measure.type); if (ctx->next != cur_err) { mkerror(expr); return; } struct ast_types *next = ctx->unresolved; ctx->unresolved = xcalloc(1, sizeof(struct ast_types)); ctx->unresolved->type = aexpr->measure.type; ctx->unresolved->next = next; if (aexpr->measure.op == M_ALIGN) { if (dim.align == ALIGN_UNDEFINED) { error(ctx, aexpr->measure.type->loc, expr, "Cannot take alignment of a type with undefined alignment"); return; } expr->literal.uval = dim.align; } else { if (dim.size == SIZE_UNDEFINED) { error(ctx, aexpr->measure.type->loc, expr, "Cannot take size of a type with undefined size"); return; } expr->literal.uval = dim.size; } } static void check_expr_propagate(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { struct expression *lvalue = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->propagate.value, lvalue, hint == &builtin_type_void ? NULL : hint); const struct type *intype = lvalue->result; if (intype->storage == STORAGE_INVALID) { mkerror(expr); return; } if (type_dealias(ctx, intype)->storage != STORAGE_TAGGED) { char *typename = gen_typename(intype); error(ctx, aexpr->loc, expr, "Cannot use error %s on non-tagged type %s", aexpr->propagate.abort ? "assertion" : "propagation", typename); free(typename); return; } if (!aexpr->propagate.abort) { struct scope *defer = scope_lookup_class(ctx->scope, SCOPE_DEFER); if (defer) { error(ctx, aexpr->loc, expr, "Cannot use error propagation in a defer expression"); return; } } struct type_tagged_union res = { .types = NULL }; struct type_tagged_union ret = { .types = NULL }; const struct type_tagged_union *intu = &type_dealias(ctx, intype)->tagged; for (size_t i = 0; i < intu->len; i++) { tagged_append(type_is_error(ctx, intu->types[i]) ? &ret : &res, intu->types[i]); } if (ret.len == 0) { error(ctx, aexpr->loc, expr, "No error can occur here, cannot %s", aexpr->propagate.abort ? "use error assertion" : "propagate"); return; } const struct type *return_type = type_store_lookup_tagged(ctx, aexpr->loc, &ret); const struct type *result_type = type_store_lookup_tagged(ctx, aexpr->loc, &res); // Lower to a match expression expr->type = EXPR_MATCH; expr->match.value = lvalue; struct scope *scope = scope_push(&ctx->scope, SCOPE_MATCH); struct match_case *case_ok = xcalloc(1, sizeof(struct match_case)); struct match_case *case_err = xcalloc(1, sizeof(struct match_case)); struct scope_object *ok_obj = NULL, *err_obj = NULL; if (result_type->size != SIZE_UNDEFINED) { struct ident *id = intern_generated(ctx, "ok.%d"); ok_obj = scope_insert(scope, O_BIND, id, id, result_type, NULL); } case_ok->type = result_type; case_ok->object = ok_obj; case_ok->value = xcalloc(1, sizeof(struct expression)); case_ok->value->result = result_type; case_ok->value->loc = expr->loc; if (ok_obj) { case_ok->value->type = EXPR_ACCESS; case_ok->value->access.type = ACCESS_IDENTIFIER; case_ok->value->access.object = ok_obj; } else { case_ok->value->type = EXPR_LITERAL; } case_err->value = xcalloc(1, sizeof(struct expression)); case_err->value->loc = expr->loc; if (aexpr->propagate.abort) { case_err->value->type = EXPR_ASSERT; case_err->value->assert = (struct expression_assert){ .cond = NULL, .message = NULL, .fixed_reason = ABORT_PROPAGATE_ERROR_OCCURRED, }; } else { if (return_type->size != SIZE_UNDEFINED) { struct ident *id = intern_generated(ctx, "err.%d"); err_obj = scope_insert(scope, O_BIND, id, id, return_type, NULL); } case_err->type = return_type; case_err->object = err_obj; if (!type_is_assignable(ctx, ctx->fntype->func.result, return_type)) { char *res = gen_typename(ctx->fntype->func.result); char *ret = gen_typename(return_type); error(ctx, aexpr->loc, expr, "Error type %s is not assignable to function result type %s", ret, res); free(res); free(ret); return; } case_err->value->type = EXPR_RETURN; struct expression *rval = xcalloc(1, sizeof(struct expression)); rval->result = return_type; rval->loc = expr->loc; if (err_obj != NULL) { rval->type = EXPR_ACCESS; rval->access.type = ACCESS_IDENTIFIER; rval->access.object = err_obj; } else { rval->type = EXPR_LITERAL; } case_err->value->_return.value = lower_implicit_cast(ctx, ctx->fntype->func.result, rval); } case_err->value->result = &builtin_type_never; expr->match.cases = case_ok; case_ok->next = case_err; scope_pop(&ctx->scope); expr->result = result_type; } static void check_expr_return(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { struct scope *defer = scope_lookup_class(ctx->scope, SCOPE_DEFER); if (defer) { error(ctx, aexpr->loc, NULL, "Cannot return inside a defer expression"); // continue checking so other errors can be reported } expr->type = EXPR_RETURN; expr->result = &builtin_type_never; struct expression *rval = expr->_return.value = xcalloc(1, sizeof(struct expression)); if (aexpr->control.value) { const struct type *hint = NULL; if (ctx->fntype) { hint = ctx->fntype->func.result; } check_expression(ctx, aexpr->control.value, rval, hint); } else { rval->type = EXPR_LITERAL; rval->result = &builtin_type_void; } if (ctx->fntype == NULL) { error(ctx, aexpr->loc, NULL, "Cannot return outside a function body"); return; } if (!type_is_assignable(ctx, ctx->fntype->func.result, rval->result)) { char *rettypename = gen_typename(rval->result); char *fntypename = gen_typename(ctx->fntype->func.result); error(ctx, aexpr->loc, NULL, "Return type %s is not assignable to function result type %s", rettypename, fntypename); free(rettypename); free(fntypename); return; } expr->_return.value = lower_implicit_cast(ctx, ctx->fntype->func.result, rval); } static void slice_bounds_check(struct context *ctx, struct expression *expr) { const struct type *atype = type_dereference(ctx, expr->slice.object->result, false); const struct type *dtype = type_dealias(ctx, atype); struct expression start, end; enum { START = 1, END = 1 << 1, LENGTH = 1 << 2 } bounds = 0; if (expr->slice.start && eval_expr(ctx, expr->slice.start, &start)) { bounds |= START; } if (expr->slice.end && eval_expr(ctx, expr->slice.end, &end)) { bounds |= END; } if (dtype->storage == STORAGE_ARRAY && dtype->array.length != SIZE_UNDEFINED) { bounds |= LENGTH; } if ((bounds & (START | LENGTH)) == (START | LENGTH) && start.literal.uval > dtype->array.length) { error(ctx, expr->loc, expr, "Start index must not be greater than array length"); } if ((bounds & (START | END)) == (START | END) && start.literal.uval > end.literal.uval) { error(ctx, expr->loc, expr, "Start index must not be greater than end index"); } if ((bounds & (END | LENGTH)) == (END | LENGTH) && end.literal.uval > dtype->array.length) { error(ctx, expr->loc, expr, "End index must not be greater than array length"); } } static void check_expr_slice(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_SLICE; expr->slice.object = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->slice.object, expr->slice.object, NULL); if (expr->slice.object->result->storage == STORAGE_INVALID) { mkerror(expr); return; } const struct type *atype = check_autodereference(ctx, aexpr->slice.object->loc, expr->slice.object->result); const struct type *dtype = type_dealias(ctx, atype); if (dtype->storage != STORAGE_SLICE && dtype->storage != STORAGE_ARRAY) { error(ctx, aexpr->slice.object->loc, expr, "Cannot slice non-array, non-slice object"); return; } const struct type *itype; if (aexpr->slice.start) { expr->slice.start = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->slice.start, expr->slice.start, &builtin_type_size); itype = type_dealias(ctx, expr->slice.start->result); if (!type_is_integer(ctx, itype)) { error(ctx, aexpr->slice.start->loc, expr, "Cannot use non-integer %s type as slicing operand", type_storage_unparse(itype->storage)); return; } if (dtype->array.members->size == SIZE_UNDEFINED) { error(ctx, aexpr->slice.start->loc, expr, "Cannot use left subslicing operand on a slice with member type of unknown size"); return; } expr->slice.start = lower_implicit_cast(ctx, &builtin_type_size, expr->slice.start); } if (aexpr->slice.end) { expr->slice.end = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->slice.end, expr->slice.end, &builtin_type_size); itype = type_dealias(ctx, expr->slice.end->result); if (!type_is_integer(ctx, itype)) { error(ctx, aexpr->slice.end->loc, expr, "Cannot use non-integer %s type as slicing operand", type_storage_unparse(itype->storage)); return; } expr->slice.end = lower_implicit_cast(ctx, &builtin_type_size, expr->slice.end); } else if (dtype->storage == STORAGE_ARRAY && dtype->array.length == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "Must have end index when slicing unbounded array"); return; } slice_bounds_check(ctx, expr); if (dtype->storage == STORAGE_SLICE) { expr->result = atype; } else { expr->result = type_store_lookup_slice(ctx, aexpr->loc, dtype->array.members); } } static void check_struct_exhaustive(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *stype) { stype = type_dealias(ctx, stype); if (stype->storage == STORAGE_UNION) { return; } assert(stype->storage == STORAGE_STRUCT); struct struct_field *sf = stype->struct_union.fields; struct ast_field_value *af = aexpr->_struct.fields; // XXX: O(n^2)? while (sf) { bool found = false; for (struct ast_field_value *f = af; f; f = f->next) { if (!sf->name) { check_struct_exhaustive(ctx, aexpr, expr, sf->type); found = true; continue; } if (strcmp(f->name, sf->name) == 0) { if (found) { error(ctx, aexpr->loc, expr, "Field '%s' is initialized multiple times", sf->name); } found = true; } } bool has_default = type_has_default(ctx, sf->type) || aexpr->_struct.undefined; if (!found && (!aexpr->_struct.autofill || !has_default)) { error(ctx, aexpr->loc, expr, "Field '%s' is uninitialized", sf->name); } sf = sf->next; } } static void check_expr_struct(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_STRUCT; const struct type *stype = NULL; if (aexpr->_struct.type != NULL) { struct scope_object *obj = scope_lookup(ctx->scope, aexpr->_struct.type); // resolve the unknown type wrap_resolver(ctx, obj, resolve_type); if (!obj) { error(ctx, aexpr->loc, expr, "Unknown type alias"); return; } if (obj->otype != O_TYPE) { error(ctx, aexpr->loc, expr, "Identifier does not refer to a type"); return; } stype = obj->type; enum type_storage storage = type_dealias(ctx, stype)->storage; if (storage != STORAGE_STRUCT && storage != STORAGE_UNION) { error(ctx, aexpr->loc, expr, "Type named is not a struct or union type"); return; } } struct ast_type satype = { .storage = STORAGE_STRUCT, }; struct ast_struct_union_field *tfield = &satype.struct_union.fields; struct ast_struct_union_field **tnext = &tfield->next; struct expr_struct_field *sexpr, **snext = &expr->_struct.fields; expr->_struct.autofill = aexpr->_struct.autofill; expr->_struct.undefined = aexpr->_struct.undefined; if (stype == NULL && expr->_struct.autofill) { error(ctx, aexpr->loc, expr, "Autofill is only permitted for named struct initializers"); return; } struct ast_field_value *afield = aexpr->_struct.fields; while (afield) { const struct type *ftype; *snext = sexpr = xcalloc(1, sizeof(struct expr_struct_field)); snext = &sexpr->next; sexpr->value = xcalloc(1, sizeof(struct expression)); if (!stype) { assert(afield->name); // TODO if (!afield->type) { error(ctx, aexpr->loc, expr, "Unnamed struct must specify field type"); return; } tfield->name = afield->name; tfield->type = afield->type; ftype = type_store_lookup_atype(ctx, tfield->type); check_expression(ctx, afield->initializer, sexpr->value, ftype); if (afield->next) { *tnext = tfield = xcalloc( 1, sizeof(struct ast_struct_union_type)); tnext = &tfield->next; } } else { if (!afield->name) { error(ctx, afield->initializer->loc, expr, "Cannot embed a struct literal into " "a named struct literal"); return; } sexpr->field = type_get_field(ctx, type_dealias(ctx, stype), afield->name); if (!sexpr->field) { error(ctx, afield->initializer->loc, expr, "No field by this name exists for this type"); return; } ftype = sexpr->field->type; check_expression(ctx, afield->initializer, sexpr->value, ftype); if (!type_is_assignable(ctx, sexpr->field->type, sexpr->value->result)) { char *init = gen_typename(sexpr->value->result); char *bind = gen_typename(sexpr->field->type); error(ctx, afield->initializer->loc, expr, "Initializer of type %s not assignable to struct field of type %s", init, bind); free(init); free(bind); return; } sexpr->value = lower_implicit_cast(ctx, sexpr->field->type, sexpr->value); } afield = afield->next; } if (stype) { expr->result = stype; check_struct_exhaustive(ctx, aexpr, expr, stype); } else { expr->result = type_store_lookup_atype(ctx, &satype); tfield = &satype.struct_union.fields; sexpr = expr->_struct.fields; while (tfield) { const struct struct_field *field = type_get_field(ctx, expr->result, tfield->name); if (!field) { // TODO: Use more specific error location error(ctx, aexpr->loc, expr, "No field by this name exists for this type"); return; } if (!type_is_assignable(ctx, field->type, sexpr->value->result)) { error(ctx, aexpr->loc, expr, "Cannot initialize struct field '%s' from value of this type", field->name); return; } sexpr->field = field; sexpr->value = lower_implicit_cast(ctx, field->type, sexpr->value); struct ast_struct_union_field *next = tfield->next; if (tfield != &satype.struct_union.fields) { free(tfield); } tfield = next; sexpr = sexpr->next; } } } static int casecmp(const void *_a, const void *_b) { const struct expression *a = *(const struct expression **)_a; const struct expression *b = *(const struct expression **)_b; assert(a->type == EXPR_LITERAL && b->type == EXPR_LITERAL); assert(type_dealias(NULL, a->result)->storage == type_dealias(NULL, b->result)->storage); if (type_is_signed(NULL, a->result)) { return a->literal.ival < b->literal.ival ? -1 : a->literal.ival > b->literal.ival ? 1 : 0; } else if (type_is_integer(NULL, a->result)) { return a->literal.uval < b->literal.uval ? -1 : a->literal.uval > b->literal.uval ? 1 : 0; } else if (type_dealias(NULL, a->result)->storage == STORAGE_STRING) { size_t len = a->literal.string.len < b->literal.string.len ? a->literal.string.len : b->literal.string.len; int ret = memcmp(a->literal.string.value, b->literal.string.value, len); if (ret != 0) { return ret; } return a->literal.string.len < b->literal.string.len ? -1 : a->literal.string.len > b->literal.string.len ? 1 : 0; } else if (type_dealias(NULL, a->result)->storage == STORAGE_BOOL) { return (int)a->literal.bval - (int)b->literal.bval; } else { assert(type_dealias(NULL, a->result)->storage == STORAGE_RCONST || type_dealias(NULL, a->result)->storage == STORAGE_RUNE); return a->literal.rune < b->literal.rune ? -1 : a->literal.rune > b->literal.rune ? 1 : 0; } } static size_t num_cases(struct context *ctx, const struct type *type) { type = type_dealias(ctx, type); switch (type->storage) { case STORAGE_BOOL: return 2; case STORAGE_STRING: return -1; case STORAGE_ENUM:; struct scope_object *obj = type->_enum.values->objects; assert(obj != NULL); size_t n = 0; for (struct scope_object *o = obj; o; o = o->lnext, ++n) { if (o->otype == O_SCAN) { wrap_resolver(ctx, o, resolve_enum_field); } assert(o->otype == O_CONST); } struct expression **cases_array = xcalloc(n, sizeof(struct expression *)); size_t i = 0; for (struct scope_object *o = obj; o; o = o->lnext, ++i) { cases_array[i] = o->value; } qsort(cases_array, n, sizeof(struct expression *), &casecmp); for (size_t i = 1, sz = n; i < sz; ++i) { if (casecmp(&cases_array[i - 1], &cases_array[i]) == 0) { --n; } } free(cases_array); return n; default: assert(type_is_integer(ctx, type) || type->storage == STORAGE_RUNE); assert(!type_is_flexible(type)); if (type->size >= sizeof(size_t)) { return -1; } return (size_t)1 << (type->size * 8); } } static void check_expr_switch(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_SWITCH; struct expression *value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->_switch.value, value, NULL); const struct type *type = lower_flexible(ctx, value->result, NULL); expr->_switch.value = value; if (!type_is_integer(ctx, type) && type_dealias(ctx, type)->storage != STORAGE_STRING && type_dealias(ctx, type)->storage != STORAGE_BOOL && type_dealias(ctx, type)->storage != STORAGE_RUNE) { error(ctx, aexpr->loc, expr, "Cannot switch on %s type", type_storage_unparse(type_dealias(ctx, type)->storage)); return; } struct type_tagged_union tagged = { .types = NULL }; struct switch_case **next = &expr->_switch.cases, *_case = NULL; size_t n = 0; bool has_default_case = false; struct ast_switch_case *acase; for (acase = aexpr->_switch.cases; acase; acase = acase->next) { _case = *next = xcalloc(1, sizeof(struct switch_case)); next = &_case->next; _case->value = xcalloc(1, sizeof(struct expression)); if (acase->options == NULL) { if (has_default_case) { error(ctx, acase->exprs.expr->loc, _case->value, "Duplicate default case"); } has_default_case = true; } struct case_option *opt, **next_opt = &_case->options; for (const struct ast_case_option *aopt = acase->options; aopt; aopt = aopt->next) { opt = *next_opt = xcalloc(1, sizeof(struct case_option)); struct expression *value = xcalloc(1, sizeof(struct expression)); struct expression *evaled = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aopt->value, value, type); if (!type_is_assignable(ctx, type, value->result)) { char *vtype = gen_typename(value->result); char *stype = gen_typename(type); error(ctx, aopt->value->loc, expr, "Invalid type %s for case in switch on type %s", vtype, stype); free(vtype); free(stype); return; } value = lower_implicit_cast(ctx, type, value); if (!eval_expr(ctx, value, evaled)) { error(ctx, aopt->value->loc, expr, "Unable to evaluate case at compile time"); return; } opt->value = evaled; next_opt = &opt->next; n++; } // Lower to compound // TODO: This should probably be done in a more first-class way struct ast_expression compound = { .type = EXPR_COMPOUND, .compound = { .label = aexpr->_switch.label, .list = acase->exprs, }, }; check_expression(ctx, &compound, _case->value, hint); tagged_append(&tagged, _case->value->result); } struct expression **cases_array = xcalloc(n, sizeof(struct expression *)); size_t i = 0; for (_case = expr->_switch.cases; _case; _case = _case->next) { for (const struct case_option *opt = _case->options; opt; opt = opt->next) { assert(i < n); if (opt->value->result->storage != STORAGE_INVALID) { cases_array[i] = opt->value; i++; } } } n = i; qsort(cases_array, n, sizeof(struct expression *), &casecmp); bool has_duplicate = false; for (size_t i = 1; i < n; i++) { if (casecmp(&cases_array[i - 1], &cases_array[i]) == 0) { error(ctx, cases_array[i - 1]->loc, cases_array[i - 1], "Duplicate switch case"); has_duplicate = true; } } free(cases_array); if (!has_default_case && !has_duplicate && value->result->storage != STORAGE_INVALID && (n == (size_t)-1 || n != num_cases(ctx, value->result))) { error(ctx, aexpr->loc, value, "Switch expression isn't exhaustive"); } if (hint) { expr->result = hint; } else { expr->result = type_store_reduce_result( ctx, aexpr->loc, &tagged); } _case = expr->_switch.cases; acase = aexpr->_switch.cases; while (_case) { if (!type_is_assignable(ctx, expr->result, _case->value->result)) { error(ctx, acase->exprs.expr->loc, expr, "Switch case is not assignable to result type"); return; } _case->value = lower_implicit_cast(ctx, expr->result, _case->value); _case = _case->next; acase = acase->next; } free(tagged.types); } static void check_expr_tuple(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_TUPLE; const struct type_tuple *ttuple = NULL; if (hint && type_dealias(ctx, hint)->storage == STORAGE_TUPLE) { ttuple = &type_dealias(ctx, hint)->tuple; } struct type_tuple result = {0}; struct type_tuple *rtype = &result; struct expression_tuple *tuple = &expr->tuple; for (const struct ast_expression_tuple *atuple = &aexpr->tuple; atuple; atuple = atuple->next) { tuple->value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, atuple->expr, tuple->value, ttuple ? ttuple->type : NULL); rtype->type = tuple->value->result; if (atuple->next) { rtype->next = xcalloc(1, sizeof(struct type_tuple)); rtype = rtype->next; tuple->next = xcalloc(1, sizeof(struct expression_tuple)); tuple = tuple->next; } if (ttuple) { ttuple = ttuple->next; } } if (hint && type_dealias(ctx, hint)->storage == STORAGE_TUPLE) { expr->result = hint; } else if (hint && type_dealias(ctx, hint)->storage == STORAGE_TAGGED) { const struct type *tagged = type_dealias(ctx, hint); for (size_t i = 0; i < tagged->tagged.len; i++) { const struct type *memb = tagged->tagged.types[i]; if (type_dealias(ctx, memb)->storage != STORAGE_TUPLE) { continue; } const struct type_tuple *ttuple = &type_dealias(ctx, memb)->tuple; const struct expression_tuple *etuple = &expr->tuple; bool valid = true; while (etuple) { if (!ttuple || !type_is_assignable(ctx, ttuple->type, etuple->value->result)) { valid = false; break; } ttuple = ttuple->next; etuple = etuple->next; } if (!ttuple && valid) { expr->result = type_dealias(ctx, memb); break; } } if (!expr->result) { error(ctx, aexpr->loc, expr, "Tuple value is not assignable to tagged union hint"); return; } } else { expr->result = type_store_lookup_tuple(ctx, aexpr->loc, &result); if (expr->result == &builtin_type_invalid) { // an error occurred return; } } ttuple = &type_dealias(ctx, expr->result)->tuple; struct expression_tuple *etuple = &expr->tuple; const struct ast_expression_tuple *atuple = &aexpr->tuple; while (etuple) { if (!ttuple) { error(ctx, atuple->expr->loc, expr, "Too many values for tuple type"); return; } if (!type_is_assignable(ctx, ttuple->type, etuple->value->result)) { char *vtype = gen_typename(etuple->value->result); char *ttype= gen_typename(ttuple->type); error(ctx, atuple->expr->loc, expr, "Value of type %s is not assignable to tuple value of type %s", vtype, ttype); free(vtype); free(ttype); return; } etuple->value = lower_implicit_cast(ctx, ttuple->type, etuple->value); etuple = etuple->next; atuple = atuple->next; ttuple = ttuple->next; } if (ttuple) { error(ctx, aexpr->loc, expr, "Too few values for tuple type"); return; } } static void check_expr_unarithm(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_UNARITHM; struct expression *operand = xcalloc(1, sizeof(struct expression)); expr->unarithm.operand = operand; expr->unarithm.op = aexpr->unarithm.op; const struct type *operand_hint = NULL; if (hint) { switch (expr->unarithm.op) { case UN_ADDRESS: if (hint->storage == STORAGE_SLICE) { operand_hint = type_store_lookup_array(ctx, aexpr->loc, hint->array.members, SIZE_UNDEFINED, false); } else if (hint->storage == STORAGE_POINTER) { operand_hint = hint->pointer.referent; } break; case UN_DEREF: operand_hint = type_store_lookup_pointer( ctx, aexpr->loc, hint, false); break; default: break; } } check_expression(ctx, aexpr->unarithm.operand, operand, operand_hint); if (operand->result->storage == STORAGE_INVALID) { mkerror(expr); return; } switch (expr->unarithm.op) { case UN_LNOT: if (type_dealias(ctx, operand->result)->storage != STORAGE_BOOL) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot perform logical NOT (!) on non-boolean type"); return; } expr->result = &builtin_type_bool; break; case UN_BNOT: if (!type_is_integer(ctx, operand->result)) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot perform binary NOT (~) on non-integer type"); return; } expr->result = operand->result; break; case UN_MINUS: if (!type_is_numeric(ctx, operand->result)) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot perform operation on non-numeric type"); return; } if (operand->result->storage == STORAGE_ICONST) { // Not technically quite right, but we need // operand->result to be lowered with expr->result, and // this is correct enough const struct type *old = operand->result; const struct type *new = type_create_flexible( STORAGE_ICONST, -old->flexible.min, -old->flexible.max); lower_flexible(ctx, old, new); } expr->result = operand->result; break; case UN_ADDRESS:; const struct type *ptrhint = NULL; if (hint && type_dealias(ctx, hint)->storage == STORAGE_POINTER) { ptrhint = type_dealias(ctx, hint)->pointer.referent; if (type_dealias(ctx, ptrhint)->storage == STORAGE_OPAQUE) { ptrhint = NULL; } } if (type_is_flexible(operand->result) && ptrhint) { const struct type *promoted = promote_flexible(ctx, operand->result, ptrhint); if (promoted) { operand->result = promoted; } } else if (ptrhint) { if (type_dealias(ctx, operand->result) == type_dealias(ctx, ptrhint)) { operand->result = ptrhint; } } expr->result = type_store_lookup_pointer( ctx, aexpr->loc, operand->result, false); break; case UN_DEREF: if (type_dealias(ctx, operand->result)->storage != STORAGE_POINTER) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot de-reference non-pointer type"); return; } if (type_dealias(ctx, operand->result)->pointer.nullable) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot dereference nullable pointer type"); return; } if (type_dealias(ctx, operand->result)->pointer.referent->size == SIZE_UNDEFINED) { error(ctx, aexpr->unarithm.operand->loc, expr, "Cannot dereference pointer to type of undefined size"); return; } expr->result = type_dealias(ctx, operand->result)->pointer.referent; break; } } static void check_expr_vastart(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { if (ctx->fntype->func.variadism != VARIADISM_C) { error(ctx, aexpr->loc, expr, "Cannot use vastart within function which does not use C-style variadism"); return; } expr->type = EXPR_VASTART; expr->result = &builtin_type_valist; } static void check_expr_vaarg(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_VAARG; expr->vaarg.ap = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->vaarg.ap, expr->vaarg.ap, &builtin_type_valist); if (type_dealias(ctx, expr->vaarg.ap->result)->storage != STORAGE_VALIST) { error(ctx, aexpr->loc, expr, "Expected vaarg operand to be valist"); return; } expr->result = type_store_lookup_atype(ctx, aexpr->vaarg.type); if (expr->result->size == SIZE_UNDEFINED) { error(ctx, aexpr->loc, expr, "vaarg type must have defined size"); return; } } static void check_expr_vaend(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->type = EXPR_VAEND; expr->vaarg.ap = xcalloc(1, sizeof(struct expression)); check_expression(ctx, aexpr->vaarg.ap, expr->vaarg.ap, &builtin_type_valist); if (type_dealias(ctx, expr->vaarg.ap->result)->storage != STORAGE_VALIST) { error(ctx, aexpr->loc, expr, "Expected vaend operand to be valist"); return; } expr->result = &builtin_type_void; } void check_expression(struct context *ctx, const struct ast_expression *aexpr, struct expression *expr, const struct type *hint) { expr->loc = aexpr->loc; switch (aexpr->type) { case EXPR_ACCESS: check_expr_access(ctx, aexpr, expr, hint); break; case EXPR_ALLOC: check_expr_alloc(ctx, aexpr, expr, hint); break; case EXPR_APPEND: check_expr_append_insert(ctx, aexpr, expr, hint); break; case EXPR_ASSERT: check_expr_assert(ctx, aexpr, expr, hint); break; case EXPR_ASSIGN: check_expr_assign(ctx, aexpr, expr, hint); break; case EXPR_BINARITHM: check_expr_binarithm(ctx, aexpr, expr, hint); break; case EXPR_BINDING: case EXPR_DEFINE: check_expr_binding(ctx, aexpr, expr, hint); break; case EXPR_BREAK: case EXPR_CONTINUE: case EXPR_YIELD: check_expr_control(ctx, aexpr, expr, hint); break; case EXPR_CALL: check_expr_call(ctx, aexpr, expr, hint); break; case EXPR_CAST: check_expr_cast(ctx, aexpr, expr, hint); break; case EXPR_COMPOUND: check_expr_compound(ctx, aexpr, expr, hint); break; case EXPR_LITERAL: check_expr_literal(ctx, aexpr, expr, hint); break; case EXPR_DEFER: check_expr_defer(ctx, aexpr, expr, hint); break; case EXPR_DELETE: check_expr_delete(ctx, aexpr, expr, hint); break; case EXPR_FOR: check_expr_for(ctx, aexpr, expr, hint); break; case EXPR_FREE: check_expr_free(ctx, aexpr, expr, hint); break; case EXPR_IF: check_expr_if(ctx, aexpr, expr, hint); break; case EXPR_INSERT: check_expr_append_insert(ctx, aexpr, expr, hint); break; case EXPR_MATCH: check_expr_match(ctx, aexpr, expr, hint); break; case EXPR_MEASURE: check_expr_measure(ctx, aexpr, expr, hint); break; case EXPR_PROPAGATE: check_expr_propagate(ctx, aexpr, expr, hint); break; case EXPR_RETURN: check_expr_return(ctx, aexpr, expr, hint); break; case EXPR_SLICE: check_expr_slice(ctx, aexpr, expr, hint); break; case EXPR_STRUCT: check_expr_struct(ctx, aexpr, expr, hint); break; case EXPR_SWITCH: check_expr_switch(ctx, aexpr, expr, hint); break; case EXPR_TUPLE: check_expr_tuple(ctx, aexpr, expr, hint); break; case EXPR_UNARITHM: check_expr_unarithm(ctx, aexpr, expr, hint); break; case EXPR_UNDEFINED: expr->type = EXPR_UNDEFINED; expr->result = &builtin_type_undefined; break; case EXPR_VAARG: check_expr_vaarg(ctx, aexpr, expr, hint); break; case EXPR_VAEND: check_expr_vaend(ctx, aexpr, expr, hint); break; case EXPR_VASTART: check_expr_vastart(ctx, aexpr, expr, hint); break; } assert(expr->result); flexible_refer(expr->result, &expr->result); } void append_decl(struct context *ctx, struct declaration *decl) { struct declarations *decls = xcalloc(1, sizeof(struct declarations)); decls->decl = *decl; decls->next = ctx->decls; ctx->decls = decls; } static void resolve_unresolved(struct context *ctx) { while (ctx->unresolved) { struct ast_types *unresolved = ctx->unresolved; ctx->unresolved = unresolved->next; type_store_lookup_atype(ctx, unresolved->type); free(unresolved); } } static void check_function(struct context *ctx, const struct scope_object *obj, const struct ast_decl *adecl) { const struct ast_function_decl *afndecl = &adecl->function; ctx->fntype = obj->type; if (ctx->fntype->storage == STORAGE_INVALID) { return; } struct declaration _decl, *decl = &_decl; decl->decl_type = DECL_FUNC; decl->func.type = obj->type; decl->func.flags = afndecl->flags; decl->exported = adecl->exported; decl->file = adecl->loc.file; decl->symbol = ident_to_sym(ctx->itbl, obj->ident); decl->ident = mkident(ctx, afndecl->ident, NULL); if (!adecl->function.body) { if (decl->func.flags != 0) { error(ctx, adecl->loc, NULL, "Function attributes cannot be used on prototypes"); return; } decl->func.body = NULL; goto end; // Prototype } if (afndecl->symbol != NULL && decl->func.flags != 0) { error(ctx, adecl->loc, NULL, "@symbol cannot be used alongside other function attributes"); } decl->func.scope = scope_push(&ctx->scope, SCOPE_FUNC); struct ast_function_parameters *params = afndecl->prototype.params; while (params) { const struct type *type = type_store_lookup_atype( ctx, params->type); if (obj->type->func.variadism == VARIADISM_HARE && !params->next) { type = type_store_lookup_slice(ctx, params->loc, type); } scope_insert(decl->func.scope, O_BIND, params->name, params->name, type, NULL); params = params->next; } // TODO: Add function name to errors if (decl->func.flags != 0) { const char *flag = NULL; switch (decl->func.flags) { case FN_INIT: flag = "@init"; break; case FN_FINI: flag = "@fini"; break; case FN_TEST: flag = "@test"; break; default: assert(0); // unreachable } if (obj->type->func.result != &builtin_type_void) { error(ctx, adecl->loc, NULL, "%s function must return void", flag); } if (decl->exported) { error(ctx, adecl->loc, NULL, "%s function cannot be exported", flag); } if (afndecl->prototype.params) { error(ctx, adecl->loc, NULL, "%s function cannot have parameters", flag); } else if (obj->type->func.variadism != VARIADISM_NONE) { error(ctx, adecl->loc, NULL, "%s function cannot be variadic", flag); } } struct expression *body = xcalloc(1, sizeof(struct expression)); check_expression(ctx, afndecl->body, body, obj->type->func.result); resolve_unresolved(ctx); if (!type_is_assignable(ctx, obj->type->func.result, body->result)) { char *restypename = gen_typename(body->result); char *fntypename = gen_typename(obj->type->func.result); error(ctx, afndecl->body->loc, body, "Expression result type %s is not assignable to function result type %s", restypename, fntypename); free(restypename); free(fntypename); return; } if (obj->type->func.result->storage != STORAGE_NEVER && obj->type->func.result->size == SIZE_UNDEFINED) { char *fntypename = gen_typename(obj->type->func.result); error(ctx, afndecl->body->loc, body, "Types with undefined size such as %s cannot be returned, consider using a pointer instead", fntypename); free(fntypename); return; } decl->func.body = lower_implicit_cast(ctx, obj->type->func.result, body); scope_pop(&ctx->scope); ctx->fntype = NULL; end: if ((adecl->function.flags & FN_TEST) && !ctx->is_test) { return; } append_decl(ctx, decl); } static struct scope_object * incomplete_decl_create(struct context *ctx, struct location loc, struct scope *scope, struct ident *ident, struct ident *name) { struct scope *subunit = ctx->unit->parent; ctx->unit->parent = NULL; struct scope_object *obj = scope_lookup(scope, name); ctx->unit->parent = subunit; if (obj) { error_norec(ctx, loc, "Duplicate global ident '%s'", ident_unparse(ident)); } obj = scope_insert(scope, O_SCAN, ident, name, NULL, NULL); obj->idecl = xcalloc(1, sizeof(struct incomplete_decl)); return obj; } static void scan_enum_field(struct context *ctx, struct scope *imports, struct scope *enum_scope, const struct type *etype, struct ast_enum_field *f) { // We have to process the last field first // This way, objects in enum_scope will have lnext pointing to // the previous element, which is important for implicit enum values. if (f->next) { scan_enum_field(ctx, imports, enum_scope, etype, f->next); } assert(etype->storage == STORAGE_ENUM); struct incomplete_enum_field *field = xcalloc(1, sizeof(struct incomplete_enum_field)); *field = (struct incomplete_enum_field){ .field = f, .enum_scope = enum_scope, }; struct ident *name = intern_ident(ctx->itbl, f->name->name, etype->alias.name); struct scope_object *obj = incomplete_decl_create( ctx, f->loc, enum_scope, name, f->name); obj->idecl->type = IDECL_ENUM_FLD; obj->idecl->imports = imports; obj->type = etype, obj->idecl->field = field; } static void check_hosted_main(struct context *ctx, struct location loc, const struct ast_decl *decl, struct ident *ident, const char *symbol) { if (*ctx->mainsym == '\0' || ctx->is_test) { return; } if (symbol != ctx->mainsym && (symbol != NULL || ident != ctx->mainident)) { return; } const struct ast_function_decl *func; if (decl && decl->decl_type == ADECL_FUNC) { func = &decl->function; if (func->flags != 0) { return; } } else { error(ctx, loc, NULL, "main must be a function in hosted environment"); return; } if (func->body != NULL && !decl->exported) { error(ctx, loc, NULL, "main must be exported in hosted environment"); return; } if (func->prototype.params != NULL) { error(ctx, loc, NULL, "main must not have parameters in hosted environment"); return; } if (func->prototype.variadism != VARIADISM_NONE) { error(ctx, loc, NULL, "main must not be variadic in hosted environment"); return; } if (func->prototype.result->storage != STORAGE_VOID) { error(ctx, loc, NULL, "main must return void in hosted environment"); return; } } static void scan_types(struct context *ctx, struct scope *imp, const struct ast_decl *decl) { const struct ast_type_decl *t = &decl->type; struct ident *with_ns = mkident(ctx, t->ident, NULL); check_hosted_main(ctx, decl->loc, NULL, with_ns, NULL); struct scope_object *obj = incomplete_decl_create(ctx, decl->loc, ctx->scope, with_ns, t->ident); obj->idecl->decl = (struct ast_decl){ .decl_type = ADECL_TYPE, .loc = decl->loc, .type = *t, .exported = decl->exported, }; obj->idecl->imports = imp; if (t->type->storage == STORAGE_ENUM) { bool exported = obj->idecl->decl.exported; const struct type *type = type_store_lookup_enum( ctx, t->type, exported); if (type->storage == STORAGE_INVALID) { return; // error occured } scope_push((struct scope **)&type->_enum.values, SCOPE_ENUM); scan_enum_field(ctx, imp, type->_enum.values, type, t->type->_enum.values); type->_enum.values->parent = ctx->defines; obj->otype = O_TYPE; obj->type = type; append_decl(ctx, &(struct declaration){ .decl_type = DECL_TYPE, .file = decl->loc.file, .ident = obj->ident, .exported = exported, .type = type, }); } else { obj->idecl->type = IDECL_DECL; } } static void unexported_type_error(struct context *ctx, struct location loc, const struct type *type) { char *s = gen_typename(type); error(ctx, loc, NULL, "Can't use unexported type %s in exported declaration", s); free(s); } static void check_exported_type(struct context *ctx, struct location loc, const struct type *type) { switch (type->storage) { case STORAGE_ALIAS: case STORAGE_ENUM: if (!type->alias.exported) { unexported_type_error(ctx, loc, type); } break; case STORAGE_ARRAY: case STORAGE_SLICE: check_exported_type(ctx, loc, type->array.members); break; case STORAGE_ERROR: check_exported_type(ctx, loc, type->error); break; case STORAGE_FUNCTION: for (const struct type_func_param *param = type->func.params; param; param = param->next) { check_exported_type(ctx, loc, param->type); } check_exported_type(ctx, loc, type->func.result); break; case STORAGE_POINTER: check_exported_type(ctx, loc, type->pointer.referent); break; case STORAGE_STRUCT: case STORAGE_UNION: for (const struct struct_field *field = type->struct_union.fields; field; field = field->next) { check_exported_type(ctx, loc, field->type); } break; case STORAGE_TAGGED: for (size_t i = 0; i < type->tagged.len; i++) { check_exported_type(ctx, loc, type->tagged.types[i]); } break; case STORAGE_TUPLE: for (const struct type_tuple *t = &type->tuple; t; t = t->next) { check_exported_type(ctx, loc, t->type); } break; case STORAGE_BOOL: case STORAGE_DONE: case STORAGE_INVALID: case STORAGE_F32: case STORAGE_F64: case STORAGE_FCONST: case STORAGE_I16: case STORAGE_I32: case STORAGE_I64: case STORAGE_I8: case STORAGE_ICONST: case STORAGE_INT: case STORAGE_NEVER: case STORAGE_NOMEM: case STORAGE_NULL: case STORAGE_OPAQUE: case STORAGE_RCONST: case STORAGE_RUNE: case STORAGE_SIZE: case STORAGE_STRING: case STORAGE_U16: case STORAGE_U32: case STORAGE_U64: case STORAGE_U8: case STORAGE_UINT: case STORAGE_UINTPTR: case STORAGE_VALIST: case STORAGE_VOID: case STORAGE_UNDEFINED: break; } } static void resolve_const(struct context *ctx, struct scope_object *obj) { const struct ast_global_decl *decl = &obj->idecl->decl.constant; assert(!decl->symbol); // Invariant const struct type *type = NULL; if (decl->type) { type = type_store_lookup_atype(ctx, decl->type); } struct expression *init = xcalloc(1, sizeof(struct expression)); obj->value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, decl->init, init, type); if (!decl->type) { type = init->result; } if (obj->idecl->decl.exported) { struct location loc = decl->type ? decl->type->loc : decl->init->loc; check_exported_type(ctx, loc, type); } if (!type_is_assignable(ctx, type, init->result)) { char *typename1 = gen_typename(init->result); char *typename2 = gen_typename(type); error(ctx, decl->init->loc, obj->value, "Initializer type %s is not assignable to constant type %s", typename1, typename2); free(typename1); free(typename2); type = &builtin_type_invalid; goto end; } if (decl->type) { if (decl->type->storage == STORAGE_ARRAY && decl->type->array.contextual) { type = lower_flexible(ctx, init->result, NULL); } else { init = lower_implicit_cast(ctx, type, init); } } if (!eval_expr(ctx, init, obj->value)) { error(ctx, decl->init->loc, obj->value, "Unable to evaluate initializer at compile time"); type = &builtin_type_invalid; goto end; } end: obj->otype = O_CONST; if (!ctx->defines || ctx->errors) { return; } struct scope_object *shadow_obj = scope_lookup(ctx->defines, obj->ident); if (shadow_obj && obj != shadow_obj) { // Shadowed by define if (type_is_flexible(obj->value->result) || type_is_flexible(shadow_obj->value->result)) { const struct type *promoted = promote_flexible(ctx, obj->value->result, shadow_obj->value->result); if (promoted == NULL) { const char *msg; char *typename = NULL; if (!type_is_flexible(obj->value->result)) { msg = "Constant of type %s is shadowed by define of incompatible flexible type"; typename = gen_typename(obj->value->result); } else if (!type_is_flexible(shadow_obj->value->result)) { msg = "Constant of flexible type is shadowed by define of incompatible type %s"; typename = gen_typename(shadow_obj->value->result); } else { msg = "Constant of flexible type is shadowed by define of incompatible flexible type"; } error(ctx, obj->idecl->decl.loc, NULL, msg, typename); free(typename); } else { shadow_obj->value = lower_implicit_cast(ctx, promoted, shadow_obj->value); } } else if (obj->value->result != shadow_obj->value->result) { char *typename = gen_typename(obj->value->result); char *shadow_typename = gen_typename(shadow_obj->value->result); error(ctx, obj->idecl->decl.loc, NULL, "Constant of type %s is shadowed by define of incompatible type %s", typename, shadow_typename); free(typename); free(shadow_typename); } obj->value = shadow_obj->value; } append_decl(ctx, &(struct declaration){ .decl_type = DECL_CONST, .file = obj->idecl->decl.loc.file, .ident = obj->ident, .exported = obj->idecl->decl.exported, .constant = { .type = type, .value = obj->value, } }); } static void resolve_function(struct context *ctx, struct scope_object *obj) { const struct ast_function_decl *decl = &obj->idecl->decl.function; const struct ast_type fn_atype = { .loc = obj->idecl->decl.loc, .storage = STORAGE_FUNCTION, .func = decl->prototype, }; const struct type *fntype = type_store_lookup_atype(ctx, &fn_atype); if (obj->idecl->decl.exported) { check_exported_type(ctx, obj->idecl->decl.loc, fntype); } obj->otype = O_DECL; obj->type = fntype; } static void resolve_global(struct context *ctx, struct scope_object *obj) { const struct ast_global_decl *decl = &obj->idecl->decl.global; const struct type *type = NULL; bool context = false; struct expression *init, *value = NULL; if (decl->type) { type = type_store_lookup_atype(ctx, decl->type); if (type->storage == STORAGE_NEVER) { error(ctx, decl->type->loc, NULL, "Global cannot have type never"); type = &builtin_type_invalid; goto end; } context = decl->type->storage == STORAGE_ARRAY && decl->type->array.contextual; if (context && !decl->init) { error(ctx, decl->type->loc, NULL, "Cannot infer array length without an initializer"); type = &builtin_type_invalid; goto end; } } if (decl->init) { init = xcalloc(1, sizeof(struct expression)); value = xcalloc(1, sizeof(struct expression)); check_expression(ctx, decl->init, init, type); if (type) { if (!type_is_assignable(ctx, type, init->result)) { char *typename1 = gen_typename(init->result); char *typename2 = gen_typename(type); error(ctx, decl->init->loc, value, "Initializer type %s is not assignable to global type %s", typename1, typename2); free(typename1); free(typename2); type = &builtin_type_invalid; goto end; } } else { type = lower_flexible(ctx, init->result, NULL); } if (context) { type = init->result; } else { init = lower_implicit_cast(ctx, type, init); } if (type->storage == STORAGE_NEVER) { error(ctx, obj->idecl->decl.loc, NULL, "Global cannot have type never"); type = &builtin_type_invalid; goto end; } if (type->size == SIZE_UNDEFINED) { error(ctx, decl->init->loc, NULL, "Cannot initialize object with undefined size"); type = &builtin_type_invalid; goto end; } if (type->storage == STORAGE_NULL) { error(ctx, decl->init->loc, NULL, "Can't initialize global as null without explicit type hint"); type = &builtin_type_invalid; goto end; } if (!eval_expr(ctx, init, value)) { error(ctx, decl->init->loc, value, "Unable to evaluate initializer at compile time"); type = &builtin_type_invalid; goto end; } } if (obj->idecl->decl.exported) { struct location loc = decl->type ? decl->type->loc : decl->init->loc; check_exported_type(ctx, loc, type); } end:; struct ident *name = mkident(ctx, obj->name, NULL); obj->otype = O_DECL; obj->type = type; if (decl->threadlocal) { obj->flags |= SO_THREADLOCAL; } append_decl(ctx, &(struct declaration){ .decl_type = DECL_GLOBAL, .file = obj->idecl->decl.loc.file, .ident = name, .symbol = ident_to_sym(ctx->itbl, obj->ident), .exported = obj->idecl->decl.exported, .global = { .type = type, .value = value, .threadlocal = obj->idecl->decl.global.threadlocal, } }); } static void resolve_enum_field(struct context *ctx, struct scope_object *obj) { assert(obj->idecl->type == IDECL_ENUM_FLD); const struct type *type = obj->type; struct ident *localname = intern_name(ctx->itbl, obj->ident->name); struct scope_object *new = scope_lookup(obj->idecl->field->enum_scope, localname); if (new != obj) { wrap_resolver(ctx, new, resolve_enum_field); assert(new->otype == O_CONST); obj->otype = O_CONST; obj->value = new->value; return; } ctx->scope = obj->idecl->field->enum_scope; obj->value = xcalloc(1, sizeof(struct expression)); obj->value->result = type; if (obj->idecl->field->field->value) { // explicit value struct expression *initializer = xcalloc(1, sizeof(struct expression)); check_expression(ctx, obj->idecl->field->field->value, initializer, type->alias.type); if (!type_is_assignable(ctx, type->alias.type, initializer->result)) { char *inittypename = gen_typename(initializer->result); char *builtintypename = gen_typename(type->alias.type); error_norec(ctx, obj->idecl->field->field->value->loc, "Enum value type (%s) is not assignable from initializer type (%s) for value %s", builtintypename, inittypename, obj->ident->name); } initializer = lower_implicit_cast(ctx, type, initializer); if (!eval_expr(ctx, initializer, obj->value)) { error_norec(ctx, obj->idecl->field->field->value->loc, "Unable to evaluate constant initializer at compile time"); } } else { // implicit value struct scope_object *next = obj->lnext; // find previous enum value wrap_resolver(ctx, next, resolve_enum_field); obj->value->type = EXPR_LITERAL; if (type_is_signed(ctx, type_dealias(ctx, type))) { if (next == NULL) { obj->value->literal.ival = 0; } else { obj->value->literal.ival = next->value->literal.ival + 1; } } else { if (next == NULL) { obj->value->literal.uval = 0; } else { obj->value->literal.uval = next->value->literal.uval + 1; } } } obj->otype = O_CONST; } static const struct type * lookup_enum_type(struct context *ctx, const struct scope_object *obj) { const struct type *enum_type = NULL; switch (obj->otype) { case O_SCAN: { if (obj->idecl->in_progress) { // Type alias cycle will be handled in check return NULL; } if (obj->idecl->type != IDECL_DECL || obj->idecl->decl.decl_type != ADECL_TYPE) { return NULL; } if (obj->idecl->decl.type.type->storage == STORAGE_ENUM) { assert(false); } else if (obj->idecl->decl.type.type->storage == STORAGE_ALIAS) { ctx->scope->parent = obj->idecl->imports; const struct scope_object *new = scope_lookup(ctx->scope, obj->idecl->decl.type.type->alias); if (new) { obj->idecl->in_progress = true; enum_type = lookup_enum_type(ctx, new); obj->idecl->in_progress = false; } } break; } case O_TYPE: enum_type = obj->type; break; default: return NULL; } if (!enum_type) { return NULL; } enum_type = type_dealias(ctx, enum_type); if (enum_type->storage != STORAGE_ENUM) { return NULL; } return enum_type; } static void scan_enum_field_aliases(struct context *ctx, struct scope_object *obj) { const struct type *enum_type = lookup_enum_type(ctx, obj); if (!enum_type) { return; } // orig->type is (perhaps transitively) an alias of a resolved enum // type, which means its dependency graph is a linear chain of // resolved types ending with that enum, so we can immediately resolve it wrap_resolver(ctx, obj, resolve_type); for (const struct scope_object *val = enum_type->_enum.values->objects; val; val = val->lnext) { struct ast_enum_field *afield = xcalloc(1, sizeof(struct ast_enum_field)); *afield = (struct ast_enum_field){ .loc = (struct location){0}, // XXX: what to put here? .name = (struct ident *)val->name, }; struct incomplete_enum_field *field = xcalloc(1, sizeof(struct incomplete_enum_field)); *field = (struct incomplete_enum_field){ .field = afield, .enum_scope = val->idecl->field->enum_scope, }; struct ident *name = intern_ident(ctx->itbl, val->name->name, obj->name); struct scope_object *new = incomplete_decl_create(ctx, (struct location){0}, ctx->scope, name, name); new->idecl->type = IDECL_ENUM_FLD; new->type = obj->type; new->idecl->field = field; } } void resolve_dimensions(struct context *ctx, struct scope_object *obj) { if (obj->idecl->type != IDECL_DECL || obj->idecl->decl.decl_type != ADECL_TYPE) { struct location loc; if (obj->idecl->type == IDECL_ENUM_FLD) { loc = obj->idecl->field->field->loc; } else { loc = obj->idecl->decl.loc; } char *ident = ident_unparse(obj->name); error(ctx, loc, NULL, "'%s' is not a type", ident); free(ident); obj->type = &builtin_type_invalid; return; } struct dimensions dim = type_store_lookup_dimensions(ctx, obj->idecl->decl.type.type); obj->type = xcalloc(1, sizeof(struct type)); *(struct type *)obj->type = (struct type){ .size = dim.size, .align = dim.align, }; } void resolve_type(struct context *ctx, struct scope_object *obj) { struct location loc; if (obj->idecl->type == IDECL_ENUM_FLD) { loc = obj->idecl->field->field->loc; } else { loc = obj->idecl->decl.loc; } if (obj->idecl->type != IDECL_DECL || obj->idecl->decl.decl_type != ADECL_TYPE) { error_norec(ctx, loc, "'%s' is not a type", ident_unparse(obj->name)); } // compute type dimensions struct errors **cur_err = ctx->next; struct dimensions dim = type_store_lookup_dimensions( ctx, obj->idecl->decl.type.type); obj->idecl->in_progress = false; // compute type representation and store it struct type *alias = (struct type *)type_store_lookup_alias(ctx, obj->ident, obj->name, NULL, obj->idecl->decl.exported); obj->otype = O_TYPE; obj->type = alias; if (ctx->next == cur_err) { alias->size = dim.size; alias->align = dim.align; alias->alias.type = type_store_lookup_atype( ctx, obj->idecl->decl.type.type); } else { alias->alias.type = &builtin_type_invalid; } assert(alias->alias.type != NULL); if (obj->idecl->decl.exported) { check_exported_type(ctx, obj->idecl->decl.type.type->loc, alias->alias.type); } if (alias->alias.type->storage == STORAGE_NEVER) { error(ctx, loc, NULL, "Can't declare type alias of never"); alias->alias.type = &builtin_type_invalid; } append_decl(ctx, &(struct declaration){ .decl_type = DECL_TYPE, .file = obj->idecl->decl.loc.file, .ident = obj->ident, .exported = obj->idecl->decl.exported, .type = alias, }); } static struct scope_object * scan_const(struct context *ctx, struct scope *imports, bool exported, struct location loc, const struct ast_global_decl *decl) { struct ident *with_ns = mkident(ctx, decl->ident, NULL); check_hosted_main(ctx, loc, NULL, with_ns, NULL); struct scope_object *obj = incomplete_decl_create(ctx, loc, ctx->scope, with_ns, decl->ident); obj->idecl->type = IDECL_DECL; obj->idecl->decl = (struct ast_decl){ .decl_type = ADECL_CONST, .loc = loc, .constant = *decl, .exported = exported, }; obj->idecl->imports = imports; return obj; } static void scan_decl(struct context *ctx, struct scope *imports, const struct ast_decl *decl) { struct scope_object *obj; struct ident *ident; switch (decl->decl_type) { case ADECL_CONST: scan_const(ctx, imports, decl->exported, decl->loc, &decl->constant); break; case ADECL_GLOBAL: ident = mkident(ctx, decl->global.ident, decl->global.symbol); check_hosted_main(ctx, decl->loc, NULL, ident, decl->global.symbol); obj = incomplete_decl_create(ctx, decl->loc, ctx->scope, ident, decl->global.ident); obj->idecl->type = IDECL_DECL; obj->idecl->decl = (struct ast_decl){ .decl_type = ADECL_GLOBAL, .loc = decl->loc, .global = decl->global, .exported = decl->exported, }; obj->idecl->imports = imports; break; case ADECL_FUNC:; const struct ast_function_decl *func = &decl->function; struct ident *name; if (func->flags) { const char *template = NULL; if (func->flags & FN_TEST) { template = "testfunc.%d"; } else if (func->flags & FN_INIT) { template = "initfunc.%d"; } else if (func->flags & FN_FINI) { template = "finifunc.%d"; } assert(template); ident = name = intern_generated(ctx, template); } else { ident = mkident(ctx, func->ident, func->symbol); name = func->ident; } obj = incomplete_decl_create(ctx, decl->loc, ctx->scope, ident, name); check_hosted_main(ctx, decl->loc, decl, ident, func->symbol); obj->idecl->type = IDECL_DECL; obj->idecl->decl = (struct ast_decl){ .decl_type = ADECL_FUNC, .loc = decl->loc, .function = *func, .exported = decl->exported, }; obj->idecl->imports = imports; break; case ADECL_TYPE: scan_types(ctx, imports, decl); break; case ADECL_ASSERT:; struct ident *id = intern_generated(ctx, "static_assert.%d"); obj = incomplete_decl_create(ctx, decl->loc, ctx->scope, id, id); obj->idecl->type = IDECL_DECL; obj->idecl->decl = (struct ast_decl){ .decl_type = ADECL_ASSERT, .loc = decl->loc, .assert = decl->assert, .exported = decl->exported, }; obj->idecl->imports = imports; break; } } static void resolve_decl(struct context *ctx, struct scope_object *obj) { switch (obj->idecl->type) { case IDECL_ENUM_FLD: resolve_enum_field(ctx, obj); return; case IDECL_DECL: break; } switch (obj->idecl->decl.decl_type) { case ADECL_CONST: resolve_const(ctx, obj); return; case ADECL_GLOBAL: resolve_global(ctx, obj); return; case ADECL_FUNC: resolve_function(ctx, obj); return; case ADECL_TYPE: resolve_type(ctx, obj); return; case ADECL_ASSERT:; struct expression expr = {0}; check_assert(ctx, obj->idecl->decl.assert, obj->idecl->decl.loc, &expr); return; } abort(); } void wrap_resolver(struct context *ctx, struct scope_object *obj, resolvefn resolver) { // ensure this declaration wasn't already scanned if (!obj || obj->otype != O_SCAN) { return; } // save current subunit and enum context struct scope *scope = ctx->scope; struct scope *subunit = ctx->unit->parent; ctx->unit->parent = NULL; const struct type *fntype = ctx->fntype; ctx->fntype = NULL; struct ast_types *unresolved = ctx->unresolved; ctx->unresolved = NULL; // load this declaration's subunit context ctx->scope = ctx->defines; ctx->unit->parent = obj->idecl->imports; // resolving a declaration that is already in progress -> cycle if (obj->idecl->in_progress) { struct location loc; if (obj->idecl->type == IDECL_ENUM_FLD) { loc = obj->idecl->field->field->loc; } else { loc = obj->idecl->decl.loc; } error_norec(ctx, loc, "Circular dependency for '%s'", ident_unparse(obj->name)); } obj->idecl->in_progress = true; resolver(ctx, obj); obj->idecl->in_progress = false; resolve_unresolved(ctx); // load stored context ctx->unresolved = unresolved; ctx->fntype = fntype; ctx->unit->parent = subunit; ctx->scope = scope; } static void load_import(struct context *ctx, const struct ast_decls *defines, struct ast_imports *import, struct scope *scope) { struct scope *mod = module_resolve(ctx, defines, import->ident); if (import->mode == IMPORT_MEMBERS) { for (const struct ast_import_members *member = import->members; member; member = member->next) { struct ident *ident = intern_ident(ctx->itbl, member->name->name, import->ident); const struct scope_object *obj = scope_lookup(mod, ident); if (!obj) { error_norec(ctx, member->loc, "Unknown object '%s'", ident_unparse(ident)); } assert(obj->otype != O_SCAN); // obj->type and obj->value are a union, so it doesn't // matter which is passed into scope_insert struct scope_object *new = scope_insert(scope, obj->otype, obj->ident, member->name, obj->type, NULL); new->flags = obj->flags; if (obj->otype != O_TYPE || type_dealias(ctx, obj->type)->storage != STORAGE_ENUM) { continue; } const struct scope *enum_scope = type_dealias(ctx, obj->type)->_enum.values; for (const struct scope_object *o = enum_scope->objects; o; o = o->lnext) { struct ident *value_ident = intern_ident(ctx->itbl, o->name->name, ident); struct ident *value_name = intern_ident(ctx->itbl, o->name->name, member->name); scope_insert(scope, o->otype, value_ident, value_name, NULL, o->value); } } return; } struct ident *prefix = NULL; switch (import->mode) { case IMPORT_NORMAL: prefix = intern_name(ctx->itbl, import->ident->name); break; case IMPORT_ALIAS: prefix = intern_name(ctx->itbl, import->alias); break; case IMPORT_WILDCARD: prefix = NULL; break; case IMPORT_MEMBERS: abort(); // Unreachable } for (const struct scope_object *obj = mod->objects; obj; obj = obj->lnext) { assert(obj->otype != O_SCAN); struct scope_object *new; if (import->mode == IMPORT_NORMAL) { // obj->type and obj->value are a union, so it doesn't // matter which is passed into scope_insert new = scope_insert(scope, obj->otype, obj->ident, obj->name, obj->type, NULL); new->flags = obj->flags; } struct ident *name; if (obj->name->ns == NULL) { // this is only possible if an invalid .td file is used. // this check is necessary since the scope_lookup below // will segfault if obj->name.ns is NULL error_norec(ctx, (struct location){0}, "Invalid typedefs for %s", ident_unparse(import->ident)); } const struct scope_object *_enum = scope_lookup(mod, obj->name->ns); if (_enum != NULL && _enum->otype == O_TYPE && type_dealias(NULL, _enum->type)->storage == STORAGE_ENUM) { // include enum type in ident if object is an enum // constant struct ident *ns = intern_ident(ctx->itbl, obj->name->ns->name, prefix); name = intern_ident(ctx->itbl, obj->name->name, ns); } else { name = intern_ident(ctx->itbl, obj->name->name, prefix); } // obj->type and obj->value are a union, so it doesn't matter // which is passed into scope_insert new = scope_insert(scope, obj->otype, obj->ident, name, obj->type, NULL); new->flags = obj->flags; } } static const struct location defineloc = { .file = 0, .lineno = 1, .colno = 1, }; struct scope * check_internal(type_store *ts, struct modcache **cache, bool is_test, const char *mainsym, struct ident *mainident, const struct ast_decls *defines, const struct ast_unit *aunit, struct unit *unit, struct intern_table *itbl, bool scan_only) { struct context ctx = {0}; ctx.ns = unit->ns; ctx.is_test = is_test; ctx.mainsym = mainsym; ctx.mainident = mainident; ctx.store = ts; ctx.next = &ctx.errors; ctx.modcache = cache; ctx.itbl = itbl; // Top-level scope management involves: // // - Creating a top-level scope for the whole unit, to which // declarations are added. // - Creating a scope for each sub-unit, and populating it with imports. // // Further down the call frame, subsequent functions will create // sub-scopes for each declaration, expression-list, etc. // Put defines into a temporary scope (-D on the command line) sources[0] = "-D"; ctx.scope = NULL; ctx.unit = scope_push(&ctx.scope, SCOPE_DEFINES); for (const struct ast_decls *def = defines; def; def = def->next) { const struct ast_decl *decl = &def->decl; assert(decl->decl_type == ADECL_CONST); struct scope_object *obj = scan_const(&ctx, NULL, false, defineloc, &decl->constant); resolve_const(&ctx, obj); } ctx.defines = ctx.scope; ctx.scope = NULL; ctx.defines->parent = ctx.unit = scope_push(&ctx.scope, SCOPE_UNIT); sources[0] = ""; // Populate the imports and put declarations into a scope. // Each declaration holds a reference to its subunit's imports // A scope gets us: // a) duplicate detection for free // b) a way to find declaration's definition when it's refered to struct scopes *subunit_scopes = NULL, **next = &subunit_scopes; struct scope *su_scope = NULL; struct identifiers **inext = &unit->imports; for (const struct ast_subunit *su = &aunit->subunits; su; su = su->next) { su_scope = NULL; scope_push(&su_scope, SCOPE_SUBUNIT); for (struct ast_imports *imports = su->imports; imports; imports = imports->next) { load_import(&ctx, defines, imports, su_scope); bool found = false; for (struct identifiers *uimports = unit->imports; uimports; uimports = uimports->next) { if (uimports->ident == imports->ident) { found = true; break; } } if (!found) { struct identifiers *uimport = *inext = xcalloc(1, sizeof(struct identifiers)); uimport->ident = imports->ident; inext = &uimport->next; } } for (struct ast_decls *d = su->decls; d; d = d->next) { scan_decl(&ctx, su_scope, &d->decl); } *next = xcalloc(1, sizeof(struct scopes)); (*next)->scope = su_scope; next = &(*next)->next; } // Find enum aliases and store them in incomplete enum value declarations for (struct scope_object *obj = ctx.scope->objects; obj; obj = obj->lnext) { scan_enum_field_aliases(&ctx, obj); } // XXX: shadowed declarations are not checked for consistency ctx.scope = ctx.defines; for (const struct scope_object *obj = ctx.scope->objects; obj; obj = obj->lnext) { const struct scope_object *shadowed_obj = scope_lookup(ctx.unit, obj->name); if (!shadowed_obj) { continue; } if (shadowed_obj->otype == O_CONST) { continue; } if (shadowed_obj->otype == O_SCAN) { if (shadowed_obj->idecl->type == IDECL_DECL && shadowed_obj->idecl->decl.decl_type == ADECL_CONST) { continue; } } error(&ctx, defineloc, NULL, "Define shadows a non-define object"); } // Perform actual declaration resolution for (struct scope_object *obj = ctx.unit->objects; obj; obj = obj->lnext) { wrap_resolver(&ctx, obj, resolve_decl); // populate the expression graph if (obj->idecl->type == IDECL_DECL && obj->idecl->decl.decl_type == ADECL_FUNC) { ctx.unit->parent = obj->idecl->imports; check_function(&ctx, obj, &obj->idecl->decl); } } assert(ctx.unresolved == NULL); handle_errors(ctx.errors); unit->declarations = ctx.decls; if (!(scan_only || unit->declarations)) { xfprintf(stderr, "Error: module contains no declarations\n"); exit(EXIT_CHECK); } ctx.unit->parent = NULL; return ctx.unit; } struct scope * check(type_store *ts, bool is_test, const char *mainsym, struct ident *mainident, const struct ast_decls *defines, const struct ast_unit *aunit, struct unit *unit, struct intern_table *itbl) { struct modcache *modcache[MODCACHE_BUCKETS] = {0}; return check_internal(ts, modcache, is_test, mainsym, mainident, defines, aunit, unit, itbl, false); }