//===--- 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(VD)) { if (FD->getGenericParams()) { // Generic function cannot have opaque result type. return nullptr; } ResultT = FD->getResultInterfaceType(); } else if (auto *SD = dyn_cast(VD)) { if (SD->getGenericParams()) { // Generic subscript cannot have opaque result type. return nullptr; } ResultT = SD->getElementInterfaceType(); } else if (auto *VarD = dyn_cast(VD)) { ResultT = VarD->getInterfaceType(); } else { return nullptr; } if (!ResultT->is() || !ResultT->castTo()->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()) // 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(D)) { // We cannot override operators as members. if (FD->isBinaryOperator() || FD->isUnaryOperator()) return; // We cannot override individual accessors. if (isa(FD)) return; if (hasFuncIntroducer || (!hasIntroducer && !hasInitializerModifier)) addMethodOverride(FD, Reason, dynamicLookupInfo); return; } if (auto *VD = dyn_cast(D)) { if (hasVarIntroducer || (!hasIntroducer && !hasInitializerModifier)) addVarOverride(VD, Reason, dynamicLookupInfo); return; } if (auto *SD = dyn_cast(D)) { if (!hasIntroducer && !hasInitializerModifier) addSubscriptOverride(SD, Reason, dynamicLookupInfo); } if (auto *CD = dyn_cast(D)) { if (!isa(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(NTD); if (!CD) return; if (!CD->hasSuperclass()) return; CD = CD->getSuperclassDecl(); for (const auto *Member : CD->getMembers()) { const auto *Constructor = dyn_cast(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(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(CurrDeclContext)) return; Type CurrTy = CurrDeclContext->getSelfTypeInContext(); auto *NTD = CurrDeclContext->getSelfNominalTypeDecl(); if (CurrTy && !CurrTy->is()) { // 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()) { addResultBuilderBuildCompletions(NTD); } }