fix honey compiler blockers

This commit is contained in:
2026-08-05 08:18:11 +02:00
parent 9e79d6692b
commit 84b88f6127
7 changed files with 141 additions and 39 deletions
+8 -6
View File
@@ -48,9 +48,10 @@ Instead of passing these on the command line, a project can describe its build
in Brolang itself. `brolang new <project>` creates a project with local `std` in Brolang itself. `brolang new <project>` creates a project with local `std`
and `ffi` copies; `brolang init` does the same for the current directory without and `ffi` copies; `brolang init` does the same for the current directory without
overwriting existing files. `brolang build [root]` reads a `config` constant overwriting existing files. `brolang build [root]` reads a `config` constant
from `root/build.bro` and compiles the program package it names. Without from exactly one of `root/build.bro` or `root/build.hon` and compiles the
`root`, it searches the current directory and parents for the nearest program package it names. Without `root`, it searches the current directory
`build.bro`. Build outputs are written to `root/build/<name>`. and parents for the nearest build file. Build outputs are written to
`root/build/<name>`.
```bro ```bro
b :: import "@std/build" b :: import "@std/build"
@@ -67,15 +68,16 @@ config :: b.BuildConfig{
``` ```
`name` is a plain executable name, and `source` is the program package relative `name` is a plain executable name, and `source` is the program package relative
to `build.bro`. The list fields map to the matching C options (`libraries` to the build file. The list fields map to the matching C options (`libraries`
`-l`, `lib_paths``-L`, `includes``-I`, `defines` → C defines, `links` `-l`, `lib_paths``-L`, `includes``-I`, `defines` → C defines, `links`
linker inputs) and, like those flags, their paths are relative to the invocation linker inputs) and, like those flags, their paths are relative to the invocation
directory. Lists take the address of an array literal; empty lists are written directory. Lists take the address of an array literal; empty lists are written
`&[]`. See `examples/build/` for runnable projects. `&[]`. See `examples/build/` for runnable projects.
Projects can declare tests directly and run them with `brolang test [root]`. Projects can declare tests directly and run them with `brolang test [root]`.
The command reads the same `build.bro`, writes `build/<name>-test`, and reuses The command reads the same build file—exactly one of `build.bro` or
its C link inputs, libraries, include paths, and defines. `build.hon`—writes `build/<name>-test`, and reuses its C link inputs,
libraries, include paths, and defines.
```bro ```bro
math :: import "../math" math :: import "../math"
+17 -15
View File
@@ -66,10 +66,10 @@ load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
return {}, false return {}, false
} }
// check needs no `main`: it synthesizes a trap main and emits one benign // check needs no `main`: it synthesizes a trap main and emits one benign
// "missing main" diagnostic, which is expected for build.bro. Suppress that // "missing main" diagnostic, which is expected for a build config. Suppress
// one but surface any real errors in build.bro (and fail on them). // that one but surface any real errors in the build config (and fail on them).
hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a) hir_module := checker.check(&ast_module, &diagnostics, &symbols, target.DEFAULT, a)
if build_bro_has_errors(&diagnostics) { if build_config_has_errors(&diagnostics) {
source.print_all(&diagnostics) source.print_all(&diagnostics)
return {}, false return {}, false
} }
@@ -82,14 +82,14 @@ project_root_for_command :: proc(root, command: string) -> (string, bool, bool)
} }
project_root, found := find_build_root() project_root, found := find_build_root()
if !found { if !found {
fmt.eprintfln("brolang %s: could not find build.bro in the current directory or any parent", command) fmt.eprintfln("brolang %s: could not find build.bro or build.hon in the current directory or any parent", command)
return "", false, false return "", false, false
} }
return project_root, true, true return project_root, true, true
} }
// run_build implements `brolang build [root]`: it reads build.bro and compiles // run_build implements `brolang build [root]`: it reads the build config and
// the configured program package. // compiles the configured program package.
run_build :: proc(root: string) -> int { run_build :: proc(root: string) -> int {
project_root, owned, found := project_root_for_command(root, "build") project_root, owned, found := project_root_for_command(root, "build")
if !found { if !found {
@@ -161,7 +161,7 @@ valid_output_name :: proc(name: string) -> bool {
build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) { build_output_path :: proc(project_root, name: string, allocator := context.allocator) -> (string, bool) {
if !valid_output_name(name) { if !valid_output_name(name) {
fmt.eprintfln("build.bro: config 'name' must be a plain executable name, got '%s'", name) fmt.eprintfln("build config: config 'name' must be a plain executable name, got '%s'", name)
return "", false return "", false
} }
build_dir, dir_error := filepath.join({project_root, "build"}, allocator) build_dir, dir_error := filepath.join({project_root, "build"}, allocator)
@@ -202,7 +202,8 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
current = strings.clone(start, allocator) current = strings.clone(start, allocator)
} }
for { for {
build_path, build_error := filepath.join({current, "build.bro"}, allocator) for build_file in ([?]string{"build.bro", "build.hon"}) {
build_path, build_error := filepath.join({current, build_file}, allocator)
if build_error != nil { if build_error != nil {
delete(current, allocator) delete(current, allocator)
return "", false return "", false
@@ -212,6 +213,7 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
if found { if found {
return current, true return current, true
} }
}
if current == "/" { if current == "/" {
delete(current, allocator) delete(current, allocator)
return "", false return "", false
@@ -227,10 +229,10 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
} }
} }
// build_bro_has_errors reports whether checking build.bro produced any diagnostic // build_config_has_errors reports whether checking a build config produced any
// other than the benign "missing or unusable main function" (build.bro has no // diagnostic other than the benign "missing or unusable main function" (build
// main by design; that one is emitted with an empty span). // configs have no main by design; that one is emitted with an empty span).
build_bro_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool { build_config_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
for item in diagnostics.items { for item in diagnostics.items {
if item.span == (source.Span{}) && item.message == "missing or unusable main function" { if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
continue continue
@@ -255,12 +257,12 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
} }
} }
if !found { if !found {
fmt.eprintln("build.bro: missing top-level 'config' constant") fmt.eprintln("build config: missing top-level 'config' constant")
return {}, false return {}, false
} }
root := unwrap_coercions(m, config_expr) root := unwrap_coercions(m, config_expr)
if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct { if root == hir.INVALID_EXPR || m.exprs[root].kind != .Struct {
fmt.eprintln("build.bro: 'config' must be a BuildConfig{...} literal") fmt.eprintln("build config: 'config' must be a BuildConfig{...} literal")
return {}, false return {}, false
} }
args := m.exprs[root].args args := m.exprs[root].args
@@ -322,7 +324,7 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
cfg.c_options.defines = defines[:] cfg.c_options.defines = defines[:]
if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 { if len(cfg.output_name) == 0 || len(cfg.source_dir) == 0 {
fmt.eprintln("build.bro: config requires non-empty 'name' and 'source'") fmt.eprintln("build config: config requires non-empty 'name' and 'source'")
destroy_build_config(&cfg) destroy_build_config(&cfg)
return {}, false return {}, false
} }
+1 -1
View File
@@ -1677,7 +1677,7 @@ is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
#partial switch kind(value, store) { #partial switch kind(value, store) {
case .Scalar: case .Scalar:
return u64(bits(value, selected)/8) return u64((bits(value, selected)+7)/8)
case .Pointer: case .Pointer:
return u64(target.pointer_bits(selected)/8) return u64(target.pointer_bits(selected)/8)
case .Slice: case .Slice:
+100 -3
View File
@@ -1738,6 +1738,16 @@ layout_builtins_compile_and_run :: proc(t: ^testing.T) {
y u8 y u8
} }
Bool_First :: struct {
flag bool
byte u8
}
Bool_Last :: struct {
byte u8
flag bool
}
Opaque :: opaque Opaque :: opaque
Color :: enum { Color :: enum {
red red
@@ -1755,6 +1765,20 @@ buffer func($T type) [sizeof!(T)]u8 {
return data return data
} }
make_bool_first func() Bool_First {
value Bool_First = undefined
value.flag = true
value.byte = 41
return value
}
make_bool_last func() Bool_Last {
value Bool_Last = undefined
value.byte = 42
value.flag = true
return value
}
main func() i32 { main func() i32 {
bytes [_]u8 :: buffer(i32) bytes [_]u8 :: buffer(i32)
if (needs_usize(SIZE_GLOBAL) != 4) return 1 if (needs_usize(SIZE_GLOBAL) != 4) return 1
@@ -1771,6 +1795,15 @@ main func() i32 {
if (alignof!(Point) != 4) return 12 if (alignof!(Point) != 4) return 12
if (sizeof!(UserID) != 4) return 13 if (sizeof!(UserID) != 4) return 13
if (alignof!(UserID) != 4) return 14 if (alignof!(UserID) != 4) return 14
if (sizeof!(bool) != 1) return 15
if (sizeof!(Bool_First) != 2) return 16
if (sizeof!(Bool_Last) != 2) return 17
first :: make_bool_first()
if (!first.flag) return 18
if (first.byte != 41) return 19
last :: make_bool_last()
if (!last.flag) return 20
if (last.byte != 42) return 21
return 0 return 0
} }
` `
@@ -5560,19 +5593,27 @@ init_project_creates_missing_layout_and_preserves_existing_files :: proc(t: ^tes
} }
@(test) @(test)
build_root_search_finds_nearest_parent_build_file :: proc(t: ^testing.T) { build_hon_is_discovered_from_nested_directory_and_builds :: proc(t: ^testing.T) {
root := "/tmp/brolang-test-build-search" root := "/tmp/brolang-test-build-search"
nested := "/tmp/brolang-test-build-search/source/nested" nested := "/tmp/brolang-test-build-search/source/nested"
build_bro := "/tmp/brolang-test-build-search/build.bro"
build_hon := "/tmp/brolang-test-build-search/build.hon"
output := "/tmp/brolang-test-build-search/build/brolang-test-build-search"
_ = os2.remove_all(root) _ = os2.remove_all(root)
defer _ = os2.remove_all(root) defer _ = os2.remove_all(root)
testing.expect(t, init_project(root))
testing.expect(t, os.rename(build_bro, build_hon))
testing.expect(t, os2.make_directory_all(nested) == nil) testing.expect(t, os2.make_directory_all(nested) == nil)
build_text := "# root\n"
testing.expect(t, os.write_entire_file("/tmp/brolang-test-build-search/build.bro", transmute([]byte)build_text))
found, ok := compiler_core.find_build_root_from(nested) found, ok := compiler_core.find_build_root_from(nested)
defer delete(found) defer delete(found)
testing.expect(t, ok) testing.expect(t, ok)
testing.expect(t, strings.has_suffix(found, "/brolang-test-build-search")) testing.expect(t, strings.has_suffix(found, "/brolang-test-build-search"))
status := compiler_core.run_build(found)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
} }
@(test) @(test)
@@ -10460,6 +10501,62 @@ cross_package_recursive_specialization_reaches_fixed_point :: proc(t: ^testing.T
testing.expect(t, os.exists(output)) testing.expect(t, os.exists(output))
} }
@(test)
qualified_concrete_call_in_runtime_for_retains_specialization :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-qualified-concrete-call"
ast_directory := "/tmp/brolang-test-qualified-concrete-call/ast"
main_path := "/tmp/brolang-test-qualified-concrete-call/main.hon"
ast_path := "/tmp/brolang-test-qualified-concrete-call/ast/ast.hon"
output := "/tmp/brolang-test-qualified-concrete-call-output"
ast_text := `Node :: struct { token usize }
Token :: struct { start usize }
render_literal func(node @Node, tokens []Token, program []u8) ?[]u8 {
if (node.token >= tokens.len) return null
token :: tokens[node.token]
if (token.start >= program.len) return null
return program[token.start..token.start+1]
}
`
main_text := `ast :: import "./ast"
main func() i32 {
nodes [1]mut ast.Node = undefined
nodes[0].token = 0
token_storage [1]mut ast.Token = undefined
token_storage[0].start = 0
mutable_tokens []mut ast.Token = token_storage[..]
program :: "x"
for nodes |node| {
_ = ast.render_literal(&node, mutable_tokens, program)
}
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.make_directory(ast_directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)main_text))
testing.expect(t, os.write_entire_file(ast_path, transmute([]byte)ast_text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test) @(test)
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) { imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-package-missing-root-main" output := "/tmp/brolang-test-package-missing-root-main"
+2 -2
View File
@@ -101,10 +101,10 @@ print_usage :: proc() {
" brolang translate-c|--translate-c <header.h>... [--output-dir <dir>] [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...", " brolang translate-c|--translate-c <header.h>... [--output-dir <dir>] [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
) )
fmt.eprintln( fmt.eprintln(
" brolang build [root] (reads root/build.bro; writes root/build/name)", " brolang build [root] (reads exactly one of root/build.bro or root/build.hon; writes root/build/name)",
) )
fmt.eprintln( fmt.eprintln(
" brolang test [root] (reads root/build.bro; writes and runs root/build/name-test)", " brolang test [root] (reads exactly one of root/build.bro or root/build.hon; writes and runs root/build/name-test)",
) )
fmt.eprintln( fmt.eprintln(
" brolang new <project-path>", " brolang new <project-path>",
+5 -4
View File
@@ -1,14 +1,15 @@
# Build configuration surface for `brolang build` (v0). # Build configuration surface for `brolang build` (v0).
# #
# A project's `build.bro` imports this module and declares a top-level constant # A project's `build.bro` or `build.hon` imports this module and declares a
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks # top-level constant named `config` of type `BuildConfig`. `brolang build
# build.bro, reads the config, and writes root/build/name. # [root]` accepts exactly one of those files, reads the config, and writes
# root/build/name.
# #
# Declarative and literal-only: one executable per build. List fields take an # Declarative and literal-only: one executable per build. List fields take an
# address-of an array literal (`&["raylib"]`) and default to empty. # address-of an array literal (`&["raylib"]`) and default to empty.
BuildConfig :: struct { BuildConfig :: struct {
name []u8 # output executable name under root/build name []u8 # output executable name under root/build
source []u8 # program package directory, relative to build.bro source []u8 # program package directory, relative to the build file
libraries [][]u8 = &[] # library names to link (-l) libraries [][]u8 = &[] # library names to link (-l)
lib_paths [][]u8 = &[] # library search directories (-L) lib_paths [][]u8 = &[] # library search directories (-L)
includes [][]u8 = &[] # C include directories (-I) includes [][]u8 = &[] # C include directories (-I)
View File