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.
5048 lines
140 KiB
C
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);
|
|
}
|