distinct type aliasing

This commit is contained in:
2026-06-23 12:29:21 +02:00
parent 6512ccd543
commit f16f352d1e
15 changed files with 525 additions and 58 deletions
+1
View File
@@ -15,6 +15,7 @@
- exact-width `i8` through `i64`, `u8` through `u64`, `f32`, `f64`, `isize`, `usize`, `bool`, `void`, and inferred integer-constrained `int`
- target-dependent atomic C primitives from `c_char` through `c_longdouble`
- C primitives remain semantically distinct from exact-width Brolang primitives until target lowering
- nominal distinct types with explicit exact-backing construction: `UserID :: distinct u32` and `UserID(42)`
- contextual integer and character literals and constant folding of arithmetic and negation trees
- strict numeric conversions, binary `+ - * /` with checked integer overflow and divide-by-zero traps (floats follow IEEE), and unary negation
- boolean literals, comparisons, unary `!`, and short-circuiting `and` / `or`
+7 -2
View File
@@ -127,7 +127,12 @@
7. enums (native and c interop) (see below)
8. distinct types (see below)
8. distinct types (implemented; see below)
- nominal declarations preserve identity across packages and reuse the backing runtime representation
- construction uses `Type(value)` with exactly one value of the exact backing type
- no implicit conversion to or from the backing type
- backing-type operators and reverse explicit conversions remain deferred
- concrete runtime backing types are supported; unresolved, `int`, `void`, function, and opaque backings are rejected
## A word on multi-unwrap
@@ -204,7 +209,7 @@ Distinct types are considered distinct from their backing type. They do not impl
UserID :: distinct u32
# instantiate distinct type
my_id UserId :: UserID(42) # value must be of to backing type
my_id UserID :: UserID(42) # value must have the exact backing type
```
# A word on enums
+120 -17
View File
@@ -870,6 +870,15 @@ validate_declarations :: proc(checker: ^Checker) {
validate_type_nodes :: proc(checker: ^Checker) {
for item, index in checker.module.types.nodes {
id := types.DYNAMIC_START+types.Type(index)
if item.kind == .Distinct &&
(!item.declared || !types.is_runtime_value(item.child, &checker.module.types)) {
source.addf(
checker.diagnostics,
source.Span{},
"distinct type '%s' requires a concrete runtime backing type",
symbol_text(checker, symbol.Id(item.name)),
)
}
if item.has_sentinel {
value := i128(item.sentinel)
if types.is_signed(item.child, checker.target) {
@@ -1311,6 +1320,14 @@ infer_expr :: proc(
}
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
stack[frame_index].left = distinct_type
stack[frame_index].stage = 7
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
continue
}
last = types.INVALID
_ = pop(&stack)
continue
@@ -1426,6 +1443,10 @@ infer_expr :: proc(
stack[frame_index].args = nil
_ = pop(&stack)
}
if frame.stage == 7 {
last = frame.left
_ = pop(&stack)
}
}
return last
}
@@ -1951,6 +1972,7 @@ build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type
Build_Expr_Frame :: struct {
expr: ast.Expr_Id,
expected: types.Type,
target_type: types.Type,
stage: u8,
left: hir.Expr_Id,
arg_index: int,
@@ -2701,38 +2723,81 @@ build_expr :: proc(
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION {
callee := hir.INVALID_EXPR
callee_from_global := false
non_callable := false
non_callable_global := false
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
if _, _, _, callable := types.function_pointer(local.type, &checker.module.types); callable {
callee = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
} else {
non_callable = true
}
}
if callee == hir.INVALID_EXPR {
}
if callee == hir.INVALID_EXPR && !non_callable {
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
callee = build_global_reference(checker, global, expr.span, global_reads)
callee_from_global = true
} else {
non_callable = true
non_callable_global = true
}
}
}
if callee == hir.INVALID_EXPR {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name))
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
if distinct_ok && distinct_item.kind == .Distinct {
if !is_runtime_type(checker, distinct_type) {
id := source.addf(
checker.diagnostics,
expr.span,
"distinct type '%s' has no concrete runtime backing type",
symbol_text(checker, expr.name),
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if len(expr.args) != 1 {
id := source.addf(
checker.diagnostics,
expr.span,
"distinct type '%s' expects 1 argument, got %d",
symbol_text(checker, expr.name),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id, distinct_type)
_ = pop(&stack)
continue
}
stack[frame_index].target_type = distinct_type
stack[frame_index].stage = 8
append(&stack, Build_Expr_Frame{
expr=expr.args[0],
expected=distinct_item.child,
template=ast.INVALID_FUNCTION,
})
continue
}
id := source.INVALID_DIAGNOSTIC
if non_callable {
id = add_call_resolution_diagnostic(checker, expr, target_pkg) if non_callable_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
} else {
id = add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
}
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := add_call_resolution_diagnostic(checker, expr, target_pkg) if callee_from_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
_, function_item, function_type, _ := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
@@ -2999,6 +3064,32 @@ build_expr :: proc(
}
_ = pop(&stack)
}
if frame.stage == 8 {
distinct_item, ok := types.node(&checker.module.types, frame.target_type)
actual := checker.module.exprs[last].type
if !ok || distinct_item.kind != .Distinct || !types.equal(actual, distinct_item.child) {
id := source.addf(
checker.diagnostics,
expr.span,
"distinct type '%s' requires an exact %s value, got %s",
symbol_text(checker, expr.name),
types.name(distinct_item.child),
types.name(actual),
)
last = invalid_hir_expr(checker, expr.span, id, frame.target_type)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Retype,
span=expr.span,
type=frame.target_type,
left=last,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
_ = pop(&stack)
}
}
return last
}
@@ -3783,6 +3874,21 @@ expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
return false
}
static_integer_value :: proc(module: ^hir.Module, expr_id: hir.Expr_Id) -> (i64, bool) {
current := expr_id
for current != hir.INVALID_EXPR && int(current) < len(module.exprs) {
expr := module.exprs[current]
if expr.kind == .Integer {
return expr.integer, true
}
if expr.kind != .Retype {
break
}
current = expr.left
}
return 0, false
}
build_globals :: proc(checker: ^Checker) {
for global, global_index in checker.ast_module.globals {
if global.external {
@@ -3864,11 +3970,8 @@ build_globals :: proc(checker: ^Checker) {
)
expr = invalid_hir_expr(checker, global.span, diagnostic)
}
is_static := checker.module.exprs[expr].kind == .Integer && diagnostic == source.INVALID_DIAGNOSTIC
static_value: i64
if is_static {
static_value = checker.module.exprs[expr].integer
}
static_value, is_static := static_integer_value(&checker.module, expr)
is_static = is_static && diagnostic == source.INVALID_DIAGNOSTIC
_ = hir.global_id(len(checker.module.globals))
append(
&checker.module.globals,
+1
View File
@@ -96,6 +96,7 @@ Expr_Kind :: enum u8 {
Orelse,
Widen,
C_Vararg_Promote,
Retype,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
+1
View File
@@ -93,6 +93,7 @@ Opcode :: enum u8 {
Orelse,
Widen,
C_Vararg_Promote,
Retype,
Weaken_Pointer,
Weaken_Slice,
Decay_Array_Pointer,
+1
View File
@@ -18,6 +18,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "c_func": return .Keyword_C_Func
case "struct": return .Keyword_Struct
case "c_struct": return .Keyword_C_Struct
case "distinct": return .Keyword_Distinct
case "import": return .Keyword_Import
case "return": return .Keyword_Return
case "mut": return .Keyword_Mut
+49 -21
View File
@@ -135,35 +135,39 @@ c_abi_result_type :: proc(value: types.Type, store: ^types.Store) -> string {
}
llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
if types.is_void(value) {
resolved := value
if store != nil {
resolved = types.runtime_representation(value, store)
}
if types.is_void(resolved) {
return "void"
}
if types.is_bool(value) {
if types.is_bool(resolved) {
return "i1"
}
#partial switch types.kind(value, store) {
#partial switch types.kind(resolved, store) {
case .Pointer:
return "ptr"
case .Slice:
return "{ ptr, i64 }"
case .Range:
item, _ := types.node(store, value)
item, _ := types.node(store, resolved)
child := llvm_type(item.child, store)
return fmt.tprintf("{{ %s, %s, i1 }}", child, child)
case .Array:
item, _ := types.node(store, value)
return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store))
item, _ := types.node(store, resolved)
return fmt.tprintf("[%d x %s]", types.physical_count(resolved, store), llvm_type(item.child, store))
case .Optional:
item, _ := types.node(store, value)
item, _ := types.node(store, resolved)
if types.is_pointer(item.child, store) {
return "ptr"
}
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
case .Struct, .Union:
return fmt.tprintf("%%bro.type.%d", value)
return fmt.tprintf("%%bro.type.%d", resolved)
}
selected := store.selected if store != nil else target.DEFAULT
repr := types.representation(value, selected)
repr := types.representation(resolved, selected)
if types.is_float(repr) {
return "float" if types.bits(repr) == 32 else "double"
}
@@ -207,11 +211,15 @@ emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, s
strings.write_string(builder, function_result_type(function, store))
}
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
if types.is_bool(value_type) {
sentinel :: proc(value_type: types.Type, store: ^types.Store = nil, selected := target.DEFAULT) -> i64 {
repr := value_type
if store != nil {
repr = types.runtime_representation(value_type, store)
}
if types.is_bool(repr) {
return 0
}
switch types.bits(value_type, selected) {
switch types.bits(repr, selected) {
case 8: return -86
case 16: return -21846
case 32: return -1431655766
@@ -239,7 +247,7 @@ valid_value :: proc(
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr,
.Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse,
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Widen, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
@@ -270,18 +278,22 @@ valid_address :: proc(
}
write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.Type, store: ^types.Store = nil) {
if !types.is_concrete_scalar(value_type) {
resolved := value_type
if store != nil {
resolved = types.runtime_representation(value_type, store)
}
if !types.is_concrete_scalar(resolved) {
strings.write_string(builder, "zeroinitializer")
return
}
if types.is_bool(value_type) {
if types.is_bool(resolved) {
strings.write_string(builder, "true" if value != 0 else "false")
return
}
selected := store.selected if store != nil else target.DEFAULT
if types.is_float(value_type, selected) {
if types.is_float(resolved, selected) {
text := ""
if types.bits(value_type, selected) == 32 {
if types.bits(resolved, selected) == 32 {
bits := u32(value)
number := transmute(f32)bits
text = fmt.tprintf("%.9g", number)
@@ -306,7 +318,7 @@ write_operand :: proc(
store: ^types.Store,
) {
if !valid_value(instructions, value_id, expected, store) {
write_constant(builder, sentinel(expected, store.selected), expected, store)
write_constant(builder, sentinel(expected, store, store.selected), expected, store)
return
}
value := instructions[value_id]
@@ -378,7 +390,7 @@ emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction:
" %%v%d = add %s 0, %d\n",
instruction_id,
llvm_type(instruction.type, &emitter.module.types),
sentinel(instruction.type, emitter.module.target),
sentinel(instruction.type, &emitter.module.types, emitter.module.target),
)
return
}
@@ -388,9 +400,9 @@ emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction:
instruction_id,
llvm_type(instruction.type, &emitter.module.types),
)
write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types)
write_constant(&emitter.builder, sentinel(instruction.type, &emitter.module.types, emitter.module.target), instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types))
write_constant(&emitter.builder, sentinel(instruction.type, emitter.module.target), instruction.type, &emitter.module.types)
write_constant(&emitter.builder, sentinel(instruction.type, &emitter.module.types, emitter.module.target), instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
}
}
@@ -1206,6 +1218,22 @@ emit_instruction_stream :: proc(
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)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .Retype:
if !valid_instruction(instructions, instruction.a) ||
!types.can_construct_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct type construction")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
write_operand(
&emitter.builder,
instructions,
instruction.a,
instructions[instruction.a].type,
&emitter.module.types,
)
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
case .Weaken_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
+5
View File
@@ -1320,6 +1320,11 @@ canonical_type :: proc(
mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index]
}
if item.kind == .Distinct {
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 {
mapping[index] = value
fields := types.fields_for(&module.type_store, value)
+9 -7
View File
@@ -38,11 +38,12 @@ clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []i
return result
}
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
if types.is_float(value_type, selected) {
return i64(0x7fc0_0000) if types.bits(value_type, selected) == 32 else transmute(i64)u64(0x7ff8_0000_0000_0000)
sentinel :: proc(value_type: types.Type, store: ^types.Store, selected := target.DEFAULT) -> i64 {
repr := types.runtime_representation(value_type, store)
if types.is_float(repr, selected) {
return i64(0x7fc0_0000) if types.bits(repr, selected) == 32 else transmute(i64)u64(0x7ff8_0000_0000_0000)
}
switch types.bits(value_type, selected) {
switch types.bits(repr, selected) {
case 8: return -86
case 16: return -21846
case 32: return -1431655766
@@ -73,7 +74,7 @@ append_recovery_value :: proc(
op=.Const,
span=span,
type=fallback,
integer=sentinel(fallback, state.hir_module.target),
integer=sentinel(fallback, &state.hir_module.types, state.hir_module.target),
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
@@ -466,7 +467,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
case .Widen, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -524,6 +525,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Weaken_Slice: op = .Weaken_Slice
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
case .C_Vararg_Promote: op = .C_Vararg_Promote
case .Retype: op = .Retype
case: op = .Widen
}
last = append_instruction(state, ir.Instruction{
@@ -1286,7 +1288,7 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
value := append_instruction(&state, ir.Instruction{
op=.Const,
type=function.result,
integer=sentinel(function.result, hir_module.target),
integer=sentinel(function.result, &hir_module.types, hir_module.target),
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
+17
View File
@@ -1477,6 +1477,19 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool) {
_ = finish_statement(parser)
}
parse_distinct :: proc(parser: ^Parser, name: token.Token) {
start := advance(parser)
child := parse_type(parser)
id := types.named(&parser.module.type_store, u32(parser.pkg), u32(name.symbol))
if !types.define_distinct(&parser.module.type_store, id, child) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
if !types.is_valid(child) {
source.add(parser.diagnostics, start.span, "distinct declarations require a backing type")
}
_ = finish_statement(parser)
}
decode_import_path :: proc(parser: ^Parser, tok: token.Token) -> string {
text := token_text(parser, tok)
if len(text) < 2 {
@@ -1588,6 +1601,10 @@ parse_top_level :: proc(parser: ^Parser) {
parse_struct(parser, name, current(parser).kind == .Keyword_C_Struct)
return
}
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Distinct {
parse_distinct(parser, name)
return
}
expr := parse_expression(parser)
_ = ast.global_id(len(parser.module.globals))
+1
View File
@@ -52,6 +52,7 @@ Kind :: enum u8 {
Keyword_C_Func,
Keyword_Struct,
Keyword_C_Struct,
Keyword_Distinct,
Keyword_Import,
Keyword_Return,
Keyword_Mut,
+85 -4
View File
@@ -64,6 +64,7 @@ Kind :: enum u8 {
Function,
Named,
Alias,
Distinct,
Struct,
Union,
}
@@ -128,7 +129,8 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
}
intern :: proc(store: ^Store, candidate: Node) -> Type {
if candidate.kind != .Struct && candidate.kind != .Union && candidate.kind != .Named {
if candidate.kind != .Struct && candidate.kind != .Union &&
candidate.kind != .Named && candidate.kind != .Distinct {
for existing, index in store.nodes {
if existing == candidate {
return DYNAMIC_START+Type(index)
@@ -143,7 +145,8 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file {
return DYNAMIC_START+Type(index)
@@ -154,7 +157,8 @@ 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 {
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Distinct ||
existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index)
}
@@ -174,6 +178,18 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool {
return true
}
define_distinct :: proc(store: ^Store, id, child: Type) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
}
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Distinct
store.nodes[index].child = child
store.nodes[index].declared = true
return true
}
define_record :: proc(
store: ^Store,
id: Type,
@@ -440,6 +456,10 @@ is_union :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Union
}
is_distinct :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Distinct
}
resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type {
if depth > 64 {
return INVALID
@@ -456,6 +476,9 @@ is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool {
return false
}
resolved := resolve_alias(value, store)
if contains_distinct(resolved, store) {
return false
}
if is_concrete_scalar(resolved) || is_pointer(resolved, store) || is_optional_pointer(resolved, store) {
return true
}
@@ -486,7 +509,10 @@ is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
return ok && item.kind == .Optional && is_pointer(item.child, store)
}
is_runtime_value :: proc(value: Type, store: ^Store) -> bool {
is_runtime_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return false
}
value_kind := kind(value, store)
if value_kind == .Scalar || value_kind == .Pointer {
return true
@@ -498,9 +524,29 @@ is_runtime_value :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
}
if value_kind == .Distinct {
item, ok := node(store, value)
return ok && item.declared && is_runtime_value(item.child, store, depth+1)
}
return false
}
can_construct_distinct :: proc(from, to: Type, store: ^Store) -> bool {
item, ok := node(store, to)
return ok && item.kind == .Distinct && item.declared && equal(from, item.child)
}
runtime_representation :: proc(value: Type, store: ^Store, depth := 0) -> Type {
if depth > 256 {
return INVALID
}
item, ok := node(store, value)
if !ok || item.kind != .Distinct {
return value
}
return runtime_representation(item.child, store, depth+1)
}
contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return true
@@ -536,10 +582,41 @@ is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) ->
if allow_void && is_void(value) {
return true
}
if contains_distinct(value, store) {
return false
}
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) ||
(is_c_struct(value, store) && is_runtime_value(value, store))
}
contains_distinct :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return true
}
item, ok := node(store, value)
if !ok {
return false
}
if item.kind == .Distinct {
return true
}
if item.kind == .Struct || item.kind == .Union {
for field in fields_for(store, value) {
if contains_distinct(field.type, store, depth+1) {
return true
}
}
}
if item.kind == .Function {
for param in params_for(store, value) {
if contains_distinct(param.type, store, depth+1) {
return true
}
}
}
return is_valid(item.child) && contains_distinct(item.child, store, depth+1)
}
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
return value >= C_CHAR && value <= C_USHORT
}
@@ -832,6 +909,8 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
return ((child_size+1+child_align-1)/child_align)*child_align
case .Function:
return 0
case .Distinct:
return size(child_type(value, store), store, selected)
case .Struct:
item, _ := node(store, value)
if item.explicit_size > 0 {
@@ -873,6 +952,8 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
return alignment_of(child_type(value, store), store, selected)
case .Function:
return 1
case .Distinct:
return alignment_of(child_type(value, store), store, selected)
case .Struct:
item, _ := node(store, value)
if item.explicit_alignment > 0 {
+178
View File
@@ -5231,3 +5231,181 @@ checked_division_and_subtraction_emit_guarded_llvm :: proc(t: ^testing.T) {
testing.expect(t, strings.contains(llvm_text, "divzero_trap"))
testing.expect(t, strings.contains(llvm_text, "divovf_trap"))
}
@(test)
distinct_types_preserve_nominal_identity_and_backing_representation :: proc(t: ^testing.T) {
text := `Point :: struct {
x i32
y i32
}
UserID :: distinct u32
OtherID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct UserID
static_id UserID :: UserID(42)
take :: func(value UserID) UserID {
return value
}
main :: func() i32 {
id UserID :: UserID(7)
copy UserID = take(id)
maybe ?UserID = copy
pointer @UserID = &copy
point PointID :: PointID(Point { x = 1, y = 2 })
bytes Bytes :: Bytes([3, 4])
wrapped WrappedID :: WrappedID(id)
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
return 0
}
`
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)
user_id := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "UserID")))
other_id := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "OtherID")))
point_id := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "PointID")))
point := types.find_named(&ast_module.type_store, 0, u32(symbol.intern(&symbols, "Point")))
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, user_id != other_id)
testing.expect(t, user_id != types.U32)
testing.expect(t, types.is_distinct(user_id, &ast_module.type_store))
testing.expect_value(t, types.runtime_representation(user_id, &ast_module.type_store), types.U32)
testing.expect_value(t, types.runtime_representation(point_id, &ast_module.type_store), point)
testing.expect_value(t, types.size(user_id, &ast_module.type_store), types.size(types.U32, &ast_module.type_store))
testing.expect(t, hir_module.globals[0].is_static)
testing.expect_value(t, hir_module.globals[0].static_value, i64(42))
testing.expect(t, strings.contains(llvm_text, "@bro.g.0 = internal constant i32 42"))
testing.expect(t, strings.contains(llvm_text, "select i1 true, i32"))
retype_count := 0
for function in ir_module.functions {
for instruction in function.instructions {
retype_count += 1 if instruction.op == .Retype else 0
}
}
testing.expect_value(t, retype_count, 4)
}
@(test)
distinct_types_reject_implicit_conversions_operators_and_invalid_backings :: proc(t: ^testing.T) {
text := `Opaque :: c_struct
UserID :: distinct u32
OtherID :: distinct u32
BadInt :: distinct int
BadVoid :: distinct void
BadFunction :: distinct c_func() void
BadOpaque :: distinct Opaque
foreign :: c_func(value UserID) void
foreign_pointer :: c_func(value @UserID) void
main :: func() void {
raw u32 = 1
id UserID = raw
backing u32 = UserID(2)
other OtherID = UserID(3)
narrow u8 = 4
_ = UserID(narrow)
_ = UserID()
_ = UserID(1, 2)
left UserID :: UserID(5)
right UserID :: UserID(6)
_ = left + right
_ = left == right
}
`
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)
invalid_backing_count := 0
implicit_conversion_count := 0
found_exact := false
found_arity := false
found_arithmetic := false
found_comparison := false
foreign_signature_count := 0
for diagnostic in diagnostics.items {
invalid_backing_count += 1 if strings.contains(diagnostic.message, "requires a concrete runtime backing type") else 0
implicit_conversion_count += 1 if strings.contains(diagnostic.message, "cannot implicitly convert") else 0
found_exact = found_exact || strings.contains(diagnostic.message, "requires an exact u32 value, got u8")
found_arity = found_arity || strings.contains(diagnostic.message, "expects 1 argument")
found_arithmetic = found_arithmetic || strings.contains(diagnostic.message, "arithmetic requires compatible numeric operands")
found_comparison = found_comparison || strings.contains(diagnostic.message, "comparison requires compatible numeric operands")
foreign_signature_count += 1 if strings.contains(diagnostic.message, "requires concrete parameter types") else 0
}
testing.expect_value(t, invalid_backing_count, 4)
testing.expect(t, implicit_conversion_count >= 3)
testing.expect(t, found_exact)
testing.expect(t, found_arity)
testing.expect(t, found_arithmetic)
testing.expect(t, found_comparison)
testing.expect_value(t, foreign_signature_count, 2)
}
@(test)
distinct_type_construction_defers_to_callable_names :: proc(t: ^testing.T) {
text := `Value :: distinct u32
Value :: func(value i32) i32 {
return value
}
main :: func() i32 {
return Value(42)
}
`
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_call := false
found_retype := false
for expr in hir_module.exprs {
found_call = found_call || expr.kind == .Call
found_retype = found_retype || expr.kind == .Retype
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, found_call)
testing.expect(t, !found_retype)
}
@(test)
distinct_types_compile_and_run_across_packages :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-distinct-types"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/distinct_types", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@@ -0,0 +1,5 @@
UserID :: distinct u32
make :: func(value u32) UserID {
return UserID(value)
}
+38
View File
@@ -0,0 +1,38 @@
ids :: import "./ids"
Point :: struct {
x i32
y i32
}
LocalID :: distinct u32
PointID :: distinct Point
Bytes :: distinct [2]u8
WrappedID :: distinct LocalID
static_id LocalID :: LocalID(42)
take :: func(value LocalID) LocalID {
return value
}
main :: func() i32 {
id LocalID :: LocalID(7)
copy LocalID = take(id)
maybe ?LocalID = copy
pointer @LocalID = &copy
point PointID :: PointID(Point { x = 1, y = 2 })
bytes Bytes :: Bytes([3, 4])
wrapped WrappedID :: WrappedID(id)
remote ids.UserID :: ids.UserID(8)
remote_copy ids.UserID :: ids.make(9)
_ = static_id
_ = maybe
_ = pointer
_ = point
_ = bytes
_ = wrapped
_ = remote
_ = remote_copy
return 0
}