fix(c): a macro alone below an #include <…> no longer hides the next declaration (#2709)

blankLoneMacroLines blanks a macro alone on its line, like
FMT_BEGIN_NAMESPACE, before C and C++ parse, unless the line above ends
in an operator it could be an operand of. A directive like
`#include <glib/gtypes.h>` ends in `>`, so GLib's G_BEGIN_DECLS below it
stayed, and the parser read `G_BEGIN_DECLS typedef struct _GBytes
GBytes;` as one broken declaration. The first declaration of nearly
every GLib header went missing (GBytes, GError, GHashTable, GList,
GQuark, the GIOCondition enum), a few headers grew junk functions (one
named `enum` swallowing a whole header), and the parse error kept the
file off the native kernel.

Objective-C already read a directive as ending at its line (#2691,
directivesEnd). C and C++ now do too, so the flag goes: a directive
continues only through a trailing backslash. The kernel takes C and C++
input from the hoisted preParse, so it needs no change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-20 17:23:20 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 90e2456b43
commit a31bf2f476
3 changed files with 120 additions and 11 deletions
+8 -11
View File
@@ -1213,7 +1213,10 @@ export function blankCppAnnotationMacroCalls(source: string): string {
* (`=`, an operator, `,`, `(`, `?`, `:`, or a `\` macro-definition
* continuation) — so an ALL-CAPS operand split onto its own line inside a
* multi-line expression (`int x =\n SOME_CONST\n | OTHER;`) is left
* alone; and
* alone. A preprocessor directive ends at its own line, even one that
* ends in `>`: GLib's `G_BEGIN_DECLS` below `#include <glib.h>`, and
* Objective-C's `NS_ASSUME_NONNULL_BEGIN` below `#import
* <Foundation/Foundation.h>`, are blanked; and
* - the NEXT line of code starts like a declaration/scope token
* (letter, `_`, `#`, `{`, `}`, or `~`) or the file ends — an operator,
* string literal, or `;` continuation rejects the match. Comments in
@@ -1229,29 +1232,23 @@ const LONE_MACRO_CONTINUATION_END_RE = /[=+\-*/%&|^<>?:,(\\]$/;
interface LoneMacroRules {
/** What the code after the macro may start with. */
nextCode: RegExp;
/** Whether a preprocessor directive above the macro ends at its own line. */
directivesEnd: boolean;
/** Whether a macro that is a body's only statement, between `{` and `}`, stays. */
keepsSoleStatement: boolean;
}
const C_LONE_MACROS: LoneMacroRules = { nextCode: /[A-Za-z_#{}~]/, directivesEnd: false, keepsSoleStatement: false };
const C_LONE_MACROS: LoneMacroRules = { nextCode: /[A-Za-z_#{}~]/, keepsSoleStatement: false };
/**
* Objective-C's: the code after the macro may be a directive, like the
* `@interface` below `NS_ASSUME_NONNULL_BEGIN` or the `@end` below
* `OF_SINGLETON_METHODS`. A preprocessor line above it continues nothing,
* even one that ends in `>`, like the `#import <Foundation/Foundation.h>`
* above most headers' `NS_ASSUME_NONNULL_BEGIN`. Blanking that macro but not
* another before the same `@interface` (`OF_SUBCLASSING_RESTRICTED`) left
* `OF_ASSUME_NONNULL_BEGIN @interface …`, which reads as a function.
* `OF_SINGLETON_METHODS`.
*
* A macro that is a method's whole body, like ObjFW's `{
* OF_UNRECOGNIZED_SELECTOR }`, stays: the parser's error there stays inside
* that body, but blanking it changed how the parser recovered from an
* earlier misread, and OFMutableString.m lost 11 calls in the methods below.
*/
export const OBJC_LONE_MACROS: LoneMacroRules = { nextCode: /[A-Za-z_#{}~@]/, directivesEnd: true, keepsSoleStatement: true };
export const OBJC_LONE_MACROS: LoneMacroRules = { nextCode: /[A-Za-z_#{}~@]/, keepsSoleStatement: true };
export function blankLoneMacroLines(source: string, rules: LoneMacroRules = C_LONE_MACROS): string {
if (!/^[ \t]*[A-Z][A-Z0-9_]{3,}[ \t]*\r?$/m.test(source)) return source;
@@ -1274,7 +1271,7 @@ export function blankLoneMacroLines(source: string, rules: LoneMacroRules = C_LO
// continues nothing either.
const prevText = prev >= 0 ? content(lines[prev] as string) : '';
const prevCode = /^(?:\/\/|\/\*|\*)/.test(prevText) ? '' : prevText.replace(/\/\*.*?\*\/|\/\/.*$/g, '').trim();
const directiveEnds = rules.directivesEnd && prevCode.startsWith('#') && !prevCode.endsWith('\\');
const directiveEnds = prevCode.startsWith('#') && !prevCode.endsWith('\\');
if (prevCode !== '' && !directiveEnds && !/^template\s*</.test(prevCode) && LONE_MACRO_CONTINUATION_END_RE.test(prevCode)) continue;
const first = firstCodeCharAfter(lines, i + 1);
if (first !== undefined && !rules.nextCode.test(first)) continue;