@@ -1514,6 +1514,63 @@ static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_
15141514 return false;
15151515}
15161516
1517+ /* #1989: type-def counterpart of cbm_span_contains_callable_def. A rescued
1518+ * class/struct/enum definition has its name followed by a base clause (':') or
1519+ * a body ('{'), not a parameter list, so the callable validator would reject
1520+ * every type def pulled from the expanded tree. */
1521+ static bool cbm_span_contains_type_def (const char * src , int src_len , uint32_t start_line ,
1522+ uint32_t end_line , const char * name ) {
1523+ if (!src || src_len <= 0 || !name || !name [0 ] || start_line == 0 || end_line < start_line ) {
1524+ return false;
1525+ }
1526+ int span_start = 0 ;
1527+ uint32_t line = 1 ;
1528+ while (span_start < src_len && line < start_line ) {
1529+ if (src [span_start ++ ] == '\n' ) {
1530+ line ++ ;
1531+ }
1532+ }
1533+ if (line != start_line ) {
1534+ return false;
1535+ }
1536+ int span_end = span_start ;
1537+ while (span_end < src_len && line <= end_line ) {
1538+ if (src [span_end ++ ] == '\n' ) {
1539+ line ++ ;
1540+ }
1541+ }
1542+ size_t name_len = strlen (name );
1543+ for (int pos = span_start ; pos + (int )name_len <= span_end ; pos ++ ) {
1544+ if (strncmp (src + pos , name , name_len ) != 0 ||
1545+ (pos > 0 && cbm_identifier_char (src [pos - 1 ])) ||
1546+ (pos + (int )name_len < src_len && cbm_identifier_char (src [pos + name_len ]))) {
1547+ continue ;
1548+ }
1549+ int open = pos + (int )name_len ;
1550+ while (open < span_end && isspace ((unsigned char )src [open ])) {
1551+ open ++ ;
1552+ }
1553+ // Base clause (": public B"), body ("{"), or template args ("<...").
1554+ if (open < span_end && (src [open ] == ':' || src [open ] == '{' || src [open ] == '<' )) {
1555+ return true;
1556+ }
1557+ }
1558+ return false;
1559+ }
1560+
1561+ /* #1989: label buckets for the misparse-correction gates. A raw def extracted
1562+ * from a broken `class MOD_API Foo` shape lands under a callable label while
1563+ * the expanded tree yields a proper type label for the same name. */
1564+ static bool cbm_def_label_is_type (const char * label ) {
1565+ return label != NULL && (strcmp (label , "Class" ) == 0 || strcmp (label , "Enum" ) == 0 ||
1566+ strcmp (label , "Interface" ) == 0 || strcmp (label , "Type" ) == 0 ||
1567+ strcmp (label , "Struct" ) == 0 || strcmp (label , "Union" ) == 0 );
1568+ }
1569+
1570+ static bool cbm_def_label_is_callable (const char * label ) {
1571+ return label != NULL && (strcmp (label , "Function" ) == 0 || strcmp (label , "Method" ) == 0 );
1572+ }
1573+
15171574/* Remap an expanded-source definition back to the original input file. Every
15181575 * line in the definition must be attributable to the main file; generated
15191576 * macro bodies, included headers, and ambiguous spans fail closed. */
@@ -2133,6 +2190,14 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
21332190 // Defs keep original-source line numbers; only CALLS are extracted from expanded source.
21342191 if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA ) {
21352192 uint64_t pp_start = now_ns ();
2193+ /* #1989: collect build-system export-macro candidates from the raw
2194+ * source. They are predefined empty in the expanded buffer (see
2195+ * preprocessor.cpp) so `class MOD_API Foo` parses with its real name,
2196+ * and the rescue loop below can adopt corrected type defs. Bounded:
2197+ * at most CBM_EXPORT_MACRO_MAX names per file. */
2198+ char export_cands [CBM_EXPORT_MACRO_MAX ][CBM_EXPORT_MACRO_NAME_MAX ];
2199+ int export_cand_count =
2200+ cbm_export_macro_candidates (source , source_len , export_cands , CBM_EXPORT_MACRO_MAX );
21362201 CBMPreprocessedSource * preprocessed = cbm_preprocess_with_map (
21372202 source , source_len , rel_path , extra_defines , include_paths , language != CBM_LANG_C );
21382203 if (preprocessed && preprocessed -> source ) {
@@ -2213,44 +2278,212 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
22132278 * recover defs from it — adopting ONLY those that
22142279 * intersect a raw ERROR region, whose name is visible on
22152280 * the raw source line, and whose QN the raw pass did not
2216- * already extract. */
2217- if (ts_node_has_error (root )) {
2281+ * already extract.
2282+ *
2283+ * #1989: build-system export macros (<MOD>_API,
2284+ * <lib>_EXPORT, ...) are empty on the real compile line but
2285+ * opaque to tree-sitter, so `class MOD_API Foo` misparses
2286+ * with the macro as the type name — often with NO raw ERROR
2287+ * region at all (the class_specifier "succeeds" with the
2288+ * wrong name; enums and free functions do error out). When
2289+ * export-macro candidates were collected for this file, also
2290+ * adopt expanded defs that replace a raw def literally named
2291+ * as a candidate, or that correct a raw misparse label (the
2292+ * raw pass extracted the same name as Function/Method from
2293+ * the broken function_definition shape; the expanded tree
2294+ * yields a proper type def). Superseded raw defs are
2295+ * dropped so the corrected definition is not shadowed by
2296+ * the qualified-name dedup. With no candidates the block
2297+ * reduces to the original #961 behavior. */
2298+ if (ts_node_has_error (root ) || export_cand_count > 0 ) {
22182299 cbm_error_regions_t raw_regs = {{0 }, {0 }, 0 , 0 };
22192300 cbm_collect_error_regions (root , & raw_regs , source , source_len );
2220- if (raw_regs .count > 0 ) {
2301+ if (raw_regs .count > 0 || export_cand_count > 0 ) {
22212302 int defs_before = result -> defs .count ;
22222303 cbm_extract_definitions (& pp_ctx );
22232304 int w = defs_before ;
2305+ char * superseded = NULL ;
2306+ if (export_cand_count > 0 && defs_before > 0 ) {
2307+ superseded =
2308+ (char * )cbm_calloc (CBM_MEM_CLASS_EXTRACT , (size_t )defs_before );
2309+ }
2310+ /* #1989: spans of defs already adopted from the
2311+ * expanded tree. A def nested inside one of these
2312+ * (an inline method of a rescued class) was dropped
2313+ * by the raw pass — the misparsed
2314+ * `class MOD_API Foo {...}` parsed as a
2315+ * function_definition, and nested defs in its body
2316+ * are never walked. Bounded: one span per adopted
2317+ * def, capped at 64. */
2318+ struct {
2319+ uint32_t start ;
2320+ uint32_t end ;
2321+ } rescued_spans [64 ];
2322+ int rescued_count = 0 ;
22242323 for (int i = defs_before ; i < result -> defs .count ; i ++ ) {
22252324 CBMDefinition * d = & result -> defs .items [i ];
22262325 bool adopt = false;
2326+ int supersede_j = -1 ;
22272327 if (cbm_remap_preprocessed_def (d , preprocessed )) {
22282328 for (int rj = 0 ; rj < raw_regs .count && !adopt ; rj ++ ) {
22292329 if (d -> start_line <= raw_regs .ends [rj ] &&
22302330 d -> end_line >= raw_regs .starts [rj ]) {
22312331 adopt = true;
22322332 }
22332333 }
2334+ if (!adopt && export_cand_count > 0 && d -> name ) {
2335+ for (int ci = 0 ; ci < export_cand_count && !adopt ; ci ++ ) {
2336+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
2337+ CBMDefinition * r = & result -> defs .items [j ];
2338+ if (!r -> name ||
2339+ strcmp (r -> name , export_cands [ci ]) != 0 ) {
2340+ continue ;
2341+ }
2342+ if (r -> start_line <= d -> end_line &&
2343+ d -> start_line <= r -> end_line &&
2344+ cbm_span_contains_type_def (
2345+ source , source_len , d -> start_line ,
2346+ d -> end_line , d -> name )) {
2347+ adopt = true;
2348+ supersede_j = j ;
2349+ }
2350+ }
2351+ }
2352+ if (!adopt && d -> label && cbm_def_label_is_type (d -> label )) {
2353+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
2354+ CBMDefinition * r = & result -> defs .items [j ];
2355+ /* Widened past callables (#1989): the
2356+ * misparse also mints value-label raw
2357+ * defs for types (`class X_API FEmpty {}`
2358+ * -> Variable FEmpty). Same name,
2359+ * overlapping span, raw label is NOT a
2360+ * type => raw is the misparse artifact. */
2361+ if (!r -> name || !r -> label || !d -> name ||
2362+ strcmp (r -> name , d -> name ) != 0 ||
2363+ cbm_def_label_is_type (r -> label )) {
2364+ continue ;
2365+ }
2366+ if (r -> start_line <= d -> end_line &&
2367+ d -> start_line <= r -> end_line ) {
2368+ adopt = true;
2369+ supersede_j = j ;
2370+ }
2371+ }
2372+ }
2373+ /* Nested rescue (#1989): a def nested inside
2374+ * an already-adopted def's span — the inline
2375+ * method of a rescued class, dropped by the
2376+ * raw pass because the class parsed as a
2377+ * function body. The same validation gates
2378+ * (name-on-line, shape, QN dedup) still run
2379+ * below before it lands. */
2380+ if (!adopt && rescued_count > 0 ) {
2381+ for (int k = 0 ; k < rescued_count && !adopt ; k ++ ) {
2382+ if (d -> start_line >= rescued_spans [k ].start &&
2383+ d -> end_line <= rescued_spans [k ].end ) {
2384+ adopt = true;
2385+ }
2386+ }
2387+ }
2388+ }
22342389 }
2235- if (adopt && (!d -> name ||
2236- !cbm_line_contains (source , source_len , d -> start_line ,
2237- d -> name ) ||
2238- !cbm_span_contains_callable_def (
2239- source , source_len , d -> start_line , d -> end_line ,
2240- d -> name ))) {
2390+ if (adopt &&
2391+ (!d -> name ||
2392+ !cbm_line_contains (source , source_len , d -> start_line ,
2393+ d -> name ) ||
2394+ (!cbm_span_contains_callable_def (source , source_len ,
2395+ d -> start_line , d -> end_line ,
2396+ d -> name ) &&
2397+ !cbm_span_contains_type_def (source , source_len , d -> start_line ,
2398+ d -> end_line , d -> name )))) {
22412399 adopt = false;
22422400 }
22432401 for (int j = 0 ; j < defs_before && adopt ; j ++ ) {
2402+ if (j == supersede_j || (superseded && superseded [j ])) {
2403+ continue ; // being replaced by this very def
2404+ }
22442405 const char * q = result -> defs .items [j ].qualified_name ;
22452406 if (q && d -> qualified_name &&
22462407 strcmp (q , d -> qualified_name ) == 0 ) {
2408+ /* #1989: a raw def holding the same QN is
2409+ * usually a reject — unless it is the
2410+ * misparse artifact this very def came to
2411+ * replace (raw "Function FCalc" from the
2412+ * broken `class MOD_API FCalc` shape vs the
2413+ * rescued "Class FCalc"). Same name, same
2414+ * span, raw label is not a type: supersede
2415+ * instead of dropping the correction. */
2416+ CBMDefinition * r = & result -> defs .items [j ];
2417+ if (d -> label && cbm_def_label_is_type (d -> label ) &&
2418+ r -> label && r -> name && d -> name &&
2419+ strcmp (r -> name , d -> name ) == 0 &&
2420+ !cbm_def_label_is_type (r -> label ) &&
2421+ r -> start_line == d -> start_line &&
2422+ r -> end_line == d -> end_line && superseded ) {
2423+ superseded [j ] = 1 ;
2424+ continue ;
2425+ }
22472426 adopt = false;
22482427 }
22492428 }
22502429 if (adopt ) {
2430+ if (supersede_j >= 0 && supersede_j < defs_before &&
2431+ superseded ) {
2432+ superseded [supersede_j ] = 1 ;
2433+ }
2434+ /* Phantom-artifact suppression (#1989): on the
2435+ * raw tree `class MOD_API Foo : public Base
2436+ * {...}` parses as a function_definition whose
2437+ * declarator is the BASE-CLASS name, so the raw
2438+ * pass mints a bogus Function def named after
2439+ * the base spanning the whole class. Fingerprint:
2440+ * a raw callable def with an IDENTICAL span to
2441+ * the adopted type def whose name is one of the
2442+ * adopted def's base classes. A real method is
2443+ * a strict subset of the class span and never
2444+ * carries a base-class name. */
2445+ if (d -> label && cbm_def_label_is_type (d -> label ) && superseded &&
2446+ d -> base_classes ) {
2447+ for (int j = 0 ; j < defs_before ; j ++ ) {
2448+ CBMDefinition * r = & result -> defs .items [j ];
2449+ if (!r -> label || !r -> name ||
2450+ !cbm_def_label_is_callable (r -> label ) ||
2451+ r -> start_line != d -> start_line ||
2452+ r -> end_line != d -> end_line ) {
2453+ continue ;
2454+ }
2455+ for (const char * * b = d -> base_classes ; * b ; b ++ ) {
2456+ if (strcmp (* b , r -> name ) == 0 ) {
2457+ superseded [j ] = 1 ;
2458+ break ;
2459+ }
2460+ }
2461+ }
2462+ }
2463+ if (rescued_count < 64 ) {
2464+ rescued_spans [rescued_count ].start = d -> start_line ;
2465+ rescued_spans [rescued_count ].end = d -> end_line ;
2466+ rescued_count ++ ;
2467+ }
22512468 result -> defs .items [w ++ ] = * d ;
22522469 }
22532470 }
2471+ if (superseded ) {
2472+ int rw = 0 ;
2473+ for (int j = 0 ; j < defs_before ; j ++ ) {
2474+ if (!superseded [j ]) {
2475+ result -> defs .items [rw ++ ] = result -> defs .items [j ];
2476+ }
2477+ }
2478+ int shift = defs_before - rw ;
2479+ if (shift > 0 && w > defs_before ) {
2480+ memmove (& result -> defs .items [rw ],
2481+ & result -> defs .items [defs_before ],
2482+ (size_t )(w - defs_before ) * sizeof (CBMDefinition ));
2483+ }
2484+ w -= shift ;
2485+ cbm_free (CBM_MEM_CLASS_EXTRACT , superseded );
2486+ }
22542487 result -> defs .count = w ;
22552488 }
22562489 }
0 commit comments