Skip to content

Commit fb05c1c

Browse files
committed
Adjust BriefCommentRequest to only query swiftdoc if we have it
If we have both loaded a swiftdoc, and the decl we have should have had its doc comment serialized into it, we can check it without needing to fall back to the swiftsourceinfo. This requires a couple of refactorings: - Factoring out the `shouldIncludeDecl` logic into `getDocCommentSerializationTargetFor` for determining whether a doc comment should end up in the swiftdoc or not. - Factoring out `CommentProviderFinder` for searching for the doc providing comment decl for brief comments, in order to allow us to avoid querying the raw comment when searching for it. This has the added bonus of meaning we no longer need to fall back to parsing the raw comment for the brief comment if the comment is provided by another decl in the swiftdoc. This diff is best viewed without whitespace.
1 parent 5025fb1 commit fb05c1c

File tree

9 files changed

+259
-138
lines changed

9 files changed

+259
-138
lines changed

include/swift/AST/Comment.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,25 @@ extractCommentParts(swift::markup::MarkupContext &MC,
111111

112112
/// Extract brief comment from \p RC, and print it to \p OS .
113113
void printBriefComment(RawComment RC, llvm::raw_ostream &OS);
114+
115+
/// Describes the intended serialization target for a doc comment.
116+
enum class DocCommentSerializationTarget : uint8_t {
117+
/// The doc comment should not be serialized.
118+
None = 0,
119+
120+
/// The doc comment should only be serialized in the 'swiftsourceinfo'.
121+
SourceInfoOnly,
122+
123+
/// The doc comment should be serialized in both the 'swiftdoc' and
124+
/// 'swiftsourceinfo'.
125+
SwiftDocAndSourceInfo,
126+
};
127+
128+
/// Retrieve the expected serialization target for a documentation comment
129+
/// attached to the given decl.
130+
DocCommentSerializationTarget
131+
getDocCommentSerializationTargetFor(const Decl *D);
132+
114133
} // namespace swift
115134

116135
#endif // LLVM_SWIFT_AST_COMMENT_H

include/swift/AST/FileUnit.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ class FileUnit : public DeclContext, public ASTAllocated<FileUnit> {
167167
return None;
168168
}
169169

170+
/// For a serialized AST file, returns \c true if an adjacent swiftdoc has been
171+
/// loaded. Otherwise, returns \c false.
172+
virtual bool hasLoadedSwiftDoc() const { return false; }
173+
170174
virtual Optional<StringRef>
171175
getGroupNameForDecl(const Decl *D) const {
172176
return None;

include/swift/Serialization/SerializedModuleLoader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ class SerializedASTFile final : public LoadedFile {
430430

431431
Optional<CommentInfo> getCommentForDecl(const Decl *D) const override;
432432

433+
bool hasLoadedSwiftDoc() const override;
434+
433435
Optional<StringRef> getGroupNameForDecl(const Decl *D) const override;
434436

435437

lib/AST/DocComment.cpp

Lines changed: 132 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -392,85 +392,122 @@ DocComment *swift::getSingleDocComment(swift::markup::MarkupContext &MC,
392392
}
393393

394394
namespace {
395-
const ValueDecl *findOverriddenDeclWithDocComment(const ValueDecl *VD) {
396-
// Only applies to class member.
397-
if (!VD->getDeclContext()->getSelfClassDecl())
398-
return nullptr;
399-
400-
while (auto *baseDecl = VD->getOverriddenDecl()) {
401-
if (!baseDecl->getRawComment().isEmpty())
402-
return baseDecl;
403-
VD = baseDecl;
395+
/// Helper class for finding the comment providing decl for either a brief or
396+
/// raw comment.
397+
template <typename Result>
398+
class CommentProviderFinder final {
399+
using ResultWithDecl = std::pair<Result, const Decl *>;
400+
using VisitFnTy = Optional<Result>(*)(const Decl *);
401+
402+
VisitFnTy VisitFn;
403+
404+
public:
405+
CommentProviderFinder(VisitFnTy visitFn) : VisitFn(visitFn) {}
406+
407+
private:
408+
Optional<ResultWithDecl> visit(const Decl *D) {
409+
// Adapt the provided visitor function to also return the decl.
410+
if (auto result = VisitFn(D))
411+
return {{*result, D}};
412+
return None;
404413
}
405414

406-
return nullptr;
407-
}
415+
Optional<ResultWithDecl> findOverriddenDecl(const ValueDecl *VD) {
416+
// Only applies to class member.
417+
if (!VD->getDeclContext()->getSelfClassDecl())
418+
return None;
408419

409-
const ValueDecl *findDefaultProvidedDeclWithDocComment(const ValueDecl *VD) {
410-
auto protocol = VD->getDeclContext()->getExtendedProtocolDecl();
411-
// Only applies to protocol extension member.
412-
if (!protocol)
413-
return nullptr;
420+
while (auto *baseDecl = VD->getOverriddenDecl()) {
421+
if (auto result = visit(baseDecl))
422+
return result;
414423

415-
ValueDecl *requirement = nullptr;
424+
VD = baseDecl;
425+
}
426+
return None;
427+
}
416428

417-
SmallVector<ValueDecl *, 2> members;
418-
protocol->lookupQualified(const_cast<ProtocolDecl *>(protocol),
419-
DeclNameRef(VD->getName()),
420-
NLOptions::NL_ProtocolMembers,
421-
members);
429+
Optional<ResultWithDecl> findDefaultProvidedDecl(const ValueDecl *VD) {
430+
// Only applies to protocol extension member.
431+
auto *protocol = VD->getDeclContext()->getExtendedProtocolDecl();
432+
if (!protocol)
433+
return None;
434+
435+
SmallVector<ValueDecl *, 2> members;
436+
protocol->lookupQualified(const_cast<ProtocolDecl *>(protocol),
437+
DeclNameRef(VD->getName()),
438+
NLOptions::NL_ProtocolMembers, members);
439+
440+
Optional<ResultWithDecl> result;
441+
for (auto *member : members) {
442+
if (!isa<ProtocolDecl>(member->getDeclContext()) ||
443+
!member->isProtocolRequirement())
444+
continue;
422445

423-
for (auto *member : members) {
424-
if (!isa<ProtocolDecl>(member->getDeclContext()) ||
425-
!member->isProtocolRequirement() || member->getRawComment().isEmpty())
426-
continue;
427-
if (requirement)
428-
// Found two or more decls with doc-comment.
429-
return nullptr;
446+
auto newResult = visit(member);
447+
if (!newResult)
448+
continue;
430449

431-
requirement = member;
450+
if (result) {
451+
// Found two or more decls with doc-comment.
452+
return None;
453+
}
454+
result = newResult;
455+
}
456+
return result;
432457
}
433-
return requirement;
434-
}
435458

436-
const ValueDecl *findRequirementDeclWithDocComment(const ValueDecl *VD) {
437-
std::queue<const ValueDecl *> requirements;
438-
while (true) {
439-
for (auto *req : VD->getSatisfiedProtocolRequirements()) {
440-
if (!req->getRawComment().isEmpty())
441-
return req;
442-
else
459+
Optional<ResultWithDecl> findRequirementDecl(const ValueDecl *VD) {
460+
std::queue<const ValueDecl *> requirements;
461+
while (true) {
462+
for (auto *req : VD->getSatisfiedProtocolRequirements()) {
463+
if (auto result = visit(req))
464+
return result;
465+
443466
requirements.push(req);
467+
}
468+
if (requirements.empty())
469+
return None;
470+
471+
VD = requirements.front();
472+
requirements.pop();
444473
}
445-
if (requirements.empty())
446-
return nullptr;
447-
VD = requirements.front();
448-
requirements.pop();
449474
}
450-
}
451-
} // namespace
452475

453-
const Decl *swift::getDocCommentProvidingDecl(const Decl *D) {
454-
if (!D->canHaveComment())
455-
return nullptr;
476+
public:
477+
Optional<ResultWithDecl> findCommentProvider(const Decl *D) {
478+
if (auto result = visit(D))
479+
return result;
456480

457-
if (!D->getRawComment().isEmpty())
458-
return D;
481+
auto *VD = dyn_cast<ValueDecl>(D);
482+
if (!VD)
483+
return None;
459484

460-
auto *VD = dyn_cast<ValueDecl>(D);
461-
if (!VD)
462-
return nullptr;
485+
if (auto result = findOverriddenDecl(VD))
486+
return result;
463487

464-
if (auto *overridden = findOverriddenDeclWithDocComment(VD))
465-
return overridden;
488+
if (auto result = findDefaultProvidedDecl(VD))
489+
return result;
466490

467-
if (auto *requirement = findDefaultProvidedDeclWithDocComment(VD))
468-
return requirement;
491+
if (auto result = findRequirementDecl(VD))
492+
return result;
469493

470-
if (auto *requirement = findRequirementDeclWithDocComment(VD))
471-
return requirement;
494+
return None;
495+
}
496+
};
497+
} // end anonymous namespace
472498

473-
return nullptr;
499+
const Decl *swift::getDocCommentProvidingDecl(const Decl *D) {
500+
// Search for the first decl we see with a non-empty raw comment.
501+
auto finder = CommentProviderFinder<RawComment>(
502+
[](const Decl *D) -> Optional<RawComment> {
503+
auto comment = D->getRawComment();
504+
if (comment.isEmpty())
505+
return None;
506+
return comment;
507+
});
508+
509+
auto result = finder.findCommentProvider(D);
510+
return result ? result->second : nullptr;
474511
}
475512

476513
DocComment *swift::getCascadingDocComment(swift::markup::MarkupContext &MC,
@@ -499,31 +536,50 @@ DocComment *swift::getCascadingDocComment(swift::markup::MarkupContext &MC,
499536
return doc;
500537
}
501538

502-
StringRef
503-
BriefCommentRequest::evaluate(Evaluator &evaluator, const Decl *D) const {
504-
auto *DC = D->getDeclContext();
505-
auto &ctx = DC->getASTContext();
539+
/// Retrieve the brief comment for a given decl \p D, without attempting to
540+
/// walk any requirements or overrides.
541+
static Optional<StringRef> getDirectBriefComment(const Decl *D) {
542+
if (!D->canHaveComment())
543+
return None;
506544

507-
// Check if the brief comment is available in the swiftdoc.
508-
if (auto *Unit = dyn_cast<FileUnit>(DC->getModuleScopeContext())) {
509-
if (auto C = Unit->getCommentForDecl(D))
510-
return C->Brief;
545+
auto *ModuleDC = D->getDeclContext()->getModuleScopeContext();
546+
auto &Ctx = ModuleDC->getASTContext();
547+
548+
// If we expect the comment to be in the swiftdoc, check for it if we loaded a
549+
// swiftdoc. If missing from the swiftdoc, we know it will not be in the
550+
// swiftsourceinfo either, so we can bail early.
551+
if (auto *Unit = dyn_cast<FileUnit>(ModuleDC)) {
552+
if (Unit->hasLoadedSwiftDoc()) {
553+
auto target = getDocCommentSerializationTargetFor(D);
554+
if (target == DocCommentSerializationTarget::SwiftDocAndSourceInfo) {
555+
auto C = Unit->getCommentForDecl(D);
556+
if (!C)
557+
return None;
558+
559+
return C->Brief;
560+
}
561+
}
511562
}
512563

513-
// Otherwise, parse the brief from the raw comment itself. This will look into the
514-
// swiftsourceinfo if needed.
564+
// Otherwise, parse the brief from the raw comment itself. This will look into
565+
// the swiftsourceinfo if needed.
515566
auto RC = D->getRawComment();
516-
if (RC.isEmpty()) {
517-
if (auto *docD = getDocCommentProvidingDecl(D))
518-
RC = docD->getRawComment();
519-
}
520567
if (RC.isEmpty())
521-
return StringRef();
568+
return None;
522569

523570
SmallString<256> BriefStr;
524571
llvm::raw_svector_ostream OS(BriefStr);
525572
printBriefComment(RC, OS);
526-
return ctx.AllocateCopy(BriefStr.str());
573+
return Ctx.AllocateCopy(BriefStr.str());
574+
}
575+
576+
StringRef BriefCommentRequest::evaluate(Evaluator &evaluator,
577+
const Decl *D) const {
578+
// Perform a walk over the potential providers of the brief comment,
579+
// retrieving the first one we come across.
580+
CommentProviderFinder finder(getDirectBriefComment);
581+
auto result = finder.findCommentProvider(D);
582+
return result ? result->first : StringRef();
527583
}
528584

529585
StringRef Decl::getBriefComment() const {

lib/AST/RawComment.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,78 @@ CharSourceRange RawComment::getCharSourceRange() {
236236
static_cast<const char *>(Start.getOpaquePointerValue());
237237
return CharSourceRange(Start, Length);
238238
}
239+
240+
static bool hasDoubleUnderscore(const Decl *D) {
241+
// Exclude decls with double-underscored names, either in arguments or
242+
// base names.
243+
static StringRef Prefix = "__";
244+
245+
// If it's a function or subscript with a parameter with leading
246+
// double underscore, it's a private function or subscript.
247+
if (isa<AbstractFunctionDecl>(D) || isa<SubscriptDecl>(D)) {
248+
auto *params = getParameterList(cast<ValueDecl>(const_cast<Decl *>(D)));
249+
if (params->hasInternalParameter(Prefix))
250+
return true;
251+
}
252+
253+
if (auto *VD = dyn_cast<ValueDecl>(D)) {
254+
auto Name = VD->getBaseName();
255+
if (!Name.isSpecial() && Name.getIdentifier().str().startswith(Prefix)) {
256+
return true;
257+
}
258+
}
259+
return false;
260+
}
261+
262+
static DocCommentSerializationTarget
263+
getDocCommentSerializationTargetImpl(const Decl *D) {
264+
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
265+
auto *extended = ED->getExtendedNominal();
266+
if (!extended)
267+
return DocCommentSerializationTarget::None;
268+
269+
return getDocCommentSerializationTargetFor(extended);
270+
}
271+
auto *VD = dyn_cast<ValueDecl>(D);
272+
if (!VD)
273+
return DocCommentSerializationTarget::None;
274+
275+
// The use of getEffectiveAccess is unusual here; we want to take the
276+
// testability state into account and emit documentation if and only if they
277+
// are visible to clients (which means public ordinarily, but public+internal
278+
// when testing enabled).
279+
switch (VD->getEffectiveAccess()) {
280+
case AccessLevel::Private:
281+
case AccessLevel::FilePrivate:
282+
case AccessLevel::Internal:
283+
// There's no point serializing anything internal or below, as they are not
284+
// accessible outside their defining module.
285+
return DocCommentSerializationTarget::None;
286+
case AccessLevel::Package:
287+
// Package doc comments can be referenced outside their module, but only
288+
// locally, so can't be included in swiftdoc.
289+
return DocCommentSerializationTarget::SourceInfoOnly;
290+
case AccessLevel::Public:
291+
case AccessLevel::Open:
292+
return DocCommentSerializationTarget::SwiftDocAndSourceInfo;
293+
}
294+
llvm_unreachable("Unhandled case in switch!");
295+
}
296+
297+
DocCommentSerializationTarget
298+
swift::getDocCommentSerializationTargetFor(const Decl *D) {
299+
auto Limit = DocCommentSerializationTarget::SwiftDocAndSourceInfo;
300+
301+
// We can't include SPI decls in swiftdoc.
302+
if (D->isSPI())
303+
Limit = DocCommentSerializationTarget::SourceInfoOnly;
304+
305+
// .swiftdoc doesn't include comments for double underscored symbols, but
306+
// for .swiftsourceinfo, having the source location for these symbols isn't
307+
// a concern because these symbols are in .swiftinterface anyway.
308+
if (hasDoubleUnderscore(D))
309+
Limit = DocCommentSerializationTarget::SourceInfoOnly;
310+
311+
auto Result = getDocCommentSerializationTargetImpl(D);
312+
return std::min(Result, Limit);
313+
}

lib/Serialization/ModuleFile.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,10 @@ Optional<CommentInfo> ModuleFile::getCommentForDecl(const Decl *D) const {
10651065
return getCommentForDeclByUSR(USRBuffer.str());
10661066
}
10671067

1068+
bool ModuleFile::hasLoadedSwiftDoc() const {
1069+
return Core->DeclCommentTable != nullptr;
1070+
}
1071+
10681072
void ModuleFile::collectSerializedSearchPath(
10691073
llvm::function_ref<void(StringRef)> callback) const {
10701074
for (auto path: Core->SearchPaths) {

lib/Serialization/ModuleFile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ class ModuleFile
883883
Optional<unsigned> getSourceOrderForDecl(const Decl *D) const;
884884
void collectAllGroups(SmallVectorImpl<StringRef> &Names) const;
885885
Optional<CommentInfo> getCommentForDecl(const Decl *D) const;
886+
bool hasLoadedSwiftDoc() const;
886887
Optional<CommentInfo> getCommentForDeclByUSR(StringRef USR) const;
887888
Optional<StringRef> getGroupNameByUSR(StringRef USR) const;
888889
Optional<ExternalSourceLocs::RawLocs>

0 commit comments

Comments
 (0)