@@ -1029,6 +1029,63 @@ static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_
10291029 return false;
10301030}
10311031
1032+ /* #1989: type-def counterpart of cbm_span_contains_callable_def. A rescued
1033+ * class/struct/enum definition has its name followed by a base clause (':') or
1034+ * a body ('{'), not a parameter list, so the callable validator would reject
1035+ * every type def pulled from the expanded tree. */
1036+ static bool cbm_span_contains_type_def (const char * src , int src_len , uint32_t start_line ,
1037+ uint32_t end_line , const char * name ) {
1038+ if (!src || src_len <= 0 || !name || !name [0 ] || start_line == 0 || end_line < start_line ) {
1039+ return false;
1040+ }
1041+ int span_start = 0 ;
1042+ uint32_t line = 1 ;
1043+ while (span_start < src_len && line < start_line ) {
1044+ if (src [span_start ++ ] == '\n' ) {
1045+ line ++ ;
1046+ }
1047+ }
1048+ if (line != start_line ) {
1049+ return false;
1050+ }
1051+ int span_end = span_start ;
1052+ while (span_end < src_len && line <= end_line ) {
1053+ if (src [span_end ++ ] == '\n' ) {
1054+ line ++ ;
1055+ }
1056+ }
1057+ size_t name_len = strlen (name );
1058+ for (int pos = span_start ; pos + (int )name_len <= span_end ; pos ++ ) {
1059+ if (strncmp (src + pos , name , name_len ) != 0 ||
1060+ (pos > 0 && cbm_identifier_char (src [pos - 1 ])) ||
1061+ (pos + (int )name_len < src_len && cbm_identifier_char (src [pos + name_len ]))) {
1062+ continue ;
1063+ }
1064+ int open = pos + (int )name_len ;
1065+ while (open < span_end && isspace ((unsigned char )src [open ])) {
1066+ open ++ ;
1067+ }
1068+ // Base clause (": public B"), body ("{"), or template args ("<...").
1069+ if (open < span_end && (src [open ] == ':' || src [open ] == '{' || src [open ] == '<' )) {
1070+ return true;
1071+ }
1072+ }
1073+ return false;
1074+ }
1075+
1076+ /* #1989: label buckets for the misparse-correction gates. A raw def extracted
1077+ * from a broken `class MOD_API Foo` shape lands under a callable label while
1078+ * the expanded tree yields a proper type label for the same name. */
1079+ static bool cbm_def_label_is_type (const char * label ) {
1080+ return label != NULL && (strcmp (label , "Class" ) == 0 || strcmp (label , "Enum" ) == 0 ||
1081+ strcmp (label , "Interface" ) == 0 || strcmp (label , "Type" ) == 0 ||
1082+ strcmp (label , "Struct" ) == 0 || strcmp (label , "Union" ) == 0 );
1083+ }
1084+
1085+ static bool cbm_def_label_is_callable (const char * label ) {
1086+ return label != NULL && (strcmp (label , "Function" ) == 0 || strcmp (label , "Method" ) == 0 );
1087+ }
1088+
10321089/* Remap an expanded-source definition back to the original input file. Every
10331090 * line in the definition must be attributable to the main file; generated
10341091 * macro bodies, included headers, and ambiguous spans fail closed. */
@@ -1423,6 +1480,14 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
14231480 // Defs keep original-source line numbers; only CALLS are extracted from expanded source.
14241481 if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA ) {
14251482 uint64_t pp_start = now_ns ();
1483+ /* #1989: collect build-system export-macro candidates from the raw
1484+ * source. They are predefined empty in the expanded buffer (see
1485+ * preprocessor.cpp) so `class MOD_API Foo` parses with its real name,
1486+ * and the rescue loop below can adopt corrected type defs. Bounded:
1487+ * at most CBM_EXPORT_MACRO_MAX names per file. */
1488+ char export_cands [CBM_EXPORT_MACRO_MAX ][CBM_EXPORT_MACRO_NAME_MAX ];
1489+ int export_cand_count =
1490+ cbm_export_macro_candidates (source , source_len , export_cands , CBM_EXPORT_MACRO_MAX );
14261491 CBMPreprocessedSource * preprocessed = cbm_preprocess_with_map (
14271492 source , source_len , rel_path , extra_defines , include_paths , language != CBM_LANG_C );
14281493 if (preprocessed && preprocessed -> source ) {
@@ -1503,44 +1568,211 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
15031568 * recover defs from it — adopting ONLY those that
15041569 * intersect a raw ERROR region, whose name is visible on
15051570 * the raw source line, and whose QN the raw pass did not
1506- * already extract. */
1507- if (ts_node_has_error (root )) {
1571+ * already extract.
1572+ *
1573+ * #1989: build-system export macros (<MOD>_API,
1574+ * <lib>_EXPORT, ...) are empty on the real compile line but
1575+ * opaque to tree-sitter, so `class MOD_API Foo` misparses
1576+ * with the macro as the type name — often with NO raw ERROR
1577+ * region at all (the class_specifier "succeeds" with the
1578+ * wrong name; enums and free functions do error out). When
1579+ * export-macro candidates were collected for this file, also
1580+ * adopt expanded defs that replace a raw def literally named
1581+ * as a candidate, or that correct a raw misparse label (the
1582+ * raw pass extracted the same name as Function/Method from
1583+ * the broken function_definition shape; the expanded tree
1584+ * yields a proper type def). Superseded raw defs are
1585+ * dropped so the corrected definition is not shadowed by
1586+ * the qualified-name dedup. With no candidates the block
1587+ * reduces to the original #961 behavior. */
1588+ if (ts_node_has_error (root ) || export_cand_count > 0 ) {
15081589 cbm_error_regions_t raw_regs = {{0 }, {0 }, 0 };
15091590 cbm_collect_error_regions (root , & raw_regs , source , source_len );
1510- if (raw_regs .count > 0 ) {
1591+ if (raw_regs .count > 0 || export_cand_count > 0 ) {
15111592 int defs_before = result -> defs .count ;
15121593 cbm_extract_definitions (& pp_ctx );
15131594 int w = defs_before ;
1595+ char * superseded = NULL ;
1596+ if (export_cand_count > 0 && defs_before > 0 ) {
1597+ superseded = (char * )calloc ((size_t )defs_before , 1 );
1598+ }
1599+ /* #1989: spans of defs already adopted from the
1600+ * expanded tree. A def nested inside one of these
1601+ * (an inline method of a rescued class) was dropped
1602+ * by the raw pass — the misparsed
1603+ * `class MOD_API Foo {...}` parsed as a
1604+ * function_definition, and nested defs in its body
1605+ * are never walked. Bounded: one span per adopted
1606+ * def, capped at 64. */
1607+ struct {
1608+ uint32_t start ;
1609+ uint32_t end ;
1610+ } rescued_spans [64 ];
1611+ int rescued_count = 0 ;
15141612 for (int i = defs_before ; i < result -> defs .count ; i ++ ) {
15151613 CBMDefinition * d = & result -> defs .items [i ];
15161614 bool adopt = false;
1615+ int supersede_j = -1 ;
15171616 if (cbm_remap_preprocessed_def (d , preprocessed )) {
15181617 for (int rj = 0 ; rj < raw_regs .count && !adopt ; rj ++ ) {
15191618 if (d -> start_line <= raw_regs .ends [rj ] &&
15201619 d -> end_line >= raw_regs .starts [rj ]) {
15211620 adopt = true;
15221621 }
15231622 }
1623+ if (!adopt && export_cand_count > 0 && d -> name ) {
1624+ for (int ci = 0 ; ci < export_cand_count && !adopt ; ci ++ ) {
1625+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
1626+ CBMDefinition * r = & result -> defs .items [j ];
1627+ if (!r -> name ||
1628+ strcmp (r -> name , export_cands [ci ]) != 0 ) {
1629+ continue ;
1630+ }
1631+ if (r -> start_line <= d -> end_line &&
1632+ d -> start_line <= r -> end_line &&
1633+ cbm_span_contains_type_def (
1634+ source , source_len , d -> start_line ,
1635+ d -> end_line , d -> name )) {
1636+ adopt = true;
1637+ supersede_j = j ;
1638+ }
1639+ }
1640+ }
1641+ if (!adopt && d -> label && cbm_def_label_is_type (d -> label )) {
1642+ for (int j = 0 ; j < defs_before && !adopt ; j ++ ) {
1643+ CBMDefinition * r = & result -> defs .items [j ];
1644+ /* Widened past callables (#1989): the
1645+ * misparse also mints value-label raw
1646+ * defs for types (`class X_API FEmpty {}`
1647+ * -> Variable FEmpty). Same name,
1648+ * overlapping span, raw label is NOT a
1649+ * type => raw is the misparse artifact. */
1650+ if (!r -> name || !r -> label || !d -> name ||
1651+ strcmp (r -> name , d -> name ) != 0 ||
1652+ cbm_def_label_is_type (r -> label )) {
1653+ continue ;
1654+ }
1655+ if (r -> start_line <= d -> end_line &&
1656+ d -> start_line <= r -> end_line ) {
1657+ adopt = true;
1658+ supersede_j = j ;
1659+ }
1660+ }
1661+ }
1662+ /* Nested rescue (#1989): a def nested inside
1663+ * an already-adopted def's span — the inline
1664+ * method of a rescued class, dropped by the
1665+ * raw pass because the class parsed as a
1666+ * function body. The same validation gates
1667+ * (name-on-line, shape, QN dedup) still run
1668+ * below before it lands. */
1669+ if (!adopt && rescued_count > 0 ) {
1670+ for (int k = 0 ; k < rescued_count && !adopt ; k ++ ) {
1671+ if (d -> start_line >= rescued_spans [k ].start &&
1672+ d -> end_line <= rescued_spans [k ].end ) {
1673+ adopt = true;
1674+ }
1675+ }
1676+ }
1677+ }
15241678 }
1525- if (adopt && (!d -> name ||
1526- !cbm_line_contains (source , source_len , d -> start_line ,
1527- d -> name ) ||
1528- !cbm_span_contains_callable_def (
1529- source , source_len , d -> start_line , d -> end_line ,
1530- d -> name ))) {
1679+ if (adopt &&
1680+ (!d -> name ||
1681+ !cbm_line_contains (source , source_len , d -> start_line ,
1682+ d -> name ) ||
1683+ (!cbm_span_contains_callable_def (source , source_len ,
1684+ d -> start_line , d -> end_line ,
1685+ d -> name ) &&
1686+ !cbm_span_contains_type_def (source , source_len , d -> start_line ,
1687+ d -> end_line , d -> name )))) {
15311688 adopt = false;
15321689 }
15331690 for (int j = 0 ; j < defs_before && adopt ; j ++ ) {
1691+ if (j == supersede_j || (superseded && superseded [j ])) {
1692+ continue ; // being replaced by this very def
1693+ }
15341694 const char * q = result -> defs .items [j ].qualified_name ;
15351695 if (q && d -> qualified_name &&
15361696 strcmp (q , d -> qualified_name ) == 0 ) {
1697+ /* #1989: a raw def holding the same QN is
1698+ * usually a reject — unless it is the
1699+ * misparse artifact this very def came to
1700+ * replace (raw "Function FCalc" from the
1701+ * broken `class MOD_API FCalc` shape vs the
1702+ * rescued "Class FCalc"). Same name, same
1703+ * span, raw label is not a type: supersede
1704+ * instead of dropping the correction. */
1705+ CBMDefinition * r = & result -> defs .items [j ];
1706+ if (d -> label && cbm_def_label_is_type (d -> label ) &&
1707+ r -> label && r -> name && d -> name &&
1708+ strcmp (r -> name , d -> name ) == 0 &&
1709+ !cbm_def_label_is_type (r -> label ) &&
1710+ r -> start_line == d -> start_line &&
1711+ r -> end_line == d -> end_line && superseded ) {
1712+ superseded [j ] = 1 ;
1713+ continue ;
1714+ }
15371715 adopt = false;
15381716 }
15391717 }
15401718 if (adopt ) {
1719+ if (supersede_j >= 0 && supersede_j < defs_before &&
1720+ superseded ) {
1721+ superseded [supersede_j ] = 1 ;
1722+ }
1723+ /* Phantom-artifact suppression (#1989): on the
1724+ * raw tree `class MOD_API Foo : public Base
1725+ * {...}` parses as a function_definition whose
1726+ * declarator is the BASE-CLASS name, so the raw
1727+ * pass mints a bogus Function def named after
1728+ * the base spanning the whole class. Fingerprint:
1729+ * a raw callable def with an IDENTICAL span to
1730+ * the adopted type def whose name is one of the
1731+ * adopted def's base classes. A real method is
1732+ * a strict subset of the class span and never
1733+ * carries a base-class name. */
1734+ if (d -> label && cbm_def_label_is_type (d -> label ) && superseded &&
1735+ d -> base_classes ) {
1736+ for (int j = 0 ; j < defs_before ; j ++ ) {
1737+ CBMDefinition * r = & result -> defs .items [j ];
1738+ if (!r -> label || !r -> name ||
1739+ !cbm_def_label_is_callable (r -> label ) ||
1740+ r -> start_line != d -> start_line ||
1741+ r -> end_line != d -> end_line ) {
1742+ continue ;
1743+ }
1744+ for (const char * * b = d -> base_classes ; * b ; b ++ ) {
1745+ if (strcmp (* b , r -> name ) == 0 ) {
1746+ superseded [j ] = 1 ;
1747+ break ;
1748+ }
1749+ }
1750+ }
1751+ }
1752+ if (rescued_count < 64 ) {
1753+ rescued_spans [rescued_count ].start = d -> start_line ;
1754+ rescued_spans [rescued_count ].end = d -> end_line ;
1755+ rescued_count ++ ;
1756+ }
15411757 result -> defs .items [w ++ ] = * d ;
15421758 }
15431759 }
1760+ if (superseded ) {
1761+ int rw = 0 ;
1762+ for (int j = 0 ; j < defs_before ; j ++ ) {
1763+ if (!superseded [j ]) {
1764+ result -> defs .items [rw ++ ] = result -> defs .items [j ];
1765+ }
1766+ }
1767+ int shift = defs_before - rw ;
1768+ if (shift > 0 && w > defs_before ) {
1769+ memmove (& result -> defs .items [rw ],
1770+ & result -> defs .items [defs_before ],
1771+ (size_t )(w - defs_before ) * sizeof (CBMDefinition ));
1772+ }
1773+ w -= shift ;
1774+ free (superseded );
1775+ }
15441776 result -> defs .count = w ;
15451777 }
15461778 }
0 commit comments