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`
and `ffi` copies; `brolang init` does the same for the current directory without
overwriting existing files. `brolang build [root]` reads a `config` constant
from `root/build.bro` and compiles the program package it names. Without
`root`, it searches the current directory and parents for the nearest
`build.bro`. Build outputs are written to `root/build/<name>`.
from exactly one of `root/build.bro` or `root/build.hon` and compiles the
program package it names. Without `root`, it searches the current directory
and parents for the nearest build file. Build outputs are written to
`root/build/<name>`.
```bro
b :: import "@std/build"
@@ -67,15 +68,16 @@ config :: b.BuildConfig{
```
`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`
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
`&[]`. See `examples/build/` for runnable projects.
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
its C link inputs, libraries, include paths, and defines.
The command reads the same build file—exactly one of `build.bro` or
`build.hon`—writes `build/<name>-test`, and reuses its C link inputs,
libraries, include paths, and defines.
```bro
math :: import "../math"
+17 -15
View File
@@ -66,10 +66,10 @@ load_build_config :: proc(project_root: string) -> (BuildConfig, bool) {
return {}, false
}
// check needs no `main`: it synthesizes a trap main and emits one benign
// "missing main" diagnostic, which is expected for build.bro. Suppress that
// one but surface any real errors in build.bro (and fail on them).
// "missing main" diagnostic, which is expected for a build config. Suppress
// 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)
if build_bro_has_errors(&diagnostics) {
if build_config_has_errors(&diagnostics) {
source.print_all(&diagnostics)
return {}, false
}
@@ -82,14 +82,14 @@ project_root_for_command :: proc(root, command: string) -> (string, bool, bool)
}
project_root, found := find_build_root()
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 project_root, true, true
}
// run_build implements `brolang build [root]`: it reads build.bro and compiles
// the configured program package.
// run_build implements `brolang build [root]`: it reads the build config and
// compiles the configured program package.
run_build :: proc(root: string) -> int {
project_root, owned, found := project_root_for_command(root, "build")
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) {
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
}
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)
}
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 {
delete(current, allocator)
return "", false
@@ -212,6 +213,7 @@ find_build_root_from :: proc(start: string, allocator := context.allocator) -> (
if found {
return current, true
}
}
if current == "/" {
delete(current, allocator)
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
// other than the benign "missing or unusable main function" (build.bro has 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 reports whether checking a build config produced any
// diagnostic other than the benign "missing or unusable main function" (build
// configs have no main by design; that one is emitted with an empty span).
build_config_has_errors :: proc(diagnostics: ^source.Diagnostics) -> bool {
for item in diagnostics.items {
if item.span == (source.Span{}) && item.message == "missing or unusable main function" {
continue
@@ -255,12 +257,12 @@ extract_build_config :: proc(m: ^hir.Module, symbols: ^symbol.Table) -> (BuildCo
}
}
if !found {
fmt.eprintln("build.bro: missing top-level 'config' constant")
fmt.eprintln("build config: missing top-level 'config' constant")
return {}, false
}
root := unwrap_coercions(m, config_expr)
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
}
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[:]
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)
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 {
#partial switch kind(value, store) {
case .Scalar:
return u64(bits(value, selected)/8)
return u64((bits(value, selected)+7)/8)
case .Pointer:
return u64(target.pointer_bits(selected)/8)
case .Slice:
+100 -3
View File
@@ -1738,6 +1738,16 @@ layout_builtins_compile_and_run :: proc(t: ^testing.T) {
y u8
}
Bool_First :: struct {
flag bool
byte u8
}
Bool_Last :: struct {
byte u8
flag bool
}
Opaque :: opaque
Color :: enum {
red
@@ -1755,6 +1765,20 @@ buffer func($T type) [sizeof!(T)]u8 {
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 {
bytes [_]u8 :: buffer(i32)
if (needs_usize(SIZE_GLOBAL) != 4) return 1
@@ -1771,6 +1795,15 @@ main func() i32 {
if (alignof!(Point) != 4) return 12
if (sizeof!(UserID) != 4) return 13
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
}
`
@@ -5560,19 +5593,27 @@ init_project_creates_missing_layout_and_preserves_existing_files :: proc(t: ^tes
}
@(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"
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)
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)
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)
defer delete(found)
testing.expect(t, ok)
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)
@@ -10460,6 +10501,62 @@ cross_package_recursive_specialization_reaches_fixed_point :: proc(t: ^testing.T
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)
imported_main_does_not_satisfy_root_main_requirement :: proc(t: ^testing.T) {
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]>]...",
)
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(
" 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(
" brolang new <project-path>",
+5 -4
View File
@@ -1,14 +1,15 @@
# Build configuration surface for `brolang build` (v0).
#
# A project's `build.bro` imports this module and declares a top-level constant
# named `config` of type `BuildConfig`. `brolang build [root]` type-checks
# build.bro, reads the config, and writes root/build/name.
# A project's `build.bro` or `build.hon` imports this module and declares a
# top-level constant named `config` of type `BuildConfig`. `brolang build
# [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
# address-of an array literal (`&["raylib"]`) and default to empty.
BuildConfig :: struct {
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)
lib_paths [][]u8 = &[] # library search directories (-L)
includes [][]u8 = &[] # C include directories (-I)
View File