|
45 | 45 | * 6. calls-extracted : inv_has_call(r, callee) == 1. |
46 | 46 | * Only asserted for languages that have non-empty |
47 | 47 | * call_types: HCL (function_call), NICKEL (infix_expr), |
48 | | - * JSONNET (functioncall), STARLARK (call). |
| 48 | + * JSONNET (functioncall), STARLARK (call), |
| 49 | + * PKL (unqualified/qualifiedAccessExpr, newExpr). |
49 | 50 | * |
50 | 51 | * FULL-PIPELINE (rh_index_files -> cbm_store_t*, via inv_count_* store helpers): |
51 | 52 | * 7. callable-sourcing : inv_count_calls_by_source(store,project,&mod,&call). |
|
85 | 86 | * Dims 1-5 ("Class"). No calls. |
86 | 87 | * XML -- class_types = element -> "Class". Dims 1-5 ("Class"). No calls. |
87 | 88 | * PROPERTIES -- var_types = property -> "Variable". Dims 1-5 ("Variable"). No calls. |
88 | | - * PKL -- func_types = classMethod/objectMethod -> "Function"; |
89 | | - * class_types = clazz -> "Class"; var_types = classProperty/objectProperty. |
90 | | - * call_types = empty_types. Dims 1-5 ("Function", "Class"). No call dim. |
91 | 89 | * |
92 | 90 | * LANGUAGES WITH CALLABLES (dims 1-6 + R, and pipeline dims 7-8 where applicable): |
| 91 | + * PKL -- func_types = classMethod/objectMethod -> "Function"; |
| 92 | + * class_types = clazz/typeAlias -> "Class"; |
| 93 | + * var_types = classProperty/objectProperty; |
| 94 | + * call_types = unqualifiedAccessExpr/qualifiedAccessExpr/newExpr. |
| 95 | + * Dims 1-8. The access-expr call types double as property reads, |
| 96 | + * so extract_pkl_callee gates them on an `argumentList` child; |
| 97 | + * the test adds an inline negative assertion for that gate. |
93 | 98 | * HCL -- class_types = block -> "Class"; var_types = attribute; |
94 | 99 | * call_types = function_call. Dims 1-6. No func_types so no pipeline |
95 | 100 | * dim 7 (calls would be module-sourced with no Function anchor). |
@@ -766,39 +771,72 @@ TEST(repro_grammar_config_ron) { |
766 | 771 |
|
767 | 772 | /* ── PKL ────────────────────────────────────────────────────────────────────── |
768 | 773 | * Idiomatic PKL (Apple Pkl) module with a class definition |
769 | | - * (pkl_class_types = {"clazz"} -> "Class"), a method inside it |
| 774 | + * (pkl_class_types = {"clazz", "typeAlias"} -> "Class"), methods inside it |
770 | 775 | * (pkl_func_types = {"classMethod", "objectMethod"} -> "Function"), and |
771 | 776 | * class properties (pkl_var_types = {"classProperty", "objectProperty"}). |
772 | | - * pkl_call_types = empty_types so no call extraction occurs. |
773 | | - * |
774 | | - * Dims asserted: 1-5 + R ("Class" for the class def, "Function" for the method). |
775 | | - * Dims 6-8 SKIPPED: call_types = empty_types in spec. |
776 | | - * Expected GREEN: dims 1-5. Dim 5 RED would indicate clazz->Class or |
777 | | - * classMethod->Function mapping is broken in the PKL grammar walker. |
| 777 | + * pkl_call_types = {"unqualifiedAccessExpr", "qualifiedAccessExpr", "newExpr"}. |
| 778 | + * |
| 779 | + * Dims asserted: 1-8 (full battery) + R. |
| 780 | + * Dim 6 GREEN: `makeUrl(host, port)` inside url() extracts callee "makeUrl". |
| 781 | + * Dim 7 GREEN: every call site in the fixture is inside a classMethod body, so |
| 782 | + * no CALLS edge is Module-sourced. (Real-world Pkl does call at module level; |
| 783 | + * the fixture deliberately avoids it because dim 7 treats Module-sourced |
| 784 | + * in-body calls as the enclosing-func gap.) |
| 785 | + * Dim 8 GREEN: makeUrl and Server are both defined in-file, so neither the |
| 786 | + * unqualified call nor the newExpr constructor edge dangles. |
| 787 | + * |
| 788 | + * PKL-SPECIFIC REGRESSION (asserted inline below): `unqualifiedAccessExpr` and |
| 789 | + * `qualifiedAccessExpr` are the same node for a call and for a bare property |
| 790 | + * read, so the interpolated `host` / `port` reads inside makeUrl must NOT be |
| 791 | + * emitted as CALLS. extract_pkl_callee gates on an `argumentList` child; without |
| 792 | + * that gate every property read in every Pkl file becomes a call edge. |
778 | 793 | */ |
779 | 794 | TEST(repro_grammar_config_pkl) { |
780 | 795 | static const char src[] = |
781 | 796 | "module cbm.Config\n" |
782 | 797 | "\n" |
783 | | - "function makeUrl(host: String, port: Int): String = \"http://\\(host):\\(port)\"\n" |
| 798 | + "typealias Port = Int\n" |
| 799 | + "\n" |
| 800 | + "function makeUrl(host: String, port: Port): String = \"http://\\(host):\\(port)\"\n" |
784 | 801 | "\n" |
785 | 802 | "class Server {\n" |
786 | 803 | " host: String = \"localhost\"\n" |
787 | | - " port: Int = 8080\n" |
| 804 | + " port: Port = 8080\n" |
788 | 805 | " tls: Boolean = false\n" |
789 | 806 | "\n" |
790 | | - " function url(): String = \"http://\\(host):\\(port)\"\n" |
791 | | - "}\n" |
| 807 | + " function url(): String = makeUrl(host, port)\n" |
792 | 808 | "\n" |
793 | | - "server = new Server {\n" |
794 | | - " host = \"0.0.0.0\"\n" |
795 | | - " port = 9000\n" |
| 809 | + " function clone(): Server = new Server { host = host }\n" |
796 | 810 | "}\n"; |
797 | 811 | static const char bad[] = "module cbm.Config\nclass Server {\n host:"; |
798 | | - if (config_struct_battery("PKL", src, CBM_LANG_PKL, "config.pkl", |
799 | | - "Class", "Function") != 0) |
| 812 | + if (config_callable_battery("PKL", src, CBM_LANG_PKL, "config.pkl", |
| 813 | + "Function", "makeUrl") != 0) |
| 814 | + return 1; |
| 815 | + |
| 816 | + /* Bare property reads must not be calls (see PKL-SPECIFIC REGRESSION above). */ |
| 817 | + CBMFileResult *pr = inv_rx(src, CBM_LANG_PKL, "config.pkl"); |
| 818 | + if (!pr) { |
| 819 | + printf(" %sFAIL%s [PKL] inv_rx returned NULL\n", tf_red(), tf_reset()); |
| 820 | + return 1; |
| 821 | + } |
| 822 | + int bogus = 0; |
| 823 | + for (int i = 0; i < pr->calls.count; i++) { |
| 824 | + const char *cn = pr->calls.items[i].callee_name; |
| 825 | + if (cn && (strcmp(cn, "host") == 0 || strcmp(cn, "port") == 0 || |
| 826 | + strcmp(cn, "tls") == 0)) { |
| 827 | + bogus++; |
| 828 | + } |
| 829 | + } |
| 830 | + cbm_free_result(pr); |
| 831 | + if (bogus != 0) { |
| 832 | + printf(" %sFAIL%s [PKL] property-read-not-call: %d bare property read(s) " |
| 833 | + "emitted as a CALLS edge\n", tf_red(), tf_reset(), bogus); |
| 834 | + return 1; |
| 835 | + } |
| 836 | + |
| 837 | + if (config_robustness("PKL", bad, CBM_LANG_PKL, "config.pkl") != 0) |
800 | 838 | return 1; |
801 | | - return config_robustness("PKL", bad, CBM_LANG_PKL, "config.pkl"); |
| 839 | + return config_pipeline_battery("PKL", "config.pkl", src); |
802 | 840 | } |
803 | 841 |
|
804 | 842 | /* ── NICKEL ─────────────────────────────────────────────────────────────────── |
|
0 commit comments