This commit is contained in:
2026-06-23 17:22:41 +02:00
parent f16f352d1e
commit 2f68fc966a
16 changed files with 809 additions and 38 deletions
+24 -1
View File
@@ -125,7 +125,14 @@
floats follow IEEE (`fadd`/`fsub`/`fmul`/`fdiv`, no trap) floats follow IEEE (`fadd`/`fsub`/`fmul`/`fdiv`, no trap)
- constant folding (global initializers) covers `-`, `*`, `/` alongside `+` - constant folding (global initializers) covers `-`, `*`, `/` alongside `+`
7. enums (native and c interop) (see below) 7. enums (native and c interop) (implemented; see below)
- native enums are nominal value types with integer runtime representations
- unbacked enums are non-empty, dense, zero-based, and use the smallest fitting unsigned backing
- explicitly backed enums require an integer type and strictly increasing literal values
- enum members support `Type.member`, `package.Type.member`, and contextual `.member`
- enum values support storage, calls/returns, and same-type equality/inequality
- explicitly backed native enums use their backing ABI in `c_func` signatures and variadic promotion
- imported C enum types alias libclang's target-selected integer backing and enumerators import as package constants
8. distinct types (implemented; see below) 8. distinct types (implemented; see below)
- nominal declarations preserve identity across packages and reuse the backing runtime representation - nominal declarations preserve identity across packages and reuse the backing runtime representation
@@ -255,3 +262,19 @@ Nat :: enum(u8) {
dog_tag1 Animal :: Animal.dog dog_tag1 Animal :: Animal.dog
dog_tag2 Animal :: .dog # type inferred dog_tag2 Animal :: .dog # type inferred
``` ```
Unbacked enums cannot assign explicit values. Backed enum values must be decimal integer
literals, fit the backing type, and increase strictly; gaps are allowed.
Native enum types remain distinct from integers and from other enum types. They support
`==` and `!=`, but not arithmetic, ordering, casts, or backing-value extraction.
C enums follow C/Zig import semantics rather than native enum semantics:
```
native :: import "native.h"
value native.Imported_Enum :: native.IMPORTED_ENUM_VALUE
```
The imported enum type is an alias of its target-selected C integer backing, and imported
enumerators are package-level constants.
+1
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array, Array,
None, None,
Name, Name,
Enum_Literal,
Address, Address,
Deref, Deref,
Index, Index,
+145 -6
View File
@@ -636,7 +636,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
append(&stack, expr.left) append(&stack, expr.left)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right) append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name: case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name, .Enum_Literal:
} }
} }
} }
@@ -879,6 +879,28 @@ validate_type_nodes :: proc(checker: ^Checker) {
symbol_text(checker, symbol.Id(item.name)), symbol_text(checker, symbol.Id(item.name)),
) )
} }
if item.kind == .Enum {
if !item.declared || !types.is_concrete_integer(item.child) {
source.addf(
checker.diagnostics,
source.Span{},
"enum type '%s' requires a concrete integer backing type",
symbol_text(checker, symbol.Id(item.name)),
)
} else {
for member in types.enum_members_for(&checker.module.types, id) {
if !fits_integer_type(member.value, item.child, checker.target) {
source.addf(
checker.diagnostics,
source.Span{},
"enum value %d does not fit in %s",
member.value,
types.name(item.child),
)
}
}
}
}
if item.has_sentinel { if item.has_sentinel {
value := i128(item.sentinel) value := i128(item.sentinel)
if types.is_signed(item.child, checker.target) { if types.is_signed(item.child, checker.target) {
@@ -1101,6 +1123,8 @@ infer_compound_expr :: proc(
return types.array(store, element, u64(len(expr.args)), false) return types.array(store, element, u64(len(expr.args)), false)
case .None: case .None:
return types.INVALID return types.INVALID
case .Enum_Literal:
return types.INVALID
case .Address: case .Address:
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
return types.pointer(store, child, false, false) return types.pointer(store, child, false, false)
@@ -1126,6 +1150,10 @@ infer_compound_expr :: proc(
preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR preserve := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
return types.slice(store, item.child, item.mutable, preserve, item.sentinel) return types.slice(store, item.child, item.mutable, preserve, item.sentinel)
case .Field: case .Field:
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file); enum_ok {
_, member_ok := find_enum_member(checker, enum_type, expr.name)
return enum_type if member_ok else types.INVALID
}
value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
field_name := symbol_text(checker, expr.name) field_name := symbol_text(checker, expr.name)
item, has_item := types.container(value, store) item, has_item := types.container(value, store)
@@ -1220,7 +1248,7 @@ infer_expr :: proc(
last = types.F64 last = types.F64
_ = pop(&stack) _ = pop(&stack)
case .String, .Array, .None, .Address, .Deref, .Index, .Slice, case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Enum_Literal,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded) last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
_ = pop(&stack) _ = pop(&stack)
@@ -1250,6 +1278,14 @@ infer_expr :: proc(
} }
} }
} }
if !types.is_valid(last) {
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
_, member_ok := find_enum_member(checker, enum_type, expr.name)
if member_ok {
last = enum_type
}
}
}
if !types.is_valid(last) { if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file) target_pkg, available := expr_package(checker, expr, pkg, file)
if available { if available {
@@ -1867,7 +1903,7 @@ promote_c_vararg_expr :: proc(checker: ^Checker, expr_id: hir.Expr_Id, span: sou
) )
return invalid_hir_expr(checker, span, id, types.C_INT) return invalid_hir_expr(checker, span, id, types.C_INT)
} }
promoted := types.c_vararg_promotion(actual, checker.target) promoted := types.c_vararg_promotion(actual, checker.target, &checker.module.types)
if types.equal(actual, promoted) { if types.equal(actual, promoted) {
return expr_id return expr_id
} }
@@ -2055,6 +2091,75 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb
return 0, {}, false return 0, {}, false
} }
find_enum_member :: proc(checker: ^Checker, enum_type: types.Type, name: symbol.Id) -> (types.Enum_Member, bool) {
for member in types.enum_members_for(&checker.module.types, enum_type) {
if member.name == u32(name) {
return member, true
}
}
return {}, false
}
enum_type_from_name_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (types.Type, bool) {
if !symbol.is_valid(expr.qualifier) ||
find_import(checker, file, expr.qualifier) != ast.INVALID_IMPORT {
return types.INVALID, false
}
enum_type := types.find_named(&checker.module.types, u32(pkg), u32(expr.qualifier))
return enum_type, types.is_enum(enum_type, &checker.module.types)
}
enum_type_from_field_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
pkg: ast.Package_Id,
file: ast.File_Id,
mark_used := false,
) -> (types.Type, bool) {
if expr.left == ast.INVALID_EXPR || int(expr.left) >= len(checker.ast_module.exprs) {
return types.INVALID, false
}
base := checker.ast_module.exprs[expr.left]
if base.kind != .Name || !symbol.is_valid(base.qualifier) {
return types.INVALID, false
}
target_pkg, available := expr_package(checker, base, pkg, file, mark_used)
if !available {
return types.INVALID, false
}
enum_type := types.find_named(&checker.module.types, u32(target_pkg), u32(base.name))
return enum_type, types.is_enum(enum_type, &checker.module.types)
}
enum_member_hir :: proc(
checker: ^Checker,
enum_type: types.Type,
name: symbol.Id,
span: source.Span,
) -> hir.Expr_Id {
member, ok := find_enum_member(checker, enum_type, name)
if !ok {
id := source.addf(checker.diagnostics, span, "unknown enum member '%s'", symbol_text(checker, name))
return invalid_hir_expr(checker, span, id, enum_type)
}
value := i64(member.value) if member.value < 0 else transmute(i64)u64(member.value)
return add_hir_expr(checker, hir.Expr{
kind=.Integer,
span=span,
type=enum_type,
integer=value,
target=hir.INVALID_REF,
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_function_value :: proc( build_function_value :: proc(
checker: ^Checker, checker: ^Checker,
template: ast.Function_Id, template: ast.Function_Id,
@@ -2213,6 +2318,17 @@ build_compound_expr :: proc(
kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF, kind=.None, span=expr.span, type=expected, target=hir.INVALID_REF,
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
case .Enum_Literal:
if !types.is_enum(expected, store) {
id := source.addf(
checker.diagnostics,
expr.span,
"'.%s' requires an enum context",
symbol_text(checker, expr.name),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
return enum_member_hir(checker, expected, expr.name, expr.span)
case .Address: case .Address:
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
if !hir_is_location(checker, value) { if !hir_is_location(checker, value) {
@@ -2278,6 +2394,9 @@ build_compound_expr :: proc(
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
case .Field: case .Field:
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, pkg, file, true); enum_ok {
return enum_member_hir(checker, enum_type, expr.name, expr.span)
}
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
base_type := checker.module.exprs[base].type base_type := checker.module.exprs[base].type
field_name := symbol_text(checker, expr.name) field_name := symbol_text(checker, expr.name)
@@ -2417,7 +2536,15 @@ build_compound_expr :: proc(
left_const := eval_constant(checker, expr.left) left_const := eval_constant(checker, expr.left)
right_const := eval_constant(checker, expr.right) right_const := eval_constant(checker, expr.right)
left, right: hir.Expr_Id left, right: hir.Expr_Id
if right_const.kind == .Value && left_const.kind != .Value { left_expr := checker.ast_module.exprs[expr.left]
right_expr := checker.ast_module.exprs[expr.right]
if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else if right_const.kind == .Value && left_const.kind != .Value {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
hint := checker.module.exprs[left].type hint := checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file) right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
@@ -2435,7 +2562,13 @@ build_compound_expr :: proc(
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL) return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
} }
operand_type := types.INVALID operand_type := types.INVALID
if types.is_bool(left_type) && types.is_bool(right_type) { if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
id := source.add(checker.diagnostics, expr.span, "enum values only support '==' and '!=' with the same enum type")
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
}
operand_type = left_type
} else if types.is_bool(left_type) && types.is_bool(right_type) {
if expr.kind != .Eq && expr.kind != .Ne { if expr.kind != .Eq && expr.kind != .Ne {
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='") id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
return invalid_hir_expr(checker, expr.span, id, types.BOOL) return invalid_hir_expr(checker, expr.span, id, types.BOOL)
@@ -2617,7 +2750,8 @@ build_expr :: proc(
switch expr.kind { switch expr.kind {
case .String, .Array, .None, .Address, .Deref, .Index, .Slice, case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed, .Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range: .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
.Enum_Literal:
last = build_compound_expr( last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file, checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
) )
@@ -2674,6 +2808,11 @@ build_expr :: proc(
}) })
} }
} }
if last == hir.INVALID_EXPR {
if enum_type, enum_ok := enum_type_from_name_expr(checker, expr, pkg, file); enum_ok {
last = enum_member_hir(checker, enum_type, expr.name, expr.span)
}
}
if last == hir.INVALID_EXPR { if last == hir.INVALID_EXPR {
target_pkg, available := expr_package(checker, expr, pkg, file, true) target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available { if !available {
+68 -2
View File
@@ -81,6 +81,9 @@ Api :: struct {
get_type_spelling: proc "c"(CXType) -> CXString, get_type_spelling: proc "c"(CXType) -> CXString,
get_typedef_underlying_type: proc "c"(CXCursor) -> CXType, get_typedef_underlying_type: proc "c"(CXCursor) -> CXType,
get_type_declaration: proc "c"(CXType) -> CXCursor, get_type_declaration: proc "c"(CXType) -> CXCursor,
get_enum_decl_integer_type: proc "c"(CXCursor) -> CXType,
get_enum_constant_value: proc "c"(CXCursor) -> i64,
get_enum_constant_unsigned: proc "c"(CXCursor) -> u64,
get_canonical_type: proc "c"(CXType) -> CXType, get_canonical_type: proc "c"(CXType) -> CXType,
get_pointee_type: proc "c"(CXType) -> CXType, get_pointee_type: proc "c"(CXType) -> CXType,
get_array_element_type: proc "c"(CXType) -> CXType, get_array_element_type: proc "c"(CXType) -> CXType,
@@ -119,6 +122,7 @@ CXCursor_VarDecl :: i32(9)
CXCursor_TypedefDecl :: i32(20) CXCursor_TypedefDecl :: i32(20)
CXCursor_MacroDefinition :: i32(501) CXCursor_MacroDefinition :: i32(501)
CXCursor_FieldDecl :: i32(6) CXCursor_FieldDecl :: i32(6)
CXCursor_EnumConstantDecl :: i32(7)
CXLinkage_External :: i32(4) CXLinkage_External :: i32(4)
CXTLS_None :: i32(0) CXTLS_None :: i32(0)
@@ -201,6 +205,9 @@ load_api_from :: proc(path: string) -> (Api, bool) {
load_proc(&api, "clang_getTypeSpelling", &api.get_type_spelling) && load_proc(&api, "clang_getTypeSpelling", &api.get_type_spelling) &&
load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) && load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) &&
load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) && load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) &&
load_proc(&api, "clang_getEnumDeclIntegerType", &api.get_enum_decl_integer_type) &&
load_proc(&api, "clang_getEnumConstantDeclValue", &api.get_enum_constant_value) &&
load_proc(&api, "clang_getEnumConstantDeclUnsignedValue", &api.get_enum_constant_unsigned) &&
load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) && load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) &&
load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) && load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) &&
load_proc(&api, "clang_getArrayElementType", &api.get_array_element_type) && load_proc(&api, "clang_getArrayElementType", &api.get_array_element_type) &&
@@ -468,6 +475,10 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
record := add_record(ctx, declaration, preferred_record_name) record := add_record(ctx, declaration, preferred_record_name)
populate_record(ctx, record, declaration) populate_record(ctx, record, declaration)
return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
case CXType_Enum:
declaration := ctx.api.get_type_declaration(value)
backing := ctx.api.get_enum_decl_integer_type(declaration)
return translate_type(ctx, backing, preferred_record_name, depth+1)
case CXType_FunctionProto: case CXType_FunctionProto:
result := translate_type(ctx, ctx.api.get_result_type(value), "", depth+1) result := translate_type(ctx, ctx.api.get_result_type(value), "", depth+1)
if result == INVALID_TYPE { if result == INVALID_TYPE {
@@ -508,7 +519,7 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
return INVALID_TYPE return INVALID_TYPE
} }
return translate_type(ctx, canonical, preferred_record_name, depth+1) return translate_type(ctx, canonical, preferred_record_name, depth+1)
case CXType_Enum, CXType_FunctionNoProto, case CXType_FunctionNoProto,
CXType_IncompleteArray, CXType_VariableArray, CXType_DependentSizedArray: CXType_IncompleteArray, CXType_VariableArray, CXType_DependentSizedArray:
return INVALID_TYPE return INVALID_TYPE
} }
@@ -551,6 +562,49 @@ add_alias :: proc(ctx: ^Context, name: string, value: Type_Id, reason := "") {
}) })
} }
enum_backing_unsigned :: proc(value: CXType) -> bool {
switch value.kind {
case CXType_Char_U, CXType_UChar, CXType_UShort, CXType_UInt, CXType_ULong, CXType_ULongLong:
return true
}
return false
}
Enum_Constant_Context :: struct {
ctx: ^Context,
backing: Type_Id,
unsigned: bool,
}
visit_enum_constant :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
context = runtime.default_context()
enum_ctx := (^Enum_Constant_Context)(client_data)
ctx := enum_ctx.ctx
if ctx.api.get_cursor_kind(cursor) != CXCursor_EnumConstantDecl {
return CXChildVisit_Continue
}
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator)
defer delete(name, ctx.allocator)
if len(name) == 0 {
return CXChildVisit_Continue
}
value := Macro_Value{kind=.Integer, type=enum_ctx.backing}
if enum_ctx.unsigned {
value.integer = ctx.api.get_enum_constant_unsigned(cursor)
} else {
signed := ctx.api.get_enum_constant_value(cursor)
value.negative = signed < 0
value.integer = u64(-i128(signed)) if signed < 0 else u64(signed)
}
append(&ctx.result.macros, Macro_Constant{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
type=enum_ctx.backing,
value=value,
reason=fmt.aprintf("", allocator=ctx.allocator),
})
return CXChildVisit_Continue
}
is_macro_identifier :: proc(value: string) -> bool { is_macro_identifier :: proc(value: string) -> bool {
if len(value) == 0 { if len(value) == 0 {
return false return false
@@ -1367,7 +1421,19 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
add_alias(ctx, name, value) add_alias(ctx, name, value)
} }
case CXCursor_EnumDecl: case CXCursor_EnumDecl:
add_unsupported(ctx, name, "C enums are not supported") backing_c := ctx.api.get_enum_decl_integer_type(cursor)
backing := translate_type(ctx, backing_c)
if backing == INVALID_TYPE {
add_unsupported(ctx, name, "C enum backing type is not supported")
break
}
add_alias(ctx, name, backing)
enum_ctx := Enum_Constant_Context{
ctx=ctx,
backing=backing,
unsigned=enum_backing_unsigned(backing_c),
}
_ = ctx.api.visit_children(cursor, visit_enum_constant, &enum_ctx)
case CXCursor_VarDecl: case CXCursor_VarDecl:
if len(name) == 0 { if len(name) == 0 {
return CXChildVisit_Continue return CXChildVisit_Continue
+1
View File
@@ -18,6 +18,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "c_func": return .Keyword_C_Func case "c_func": return .Keyword_C_Func
case "struct": return .Keyword_Struct case "struct": return .Keyword_Struct
case "c_struct": return .Keyword_C_Struct case "c_struct": return .Keyword_C_Struct
case "enum": return .Keyword_Enum
case "distinct": return .Keyword_Distinct case "distinct": return .Keyword_Distinct
case "import": return .Keyword_Import case "import": return .Keyword_Import
case "return": return .Keyword_Return case "return": return .Keyword_Return
+21 -15
View File
@@ -189,11 +189,16 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri
return llvm_type(function.result, store) return llvm_type(function.result, store)
} }
c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string { c_abi_extension :: proc(value: types.Type, store: ^types.Store) -> string {
if !types.is_concrete_integer(value) { resolved := types.runtime_representation(value, store)
if !types.is_concrete_integer(resolved) {
return "" return ""
} }
switch target.c_integer_extension(selected, types.bits(value, selected), types.is_signed(value, selected)) { switch target.c_integer_extension(
store.selected,
types.bits(resolved, store.selected),
types.is_signed(resolved, store.selected),
) {
case .Sign: return "signext" case .Sign: return "signext"
case .Zero: return "zeroext" case .Zero: return "zeroext"
case .None: return "" case .None: return ""
@@ -203,7 +208,7 @@ c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string {
emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, store: ^types.Store) { emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, store: ^types.Store) {
if function.calling_convention == .C { if function.calling_convention == .C {
extension := c_abi_extension(function.result, store.selected) extension := c_abi_extension(function.result, store)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(builder, "%s ", extension) fmt.sbprintf(builder, "%s ", extension)
} }
@@ -423,7 +428,7 @@ emit_call_args :: proc(
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
fmt.sbprintf(builder, "%s ", llvm_type(arg_type, store)) fmt.sbprintf(builder, "%s ", llvm_type(arg_type, store))
if c_abi && index < len(param_types) { if c_abi && index < len(param_types) {
extension := c_abi_extension(arg_type, store.selected) extension := c_abi_extension(arg_type, store)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(builder, "%s ", extension) fmt.sbprintf(builder, "%s ", extension)
} }
@@ -1200,11 +1205,12 @@ emit_instruction_stream :: proc(
} }
from_type := instructions[instruction.a].type from_type := instructions[instruction.a].type
if types.equal(from_type, instruction.type) || if types.equal(from_type, instruction.type) ||
!types.equal(types.c_vararg_promotion(from_type, emitter.module.target), instruction.type) { !types.equal(types.c_vararg_promotion(from_type, emitter.module.target, &emitter.module.types), instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand")
continue continue
} }
if types.bits(from_type, emitter.module.target) == types.bits(instruction.type, emitter.module.target) { from_repr := types.runtime_representation(from_type, &emitter.module.types)
if types.bits(from_repr, emitter.module.target) == types.bits(instruction.type, emitter.module.target) {
type_name := llvm_type(instruction.type, &emitter.module.types) type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name) fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
@@ -1213,8 +1219,8 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "\n") strings.write_string(&emitter.builder, "\n")
continue continue
} }
operation := "fpext" if types.is_float(from_type, emitter.module.target) else operation := "fpext" if types.is_float(from_repr, emitter.module.target) else
("sext" if types.is_signed(from_type, emitter.module.target) else "zext") ("sext" if types.is_signed(from_repr, emitter.module.target) else "zext")
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
@@ -1367,7 +1373,7 @@ emit_instruction_stream :: proc(
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
if index >= len(param_fields) && if index >= len(param_fields) &&
(!types.is_c_vararg_type(expected, &emitter.module.types) || (!types.is_c_vararg_type(expected, &emitter.module.types) ||
!types.equal(types.c_vararg_promotion(expected, emitter.module.target), expected)) { !types.equal(types.c_vararg_promotion(expected, emitter.module.target, &emitter.module.types), expected)) {
valid_args = false valid_args = false
break break
} }
@@ -1409,7 +1415,7 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "fastcc ") strings.write_string(&emitter.builder, "fastcc ")
} }
if function_item.c_abi { if function_item.c_abi {
extension := c_abi_extension(function_item.child, emitter.module.target) extension := c_abi_extension(function_item.child, &emitter.module.types)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension) fmt.sbprintf(&emitter.builder, "%s ", extension)
} }
@@ -1455,7 +1461,7 @@ emit_instruction_stream :: proc(
} else { } else {
fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types))
if function_item.c_abi && fixed { if function_item.c_abi && fixed {
extension := c_abi_extension(arg_type, emitter.module.target) extension := c_abi_extension(arg_type, &emitter.module.types)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension) fmt.sbprintf(&emitter.builder, "%s ", extension)
} }
@@ -1490,7 +1496,7 @@ emit_instruction_stream :: proc(
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
if index >= len(target.param_types) && if index >= len(target.param_types) &&
(!types.is_c_vararg_type(expected, &emitter.module.types) || (!types.is_c_vararg_type(expected, &emitter.module.types) ||
!types.equal(types.c_vararg_promotion(expected, emitter.module.target), expected)) { !types.equal(types.c_vararg_promotion(expected, emitter.module.target, &emitter.module.types), expected)) {
valid_args = false valid_args = false
break break
} }
@@ -1572,7 +1578,7 @@ emit_instruction_stream :: proc(
} else { } else {
fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types))
if target.calling_convention == .C && fixed { if target.calling_convention == .C && fixed {
extension := c_abi_extension(arg_type, emitter.module.target) extension := c_abi_extension(arg_type, &emitter.module.types)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension) fmt.sbprintf(&emitter.builder, "%s ", extension)
} }
@@ -1898,7 +1904,7 @@ emit_functions :: proc(emitter: ^Emitter) {
type_name := c_abi_param_type(param_type, &emitter.module.types) if function.calling_convention == .C else llvm_type(param_type, &emitter.module.types) type_name := c_abi_param_type(param_type, &emitter.module.types) if function.calling_convention == .C else llvm_type(param_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "%s", type_name) fmt.sbprintf(&emitter.builder, "%s", type_name)
if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) { if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) {
extension := c_abi_extension(param_type, emitter.module.target) extension := c_abi_extension(param_type, &emitter.module.types)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, " %s", extension) fmt.sbprintf(&emitter.builder, " %s", extension)
} }
+5
View File
@@ -1325,6 +1325,11 @@ canonical_type :: proc(
module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting) module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting)
return value return value
} }
if item.kind == .Enum {
mapping[index] = value
module.type_store.nodes[index].child = canonical_type(module, item.child, mapping, visiting)
return value
}
if item.kind == .Struct || item.kind == .Union { if item.kind == .Struct || item.kind == .Union {
mapping[index] = value mapping[index] = value
fields := types.fields_for(&module.type_store, value) fields := types.fields_for(&module.type_store, value)
+131
View File
@@ -588,6 +588,21 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
}) })
case .Left_Bracket: case .Left_Bracket:
return parse_array_literal(parser, nesting) return parse_array_literal(parser, nesting)
case .Dot:
start := advance(parser)
member := current(parser)
if member.kind != .Identifier {
return invalid_expr(parser, member.span, "expected an enum member after '.'")
}
advance(parser)
return add_expr(parser, ast.Expr{
kind=.Enum_Literal,
span=span_from(start.span, member.span),
name=member.symbol,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Identifier: case .Identifier:
first := advance(parser) first := advance(parser)
name := first name := first
@@ -1490,6 +1505,118 @@ parse_distinct :: proc(parser: ^Parser, name: token.Token) {
_ = finish_statement(parser) _ = finish_statement(parser)
} }
parse_enum :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
explicit_backing := false
backing := types.INVALID
if _, ok := allow(parser, .Left_Paren); ok {
explicit_backing = true
backing = parse_type(parser)
if _, close_ok := allow(parser, .Right_Paren); !close_ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after enum backing type")
}
}
skip_newlines(parser)
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '{' after enum declaration")
_ = finish_statement(parser)
return
}
members: [dynamic]types.Enum_Member
members.allocator = parser.module.allocator
defer delete(members)
next_value: i128
previous_value: i128
has_previous := false
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
if current(parser).kind != .Identifier {
source.add(parser.diagnostics, current(parser).span, "expected an enum member name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
member := advance(parser)
duplicate := false
for existing in members {
if existing.name == u32(member.symbol) {
duplicate = true
break
}
}
if duplicate {
source.addf(parser.diagnostics, member.span, "duplicate enum member '%s'", token_text(parser, member))
}
value := next_value
if _, ok := allow(parser, .Equal); ok {
if !explicit_backing {
source.add(parser.diagnostics, member.span, "explicit enum values require a backing type")
}
negative := false
if _, minus_ok := allow(parser, .Minus); minus_ok {
negative = true
}
literal := current(parser)
if literal.kind != .Integer {
source.add(parser.diagnostics, literal.span, "expected a decimal integer literal for enum value")
} else {
advance(parser)
magnitude, magnitude_ok := parse_integer_magnitude(token_text(parser, literal))
if !magnitude_ok {
source.add(parser.diagnostics, literal.span, "enum value magnitude does not fit in u64")
} else {
value = i128(magnitude)
if negative {
value = -value
}
}
}
}
if has_previous && value <= previous_value {
source.add(parser.diagnostics, member.span, "enum values must be strictly increasing")
}
if !duplicate {
append(&members, types.Enum_Member{name=u32(member.symbol), value=value})
}
previous_value = value
has_previous = true
next_value = value+1
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
} else {
_ = finish_statement(parser, true)
}
}
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after enum members")
}
if len(members) == 0 {
source.add(parser.diagnostics, start.span, "enum declarations require at least one member")
}
if !explicit_backing {
max_value := u64(max(len(members)-1, 0))
backing = types.U8
if max_value > 0xff {
backing = types.U16
}
if max_value > 0xffff {
backing = types.U32
}
if max_value > 0xffff_ffff {
backing = types.U64
}
}
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
if !types.define_enum(&parser.module.type_store, id, backing, members[:], explicit_backing) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
_ = finish_statement(parser)
}
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string { decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
text := token_text(parser, tok) text := token_text(parser, tok)
if len(text) < 2 { if len(text) < 2 {
@@ -1601,6 +1728,10 @@ parse_top_level :: proc(parser: ^Parser) {
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct) parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
return return
} }
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Enum {
parse_enum(parser, name)
return
}
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct { if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
parse_distinct(parser, name) parse_distinct(parser, name)
return return
+1
View File
@@ -52,6 +52,7 @@ Kind :: enum u8 {
Keyword_C_Func, Keyword_C_Func,
Keyword_Struct, Keyword_Struct,
Keyword_C_Struct, Keyword_C_Struct,
Keyword_Enum,
Keyword_Distinct, Keyword_Distinct,
Keyword_Import, Keyword_Import,
Keyword_Return, Keyword_Return,
+68 -9
View File
@@ -65,6 +65,7 @@ Kind :: enum u8 {
Named, Named,
Alias, Alias,
Distinct, Distinct,
Enum,
Struct, Struct,
Union, Union,
} }
@@ -91,6 +92,7 @@ Node :: struct {
c_layout: bool, c_layout: bool,
opaque: bool, opaque: bool,
declared: bool, declared: bool,
explicit_backing: bool,
} }
Field :: struct { Field :: struct {
@@ -99,9 +101,15 @@ Field :: struct {
offset: u64, offset: u64,
} }
Enum_Member :: struct {
name: u32,
value: i128,
}
Store :: struct { Store :: struct {
nodes: [dynamic]Node, nodes: [dynamic]Node,
fields: [dynamic]Field, fields: [dynamic]Field,
enum_members: [dynamic]Enum_Member,
selected: target.Target, selected: target.Target,
allocator: mem.Allocator, allocator: mem.Allocator,
} }
@@ -110,6 +118,7 @@ init_store :: proc(allocator := context.allocator) -> Store {
store: Store store: Store
store.nodes.allocator = allocator store.nodes.allocator = allocator
store.fields.allocator = allocator store.fields.allocator = allocator
store.enum_members.allocator = allocator
store.selected = target.DEFAULT store.selected = target.DEFAULT
store.allocator = allocator store.allocator = allocator
return store return store
@@ -118,19 +127,21 @@ init_store :: proc(allocator := context.allocator) -> Store {
destroy_store :: proc(store: ^Store) { destroy_store :: proc(store: ^Store) {
delete(store.nodes) delete(store.nodes)
delete(store.fields) delete(store.fields)
delete(store.enum_members)
} }
clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store { clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
store := init_store(allocator) store := init_store(allocator)
append(&store.nodes, ..source.nodes[:]) append(&store.nodes, ..source.nodes[:])
append(&store.fields, ..source.fields[:]) append(&store.fields, ..source.fields[:])
append(&store.enum_members, ..source.enum_members[:])
store.selected = source.selected store.selected = source.selected
return store return store
} }
intern :: proc(store: ^Store, candidate: Node) -> Type { intern :: proc(store: ^Store, candidate: Node) -> Type {
if candidate.kind != .Struct && candidate.kind != .Union && if candidate.kind != .Struct && candidate.kind != .Union &&
candidate.kind != .Named && candidate.kind != .Distinct { candidate.kind != .Named && candidate.kind != .Distinct && candidate.kind != .Enum {
for existing, index in store.nodes { for existing, index in store.nodes {
if existing == candidate { if existing == candidate {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
@@ -146,7 +157,7 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
normalized_file := file if qualifier != 0 else u32(0) normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct || if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Struct || existing.kind == .Union) && existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file { existing.file == normalized_file {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
@@ -158,7 +169,7 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type { find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
for existing, index in store.nodes { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct || if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Struct || existing.kind == .Union) && existing.kind == .Enum || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
} }
@@ -190,6 +201,22 @@ define_distinct :: proc(store: ^Store, id, child: Type) -> bool {
return true return true
} }
define_enum :: proc(store: ^Store, id, backing: Type, members: []Enum_Member, explicit_backing: bool) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
}
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Enum
store.nodes[index].child = backing
store.nodes[index].field_start = u32(len(store.enum_members))
store.nodes[index].field_count = u32(len(members))
store.nodes[index].explicit_backing = explicit_backing
store.nodes[index].declared = true
append(&store.enum_members, ..members)
return true
}
define_record :: proc( define_record :: proc(
store: ^Store, store: ^Store,
id: Type, id: Type,
@@ -247,6 +274,19 @@ params_for :: proc(store: ^Store, value: Type) -> []Field {
return store.fields[start:end] return store.fields[start:end]
} }
enum_members_for :: proc(store: ^Store, value: Type) -> []Enum_Member {
item, ok := node(store, value)
if !ok || item.kind != .Enum {
return nil
}
start := int(item.field_start)
end := start+int(item.field_count)
if start < 0 || end > len(store.enum_members) {
return nil
}
return store.enum_members[start:end]
}
kind :: proc(value: Type, store: ^Store = nil) -> Kind { kind :: proc(value: Type, store: ^Store = nil) -> Kind {
switch value { switch value {
case INVALID: case INVALID:
@@ -409,7 +449,8 @@ is_concrete_scalar :: proc(value: Type) -> bool {
is_concrete :: proc(value: Type, store: ^Store = nil) -> bool { is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
value_kind := kind(value, store) value_kind := kind(value, store)
if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer || if value_kind == .Scalar || value_kind == .Array || value_kind == .Pointer ||
value_kind == .Slice || value_kind == .Range || value_kind == .Optional { value_kind == .Slice || value_kind == .Range || value_kind == .Optional ||
value_kind == .Enum {
return true return true
} }
if value_kind == .Struct || value_kind == .Union { if value_kind == .Struct || value_kind == .Union {
@@ -460,6 +501,10 @@ is_distinct :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Distinct return kind(value, store) == .Distinct
} }
is_enum :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Enum
}
resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type { resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type {
if depth > 64 { if depth > 64 {
return INVALID return INVALID
@@ -486,6 +531,9 @@ is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if !ok { if !ok {
return false return false
} }
if item.kind == .Enum {
return item.explicit_backing && is_concrete_integer(item.child)
}
if item.kind == .Array { if item.kind == .Array {
return item.count > 0 && !item.has_sentinel && !item.inferred_count && return item.count > 0 && !item.has_sentinel && !item.inferred_count &&
is_c_record_field_type(item.child, store, depth+1) is_c_record_field_type(item.child, store, depth+1)
@@ -524,7 +572,7 @@ is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
item, ok := node(store, value) item, ok := node(store, value)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
} }
if value_kind == .Distinct { if value_kind == .Distinct || value_kind == .Enum {
item, ok := node(store, value) item, ok := node(store, value)
return ok && item.declared && is_runtime_value(item.child, store, depth+1) return ok && item.declared && is_runtime_value(item.child, store, depth+1)
} }
@@ -541,7 +589,7 @@ runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
return INVALID return INVALID
} }
item, ok := node(store, value) item, ok := node(store, value)
if !ok || item.kind != .Distinct { if !ok || (item.kind != .Distinct && item.kind != .Enum) {
return value return value
} }
return runtime_representation(item.child, store, depth+1) return runtime_representation(item.child, store, depth+1)
@@ -585,6 +633,9 @@ is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) ->
if contains_distinct(value, store) { if contains_distinct(value, store) {
return false return false
} }
if item, ok := node(store, value); ok && item.kind == .Enum {
return item.explicit_backing && is_concrete_integer(item.child)
}
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) || return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) ||
(is_c_struct(value, store) && is_runtime_value(value, store)) (is_c_struct(value, store) && is_runtime_value(value, store))
} }
@@ -621,7 +672,12 @@ is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
return value >= C_CHAR && value <= C_USHORT return value >= C_CHAR && value <= C_USHORT
} }
c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT) -> Type { c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT, store: ^Store = nil) -> Type {
if store != nil {
if item, ok := node(store, value); ok && item.kind == .Enum {
return c_vararg_promotion(item.child, selected, store)
}
}
if !is_concrete_scalar(value) { if !is_concrete_scalar(value) {
return value return value
} }
@@ -642,6 +698,9 @@ c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT) -> Type {
} }
is_c_vararg_type :: proc(value: Type, store: ^Store) -> bool { is_c_vararg_type :: proc(value: Type, store: ^Store) -> bool {
if item, ok := node(store, value); ok && item.kind == .Enum {
return item.explicit_backing && is_concrete_integer(item.child)
}
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
} }
@@ -909,7 +968,7 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
return ((child_size+1+child_align-1)/child_align)*child_align return ((child_size+1+child_align-1)/child_align)*child_align
case .Function: case .Function:
return 0 return 0
case .Distinct: case .Distinct, .Enum:
return size(child_type(value, store), store, selected) return size(child_type(value, store), store, selected)
case .Struct: case .Struct:
item, _ := node(store, value) item, _ := node(store, value)
@@ -952,7 +1011,7 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
return alignment_of(child_type(value, store), store, selected) return alignment_of(child_type(value, store), store, selected)
case .Function: case .Function:
return 1 return 1
case .Distinct: case .Distinct, .Enum:
return alignment_of(child_type(value, store), store, selected) return alignment_of(child_type(value, store), store, selected)
case .Struct: case .Struct:
item, _ := node(store, value) item, _ := node(store, value)
+241
View File
@@ -2379,6 +2379,42 @@ libclang_import_preserves_external_object_and_final_macro_semantics :: proc(t: ^
testing.expect(t, result.available) testing.expect(t, result.available)
testing.expect_value(t, result.error_message, "") testing.expect_value(t, result.error_message, "")
enum_alias_type := cimport.INVALID_TYPE
for alias in result.aliases {
if alias.name == "Imported_Enum" {
enum_alias_type = alias.type
break
}
}
testing.expect(t, enum_alias_type != cimport.INVALID_TYPE)
if enum_alias_type != cimport.INVALID_TYPE {
testing.expect_value(t, result.types[enum_alias_type].kind, cimport.Type_Kind.C_Int)
}
enum_negative, found_enum_negative := find_cimport_macro(&result, "IMPORTED_ENUM_NEGATIVE")
enum_same, found_enum_same := find_cimport_macro(&result, "IMPORTED_ENUM_SAME")
enum_value, found_enum_value := find_cimport_macro(&result, "IMPORTED_ENUM_VALUE")
enum_back, found_enum_back := find_cimport_macro(&result, "IMPORTED_ENUM_BACK")
enum_anon, found_enum_anon := find_cimport_macro(&result, "IMPORTED_ANON_ENUM")
testing.expect(t, found_enum_negative && found_enum_same && found_enum_value && found_enum_back && found_enum_anon)
if found_enum_negative {
testing.expect(t, enum_negative.value.negative)
testing.expect_value(t, enum_negative.value.integer, u64(2))
}
if found_enum_same {
testing.expect(t, enum_same.value.negative)
testing.expect_value(t, enum_same.value.integer, u64(2))
}
if found_enum_value {
testing.expect(t, !enum_value.value.negative)
testing.expect_value(t, enum_value.value.integer, u64(7))
}
if found_enum_back {
testing.expect_value(t, enum_back.value.integer, u64(3))
}
if found_enum_anon {
testing.expect_value(t, enum_anon.value.integer, u64(9))
}
tls, found_tls := find_cimport_variable(&result, "imported_tls_global") tls, found_tls := find_cimport_variable(&result, "imported_tls_global")
testing.expect(t, found_tls) testing.expect(t, found_tls)
if found_tls { if found_tls {
@@ -5409,3 +5445,208 @@ distinct_types_compile_and_run_across_packages :: proc(t: ^testing.T) {
state := run_executable(output) state := run_executable(output)
testing.expect_value(t, state.exit_code, 0) testing.expect_value(t, state.exit_code, 0)
} }
@(test)
native_enums_preserve_identity_members_and_integer_representation :: proc(t: ^testing.T) {
text := `Animal :: enum {
dog
cat
bird
}
Nat :: enum(u16) {
one = 1
two
five = 5
}
global Animal :: Animal.dog
take :: func(value Animal) Animal {
return value
}
identity :: c_func(value Nat) Nat {
return value
}
variadic :: c_func(marker c_int, ...) c_int
main :: func() i32 {
value Animal = .cat
values [2]Animal :: [.dog, Animal.bird]
number Nat = identity(.two)
_ = variadic(0, number)
if take(value) == Animal.cat and values[0] != values[1] and number == Nat.two {
return 0
}
return 1
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
animal := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "Animal")))
nat := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "Nat")))
animal_node, animal_ok := types.node(&ast_module.type_store, animal)
nat_node, nat_ok := types.node(&ast_module.type_store, nat)
animal_members := types.enum_members_for(&ast_module.type_store, animal)
nat_members := types.enum_members_for(&ast_module.type_store, nat)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, animal_ok && nat_ok)
testing.expect(t, types.is_enum(animal, &ast_module.type_store))
testing.expect_value(t, animal_node.child, types.U8)
testing.expect(t, !animal_node.explicit_backing)
testing.expect_value(t, nat_node.child, types.U16)
testing.expect(t, nat_node.explicit_backing)
testing.expect_value(t, len(animal_members), 3)
testing.expect_value(t, animal_members[0].value, i128(0))
testing.expect_value(t, animal_members[2].value, i128(2))
testing.expect_value(t, nat_members[0].value, i128(1))
testing.expect_value(t, nat_members[1].value, i128(2))
testing.expect_value(t, nat_members[2].value, i128(5))
testing.expect_value(t, types.runtime_representation(animal, &ast_module.type_store), types.U8)
testing.expect_value(t, types.size(animal, &ast_module.type_store), u64(1))
testing.expect(t, hir_module.globals[0].is_static)
testing.expect_value(t, hir_module.globals[0].static_value, i64(0))
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i8 0"))
found_promotion := false
for function in ir_module.functions {
for instruction in function.instructions {
found_promotion = found_promotion || instruction.op == .C_Vararg_Promote
}
}
testing.expect(t, found_promotion)
}
@(test)
unbacked_enum_selects_the_smallest_fitting_unsigned_backing :: proc(t: ^testing.T) {
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "Large :: enum {\n")
for index in 0..<257 {
fmt.sbprintf(&builder, "value_%d\n", index)
}
strings.write_string(&builder, "}\nmain :: func() void {}\n")
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
large := types.find_named(&module.type_store, 0, u32(symbol.intern(&symbols, "Large")))
item, ok := types.node(&module.type_store, large)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, ok)
testing.expect_value(t, item.child, types.U16)
testing.expect_value(t, len(types.enum_members_for(&module.type_store, large)), 257)
}
@(test)
native_enum_invalid_declarations_and_operations_are_diagnosed :: proc(t: ^testing.T) {
text := `Empty :: enum {}
Dense :: enum {
zero = 0
one
}
BadBacking :: enum(f32) {
value
}
Duplicate :: enum(u8) {
value
value
}
Jumbled :: enum(i8) {
second = 2
first = 1
}
Overflow :: enum(u8) {
value = 256
}
Other :: enum {
value
}
foreign :: c_func(value Dense) void
allowed :: c_func(value Overflow) Overflow
main :: func() void {
dense Dense = Other.value
_ = Dense.zero + Dense.one
_ = Dense.zero < Dense.one
_ = Dense.missing
_ = .zero
_ = dense
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_empty := false
found_unbacked_value := false
found_backing := false
found_duplicate := false
found_order := false
found_overflow := false
found_foreign := false
found_conversion := false
found_arithmetic := false
found_comparison := false
found_member := false
found_context := false
for diagnostic in diagnostics.items {
found_empty = found_empty || strings.contains(diagnostic.message, "require at least one member")
found_unbacked_value = found_unbacked_value || strings.contains(diagnostic.message, "explicit enum values require a backing type")
found_backing = found_backing || strings.contains(diagnostic.message, "requires a concrete integer backing type")
found_duplicate = found_duplicate || strings.contains(diagnostic.message, "duplicate enum member")
found_order = found_order || strings.contains(diagnostic.message, "strictly increasing")
found_overflow = found_overflow || strings.contains(diagnostic.message, "does not fit in u8")
found_foreign = found_foreign || strings.contains(diagnostic.message, "requires concrete parameter types")
found_conversion = found_conversion || strings.contains(diagnostic.message, "cannot implicitly convert")
found_arithmetic = found_arithmetic || strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
found_comparison = found_comparison || strings.contains(diagnostic.message, "enum values only support")
found_member = found_member || strings.contains(diagnostic.message, "unknown enum member")
found_context = found_context || strings.contains(diagnostic.message, "requires an enum context")
}
testing.expect(t, found_empty)
testing.expect(t, found_unbacked_value)
testing.expect(t, found_backing)
testing.expect(t, found_duplicate)
testing.expect(t, found_order)
testing.expect(t, found_overflow)
testing.expect(t, found_foreign)
testing.expect(t, found_conversion)
testing.expect(t, found_arithmetic)
testing.expect(t, found_comparison)
testing.expect(t, found_member)
testing.expect(t, found_context)
}
@(test)
native_enums_compile_and_run_across_packages :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-enums"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/enums", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
+12
View File
@@ -12,6 +12,10 @@ call_mapper :: func(mapper native.Imported_Mapper) c_int {
return mapper?(21) return mapper?(21)
} }
enum_identity :: c_func(value native.Imported_Enum) native.Imported_Enum {
return value
}
main :: func() void { main :: func() void {
_ = native.imported_add(pass_alias(20), 22) _ = native.imported_add(pass_alias(20), 22)
_ = native.imported_scalar(3, 4) _ = native.imported_scalar(3, 4)
@@ -19,6 +23,14 @@ main :: func() void {
_ = native.imported_read(native.imported_handle()?) _ = native.imported_read(native.imported_handle()?)
_ = native.imported_string("hello") _ = native.imported_string("hello")
_ = native.imported_apply(double_value, 21) _ = native.imported_apply(double_value, 21)
enum_value native.Imported_Enum :: native.IMPORTED_ENUM_NEGATIVE
enum_same native.Imported_Enum :: native.IMPORTED_ENUM_SAME
_ = native.imported_enum_check(enum_value, native.IMPORTED_ENUM_VALUE)
_ = native.imported_enum_variadic(-2, enum_same)
enum_record native.Imported_Enum_Record :: native.Imported_Enum_Record { value = native.IMPORTED_ENUM_BACK }
_ = native.imported_enum_record(enum_record)
_ = native.imported_enum_apply(enum_identity, native.imported_enum_global)
_ = native.IMPORTED_ANON_ENUM
_ = call_mapper(double_value) _ = call_mapper(double_value)
_ = native.configured_value(9) _ = native.configured_value(9)
_ = native.IMPORTED_SHADOW_OBJECT _ = native.IMPORTED_SHADOW_OBJECT
+16 -1
View File
@@ -80,8 +80,18 @@ typedef struct Imported_Anonymous {
}; };
} Imported_Anonymous; } Imported_Anonymous;
typedef enum Imported_Enum { typedef enum Imported_Enum {
IMPORTED_ENUM_VALUE, IMPORTED_ENUM_NEGATIVE = -2,
IMPORTED_ENUM_SAME = -2,
IMPORTED_ENUM_VALUE = 7,
IMPORTED_ENUM_BACK = 3,
} Imported_Enum; } Imported_Enum;
enum {
IMPORTED_ANON_ENUM = 9,
};
typedef struct Imported_Enum_Record {
Imported_Enum value;
} Imported_Enum_Record;
typedef Imported_Enum (*Imported_Enum_Callback)(Imported_Enum value);
int imported_add(imported_int_alias left, int right); int imported_add(imported_int_alias left, int right);
unsigned long imported_scalar(unsigned char value, unsigned long extra); unsigned long imported_scalar(unsigned char value, unsigned long extra);
@@ -89,6 +99,10 @@ Imported_Handle *imported_handle(void);
int imported_read(const Imported_Handle *handle); int imported_read(const Imported_Handle *handle);
int imported_string(const char *value); int imported_string(const char *value);
int imported_apply(Imported_Mapper mapper, int value); int imported_apply(Imported_Mapper mapper, int value);
int imported_enum_check(Imported_Enum first, Imported_Enum second);
int imported_enum_variadic(int expected, ...);
Imported_Enum_Record imported_enum_record(Imported_Enum_Record value);
Imported_Enum imported_enum_apply(Imported_Enum_Callback callback, Imported_Enum value);
Imported_Value imported_by_value(Imported_Value value); Imported_Value imported_by_value(Imported_Value value);
int imported_check_state( int imported_check_state(
Imported_Color color, Imported_Color color,
@@ -126,6 +140,7 @@ extern int imported_redeclared_array[4];
extern Imported_Value imported_record_global; extern Imported_Value imported_record_global;
extern const Imported_Array_Record imported_const_array_record; extern const Imported_Array_Record imported_const_array_record;
extern Imported_Array_Record imported_mutable_array_record; extern Imported_Array_Record imported_mutable_array_record;
extern Imported_Enum imported_enum_global;
extern _Thread_local int imported_tls_global; extern _Thread_local int imported_tls_global;
extern int IMPORTED_SHADOW_OBJECT; extern int IMPORTED_SHADOW_OBJECT;
int IMPORTED_SHADOW_FUNCTION(void); int IMPORTED_SHADOW_FUNCTION(void);
+33
View File
@@ -16,6 +16,7 @@ int imported_redeclared_array[4] = {13, 14, 15, 16};
Imported_Value imported_record_global = {40}; Imported_Value imported_record_global = {40};
const Imported_Array_Record imported_const_array_record = {{20, 21}}; const Imported_Array_Record imported_const_array_record = {{20, 21}};
Imported_Array_Record imported_mutable_array_record = {{0, 1}}; Imported_Array_Record imported_mutable_array_record = {{0, 1}};
Imported_Enum imported_enum_global = IMPORTED_ENUM_VALUE;
int child_shared_global = 0; int child_shared_global = 0;
int child_value(int value) { int child_value(int value) {
@@ -56,6 +57,38 @@ int imported_apply(Imported_Mapper mapper, int value) {
return result; return result;
} }
int imported_enum_check(Imported_Enum first, Imported_Enum second) {
if (!(first == IMPORTED_ENUM_NEGATIVE && second == IMPORTED_ENUM_VALUE)) {
abort();
}
return 0;
}
int imported_enum_variadic(int expected, ...) {
va_list args;
va_start(args, expected);
int value = va_arg(args, int);
va_end(args);
if (value != expected) {
abort();
}
return 0;
}
Imported_Enum_Record imported_enum_record(Imported_Enum_Record value) {
if (value.value != IMPORTED_ENUM_BACK) {
abort();
}
return value;
}
Imported_Enum imported_enum_apply(Imported_Enum_Callback callback, Imported_Enum value) {
if (callback == NULL || callback(value) != value) {
abort();
}
return value;
}
int imported_check_state( int imported_check_state(
Imported_Color color, Imported_Color color,
int macro_value, int macro_value,
@@ -0,0 +1,8 @@
Animal :: enum {
dog
cat
}
favorite :: func() Animal {
return .cat
}
+30
View File
@@ -0,0 +1,30 @@
animals :: import "./animals"
State :: enum(u16) {
started = 10
running
stopped = 20
}
initial State :: State.started
same :: func(left State, right State) bool {
return left == right
}
identity :: c_func(value State) State {
return value
}
main :: func() i32 {
state State = identity(.running)
values [2]State :: [.started, State.stopped]
animal animals.Animal :: animals.Animal.dog
if same(state, .running) and
values[0] != values[1] and
animal != animals.favorite() and
initial == State.started {
return 0
}
return 1
}