Files
ww/ref/harec/src/check.c
Hojun-Cho 8e3752f5ea ref: vendor harec checker sources (#20)
ref/hare/ ships only ast/lex/parse/module/unparse — no checker. Every
bail-fix landing in A.6.2.1 (helper bail audit, N_IDENT bail audit,
enum fold) needs a `ref/harec/...` cite anchor so divergence rationale
isn't claimed against memory. PLAN.md L16, L35.

Snapshot of upstream commit ee640c6b2c17f57e8014d277a3028b262ffb7952
(`rt/+openbsd/start.ha: remove outdated comment about crt0`). 20 files:
5 src (check.c, eval.c, scope.c, types.c, type_store.c), 14 transitive
include/ headers (no parser/codegen surface), and COPYING.

License GPLv3 (COPYING included). Vendored as read-only reference per
CLAUDE.md rule 6 — no redistribution as binary, no edits.
2026-05-21 23:03:11 +09:00

5048 lines
140 KiB
C

#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#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] = "<unknown>";
// 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);
}