mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[AST] Inherit doc-brief comment from protocol, superclass, and requirement
rdar://problem/38422822
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user