[AST] Inherit doc-brief comment from protocol, superclass, and requirement

rdar://problem/38422822
This commit is contained in:
Rintaro Ishizaki
2019-03-20 11:01:23 -07:00
parent bb2e003a21
commit db2c11787b
17 changed files with 361 additions and 224 deletions

View File

@@ -16,12 +16,14 @@
///
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTContext.h"
#include "swift/AST/Comment.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Types.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/RawComment.h"
#include "swift/Markup/Markup.h"
#include <queue>
using namespace swift;
@@ -30,6 +32,7 @@ void *DocComment::operator new(size_t Bytes, swift::markup::MarkupContext &MC,
return MC.allocate(Bytes, Alignment);
}
namespace {
Optional<swift::markup::ParamField *> extractParamOutlineItem(
swift::markup::MarkupContext &MC,
swift::markup::MarkupASTNode *Node) {
@@ -282,6 +285,21 @@ bool extractSimpleField(
return NormalItems.empty();
}
} // namespace
void swift::printBriefComment(RawComment RC, llvm::raw_ostream &OS) {
markup::MarkupContext MC;
markup::LineList LL = MC.getLineList(RC);
auto *markupDoc = markup::parseDocument(MC, LL);
auto children = markupDoc->getChildren();
if (children.empty())
return;
auto FirstParagraph = dyn_cast<swift::markup::Paragraph>(children.front());
if (!FirstParagraph)
return;
swift::markup::printInlinesUnder(FirstParagraph, OS);
}
swift::markup::CommentParts
swift::extractCommentParts(swift::markup::MarkupContext &MC,
@@ -334,126 +352,167 @@ swift::extractCommentParts(swift::markup::MarkupContext &MC,
return Parts;
}
Optional<DocComment *>
swift::getSingleDocComment(swift::markup::MarkupContext &MC, const Decl *D) {
DocComment *DocComment::create(markup::MarkupContext &MC, RawComment RC) {
assert(!RC.isEmpty());
swift::markup::LineList LL = MC.getLineList(RC);
auto *Doc = swift::markup::parseDocument(MC, LL);
auto Parts = extractCommentParts(MC, Doc);
return new (MC) DocComment(Doc, Parts);
}
void DocComment::addInheritanceNote(swift::markup::MarkupContext &MC,
TypeDecl *base) {
auto text = MC.allocateCopy("This documentation comment was inherited from ");
auto name = MC.allocateCopy(base->getNameStr());
auto period = MC.allocateCopy(".");
auto paragraph = markup::Paragraph::create(MC, {
markup::Text::create(MC, text),
markup::Code::create(MC, name),
markup::Text::create(MC, period)});
auto note = markup::NoteField::create(MC, {paragraph});
SmallVector<const markup::MarkupASTNode *, 8> BodyNodes{
Parts.BodyNodes.begin(), Parts.BodyNodes.end()};
BodyNodes.push_back(note);
Parts.BodyNodes = MC.allocateCopy(llvm::makeArrayRef(BodyNodes));
}
DocComment *swift::getSingleDocComment(swift::markup::MarkupContext &MC,
const Decl *D) {
PrettyStackTraceDecl StackTrace("parsing comment for", D);
auto RC = D->getRawComment();
if (RC.isEmpty())
return None;
swift::markup::LineList LL = MC.getLineList(RC);
auto *Doc = swift::markup::parseDocument(MC, LL);
auto Parts = extractCommentParts(MC, Doc);
return new (MC) DocComment(D, Doc, Parts);
}
static Optional<DocComment *>
getAnyBaseClassDocComment(swift::markup::MarkupContext &MC,
const ClassDecl *CD,
const Decl *D) {
RawComment RC;
if (const auto *VD = dyn_cast<ValueDecl>(D)) {
const auto *BaseDecl = VD->getOverriddenDecl();
while (BaseDecl) {
RC = BaseDecl->getRawComment();
if (!RC.isEmpty()) {
swift::markup::LineList LL = MC.getLineList(RC);
auto *Doc = swift::markup::parseDocument(MC, LL);
auto Parts = extractCommentParts(MC, Doc);
SmallString<48> RawCascadeText;
llvm::raw_svector_ostream OS(RawCascadeText);
OS << "This documentation comment was inherited from ";
auto *Text = swift::markup::Text::create(MC, MC.allocateCopy(OS.str()));
auto BaseClass = BaseDecl->getDeclContext()->getSelfClassDecl();
auto *BaseClassMonospace =
swift::markup::Code::create(MC,
MC.allocateCopy(BaseClass->getNameStr()));
auto *Period = swift::markup::Text::create(MC, ".");
auto *Para = swift::markup::Paragraph::create(MC, {
Text, BaseClassMonospace, Period
});
auto CascadeNote = swift::markup::NoteField::create(MC, {Para});
SmallVector<const swift::markup::MarkupASTNode *, 8> BodyNodes {
Parts.BodyNodes.begin(),
Parts.BodyNodes.end()
};
BodyNodes.push_back(CascadeNote);
Parts.BodyNodes = MC.allocateCopy(llvm::makeArrayRef(BodyNodes));
return new (MC) DocComment(D, Doc, Parts);
}
BaseDecl = BaseDecl->getOverriddenDecl();
}
}
return None;
}
static Optional<DocComment *>
getProtocolRequirementDocComment(swift::markup::MarkupContext &MC,
const ProtocolDecl *ProtoExt,
const Decl *D) {
auto getSingleRequirementWithNonemptyDoc = [](const ProtocolDecl *P,
const ValueDecl *VD)
-> const ValueDecl * {
SmallVector<ValueDecl *, 2> Members;
P->lookupQualified(const_cast<ProtocolDecl *>(P),
VD->getFullName(),
NLOptions::NL_ProtocolMembers,
Members);
SmallVector<const ValueDecl *, 1> ProtocolRequirements;
for (auto Member : Members)
if (isa<ProtocolDecl>(Member->getDeclContext()) &&
Member->isProtocolRequirement())
ProtocolRequirements.push_back(Member);
if (ProtocolRequirements.size() == 1) {
auto Requirement = ProtocolRequirements.front();
if (!Requirement->getRawComment().isEmpty())
return Requirement;
}
return nullptr;
};
return DocComment::create(MC, RC);
}
if (const auto *VD = dyn_cast<ValueDecl>(D)) {
SmallVector<const ValueDecl *, 4> RequirementsWithDocs;
if (auto Requirement = getSingleRequirementWithNonemptyDoc(ProtoExt, VD))
RequirementsWithDocs.push_back(Requirement);
namespace {
const ValueDecl *findOverriddenDeclWithDocComment(const ValueDecl *VD) {
// Only applies to class member.
if (!VD->getDeclContext()->getSelfClassDecl())
return nullptr;
if (RequirementsWithDocs.size() == 1)
return getSingleDocComment(MC, RequirementsWithDocs.front());
while (auto *baseDecl = VD->getOverriddenDecl()) {
if (!baseDecl->getRawComment().isEmpty())
return baseDecl;
VD = baseDecl;
}
return None;
return nullptr;
}
Optional<DocComment *>
const ValueDecl *findDefaultProvidedDeclWithDocComment(const ValueDecl *VD) {
auto protocol = VD->getDeclContext()->getExtendedProtocolDecl();
// Only applies to protocol extension member.
if (!protocol)
return nullptr;
ValueDecl *requirement = nullptr;
SmallVector<ValueDecl *, 2> members;
protocol->lookupQualified(const_cast<ProtocolDecl *>(protocol),
VD->getFullName(), NLOptions::NL_ProtocolMembers,
members);
for (auto *member : members) {
if (!isa<ProtocolDecl>(member->getDeclContext()) ||
!member->isProtocolRequirement() ||
member->getRawComment().isEmpty())
continue;
if (requirement)
// Found two or more decls with doc-comment.
return nullptr;
requirement = member;
}
return requirement;
}
const ValueDecl *findRequirementDeclWithDocComment(const ValueDecl *VD) {
std::queue<const ValueDecl *> requirements;
while (true) {
for (auto *req : VD->getSatisfiedProtocolRequirements()) {
if (!req->getRawComment().isEmpty())
return req;
else
requirements.push(req);
}
if (requirements.empty())
return nullptr;
VD = requirements.front();
requirements.pop();
}
}
} // namespace
const Decl *swift::getDocCommentProvidingDecl(const Decl *D) {
if (!D->canHaveComment())
return nullptr;
if (!D->getRawComment().isEmpty())
return D;
auto *VD = dyn_cast<ValueDecl>(D);
if (!VD)
return nullptr;
if (auto *overriden = findOverriddenDeclWithDocComment(VD))
return overriden;
if (auto *requirement = findDefaultProvidedDeclWithDocComment(VD))
return requirement;
if (auto *requirement = findRequirementDeclWithDocComment(VD))
return requirement;
return nullptr;
}
DocComment *
swift::getCascadingDocComment(swift::markup::MarkupContext &MC, const Decl *D) {
auto Doc = getSingleDocComment(MC, D);
if (Doc.hasValue())
return Doc;
auto *docD = getDocCommentProvidingDecl(D);
if (!docD)
return nullptr;
// If this refers to a class member, check to see if any
// base classes have a doc comment and cascade it to here.
if (const auto *CD = D->getDeclContext()->getSelfClassDecl())
if (auto BaseClassDoc = getAnyBaseClassDocComment(MC, CD, D))
return BaseClassDoc;
auto *doc = getSingleDocComment(MC, docD);
assert(doc && "getDocCommentProvidingDecl() returned decl with no comment");
if (const auto *PE = D->getDeclContext()->getExtendedProtocolDecl())
if (auto ReqDoc = getProtocolRequirementDocComment(MC, PE, D))
return ReqDoc;
// Iff the doc is inherited from overridden decl, add a note about that.
// FIXME: This is inconsistent <rdar://problem/49043711>, but let's keep the
// behavior for now.
if (docD != D)
if (auto baseClassD = docD->getDeclContext()->getSelfClassDecl())
doc->addInheritanceNote(MC, baseClassD);
return None;
return doc;
}
StringRef Decl::getBriefComment() const {
if (!this->canHaveComment())
return StringRef();
// Ensure the serialized doc is populated to ASTContext.
auto RC = getRawComment();
// Check the cache in ASTContext.
auto &Context = getASTContext();
if (Optional<StringRef> Comment = Context.getBriefComment(this))
return Comment.getValue();
StringRef Result;
if (RC.isEmpty())
if (auto *docD = getDocCommentProvidingDecl(this))
RC = docD->getRawComment();
if (!RC.isEmpty()) {
SmallString<256> BriefStr;
llvm::raw_svector_ostream OS(BriefStr);
printBriefComment(RC, OS);
Result = Context.AllocateCopy(BriefStr.str());
}
// Cache it.
Context.setBriefComment(this, Result);
return Result;
}