diff --git a/internal/cbm/extract_usages.c b/internal/cbm/extract_usages.c index 1f2e4098e..d970f7fb4 100644 --- a/internal/cbm/extract_usages.c +++ b/internal/cbm/extract_usages.c @@ -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)) { diff --git a/tests/repro/repro_call_argument_matrix_b.c b/tests/repro/repro_call_argument_matrix_b.c index 733781344..f16e17f48 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" @@ -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) \ diff --git a/tests/repro/repro_call_node_manifest.c b/tests/repro/repro_call_node_manifest.c index 97042df51..81a364b76 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 = 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), diff --git a/tests/repro/repro_language_registry.c b/tests/repro/repro_language_registry.c index afa1ee596..de3d96fd1 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), @@ -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];