distinct type aliasing
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
+127
-24
@@ -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 {
|
||||
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,
|
||||
})
|
||||
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 {
|
||||
callee = build_global_reference(checker, global, expr.span, global_reads)
|
||||
callee_from_global = true
|
||||
if _, _, _, callable := types.function_pointer(checker.global_types[global], &checker.module.types); callable {
|
||||
callee = build_global_reference(checker, global, expr.span, global_reads)
|
||||
} else {
|
||||
non_callable = true
|
||||
non_callable_global = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if callee == hir.INVALID_EXPR {
|
||||
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)
|
||||
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,
|
||||
|
||||
@@ -96,6 +96,7 @@ Expr_Kind :: enum u8 {
|
||||
Orelse,
|
||||
Widen,
|
||||
C_Vararg_Promote,
|
||||
Retype,
|
||||
Weaken_Pointer,
|
||||
Weaken_Slice,
|
||||
Decay_Array_Pointer,
|
||||
|
||||
@@ -93,6 +93,7 @@ Opcode :: enum u8 {
|
||||
Orelse,
|
||||
Widen,
|
||||
C_Vararg_Promote,
|
||||
Retype,
|
||||
Weaken_Pointer,
|
||||
Weaken_Slice,
|
||||
Decay_Array_Pointer,
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -52,6 +52,7 @@ Kind :: enum u8 {
|
||||
Keyword_C_Func,
|
||||
Keyword_Struct,
|
||||
Keyword_C_Struct,
|
||||
Keyword_Distinct,
|
||||
Keyword_Import,
|
||||
Keyword_Return,
|
||||
Keyword_Mut,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = ©
|
||||
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)
|
||||
}
|
||||
@@ -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 = ©
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user