Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ version number before tagging the release.

## [current]

### Fixed

- **A binary-package `import` in a non-entry module (a wrapper/adapter) did not
resolve.** The binary-import prepass (`prepare_binary_imports`) scanned only
the entry file's `import` lines, so an `import <binpkg>` inside a module the
entry pulls in transitively was never discovered, its interface stub never
synthesized, and the build failed with `unresolved import '<binpkg>'`. Only a
binary import written directly in the entry file worked. The prepass now walks
the full source-import graph (recursing into each imported source module, with
a visited-set for cycles) and synthesizes a stub for every binary-package
import it finds — so a wrapper module importing a binary package is as ordinary
as one importing another source module. The walk follows **dotted** package
imports too (`import a.b.c` → recurse into `a/b/c.ae`), not only flat ones, so
a binary import nested inside a dotted-path module (`import
harness.components.phone.validate`) is discovered — dotted package imports are
exactly what the `modules = "."` root export exists to support. POSIX-only,
same as the rest of the binary-import path. (Reported by the datastar-aether /
libphonenumber-ae line: `main → phone component → validate → phonenumber_ae`.)

## [0.694.0]

### Added
Expand Down
13 changes: 13 additions & 0 deletions tests/integration/binary_import/app_transitive.ae
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Entry file for the transitive binary-import case. It imports ONLY the source
// wrapper `wrap`; the `import gizmo` (a binary package) sits inside wrap.ae, not
// here. Before the transitive-prepass fix the binary-import prepass scanned only
// this entry file, never discovered wrap's `import gizmo`, and the build failed
// with "unresolved import 'gizmo'". The prepass now walks into wrap.ae.

import wrap

extern println(s: string)

main() {
println(wrap.wrapped_greet("world"))
}
39 changes: 37 additions & 2 deletions tests/integration/binary_import/test_binary_import.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ esac

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK" || true' EXIT
cp "$SCRIPT_DIR/gizmo.ae" "$SCRIPT_DIR/app.ae" "$WORK/"
cp "$SCRIPT_DIR/gizmo.ae" "$SCRIPT_DIR/app.ae" \
"$SCRIPT_DIR/wrap.ae" "$SCRIPT_DIR/app_transitive.ae" "$WORK/"
cd "$WORK"

fail() { echo " [FAIL] $1"; exit 1; }
Expand Down Expand Up @@ -54,4 +55,38 @@ OUT2="$(./app 2>&1)" || fail "built binary failed to run"
echo "$OUT2" | grep -q "hi world" || { echo "$OUT2"; fail "built binary: greet missing"; }
echo "$OUT2" | grep -q "intro" || { echo "$OUT2"; fail "built binary: builder missing"; }

echo " [PASS] binary_import: function export + builder DSL consumed from a precompiled .so (ae run + ae build)"
# 4. TRANSITIVE binary import: the `import gizmo` lives in a NON-ENTRY module
# (wrap.ae), and the entry (app_transitive.ae) imports only `wrap`. The
# binary-import prepass must walk the whole import graph, not just the entry
# file, to discover it — otherwise `import gizmo` is unresolved. This is the
# adapter/wrapper pattern (a project wraps the engine and imports the wrapper
# everywhere). gizmo.ae is already removed above, so `gizmo` resolves to the
# .so; wrap.ae is a source module the prepass must recurse into.
OUT3="$(AETHER_HOME="$ROOT" "$AE" run app_transitive.ae 2>run_t.log)" || {
echo "--- transitive run log:"; cat run_t.log
fail "transitive binary import: prepass did not walk into wrap.ae (import gizmo unresolved)"; }
echo "$OUT3" | grep -q "hi world" \
|| { echo "$OUT3"; fail "transitive binary import: wrap.wrapped_greet did not reach gizmo.greet"; }

# And it also links into a standalone binary through the wrapper.
if ! AETHER_HOME="$ROOT" "$AE" build app_transitive.ae -o app_t >build_t.log 2>&1; then
echo "--- transitive build log:"; cat build_t.log; fail "ae build app_transitive.ae"
fi
echo "$(./app_t 2>&1)" | grep -q "hi world" || fail "built transitive binary: greet missing"

# 5. TRANSITIVE through a DOTTED package import. The wrapper is reached as
# `import pkg.dotwrap` (a dotted package path), not a flat name. The graph
# walk must convert `pkg.dotwrap` -> `pkg/dotwrap.ae` and recurse, or the
# `import gizmo` inside it is never discovered — dotted package imports
# (what `modules = "."` exists to support) are idiomatic, and a real
# consumer's chain looks like `import harness.components.phone.validate`.
mkdir -p pkg
printf 'import gizmo\nexports(dgreet)\ndgreet(name: string) -> string { return gizmo.greet(name) }\n' > pkg/dotwrap.ae
printf 'import pkg.dotwrap\nextern println(s: string)\nmain() { println(dotwrap.dgreet("world")) }\n' > app_dotted.ae
OUT4="$(AETHER_HOME="$ROOT" "$AE" run app_dotted.ae 2>run_d.log)" || {
echo "--- dotted run log:"; cat run_d.log
fail "dotted transitive binary import: prepass did not follow 'import pkg.dotwrap' (import gizmo unresolved)"; }
echo "$OUT4" | grep -q "hi world" \
|| { echo "$OUT4"; fail "dotted transitive binary import: dotwrap.dgreet did not reach gizmo.greet"; }

echo " [PASS] binary_import: direct + transitive (flat + DOTTED wrapper) binary import, ae run + ae build"
12 changes: 12 additions & 0 deletions tests/integration/binary_import/wrap.ae
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// A NON-ENTRY wrapper/adapter module that imports the binary package `gizmo`.
// The transitive-prepass test drives `app_transitive.ae -> wrap -> gizmo(.so)`:
// the binary import lives here, not in the entry file, so it is only discovered
// if the binary-import prepass walks the whole import graph (not just the entry).

import gizmo

exports(wrapped_greet)

wrapped_greet(name: string) -> string {
return gizmo.greet(name)
}
187 changes: 133 additions & 54 deletions tools/ae.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading