EXPERIMENT: temporal types as data — and the pass that GREW the compiler

This block is structurally unlike the previous four. It does not only
adjudicate, it DISPATCHES: Instant + Duration must become el_instant_add_dur,
LocalDate + Duration must become el_local_date_add_dur. The emitted C depends on
the type answer, so it cannot move to a post-hoc query. Selecting which call to
emit is an emitter's actual job.

PREDICTIONS AND RESULTS
  P1 the block conflates dispatch with adjudication      TRUE
  P2 adjudication can move, dispatch cannot              TRUE
  P3 this pass shrinks codegen far less than the last    TRUE, and worse:
                                                         4513 -> 4537, it GREW
                                                         by 24 lines
  P4 the rules are affine algebra, closed by construction TRUE
  P5 no type propagation -- name tracking plus a
     hardcoded list of which builtins return which type   TRUE, 19 names

P3 is the honest result and it is not spun: moving 19 names into a data file
cost more lines than it saved, because a generic loader is larger than the
enumeration it replaces. The win is not line count. It is that adding a 20th
temporal builtin is now a one-line edit to signatures.rel instead of a compiler
change, and that the data is inspectable.

WHY THE HEADER CANNOT SUPPLY THIS, unlike arity: 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 exactly why the C boundary cannot say that
now() returns an Instant while unix_seconds() returns an Int. The El-level type
is real and the boundary erases it.

INCOMPLETE, and stated rather than hidden: P2 said adjudication could move to a
query. It has NOT. Violations still emit TIME_TYPE_ERROR inline from the
emitter. Only the type DATA moved. Moving the adjudication needs the operand
types recorded as relations, which is a further pass.

98/98 native, 4/4 temporal_signatures.sh, fixpoint ok.
This commit is contained in:
bigmerge
2026-08-17 09:25:18 -05:00
parent d9e301be6d
commit d2d89fcb60
3 changed files with 113 additions and 27 deletions
+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