diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 772adcee9..4994bd7b8 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -745,9 +745,14 @@ TSNode cbm_resolve_func_name(TSNode node, CBMLanguage lang) { } /* Swift and newer tree-sitter-kotlin: function_declaration has no `name` - * field; the function name is a `simple_identifier` child. */ + * field; the function name is a `simple_identifier` child. A Swift + * protocol requirement (a bodyless `func` inside a protocol) is a + * separate node type with the same shape; it is named here as well as in + * resolve_method_name because swift_func_types now admits it, so it can + * reach the free-function path too and would otherwise land unnamed. */ if ((lang == CBM_LANG_SWIFT || lang == CBM_LANG_KOTLIN) && - strcmp(kind, "function_declaration") == 0) { + (strcmp(kind, "function_declaration") == 0 || + strcmp(kind, "protocol_function_declaration") == 0)) { TSNode si = cbm_find_child_by_kind(node, "simple_identifier"); if (!ts_node_is_null(si)) { return si; @@ -4579,6 +4584,7 @@ static TSNode find_class_body(TSNode class_node, CBMLanguage lang) { static const char *body_types[] = {"class_body", "interface_body", "enum_body", + "protocol_body", "template_body", "interface_type", "struct_type", @@ -4699,7 +4705,8 @@ static TSNode resolve_method_name(TSNode child, CBMLanguage lang) { } if ((lang == CBM_LANG_SWIFT || lang == CBM_LANG_KOTLIN) && - strcmp(ck, "function_declaration") == 0) { + (strcmp(ck, "function_declaration") == 0 || + strcmp(ck, "protocol_function_declaration") == 0)) { return cbm_find_child_by_kind(child, "simple_identifier"); } diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 53b1ca728..9dd1cdb37 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -547,7 +547,16 @@ static const char *objc_var_types[] = {"declaration", NULL}; static const char *objc_assign_types[] = {"assignment_expression", NULL}; // ==================== SWIFT ==================== -static const char *swift_func_types[] = {"function_declaration", "macro_declaration", NULL}; +// protocol_function_declaration: a protocol's method requirements. Needed here as +// well as in the name resolvers — extract_class_methods gates on this set, so +// without the entry the requirement is walked and then discarded. +static const char *swift_func_types[] = {"function_declaration", "protocol_function_declaration", + "macro_declaration", NULL}; +// KNOWN GAP: struct_declaration and enum_declaration are not node types in the +// vendored tree-sitter-swift grammar — it models both as class_declaration — so +// these two entries never match anything, and a bare Swift `enum` is labeled +// Class rather than Enum. Left in place deliberately: they are the only marker +// of that modelling gap, and deleting them would hide it. Tracked separately. static const char *swift_class_types[] = {"class_declaration", "protocol_declaration", "struct_declaration", "enum_declaration", NULL}; static const char *swift_field_types[] = {"property_declaration", NULL}; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 506973350..687f901d0 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -655,6 +655,21 @@ TEST(swift_class) { PASS(); } +TEST(swift_protocol) { + /* A protocol requirement is a bodyless func inside a protocol body. Swift + * codebases are heavily protocol-driven, so the requirement is very often + * the declaration a reader is actually looking for — before this it was + * absent from the graph entirely. */ + CBMFileResult *r = extract("protocol StudyRunning {\n func generate() -> String\n}\n", + CBM_LANG_SWIFT, "t", "StudyRunning.swift"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_def(r, "Interface", "StudyRunning")); + ASSERT(has_def(r, "Method", "generate")); + cbm_free_result(r); + PASS(); +} + /* --- Kotlin --- */ TEST(kotlin_function) { CBMFileResult *r = extract("fun greet(name: String): String = \"Hello $name\"\nfun main() { " @@ -5451,6 +5466,7 @@ SUITE(extraction) { RUN_TEST(csharp_class); RUN_TEST(csharp_interface); RUN_TEST(swift_class); + RUN_TEST(swift_protocol); RUN_TEST(kotlin_function); RUN_TEST(kotlin_class); RUN_TEST(scala_function);