Skip to content

Commit 7b72652

Browse files
tthayerclaude
andcommitted
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. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Tony Thayer-Osborne <tony.thayerosborne@conductorone.com>
1 parent 1d6a140 commit 7b72652

4 files changed

Lines changed: 61 additions & 14 deletions

File tree

‎internal/cbm/extract_usages.c‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ typedef enum {
377377
CBM_OCCURRENCE_VHDL_INTERFACE,
378378
CBM_OCCURRENCE_PINE_FUNCTION,
379379
CBM_OCCURRENCE_LLVM_FUNCTION,
380+
CBM_OCCURRENCE_PKL_DECLARATION,
380381
} CBMOccurrencePolicy;
381382

382383
typedef struct {
@@ -495,6 +496,7 @@ static const CBMOccurrenceSpec occurrence_specs[CBM_LANG_COUNT] = {
495496
[CBM_LANG_PINE] = {NULL, NULL, CBM_OCCURRENCE_PINE_FUNCTION, false},
496497
[CBM_LANG_PUPPET] = {NULL, NULL, CBM_OCCURRENCE_STANDARD, true},
497498
[CBM_LANG_LLVM_IR] = {llvm_binding_nodes, NULL, CBM_OCCURRENCE_LLVM_FUNCTION, false},
499+
[CBM_LANG_PKL] = {NULL, NULL, CBM_OCCURRENCE_PKL_DECLARATION, false},
498500
[CBM_LANG_MESON] = {NULL, meson_write_nodes, CBM_OCCURRENCE_STANDARD, true},
499501
[CBM_LANG_GN] = {NULL, gn_write_nodes, CBM_OCCURRENCE_STANDARD, true},
500502
[CBM_LANG_LINKERSCRIPT] = {NULL, linkerscript_write_nodes, CBM_OCCURRENCE_STANDARD, true},
@@ -988,6 +990,30 @@ static bool is_pine_function_binding(TSNode node) {
988990
return ancestor_field_binds(node, "function_declaration_statement", fields);
989991
}
990992

993+
/* Pkl declares names positionally: no production labels the declared identifier
994+
* with a `name` field, so the generic declared-container rule never binds them
995+
* and every method name, parameter name, and property name would be re-emitted
996+
* as an ordinary read of itself. Each container below holds its declared name
997+
* as named child 0; annotations, defaults, and bodies follow it and stay reads.
998+
* Resolve against the NEAREST container so a nested declaration's own name is
999+
* the only occurrence its parent can bind. */
1000+
static bool is_pkl_declaration_binding(TSNode node) {
1001+
static const char *const declaration_kinds[] = {"methodHeader",
1002+
"typedIdentifier",
1003+
"classProperty",
1004+
"objectProperty",
1005+
"clazz",
1006+
"typeAlias",
1007+
NULL};
1008+
for (TSNode parent = ts_node_parent(node); !ts_node_is_null(parent);
1009+
parent = ts_node_parent(parent)) {
1010+
if (kind_in_exact_set(ts_node_type(parent), declaration_kinds)) {
1011+
return named_child_contains(parent, 0, node);
1012+
}
1013+
}
1014+
return false;
1015+
}
1016+
9911017
static bool is_policy_binding(CBMExtractCtx *ctx, TSNode node,
9921018
const CBMOccurrenceSpec *occurrence) {
9931019
switch (occurrence->policy) {
@@ -1044,6 +1070,8 @@ static bool is_policy_binding(CBMExtractCtx *ctx, TSNode node,
10441070
return is_vhdl_interface_binding(node);
10451071
case CBM_OCCURRENCE_PINE_FUNCTION:
10461072
return is_pine_function_binding(node);
1073+
case CBM_OCCURRENCE_PKL_DECLARATION:
1074+
return is_pkl_declaration_binding(node);
10471075
case CBM_OCCURRENCE_LLVM_FUNCTION:
10481076
for (TSNode parent = ts_node_parent(node); !ts_node_is_null(parent);
10491077
parent = ts_node_parent(parent)) {

‎tests/repro/repro_call_argument_matrix_b.c‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,12 @@ static const char TLAPLUS_BOUNDED_QUANTIFICATION[] =
752752
"Guard(values) == \\A item \\in values : item = item\n"
753753
"====\n";
754754

755+
/* Pkl access expressions double as plain property reads, so the bare fixture
756+
* also proves the un-applied reference is not promoted to a call. */
757+
static const char PKL_INSIDE[] = "function accept(value: Int): Int = value\n"
758+
"function run(watched: Int): Int = accept(watched)\n";
759+
static const char PKL_BARE[] = "function run(watched: Int): Int = watched\n";
760+
755761
static const char APEX_INSIDE[] = "public class Sample {\n"
756762
" private static Integer accept(Integer value) {\n"
757763
" return value;\n"
@@ -977,6 +983,9 @@ static const RoutineArgumentCase LLVM_IR_CASE = ROUTINE_ARGUMENT_CASE(
977983
static const RoutineArgumentCase TLAPLUS_CASE = ROUTINE_ARGUMENT_CASE(
978984
"TLAPLUS", CBM_LANG_TLAPLUS, "Sample.tla", TLAPLUS_INSIDE, TLAPLUS_BARE, "bound_op", "Guard",
979985
"Accept", "values", 1, 1, 0, "TLA+ operator application with a value argument");
986+
static const RoutineArgumentCase PKL_CASE = ROUTINE_ARGUMENT_CASE(
987+
"PKL", CBM_LANG_PKL, "sample.pkl", PKL_INSIDE, PKL_BARE, "unqualifiedAccessExpr", "run",
988+
"accept", "watched", 1, 1, 0, "native Pkl method application and property-read vocabulary");
980989
static const RoutineArgumentCase APEX_CASE = ROUTINE_ARGUMENT_CASE(
981990
"APEX", CBM_LANG_APEX, "Sample.cls", APEX_INSIDE, APEX_BARE, "method_invocation", "run",
982991
"accept", "watched", 1, 1, 0, "native method application");
@@ -1188,6 +1197,7 @@ DEFINE_ROUTINE_ARGUMENT_TEST(func, FUNC_CASE)
11881197
DEFINE_ROUTINE_ARGUMENT_TEST(puppet, PUPPET_CASE)
11891198
DEFINE_ROUTINE_ARGUMENT_TEST(slang, SLANG_CASE)
11901199
DEFINE_ROUTINE_ARGUMENT_TEST(llvm_ir, LLVM_IR_CASE)
1200+
DEFINE_ROUTINE_ARGUMENT_TEST(pkl, PKL_CASE)
11911201
DEFINE_ROUTINE_ARGUMENT_TEST(apex, APEX_CASE)
11921202
DEFINE_ROUTINE_ARGUMENT_TEST(pine, PINE_CASE)
11931203
DEFINE_ROUTINE_ARGUMENT_TEST(qml, QML_CASE)
@@ -1266,15 +1276,15 @@ TEST(repro_call_argument_matrix_b_domain_bitbake) {
12661276
}
12671277

12681278
enum {
1269-
ROUTINE_ARGUMENT_LANGUAGE_COUNT = 37,
1279+
ROUTINE_ARGUMENT_LANGUAGE_COUNT = 38,
12701280
MODULE_ARGUMENT_LANGUAGE_COUNT = 4,
12711281
DOMAIN_CONTROL_LANGUAGE_COUNT = 6,
12721282
MATRIX_LANGUAGE_COUNT = ROUTINE_ARGUMENT_LANGUAGE_COUNT + MODULE_ARGUMENT_LANGUAGE_COUNT +
12731283
DOMAIN_CONTROL_LANGUAGE_COUNT,
12741284
};
12751285

1276-
_Static_assert(MATRIX_LANGUAGE_COUNT == 47,
1277-
"RACKET..PLSQL call-capable matrix must contain exactly 47 "
1286+
_Static_assert(MATRIX_LANGUAGE_COUNT == 48,
1287+
"RACKET..PLSQL call-capable matrix must contain exactly 48 "
12781288
"language rows");
12791289

12801290
#define MATRIX_B_LANGUAGE_ROWS(X) \
@@ -1306,6 +1316,7 @@ _Static_assert(MATRIX_LANGUAGE_COUNT == 47,
13061316
X(repro_call_argument_matrix_b_routine_slang, SLANG_CASE.identity.language) \
13071317
X(repro_call_argument_matrix_b_routine_llvm_ir, LLVM_IR_CASE.identity.language) \
13081318
X(repro_call_argument_matrix_b_routine_tlaplus, TLAPLUS_CASE.identity.language) \
1319+
X(repro_call_argument_matrix_b_routine_pkl, PKL_CASE.identity.language) \
13091320
X(repro_call_argument_matrix_b_routine_apex, APEX_CASE.identity.language) \
13101321
X(repro_call_argument_matrix_b_routine_pine, PINE_CASE.identity.language) \
13111322
X(repro_call_argument_matrix_b_routine_qml, QML_CASE.identity.language) \

‎tests/repro/repro_call_node_manifest.c‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
* TLA+ bound_op is historical direct-call metadata. Bounded quantification is
1717
* not a historical ledger row; repro_call_argument_matrix_b is its separate
1818
* negative behavior guard.
19+
*
20+
* The historical-snapshot column is the audited ledger, not an archaeology
21+
* record: every registered call node owns a row, so a newly registered kind
22+
* (Pkl's access/new expressions) joins the snapshot with the change that
23+
* registers it.
1924
*/
2025
#include "test_framework.h"
2126
#include "lang_specs.h"
@@ -54,18 +59,18 @@ typedef struct {
5459
} CallNodeManifestEntry;
5560

5661
enum {
57-
EXPECTED_HISTORICAL_CALL_NODE_TOTAL = 222,
58-
EXPECTED_ACTIVE_PRIMARY_TOTAL = 192,
59-
EXPECTED_DIRECT_CALLS = 157,
60-
EXPECTED_CONSTRUCTOR_CALLS = 21,
62+
EXPECTED_HISTORICAL_CALL_NODE_TOTAL = 225,
63+
EXPECTED_ACTIVE_PRIMARY_TOTAL = 195,
64+
EXPECTED_DIRECT_CALLS = 159,
65+
EXPECTED_CONSTRUCTOR_CALLS = 22,
6166
EXPECTED_OPERATOR_CALLS = 12,
6267
EXPECTED_IMPLICIT_CALLS = 2,
6368
EXPECTED_DSL_INVOCATIONS = 14,
6469
EXPECTED_BUILD_DEPENDENCIES = 1,
6570
EXPECTED_CALLEE_WRAPPERS = 8,
6671
EXPECTED_ARGUMENT_WRAPPERS = 1,
6772
EXPECTED_CONTROL_OR_DOCUMENT_NONCALLS = 6,
68-
EXPECTED_PRIMARY_OWNERS = 192,
73+
EXPECTED_PRIMARY_OWNERS = 195,
6974
EXPECTED_SYNTHETIC_OWNERS = 10,
7075
EXPECTED_NO_CALL_EDGE_OWNERS = 20,
7176
EXPECTED_PRIMARY_OPERATOR_CALLS = 4,
@@ -296,6 +301,9 @@ static const CallNodeManifestEntry CALL_NODE_MANIFEST[] = {
296301
ENTRY(CBM_LANG_TLAPLUS, "function_evaluation", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
297302
ENTRY(CBM_LANG_TLAPLUS, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
298303
ENTRY(CBM_LANG_TLAPLUS, "bound_op", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
304+
ENTRY(CBM_LANG_PKL, "unqualifiedAccessExpr", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
305+
ENTRY(CBM_LANG_PKL, "qualifiedAccessExpr", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
306+
ENTRY(CBM_LANG_PKL, "newExpr", CONSTRUCTOR_CALL, PRIMARY_EXTRACTOR, true, true),
299307
ENTRY(CBM_LANG_APEX, "method_invocation", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
300308
ENTRY(CBM_LANG_PINE, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),
301309
ENTRY(CBM_LANG_MOJO, "call", DIRECT_CALL, PRIMARY_EXTRACTOR, true, true),

‎tests/repro/repro_language_registry.c‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ static const LanguageCapabilityEntry LANGUAGE_CAPABILITIES[CBM_LANG_COUNT] = {
195195
NO_CALL(SMITHY),
196196
NO_CALL(WIT),
197197
CALL_WITH_REFERENCE_VOCAB(TLAPLUS),
198-
NO_CALL(PKL),
198+
CALL_WITH_REFERENCE_VOCAB(PKL),
199199
NO_CALL(GOMOD),
200200
CALL_WITH_REFERENCE_VOCAB(APEX),
201201
NO_CALL(SOQL),
@@ -300,8 +300,8 @@ TEST(repro_language_capability_ledger_covers_every_enum) {
300300
}
301301
}
302302

303-
if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 87 ||
304-
counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 26 || counts[CAP_NO_CALL] != 50 ||
303+
if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 88 ||
304+
counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 26 || counts[CAP_NO_CALL] != 49 ||
305305
counts[CAP_TRANSFORM_ONLY] != 1 || counts[CAP_UNSUPPORTED] != 1) {
306306
fprintf(stderr,
307307
" [language-registry] invariant=capability_partition call_ref_vocab=%d ref_gap=%d "
@@ -318,9 +318,9 @@ TEST(repro_language_capability_ledger_covers_every_enum) {
318318
TEST(repro_call_argument_matrices_equal_call_capability_ledger) {
319319
enum {
320320
EXPECTED_MATRIX_A_ROWS = 68,
321-
EXPECTED_MATRIX_B_ROWS = 47,
322-
EXPECTED_CALL_CAPABLE_LANGUAGES = 113,
323-
EXPECTED_NON_CALL_LANGUAGES = 52,
321+
EXPECTED_MATRIX_B_ROWS = 48,
322+
EXPECTED_CALL_CAPABLE_LANGUAGES = 114,
323+
EXPECTED_NON_CALL_LANGUAGES = 51,
324324
EXPECTED_NON_CALL_DOMAIN_CONTROLS = 2,
325325
};
326326
CBMLanguage matrix_a_ids[CBM_LANG_COUNT];

0 commit comments

Comments
 (0)