iteration-1: the compiler stops adjudicating #164

Merged
will.anderson merged 49 commits from iteration-1 into dev 2026-08-17 15:55:22 +00:00
3 changed files with 113 additions and 27 deletions
Showing only changes of commit e01e079bda - Show all commits
+52 -27
View File
@@ -2010,40 +2010,65 @@ fn is_float_call(call_expr: Map<String, Any>) -> Bool {
// Builtins that return an Instant. Used by is_instant_expr and the BinOp
// dispatch - `now() + 5.seconds` types as Instant only because we can see
// that now() is an Instant-returning Call.
fn is_instant_call(call_expr: Map<String, Any>) -> Bool {
let func = call_expr["func"]
let fk: String = func["expr"]
if !str_eq(fk, "Ident") { return false }
let name: String = func["name"]
if str_eq(name, "now") { return true }
if str_eq(name, "el_now_instant") { return true }
if str_eq(name, "unix_seconds") { return true }
if str_eq(name, "unix_millis") { return true }
if str_eq(name, "instant_from_iso8601") { return true }
if str_eq(name, "el_instant_add_dur") { return true }
if str_eq(name, "el_instant_sub_dur") { return true }
return false
// load_signatures El-level return types, read once from
// tools/check/signatures.rel. Previously 19 hardcoded names across
// is_instant_call and is_duration_call. The header cannot carry these: it
// declares everything as el_val_t, because El has one type.
fn load_signatures() -> Void {
if !str_eq(state_get("__sig_loaded"), "") { return }
state_set("__sig_loaded", "1")
let path: String = env("EL_SIGNATURES")
if str_eq(path, "") { let path = "tools/check/signatures.rel" }
if !fs_exists(path) { return }
let lines = str_split_lines(fs_read(path))
let n: Int = native_list_len(lines)
let i = 0
while i < n {
let line: String = str_trim(native_list_get(lines, i))
if !str_eq(line, "") {
if !str_starts_with(line, "#") {
let parts = str_split(line, " ")
let np: Int = native_list_len(parts)
let name: String = ""
let ty: String = ""
let seen = 0
let j = 0
while j < np {
let tok: String = str_trim(native_list_get(parts, j))
if !str_eq(tok, "") {
if seen == 0 { let name = tok }
if seen == 2 { let ty = tok }
let seen = seen + 1
}
let j = j + 1
}
if seen >= 3 { state_set("__sig_" + name, ty) }
}
}
let i = i + 1
}
}
// Builtins that return a Duration. Same role as is_instant_call.
fn is_duration_call(call_expr: Map<String, Any>) -> Bool {
let func = call_expr["func"]
fn call_returns(expr: Map<String, Any>, want: String) -> Bool {
load_signatures()
let func = expr["func"]
let fk: String = func["expr"]
if !str_eq(fk, "Ident") { return false }
let name: String = func["name"]
if str_eq(name, "el_duration_from_nanos") { return true }
if str_eq(name, "duration_seconds") { return true }
if str_eq(name, "duration_millis") { return true }
if str_eq(name, "duration_nanos") { return true }
if str_eq(name, "el_instant_diff") { return true }
if str_eq(name, "el_duration_add") { return true }
if str_eq(name, "el_duration_sub") { return true }
if str_eq(name, "el_duration_scale") { return true }
if str_eq(name, "el_duration_div") { return true }
if str_eq(name, "ttl_cache_age") { return true }
return false
return str_eq(state_get("__sig_" + name), want)
}
fn is_instant_call(expr: Map<String, Any>) -> Bool {
call_returns(expr, "Instant")
}
// Builtins that return a Duration. Same role as is_instant_call.
fn is_duration_call(expr: Map<String, Any>) -> Bool {
call_returns(expr, "Duration")
}
// Phase 1.5 - Calendar / CalendarTime / Rhythm / LocalDate / LocalTime /
// LocalDateTime / Zone are first-class boxed types. Each has its own name
// set in process state, populated from typed `let` bindings and parameter
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Control for temporal signatures as data.
#
# Unlike the other checks this one is read BY the compiler, not after it: the
# El-level return type decides which runtime wrapper to emit, and that is
# dispatch, not adjudication. What moved out is the data.
set -uo pipefail
ELC="${1:?usage: temporal_signatures.sh <elc>}"
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)); }; }
cd "$LANG_DIR"
printf 'fn main() {\n let a = now()\n let b = el_duration_from_nanos(5)\n let c = a + b\n println("ok")\n}\n' > "$W/i.el"
chk "an inferred Instant + Duration dispatches to the typed wrapper" \
"1" "$("$ELC" "$W/i.el" 2>/dev/null | grep -c el_instant_add_dur)"
chk "with no signature file, the type is unknown and it does not" \
"0" "$(EL_SIGNATURES=/nonexistent "$ELC" "$W/i.el" 2>/dev/null | grep -c el_instant_add_dur)"
printf 'fn main() {\n let a: Instant = now()\n let b: Instant = now()\n let c: Instant = a + b\n println("x")\n}\n' > "$W/b.el"
chk "Instant + Instant is still refused" \
"1" "$("$ELC" "$W/b.el" 2>/dev/null | grep -c 'TIME_TYPE_ERROR: Instant + Instant')"
printf 'fn main() {\n let a: Instant = now()\n let d: Duration = el_duration_from_nanos(1)\n let c: Instant = a + d\n println("x")\n}\n' > "$W/g.el"
chk "Instant + Duration is allowed" \
"0" "$("$ELC" "$W/g.el" 2>/dev/null | grep -c TIME_TYPE_ERROR)"
echo; echo " 4 assertions, $((4-F)) passed, $F failed"; exit $F
+33
View File
@@ -0,0 +1,33 @@
# signatures.rel — El-level return types for runtime builtins.
#
# el_runtime.h declares every builtin as returning el_val_t, because El has ONE
# type. That single type is why the whole seam is cheap, and it is also why the
# header cannot say that now() returns an Instant while unix_seconds() returns
# an Int. The El-level type is real and the C boundary erases it.
#
# So the compiler needs this, and unlike the other checks it needs it at
# EMISSION time: Instant + Duration must become el_instant_add_dur, and that is
# dispatch, not adjudication. What moved here is the DATA -- previously 19
# hardcoded names across two functions in codegen.el. What stays in the emitter
# is choosing which call to emit, which is an emitter's actual job.
#
# <builtin> returns <El type>
now returns Instant
el_now_instant returns Instant
unix_seconds returns Instant
unix_millis returns Instant
instant_from_iso8601 returns Instant
el_instant_add_dur returns Instant
el_instant_sub_dur returns Instant
el_duration_from_nanos returns Duration
duration_seconds returns Duration
duration_millis returns Duration
duration_nanos returns Duration
el_instant_diff returns Duration
el_duration_add returns Duration
el_duration_sub returns Duration
el_duration_scale returns Duration
el_duration_div returns Duration
ttl_cache_age returns Duration