From 9c25f163858fe1f96da071d65b68ab22d2aa1e67 Mon Sep 17 00:00:00 2001 From: Tony Thayer-Osborne Date: Mon, 3 Aug 2026 10:48:24 -0700 Subject: [PATCH 1/2] feat(pkl): extract calls, branches, throws and decorators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pkl was wired end-to-end but shallow: on a real-world 172-file Pkl corpus the graph had 386 IMPORTS edges and 0 CALLS edges, because pkl_call_types was empty_types. Pkl has no dedicated call node. `unqualifiedAccessExpr` and `qualifiedAccessExpr` are the same node whether they are a call (`helper(a)`) or a bare property read (`host`) — the only discriminator is an `argumentList` child. extract_pkl_callee gates on that child, and is dispatched from extract_callee_name with an unconditional return: falling through to field-based or generic first-identifier resolution would mint a CALLS edge for every property read in every Pkl file, since a bare access expr's first child is an identifier. Callee resolution: helper(a) -> "helper" utils.fallback(a) -> "utils.fallback" (module-qualified; cbm.c shortens to the last dotted segment) s.trim().toLowerCase() -> "toLowerCase" (a receiver that is itself a call has parens in its text and is not prefixed) new Server { ... } -> "Server" (links to the class def) Also fills the other empty slots in the Pkl spec, all confirmed against real parse trees rather than guessed: typeAlias (class), importGlobClause / importExpr (import), ifExpr / whenGenerator / forGenerator (branch), throwExpr (throw), annotation (decorator). forGenerator is additionally registered in cbm_is_loop_node_type so `for (x in xs)` counts toward loop-nesting depth; the name is Pkl-unique so no other grammar collides. The Pkl repro test moves from the structural battery (dims 1-5) to the full callable battery (dims 1-8) and adds an inline negative assertion that bare property reads do NOT become CALLS edges — the regression the argumentList gate exists to prevent. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Tony Thayer-Osborne --- internal/cbm/extract_calls.c | 68 +++++++++++++++++++++++++ internal/cbm/helpers.c | 31 +++--------- internal/cbm/lang_specs.c | 22 +++++--- tests/repro/repro_grammar_config.c | 80 ++++++++++++++++++++++-------- 4 files changed, 151 insertions(+), 50 deletions(-) diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index c93e46a44..fe10dade1 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -1024,6 +1024,65 @@ static char *extract_nickel_callee(CBMArena *a, TSNode node, const char *source, return NULL; } +// Pkl: `unqualifiedAccessExpr` / `qualifiedAccessExpr` are the same node whether +// they are a call (`helper(a)`) or a bare property read (`host`) — the only +// discriminator is an `argumentList` child, so both are gated on it. For a +// qualified call the method name is the `identifier` child that is not the +// `receiver`; the receiver is prefixed only when it is itself a plain name +// (`utils.fallback(a)` -> "utils.fallback", module-qualified, which cbm.c +// shortens to the last dotted segment when resolving). A receiver that is itself +// a call must NOT be prefixed: `s.trim().toLowerCase()` -> "toLowerCase", since +// the receiver's text carries parens and would never resolve. +// `newExpr` resolves to its `declaredType` so `new Server {}` links to the class. +static char *extract_pkl_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { + if (strcmp(nk, "newExpr") == 0) { + // `new { ... }` with an inferred type has no declaredType child. + TSNode dt = cbm_find_child_by_kind(node, "declaredType"); + return ts_node_is_null(dt) ? NULL : cbm_node_text(a, dt, source); + } + + bool qualified = strcmp(nk, "qualifiedAccessExpr") == 0; + if (!qualified && strcmp(nk, "unqualifiedAccessExpr") != 0) { + return NULL; + } + // No argument list -> property read, not a call. + if (ts_node_is_null(cbm_find_child_by_kind(node, "argumentList"))) { + return NULL; + } + + TSNode recv = ts_node_child_by_field_name(node, TS_FIELD("receiver")); + TSNode name = (TSNode){0}; + uint32_t nc = ts_node_named_child_count(node); + for (uint32_t i = 0; i < nc; i++) { + TSNode child = ts_node_named_child(node, i); + if (!ts_node_is_null(recv) && ts_node_eq(child, recv)) { + continue; + } + if (strcmp(ts_node_type(child), "identifier") == 0) { + name = child; + break; + } + } + if (ts_node_is_null(name)) { + return NULL; + } + char *mn = cbm_node_text(a, name, source); + if (!mn || !mn[0]) { + return NULL; + } + if (!qualified || ts_node_is_null(recv)) { + return mn; + } + if (strcmp(ts_node_type(recv), "unqualifiedAccessExpr") == 0 && + ts_node_is_null(cbm_find_child_by_kind(recv, "argumentList"))) { + char *rt = cbm_node_text(a, recv, source); + if (rt && rt[0]) { + return cbm_arena_sprintf(a, "%s.%s", rt, mn); + } + } + return mn; +} + // Typst: a `call` node's callee is its `item` field (an ident), matching the // def-side resolution of `#let greet(name) = ...`. static char *extract_typst_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { @@ -1648,6 +1707,15 @@ static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, C } } + /* Pkl: resolve here and return unconditionally — the access-expr call node + * types double as plain property reads, so falling through to field-based or + * generic first-identifier resolution would mint a CALLS edge for every + * property read (a bare `host` has an `identifier` first child, which the + * generic fallback would happily emit). NULL here means "not a call". */ + if (lang == CBM_LANG_PKL) { + return extract_pkl_callee(a, node, source, ts_node_type(node)); + } + // Helm / Go templates: resolve `include "x"` / `template "x"` to the // referenced named template so it links to the define'd Function (#338). if (lang == CBM_LANG_GOTEMPLATE) { diff --git a/internal/cbm/helpers.c b/internal/cbm/helpers.c index 26fa55820..813e608a0 100644 --- a/internal/cbm/helpers.c +++ b/internal/cbm/helpers.c @@ -578,29 +578,14 @@ int cbm_count_branching(TSNode node, const char **branching_types) { // Loop node-type names across tree-sitter grammars, for loop-nesting depth. bool cbm_is_loop_node_type(const char *kind) { - static const char *const loops[] = {"for_statement", - "while_statement", - "do_statement", - "do_while_statement", - "for_in_statement", - "for_of_statement", - "for_each_statement", - "foreach_statement", - "enhanced_for_statement", - "for_range_loop", - "c_style_for_statement", - "for_expression", - "while_expression", - "loop_expression", - "while_let_expression", - "repeat_statement", - "repeat_while_statement", - "until", - "while_modifier", - "until_modifier", - "for", - "while", - NULL}; + static const char *const loops[] = { + "for_statement", "while_statement", "do_statement", "do_while_statement", + "for_in_statement", "for_of_statement", "for_each_statement", "foreach_statement", + "enhanced_for_statement", "for_range_loop", "c_style_for_statement", "for_expression", + "while_expression", "loop_expression", "while_let_expression", "repeat_statement", + "repeat_while_statement", + // Pkl: `for (x in xs) { ... }` inside an object body. + "forGenerator", "until", "while_modifier", "until_modifier", "for", "while", NULL}; for (const char *const *l = loops; *l; l++) { if (strcmp(kind, *l) == 0) { return true; diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 9dd1cdb37..90e4d37f5 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -1559,11 +1559,21 @@ static const char *tlaplus_branch_types[] = {"if_then_else", "case", NULL}; static const char *tlaplus_var_types[] = {"variable_declaration", NULL}; static const char *tlaplus_module_types[] = {"source_file", NULL}; static const char *pkl_func_types[] = {"classMethod", "objectMethod", NULL}; -static const char *pkl_class_types[] = {"clazz", NULL}; -static const char *pkl_import_types[] = {"importClause", "extendsOrAmendsClause", "extends", - "import", NULL}; +static const char *pkl_class_types[] = {"clazz", "typeAlias", NULL}; +static const char *pkl_import_types[] = { + "importClause", "importGlobClause", "importExpr", "extendsOrAmendsClause", + "extends", "import", NULL}; static const char *pkl_var_types[] = {"classProperty", "objectProperty", NULL}; static const char *pkl_module_types[] = {"module", NULL}; +/* Both access exprs double as plain property reads; extract_pkl_callee keeps + * only the ones carrying an argumentList. `newExpr` resolves to its type. */ +static const char *pkl_call_types[] = {"unqualifiedAccessExpr", "qualifiedAccessExpr", "newExpr", + NULL}; +/* Control-flow only, matching every other spec (short-circuit operators are + * deliberately excluded). `forGenerator` is also a loop — see helpers.c. */ +static const char *pkl_branch_types[] = {"ifExpr", "whenGenerator", "forGenerator", NULL}; +static const char *pkl_throw_types[] = {"throwExpr", NULL}; +static const char *pkl_decorator_types[] = {"annotation", NULL}; static const char *gomod_var_types[] = {"require_directive", "replace_directive", NULL}; static const char *gomod_import_types[] = {"require", NULL}; static const char *gomod_module_types[] = {"source_file", NULL}; @@ -2566,9 +2576,9 @@ static const CBMLangSpec lang_specs[CBM_LANG_COUNT] = { // CBM_LANG_PKL [CBM_LANG_PKL] = {CBM_LANG_PKL, pkl_func_types, pkl_class_types, empty_types, pkl_module_types, - empty_types, pkl_import_types, empty_types, empty_types, pkl_var_types, - empty_types, empty_types, NULL, empty_types, NULL, NULL, tree_sitter_pkl, - NULL}, + pkl_call_types, pkl_import_types, empty_types, pkl_branch_types, + pkl_var_types, empty_types, pkl_throw_types, NULL, pkl_decorator_types, NULL, + NULL, tree_sitter_pkl, NULL}, // CBM_LANG_GOMOD [CBM_LANG_GOMOD] = {CBM_LANG_GOMOD, empty_types, empty_types, empty_types, gomod_module_types, diff --git a/tests/repro/repro_grammar_config.c b/tests/repro/repro_grammar_config.c index 9b143cfe3..c76f811b1 100644 --- a/tests/repro/repro_grammar_config.c +++ b/tests/repro/repro_grammar_config.c @@ -45,7 +45,8 @@ * 6. calls-extracted : inv_has_call(r, callee) == 1. * Only asserted for languages that have non-empty * call_types: HCL (function_call), NICKEL (infix_expr), - * JSONNET (functioncall), STARLARK (call). + * JSONNET (functioncall), STARLARK (call), + * PKL (unqualified/qualifiedAccessExpr, newExpr). * * FULL-PIPELINE (rh_index_files -> cbm_store_t*, via inv_count_* store helpers): * 7. callable-sourcing : inv_count_calls_by_source(store,project,&mod,&call). @@ -85,11 +86,15 @@ * Dims 1-5 ("Class"). No calls. * XML -- class_types = element -> "Class". Dims 1-5 ("Class"). No calls. * PROPERTIES -- var_types = property -> "Variable". Dims 1-5 ("Variable"). No calls. - * PKL -- func_types = classMethod/objectMethod -> "Function"; - * class_types = clazz -> "Class"; var_types = classProperty/objectProperty. - * call_types = empty_types. Dims 1-5 ("Function", "Class"). No call dim. * * LANGUAGES WITH CALLABLES (dims 1-6 + R, and pipeline dims 7-8 where applicable): + * PKL -- func_types = classMethod/objectMethod -> "Function"; + * class_types = clazz/typeAlias -> "Class"; + * var_types = classProperty/objectProperty; + * call_types = unqualifiedAccessExpr/qualifiedAccessExpr/newExpr. + * Dims 1-8. The access-expr call types double as property reads, + * so extract_pkl_callee gates them on an `argumentList` child; + * the test adds an inline negative assertion for that gate. * HCL -- class_types = block -> "Class"; var_types = attribute; * call_types = function_call. Dims 1-6. No func_types so no pipeline * dim 7 (calls would be module-sourced with no Function anchor). @@ -766,39 +771,72 @@ TEST(repro_grammar_config_ron) { /* ── PKL ────────────────────────────────────────────────────────────────────── * Idiomatic PKL (Apple Pkl) module with a class definition - * (pkl_class_types = {"clazz"} -> "Class"), a method inside it + * (pkl_class_types = {"clazz", "typeAlias"} -> "Class"), methods inside it * (pkl_func_types = {"classMethod", "objectMethod"} -> "Function"), and * class properties (pkl_var_types = {"classProperty", "objectProperty"}). - * pkl_call_types = empty_types so no call extraction occurs. - * - * Dims asserted: 1-5 + R ("Class" for the class def, "Function" for the method). - * Dims 6-8 SKIPPED: call_types = empty_types in spec. - * Expected GREEN: dims 1-5. Dim 5 RED would indicate clazz->Class or - * classMethod->Function mapping is broken in the PKL grammar walker. + * pkl_call_types = {"unqualifiedAccessExpr", "qualifiedAccessExpr", "newExpr"}. + * + * Dims asserted: 1-8 (full battery) + R. + * Dim 6 GREEN: `makeUrl(host, port)` inside url() extracts callee "makeUrl". + * Dim 7 GREEN: every call site in the fixture is inside a classMethod body, so + * no CALLS edge is Module-sourced. (Real-world Pkl does call at module level; + * the fixture deliberately avoids it because dim 7 treats Module-sourced + * in-body calls as the enclosing-func gap.) + * Dim 8 GREEN: makeUrl and Server are both defined in-file, so neither the + * unqualified call nor the newExpr constructor edge dangles. + * + * PKL-SPECIFIC REGRESSION (asserted inline below): `unqualifiedAccessExpr` and + * `qualifiedAccessExpr` are the same node for a call and for a bare property + * read, so the interpolated `host` / `port` reads inside makeUrl must NOT be + * emitted as CALLS. extract_pkl_callee gates on an `argumentList` child; without + * that gate every property read in every Pkl file becomes a call edge. */ TEST(repro_grammar_config_pkl) { static const char src[] = "module cbm.Config\n" "\n" - "function makeUrl(host: String, port: Int): String = \"http://\\(host):\\(port)\"\n" + "typealias Port = Int\n" + "\n" + "function makeUrl(host: String, port: Port): String = \"http://\\(host):\\(port)\"\n" "\n" "class Server {\n" " host: String = \"localhost\"\n" - " port: Int = 8080\n" + " port: Port = 8080\n" " tls: Boolean = false\n" "\n" - " function url(): String = \"http://\\(host):\\(port)\"\n" - "}\n" + " function url(): String = makeUrl(host, port)\n" "\n" - "server = new Server {\n" - " host = \"0.0.0.0\"\n" - " port = 9000\n" + " function clone(): Server = new Server { host = host }\n" "}\n"; static const char bad[] = "module cbm.Config\nclass Server {\n host:"; - if (config_struct_battery("PKL", src, CBM_LANG_PKL, "config.pkl", - "Class", "Function") != 0) + if (config_callable_battery("PKL", src, CBM_LANG_PKL, "config.pkl", + "Function", "makeUrl") != 0) + return 1; + + /* Bare property reads must not be calls (see PKL-SPECIFIC REGRESSION above). */ + CBMFileResult *pr = inv_rx(src, CBM_LANG_PKL, "config.pkl"); + if (!pr) { + printf(" %sFAIL%s [PKL] inv_rx returned NULL\n", tf_red(), tf_reset()); + return 1; + } + int bogus = 0; + for (int i = 0; i < pr->calls.count; i++) { + const char *cn = pr->calls.items[i].callee_name; + if (cn && (strcmp(cn, "host") == 0 || strcmp(cn, "port") == 0 || + strcmp(cn, "tls") == 0)) { + bogus++; + } + } + cbm_free_result(pr); + if (bogus != 0) { + printf(" %sFAIL%s [PKL] property-read-not-call: %d bare property read(s) " + "emitted as a CALLS edge\n", tf_red(), tf_reset(), bogus); + return 1; + } + + if (config_robustness("PKL", bad, CBM_LANG_PKL, "config.pkl") != 0) return 1; - return config_robustness("PKL", bad, CBM_LANG_PKL, "config.pkl"); + return config_pipeline_battery("PKL", "config.pkl", src); } /* ── NICKEL ─────────────────────────────────────────────────────────────────── From 597985d210255e3a714d76d82183c90234ca5cdc Mon Sep 17 00:00:00 2001 From: Tony Thayer-Osborne Date: Wed, 5 Aug 2026 13:26:05 -0700 Subject: [PATCH 2/2] fix(pkl): register call nodes in the ledgers and bind Pkl declarations Merging main brought in three contracts this branch violated. repro_call_node_manifest requires a table row for every live call_node_types entry, so Pkl's unqualifiedAccessExpr/qualifiedAccessExpr (direct) and newExpr (constructor) each get one, with the partition totals moved to match. The historical-snapshot column is the audited ledger, not an archaeology record -- the suite hard-fails on a row that claims it was absent -- so a newly registered kind joins the snapshot with the change that registers it. repro_language_registry then requires every call-capable language to own exactly one call-argument matrix row, which turned out to be a real extractor gap rather than a bookkeeping entry. Pkl's grammar labels no production with a `name` field, so the generic declared-container rule never recognised a Pkl binding: method names, parameter names, and property names were each re-emitted as an ordinary read of themselves, which Go and Nickel do not do. Add a Pkl occurrence policy that binds the declared identifier of methodHeader, typedIdentifier, classProperty, objectProperty, clazz, and typeAlias, resolved against the nearest such container so annotations, defaults, and bodies stay reads. Recording the binding also supplies the lexical-shadow proof the matrix row asserts. Separately, a700bff1 gave the Windows helpers their own timeout floor but left the harness contract allowing the scheduler only eight seconds to finish refusing -- a path that can spend that floor twice, once proving descendants and once in the cleanup re-entry. The Windows shard died on subprocess.TimeoutExpired, taking shard-completeness with it. Derive the budget from the scheduler's own constant instead of restating it. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Tony Thayer-Osborne --- internal/cbm/extract_usages.c | 28 ++++++++++++++++++++++ tests/repro/repro_call_argument_matrix_b.c | 17 ++++++++++--- tests/repro/repro_call_node_manifest.c | 18 ++++++++++---- tests/repro/repro_language_registry.c | 12 +++++----- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/internal/cbm/extract_usages.c b/internal/cbm/extract_usages.c index 98ebc9c3b..e2f4d521c 100644 --- a/internal/cbm/extract_usages.c +++ b/internal/cbm/extract_usages.c @@ -374,6 +374,7 @@ typedef enum { CBM_OCCURRENCE_VHDL_INTERFACE, CBM_OCCURRENCE_PINE_FUNCTION, CBM_OCCURRENCE_LLVM_FUNCTION, + CBM_OCCURRENCE_PKL_DECLARATION, } CBMOccurrencePolicy; typedef struct { @@ -492,6 +493,7 @@ static const CBMOccurrenceSpec occurrence_specs[CBM_LANG_COUNT] = { [CBM_LANG_PINE] = {NULL, NULL, CBM_OCCURRENCE_PINE_FUNCTION, false}, [CBM_LANG_PUPPET] = {NULL, NULL, CBM_OCCURRENCE_STANDARD, true}, [CBM_LANG_LLVM_IR] = {llvm_binding_nodes, NULL, CBM_OCCURRENCE_LLVM_FUNCTION, false}, + [CBM_LANG_PKL] = {NULL, NULL, CBM_OCCURRENCE_PKL_DECLARATION, false}, [CBM_LANG_MESON] = {NULL, meson_write_nodes, CBM_OCCURRENCE_STANDARD, true}, [CBM_LANG_GN] = {NULL, gn_write_nodes, CBM_OCCURRENCE_STANDARD, true}, [CBM_LANG_LINKERSCRIPT] = {NULL, linkerscript_write_nodes, CBM_OCCURRENCE_STANDARD, true}, @@ -985,6 +987,30 @@ static bool is_pine_function_binding(TSNode node) { return ancestor_field_binds(node, "function_declaration_statement", fields); } +/* Pkl declares names positionally: no production labels the declared identifier + * with a `name` field, so the generic declared-container rule never binds them + * and every method name, parameter name, and property name would be re-emitted + * as an ordinary read of itself. Each container below holds its declared name + * as named child 0; annotations, defaults, and bodies follow it and stay reads. + * Resolve against the NEAREST container so a nested declaration's own name is + * the only occurrence its parent can bind. */ +static bool is_pkl_declaration_binding(TSNode node) { + static const char *const declaration_kinds[] = {"methodHeader", + "typedIdentifier", + "classProperty", + "objectProperty", + "clazz", + "typeAlias", + NULL}; + for (TSNode parent = ts_node_parent(node); !ts_node_is_null(parent); + parent = ts_node_parent(parent)) { + if (kind_in_exact_set(ts_node_type(parent), declaration_kinds)) { + return named_child_contains(parent, 0, node); + } + } + return false; +} + static bool is_policy_binding(CBMExtractCtx *ctx, TSNode node, const CBMOccurrenceSpec *occurrence) { switch (occurrence->policy) { @@ -1041,6 +1067,8 @@ static bool is_policy_binding(CBMExtractCtx *ctx, TSNode node, return is_vhdl_interface_binding(node); case CBM_OCCURRENCE_PINE_FUNCTION: return is_pine_function_binding(node); + case CBM_OCCURRENCE_PKL_DECLARATION: + return is_pkl_declaration_binding(node); case CBM_OCCURRENCE_LLVM_FUNCTION: for (TSNode parent = ts_node_parent(node); !ts_node_is_null(parent); parent = ts_node_parent(parent)) { diff --git a/tests/repro/repro_call_argument_matrix_b.c b/tests/repro/repro_call_argument_matrix_b.c index 8b701ea0a..25e085380 100644 --- a/tests/repro/repro_call_argument_matrix_b.c +++ b/tests/repro/repro_call_argument_matrix_b.c @@ -752,6 +752,12 @@ static const char TLAPLUS_BOUNDED_QUANTIFICATION[] = "Guard(values) == \\A item \\in values : item = item\n" "====\n"; +/* Pkl access expressions double as plain property reads, so the bare fixture + * also proves the un-applied reference is not promoted to a call. */ +static const char PKL_INSIDE[] = "function accept(value: Int): Int = value\n" + "function run(watched: Int): Int = accept(watched)\n"; +static const char PKL_BARE[] = "function run(watched: Int): Int = watched\n"; + static const char APEX_INSIDE[] = "public class Sample {\n" " private static Integer accept(Integer value) {\n" " return value;\n" @@ -958,6 +964,9 @@ static const RoutineArgumentCase LLVM_IR_CASE = ROUTINE_ARGUMENT_CASE( static const RoutineArgumentCase TLAPLUS_CASE = ROUTINE_ARGUMENT_CASE( "TLAPLUS", CBM_LANG_TLAPLUS, "Sample.tla", TLAPLUS_INSIDE, TLAPLUS_BARE, "bound_op", "Guard", "Accept", "values", 1, 1, 0, "TLA+ operator application with a value argument"); +static const RoutineArgumentCase PKL_CASE = ROUTINE_ARGUMENT_CASE( + "PKL", CBM_LANG_PKL, "sample.pkl", PKL_INSIDE, PKL_BARE, "unqualifiedAccessExpr", "run", + "accept", "watched", 1, 1, 0, "native Pkl method application and property-read vocabulary"); static const RoutineArgumentCase APEX_CASE = ROUTINE_ARGUMENT_CASE( "APEX", CBM_LANG_APEX, "Sample.cls", APEX_INSIDE, APEX_BARE, "method_invocation", "run", "accept", "watched", 1, 1, 0, "native method application"); @@ -1166,6 +1175,7 @@ DEFINE_ROUTINE_ARGUMENT_TEST(func, FUNC_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(puppet, PUPPET_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(slang, SLANG_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(llvm_ir, LLVM_IR_CASE) +DEFINE_ROUTINE_ARGUMENT_TEST(pkl, PKL_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(apex, APEX_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(pine, PINE_CASE) DEFINE_ROUTINE_ARGUMENT_TEST(qml, QML_CASE) @@ -1243,15 +1253,15 @@ TEST(repro_call_argument_matrix_b_domain_bitbake) { } enum { - ROUTINE_ARGUMENT_LANGUAGE_COUNT = 36, + ROUTINE_ARGUMENT_LANGUAGE_COUNT = 37, MODULE_ARGUMENT_LANGUAGE_COUNT = 4, DOMAIN_CONTROL_LANGUAGE_COUNT = 6, MATRIX_LANGUAGE_COUNT = ROUTINE_ARGUMENT_LANGUAGE_COUNT + MODULE_ARGUMENT_LANGUAGE_COUNT + DOMAIN_CONTROL_LANGUAGE_COUNT, }; -_Static_assert(MATRIX_LANGUAGE_COUNT == 46, - "RACKET..OBJECTSCRIPT_ROUTINE call-capable matrix must contain exactly 46 " +_Static_assert(MATRIX_LANGUAGE_COUNT == 47, + "RACKET..OBJECTSCRIPT_ROUTINE call-capable matrix must contain exactly 47 " "language rows"); #define MATRIX_B_LANGUAGE_ROWS(X) \ @@ -1283,6 +1293,7 @@ _Static_assert(MATRIX_LANGUAGE_COUNT == 46, X(repro_call_argument_matrix_b_routine_slang, SLANG_CASE.identity.language) \ X(repro_call_argument_matrix_b_routine_llvm_ir, LLVM_IR_CASE.identity.language) \ X(repro_call_argument_matrix_b_routine_tlaplus, TLAPLUS_CASE.identity.language) \ + X(repro_call_argument_matrix_b_routine_pkl, PKL_CASE.identity.language) \ X(repro_call_argument_matrix_b_routine_apex, APEX_CASE.identity.language) \ X(repro_call_argument_matrix_b_routine_pine, PINE_CASE.identity.language) \ X(repro_call_argument_matrix_b_routine_qml, QML_CASE.identity.language) \ diff --git a/tests/repro/repro_call_node_manifest.c b/tests/repro/repro_call_node_manifest.c index 822176a4c..bbedb6410 100644 --- a/tests/repro/repro_call_node_manifest.c +++ b/tests/repro/repro_call_node_manifest.c @@ -16,6 +16,11 @@ * TLA+ bound_op is historical direct-call metadata. Bounded quantification is * not a historical ledger row; repro_call_argument_matrix_b is its separate * negative behavior guard. + * + * The historical-snapshot column is the audited ledger, not an archaeology + * record: every registered call node owns a row, so a newly registered kind + * (Pkl's access/new expressions) joins the snapshot with the change that + * registers it. */ #include "test_framework.h" #include "lang_specs.h" @@ -54,10 +59,10 @@ typedef struct { } CallNodeManifestEntry; enum { - EXPECTED_HISTORICAL_CALL_NODE_TOTAL = 219, - EXPECTED_ACTIVE_PRIMARY_TOTAL = 189, - EXPECTED_DIRECT_CALLS = 155, - EXPECTED_CONSTRUCTOR_CALLS = 20, + EXPECTED_HISTORICAL_CALL_NODE_TOTAL = 222, + EXPECTED_ACTIVE_PRIMARY_TOTAL = 192, + EXPECTED_DIRECT_CALLS = 157, + EXPECTED_CONSTRUCTOR_CALLS = 21, EXPECTED_OPERATOR_CALLS = 12, EXPECTED_IMPLICIT_CALLS = 2, EXPECTED_DSL_INVOCATIONS = 14, @@ -65,7 +70,7 @@ enum { EXPECTED_CALLEE_WRAPPERS = 8, EXPECTED_ARGUMENT_WRAPPERS = 1, EXPECTED_CONTROL_OR_DOCUMENT_NONCALLS = 6, - EXPECTED_PRIMARY_OWNERS = 189, + EXPECTED_PRIMARY_OWNERS = 192, EXPECTED_SYNTHETIC_OWNERS = 10, EXPECTED_NO_CALL_EDGE_OWNERS = 20, EXPECTED_PRIMARY_OPERATOR_CALLS = 4, @@ -296,6 +301,9 @@ static const CallNodeManifestEntry CALL_NODE_MANIFEST[] = { ENTRY(CBM_LANG_TLAPLUS, "function_evaluation", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), ENTRY(CBM_LANG_TLAPLUS, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), ENTRY(CBM_LANG_TLAPLUS, "bound_op", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), + ENTRY(CBM_LANG_PKL, "unqualifiedAccessExpr", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), + ENTRY(CBM_LANG_PKL, "qualifiedAccessExpr", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), + ENTRY(CBM_LANG_PKL, "newExpr", CONSTRUCTOR_CALL, PRIMARY_EXTRACTOR, true, true), ENTRY(CBM_LANG_APEX, "method_invocation", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), ENTRY(CBM_LANG_PINE, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), ENTRY(CBM_LANG_MOJO, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true), diff --git a/tests/repro/repro_language_registry.c b/tests/repro/repro_language_registry.c index a779a1ba0..7547856b3 100644 --- a/tests/repro/repro_language_registry.c +++ b/tests/repro/repro_language_registry.c @@ -195,7 +195,7 @@ static const LanguageCapabilityEntry LANGUAGE_CAPABILITIES[CBM_LANG_COUNT] = { NO_CALL(SMITHY), NO_CALL(WIT), CALL_WITH_REFERENCE_VOCAB(TLAPLUS), - NO_CALL(PKL), + CALL_WITH_REFERENCE_VOCAB(PKL), NO_CALL(GOMOD), CALL_WITH_REFERENCE_VOCAB(APEX), NO_CALL(SOQL), @@ -298,8 +298,8 @@ TEST(repro_language_capability_ledger_covers_every_enum) { } } - if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 86 || - counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 25 || counts[CAP_NO_CALL] != 50 || + if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 87 || + counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 25 || counts[CAP_NO_CALL] != 49 || counts[CAP_TRANSFORM_ONLY] != 1 || counts[CAP_UNSUPPORTED] != 1) { fprintf(stderr, " [language-registry] invariant=capability_partition call_ref_vocab=%d ref_gap=%d " @@ -316,9 +316,9 @@ TEST(repro_language_capability_ledger_covers_every_enum) { TEST(repro_call_argument_matrices_equal_call_capability_ledger) { enum { EXPECTED_MATRIX_A_ROWS = 67, - EXPECTED_MATRIX_B_ROWS = 46, - EXPECTED_CALL_CAPABLE_LANGUAGES = 111, - EXPECTED_NON_CALL_LANGUAGES = 52, + EXPECTED_MATRIX_B_ROWS = 47, + EXPECTED_CALL_CAPABLE_LANGUAGES = 112, + EXPECTED_NON_CALL_LANGUAGES = 51, EXPECTED_NON_CALL_DOMAIN_CONTROLS = 2, }; CBMLanguage matrix_a_ids[CBM_LANG_COUNT];