@@ -956,6 +956,101 @@ static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMD
956956 regs -> count = kept ;
957957}
958958
959+ /* #1071: a function-like macro invocation whose argument is a type token
960+ * (e.g. ALLOC(int, n)) makes tree-sitter's C/C++ grammar emit an ERROR node — it
961+ * parses `int` in expression position — which would be recorded as a parse_partial
962+ * coverage gap. But the macro is #defined in THIS file, so nothing is actually
963+ * missing from the graph; it's a benign call the grammar can't parse without the
964+ * preprocessor. True if the [start_line, end_line] span contains a call `NAME(` to
965+ * a file-defined function-like macro (Macro label + a parameter signature). */
966+ static bool cbm_span_is_macro_invocation (const char * src , int src_len , uint32_t start_line ,
967+ uint32_t end_line , const CBMDefArray * defs ) {
968+ if (!src || src_len <= 0 || !defs || start_line == 0 || end_line < start_line ) {
969+ return false;
970+ }
971+ int span_start = 0 ;
972+ uint32_t line = 1 ;
973+ while (span_start < src_len && line < start_line ) {
974+ if (src [span_start ++ ] == '\n' ) {
975+ line ++ ;
976+ }
977+ }
978+ if (line != start_line ) {
979+ return false;
980+ }
981+ int span_end = span_start ;
982+ while (span_end < src_len && line <= end_line ) {
983+ if (src [span_end ++ ] == '\n' ) {
984+ line ++ ;
985+ }
986+ }
987+ for (int di = 0 ; di < defs -> count ; di ++ ) {
988+ const CBMDefinition * d = & defs -> items [di ];
989+ /* Function-like macros only: an object-like macro (#define PI 3.14) has no
990+ * parameter signature and can't be mistaken for a call. */
991+ if (!d -> label || strcmp (d -> label , "Macro" ) != 0 || !d -> signature || !d -> name ||
992+ !d -> name [0 ]) {
993+ continue ;
994+ }
995+ int nlen = (int )strlen (d -> name );
996+ for (int pos = span_start ; pos + nlen <= span_end ; pos ++ ) {
997+ if (strncmp (src + pos , d -> name , (size_t )nlen ) != 0 ||
998+ (pos > 0 && cbm_identifier_char (src [pos - 1 ])) ||
999+ (pos + nlen < src_len && cbm_identifier_char (src [pos + nlen ]))) {
1000+ continue ;
1001+ }
1002+ int open = pos + nlen ;
1003+ while (open < span_end && isspace ((unsigned char )src [open ])) {
1004+ open ++ ;
1005+ }
1006+ if (open < span_end && src [open ] == '(' ) {
1007+ return true; /* NAME( ... ) — an invocation of this file's macro */
1008+ }
1009+ }
1010+ }
1011+ return false;
1012+ }
1013+
1014+ /* True if [rs, re] is fully enclosed by an extracted callable definition (a
1015+ * Function/Method body). A macro invocation INSIDE a real function body is an
1016+ * expression-level use where nothing is missing (#1071). A TOP-LEVEL invocation
1017+ * is different: the macro may itself expand to a definition that the original
1018+ * span doesn't contain (#949), which must stay flagged. Restricting the #1071
1019+ * suppression to in-body calls keeps that #949 gap honest and fails safe. */
1020+ static bool cbm_region_inside_callable (uint32_t rs , uint32_t re , const CBMDefArray * defs ) {
1021+ for (int i = 0 ; i < defs -> count ; i ++ ) {
1022+ const CBMDefinition * d = & defs -> items [i ];
1023+ if (!d -> label ) {
1024+ continue ;
1025+ }
1026+ if (strcmp (d -> label , "Function" ) != 0 && strcmp (d -> label , "Method" ) != 0 &&
1027+ strcmp (d -> label , "Constructor" ) != 0 && strcmp (d -> label , "Destructor" ) != 0 ) {
1028+ continue ;
1029+ }
1030+ if (d -> start_line <= rs && d -> end_line >= re && d -> end_line > d -> start_line ) {
1031+ return true;
1032+ }
1033+ }
1034+ return false;
1035+ }
1036+
1037+ static void cbm_subtract_macro_invocation_regions (cbm_error_regions_t * regs ,
1038+ const CBMDefArray * defs , const char * src ,
1039+ int src_len ) {
1040+ int kept = 0 ;
1041+ for (int i = 0 ; i < regs -> count ; i ++ ) {
1042+ bool benign =
1043+ cbm_span_is_macro_invocation (src , src_len , regs -> starts [i ], regs -> ends [i ], defs ) &&
1044+ cbm_region_inside_callable (regs -> starts [i ], regs -> ends [i ], defs );
1045+ if (!benign ) {
1046+ regs -> starts [kept ] = regs -> starts [i ];
1047+ regs -> ends [kept ] = regs -> ends [i ];
1048+ kept ++ ;
1049+ }
1050+ }
1051+ regs -> count = kept ;
1052+ }
1053+
9591054/* Serialize collected regions as "start-end,start-end,..." into the arena. */
9601055static const char * cbm_error_ranges_str (CBMArena * a , const cbm_error_regions_t * regs ) {
9611056 if (regs -> count <= 0 ) {
@@ -1410,6 +1505,9 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
14101505 cbm_collect_error_regions (root , & regs );
14111506 }
14121507 cbm_subtract_recovered_regions (& regs , & result -> defs );
1508+ /* #1071: don't flag a benign function-like-macro call (defined in-file)
1509+ * that tree-sitter can't parse without the preprocessor. */
1510+ cbm_subtract_macro_invocation_regions (& regs , & result -> defs , source , source_len );
14131511 if (regs .count > 0 ) {
14141512 result -> parse_incomplete = true;
14151513 result -> error_region_count = regs .count ;
0 commit comments