Skip to content

Commit b67d370

Browse files
[clang] Prioritze decl comments from macro expansion site (#65481)
For declarations declared inside a macro, e.g.: ``` `#define MAKE_FUNC(suffix) \ /// Not selected doc comment \ void func_##suffix(void) { } /// Doc comment foo MAKE_FUNC(foo) /// Doc comment bar MAKE_FUNC(bar) ```` Prefer the doc comment at the expansion site instead of the one defined in the macro. rdar://113995729
1 parent 45ccc16 commit b67d370

File tree

3 files changed

+102
-151
lines changed

3 files changed

+102
-151
lines changed

clang/lib/AST/ASTContext.cpp

Lines changed: 69 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,10 @@ enum FloatingRank {
112112
Ibm128Rank
113113
};
114114

115-
/// \returns location that is relevant when searching for Doc comments related
116-
/// to \p D.
117-
static SourceLocation getDeclLocForCommentSearch(const Decl *D,
118-
SourceManager &SourceMgr) {
115+
/// \returns The locations that are relevant when searching for Doc comments
116+
/// related to \p D.
117+
static SmallVector<SourceLocation, 2>
118+
getDeclLocsForCommentSearch(const Decl *D, SourceManager &SourceMgr) {
119119
assert(D);
120120

121121
// User can not attach documentation to implicit declarations.
@@ -167,115 +167,48 @@ static SourceLocation getDeclLocForCommentSearch(const Decl *D,
167167
isa<TemplateTemplateParmDecl>(D))
168168
return {};
169169

170+
SmallVector<SourceLocation, 2> Locations;
170171
// Find declaration location.
171172
// For Objective-C declarations we generally don't expect to have multiple
172173
// declarators, thus use declaration starting location as the "declaration
173174
// location".
174175
// For all other declarations multiple declarators are used quite frequently,
175176
// so we use the location of the identifier as the "declaration location".
177+
SourceLocation BaseLocation;
176178
if (isa<ObjCMethodDecl>(D) || isa<ObjCContainerDecl>(D) ||
177-
isa<ObjCPropertyDecl>(D) ||
178-
isa<RedeclarableTemplateDecl>(D) ||
179+
isa<ObjCPropertyDecl>(D) || isa<RedeclarableTemplateDecl>(D) ||
179180
isa<ClassTemplateSpecializationDecl>(D) ||
180181
// Allow association with Y across {} in `typedef struct X {} Y`.
181182
isa<TypedefDecl>(D))
182-
return D->getBeginLoc();
183+
BaseLocation = D->getBeginLoc();
184+
else
185+
BaseLocation = D->getLocation();
183186

184-
const SourceLocation DeclLoc = D->getLocation();
185-
if (DeclLoc.isMacroID()) {
186-
// There are (at least) three types of macros we care about here.
187-
//
188-
// 1. Macros that are used in the definition of a type outside the macro,
189-
// with a comment attached at the macro call site.
190-
// ```
191-
// #define MAKE_NAME(Foo) Name##Foo
192-
//
193-
// /// Comment is here, where we use the macro.
194-
// struct MAKE_NAME(Foo) {
195-
// int a;
196-
// int b;
197-
// };
198-
// ```
199-
// 2. Macros that define whole things along with the comment.
200-
// ```
201-
// #define MAKE_METHOD(name) \
202-
// /** Comment is here, inside the macro. */ \
203-
// void name() {}
204-
//
205-
// struct S {
206-
// MAKE_METHOD(f)
207-
// }
208-
// ```
209-
// 3. Macros that both declare a type and name a decl outside the macro.
210-
// ```
211-
// /// Comment is here, where we use the macro.
212-
// typedef NS_ENUM(NSInteger, Size) {
213-
// SizeWidth,
214-
// SizeHeight
215-
// };
216-
// ```
217-
// In this case NS_ENUM declares am enum type, and uses the same name for
218-
// the typedef declaration that appears outside the macro. The comment
219-
// here should be applied to both declarations inside and outside the
220-
// macro.
221-
//
222-
// We have found a Decl name that comes from inside a macro, but
223-
// Decl::getLocation() returns the place where the macro is being called.
224-
// If the declaration (and not just the name) resides inside the macro,
225-
// then we want to map Decl::getLocation() into the macro to where the
226-
// declaration and its attached comment (if any) were written.
227-
//
228-
// This mapping into the macro is done by mapping the location to its
229-
// spelling location, however even if the declaration is inside a macro,
230-
// the name's spelling can come from a macro argument (case 2 above). In
231-
// this case mapping the location to the spelling location finds the
232-
// argument's position (at `f` in MAKE_METHOD(`f`) above), which is not
233-
// where the declaration and its comment are located.
234-
//
235-
// To avoid this issue, we make use of Decl::getBeginLocation() instead.
236-
// While the declaration's position is where the name is written, the
237-
// comment is always attached to the begining of the declaration, not to
238-
// the name.
239-
//
240-
// In the first case, the begin location of the decl is outside the macro,
241-
// at the location of `typedef`. This is where the comment is found as
242-
// well. The begin location is not inside a macro, so it's spelling
243-
// location is the same.
244-
//
245-
// In the second case, the begin location of the decl is the call to the
246-
// macro, at `MAKE_METHOD`. However its spelling location is inside the
247-
// the macro at the location of `void`. This is where the comment is found
248-
// again.
249-
//
250-
// In the third case, there's no correct single behaviour. We want to use
251-
// the comment outside the macro for the definition that's inside the macro.
252-
// There is also a definition outside the macro, and we want the comment to
253-
// apply to both. The cases we care about here is NS_ENUM() and
254-
// NS_OPTIONS(). In general, if an enum is defined inside a macro, we should
255-
// try to find the comment there.
256-
257-
// This is handling case 3 for NS_ENUM() and NS_OPTIONS(), which define
258-
// enum types inside the macro.
259-
if (isa<EnumDecl>(D)) {
260-
SourceLocation MacroCallLoc = SourceMgr.getExpansionLoc(DeclLoc);
261-
if (auto BufferRef =
262-
SourceMgr.getBufferOrNone(SourceMgr.getFileID(MacroCallLoc));
263-
BufferRef.has_value()) {
264-
llvm::StringRef buffer = BufferRef->getBuffer().substr(
265-
SourceMgr.getFileOffset(MacroCallLoc));
266-
if (buffer.starts_with("NS_ENUM(") ||
267-
buffer.starts_with("NS_OPTIONS(")) {
268-
// We want to use the comment on the call to NS_ENUM and NS_OPTIONS
269-
// macros for the types defined inside the macros, which is at the
270-
// expansion location.
271-
return MacroCallLoc;
272-
}
273-
}
187+
if (!D->getLocation().isMacroID()) {
188+
Locations.emplace_back(BaseLocation);
189+
} else {
190+
const auto *DeclCtx = D->getDeclContext();
191+
192+
// When encountering definitions generated from a macro (that are not
193+
// contained by another declaration in the macro) we need to try and find
194+
// the comment at the location of the expansion but if there is no comment
195+
// there we should retry to see if there is a comment inside the macro as
196+
// well. To this end we return first BaseLocation to first look at the
197+
// expansion site, the second value is the spelling location of the
198+
// beginning of the declaration defined inside the macro.
199+
if (!(DeclCtx &&
200+
Decl::castFromDeclContext(DeclCtx)->getLocation().isMacroID())) {
201+
Locations.emplace_back(SourceMgr.getExpansionLoc(BaseLocation));
274202
}
275-
return SourceMgr.getSpellingLoc(D->getBeginLoc());
203+
204+
// We use Decl::getBeginLoc() and not just BaseLocation here to ensure that
205+
// we don't refer to the macro argument location at the expansion site (this
206+
// can happen if the name's spelling is provided via macro argument), and
207+
// always to the declaration itself.
208+
Locations.emplace_back(SourceMgr.getSpellingLoc(D->getBeginLoc()));
276209
}
277210

278-
return DeclLoc;
211+
return Locations;
279212
}
280213

281214
RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
@@ -357,30 +290,36 @@ RawComment *ASTContext::getRawCommentForDeclNoCacheImpl(
357290
}
358291

359292
RawComment *ASTContext::getRawCommentForDeclNoCache(const Decl *D) const {
360-
const SourceLocation DeclLoc = getDeclLocForCommentSearch(D, SourceMgr);
293+
const auto DeclLocs = getDeclLocsForCommentSearch(D, SourceMgr);
361294

362-
// If the declaration doesn't map directly to a location in a file, we
363-
// can't find the comment.
364-
if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
365-
return nullptr;
295+
for (const auto DeclLoc : DeclLocs) {
296+
// If the declaration doesn't map directly to a location in a file, we
297+
// can't find the comment.
298+
if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
299+
continue;
366300

367-
if (ExternalSource && !CommentsLoaded) {
368-
ExternalSource->ReadComments();
369-
CommentsLoaded = true;
370-
}
301+
if (ExternalSource && !CommentsLoaded) {
302+
ExternalSource->ReadComments();
303+
CommentsLoaded = true;
304+
}
371305

372-
if (Comments.empty())
373-
return nullptr;
306+
if (Comments.empty())
307+
continue;
374308

375-
const FileID File = SourceMgr.getDecomposedLoc(DeclLoc).first;
376-
if (!File.isValid()) {
377-
return nullptr;
309+
const FileID File = SourceMgr.getDecomposedLoc(DeclLoc).first;
310+
if (!File.isValid())
311+
continue;
312+
313+
const auto CommentsInThisFile = Comments.getCommentsInFile(File);
314+
if (!CommentsInThisFile || CommentsInThisFile->empty())
315+
continue;
316+
317+
if (RawComment *Comment =
318+
getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile))
319+
return Comment;
378320
}
379-
const auto CommentsInThisFile = Comments.getCommentsInFile(File);
380-
if (!CommentsInThisFile || CommentsInThisFile->empty())
381-
return nullptr;
382321

383-
return getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile);
322+
return nullptr;
384323
}
385324

386325
void ASTContext::addComment(const RawComment &RC) {
@@ -584,27 +523,29 @@ void ASTContext::attachCommentsToJustParsedDecls(ArrayRef<Decl *> Decls,
584523
// declaration, but also comments that *follow* the declaration -- thanks to
585524
// the lookahead in the lexer: we've consumed the semicolon and looked
586525
// ahead through comments.
587-
588526
for (const Decl *D : Decls) {
589527
assert(D);
590528
if (D->isInvalidDecl())
591529
continue;
592530

593531
D = &adjustDeclToTemplate(*D);
594532

595-
const SourceLocation DeclLoc = getDeclLocForCommentSearch(D, SourceMgr);
596-
597-
if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
598-
continue;
599-
600533
if (DeclRawComments.count(D) > 0)
601534
continue;
602535

603-
if (RawComment *const DocComment =
604-
getRawCommentForDeclNoCacheImpl(D, DeclLoc, *CommentsInThisFile)) {
605-
cacheRawCommentForDecl(*D, *DocComment);
606-
comments::FullComment *FC = DocComment->parse(*this, PP, D);
607-
ParsedComments[D->getCanonicalDecl()] = FC;
536+
const auto DeclLocs = getDeclLocsForCommentSearch(D, SourceMgr);
537+
538+
for (const auto DeclLoc : DeclLocs) {
539+
if (DeclLoc.isInvalid() || !DeclLoc.isFileID())
540+
continue;
541+
542+
if (RawComment *const DocComment = getRawCommentForDeclNoCacheImpl(
543+
D, DeclLoc, *CommentsInThisFile)) {
544+
cacheRawCommentForDecl(*D, *DocComment);
545+
comments::FullComment *FC = DocComment->parse(*this, PP, D);
546+
ParsedComments[D->getCanonicalDecl()] = FC;
547+
break;
548+
}
608549
}
609550
}
610551
}

clang/test/Index/annotate-comments-objc.m

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,23 @@ - (void)method1_isdoxy4; /*!< method1_isdoxy4 IS_DOXYGEN_SINGLE */
4646
// attach unrelated comments in the following cases where tag decls are
4747
// embedded in declarators.
4848

49-
#define DECLARE_FUNCTIONS(suffix) \
50-
/** functionFromMacro IS_DOXYGEN_SINGLE */ \
51-
void functionFromMacro(void) { \
52-
typedef struct Struct_notdoxy Struct_notdoxy; \
53-
} \
54-
/** functionFromMacroWithSuffix IS_DOXYGEN_SINGLE */ \
55-
void functionFromMacro##suffix(void) { \
56-
typedef struct Struct_notdoxy Struct_notdoxy; \
57-
}
58-
59-
/// IS_DOXYGEN_NOT_ATTACHED
60-
DECLARE_FUNCTIONS(WithSuffix)
49+
#define DECLARE_FUNCTIONS_COMMENTS_IN_MACRO(suffix) \
50+
/** functionFromMacro IS_DOXYGEN_SINGLE */ \
51+
void functionFromMacro(void) { \
52+
typedef struct Struct_notdoxy Struct_notdoxy; \
53+
} \
54+
/** functionFromMacroWithSuffix IS_DOXYGEN_SINGLE */ \
55+
void functionFromMacro##suffix(void) { \
56+
typedef struct Struct_notdoxy Struct_notdoxy; \
57+
}
58+
59+
DECLARE_FUNCTIONS_COMMENTS_IN_MACRO(WithSuffix)
60+
61+
#define DECLARE_FUNCTIONS \
62+
void functionFromMacroWithCommentFromExpansionSite(void) { typedef struct Struct_notdoxy Struct_notdoxy; }
63+
64+
/// functionFromMacroWithCommentFromExpansionSite IS_DOXYGEN_SINGLE
65+
DECLARE_FUNCTIONS
6166

6267
/// typedef_isdoxy1 IS_DOXYGEN_SINGLE
6368
typedef struct Struct_notdoxy *typedef_isdoxy1;
@@ -68,9 +73,14 @@ void functionFromMacro(void) { \
6873
/** namedEnumFromMacro IS_DOXYGEN_SINGLE */ \
6974
enum name { B };
7075

71-
/// IS_DOXYGEN_NOT_ATTACHED
7276
DECLARE_ENUMS(namedEnumFromMacro)
7377

78+
#define MYENUM(name) enum name
79+
struct Foo {
80+
/// Vehicles IS_DOXYGEN_SINGLE
81+
MYENUM(Vehicles) { Car, Motorbike, Boat} a;
82+
};
83+
7484
#endif
7585

7686
// RUN: rm -rf %t
@@ -133,8 +143,10 @@ void functionFromMacro(void) { \
133143
// CHECK: annotate-comments-objc.m:41:22: EnumDecl=An_NS_ENUM_isdoxy1:{{.*}} An_NS_ENUM_isdoxy1 IS_DOXYGEN_SINGLE
134144
// CHECK: annotate-comments-objc.m:41:22: TypedefDecl=An_NS_ENUM_isdoxy1:{{.*}} An_NS_ENUM_isdoxy1 IS_DOXYGEN_SINGLE
135145
// CHECK: annotate-comments-objc.m:41:22: EnumDecl=An_NS_ENUM_isdoxy1:{{.*}} An_NS_ENUM_isdoxy1 IS_DOXYGEN_SINGLE
136-
// CHECK: annotate-comments-objc.m:60:1: FunctionDecl=functionFromMacro:{{.*}} BriefComment=[functionFromMacro IS_DOXYGEN_SINGLE]
137-
// CHECK: annotate-comments-objc.m:60:1: FunctionDecl=functionFromMacroWithSuffix:{{.*}} BriefComment=[functionFromMacroWithSuffix IS_DOXYGEN_SINGLE]
138-
// CHECK: annotate-comments-objc.m:63:32: TypedefDecl=typedef_isdoxy1:{{.*}} typedef_isdoxy1 IS_DOXYGEN_SINGLE
139-
// CHECK: annotate-comments-objc.m:72:1: EnumDecl=enumFromMacro:{{.*}} BriefComment=[enumFromMacro IS_DOXYGEN_SINGLE]
140-
// CHECK: annotate-comments-objc.m:72:15: EnumDecl=namedEnumFromMacro:{{.*}} BriefComment=[namedEnumFromMacro IS_DOXYGEN_SINGLE]
146+
// CHECK: annotate-comments-objc.m:59:1: FunctionDecl=functionFromMacro:{{.*}} BriefComment=[functionFromMacro IS_DOXYGEN_SINGLE]
147+
// CHECK: annotate-comments-objc.m:59:1: FunctionDecl=functionFromMacroWithSuffix:{{.*}} BriefComment=[functionFromMacroWithSuffix IS_DOXYGEN_SINGLE]
148+
// CHECK: annotate-comments-objc.m:65:1: FunctionDecl=functionFromMacroWithCommentFromExpansionSite:{{.*}} BriefComment=[functionFromMacroWithCommentFromExpansionSite IS_DOXYGEN_SINGLE]
149+
// CHECK: annotate-comments-objc.m:68:32: TypedefDecl=typedef_isdoxy1:{{.*}} typedef_isdoxy1 IS_DOXYGEN_SINGLE
150+
// CHECK: annotate-comments-objc.m:76:1: EnumDecl=enumFromMacro:{{.*}} BriefComment=[enumFromMacro IS_DOXYGEN_SINGLE]
151+
// CHECK: annotate-comments-objc.m:76:15: EnumDecl=namedEnumFromMacro:{{.*}} BriefComment=[namedEnumFromMacro IS_DOXYGEN_SINGLE]
152+
// CHECK: annotate-comments-objc.m:81:10: EnumDecl=Vehicles:{{.*}} Vehicles IS_DOXYGEN_SINGLE

clang/unittests/Tooling/SourceCodeTest.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -372,13 +372,11 @@ TEST(SourceCodeTest, getAssociatedRangeWithComments) {
372372
#define DECL /* Comment */ int x
373373
$r[[DECL;]])cpp");
374374

375-
// Does not include comments when only the decl or the comment come from a
376-
// macro.
377-
// FIXME: Change code to allow this.
378375
Visit(R"cpp(
379376
#define DECL int x
380-
// Comment
381-
$r[[DECL;]])cpp");
377+
$r[[// Comment
378+
DECL;]])cpp");
379+
// Does not include comments when only the comment come from a macro.
382380
Visit(R"cpp(
383381
#define COMMENT /* Comment */
384382
COMMENT

0 commit comments

Comments
 (0)