commit 2eeb98fe358d0536a4f45577eadd61733f07c005
parent 7ecfa4acc36b37a549b5ff44d19cc754137af45c
Author: Drew DeVault <sir@cmpwn.com>
Date: Sun, 10 Jan 2021 14:22:22 -0500
Implement struct field selection
There is an issue that I have yet to investigate with recursive
auto-dereferencing.
Diffstat:
6 files changed, 160 insertions(+), 8 deletions(-)
diff --git a/include/expr.h b/include/expr.h
@@ -44,7 +44,10 @@ struct expression_access {
struct expression *array;
struct expression *index;
};
- // TODO: Field selection
+ struct {
+ struct expression *_struct;
+ const struct struct_field *field;
+ };
};
};
diff --git a/include/types.h b/include/types.h
@@ -114,8 +114,8 @@ struct type {
};
const struct type *type_dereference(const struct type *type);
-const struct struct_field *type_lookup_field(
- const struct type *type, const char *name);
+const struct struct_field *type_get_field(
+ const struct type *type, const char *name);
const char *type_storage_unparse(enum type_storage storage);
bool type_is_signed(const struct type *type);
diff --git a/src/check.c b/src/check.c
@@ -104,7 +104,23 @@ check_expr_access(struct context *ctx,
atype->array.members, atype->flags | atype->array.members->flags);
break;
case ACCESS_FIELD:
- assert(0); // TODO
+ expr->access._struct = xcalloc(1, sizeof(struct expression));
+ check_expression(ctx, aexpr->access._struct, expr->access._struct);
+ const struct type *stype = expr->access._struct->result;
+ while (stype->storage == TYPE_STORAGE_POINTER) {
+ expect(&aexpr->access.array->loc,
+ !(stype->pointer.flags & PTR_NULLABLE),
+ "Cannot dereference nullable pointer for field selection");
+ stype = stype->pointer.referent;
+ }
+ expect(&aexpr->access._struct->loc,
+ stype->storage == TYPE_STORAGE_STRUCT || stype->storage == TYPE_STORAGE_UNION,
+ "Cannot index non-struct, non-union object");
+ expr->access.field = type_get_field(stype, aexpr->access.field);
+ expect(&aexpr->access._struct->loc, expr->access.field,
+ "No such struct field '%s'", aexpr->access.field);
+ expr->result = expr->access.field->type;
+ break;
}
}
@@ -688,7 +704,7 @@ check_expr_struct(struct context *ctx,
tfield = &stype.struct_union;
sexpr = &expr->_struct;
while (tfield) {
- const struct struct_field *field = type_lookup_field(
+ const struct struct_field *field = type_get_field(
expr->result, tfield->field.name);
// TODO: Use more specific error location
expect(&aexpr->loc, field, "No field by this name exists for this type");
diff --git a/src/gen.c b/src/gen.c
@@ -321,6 +321,44 @@ address_index(struct gen_context *ctx,
}
static void
+address_field(struct gen_context *ctx,
+ const struct expression *expr,
+ struct qbe_value *out)
+{
+ const struct type *stype = expr->access._struct->result;
+ while (stype->storage == TYPE_STORAGE_POINTER) {
+ stype = stype->pointer.referent;
+ }
+
+ gen_temp(ctx, out, &qbe_long, "object.%d"); // XXX: ARCH
+ gen_expression(ctx, expr->access._struct, out);
+
+ stype = expr->access._struct->result;
+ if (stype->storage == TYPE_STORAGE_POINTER) {
+ // We get one dereference for free for aggregate types
+ stype = stype->pointer.referent;
+ }
+ while (stype->storage == TYPE_STORAGE_POINTER) {
+ qval_deref(out);
+ struct qbe_value deref;
+ gen_loadtemp(ctx, &deref, out,
+ qtype_for_type(ctx, stype->pointer.referent, false),
+ type_is_signed(stype->pointer.referent));
+ *out = deref;
+ stype = stype->pointer.referent;
+ }
+
+ const struct struct_field *field = expr->access.field;
+
+ struct qbe_value offset = {0};
+ constl(&offset, field->offset);
+ pushi(ctx->current, out, Q_ADD, out, &offset, NULL);
+ if (!type_is_aggregate(field->type)) {
+ qval_deref(out);
+ }
+}
+
+static void
address_object(struct gen_context *ctx,
const struct expression *expr,
struct qbe_value *out)
@@ -334,7 +372,8 @@ address_object(struct gen_context *ctx,
address_index(ctx, expr, out);
break;
case ACCESS_FIELD:
- assert(0); // TODO
+ address_field(ctx, expr, out);
+ break;
}
}
diff --git a/src/types.c b/src/types.c
@@ -13,7 +13,7 @@ type_dereference(const struct type *type)
}
const struct struct_field *
-type_lookup_field(const struct type *type, const char *name)
+type_get_field(const struct type *type, const char *name)
{
// TODO: We should consider lowering unions into structs with explicit
// offsets
diff --git a/tests/06-structs.ha b/tests/06-structs.ha
@@ -1,7 +1,20 @@
+fn rt::compile(src: str) int;
+
fn padding() void = {
assert(size(struct { x: i32, y: i32 }) == 8z);
assert(size(struct { x: i32, y: i64 }) == 16z);
assert(size(union { x: i8, y: i16, z: i32 }) == 4z);
+
+ const s = struct {
+ x: i8 = 10i8,
+ y: i16 = 20i16,
+ z: i32 = 30i32,
+ q: i64 = 40i32,
+ };
+ assert(&s.x: uintptr: size % 1z == 0z);
+ assert(&s.y: uintptr: size % 2z == 0z);
+ assert(&s.z: uintptr: size % 4z == 0z);
+ assert(&s.q: uintptr: size % 8z == 0z);
};
fn storage() void = {
@@ -11,8 +24,89 @@ fn storage() void = {
assert(ptr[1] == 20i32);
};
+fn assignment() void = {
+ let coords = struct { x: int = 20, y: int = 30 };
+ coords.x = 40;
+ coords.y = 50;
+ assert(coords.x == 40);
+ assert(coords.y == 50);
+ coords = struct { x: int = 60, y: int = 70 };
+ assert(coords.x == 60);
+ assert(coords.y == 70);
+};
+
+fn deref() void = {
+ let coords = struct { x: int = 20, y: int = 30 };
+ let a = &coords;
+ assert(a.x == 20);
+ assert(a.y == 30);
+ // TODO: Why does this fail?
+ //let b = &a;
+ //assert(b.x == 20);
+ //assert(b.y == 30);
+ //b.x = 42;
+ //b.y = 96;
+ //assert(coords.x == 42);
+ //assert(coords.y == 96);
+};
+
+fn nested() void = {
+ let s = struct {
+ x: int = 10,
+ y: struct {
+ z: int,
+ q: int,
+ a: struct { b: int, c: int },
+ } = struct {
+ z: int = 20,
+ q: int = 30,
+ a: struct { b: int, c: int } = struct {
+ b: int = 42, c: int = 24,
+ },
+ },
+ };
+ assert(s.x == 10);
+ assert(s.y.z == 20);
+ assert(s.y.q == 30);
+ assert(s.y.a.b == 42);
+ assert(s.y.a.c == 24);
+ assert(&s.y: uintptr == &s.y.z: uintptr);
+
+ s.x = 1337;
+ assert(s.x == 1337);
+
+ s.y.a = struct { b: int = 1337, c: int = 7331 };
+ assert(s.y.a.b == 1337);
+ assert(s.y.a.c == 7331);
+};
+
+fn invariants() void = {
+ const failures = [
+ // Assign field from non-assignable type:
+ "fn test() void = { let x: struct { y: int } = struct { y: int = 10u }; };",
+ // Dereference non-nullable pointer:
+ "fn test() void = { let x: nullable *struct { y: int } = null; x.y; };",
+ // Select field from non-struct object:
+ "fn test() void = { let x = 10; x.y; };",
+ // Unknown field:
+ "fn test() void = { let x: struct { y: int } = struct { y: int = 10 }; x.z; };",
+ ];
+ for (let i = 0z; i < len(failures); i += 1z) {
+ assert(rt::compile(failures[i]) != 0);
+ };
+};
+
export fn main() void = {
padding();
storage();
- // TODO: These tests need to be expanded when field selection works
+ assignment();
+ deref();
+ nested();
+ invariants();
+ // TODO:
+ // - Union tests
+ // - Named structs
+ // - Embedded structs
+ // - offset()
+ // - Autofilling (...) and its invariants
};