mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
519 lines
18 KiB
C++
519 lines
18 KiB
C++
//===--- CompletionOverrideLookup.cpp -------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2022 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 "swift/IDE/CompletionOverrideLookup.h"
|
|
|
|
#include "CodeCompletionResultBuilder.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/ProtocolConformance.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/IDE/CodeCompletionString.h"
|
|
#include "swift/IDE/CodeCompletionStringPrinter.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::ide;
|
|
|
|
bool CompletionOverrideLookup::addAccessControl(
|
|
const ValueDecl *VD, CodeCompletionResultBuilder &Builder) {
|
|
auto CurrentNominal = CurrDeclContext->getSelfNominalTypeDecl();
|
|
assert(CurrentNominal);
|
|
|
|
auto AccessOfContext = CurrentNominal->getFormalAccess();
|
|
if (AccessOfContext < AccessLevel::Public)
|
|
return false;
|
|
|
|
AccessLevel Access = std::min(VD->getFormalAccess(), AccessOfContext);
|
|
// Only emit 'public', not needed otherwise.
|
|
if (Access < AccessLevel::Public)
|
|
return false;
|
|
|
|
Builder.addAccessControlKeyword(Access);
|
|
return true;
|
|
}
|
|
|
|
Type CompletionOverrideLookup::getOpaqueResultType(
|
|
const ValueDecl *VD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
if (Reason != DeclVisibilityKind::MemberOfProtocolConformedToByCurrentNominal)
|
|
return nullptr;
|
|
|
|
auto currTy = CurrDeclContext->getDeclaredTypeInContext();
|
|
if (!currTy)
|
|
return nullptr;
|
|
|
|
Type ResultT;
|
|
if (auto *FD = dyn_cast<FuncDecl>(VD)) {
|
|
if (FD->getGenericParams()) {
|
|
// Generic function cannot have opaque result type.
|
|
return nullptr;
|
|
}
|
|
ResultT = FD->getResultInterfaceType();
|
|
} else if (auto *SD = dyn_cast<SubscriptDecl>(VD)) {
|
|
if (SD->getGenericParams()) {
|
|
// Generic subscript cannot have opaque result type.
|
|
return nullptr;
|
|
}
|
|
ResultT = SD->getElementInterfaceType();
|
|
} else if (auto *VarD = dyn_cast<VarDecl>(VD)) {
|
|
ResultT = VarD->getInterfaceType();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!ResultT->is<DependentMemberType>() ||
|
|
!ResultT->castTo<DependentMemberType>()->getAssocType())
|
|
// The result is not a valid associatedtype.
|
|
return nullptr;
|
|
|
|
// Try substitution to see if the associated type is resolved to concrete
|
|
// type.
|
|
auto substMap = currTy->getMemberSubstitutionMap(VD);
|
|
if (!ResultT.subst(substMap)->is<DependentMemberType>())
|
|
// If resolved print it.
|
|
return nullptr;
|
|
|
|
auto genericSig = VD->getDeclContext()->getGenericSignatureOfContext();
|
|
|
|
if (genericSig->isConcreteType(ResultT))
|
|
// If it has same type requrement, we will emit the concrete type.
|
|
return nullptr;
|
|
|
|
auto upperBound = genericSig->getUpperBound(
|
|
ResultT,
|
|
/*forExistentialSelf=*/false,
|
|
/*withParameterizedProtocols=*/false);
|
|
|
|
if (upperBound->isAny())
|
|
return nullptr;
|
|
|
|
return upperBound;
|
|
}
|
|
|
|
void CompletionOverrideLookup::addValueOverride(
|
|
const ValueDecl *VD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo, CodeCompletionResultBuilder &Builder,
|
|
bool hasDeclIntroducer) {
|
|
Type opaqueResultType = getOpaqueResultType(VD, Reason, dynamicLookupInfo);
|
|
|
|
class DeclPrinter : public CodeCompletionStringPrinter {
|
|
Type OpaqueBaseTy;
|
|
|
|
public:
|
|
DeclPrinter(CodeCompletionResultBuilder &Builder, Type OpaqueBaseTy)
|
|
: CodeCompletionStringPrinter(Builder), OpaqueBaseTy(OpaqueBaseTy) {}
|
|
|
|
// As for FuncDecl, SubscriptDecl, and VarDecl, substitute the result type
|
|
// with 'OpaqueBaseTy' if specified.
|
|
void printDeclResultTypePre(ValueDecl *VD, TypeLoc &TL) override {
|
|
if (!OpaqueBaseTy.isNull()) {
|
|
setNextChunkKind(ChunkKind::Keyword);
|
|
printText("some ");
|
|
setNextChunkKind(ChunkKind::Text);
|
|
TL = TypeLoc::withoutLoc(OpaqueBaseTy);
|
|
}
|
|
CodeCompletionStringPrinter::printDeclResultTypePre(VD, TL);
|
|
}
|
|
};
|
|
|
|
DeclPrinter Printer(Builder, opaqueResultType);
|
|
Printer.startPreamble();
|
|
|
|
bool modifierAdded = false;
|
|
|
|
// 'public' if needed.
|
|
modifierAdded |= !hasAccessModifier && addAccessControl(VD, Builder);
|
|
|
|
// 'override' if needed
|
|
if (missingOverride(Reason)) {
|
|
Builder.addOverrideKeyword();
|
|
modifierAdded |= true;
|
|
}
|
|
|
|
// Erase existing introducer (e.g. 'func') if any modifiers are added.
|
|
if (hasDeclIntroducer && modifierAdded) {
|
|
auto dist = Ctx.SourceMgr.getByteDistance(
|
|
introducerLoc, Ctx.SourceMgr.getIDEInspectionTargetLoc());
|
|
if (dist <= CodeCompletionResult::MaxNumBytesToErase) {
|
|
Builder.setNumBytesToErase(dist);
|
|
hasDeclIntroducer = false;
|
|
}
|
|
}
|
|
|
|
PrintOptions PO;
|
|
if (auto transformType = CurrDeclContext->getDeclaredTypeInContext())
|
|
PO.setBaseType(transformType);
|
|
PO.PrintPropertyAccessors = false;
|
|
PO.PrintSubscriptAccessors = false;
|
|
|
|
PO.SkipUnderscoredKeywords = true;
|
|
PO.PrintImplicitAttrs = false;
|
|
PO.ExclusiveAttrList.push_back(TypeAttrKind::Escaping);
|
|
PO.ExclusiveAttrList.push_back(TypeAttrKind::Autoclosure);
|
|
// Print certain modifiers only when the introducer is not written.
|
|
// Otherwise, the user can add it after the completion.
|
|
if (!hasDeclIntroducer) {
|
|
PO.ExclusiveAttrList.push_back(DeclAttrKind::Nonisolated);
|
|
}
|
|
|
|
PO.PrintAccess = false;
|
|
PO.PrintOverrideKeyword = false;
|
|
PO.PrintSelfAccessKindKeyword = false;
|
|
|
|
PO.PrintStaticKeyword = !hasStaticOrClass && !hasDeclIntroducer;
|
|
PO.SkipIntroducerKeywords = hasDeclIntroducer;
|
|
VD->print(Printer, PO);
|
|
}
|
|
|
|
void CompletionOverrideLookup::addMethodOverride(
|
|
const FuncDecl *FD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
CodeCompletionResultBuilder Builder(Sink,
|
|
CodeCompletionResultKind::Declaration,
|
|
SemanticContextKind::Super);
|
|
Builder.setResultTypeNotApplicable();
|
|
Builder.setAssociatedDecl(FD);
|
|
addValueOverride(FD, Reason, dynamicLookupInfo, Builder, hasFuncIntroducer);
|
|
Builder.addBraceStmtWithCursor();
|
|
}
|
|
|
|
void CompletionOverrideLookup::addVarOverride(
|
|
const VarDecl *VD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
// Overrides cannot use 'let', but if the 'override' keyword is specified
|
|
// then the intention is clear, so provide the results anyway. The compiler
|
|
// can then provide an error telling you to use 'var' instead.
|
|
// If we don't need override then it's a protocol requirement, so show it.
|
|
if (missingOverride(Reason) && hasVarIntroducer && isKeywordSpecified("let"))
|
|
return;
|
|
|
|
CodeCompletionResultBuilder Builder(Sink,
|
|
CodeCompletionResultKind::Declaration,
|
|
SemanticContextKind::Super);
|
|
Builder.setAssociatedDecl(VD);
|
|
addValueOverride(VD, Reason, dynamicLookupInfo, Builder, hasVarIntroducer);
|
|
}
|
|
|
|
void CompletionOverrideLookup::addSubscriptOverride(
|
|
const SubscriptDecl *SD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
CodeCompletionResultBuilder Builder(Sink,
|
|
CodeCompletionResultKind::Declaration,
|
|
SemanticContextKind::Super);
|
|
Builder.setResultTypeNotApplicable();
|
|
Builder.setAssociatedDecl(SD);
|
|
addValueOverride(SD, Reason, dynamicLookupInfo, Builder, false);
|
|
Builder.addBraceStmtWithCursor();
|
|
}
|
|
|
|
void CompletionOverrideLookup::addTypeAlias(
|
|
const AssociatedTypeDecl *ATD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
CodeCompletionResultBuilder Builder(Sink,
|
|
CodeCompletionResultKind::Declaration,
|
|
SemanticContextKind::Super);
|
|
Builder.setResultTypeNotApplicable();
|
|
Builder.setAssociatedDecl(ATD);
|
|
if (!hasTypealiasIntroducer && !hasAccessModifier)
|
|
(void)addAccessControl(ATD, Builder);
|
|
if (!hasTypealiasIntroducer)
|
|
Builder.addDeclIntroducer("typealias ");
|
|
Builder.addBaseName(ATD->getName().str());
|
|
Builder.addTextChunk(" = ");
|
|
Builder.addSimpleNamedParameter("Type");
|
|
}
|
|
|
|
void CompletionOverrideLookup::addConstructor(
|
|
const ConstructorDecl *CD, DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
CodeCompletionResultBuilder Builder(Sink,
|
|
CodeCompletionResultKind::Declaration,
|
|
SemanticContextKind::Super);
|
|
Builder.setResultTypeNotApplicable();
|
|
Builder.setAssociatedDecl(CD);
|
|
|
|
CodeCompletionStringPrinter printer(Builder);
|
|
printer.startPreamble();
|
|
|
|
if (!hasAccessModifier)
|
|
(void)addAccessControl(CD, Builder);
|
|
|
|
if (missingOverride(Reason) && CD->isDesignatedInit() && !CD->isRequired())
|
|
Builder.addOverrideKeyword();
|
|
|
|
// Emit 'required' if we're in class context, 'required' is not specified,
|
|
// and 1) this is a protocol conformance and the class is not final, or 2)
|
|
// this is subclass and the initializer is marked as required.
|
|
bool needRequired = false;
|
|
auto C = CurrDeclContext->getSelfClassDecl();
|
|
if (C && !isKeywordSpecified("required")) {
|
|
switch (Reason) {
|
|
case DeclVisibilityKind::MemberOfProtocolConformedToByCurrentNominal:
|
|
case DeclVisibilityKind::MemberOfProtocolDerivedByCurrentNominal:
|
|
if (!C->isSemanticallyFinal())
|
|
needRequired = true;
|
|
break;
|
|
case DeclVisibilityKind::MemberOfSuper:
|
|
if (CD->isRequired())
|
|
needRequired = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (needRequired)
|
|
Builder.addRequiredKeyword();
|
|
|
|
{
|
|
PrintOptions Options;
|
|
if (auto transformType = CurrDeclContext->getDeclaredTypeInContext())
|
|
Options.setBaseType(transformType);
|
|
Options.PrintImplicitAttrs = false;
|
|
Options.SkipAttributes = true;
|
|
CD->print(printer, Options);
|
|
}
|
|
printer.flush();
|
|
|
|
Builder.addBraceStmtWithCursor();
|
|
}
|
|
|
|
void CompletionOverrideLookup::foundDecl(ValueDecl *D,
|
|
DeclVisibilityKind Reason,
|
|
DynamicLookupInfo dynamicLookupInfo) {
|
|
if (Reason == DeclVisibilityKind::MemberOfCurrentNominal)
|
|
return;
|
|
|
|
if (D->shouldHideFromEditor())
|
|
return;
|
|
|
|
if (D->isSemanticallyFinal())
|
|
return;
|
|
|
|
bool hasIntroducer =
|
|
hasFuncIntroducer || hasVarIntroducer || hasTypealiasIntroducer;
|
|
|
|
if (hasStaticOrClass && !D->isStatic())
|
|
return;
|
|
|
|
// As per the current convention, only instance members are
|
|
// suggested if an introducer is not accompanied by a 'static' or
|
|
// 'class' modifier.
|
|
if (hasIntroducer && !hasStaticOrClass && D->isStatic())
|
|
return;
|
|
|
|
if (auto *FD = dyn_cast<FuncDecl>(D)) {
|
|
// We cannot override operators as members.
|
|
if (FD->isBinaryOperator() || FD->isUnaryOperator())
|
|
return;
|
|
|
|
// We cannot override individual accessors.
|
|
if (isa<AccessorDecl>(FD))
|
|
return;
|
|
|
|
if (hasFuncIntroducer || (!hasIntroducer && !hasInitializerModifier))
|
|
addMethodOverride(FD, Reason, dynamicLookupInfo);
|
|
return;
|
|
}
|
|
|
|
if (auto *VD = dyn_cast<VarDecl>(D)) {
|
|
if (hasVarIntroducer || (!hasIntroducer && !hasInitializerModifier))
|
|
addVarOverride(VD, Reason, dynamicLookupInfo);
|
|
return;
|
|
}
|
|
|
|
if (auto *SD = dyn_cast<SubscriptDecl>(D)) {
|
|
if (!hasIntroducer && !hasInitializerModifier)
|
|
addSubscriptOverride(SD, Reason, dynamicLookupInfo);
|
|
}
|
|
|
|
if (auto *CD = dyn_cast<ConstructorDecl>(D)) {
|
|
if (!isa<ProtocolDecl>(CD->getDeclContext()))
|
|
return;
|
|
if (hasIntroducer || hasOverride || hasOverridabilityModifier ||
|
|
hasStaticOrClass)
|
|
return;
|
|
if (CD->isRequired() || CD->isDesignatedInit())
|
|
addConstructor(CD, Reason, dynamicLookupInfo);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CompletionOverrideLookup::addDesignatedInitializers(NominalTypeDecl *NTD) {
|
|
if (hasFuncIntroducer || hasVarIntroducer || hasTypealiasIntroducer ||
|
|
hasOverridabilityModifier || hasStaticOrClass)
|
|
return;
|
|
|
|
const auto *CD = dyn_cast<ClassDecl>(NTD);
|
|
if (!CD)
|
|
return;
|
|
if (!CD->hasSuperclass())
|
|
return;
|
|
CD = CD->getSuperclassDecl();
|
|
for (const auto *Member : CD->getMembers()) {
|
|
const auto *Constructor = dyn_cast<ConstructorDecl>(Member);
|
|
if (!Constructor)
|
|
continue;
|
|
if (Constructor->hasStubImplementation())
|
|
continue;
|
|
if (Constructor->isDesignatedInit())
|
|
addConstructor(Constructor, DeclVisibilityKind::MemberOfSuper, {});
|
|
}
|
|
}
|
|
|
|
void CompletionOverrideLookup::addAssociatedTypes(NominalTypeDecl *NTD) {
|
|
if (!hasTypealiasIntroducer &&
|
|
(hasFuncIntroducer || hasVarIntroducer || hasInitializerModifier ||
|
|
hasOverride || hasOverridabilityModifier || hasStaticOrClass))
|
|
return;
|
|
|
|
if (isa<ProtocolDecl>(NTD))
|
|
return;
|
|
|
|
for (auto Conformance : NTD->getAllConformances()) {
|
|
auto Proto = Conformance->getProtocol();
|
|
if (!Proto->isAccessibleFrom(CurrDeclContext))
|
|
continue;
|
|
for (auto *ATD : Proto->getAssociatedTypeMembers()) {
|
|
// FIXME: Also exclude the type alias that has already been specified.
|
|
if (!Conformance->hasTypeWitness(ATD) || ATD->hasDefaultDefinitionType())
|
|
continue;
|
|
addTypeAlias(
|
|
ATD, DeclVisibilityKind::MemberOfProtocolConformedToByCurrentNominal,
|
|
{});
|
|
}
|
|
}
|
|
}
|
|
|
|
StringRef CompletionOverrideLookup::getResultBuilderDocComment(
|
|
ResultBuilderBuildFunction function) {
|
|
switch (function) {
|
|
case ResultBuilderBuildFunction::BuildArray:
|
|
return "Enables support for..in loops in a result builder by "
|
|
"combining the results of all iterations into a single result";
|
|
|
|
case ResultBuilderBuildFunction::BuildBlock:
|
|
return "Required by every result builder to build combined results "
|
|
"from statement blocks";
|
|
|
|
case ResultBuilderBuildFunction::BuildEitherFirst:
|
|
return "With buildEither(second:), enables support for 'if-else' and "
|
|
"'switch' statements by folding conditional results into a single "
|
|
"result";
|
|
|
|
case ResultBuilderBuildFunction::BuildEitherSecond:
|
|
return "With buildEither(first:), enables support for 'if-else' and "
|
|
"'switch' statements by folding conditional results into a single "
|
|
"result";
|
|
|
|
case ResultBuilderBuildFunction::BuildExpression:
|
|
return "If declared, provides contextual type information for statement "
|
|
"expressions to translate them into partial results";
|
|
|
|
case ResultBuilderBuildFunction::BuildFinalResult:
|
|
return "If declared, this will be called on the partial result from the "
|
|
"outermost block statement to produce the final returned result";
|
|
|
|
case ResultBuilderBuildFunction::BuildLimitedAvailability:
|
|
return "If declared, this will be called on the partial result of "
|
|
"an 'if #available' block to allow the result builder to erase "
|
|
"type information";
|
|
|
|
case ResultBuilderBuildFunction::BuildOptional:
|
|
return "Enables support for `if` statements that do not have an `else`";
|
|
case ResultBuilderBuildFunction::BuildPartialBlockFirst:
|
|
return "Builds a partial result component from the first component";
|
|
case ResultBuilderBuildFunction::BuildPartialBlockAccumulated:
|
|
return "Builds a partial result component by combining an accumulated "
|
|
"component and a new component";
|
|
}
|
|
}
|
|
|
|
void CompletionOverrideLookup::addResultBuilderBuildCompletion(
|
|
NominalTypeDecl *builder, Type componentType,
|
|
ResultBuilderBuildFunction function) {
|
|
CodeCompletionResultBuilder Builder(Sink, CodeCompletionResultKind::Pattern,
|
|
SemanticContextKind::CurrentNominal);
|
|
Builder.setResultTypeNotApplicable();
|
|
|
|
if (!hasFuncIntroducer) {
|
|
if (!hasAccessModifier && builder->getFormalAccess() >= AccessLevel::Public)
|
|
Builder.addAccessControlKeyword(AccessLevel::Public);
|
|
|
|
if (!hasStaticOrClass)
|
|
Builder.addTextChunk("static ");
|
|
|
|
Builder.addTextChunk("func ");
|
|
}
|
|
|
|
std::string declStringWithoutFunc;
|
|
{
|
|
llvm::raw_string_ostream out(declStringWithoutFunc);
|
|
printResultBuilderBuildFunction(builder, componentType, function,
|
|
std::nullopt, out);
|
|
}
|
|
Builder.addTextChunk(declStringWithoutFunc);
|
|
Builder.addBraceStmtWithCursor();
|
|
Builder.setBriefDocComment(getResultBuilderDocComment(function));
|
|
}
|
|
|
|
void CompletionOverrideLookup::addResultBuilderBuildCompletions(
|
|
NominalTypeDecl *builder) {
|
|
Type componentType = inferResultBuilderComponentType(builder);
|
|
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildBlock);
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildExpression);
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildOptional);
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildEitherFirst);
|
|
addResultBuilderBuildCompletion(
|
|
builder, componentType, ResultBuilderBuildFunction::BuildEitherSecond);
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildArray);
|
|
addResultBuilderBuildCompletion(
|
|
builder, componentType,
|
|
ResultBuilderBuildFunction::BuildLimitedAvailability);
|
|
addResultBuilderBuildCompletion(builder, componentType,
|
|
ResultBuilderBuildFunction::BuildFinalResult);
|
|
addResultBuilderBuildCompletion(
|
|
builder, componentType,
|
|
ResultBuilderBuildFunction::BuildPartialBlockFirst);
|
|
addResultBuilderBuildCompletion(
|
|
builder, componentType,
|
|
ResultBuilderBuildFunction::BuildPartialBlockAccumulated);
|
|
}
|
|
|
|
void CompletionOverrideLookup::getOverrideCompletions(SourceLoc Loc) {
|
|
if (!CurrDeclContext->isTypeContext())
|
|
return;
|
|
if (isa<ProtocolDecl>(CurrDeclContext))
|
|
return;
|
|
|
|
Type CurrTy = CurrDeclContext->getSelfTypeInContext();
|
|
auto *NTD = CurrDeclContext->getSelfNominalTypeDecl();
|
|
if (CurrTy && !CurrTy->is<ErrorType>()) {
|
|
// Look for overridable static members too.
|
|
Type Meta = MetatypeType::get(CurrTy);
|
|
lookupVisibleMemberDecls(*this, Meta, introducerLoc, CurrDeclContext,
|
|
/*includeInstanceMembers=*/true,
|
|
/*includeDerivedRequirements*/ true,
|
|
/*includeProtocolExtensionMembers*/ false);
|
|
addDesignatedInitializers(NTD);
|
|
addAssociatedTypes(NTD);
|
|
}
|
|
|
|
if (NTD && NTD->getAttrs().hasAttribute<ResultBuilderAttr>()) {
|
|
addResultBuilderBuildCompletions(NTD);
|
|
}
|
|
}
|