Skip to content

Enum Types #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ else
endif

LLVM_CXXFLAGS = $(shell $(LLVM_CONFIG) --cxxflags)
LLVM_CXXLFLAGS = $(shell $(LLVM_CONFIG) --ldflags --link-static --system-libs --libs X86AsmParser X86CodeGen Core Support BitReader AsmParser Analysis TransformUtils ScalarOpts Target)
LLVM_CXXLFLAGS = $(shell $(LLVM_CONFIG) --ldflags --link-static) -lLLVMAsmParser -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMGlobalISel -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMCodeGen -lLLVMTarget -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMX86Desc -lLLVMObject -lLLVMMCParser -lLLVMBitReader -lLLVMMCDisassembler -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMMC -lLLVMX86Utils -lLLVMCore -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle -lz -lm

LFLAGS =
DISABLED_WARNINGS = -Wno-writable-strings -Wno-switch -Wno-c11-extensions -Wno-c99-extensions
Expand Down Expand Up @@ -53,4 +53,4 @@ install:
clean:
rm -f $(TARGET) core.o llvm.o $(TEST_TARGET) $(TEST_LOG) $(TEST_MAIN)

.PHONY: all clean debug release tests
.PHONY: all clean debug release tests install
140 changes: 129 additions & 11 deletions src/checker.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ CheckerInfo *CheckerInfoForDecl(Package *pkg, Decl *decl) {
}

b32 declareSymbol(Package *pkg, Scope *scope, const char *name, Symbol **symbol, Decl *decl) {
Symbol *old = Lookup(scope, name);
Symbol *old = LookupNoRecurse(scope, name);
if (old) {
ReportError(pkg, RedefinitionError, decl->start, "Duplicate definition of symbol %s", name);
ReportNote(pkg, old->decl->start, "Previous definition of %s", name);
Expand Down Expand Up @@ -223,6 +223,10 @@ b32 IsFloat(Type *type) {
return type->kind == TypeKind_Float;
}

b32 isEnum(Type *type) {
return type->kind == TypeKind_Enum;
}

b32 isFunction(Type *type) {
return type->kind == TypeKind_Function;
}
Expand Down Expand Up @@ -267,10 +271,6 @@ b32 isSlice(Type *type) {
return type->kind == TypeKind_Slice;
}

b32 isEnum(Type *type) {
return type->kind == TypeKind_Enum;
}

b32 isEnumFlags(Type *type) {
return isEnum(type) && (type->Flags & TypeFlag_EnumFlags) != 0;
}
Expand Down Expand Up @@ -366,6 +366,10 @@ b32 canCoerce(Type *type, Type *target, CheckerContext *ctx) {
return type->Width <= target->Width;
}

if (isEnum(type) && IsInteger(target)) {
return canCoerce(type->Enum.backingType, target, ctx);
}

if (IsFloat(type)) {
// Coercion between float types requires the target is larger or equal in size.
return IsFloat(target) && type->Width <= target->Width;
Expand Down Expand Up @@ -473,6 +477,10 @@ Conversion conversion(Type *type, Type *target) {
return result;
}

if (isEnum(type) && IsInteger(target)) {
return ConversionKind_Enum;
}

if (IsFloat(type) && IsInteger(target)) {
result |= ConversionKind_FtoI & ConversionFlag_Float;
if (IsSigned(target)) result |= ConversionFlag_Signed;
Expand Down Expand Up @@ -668,6 +676,10 @@ Symbol *Lookup(Scope *scope, const char *name) {
return NULL;
}

Symbol *LookupNoRecurse(Scope *scope, const char *name) {
return MapGet(&scope->members, name);
}

Type *TypeFromCheckerInfo(CheckerInfo info) {
switch (info.kind) {
case CheckerInfoKind_BasicExpr:
Expand Down Expand Up @@ -876,6 +888,87 @@ Type *checkExprTypeVariadic(Expr *expr, CheckerContext *ctx, Package *pkg) {
return InvalidType;
}

Type *checkExprTypeEnum(Expr *expr, CheckerContext *ctx, Package *pkg) {
ASSERT(expr->kind == ExprKind_TypeEnum);
Expr_TypeEnum enm = expr->TypeEnum;

b32 hasMinMax = false;
u64 maxValue;

Type *backingType = NULL;
if (enm.explicitType) {
Type *type = checkExpr(enm.explicitType, ctx, pkg);
if (ctx->mode == ExprMode_Unresolved) goto unresolved;

if (ctx->mode != ExprMode_Invalid) {
expectType(pkg, type, ctx, enm.explicitType->start);
if (IsInteger(type)) {
// TODO: flags
maxValue = MaxValueForIntOrPointerType(type);
hasMinMax = true;
backingType = type;
} else {
ReportError(pkg, TypeMismatchError, enm.explicitType->start,
"Enum backing type must be an integer. Got: %s", DescribeType(type));
}
}
}

DynamicArray(EnumField) fields = NULL;
ArrayFit(fields, ArrayLen(enm.items));

u64 currentValue = 0;
u64 largestValue = 0;

For(enm.items) {
EnumItem item = enm.items[i];

if (item.init) {
CheckerContext itemCtx = {.scope = ctx->scope, .desiredType = backingType};
Type *type = checkExpr(item.init, &itemCtx, pkg);
if (itemCtx.mode == ExprMode_Unresolved) goto unresolved;

if (!IsConstant(&itemCtx)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a b32 expectType(Package *pkg, Type *type, CheckerContext *ctx, Position pos) do you think we should have a b32 expectConstant(Package *pkg, CheckerContext *ctx, Position pos)? This would mean we would get a more consistent error message. What do you think?

ReportError(pkg, TODOError, item.init->start,
"Enum cases must be a constant value");
continue;
}

if (backingType && !coerceType(item.init, ctx, &type, backingType, pkg)) {
continue;
}

u64 val = itemCtx.val.u64;
currentValue = val;
largestValue = MAX(largestValue, val);
}

if (hasMinMax && currentValue > maxValue) {
ReportError(pkg, IntOverflowError, item.init->start,
"Value for enum case exceeds the max value for the enum backing type (%s)",
DescribeType(backingType));
ReportNote(pkg, item.init->start,
"You can force the overflow by explicitly casting the value '%s(%s)",
DescribeType(backingType), DescribeExpr(item.init));
continue;
}

ArrayPush(fields, (EnumField){.name = item.name, .val = currentValue});
currentValue++;
}

backingType = backingType ? backingType : SmallestIntTypeForPositiveValue(largestValue);
Type *type = NewTypeEnum(TypeFlag_None, backingType, fields);
storeInfoBasicExpr(pkg, expr, type, ctx);
ctx->mode = ExprMode_Type;
return type;

unresolved:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the prior leak of fields with:

if (fields) ArrayFree(fields);

if (fields) ArrayFree(fields);
ctx->mode = ExprMode_Unresolved;
return NULL;
}

Type *checkExprTypeFunction(Expr *expr, CheckerContext *ctx, Package *pkg) {
ASSERT(expr->kind == ExprKind_TypeFunction);
Expr_TypeFunction func = expr->TypeFunction;
Expand All @@ -886,7 +979,7 @@ Type *checkExprTypeFunction(Expr *expr, CheckerContext *ctx, Package *pkg) {
DynamicArray(Type *) params = NULL;
ArrayFit(params, ArrayLen(func.params));

CheckerContext paramCtx = { pushScope(pkg, ctx->scope) };
CheckerContext paramCtx = { .scope = pushScope(pkg, ctx->scope) };
For (func.params) {
Type *type = checkExpr(func.params[i]->value, &paramCtx, pkg);
if (paramCtx.mode == ExprMode_Invalid) goto error;
Expand Down Expand Up @@ -1011,6 +1104,8 @@ Type *checkExprLitFunction(Expr *expr, CheckerContext *ctx, Package *pkg) {
bodyCtx.desiredType = NewTypeTupleFromFunctionResults(TypeFlag_None, type->Function);
}

CheckerContext bodyCtx = { .scope = bodyScope, .desiredType = tuple };

size_t len = ArrayLen(func.body->stmts);
for (size_t i = 0; i < len; i++) {
checkStmt(func.body->stmts[i], &bodyCtx, pkg);
Expand Down Expand Up @@ -1626,6 +1721,25 @@ Type *checkExprSelector(Expr *expr, CheckerContext *ctx, Package *pkg) {
break;
}

case TypeKind_Enum: {
if (ctx->mode == ExprMode_Type) {
EnumFieldLookupResult result = EnumFieldLookup(base->Enum, expr->Selector.name);
if (!result.field) {
ReportError(pkg, TODOError, expr->Selector.start, "Enum %s has no member named %s",
DescribeType(base), expr->Selector.name);
goto error;
}

ctx->val.u64 = result.field->val;
SelectorValue val = {.Enum.value = result.field->val};
storeInfoSelector(pkg, expr, base, SelectorKind_Enum, val, ctx);
type = base;
ctx->mode = ExprMode_Addressable;
ctx->flags |= CheckerContextFlag_Constant;
break;
}
}

TypeKind_File: {
Symbol *file = pkg->checkerInfo[expr->Selector.expr->id].Ident.symbol;
Package *import = (Package *) file->backendUserdata;
Expand Down Expand Up @@ -1805,6 +1919,9 @@ Type *checkExpr(Expr *expr, CheckerContext *ctx, Package *pkg) {
break;

case ExprKind_TypeEnum:
type = checkExprTypeEnum(expr, ctx, pkg);
break;

case ExprKind_TypeUnion:
case ExprKind_TypePolymorphic:
UNIMPLEMENTED();
Expand Down Expand Up @@ -1918,8 +2035,12 @@ void checkDeclConstant(Decl *decl, CheckerContext *ctx, Package *pkg) {
break;
}

case ExprKind_TypeUnion:
case ExprKind_TypeEnum: {
symbol->state = SymbolState_Resolving;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to do additional work to support circular enum references ... for example:

StmtKind :: enum (u8) {
    Invalid
    Start
    // ...
    End

    ExprStart :: Start + 100
    // ...
    ExprEnd

    DeclStart :: Start + 200
    // ...
    DeclEnd
}

will work fine with a naïve solution should work fine in this case because Start is processed before DeclStart. Do we limit Enums to forbid forward references? Saves us handling the circular checking case

symbol->kind = SymbolKind_Type;
} break;

case ExprKind_TypeUnion: {
UNIMPLEMENTED();
} break;

Expand Down Expand Up @@ -2445,7 +2566,7 @@ void checkStmtSwitch(Stmt *stmt, CheckerContext *ctx, Package *pkg) {
Type *switchType = BoolType;
if (stmt->Switch.match) {
switchType = checkExpr(stmt->Switch.match, &switchCtx, pkg);
if (!isNumericOrPointer(switchType) && !isBoolean(switchType)) {
if (!isNumericOrPointer(switchType) && !isBoolean(switchType) && !isEnum(switchType)) {
ReportError(pkg, CannotSwitchError, stmt->Switch.match->start,
"Cannot switch on value of type %s", DescribeType(switchType));
}
Expand Down Expand Up @@ -3270,6 +3391,3 @@ info = GetStmtInfo(&pkg, stmt)->Switch
#undef pkg
#endif




14 changes: 12 additions & 2 deletions src/checker.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum Enum_CheckerInfoKind {
STATIC_ASSERT(_StmtKind_End <= UINT8_MAX, "enum values overflow storage type");

typedef u8 Conversion;
#define ConversionKind_Mask 0x0F // Lower 3 bits denote the class
#define ConversionKind_Mask 0x0F // Lower 4 bits denote the class
#define ConversionKind_None 0
#define ConversionKind_Same 1
#define ConversionKind_FtoI 2
Expand All @@ -29,11 +29,13 @@ typedef u8 Conversion;
#define ConversionKind_ItoP 5
#define ConversionKind_Bool 6
#define ConversionKind_Tuple 7 // Information on the conversion can be found on their receiver.
#define ConversionKind_Enum 8
#define ConversionKind_Any 15

#define ConversionFlag_Extend 0x10 // 0001
#define ConversionFlag_Signed 0x20 // 0010
#define ConversionFlag_Float 0x40 // 0100 (Source type is a Float)
#define ConversionFlag_Enum 0x80 // 1000

typedef struct CheckerInfo_Constant CheckerInfo_Constant;
struct CheckerInfo_Constant {
Expand All @@ -60,14 +62,20 @@ struct CheckerInfo_Ident {
typedef u8 SelectorKind;
#define SelectorKind_None 0x0
#define SelectorKind_Struct 0x1
#define SelectorKind_Import 0x2
#define SelectorKind_Enum 0x2
#define SelectorKind_Import 0x3

typedef struct Selector_Struct Selector_Struct;
struct Selector_Struct {
u32 index; // The member index in the structure
u32 offset; // The member offset in the structure (in bits)
};

typedef struct Selector_Enum Selector_Enum;
struct Selector_Enum {
u64 value;
};

typedef struct Selector_Import Selector_Import;
struct Selector_Import {
Symbol *symbol;
Expand All @@ -77,6 +85,7 @@ struct Selector_Import {
typedef union SelectorValue SelectorValue;
union SelectorValue {
Selector_Struct Struct;
Selector_Enum Enum;
Selector_Import Import;
};

Expand Down Expand Up @@ -154,6 +163,7 @@ struct CheckerInfo {
extern "C" {
#endif
Symbol *Lookup(Scope *scope, const char *name);
Symbol *LookupNoRecurse(Scope *scope, const char *name);
Type *TypeFromCheckerInfo(CheckerInfo info);
b32 IsInteger(Type *type);
b32 IsSigned(Type *type);
Expand Down
37 changes: 31 additions & 6 deletions src/header.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ void cgenType(String *buffer, const char * name, Type *type) {

switch (type->kind) {
case TypeKind_Int:
if (type->Width == 1)
return cgenType(buffer, name, I8Type);

ArrayPrintf(*buffer, "Kai%s%d", type->Flags & TypeFlag_Signed ? "I" : "U", type->Width);
break;

Expand All @@ -19,10 +22,19 @@ void cgenType(String *buffer, const char * name, Type *type) {
break;

case TypeKind_Pointer: {
String pointee = NULL;
cgenType(&pointee, NULL, type->Pointer.pointeeType);
ArrayPrintf(*buffer, "%s*", pointee);
ArrayFree(pointee);
if (type == RawptrType) {
ArrayPrintf(*buffer, "KaiRawptr");
} else {
String pointee = NULL;
cgenType(&pointee, NULL, type->Pointer.pointeeType);
ArrayPrintf(*buffer, "%s*", pointee);
ArrayFree(pointee);
}
} break;

case TypeKind_Enum: {
cgenType(buffer, name, type->Enum.backingType);
return;
} break;

case TypeKind_Struct: {
Expand Down Expand Up @@ -82,6 +94,19 @@ void cgenDecl(HeaderContext *ctx, DynamicArray(Expr_Ident *) names, Type *type,
cgenFuncPrototype(&ctx->functions, it->name, type);
break;

case TypeKind_Enum: {
For(type->Enum.cases) {
struct EnumField field = type->Enum.cases[i];
ArrayPrintf(
ctx->primitiveDecls,
"#define %s_%s %lu\n",
type->Symbol->name,
field.name,
field.val
);
}
} break;

case TypeKind_Struct: {
if (type->Symbol->backendUserdata != HEAD_GENERATED) {
ArrayPrintf(ctx->primitiveDecls, "typedef ");
Expand All @@ -97,7 +122,7 @@ void cgenDecl(HeaderContext *ctx, DynamicArray(Expr_Ident *) names, Type *type,
cgenType(&ctx->complexDecls, it.name, it.type);
ArrayPrintf(ctx->complexDecls, ";\n");
}
ArrayPrintf(ctx->complexDecls, "};\n");
ArrayPrintf(ctx->complexDecls, "};\n\n");
} break;

default: {
Expand Down Expand Up @@ -127,7 +152,7 @@ void cgenStmt(HeaderContext *ctx, CheckerInfo *info, Stmt *stmt) {
}
}

const char *headerPreface = "#ifndef KAI_%s_H\n#define KAI_%s_H\n\n#ifndef KAI_TYPES\n#define KAI_TYPES\n#include <inttypes.h>\n\ntypedef int8_t KaiI8;\ntypedef int16_t KaiI16;\ntypedef int32_t KaiI32;\ntypedef int64_t KaiI64;\n\ntypedef uint8_t KaiU8;\ntypedef uint16_t KaiU16;\ntypedef uint32_t KaiU32;\ntypedef uint64_t KaiU64;\n\ntypedef float KaiF32;\ntypedef double KaiF64;\n#endif\n";
const char *headerPreface = "#ifndef KAI_%s_H\n#define KAI_%s_H\n\n#ifndef KAI_TYPES\n#define KAI_TYPES\n#include <inttypes.h>\n\ntypedef int8_t KaiI8;\ntypedef int16_t KaiI16;\ntypedef int32_t KaiI32;\ntypedef int64_t KaiI64;\n\ntypedef uint8_t KaiU8;\ntypedef uint16_t KaiU16;\ntypedef uint32_t KaiU32;\ntypedef uint64_t KaiU64;\n\ntypedef float KaiF32;\ntypedef double KaiF64;\n\ntypedef void * KaiRawptr;\n#endif\n";

void CodegenCHeader(Package *pkg) {
char buff[MAX_PATH];
Expand Down
Loading