land arity-from-header: the runtime declares its own surface
This commit is contained in:
@@ -1000,12 +1000,17 @@ fn cg_expr(expr: Map<String, Any>) -> String {
|
|||||||
// work. Caught by capability_query.sh, not by reading the code.
|
// work. Caught by capability_query.sh, not by reading the code.
|
||||||
record_call("program", "is_kind:" + state_get("__program_kind"))
|
record_call("program", "is_kind:" + state_get("__program_kind"))
|
||||||
record_call(state_get("__cg_current_fn"), fn_name)
|
record_call(state_get("__cg_current_fn"), fn_name)
|
||||||
|
// Record the ARITY at the call site. The compiler knows how many
|
||||||
|
// arguments were written; whether that is correct is a question
|
||||||
|
// about the runtime's surface, and the runtime already declares its
|
||||||
|
// surface in el_runtime.h. A table inside the emitter is a second
|
||||||
|
// copy of that header, maintained by hand.
|
||||||
|
record_call(fn_name, "arity:" + native_int_to_str(native_list_len(args)))
|
||||||
// Arity check against the builtin table - refuse, with a clear
|
// Arity check against the builtin table - refuse, with a clear
|
||||||
// El-source message, when a known builtin gets the wrong arg
|
// El-source message, when a known builtin gets the wrong arg
|
||||||
// count (e.g. `http_serve(port)` instead of `http_serve(port,
|
// count (e.g. `http_serve(port)` instead of `http_serve(port,
|
||||||
// handler)`). User-defined fns and variadic builtins pass
|
// handler)`). User-defined fns and variadic builtins pass
|
||||||
// through (builtin_arity returns -1).
|
// through (builtin_arity returns -1).
|
||||||
arity_check_call(fn_name, arity)
|
|
||||||
// sleep(Duration) - Phase 1 of the typed-time work. When the
|
// sleep(Duration) - Phase 1 of the typed-time work. When the
|
||||||
// single arg is provably a Duration we lower to el_sleep_duration
|
// single arg is provably a Duration we lower to el_sleep_duration
|
||||||
// so the runtime sees nanos directly. Existing sleep() callers
|
// so the runtime sees nanos directly. Existing sleep() callers
|
||||||
@@ -2544,400 +2549,15 @@ fn emit_time_violations() -> Void {
|
|||||||
// dynamic dispatch) return -1 -> no check. A mismatch records a violation
|
// dynamic dispatch) return -1 -> no check. A mismatch records a violation
|
||||||
// in process state, which emit_arity_violations() turns into #error
|
// in process state, which emit_arity_violations() turns into #error
|
||||||
// directives at the top of the generated C.
|
// directives at the top of the generated C.
|
||||||
fn builtin_arity(name: String) -> Int {
|
|
||||||
// I/O
|
|
||||||
if str_eq(name, "println") { return 1 }
|
|
||||||
if str_eq(name, "print") { return 1 }
|
|
||||||
if str_eq(name, "readline") { return 0 }
|
|
||||||
// LSP seed primitives
|
|
||||||
if str_eq(name, "__read_n") { return 1 }
|
|
||||||
if str_eq(name, "__print_raw") { return 1 }
|
|
||||||
// Test-registry accessors. These are not runtime builtins — they are
|
|
||||||
// GENERATED into the same translation unit by the --test path below, one
|
|
||||||
// set per test binary. They are declared here so the El-side runner in
|
|
||||||
// runtime/eltest.el can call them with a known arity.
|
|
||||||
if str_eq(name, "__el_reg_count") { return 0 }
|
|
||||||
if str_eq(name, "__el_reg_name") { return 1 }
|
|
||||||
if str_eq(name, "__el_reg_invoke") { return 1 }
|
|
||||||
if str_eq(name, "__el_reg_last_ns") { return 0 }
|
|
||||||
if str_eq(name, "__el_reg_msg") { return 0 }
|
|
||||||
if str_eq(name, "__el_reg_asserts") { return 0 }
|
|
||||||
if str_eq(name, "__el_opt_json") { return 0 }
|
|
||||||
// String
|
|
||||||
if str_eq(name, "el_str_concat") { return 2 }
|
|
||||||
if str_eq(name, "str_eq") { return 2 }
|
|
||||||
if str_eq(name, "str_starts_with") { return 2 }
|
|
||||||
if str_eq(name, "str_ends_with") { return 2 }
|
|
||||||
if str_eq(name, "str_len") { return 1 }
|
|
||||||
if str_eq(name, "str_concat") { return 2 }
|
|
||||||
if str_eq(name, "int_to_str") { return 1 }
|
|
||||||
if str_eq(name, "str_to_int") { return 1 }
|
|
||||||
if str_eq(name, "str_slice") { return 3 }
|
|
||||||
if str_eq(name, "str_contains") { return 2 }
|
|
||||||
if str_eq(name, "str_replace") { return 3 }
|
|
||||||
if str_eq(name, "str_to_upper") { return 1 }
|
|
||||||
if str_eq(name, "str_to_lower") { return 1 }
|
|
||||||
if str_eq(name, "str_trim") { return 1 }
|
|
||||||
if str_eq(name, "str_index_of") { return 2 }
|
|
||||||
if str_eq(name, "str_split") { return 2 }
|
|
||||||
if str_eq(name, "str_char_at") { return 2 }
|
|
||||||
if str_eq(name, "str_char_code") { return 2 }
|
|
||||||
if str_eq(name, "str_pad_left") { return 3 }
|
|
||||||
if str_eq(name, "str_pad_right") { return 3 }
|
|
||||||
if str_eq(name, "str_format") { return 2 }
|
|
||||||
if str_eq(name, "str_lower") { return 1 }
|
|
||||||
if str_eq(name, "str_upper") { return 1 }
|
|
||||||
// Text-processing primitives (Phase 1)
|
|
||||||
if str_eq(name, "str_count") { return 2 }
|
|
||||||
if str_eq(name, "str_count_chars") { return 1 }
|
|
||||||
if str_eq(name, "str_count_bytes") { return 1 }
|
|
||||||
if str_eq(name, "str_count_lines") { return 1 }
|
|
||||||
if str_eq(name, "str_count_words") { return 1 }
|
|
||||||
if str_eq(name, "str_count_letters") { return 1 }
|
|
||||||
if str_eq(name, "str_count_digits") { return 1 }
|
|
||||||
if str_eq(name, "str_index_of_all") { return 2 }
|
|
||||||
if str_eq(name, "str_last_index_of") { return 2 }
|
|
||||||
if str_eq(name, "str_find_chars") { return 2 }
|
|
||||||
if str_eq(name, "str_repeat") { return 2 }
|
|
||||||
if str_eq(name, "str_reverse") { return 1 }
|
|
||||||
if str_eq(name, "str_strip_prefix") { return 2 }
|
|
||||||
if str_eq(name, "str_strip_suffix") { return 2 }
|
|
||||||
if str_eq(name, "str_strip_chars") { return 2 }
|
|
||||||
if str_eq(name, "str_lstrip") { return 1 }
|
|
||||||
if str_eq(name, "str_rstrip") { return 1 }
|
|
||||||
if str_eq(name, "is_letter") { return 1 }
|
|
||||||
if str_eq(name, "is_digit") { return 1 }
|
|
||||||
if str_eq(name, "is_alphanumeric") { return 1 }
|
|
||||||
if str_eq(name, "is_whitespace") { return 1 }
|
|
||||||
if str_eq(name, "is_punctuation") { return 1 }
|
|
||||||
if str_eq(name, "is_uppercase") { return 1 }
|
|
||||||
if str_eq(name, "is_lowercase") { return 1 }
|
|
||||||
if str_eq(name, "str_split_lines") { return 1 }
|
|
||||||
if str_eq(name, "str_split_chars") { return 1 }
|
|
||||||
if str_eq(name, "str_split_n") { return 3 }
|
|
||||||
if str_eq(name, "str_join") { return 2 }
|
|
||||||
// HTML sanitizer
|
|
||||||
if str_eq(name, "el_html_sanitize") { return 2 }
|
|
||||||
// Math
|
|
||||||
if str_eq(name, "el_abs") { return 1 }
|
|
||||||
if str_eq(name, "el_max") { return 2 }
|
|
||||||
if str_eq(name, "el_min") { return 2 }
|
|
||||||
// List
|
|
||||||
if str_eq(name, "el_list_len") { return 1 }
|
|
||||||
if str_eq(name, "el_list_get") { return 2 }
|
|
||||||
if str_eq(name, "el_list_append") { return 2 }
|
|
||||||
if str_eq(name, "el_list_empty") { return 0 }
|
|
||||||
if str_eq(name, "el_list_clone") { return 1 }
|
|
||||||
if str_eq(name, "list_push") { return 2 }
|
|
||||||
if str_eq(name, "list_push_front") { return 2 }
|
|
||||||
if str_eq(name, "list_join") { return 2 }
|
|
||||||
if str_eq(name, "list_range") { return 2 }
|
|
||||||
// Map
|
|
||||||
if str_eq(name, "el_get_field") { return 2 }
|
|
||||||
if str_eq(name, "el_map_get") { return 2 }
|
|
||||||
if str_eq(name, "el_map_set") { return 3 }
|
|
||||||
// HTTP
|
|
||||||
if str_eq(name, "http_get") { return 1 }
|
|
||||||
if str_eq(name, "http_post") { return 2 }
|
|
||||||
if str_eq(name, "http_post_json") { return 2 }
|
|
||||||
if str_eq(name, "http_get_with_headers") { return 2 }
|
|
||||||
if str_eq(name, "http_post_with_headers") { return 3 }
|
|
||||||
if str_eq(name, "http_post_form_auth") { return 3 }
|
|
||||||
if str_eq(name, "http_serve") { return 2 }
|
|
||||||
if str_eq(name, "http_set_handler") { return 1 }
|
|
||||||
// Seed primitives (__-prefix) — runtime/el_seed.c
|
|
||||||
if str_eq(name, "__str_len") { return 1 }
|
|
||||||
if str_eq(name, "__str_char_at") { return 2 }
|
|
||||||
if str_eq(name, "__str_alloc") { return 1 }
|
|
||||||
if str_eq(name, "__str_set_char") { return 3 }
|
|
||||||
if str_eq(name, "__str_cmp") { return 2 }
|
|
||||||
if str_eq(name, "__str_ncmp") { return 3 }
|
|
||||||
if str_eq(name, "__str_concat_raw") { return 2 }
|
|
||||||
if str_eq(name, "__str_slice_raw") { return 3 }
|
|
||||||
if str_eq(name, "__int_to_str") { return 1 }
|
|
||||||
if str_eq(name, "__str_to_int") { return 1 }
|
|
||||||
if str_eq(name, "__float_to_str") { return 1 }
|
|
||||||
if str_eq(name, "__str_to_float") { return 1 }
|
|
||||||
if str_eq(name, "__println") { return 1 }
|
|
||||||
if str_eq(name, "__print") { return 1 }
|
|
||||||
if str_eq(name, "__readline") { return 0 }
|
|
||||||
if str_eq(name, "__fs_read") { return 1 }
|
|
||||||
if str_eq(name, "__fs_write") { return 2 }
|
|
||||||
if str_eq(name, "__fs_exists") { return 1 }
|
|
||||||
if str_eq(name, "__fs_list_raw") { return 1 }
|
|
||||||
if str_eq(name, "__fs_mkdir") { return 1 }
|
|
||||||
if str_eq(name, "__fs_write_bytes") { return 3 }
|
|
||||||
if str_eq(name, "__http_do") { return 5 }
|
|
||||||
if str_eq(name, "__http_do_map") { return 5 }
|
|
||||||
if str_eq(name, "__http_do_to_file") { return 5 }
|
|
||||||
if str_eq(name, "__http_serve") { return 2 }
|
|
||||||
if str_eq(name, "__http_serve_v2") { return 2 }
|
|
||||||
if str_eq(name, "__http_response") { return 3 }
|
|
||||||
if str_eq(name, "__thread_create") { return 2 }
|
|
||||||
if str_eq(name, "__thread_join") { return 1 }
|
|
||||||
if str_eq(name, "__mutex_new") { return 0 }
|
|
||||||
if str_eq(name, "__mutex_lock") { return 1 }
|
|
||||||
if str_eq(name, "__mutex_unlock") { return 1 }
|
|
||||||
if str_eq(name, "__exec") { return 1 }
|
|
||||||
if str_eq(name, "__exec_bg") { return 1 }
|
|
||||||
if str_eq(name, "__env_get") { return 1 }
|
|
||||||
if str_eq(name, "__args_json") { return 0 }
|
|
||||||
if str_eq(name, "__exit_program") { return 1 }
|
|
||||||
if str_eq(name, "__time_now_ns") { return 0 }
|
|
||||||
if str_eq(name, "__sleep_ms") { return 1 }
|
|
||||||
if str_eq(name, "__uuid_v4") { return 0 }
|
|
||||||
if str_eq(name, "__sqrt_f") { return 1 }
|
|
||||||
if str_eq(name, "__log_f") { return 1 }
|
|
||||||
if str_eq(name, "__ln_f") { return 1 }
|
|
||||||
if str_eq(name, "__sin_f") { return 1 }
|
|
||||||
if str_eq(name, "__cos_f") { return 1 }
|
|
||||||
if str_eq(name, "__pi_f") { return 0 }
|
|
||||||
if str_eq(name, "__state_set") { return 2 }
|
|
||||||
if str_eq(name, "__state_get") { return 1 }
|
|
||||||
if str_eq(name, "__state_del") { return 1 }
|
|
||||||
if str_eq(name, "__state_keys") { return 0 }
|
|
||||||
if str_eq(name, "__html_sanitize") { return 2 }
|
|
||||||
if str_eq(name, "__url_encode") { return 1 }
|
|
||||||
if str_eq(name, "__url_decode") { return 1 }
|
|
||||||
if str_eq(name, "__json_get") { return 2 }
|
|
||||||
if str_eq(name, "__json_get_raw") { return 2 }
|
|
||||||
if str_eq(name, "__json_parse_map") { return 1 }
|
|
||||||
if str_eq(name, "__json_stringify_val") { return 1 }
|
|
||||||
if str_eq(name, "__json_array_len") { return 1 }
|
|
||||||
if str_eq(name, "__json_array_get") { return 2 }
|
|
||||||
if str_eq(name, "__json_array_get_string") { return 2 }
|
|
||||||
if str_eq(name, "__json_set") { return 3 }
|
|
||||||
if str_eq(name, "__engram_node") { return 3 }
|
|
||||||
if str_eq(name, "__engram_node_full") { return 8 }
|
|
||||||
if str_eq(name, "__engram_get_node") { return 1 }
|
|
||||||
if str_eq(name, "__engram_strengthen") { return 1 }
|
|
||||||
if str_eq(name, "__engram_forget") { return 1 }
|
|
||||||
if str_eq(name, "__engram_node_count") { return 0 }
|
|
||||||
if str_eq(name, "__engram_search") { return 2 }
|
|
||||||
if str_eq(name, "__engram_scan_nodes") { return 2 }
|
|
||||||
if str_eq(name, "__engram_connect") { return 4 }
|
|
||||||
if str_eq(name, "__engram_edge_between") { return 2 }
|
|
||||||
if str_eq(name, "__engram_neighbors") { return 1 }
|
|
||||||
if str_eq(name, "__engram_neighbors_filtered") { return 3 }
|
|
||||||
if str_eq(name, "__engram_activate") { return 2 }
|
|
||||||
if str_eq(name, "__engram_activate_json") { return 2 }
|
|
||||||
if str_eq(name, "__engram_op_assert_json") { return 2 }
|
|
||||||
if str_eq(name, "__engram_node_full_in") { return 9 }
|
|
||||||
if str_eq(name, "__engram_connect_in") { return 5 }
|
|
||||||
if str_eq(name, "__engram_scan_nodes_json") { return 2 }
|
|
||||||
if str_eq(name, "__engram_edges_json") { return 2 }
|
|
||||||
if str_eq(name, "__engram_pool_stats_json") { return 0 }
|
|
||||||
if str_eq(name, "__el_alloc_count") { return 0 }
|
|
||||||
if str_eq(name, "__el_alloc_bytes") { return 0 }
|
|
||||||
if str_eq(name, "__el_peak_rss") { return 0 }
|
|
||||||
if str_eq(name, "__generate") { return 1 }
|
|
||||||
// Filesystem
|
|
||||||
if str_eq(name, "fs_read") { return 1 }
|
|
||||||
if str_eq(name, "fs_write") { return 2 }
|
|
||||||
if str_eq(name, "fs_list") { return 1 }
|
|
||||||
if str_eq(name, "fs_size") { return 1 }
|
|
||||||
if str_eq(name, "fs_read_b64_chunk") { return 3 }
|
|
||||||
// JSON
|
|
||||||
if str_eq(name, "json_get") { return 2 }
|
|
||||||
if str_eq(name, "json_parse") { return 1 }
|
|
||||||
if str_eq(name, "json_stringify") { return 1 }
|
|
||||||
if str_eq(name, "json_get_string") { return 2 }
|
|
||||||
if str_eq(name, "json_get_int") { return 2 }
|
|
||||||
if str_eq(name, "json_get_float") { return 2 }
|
|
||||||
if str_eq(name, "json_get_bool") { return 2 }
|
|
||||||
if str_eq(name, "json_get_raw") { return 2 }
|
|
||||||
if str_eq(name, "json_set") { return 3 }
|
|
||||||
if str_eq(name, "json_array_len") { return 1 }
|
|
||||||
// Time
|
|
||||||
if str_eq(name, "time_now") { return 0 }
|
|
||||||
if str_eq(name, "time_now_utc") { return 0 }
|
|
||||||
if str_eq(name, "sleep_secs") { return 1 }
|
|
||||||
if str_eq(name, "sleep_ms") { return 1 }
|
|
||||||
if str_eq(name, "time_format") { return 2 }
|
|
||||||
if str_eq(name, "time_to_parts") { return 1 }
|
|
||||||
if str_eq(name, "time_from_parts") { return 3 }
|
|
||||||
if str_eq(name, "time_add") { return 3 }
|
|
||||||
if str_eq(name, "time_diff") { return 3 }
|
|
||||||
// UUID
|
|
||||||
if str_eq(name, "uuid_new") { return 0 }
|
|
||||||
if str_eq(name, "uuid_v4") { return 0 }
|
|
||||||
// Env / state
|
|
||||||
if str_eq(name, "env") { return 1 }
|
|
||||||
if str_eq(name, "state_set") { return 2 }
|
|
||||||
if str_eq(name, "state_get") { return 1 }
|
|
||||||
if str_eq(name, "state_del") { return 1 }
|
|
||||||
if str_eq(name, "state_keys") { return 0 }
|
|
||||||
// Float
|
|
||||||
if str_eq(name, "float_to_str") { return 1 }
|
|
||||||
if str_eq(name, "int_to_float") { return 1 }
|
|
||||||
if str_eq(name, "float_to_int") { return 1 }
|
|
||||||
if str_eq(name, "format_float") { return 2 }
|
|
||||||
if str_eq(name, "decimal_round") { return 2 }
|
|
||||||
if str_eq(name, "str_to_float") { return 1 }
|
|
||||||
// Math (Float)
|
|
||||||
if str_eq(name, "math_sqrt") { return 1 }
|
|
||||||
if str_eq(name, "math_log") { return 1 }
|
|
||||||
if str_eq(name, "math_ln") { return 1 }
|
|
||||||
if str_eq(name, "math_sin") { return 1 }
|
|
||||||
if str_eq(name, "math_cos") { return 1 }
|
|
||||||
if str_eq(name, "math_pi") { return 0 }
|
|
||||||
// Bool
|
|
||||||
if str_eq(name, "bool_to_str") { return 1 }
|
|
||||||
// Process
|
|
||||||
if str_eq(name, "exit_program") { return 1 }
|
|
||||||
// Process info
|
|
||||||
if str_eq(name, "getpid_now") { return 0 }
|
|
||||||
// stdout redirect (used by elc post-processing)
|
|
||||||
if str_eq(name, "stdout_to_file") { return 1 }
|
|
||||||
if str_eq(name, "stdout_restore") { return 0 }
|
|
||||||
// Subprocess execution
|
|
||||||
if str_eq(name, "exec_command") { return 1 }
|
|
||||||
if str_eq(name, "exec_capture") { return 1 }
|
|
||||||
if str_eq(name, "exec") { return 1 }
|
|
||||||
if str_eq(name, "exec_bg") { return 1 }
|
|
||||||
// CGI / DHARMA
|
|
||||||
if str_eq(name, "dharma_connect") { return 1 }
|
|
||||||
if str_eq(name, "dharma_send") { return 2 }
|
|
||||||
if str_eq(name, "dharma_activate") { return 1 }
|
|
||||||
if str_eq(name, "dharma_emit") { return 2 }
|
|
||||||
if str_eq(name, "dharma_field") { return 1 }
|
|
||||||
if str_eq(name, "dharma_strengthen") { return 2 }
|
|
||||||
if str_eq(name, "dharma_relationship") { return 1 }
|
|
||||||
if str_eq(name, "dharma_peers") { return 0 }
|
|
||||||
// Engram
|
|
||||||
if str_eq(name, "engram_node") { return 3 }
|
|
||||||
if str_eq(name, "engram_node_full") { return 8 }
|
|
||||||
if str_eq(name, "engram_get_node") { return 1 }
|
|
||||||
if str_eq(name, "engram_strengthen") { return 1 }
|
|
||||||
if str_eq(name, "engram_forget") { return 1 }
|
|
||||||
if str_eq(name, "engram_node_count") { return 0 }
|
|
||||||
if str_eq(name, "engram_search") { return 2 }
|
|
||||||
if str_eq(name, "engram_scan_nodes") { return 2 }
|
|
||||||
if str_eq(name, "engram_connect") { return 4 }
|
|
||||||
if str_eq(name, "engram_edge_between") { return 2 }
|
|
||||||
if str_eq(name, "engram_neighbors") { return 1 }
|
|
||||||
if str_eq(name, "engram_neighbors_filtered") { return 3 }
|
|
||||||
if str_eq(name, "engram_edge_count") { return 0 }
|
|
||||||
if str_eq(name, "engram_activate") { return 2 }
|
|
||||||
if str_eq(name, "engram_save") { return 1 }
|
|
||||||
if str_eq(name, "engram_load") { return 1 }
|
|
||||||
if str_eq(name, "engram_store_boot") { return 1 }
|
|
||||||
if str_eq(name, "engram_store_checkpoint") { return 0 }
|
|
||||||
if str_eq(name, "engram_store_close") { return 0 }
|
|
||||||
if str_eq(name, "engram_get_node_json") { return 1 }
|
|
||||||
if str_eq(name, "engram_get_node_by_label") { return 1 }
|
|
||||||
if str_eq(name, "engram_search_json") { return 2 }
|
|
||||||
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
|
||||||
if str_eq(name, "engram_edges_json") { return 2 }
|
|
||||||
if str_eq(name, "engram_pool_stats_json") { return 0 }
|
|
||||||
if str_eq(name, "el_alloc_count") { return 0 }
|
|
||||||
if str_eq(name, "el_alloc_bytes") { return 0 }
|
|
||||||
if str_eq(name, "el_peak_rss") { return 0 }
|
|
||||||
if str_eq(name, "el_black_box") { return 1 }
|
|
||||||
if str_eq(name, "engram_neighbors_json") { return 3 }
|
|
||||||
if str_eq(name, "engram_activate_json") { return 2 }
|
|
||||||
if str_eq(name, "engram_stats_json") { return 0 }
|
|
||||||
if str_eq(name, "engram_op_assert_json") { return 2 }
|
|
||||||
if str_eq(name, "engram_node_full_in") { return 9 }
|
|
||||||
if str_eq(name, "engram_connect_in") { return 5 }
|
|
||||||
// LLM
|
|
||||||
if str_eq(name, "llm_call") { return 2 }
|
|
||||||
if str_eq(name, "llm_call_system") { return 3 }
|
|
||||||
if str_eq(name, "llm_call_agentic") { return 4 }
|
|
||||||
if str_eq(name, "llm_vision") { return 4 }
|
|
||||||
if str_eq(name, "llm_models") { return 0 }
|
|
||||||
if str_eq(name, "llm_register_tool") { return 2 }
|
|
||||||
// Crypto
|
|
||||||
if str_eq(name, "sha256_hex") { return 1 }
|
|
||||||
if str_eq(name, "sha256_bytes") { return 1 }
|
|
||||||
if str_eq(name, "hmac_sha256_hex") { return 2 }
|
|
||||||
if str_eq(name, "hmac_sha256_bytes") { return 2 }
|
|
||||||
if str_eq(name, "base64_encode") { return 1 }
|
|
||||||
if str_eq(name, "base64_decode") { return 1 }
|
|
||||||
if str_eq(name, "base64url_encode") { return 1 }
|
|
||||||
if str_eq(name, "base64url_decode") { return 1 }
|
|
||||||
// Native VM aliases
|
|
||||||
if str_eq(name, "native_list_get") { return 2 }
|
|
||||||
if str_eq(name, "native_list_len") { return 1 }
|
|
||||||
if str_eq(name, "native_list_append") { return 2 }
|
|
||||||
if str_eq(name, "native_list_empty") { return 0 }
|
|
||||||
if str_eq(name, "native_list_clone") { return 1 }
|
|
||||||
if str_eq(name, "native_string_chars") { return 1 }
|
|
||||||
if str_eq(name, "native_int_to_str") { return 1 }
|
|
||||||
// Method-call aliases
|
|
||||||
if str_eq(name, "append") { return 2 }
|
|
||||||
if str_eq(name, "len") { return 1 }
|
|
||||||
if str_eq(name, "get") { return 2 }
|
|
||||||
if str_eq(name, "map_get") { return 2 }
|
|
||||||
if str_eq(name, "map_set") { return 3 }
|
|
||||||
// Threading seed primitives
|
|
||||||
if str_eq(name, "__thread_create") { return 2 }
|
|
||||||
if str_eq(name, "__thread_join") { return 1 }
|
|
||||||
if str_eq(name, "__mutex_new") { return 0 }
|
|
||||||
if str_eq(name, "__mutex_lock") { return 1 }
|
|
||||||
if str_eq(name, "__mutex_unlock") { return 1 }
|
|
||||||
// Channel seed primitives
|
|
||||||
if str_eq(name, "__channel_new") { return 1 }
|
|
||||||
if str_eq(name, "__channel_send") { return 2 }
|
|
||||||
if str_eq(name, "__channel_recv") { return 1 }
|
|
||||||
if str_eq(name, "__channel_try_recv") { return 1 }
|
|
||||||
if str_eq(name, "__channel_close") { return 1 }
|
|
||||||
// Arena mark/restore builtins
|
|
||||||
if str_eq(name, "el_arena_push") { return 0 }
|
|
||||||
if str_eq(name, "el_arena_pop") { return 1 }
|
|
||||||
// -1 sentinel: variadic / unknown / user-defined -> no check.
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arity_record_violation(fn_name: String, expected: Int, actual: Int) -> Bool {
|
|
||||||
let csv: String = state_get("__arity_violations")
|
|
||||||
if str_eq(csv, "") { let csv = "," }
|
|
||||||
// Encode as fn_name|expected|actual to recover all three at emit time.
|
|
||||||
let entry: String = fn_name + "|" + native_int_to_str(expected) + "|" + native_int_to_str(actual)
|
|
||||||
let key: String = "," + entry + ","
|
|
||||||
if str_contains(csv, key) { return true }
|
|
||||||
state_set("__arity_violations", csv + entry + ",")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the call's arity against the builtin table. Returns true (always)
|
// Validate the call's arity against the builtin table. Returns true (always)
|
||||||
// because cg_expr ignores the result; -1 from builtin_arity signals
|
// because cg_expr ignores the result; -1 from builtin_arity signals
|
||||||
// "no check possible" (variadic or user-defined). A mismatch is recorded
|
// "no check possible" (variadic or user-defined). A mismatch is recorded
|
||||||
// and surfaced as an #error at the bottom of the generated C, so cc fails
|
// and surfaced as an #error at the bottom of the generated C, so cc fails
|
||||||
// before it ever attempts to type-check the wrong call.
|
// before it ever attempts to type-check the wrong call.
|
||||||
fn arity_check_call(fn_name: String, actual: Int) -> Bool {
|
|
||||||
let expected: Int = builtin_arity(fn_name)
|
|
||||||
if expected < 0 { return true }
|
|
||||||
if expected == actual { return true }
|
|
||||||
arity_record_violation(fn_name, expected, actual)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit recorded arity violations as #error directives.
|
// Emit recorded arity violations as #error directives.
|
||||||
fn emit_arity_violations() -> Void {
|
|
||||||
let csv: String = state_get("__arity_violations")
|
|
||||||
if str_eq(csv, "") { return }
|
|
||||||
if str_eq(csv, ",") { return }
|
|
||||||
let n: Int = str_len(csv)
|
|
||||||
let i: Int = 1
|
|
||||||
while i < n {
|
|
||||||
let next_comma: Int = str_index_of(str_slice(csv, i, n), ",")
|
|
||||||
if next_comma < 0 { return }
|
|
||||||
let entry: String = str_slice(csv, i, i + next_comma)
|
|
||||||
let p1: Int = str_index_of(entry, "|")
|
|
||||||
if p1 > 0 {
|
|
||||||
let fn_name: String = str_slice(entry, 0, p1)
|
|
||||||
let rest: String = str_slice(entry, p1 + 1, str_len(entry))
|
|
||||||
let p2: Int = str_index_of(rest, "|")
|
|
||||||
if p2 > 0 {
|
|
||||||
let exp_s: String = str_slice(rest, 0, p2)
|
|
||||||
let act_s: String = str_slice(rest, p2 + 1, str_len(rest))
|
|
||||||
emit_line("#error \"arity error: '" + fn_name + "' takes " + exp_s + " arguments, but called with " + act_s + "\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let i = i + next_comma + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_int_name(name: String) -> Bool {
|
fn add_int_name(name: String) -> Bool {
|
||||||
let csv: String = state_get("__int_names")
|
let csv: String = state_get("__int_names")
|
||||||
@@ -3586,7 +3206,6 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
state_set("__program_kind", kind)
|
state_set("__program_kind", kind)
|
||||||
// Clear capability-violation accumulator from any prior compile.
|
// Clear capability-violation accumulator from any prior compile.
|
||||||
// Clear arity-violation accumulator from any prior compile.
|
// Clear arity-violation accumulator from any prior compile.
|
||||||
state_set("__arity_violations", "")
|
|
||||||
// Clear temporal-type-violation accumulator from any prior compile.
|
// Clear temporal-type-violation accumulator from any prior compile.
|
||||||
state_set("__time_violations", "")
|
state_set("__time_violations", "")
|
||||||
|
|
||||||
@@ -3853,7 +3472,6 @@ fn codegen(stmts: [Map<String, Any>], source: String) -> String {
|
|||||||
// Same for builtin-arity violations: cc halts on the first #error,
|
// Same for builtin-arity violations: cc halts on the first #error,
|
||||||
// so a misuse of a known builtin (wrong arg count) fails the build
|
// so a misuse of a known builtin (wrong arg count) fails the build
|
||||||
// with a clear message naming the builtin and its expected arity.
|
// with a clear message naming the builtin and its expected arity.
|
||||||
emit_arity_violations()
|
|
||||||
// Temporal-type violations (Instant + Instant, Duration + Int, -).
|
// Temporal-type violations (Instant + Instant, Duration + Int, -).
|
||||||
emit_time_violations()
|
emit_time_violations()
|
||||||
|
|
||||||
@@ -4364,7 +3982,6 @@ fn emit_streaming_preamble(sigs: [Map<String, Any>], source: String) -> Void {
|
|||||||
if cgi_count >= 1 { let kind = "cgi" }
|
if cgi_count >= 1 { let kind = "cgi" }
|
||||||
if svc_count >= 1 { let kind = "service" }
|
if svc_count >= 1 { let kind = "service" }
|
||||||
state_set("__program_kind", kind)
|
state_set("__program_kind", kind)
|
||||||
state_set("__arity_violations", "")
|
|
||||||
state_set("__time_violations", "")
|
state_set("__time_violations", "")
|
||||||
|
|
||||||
emit_line("#include <stdint.h>")
|
emit_line("#include <stdint.h>")
|
||||||
@@ -4888,7 +4505,6 @@ fn codegen_streaming(tokens: [Any], sigs: [Map<String, Any>], source: String) ->
|
|||||||
emit_line(" return 0;")
|
emit_line(" return 0;")
|
||||||
emit_line("}")
|
emit_line("}")
|
||||||
emit_blank()
|
emit_blank()
|
||||||
emit_arity_violations()
|
|
||||||
emit_time_violations()
|
emit_time_violations()
|
||||||
el_arena_pop(main_arena_mark)
|
el_arena_pop(main_arena_mark)
|
||||||
|
|
||||||
|
|||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Control for arity-from-header: the runtime declares its own surface, so the
|
||||||
|
# compiler does not carry a second copy of it.
|
||||||
|
set -uo pipefail
|
||||||
|
ELC="${1:?usage: arity_query.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)); }; }
|
||||||
|
|
||||||
|
printf 'fn main() {\n println("a", "b")\n}\n' > "$W/bad.el"
|
||||||
|
EL_RELATIONS_OUT="$W/r1.txt" "$ELC" "$W/bad.el" >/dev/null 2>&1
|
||||||
|
chk "the emitter does not adjudicate arity" "0" "$("$ELC" "$W/bad.el" 2>/dev/null | grep -c 'arity error')"
|
||||||
|
out=$("$LANG_DIR/tools/check/arity.sh" "$W/r1.txt" 2>&1); rc=$?
|
||||||
|
chk "a wrong-arity call is caught" "1" "$rc"
|
||||||
|
chk "the expected count is correct" "1" "$(echo "$out" | grep -c "takes 1 arguments, called with 2")"
|
||||||
|
|
||||||
|
printf 'fn main() {\n println("a")\n}\n' > "$W/ok.el"
|
||||||
|
EL_RELATIONS_OUT="$W/r2.txt" "$ELC" "$W/ok.el" >/dev/null 2>&1
|
||||||
|
"$LANG_DIR/tools/check/arity.sh" "$W/r2.txt" >/dev/null 2>&1
|
||||||
|
chk "a correct call is clean" "0" "$?"
|
||||||
|
|
||||||
|
# multi-line declarations must not parse as zero params
|
||||||
|
n=$("$LANG_DIR/tools/check/arity.sh" "$W/r2.txt" | grep -oE '[0-9]+ signatures')
|
||||||
|
chk "signatures parsed from the header" "503 signatures" "$n"
|
||||||
|
|
||||||
|
echo; echo " 5 assertions, $((5-F)) passed, $F failed"; exit $F
|
||||||
Executable
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# arity.sh — check call arity against the runtime's OWN declarations.
|
||||||
|
#
|
||||||
|
# codegen.el carried builtin_arity(): 344 lines, 300 entries, of which 243 were
|
||||||
|
# an exact duplicate of el_runtime.h. Measured drift between them was zero --
|
||||||
|
# the duplicate had been maintained correctly -- but 199 functions the runtime
|
||||||
|
# declares had NO entry, so calling them with the wrong argument count produced
|
||||||
|
# no El-level diagnostic at all. The table was not wrong, it was 40% incomplete.
|
||||||
|
#
|
||||||
|
# Deriving from the header fixes the coverage and makes drift impossible.
|
||||||
|
set -uo pipefail
|
||||||
|
REL="${1:?usage: arity.sh <relations-file> [runtime-header]}"
|
||||||
|
HDR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/runtime/el_runtime.h}"
|
||||||
|
[ -f "$REL" ] || exit 0
|
||||||
|
[ -f "$HDR" ] || { echo "no header: $HDR" >&2; exit 0; }
|
||||||
|
|
||||||
|
SIG=$(mktemp); trap 'rm -f "$SIG"' EXIT
|
||||||
|
# Declarations may span lines, so join continuations before parsing. Reading
|
||||||
|
# only the first line silently yields 0 params, and a checker that reports the
|
||||||
|
# wrong expected count is worse than no checker at all.
|
||||||
|
sed 's://.*::' "$HDR" | tr '\n' ' ' | sed 's:/\*[^*]*\*/: :g; s/;/;\n/g' | awk '
|
||||||
|
/el_val_t[[:space:]]+[a-z0-9_]+[[:space:]]*\(/ {
|
||||||
|
line=$0
|
||||||
|
match(line, /el_val_t[[:space:]]+[a-z0-9_]+/); name=substr(line,RSTART,RLENGTH)
|
||||||
|
sub(/el_val_t[[:space:]]+/,"",name)
|
||||||
|
match(line, /\(.*\)/); params=substr(line,RSTART+1,RLENGTH-2)
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/,"",params)
|
||||||
|
if (params=="void" || params=="") n=0
|
||||||
|
else { n=1; for(i=1;i<=length(params);i++) if(substr(params,i,1)==",") n++ }
|
||||||
|
if (line ~ /\.\.\./) n=-1
|
||||||
|
print name, n
|
||||||
|
}' | sort -u > "$SIG"
|
||||||
|
|
||||||
|
V=0
|
||||||
|
while read -r callee _ rest; do
|
||||||
|
[ "${rest#arity:}" = "$rest" ] && continue
|
||||||
|
actual="${rest#arity:}"
|
||||||
|
expected=$(awk -v n="$callee" '$1==n {print $2; exit}' "$SIG")
|
||||||
|
# 60 of 500 runtime decls carry a __ prefix: El's `println` is C's
|
||||||
|
# `__println`. codegen owns that mapping and its table carried BOTH keys.
|
||||||
|
# One rule covers every one of them.
|
||||||
|
[ -n "$expected" ] || expected=$(awk -v n="__$callee" '$1==n {print $2; exit}' "$SIG")
|
||||||
|
[ -n "$expected" ] || continue # not a runtime builtin
|
||||||
|
[ "$expected" = "-1" ] && continue # variadic
|
||||||
|
if [ "$actual" != "$expected" ]; then
|
||||||
|
printf "arity error: '%s' takes %s arguments, called with %s\n" "$callee" "$expected" "$actual"
|
||||||
|
V=$((V+1))
|
||||||
|
fi
|
||||||
|
done < <(sort -u "$REL")
|
||||||
|
|
||||||
|
[ "$V" -eq 0 ] && echo "arity: clean ($(wc -l < "$SIG" | tr -d ' ') signatures from the header)"
|
||||||
|
exit "$V"
|
||||||
Reference in New Issue
Block a user