@@ -1021,6 +1021,63 @@ static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_
10211021 return false;
10221022}
10231023
1024+ /* #1989: type-def counterpart of cbm_span_contains_callable_def. A rescued
1025+ * class/struct/enum definition has its name followed by a base clause (':') or
1026+ * a body ('{'), not a parameter list, so the callable validator would reject
1027+ * every type def pulled from the expanded tree. */
1028+ static bool cbm_span_contains_type_def (const char * src , int src_len , uint32_t start_line ,
1029+ uint32_t end_line , const char * name ) {
1030+ if (!src || src_len <= 0 || !name || !name [0 ] || start_line == 0 || end_line < start_line ) {
1031+ return false;
1032+ }
1033+ int span_start = 0 ;
1034+ uint32_t line = 1 ;
1035+ while (span_start < src_len && line < start_line ) {
1036+ if (src [span_start ++ ] == '\n' ) {
1037+ line ++ ;
1038+ }
1039+ }
1040+ if (line != start_line ) {
1041+ return false;
1042+ }
1043+ int span_end = span_start ;
1044+ while (span_end < src_len && line <= end_line ) {
1045+ if (src [span_end ++ ] == '\n' ) {
1046+ line ++ ;
1047+ }
1048+ }
1049+ size_t name_len = strlen (name );
1050+ for (int pos = span_start ; pos + (int )name_len <= span_end ; pos ++ ) {
1051+ if (strncmp (src + pos , name , name_len ) != 0 ||
1052+ (pos > 0 && cbm_identifier_char (src [pos - 1 ])) ||
1053+ (pos + (int )name_len < src_len && cbm_identifier_char (src [pos + name_len ]))) {
1054+ continue ;
1055+ }
1056+ int open = pos + (int )name_len ;
1057+ while (open < span_end && isspace ((unsigned char )src [open ])) {
1058+ open ++ ;
1059+ }
1060+ // Base clause (": public B"), body ("{"), or template args ("<...").
1061+ if (open < span_end && (src [open ] == ':' || src [open ] == '{' || src [open ] == '<' )) {
1062+ return true;
1063+ }
1064+ }
1065+ return false;
1066+ }
1067+
1068+ /* #1989: label buckets for the misparse-correction gates. A raw def extracted
1069+ * from a broken `class MOD_API Foo` shape lands under a callable label while
1070+ * the expanded tree yields a proper type label for the same name. */
1071+ static bool cbm_def_label_is_type (const char * label ) {
1072+ return label != NULL && (strcmp (label , "Class" ) == 0 || strcmp (label , "Enum" ) == 0 ||
1073+ strcmp (label , "Interface" ) == 0 || strcmp (label , "Type" ) == 0 ||
1074+ strcmp (label , "Struct" ) == 0 || strcmp (label , "Union" ) == 0 );
1075+ }
1076+
1077+ static bool cbm_def_label_is_callable (const char * label ) {
1078+ return label != NULL && (strcmp (label , "Function" ) == 0 || strcmp (label , "Method" ) == 0 );
1079+ }
1080+
10241081/* Remap an expanded-source definition back to the original input file. Every
10251082 * line in the definition must be attributable to the main file; generated
10261083 * macro bodies, included headers, and ambiguous spans fail closed. */
@@ -1398,6 +1455,14 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
13981455 // Defs keep original-source line numbers; only CALLS are extracted from expanded source.
13991456 if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA ) {
14001457 uint64_t pp_start = now_ns ();
1458+ /* #1989: collect build-system export-macro candidates from the raw
1459+ * source. They are predefined empty in the expanded buffer (see
1460+ * preprocessor.cpp) so `class MOD_API Foo` parses with its real name,
1461+ * and the rescue loop below can adopt corrected type defs. Bounded:
1462+ * at most CBM_EXPORT_MACRO_MAX names per file. */
1463+ char export_cands [CBM_EXPORT_MACRO_MAX ][CBM_EXPORT_MACRO_NAME_MAX ];
1464+ int export_cand_count =
1465+ cbm_export_macro_candidates (source , source_len , export_cands , CBM_EXPORT_MACRO_MAX );
14011466 CBMPreprocessedSource * preprocessed = cbm_preprocess_with_map (
14021467 source , source_len , rel_path , extra_defines , include_paths , language != CBM_LANG_C );
14031468 if (preprocessed && preprocessed -> source ) {
@@ -1477,44 +1542,211 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
14771542 * recover defs from it — adopting ONLY those that
14781543 * intersect a raw ERROR region, whose name is visible on
14791544 * the raw source line, and whose QN the raw pass did not
1480- * already extract. */
1481- if (ts_node_has_error (root )) {
1545+ * already extract.
1546+ *
1547+ * #1989: build-system export macros (<MOD>_API,
1548+ * <lib>_EXPORT, ...) are empty on the real compile line but
1549+ * opaque to tree-sitter, so `class MOD_API Foo` misparses
1550+ * with the macro as the type name — often with NO raw ERROR
1551+ * region at all (the class_specifier "succeeds" with the
1552+ * wrong name; enums and free functions do error out). When
1553+ * export-macro candidates were collected for this file, also
1554+ * adopt expanded defs that replace a raw def literally named
1555+ * as a candidate, or that correct a raw misparse label (the
1556+ * raw pass extracted the same name as Function/Method from
1557+ * the broken function_definition shape; the expanded tree
1558+ * yields a proper type def). Superseded raw defs are
1559+ * dropped so the corrected definition is not shadowed by
1560+ * the qualified-name dedup. With no candidates the block
1561+ * reduces to the original #961 behavior. */
1562+ if (ts_node_has_error (root ) || export_cand_count > 0 ) {
14821563 cbm_error_regions_t raw_regs = {{0 }, {0 }, 0 };
14831564 cbm_collect_error_regions (root , & raw_regs , source , source_len );
1484- if (raw_regs .count > 0 ) {
1565+ if (raw_regs .count > 0 || export_cand_count > 0 ) {
14851566 int defs_before = result -> defs .count ;
14861567 cbm_extract_definitions (& pp_ctx );
14871568 int w = defs_before ;
1569+ char * superseded = NULL ;
1570+ if (export_cand_count > 0 && defs_before > 0 ) {
1571+ superseded = (char * )calloc ((size_t )defs_before , 1 );
1572+ }
1573+ /* #1989: spans of defs already adopted from the
1574+ * expanded tree. A def nested inside one of these
1575+ * (an inline method of a rescued class) was dropped
1576+ * by the raw pass — the misparsed
1577+ * `class MOD_API Foo {...}` parsed as a
1578+ * function_definition, and nested defs in its body
1579+ * are never walked. Bounded: one span per adopted
1580+ * def, capped at 64. */
1581+ struct {
1582+ uint32_t start ;
1583+ uint32_t end ;
1584+ } rescued_spans [64 ];
1585+ int rescued_count = 0 ;
14881586 for (int i = defs_before ; i < result -> defs .count ; i ++ ) {
14891587 CBMDefinition * d = & result -> defs .items [i ];
14901588 bool adopt = false;
1589+ int supersede_j = -1 ;
14911590 if (cbm_remap_preprocessed_def (d , preprocessed )) {
14921591 for (int rj = 0 ; rj < raw_regs .count && !adopt ; rj ++ ) {
14931592 if (d -> start_line <= raw_regs .ends [rj ] &&
14941593 d -> end_line >= raw_regs .starts [rj ]) {
14951594 adopt = true;
14961595 }
14971596 }
1597+ if (!adopt && export_cand_count > 0 && d -> name ) {
1598+ for (int ci = 0 ; ci < export_cand_count && !adopt ; ci ++ ) {
1599+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
1600+ CBMDefinition * r = & result -> defs .items [j ];
1601+ if (!r -> name ||
1602+ strcmp (r -> name , export_cands [ci ]) != 0 ) {
1603+ continue ;
1604+ }
1605+ if (r -> start_line <= d -> end_line &&
1606+ d -> start_line <= r -> end_line &&
1607+ cbm_span_contains_type_def (
1608+ source , source_len , d -> start_line ,
1609+ d -> end_line , d -> name )) {
1610+ adopt = true;
1611+ supersede_j = j ;
1612+ }
1613+ }
1614+ }
1615+ if (!adopt && d -> label && cbm_def_label_is_type (d -> label )) {
1616+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
1617+ CBMDefinition * r = & result -> defs .items [j ];
1618+ /* Widened past callables (#1989): the
1619+ * misparse also mints value-label raw
1620+ * defs for types (`class X_API FEmpty {}`
1621+ * -> Variable FEmpty). Same name,
1622+ * overlapping span, raw label is NOT a
1623+ * type => raw is the misparse artifact. */
1624+ if (!r -> name || !r -> label || !d -> name ||
1625+ strcmp (r -> name , d -> name ) != 0 ||
1626+ cbm_def_label_is_type (r -> label )) {
1627+ continue ;
1628+ }
1629+ if (r -> start_line <= d -> end_line &&
1630+ d -> start_line <= r -> end_line ) {
1631+ adopt = true;
1632+ supersede_j = j ;
1633+ }
1634+ }
1635+ }
1636+ /* Nested rescue (#1989): a def nested inside
1637+ * an already-adopted def's span — the inline
1638+ * method of a rescued class, dropped by the
1639+ * raw pass because the class parsed as a
1640+ * function body. The same validation gates
1641+ * (name-on-line, shape, QN dedup) still run
1642+ * below before it lands. */
1643+ if (!adopt && rescued_count > 0 ) {
1644+ for (int k = 0 ; k < rescued_count && !adopt ; k ++ ) {
1645+ if (d -> start_line >= rescued_spans [k ].start &&
1646+ d -> end_line <= rescued_spans [k ].end ) {
1647+ adopt = true;
1648+ }
1649+ }
1650+ }
1651+ }
14981652 }
1499- if (adopt && (!d -> name ||
1500- !cbm_line_contains (source , source_len , d -> start_line ,
1501- d -> name ) ||
1502- !cbm_span_contains_callable_def (
1503- source , source_len , d -> start_line , d -> end_line ,
1504- d -> name ))) {
1653+ if (adopt &&
1654+ (!d -> name ||
1655+ !cbm_line_contains (source , source_len , d -> start_line ,
1656+ d -> name ) ||
1657+ (!cbm_span_contains_callable_def (source , source_len ,
1658+ d -> start_line , d -> end_line ,
1659+ d -> name ) &&
1660+ !cbm_span_contains_type_def (source , source_len , d -> start_line ,
1661+ d -> end_line , d -> name )))) {
15051662 adopt = false;
15061663 }
15071664 for (int j = 0 ; j < defs_before && adopt ; j ++ ) {
1665+ if (j == supersede_j || (superseded && superseded [j ])) {
1666+ continue ; // being replaced by this very def
1667+ }
15081668 const char * q = result -> defs .items [j ].qualified_name ;
15091669 if (q && d -> qualified_name &&
15101670 strcmp (q , d -> qualified_name ) == 0 ) {
1671+ /* #1989: a raw def holding the same QN is
1672+ * usually a reject — unless it is the
1673+ * misparse artifact this very def came to
1674+ * replace (raw "Function FCalc" from the
1675+ * broken `class MOD_API FCalc` shape vs the
1676+ * rescued "Class FCalc"). Same name, same
1677+ * span, raw label is not a type: supersede
1678+ * instead of dropping the correction. */
1679+ CBMDefinition * r = & result -> defs .items [j ];
1680+ if (d -> label && cbm_def_label_is_type (d -> label ) &&
1681+ r -> label && r -> name && d -> name &&
1682+ strcmp (r -> name , d -> name ) == 0 &&
1683+ !cbm_def_label_is_type (r -> label ) &&
1684+ r -> start_line == d -> start_line &&
1685+ r -> end_line == d -> end_line && superseded ) {
1686+ superseded [j ] = 1 ;
1687+ continue ;
1688+ }
15111689 adopt = false;
15121690 }
15131691 }
15141692 if (adopt ) {
1693+ if (supersede_j >= 0 && supersede_j < defs_before &&
1694+ superseded ) {
1695+ superseded [supersede_j ] = 1 ;
1696+ }
1697+ /* Phantom-artifact suppression (#1989): on the
1698+ * raw tree `class MOD_API Foo : public Base
1699+ * {...}` parses as a function_definition whose
1700+ * declarator is the BASE-CLASS name, so the raw
1701+ * pass mints a bogus Function def named after
1702+ * the base spanning the whole class. Fingerprint:
1703+ * a raw callable def with an IDENTICAL span to
1704+ * the adopted type def whose name is one of the
1705+ * adopted def's base classes. A real method is
1706+ * a strict subset of the class span and never
1707+ * carries a base-class name. */
1708+ if (d -> label && cbm_def_label_is_type (d -> label ) && superseded &&
1709+ d -> base_classes ) {
1710+ for (int j = 0 ; j < defs_before ; j ++ ) {
1711+ CBMDefinition * r = & result -> defs .items [j ];
1712+ if (!r -> label || !r -> name ||
1713+ !cbm_def_label_is_callable (r -> label ) ||
1714+ r -> start_line != d -> start_line ||
1715+ r -> end_line != d -> end_line ) {
1716+ continue ;
1717+ }
1718+ for (const char * * b = d -> base_classes ; * b ; b ++ ) {
1719+ if (strcmp (* b , r -> name ) == 0 ) {
1720+ superseded [j ] = 1 ;
1721+ break ;
1722+ }
1723+ }
1724+ }
1725+ }
1726+ if (rescued_count < 64 ) {
1727+ rescued_spans [rescued_count ].start = d -> start_line ;
1728+ rescued_spans [rescued_count ].end = d -> end_line ;
1729+ rescued_count ++ ;
1730+ }
15151731 result -> defs .items [w ++ ] = * d ;
15161732 }
15171733 }
1734+ if (superseded ) {
1735+ int rw = 0 ;
1736+ for (int j = 0 ; j < defs_before ; j ++ ) {
1737+ if (!superseded [j ]) {
1738+ result -> defs .items [rw ++ ] = result -> defs .items [j ];
1739+ }
1740+ }
1741+ int shift = defs_before - rw ;
1742+ if (shift > 0 && w > defs_before ) {
1743+ memmove (& result -> defs .items [rw ],
1744+ & result -> defs .items [defs_before ],
1745+ (size_t )(w - defs_before ) * sizeof (CBMDefinition ));
1746+ }
1747+ w -= shift ;
1748+ free (superseded );
1749+ }
15181750 result -> defs .count = w ;
15191751 }
15201752 }
0 commit comments