From 0385e46295602e02c10272db20abda10b29efe3e Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 25 Oct 2026 22:00:38 +0100 Subject: [PATCH] test: complete scalar-function package acceptance --- README.md | 2 ++ TODO.md | 8 ++++---- tests/native.py | 29 ++++++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 84c40b1..e60d5d7 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ the backend (`@source/llvm`) consumes the existing typed IR, emits native functi run the bootstrap suite with `bro test`. after `bro build`, run `python3 tests/native.py` for executable-level interpreter comparisons at both `-O0` and `-O2`, including traps, source diagnostics, array ownership, calling conventions, and CLI error handling. +the scalar-function package acceptance gate is verified by the native suite: root and dependency sibling files, transitive/shared imports, same-spelled declarations with distinct signatures, file-local aliases and conflicts, missing packages/members, argument errors and dependency-file diagnostics. file-command checks cover `run`, `emit-llvm` and `build`, loading dependency siblings while excluding root siblings. + dependency provenance checks cover lexer, parser, resolver, checker and lowering diagnostics, plus overflow, bounds, local initialization and global-cycle traps through transitive calls. they assert originating filenames and line/byte-column positions, compare reached and untaken paths, and run generated executables with the source tree temporarily removed. the native bootstrap accepts either a no-argument `main` or `main proc(init process.Init)`. it supplies `init.io` and `init.args` from native startup `argc`/`argv`. startup scans each argument once into a heap-allocated array of borrowed zero-terminated slices (`process.Args.values: [][;0]u8`), without copying strings. the descriptor array and borrowed strings remain valid until `main` returns; startup then frees the descriptors, including on a fallible return. allocation failure exits with status `2`. `init.args.values.len` includes the executable name, and `init.args.values[index]` directly accesses an argument after a length guard. callers can supply their own `process.Args { values = slices[..] }` for testing. diff --git a/TODO.md b/TODO.md index db1aa08..3f6578d 100644 --- a/TODO.md +++ b/TODO.md @@ -11,10 +11,10 @@ and source provenance through `@source/compilation`, with compilation-wide AST n file commands retain single-file behavior; directory commands compile the package's production files and load its dependencies in separate scopes. -## next milestone: scalar-function packages +## completed milestone: scalar-function packages -compile and run a small multi-file directory package importing a second package -of scalar functions, through both the interpreter and native backend. +verified: compile and run a small multi-file directory package importing a second +package of scalar functions, through both the interpreter and native backend. using the existing standard library is a later acceptance gate: even `std/mem` needs records, pointers, generics and C calls. @@ -23,7 +23,7 @@ needs records, pointers, generics and C calls. - [x] finish postfix namespace selection and resolve file-local import aliases without injecting dependency names into the importing package; bare and aliased import declarations are already parsed for dependency discovery. - [x] reuse ordinary scalar signature checking, function values and IR calls for selected declarations; namespaces have no runtime representation. - [x] preserve originating filenames and spans in dependency diagnostics and native runtime traps; retain demand-driven diagnostic recovery. -- [ ] verify sibling source files, transitive/shared imports, same-spelled declarations in different packages, alias conflicts, missing packages/members, argument errors and dependency-file diagnostics. preserve existing single-file commands. +- [x] verify sibling source files, transitive/shared imports, same-spelled declarations in different packages, alias conflicts, missing packages/members, argument errors and dependency-file diagnostics. preserve existing single-file commands. visibility follows Bro's public-by-default contract; unsupported visibility attributes must be diagnosed, never ignored. this milestone does not include diff --git a/tests/native.py b/tests/native.py index 9eed34b..8038c69 100644 --- a/tests/native.py +++ b/tests/native.py @@ -364,6 +364,7 @@ sibling proc(n int) int { return n * 2 + right.helper() - 1 } put("left/main.hon", """ import "../shared" helper proc() int { return shared.helper() - 2 } +identity proc(n int) int { return n } main proc() int { return 99 } """) put("right/main.hon", """ @@ -371,11 +372,14 @@ import "../shared-link" helper proc() int { return shared.helper() - 1 } """) dependency = put("shared/main.hon", """ -value :: 3 helper proc() int { return value } -identity proc(n f64) f64 { return n } fail proc() int { return 9223372036854775807 + 1 } """) + put("shared/zfunctions.bro", """ +value :: 3 +identity proc(n f64) f64 { return n } +""") + put("shared/ignored.test.bro", "this is deliberately not valid source\n") (project / "shared-link").symlink_to(project / "shared", target_is_directory=True) app = main.parent check(app, executable, (b"42\n", None)) @@ -386,6 +390,13 @@ fail proc() int { return 9223372036854775807 + 1 } ('import "../left"\nmain proc() int { return left.helper() }', b"1\n", None), ('lib :: import "../shared"\nmain proc() f64 { f :: lib.identity\nreturn f(1.5) }', b"1.5\n", None), ('lib :: import "../shared"\nmain proc() int { return lib.value }', b"3\n", None), + (""" +import "../left" +import "../shared" +main proc() bool { + return left.identity(41) == 41 and shared.identity(1.5) == 1.5 +} +""", b"true\n", None), ('lib :: import "../shared"\nmain proc() int { return lib.absent() }', b"", b"unknown name"), ('lib :: import "../shared"\nmain proc() int { return helper() }', b"", b"unknown name"), ('import "../left"\nmain proc() int { return left.shared.helper() }', b"", b"unknown name"), @@ -426,7 +437,19 @@ fail proc() int { return 9223372036854775807 + 1 } check(app, executable, (b"", b"integer arithmetic overflow")) trapped = run(str(HONEY), "run", str(app)) assert str(dependency.resolve()).encode() + b":2:" in trapped.stderr, outcome(trapped) - return len(cases) + 4 + # File commands still load dependency siblings, but never their own siblings. + single = put("single/main.bro", """ +import "../shared" +main proc() f64 { return shared.identity(42) } +""") + put("single/poison.hon", """ +main proc() int { return missing } +""") + check(single, executable, (b"42\n", None)) + emitted = run(str(HONEY), "emit-llvm", str(single)) + assert emitted.returncode == 0 and emitted.stderr == b"", outcome(emitted) + check(single.parent, executable, (b"42\n", b"duplicate name")) + return len(cases) + 6 def check_dependency_provenance(temporary, executable):