land temporal signatures as data: the type table leaves, the dispatch stays
This commit is contained in:
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user