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
+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)
}