From e9319b469294ef4b1284ff603b2ea7c884d8b2b4 Mon Sep 17 00:00:00 2001 From: Charlie Gordon Date: Wed, 10 Dec 2025 21:25:27 +0100 Subject: [PATCH] Analyser: allow enum type as array length in array definitions * arrays defined with an enum type as the length must be indexed using enum values of this type * enum values are scoped in array subscript expressions and initializers Implements #403 --- analyser/module_analyser_expr.c2 | 63 +++++++++++++++++++++----------- analyser/module_analyser_init.c2 | 13 +++++-- analyser/module_analyser_type.c2 | 62 ++++++++++++++++++++----------- ast/array_type.c2 | 30 ++++++++++++--- ast/string_type_pool.c2 | 2 +- common/ast_builder.c2 | 8 ++-- test/expr/array/enum_array.c2 | 38 +++++++++++++++++++ 7 files changed, 159 insertions(+), 57 deletions(-) create mode 100644 test/expr/array/enum_array.c2 diff --git a/analyser/module_analyser_expr.c2 b/analyser/module_analyser_expr.c2 index 560f82c41..d3dd5a63c 100644 --- a/analyser/module_analyser_expr.c2 +++ b/analyser/module_analyser_expr.c2 @@ -24,29 +24,29 @@ fn QualType Analyser.analyseExpr(Analyser* ma, Expr** e_ptr, bool need_rvalue, u assert(e_ptr); QualType result = ma.analyseExprInner(e_ptr, side); if (result.isInvalid()) return result; + (*e_ptr).setType(result); + if (need_rvalue) return ma.convertRvalue(e_ptr, result); + return result; +} +fn QualType Analyser.convertRvalue(Analyser* ma, Expr** e_ptr, QualType result) { Expr* e = *e_ptr; - e.setType(result); - - if (need_rvalue) { - if (e.isLValue()) { - QualType canon = result.getCanonicalType(); - assert(canon.isValid()); - - if (canon.isArray()) { - result = getPointerFromArray(ma.builder, canon); - ma.builder.insertImplicitCast(ArrayToPointerDecay, e_ptr, result); - } else { - // LValueToRValue conversion strips const of type - result.unsetConst(); - ma.builder.insertImplicitCast(LValueToRValue, e_ptr, result); - } - } else if (e.isNValue()) { - ma.error(e.getLoc(), "lvalue/rvalue required"); - return QualType_Invalid; + if (e.isLValue()) { + QualType canon = result.getCanonicalType(); + assert(canon.isValid()); + + if (canon.isArray()) { + result = getPointerFromArray(ma.builder, canon); + ma.builder.insertImplicitCast(ArrayToPointerDecay, e_ptr, result); + } else { + // LValueToRValue conversion strips const of type + result.unsetConst(); + ma.builder.insertImplicitCast(LValueToRValue, e_ptr, result); } + } else if (e.isNValue()) { + ma.error(e.getLoc(), "lvalue/rvalue required"); + return QualType_Invalid; } - return result; } @@ -468,15 +468,34 @@ fn QualType Analyser.analyseArraySubscriptExpr(Analyser* ma, Expr** e_ptr, u32 s return QualType_Invalid; } - QualType qidx = ma.analyseExpr(sub.getIndex2(), true, RHS); + // if base is array with enum index_type -> resolve index with enum scope + bool is_enum_index = false; + QualType index_type = QualType_Invalid; + QualType qidx; + QualType otype = orig.getType(); + otype = otype.getCanonicalType(); + if (otype.isArray()) { + const ArrayType* at = otype.getArrayType(); + is_enum_index = at.isEnumIndex(); + index_type = at.getIndexType(); + } + if (is_enum_index && ma.checkEnumArg(sub.getIndex2(), index_type)) { + qidx = index_type; + } else { + qidx = ma.analyseExpr(sub.getIndex2(), true, RHS); + } if (qidx.isInvalid()) return qidx; QualType canon = qidx.getCanonicalType(); + index = sub.getIndex(); if (!canon.isInteger() && !canon.isEnum()) { - ma.error(sub.getIndex().getLoc(), "array subscript is not an integer"); + ma.error(index.getLoc(), "array subscript is not an integer"); + return QualType_Invalid; + } + if (is_enum_index && index_type.getTypeOrNil() != canon.getTypeOrNil()) { + ma.error(index.getLoc(), "array subscript must have enum type '%s'", index_type.diagName()); return QualType_Invalid; } - index = sub.getIndex(); if (index.isCtv()) { QualType q2 = orig.getType(); ArrayType* at = q2.getArrayTypeOrNil(); diff --git a/analyser/module_analyser_init.c2 b/analyser/module_analyser_init.c2 index 66d9c9eb6..cb7f11280 100644 --- a/analyser/module_analyser_init.c2 +++ b/analyser/module_analyser_init.c2 @@ -161,10 +161,15 @@ fn bool Analyser.analyseInitListExpr(Analyser* ma, InitListExpr* ile, QualType e } // Note: this function should only be called from analyseInitListArray directly! -fn bool Analyser.analyseArrayDesignatedInit(Analyser* ma, Expr* e, QualType expectedType) { +fn bool Analyser.analyseArrayDesignatedInit(Analyser* ma, Expr* e, QualType expectedType, bool isEnumIndex, QualType indexType) { ArrayDesignatedInitExpr* ad = (ArrayDesignatedInitExpr*)e; + QualType qt; - QualType qt = ma.analyseExpr(ad.getDesignator2(), false, RHS); + if (isEnumIndex && ma.checkEnumArg(ad.getDesignator2(), indexType)) { + qt = indexType; + } else { + qt = ma.analyseExpr(ad.getDesignator2(), false, RHS); + } if (qt.isInvalid()) return false; Expr* de = ad.getDesignator(); @@ -173,6 +178,8 @@ fn bool Analyser.analyseArrayDesignatedInit(Analyser* ma, Expr* e, QualType expe return false; } + // TODO: compute index value, check if designator is integer and < array length if specified + Expr* val = ad.getInit(); if (val.isInitList()) { @@ -211,7 +218,7 @@ fn bool Analyser.analyseInitListArray(Analyser* ma, InitListExpr* ile, QualType continue; } if (value.isArrayDesignatedInit()) { - ok &= ma.analyseArrayDesignatedInit(value, et); + ok &= ma.analyseArrayDesignatedInit(value, et, at.isEnumIndex(), at.getIndexType()); have_designators = true; } else { ok &= ma.analyseInitExpr(&values[i], et, values[i].getLoc(), false, false); diff --git a/analyser/module_analyser_type.c2 b/analyser/module_analyser_type.c2 index 2907e2d17..48e06480f 100644 --- a/analyser/module_analyser_type.c2 +++ b/analyser/module_analyser_type.c2 @@ -230,36 +230,54 @@ fn QualType Analyser.analyseTypeRef(Analyser* ma, TypeRef* ref) { u32 num_arrays = ref.getNumArrays(); // Note: iterate in reverse, since outer array comes first for (u32 i=num_arrays; i>0; i--) { - Expr* sizeExpr = ref.getArray(i-1); // note: ImplicitCast could have been inserted + Expr** sizeExpr_p = ref.getArray2(i - 1); + Expr* sizeExpr = *sizeExpr_p; u32 size = 0; + bool is_enum = false; + QualType qt = QualType_Invalid; if (sizeExpr) { - QualType qt = ma.analyseExpr(ref.getArray2(i-1), true, RHS); + qt = ma.analyseExpr(sizeExpr_p, false, RHS); if (qt.isInvalid()) return qt; - sizeExpr = ref.getArray(i-1); // note: ImplicitCast could have been inserted - - // TODO canonical? - if (!qt.isInteger()) { - ma.error(ref.getLoc(), "array size has non-integer type '%s'", qt.diagName()); - return QualType_Invalid; - } - - if (!sizeExpr.isCtv()) { - ma.errorRange(sizeExpr.getLoc(), sizeExpr.getRange(), "array size is not a compile-time value"); - return QualType_Invalid; - } - - Value value = ast.evalExpr(sizeExpr); - if (value.isNegative()) { - ma.errorRange(sizeExpr.getLoc(), sizeExpr.getRange(), "array size has negative value '%s'", value.str()); - return QualType_Invalid; + sizeExpr = *sizeExpr_p; + if (sizeExpr.isNValue()) { + const EnumType* et = qt.getEnumTypeOrNil(); + if (!et) { + ma.error(ref.getLoc(), "array size must be an integer or an enum type ('%s')", qt.diagName()); + return QualType_Invalid; + } + const EnumTypeDecl* etd = et.getDecl(); + is_enum = true; + size = etd.getNumConstants(); + } else { + qt = ma.convertRvalue(sizeExpr_p, qt); + if (qt.isInvalid()) return qt; + + sizeExpr = *sizeExpr_p; // note: ImplicitCast could have been inserted + + // TODO canonical? + if (!qt.isInteger()) { + ma.error(ref.getLoc(), "array size has non-integer type '%s'", qt.diagName()); + return QualType_Invalid; + } + + if (!sizeExpr.isCtv()) { + ma.errorRange(sizeExpr.getLoc(), sizeExpr.getRange(), "array size is not a compile-time value"); + return QualType_Invalid; + } + + Value value = ast.evalExpr(sizeExpr); + if (value.isNegative()) { + ma.errorRange(sizeExpr.getLoc(), sizeExpr.getRange(), "array size has negative value '%s'", value.str()); + return QualType_Invalid; + } + size = value.as_u32(); } - size = value.as_u32(); } if (resolved.isVoid()) { ma.error(ref.getLoc(), "array element has invalid type 'void'"); return QualType_Invalid; } - resolved = ma.builder.actOnArrayType(resolved, sizeExpr != nil, size); + resolved = ma.builder.actOnArrayType(resolved, sizeExpr != nil, size, is_enum, qt); } if (ref.isIncrArray()) { resolved = ma.builder.actOnIncrementalArrayType(resolved); @@ -296,7 +314,7 @@ fn QualType Analyser.analyseIncrTypeRef(Analyser* ma, TypeRef* ref, u32 size) { return QualType_Invalid; } // always insert a one-dimensional array with size entries - resolved = ma.builder.actOnArrayType(resolved, true, size); + resolved = ma.builder.actOnArrayType(resolved, true, size, false, QualType_Invalid); if (ref.isUser()) ref.setDest(base.getIndex()); return resolved; diff --git a/ast/array_type.c2 b/ast/array_type.c2 index 2a7b8d9dc..f581c1597 100644 --- a/ast/array_type.c2 +++ b/ast/array_type.c2 @@ -22,6 +22,7 @@ type ArrayTypeBits struct { u32 : NumTypeBits; u32 has_size : 1; // if it has an sizeExpr u32 is_incremental : 1; + u32 is_enum_index : 1; } public type ArrayType struct @(opaque) { @@ -29,21 +30,29 @@ public type ArrayType struct @(opaque) { QualType elem; u32 size; // set during analysis, number of elements // Note: 4 bytes padding here on 64-bit systems + QualType[0] index_type; } public fn ArrayType* ArrayType.create(ast_context.Context* c, - QualType elem, - bool has_size, - u32 size) + QualType elem, + bool has_size, + u32 length, + bool is_enum_index, + QualType index_type) { - ArrayType* t = c.alloc(sizeof(ArrayType)); + u32 size = sizeof(ArrayType) + is_enum_index * sizeof(QualType); + ArrayType* t = c.alloc(size); t.base.init(TypeKind.Array); t.base.arrayTypeBits.is_incremental = false; t.base.arrayTypeBits.has_size = has_size; t.elem = elem; - t.size = size; + t.size = length; + if (is_enum_index) { + t.base.arrayTypeBits.is_enum_index = true; + t.index_type[0] = index_type; + } #if AstStatistics - Stats.addType(TypeKind.Array, sizeof(ArrayType)); + Stats.addType(TypeKind.Array, size); #endif return t; } @@ -84,6 +93,15 @@ public fn void ArrayType.setSize(ArrayType* t, u32 size) { t.size = size; } +public fn bool ArrayType.isEnumIndex(const ArrayType* t) { + return t.base.arrayTypeBits.is_enum_index; +} + +public fn QualType ArrayType.getIndexType(const ArrayType* t) { + if (t.base.arrayTypeBits.is_enum_index) return t.index_type[0]; + return QualType_Invalid; +} + fn void ArrayType.printPreName(const ArrayType* t, string_buffer.Buf* out) { t.elem.print(out); } diff --git a/ast/string_type_pool.c2 b/ast/string_type_pool.c2 index fd179d211..4ab2d6dc6 100644 --- a/ast/string_type_pool.c2 +++ b/ast/string_type_pool.c2 @@ -78,7 +78,7 @@ fn QualType StringTypePool.get(StringTypePool* p, u32 len) { } if (p.count == p.capacity) p.resize(p.capacity * 2); - Type* t = (Type*)ArrayType.create(p.context, builtins[BuiltinKind.Char], true, len); + Type* t = (Type*)ArrayType.create(p.context, builtins[BuiltinKind.Char], true, len, false, QualType_Invalid); u32 idx = p.count; p.slots[idx].len = len; p.slots[idx].type_ = t; diff --git a/common/ast_builder.c2 b/common/ast_builder.c2 index bbc099935..b4319561e 100644 --- a/common/ast_builder.c2 +++ b/common/ast_builder.c2 @@ -685,8 +685,10 @@ public fn QualType Builder.actOnPointerType(Builder*, QualType inner) { public fn QualType Builder.actOnArrayType(Builder* b, QualType elem, bool has_size, - u32 size) { - ArrayType* t = ArrayType.create(b.context, elem, has_size, size); + u32 size, + bool is_enum_index, + QualType index_type) { + ArrayType* t = ArrayType.create(b.context, elem, has_size, size, is_enum_index, index_type); QualType a = QualType.create((Type*)t); // canonical can be either self or a pointer to elem's canonical type @@ -694,7 +696,7 @@ public fn QualType Builder.actOnArrayType(Builder* b, if (elem.getTypeOrNil() == canon.getTypeOrNil()) { canon = a; } else { - ArrayType* t2 = ArrayType.create(b.context, canon, has_size, size); + ArrayType* t2 = ArrayType.create(b.context, canon, has_size, size, is_enum_index, index_type); // Note: keep same quals here, even if canonical type may be a PointerType! canon = QualType.create((Type*)t2); } diff --git a/test/expr/array/enum_array.c2 b/test/expr/array/enum_array.c2 new file mode 100644 index 000000000..8dd5c79f4 --- /dev/null +++ b/test/expr/array/enum_array.c2 @@ -0,0 +1,38 @@ +module test; + +import stdio local; + +type Enum enum u8 { No, Yes, Maybe } + +u32[Enum] a = { + [No]= 0, + [Yes]= 1, + [Maybe]= 2, +} + +static_assert(elemsof(a), elemsof(Enum)); + +public fn i32 main() { + printf("%d\n", a[No]); + printf("%d\n", a[Yes]); + printf("%d\n", a[Maybe]); + a[No] = 3; + a[Yes] = 4; + a[Maybe] = 5; + printf("%d\n", a[No]); + printf("%d\n", a[Yes]); + printf("%d\n", a[Maybe]); + printf("%d\n", a[0 ? Enum.Yes : Enum.No]); + Enum e = Yes; + printf("%d\n", a[e]); + printf("%d\n", a[Enum.max]); + i32 i = 1; + i32[] aa = { + printf("%d\n", a[No + 1]), // @error{use of undeclared identifier 'No'} + printf("%d\n", a[1 ? Yes : // @error{use of undeclared identifier 'Yes'} + No]), // @error{use of undeclared identifier 'No'} + printf("%d\n", a[1]), // @error{array subscript must have enum type 'test.Enum'} + printf("%d\n", a[i]), // @error{array subscript must have enum type 'test.Enum'} + } + return 0; +}