Files
swift-mirror/lib/IDE/CodeCompletionResultBuilder.cpp
Allan Shortlidge c5398e17d3 AST: Introduce Decl::getDeprecatedAttr().
It replaces `DeclAttr::getDeprecated()` as the designated way to query for the
attribute that makes a decl deprecated.
2024-12-02 07:35:58 -08:00

437 lines
15 KiB
C++

//===--- CodeCompletionResultBuilder.cpp ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "CodeCompletionResultBuilder.h"
#include "CodeCompletionDiagnostics.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/USRGeneration.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "swift/IDE/CodeCompletionStringPrinter.h"
#include "swift/IDE/Utils.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Comment.h"
#include "clang/Basic/Module.h"
#include "clang/Index/USRGeneration.h"
using namespace swift;
using namespace swift::ide;
static bool shouldCopyAssociatedUSRForDecl(const ValueDecl *VD) {
// Avoid trying to generate a USR for some declaration types.
if (isa<GenericTypeParamDecl>(VD))
return false;
if (isa<ParamDecl>(VD))
return false;
if (isa<ModuleDecl>(VD))
return false;
if (VD->hasClangNode() && !VD->getClangDecl())
return false;
// Avoid generating USRs for decls in local contexts, we cannot guarantee
// any parent closures will be type-checked, which is needed for mangling.
if (VD->getDeclContext()->getLocalContext())
return false;
return true;
}
template <typename FnTy>
static void walkValueDeclAndOverriddenDecls(const Decl *D, const FnTy &Fn) {
if (auto *VD = dyn_cast<ValueDecl>(D)) {
Fn(VD);
walkOverriddenDecls(VD, Fn);
}
}
static ArrayRef<NullTerminatedStringRef>
copyAssociatedUSRs(llvm::BumpPtrAllocator &Allocator, const Decl *D) {
llvm::SmallVector<NullTerminatedStringRef, 4> USRs;
walkValueDeclAndOverriddenDecls(
D,
[&](llvm::PointerUnion<const ValueDecl *, const clang::NamedDecl *> OD) {
llvm::SmallString<128> SS;
bool Ignored = true;
if (auto *OVD = OD.dyn_cast<const ValueDecl *>()) {
if (shouldCopyAssociatedUSRForDecl(OVD)) {
llvm::raw_svector_ostream OS(SS);
Ignored = printValueDeclUSR(OVD, OS);
}
} else if (auto *OND = OD.dyn_cast<const clang::NamedDecl *>()) {
Ignored = clang::index::generateUSRForDecl(OND, SS);
}
if (!Ignored)
USRs.emplace_back(SS, Allocator);
});
if (!USRs.empty())
return llvm::ArrayRef(USRs).copy(Allocator);
return {};
}
void CodeCompletionResultBuilder::addChunkWithText(
CodeCompletionString::Chunk::ChunkKind Kind, StringRef Text) {
addChunkWithTextNoCopy(Kind, Text.copy(*Sink.Allocator));
}
CodeCompletionResult *CodeCompletionResultBuilder::takeResult() {
auto &Allocator = *Sink.Allocator;
auto *CCS = CodeCompletionString::create(Allocator, Chunks);
CodeCompletionDiagnosticSeverity ContextFreeDiagnosticSeverity =
CodeCompletionDiagnosticSeverity::None;
NullTerminatedStringRef ContextFreeDiagnosticMessage;
if (ContextFreeNotRecReason != ContextFreeNotRecommendedReason::None) {
assert(AssociatedDecl != nullptr &&
"There should be no case where ContextFreeNotRecReason != None && "
"AssociatedDecl == nulptr");
// FIXME: We should generate the message lazily.
if (const auto *VD = dyn_cast_or_null<ValueDecl>(AssociatedDecl)) {
CodeCompletionDiagnosticSeverity severity;
SmallString<256> message;
llvm::raw_svector_ostream messageOS(message);
if (!getContextFreeCompletionDiagnostics(ContextFreeNotRecReason, VD,
severity, messageOS)) {
ContextFreeDiagnosticSeverity = severity;
ContextFreeDiagnosticMessage =
NullTerminatedStringRef(message, Allocator);
}
}
}
/// This variable should be initialized by all the switch cases below.
ContextFreeCodeCompletionResult *ContextFreeResult = nullptr;
switch (Kind) {
case CodeCompletionResultKind::Declaration: {
NullTerminatedStringRef ModuleName;
if (CurrentModule) {
if (Sink.LastModule.first == CurrentModule.getOpaqueValue()) {
ModuleName = Sink.LastModule.second;
} else {
if (auto *C = CurrentModule.dyn_cast<const clang::Module *>()) {
ModuleName =
NullTerminatedStringRef(C->getFullModuleName(), Allocator);
} else {
ModuleName = NullTerminatedStringRef(
CurrentModule.get<const swift::ModuleDecl *>()->getName().str(),
Allocator);
}
Sink.LastModule.first = CurrentModule.getOpaqueValue();
Sink.LastModule.second = ModuleName;
}
}
ContextFreeResult = ContextFreeCodeCompletionResult::createDeclResult(
Sink, CCS, AssociatedDecl, HasAsyncAlternative, ModuleName,
NullTerminatedStringRef(BriefDocComment, Allocator),
copyAssociatedUSRs(Allocator, AssociatedDecl), ResultType,
ContextFreeNotRecReason, ContextFreeDiagnosticSeverity,
ContextFreeDiagnosticMessage);
break;
}
case CodeCompletionResultKind::Keyword:
ContextFreeResult = ContextFreeCodeCompletionResult::createKeywordResult(
Sink, KeywordKind, CCS,
NullTerminatedStringRef(BriefDocComment, Allocator), ResultType);
break;
case CodeCompletionResultKind::BuiltinOperator:
case CodeCompletionResultKind::Pattern:
ContextFreeResult =
ContextFreeCodeCompletionResult::createPatternOrBuiltInOperatorResult(
Sink, Kind, CCS, CodeCompletionOperatorKind::None,
NullTerminatedStringRef(BriefDocComment, Allocator), ResultType,
ContextFreeNotRecReason, ContextFreeDiagnosticSeverity,
ContextFreeDiagnosticMessage);
break;
case CodeCompletionResultKind::Literal:
assert(LiteralKind.has_value());
ContextFreeResult = ContextFreeCodeCompletionResult::createLiteralResult(
Sink, *LiteralKind, CCS, ResultType);
break;
}
if (Sink.shouldProduceContextFreeResults()) {
// If the sink only intends to store the context free results in the cache,
// we don't need to compute any contextual properties.
return new (Allocator) CodeCompletionResult(
*ContextFreeResult, SemanticContextKind::None, CodeCompletionFlair(),
/*NumBytesToErase=*/0, CodeCompletionResultTypeRelation::Unrelated,
ContextualNotRecommendedReason::None);
} else {
assert(
ContextFreeResult != nullptr &&
"ContextFreeResult should have been constructed by the switch above");
// We know that the ContextFreeResult has an AST-based type because it was
// just computed and not read from the cache and
// Sink.shouldProduceContextFreeResults() is false. So we can pass nullptr
// for USRTypeContext.
CodeCompletionResultTypeRelation typeRelation =
ContextFreeResult->calculateContextualTypeRelation(
DC, TypeContext, /*usrTypeContext=*/nullptr);
ContextualNotRecommendedReason notRecommendedReason =
ContextFreeResult->calculateContextualNotRecommendedReason(
ContextualNotRecReason, CanCurrDeclContextHandleAsync);
return new (Allocator) CodeCompletionResult(
*ContextFreeResult, SemanticContext, Flair, NumBytesToErase,
typeRelation, notRecommendedReason);
}
}
void CodeCompletionResultBuilder::finishResult() {
if (!Cancelled)
Sink.Results.push_back(takeResult());
}
void CodeCompletionResultBuilder::setAssociatedDecl(const Decl *D) {
assert(Kind == CodeCompletionResultKind::Declaration);
AssociatedDecl = D;
if (auto *ClangD = D->getClangDecl())
CurrentModule = ClangD->getImportedOwningModule();
// FIXME: macros
// FIXME: imported header module
if (!CurrentModule) {
ModuleDecl *MD = D->getModuleContext();
// If this is an underscored cross-import overlay, map it to the underlying
// module that declares it instead.
if (ModuleDecl *Declaring = MD->getDeclaringModuleIfCrossImportOverlay())
MD = Declaring;
CurrentModule = MD;
}
if (D->isDeprecated())
setContextFreeNotRecommended(ContextFreeNotRecommendedReason::Deprecated);
else if (D->getSoftDeprecatedAttr())
setContextFreeNotRecommended(
ContextFreeNotRecommendedReason::SoftDeprecated);
if (D->getClangNode()) {
if (auto *ClangD = D->getClangDecl()) {
const auto &ClangContext = ClangD->getASTContext();
if (const clang::RawComment *RC =
ClangContext.getRawCommentForAnyRedecl(ClangD)) {
setBriefDocComment(RC->getBriefText(ClangContext));
}
}
} else {
setBriefDocComment(AssociatedDecl->getSemanticBriefComment());
}
}
void CodeCompletionResultBuilder::addCallArgument(
Identifier Name, Identifier LocalName, Type Ty, Type ContextTy,
bool IsVarArg, bool IsInOut, bool IsIUO, bool IsAutoClosure,
bool IsLabeledTrailingClosure, bool IsForOperator, bool HasDefault) {
++CurrentNestingLevel;
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
addSimpleChunk(ChunkKind::CallArgumentBegin);
if (shouldAnnotateResults()) {
llvm::SmallString<16> EscapedKeyword;
if (!Name.empty()) {
addChunkWithText(ChunkKind::CallArgumentName,
escapeKeyword(Name.str(), false, EscapedKeyword));
if (!LocalName.empty() && Name != LocalName) {
addChunkWithTextNoCopy(ChunkKind::Text, " ");
getLastChunk().setIsAnnotation();
addChunkWithText(ChunkKind::CallArgumentInternalName,
escapeKeyword(LocalName.str(), false, EscapedKeyword));
getLastChunk().setIsAnnotation();
}
addChunkWithTextNoCopy(ChunkKind::CallArgumentColon, ": ");
} else if (!LocalName.empty()) {
addChunkWithTextNoCopy(ChunkKind::CallArgumentName, "_");
getLastChunk().setIsAnnotation();
addChunkWithTextNoCopy(ChunkKind::Text, " ");
getLastChunk().setIsAnnotation();
addChunkWithText(ChunkKind::CallArgumentInternalName,
escapeKeyword(LocalName.str(), false, EscapedKeyword));
addChunkWithTextNoCopy(ChunkKind::CallArgumentColon, ": ");
} else if (!IsForOperator) {
addChunkWithTextNoCopy(ChunkKind::CallArgumentName, "_");
if (!IsLabeledTrailingClosure)
getLastChunk().setIsAnnotation();
addChunkWithTextNoCopy(ChunkKind::CallArgumentColon, ": ");
if (!IsLabeledTrailingClosure)
getLastChunk().setIsAnnotation();
}
} else {
llvm::SmallString<16> stash;
ChunkKind nameKind;
StringRef nameStr;
if (!Name.empty()) {
nameKind = ChunkKind::CallArgumentName;
nameStr = escapeKeyword(Name.str(), false, stash);
} else if (IsLabeledTrailingClosure) {
nameKind = ChunkKind::CallArgumentName;
nameStr = "_";
} else if (!LocalName.empty()) {
nameKind = ChunkKind::CallArgumentInternalName;
nameStr = escapeKeyword(LocalName.str(), false, stash);
}
if (!nameStr.empty()) {
addChunkWithText(nameKind, nameStr);
addChunkWithTextNoCopy(ChunkKind::CallArgumentColon, ": ");
}
}
// 'inout' arguments are printed specially.
if (IsInOut) {
addChunkWithTextNoCopy(CodeCompletionString::Chunk::ChunkKind::Ampersand,
"&");
Ty = Ty->getInOutObjectType();
}
// If the parameter is of the type @autoclosure ()->output, then the
// code completion should show the parameter of the output type
// instead of the function type ()->output.
if (IsAutoClosure) {
// 'Ty' may be ErrorType.
if (auto funcTy = Ty->getAs<FunctionType>())
Ty = funcTy->getResult();
}
PrintOptions PO;
PO.SkipAttributes = true;
PO.PrintOptionalAsImplicitlyUnwrapped = IsIUO;
PO.OpaqueReturnTypePrinting =
PrintOptions::OpaqueReturnTypePrintingMode::WithoutOpaqueKeyword;
if (ContextTy)
PO.setBaseType(ContextTy);
if (shouldAnnotateResults()) {
withNestedGroup(ChunkKind::CallArgumentTypeBegin, [&]() {
CodeCompletionStringPrinter printer(*this);
auto TL = TypeLoc::withoutLoc(Ty);
printer.printTypePre(TL);
Ty->print(printer, PO);
printer.printTypePost(TL);
});
} else {
std::string TypeName = Ty->getString(PO);
addChunkWithText(ChunkKind::CallArgumentType, TypeName);
}
if (HasDefault) {
withNestedGroup(ChunkKind::CallArgumentDefaultBegin, []() {
// Possibly add the actual value in the future
});
}
// Look through optional types and type aliases to find out if we have
// function type.
Ty = Ty->lookThroughAllOptionalTypes();
if (auto AFT = Ty->getAs<AnyFunctionType>()) {
// If this is a closure type, add ChunkKind::CallArgumentClosureType or
// ChunkKind::CallArgumentClosureExpr for labeled trailing closures.
PrintOptions PO;
PO.PrintFunctionRepresentationAttrs =
PrintOptions::FunctionRepresentationMode::None;
PO.SkipAttributes = true;
PO.OpaqueReturnTypePrinting =
PrintOptions::OpaqueReturnTypePrintingMode::WithoutOpaqueKeyword;
PO.AlwaysTryPrintParameterLabels = true;
if (ContextTy)
PO.setBaseType(ContextTy);
if (IsLabeledTrailingClosure) {
// Expand the closure body.
SmallString<32> buffer;
llvm::raw_svector_ostream OS(buffer);
bool firstParam = true;
for (const auto &param : AFT->getParams()) {
if (!firstParam)
OS << ", ";
firstParam = false;
if (param.hasLabel()) {
OS << param.getLabel();
} else if (param.hasInternalLabel()) {
OS << param.getInternalLabel();
} else {
OS << "<#";
if (param.isInOut())
OS << "inout ";
OS << param.getPlainType()->getString(PO);
if (param.isVariadic())
OS << "...";
OS << "#>";
}
}
if (!firstParam)
OS << " in";
addChunkWithText(
CodeCompletionString::Chunk::ChunkKind::CallArgumentClosureExpr,
OS.str());
} else {
// Add the closure type.
addChunkWithText(
CodeCompletionString::Chunk::ChunkKind::CallArgumentClosureType,
AFT->getString(PO));
}
}
if (IsVarArg)
addEllipsis();
--CurrentNestingLevel;
}
void CodeCompletionResultBuilder::withNestedGroup(
CodeCompletionString::Chunk::ChunkKind Kind,
llvm::function_ref<void()> body) {
++CurrentNestingLevel;
addSimpleChunk(Kind);
body();
--CurrentNestingLevel;
}
void CodeCompletionResultBuilder::addTypeAnnotation(Type T, PrintOptions PO,
StringRef suffix) {
T = T->getReferenceStorageReferent();
// Replace '()' with 'Void'.
if (T->isVoid())
T = T->getASTContext().getVoidDecl()->getDeclaredInterfaceType();
if (shouldAnnotateResults()) {
withNestedGroup(CodeCompletionString::Chunk::ChunkKind::TypeAnnotationBegin,
[&]() {
CodeCompletionStringPrinter printer(*this);
auto TL = TypeLoc::withoutLoc(T);
printer.printTypePre(TL);
T->print(printer, PO);
printer.printTypePost(TL);
if (!suffix.empty())
printer.printText(suffix);
});
} else {
auto str = T.getString(PO);
if (!suffix.empty())
str += suffix.str();
addTypeAnnotation(str);
}
}