//===--- ConformingMethodList.cpp -----------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2019 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/ConformingMethodList.h" #include "ExprContextAnalysis.h" #include "swift/AST/ASTDemangler.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/GenericEnvironment.h" #include "swift/AST/NameLookup.h" #include "swift/AST/USRGeneration.h" #include "swift/Basic/Assertions.h" #include "swift/IDE/TypeCheckCompletionCallback.h" #include "swift/Parse/IDEInspectionCallbacks.h" #include "swift/Sema/IDETypeChecking.h" #include "swift/Sema/ConstraintSystem.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" using namespace swift; using namespace ide; namespace { class ConformingMethodListCallbacks : public CodeCompletionCallbacks, public DoneParsingCallback { ArrayRef ExpectedTypeNames; ConformingMethodListConsumer &Consumer; SourceLoc Loc; CodeCompletionExpr *CCExpr = nullptr; DeclContext *CurDeclContext = nullptr; void getMatchingMethods(Type T, llvm::SmallPtrSetImpl &expectedTypes, SmallVectorImpl &result); public: ConformingMethodListCallbacks(Parser &P, ArrayRef ExpectedTypeNames, ConformingMethodListConsumer &Consumer) : CodeCompletionCallbacks(P), DoneParsingCallback(), ExpectedTypeNames(ExpectedTypeNames), Consumer(Consumer) {} // Only handle callbacks for suffix completions. // { void completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) override; void completePostfixExpr(CodeCompletionExpr *E, bool hasSpace) override; // } void doneParsing(SourceFile *SrcFile) override; }; void ConformingMethodListCallbacks::completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) { CurDeclContext = P.CurDeclContext; CCExpr = E; } void ConformingMethodListCallbacks::completePostfixExpr(CodeCompletionExpr *E, bool hasSpace) { CurDeclContext = P.CurDeclContext; CCExpr = E; } class ConformingMethodListCallback : public TypeCheckCompletionCallback { public: struct Result { Type Ty; /// Types of variables that were determined in the solution that produced /// this result. This in particular includes parameters of closures that /// were type-checked with the code completion expression. llvm::SmallDenseMap SolutionSpecificVarTypes; }; private: CodeCompletionExpr *CCExpr; SmallVector Results; void sawSolutionImpl(const constraints::Solution &S) override { if (!S.hasType(CCExpr->getBase())) { return; } if (Type T = getTypeForCompletion(S, CCExpr->getBase())) { llvm::SmallDenseMap SolutionSpecificVarTypes; getSolutionSpecificVarTypes(S, SolutionSpecificVarTypes); Results.push_back({T, SolutionSpecificVarTypes}); } } public: ConformingMethodListCallback(CodeCompletionExpr *CCExpr) : CCExpr(CCExpr) {} ArrayRef getResults() const { return Results; } }; void ConformingMethodListCallbacks::doneParsing(SourceFile *SrcFile) { if (!CCExpr || !CCExpr->getBase()) return; ConformingMethodListCallback TypeCheckCallback(CCExpr); { llvm::SaveAndRestore CompletionCollector( Context.CompletionCallback, &TypeCheckCallback); typeCheckContextAt( TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), CCExpr->getLoc()); } if (TypeCheckCallback.getResults().size() != 1) { // Either no results or results were ambiguous, which we cannot handle. return; } auto Res = TypeCheckCallback.getResults()[0]; Type T = Res.Ty; WithSolutionSpecificVarTypesRAII VarType(Res.SolutionSpecificVarTypes); if (!T || T->is() || T->is()) return; T = T->getRValueType(); // If there are no (instance) members for this type, bail. if (!T->mayHaveMembers() || T->is()) { return; } auto interfaceTy = T; if (T->hasArchetype()) interfaceTy = interfaceTy->mapTypeOutOfContext(); llvm::SmallPtrSet expectedProtocols; for (auto Name: ExpectedTypeNames) { if (auto *PD = resolveProtocolName(CurDeclContext, Name)) { expectedProtocols.insert(PD); } } // Collect the matching methods. ConformingMethodListResult result(CurDeclContext, interfaceTy); getMatchingMethods(T, expectedProtocols, result.Members); Consumer.handleResult(result); } void ConformingMethodListCallbacks::getMatchingMethods( Type T, llvm::SmallPtrSetImpl &expectedTypes, SmallVectorImpl &result) { assert(T->mayHaveMembers() && !T->is()); class LocalConsumer : public VisibleDeclConsumer { /// The type of the parsed expression. Type T; /// The list of expected types. llvm::SmallPtrSetImpl &ExpectedTypes; /// Result sink to populate. SmallVectorImpl &Result; /// Returns true if \p VD is a instance method whose return type conforms /// to the requested protocols. bool isMatchingMethod(ValueDecl *VD) { auto *FD = dyn_cast(VD); if (!FD) return false; if (FD->isStatic() || FD->isOperator()) return false; assert(!T->hasTypeParameter()); // T may contain primary archetypes from some fixed generic signature G. // This might be unrelated to the generic signature of FD. However if // FD has a generic parameter of its own and it returns a type containing // that parameter, we want to map it to the corresponding archetype // from the generic environment of FD, because all we do with the // resulting type is check conformance. If the conformance is conditional, // we might run into trouble with really complicated cases but the fake // archetype setup will mostly work. auto substitutions = T->getMemberSubstitutionMap( FD, FD->getGenericEnvironment()); auto resultTy = FD->getResultInterfaceType().subst(substitutions); if (resultTy->is()) return false; // The return type conforms to any of the requested protocols. for (auto Proto : ExpectedTypes) { if (checkConformance(resultTy, Proto)) return true; } return false; } public: LocalConsumer(DeclContext *DC, Type T, llvm::SmallPtrSetImpl &expectedTypes, SmallVectorImpl &result) : T(T), ExpectedTypes(expectedTypes), Result(result) {} void foundDecl(ValueDecl *VD, DeclVisibilityKind reason, DynamicLookupInfo) override { if (isMatchingMethod(VD) && !VD->shouldHideFromEditor()) Result.push_back(VD); } } LocalConsumer(CurDeclContext, T, expectedTypes, result); lookupVisibleMemberDecls(LocalConsumer, MetatypeType::get(T), Loc, CurDeclContext, /*includeInstanceMembers=*/false, /*includeDerivedRequirements*/false, /*includeProtocolExtensionMembers*/true); } } // anonymous namespace. IDEInspectionCallbacksFactory * swift::ide::makeConformingMethodListCallbacksFactory( ArrayRef expectedTypeNames, ConformingMethodListConsumer &Consumer) { // CC callback factory which produces 'ContextInfoCallbacks'. class ConformingMethodListCallbacksFactoryImpl : public IDEInspectionCallbacksFactory { ArrayRef ExpectedTypeNames; ConformingMethodListConsumer &Consumer; public: ConformingMethodListCallbacksFactoryImpl( ArrayRef ExpectedTypeNames, ConformingMethodListConsumer &Consumer) : ExpectedTypeNames(ExpectedTypeNames), Consumer(Consumer) {} Callbacks createCallbacks(Parser &P) override { auto Callback = std::make_shared( P, ExpectedTypeNames, Consumer); return {Callback, Callback}; } }; return new ConformingMethodListCallbacksFactoryImpl(expectedTypeNames, Consumer); }