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>
This commit is contained in:
Tony Thayer-Osborne
2026-08-28 08:37:44 -07:00
co-authored by Claude Opus 5
parent 1d6a140f23
commit 7b72652a58
4 changed files with 61 additions and 14 deletions
+28
View File
@@ -377,6 +377,7 @@ typedef enum {
CBM_OCCURRENCE_VHDL_INTERFACE,
CBM_OCCURRENCE_PINE_FUNCTION,
CBM_OCCURRENCE_LLVM_FUNCTION,
CBM_OCCURRENCE_PKL_DECLARATION,
} CBMOccurrencePolicy;
typedef struct {
@@ -495,6 +496,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},
@@ -988,6 +990,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) {
@@ -1044,6 +1070,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)) {
+14 -3
View File
@@ -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"
@@ -977,6 +983,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");
@@ -1188,6 +1197,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)
@@ -1266,15 +1276,15 @@ TEST(repro_call_argument_matrix_b_domain_bitbake) {
}
enum {
ROUTINE_ARGUMENT_LANGUAGE_COUNT = 37,
ROUTINE_ARGUMENT_LANGUAGE_COUNT = 38,
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 == 47,
"RACKET..PLSQL call-capable matrix must contain exactly 47 "
_Static_assert(MATRIX_LANGUAGE_COUNT == 48,
"RACKET..PLSQL call-capable matrix must contain exactly 48 "
"language rows");
#define MATRIX_B_LANGUAGE_ROWS(X) \
@@ -1306,6 +1316,7 @@ _Static_assert(MATRIX_LANGUAGE_COUNT == 47,
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) \
+13 -5
View File
@@ -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 = 222,
EXPECTED_ACTIVE_PRIMARY_TOTAL = 192,
EXPECTED_DIRECT_CALLS = 157,
EXPECTED_CONSTRUCTOR_CALLS = 21,
EXPECTED_HISTORICAL_CALL_NODE_TOTAL = 225,
EXPECTED_ACTIVE_PRIMARY_TOTAL = 195,
EXPECTED_DIRECT_CALLS = 159,
EXPECTED_CONSTRUCTOR_CALLS = 22,
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 = 192,
EXPECTED_PRIMARY_OWNERS = 195,
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),
+6 -6
View File
@@ -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),
@@ -300,8 +300,8 @@ TEST(repro_language_capability_ledger_covers_every_enum) {
}
}
if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 87 ||
counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 26 || counts[CAP_NO_CALL] != 50 ||
if (counts[CAP_CALL_WITH_REFERENCE_VOCAB] != 88 ||
counts[CAP_CALL_WITHOUT_REFERENCE_VOCAB] != 26 || 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 "
@@ -318,9 +318,9 @@ TEST(repro_language_capability_ledger_covers_every_enum) {
TEST(repro_call_argument_matrices_equal_call_capability_ledger) {
enum {
EXPECTED_MATRIX_A_ROWS = 68,
EXPECTED_MATRIX_B_ROWS = 47,
EXPECTED_CALL_CAPABLE_LANGUAGES = 113,
EXPECTED_NON_CALL_LANGUAGES = 52,
EXPECTED_MATRIX_B_ROWS = 48,
EXPECTED_CALL_CAPABLE_LANGUAGES = 114,
EXPECTED_NON_CALL_LANGUAGES = 51,
EXPECTED_NON_CALL_DOMAIN_CONTROLS = 2,
};
CBMLanguage matrix_a_ids[CBM_LANG_COUNT];