From 79f6cb79850b5affd7735fcc418ede2242b46e5b Mon Sep 17 00:00:00 2001 From: bigmerge Date: Mon, 17 Aug 2026 09:54:23 -0500 Subject: [PATCH] ANSWER: if the partition is a neighbourhood, does linking survive? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The question is premature, and measuring says why. El's partition is a FILESYSTEM PATH, not a neighbourhood, and there is no namespacing at all. MEASURED import is textual inlining (resolve_imports), guarded against double inclusion by a __elc_imp__: state key when a .elh header exists the header is inlined instead and the .el is marked seen, so symbols resolve at C link time -- so linking IS real, delegated to C two modules defining `helper` emit two C functions into one translation unit So linking barely survives the PATH partition. Whether it survives a neighbourhood partition cannot be asked yet. A DIAGNOSTIC REGRESSION I CAUSED, found by asking this question. cc does catch the collision, but reports: error: redefinition of '__el_body_helper' error: redefinition of '__env_helper' error: redefinition of '__thunk_helper' error: redefinition of 'helper' The user's own function is FOURTH. The first three are generated symbols introduced by the unconditional-wrapper pass earlier today -- before it, there was one clear message. Repaired by catching the collision at El level instead: duplicate definition: 'helper' is defined 2 times — El has no namespacing, so imported modules share one global scope LIMIT, stated rather than hidden: textual inlining destroys file provenance. By the time codegen runs there is one source string, so the message can say WHICH name collides but not which files. Naming a.el and b.el needs provenance threaded through resolve_imports. 104/104 native, 4/4 definitions_query.sh, the compiler itself reports clean, fixpoint ok. --- lang/el-compiler/src/codegen.el | 6 +++++ lang/tests/integration/definitions_query.sh | 25 +++++++++++++++++++++ lang/tools/check/definitions.sh | 24 ++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100755 lang/tests/integration/definitions_query.sh create mode 100755 lang/tools/check/definitions.sh diff --git a/lang/el-compiler/src/codegen.el b/lang/el-compiler/src/codegen.el index 380e32a..288e1c2 100644 --- a/lang/el-compiler/src/codegen.el +++ b/lang/el-compiler/src/codegen.el @@ -2797,6 +2797,12 @@ fn params_to_env_args(params: [Any]) -> String { fn cg_fn(stmt: Map) -> Void { let fn_name: String = stmt["name"] state_set("__cg_current_fn", fn_name) + // Record every top-level definition. El has NO namespacing -- imports are + // textual inlining, so two modules defining the same name emit two C + // functions into one translation unit. cc catches it, but names the + // generated helpers first and the user's fn fourth. Recording it lets the + // collision be reported at El level, in El terms. + record_call(fn_name, "defines") // Emit which constructs this fn carries, so the prohibition check can be a // query over relations instead of a walk inside the emitter. let cdl = stmt["decorators"] diff --git a/lang/tests/integration/definitions_query.sh b/lang/tests/integration/definitions_query.sh new file mode 100755 index 0000000..2f31c3c --- /dev/null +++ b/lang/tests/integration/definitions_query.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Control for duplicate-definition detection. +# +# El has no namespacing: import is textual inlining, so two modules defining the +# same name emit two C functions into one translation unit. +set -uo pipefail +ELC="${1:?usage: definitions_query.sh }" +LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +W=$(mktemp -d); trap 'rm -rf "$W"' EXIT; F=0 +chk(){ [ "$2" = "$3" ] && printf ' ok %s\n' "$1" || { printf ' FAIL %s\n expected %s got %s\n' "$1" "$2" "$3"; F=$((F+1)); }; } + +printf 'fn helper() -> Int { return 1 }\n' > "$W/a.el" +printf 'fn helper() -> Int { return 2 }\n' > "$W/b.el" +printf 'import "a.el"\nimport "b.el"\nfn main() { println(int_to_str(helper())) }\n' > "$W/m.el" +EL_RELATIONS_OUT="$W/r.txt" "$ELC" "$W/m.el" >/dev/null 2>&1 +out=$("$LANG_DIR/tools/check/definitions.sh" "$W/r.txt" 2>&1); rc=$? +chk "a collision across modules is caught at El level" "1" "$rc" +chk "the colliding name is reported" "1" "$(echo "$out" | grep -c "'helper' is defined 2 times")" +chk "and the reason is given" "1" "$(echo "$out" | grep -c 'no namespacing')" + +printf 'fn only_once() -> Int { return 1 }\nfn main() { println(int_to_str(only_once())) }\n' > "$W/c.el" +EL_RELATIONS_OUT="$W/r2.txt" "$ELC" "$W/c.el" >/dev/null 2>&1 +"$LANG_DIR/tools/check/definitions.sh" "$W/r2.txt" >/dev/null 2>&1 +chk "a clean program exits 0" "0" "$?" +echo; echo " 4 assertions, $((4-F)) passed, $F failed"; exit $F diff --git a/lang/tools/check/definitions.sh b/lang/tools/check/definitions.sh new file mode 100755 index 0000000..b97c7f2 --- /dev/null +++ b/lang/tools/check/definitions.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# definitions.sh — catch duplicate top-level definitions at El level. +# +# El has no namespacing. `import` is textual inlining, so two modules defining +# the same name emit two C functions into one translation unit. cc does catch +# it, but reports the generated helpers (__el_body_f, __env_f, __thunk_f) before +# the user's own function, so the first three errors name symbols the user never +# wrote. +# +# LIMIT, stated rather than hidden: textual inlining destroys file provenance. +# By the time codegen runs there is one source string and no record of which +# file a definition came from, so this can say WHICH name collides but not which +# files. Naming the files needs provenance threaded through resolve_imports. +set -uo pipefail +REL="${1:?usage: definitions.sh }" +[ -f "$REL" ] || exit 0 +V=0 +while read -r name count; do + [ "$count" -gt 1 ] || continue + printf "duplicate definition: '%s' is defined %s times — El has no namespacing, so imported modules share one global scope\n" "$name" "$count" + V=$((V+1)) +done < <(grep ' calls defines$' "$REL" | awk '{print $1}' | sort | uniq -c | awk '{print $2, $1}') +[ "$V" -eq 0 ] && echo "definitions: clean" +exit "$V"