diff --git a/include/swift/IDE/CodeCompletionResult.h b/include/swift/IDE/CodeCompletionResult.h index 991bd47e2e3..5de46d576dc 100644 --- a/include/swift/IDE/CodeCompletionResult.h +++ b/include/swift/IDE/CodeCompletionResult.h @@ -223,6 +223,7 @@ enum class CompletionKind : uint8_t { StmtLabel, ForEachPatternBeginning, TypeAttrBeginning, + OptionalBinding, }; enum class CodeCompletionDiagnosticSeverity : uint8_t { diff --git a/include/swift/IDE/CompletionLookup.h b/include/swift/IDE/CompletionLookup.h index 9dd5237b399..5f0945fa1aa 100644 --- a/include/swift/IDE/CompletionLookup.h +++ b/include/swift/IDE/CompletionLookup.h @@ -33,12 +33,15 @@ namespace swift { namespace ide { -using DeclFilter = std::function; +using DeclFilter = + std::function; /// A filter that always returns \c true. -bool DefaultFilter(ValueDecl *VD, DeclVisibilityKind Kind); +bool DefaultFilter(ValueDecl *VD, DeclVisibilityKind Kind, + DynamicLookupInfo dynamicLookupInfo); -bool KeyPathFilter(ValueDecl *decl, DeclVisibilityKind); +bool KeyPathFilter(ValueDecl *decl, DeclVisibilityKind, + DynamicLookupInfo dynamicLookupInfo); /// Returns \c true only if the completion is happening for top-level /// declrarations. i.e.: @@ -528,7 +531,7 @@ public: : Consumer(Consumer), Filter(Filter) {} void foundDecl(ValueDecl *VD, DeclVisibilityKind Kind, DynamicLookupInfo dynamicLookupInfo) override { - if (Filter(VD, Kind)) + if (Filter(VD, Kind, dynamicLookupInfo)) Consumer.foundDecl(VD, Kind, dynamicLookupInfo); } }; @@ -588,6 +591,8 @@ public: bool ResultsHaveLeadingDot); void getStmtLabelCompletions(SourceLoc Loc, bool isContinue); + + void getOptionalBindingCompletions(SourceLoc Loc); }; } // end namespace ide diff --git a/include/swift/Parse/CodeCompletionCallbacks.h b/include/swift/Parse/CodeCompletionCallbacks.h index 3de2afc37cf..ccc55319c55 100644 --- a/include/swift/Parse/CodeCompletionCallbacks.h +++ b/include/swift/Parse/CodeCompletionCallbacks.h @@ -236,6 +236,8 @@ public: virtual void completeTypeAttrBeginning() {}; + virtual void completeOptionalBinding(){}; + /// Signals that the AST for the all the delayed-parsed code was /// constructed. No \c complete*() callbacks will be done after this. virtual void doneParsing() = 0; diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 98ff700ba3a..54c75a24f5f 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -274,6 +274,7 @@ public: void completeStmtLabel(StmtKind ParentKind) override; void completeForEachPatternBeginning(bool hasTry, bool hasAwait) override; void completeTypeAttrBeginning() override; + void completeOptionalBinding() override; void doneParsing() override; @@ -625,6 +626,11 @@ void CodeCompletionCallbacksImpl::completeForEachPatternBeginning( ParsedKeywords.emplace_back("await"); } +void CodeCompletionCallbacksImpl::completeOptionalBinding() { + CurDeclContext = P.CurDeclContext; + Kind = CompletionKind::OptionalBinding; +} + void CodeCompletionCallbacksImpl::completeTypeAttrBeginning() { CurDeclContext = P.CurDeclContext; Kind = CompletionKind::TypeAttrBeginning; @@ -907,6 +913,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink, case CompletionKind::PrecedenceGroup: case CompletionKind::StmtLabel: case CompletionKind::TypeAttrBeginning: + case CompletionKind::OptionalBinding: break; case CompletionKind::EffectsSpecifier: { @@ -1886,6 +1893,12 @@ void CodeCompletionCallbacksImpl::doneParsing() { break; } + case CompletionKind::OptionalBinding: { + SourceLoc Loc = P.Context.SourceMgr.getCodeCompletionLoc(); + Lookup.getOptionalBindingCompletions(Loc); + break; + } + case CompletionKind::AfterIfStmtElse: case CompletionKind::CaseStmtKeyword: case CompletionKind::EffectsSpecifier: diff --git a/lib/IDE/CompletionLookup.cpp b/lib/IDE/CompletionLookup.cpp index 9983fd2f406..e836ae286c2 100644 --- a/lib/IDE/CompletionLookup.cpp +++ b/lib/IDE/CompletionLookup.cpp @@ -119,11 +119,13 @@ static bool hasTrivialTrailingClosure(const FuncDecl *FD, } } // end anonymous namespace -bool swift::ide::DefaultFilter(ValueDecl *VD, DeclVisibilityKind Kind) { +bool swift::ide::DefaultFilter(ValueDecl *VD, DeclVisibilityKind Kind, + DynamicLookupInfo dynamicLookupInfo) { return true; } -bool swift::ide::KeyPathFilter(ValueDecl *decl, DeclVisibilityKind) { +bool swift::ide::KeyPathFilter(ValueDecl *decl, DeclVisibilityKind, + DynamicLookupInfo dynamicLookupInfo) { return isa(decl) || (isa(decl) && decl->getDeclContext()->isTypeContext()); } @@ -1797,7 +1799,7 @@ void CompletionLookup::foundDecl(ValueDecl *D, DeclVisibilityKind Reason, if (D->shouldHideFromEditor()) return; - if (IsKeyPathExpr && !KeyPathFilter(D, Reason)) + if (IsKeyPathExpr && !KeyPathFilter(D, Reason, dynamicLookupInfo)) return; if (IsSwiftKeyPathExpr && !SwiftKeyPathFilter(D, Reason)) @@ -2693,7 +2695,8 @@ void CompletionLookup::getUnresolvedMemberCompletions(Type T) { // type and has the same type (or if the member is a function, then the // same result type) as the contextual type. FilteredDeclConsumer consumer(*this, - [=](ValueDecl *VD, DeclVisibilityKind Reason) { + [=](ValueDecl *VD, DeclVisibilityKind Reason, + DynamicLookupInfo dynamicLookupInfo) { // In optional context, ignore // '.init()', 'init(nilLiteral:)', return !isInitializerOnOptional(T, VD); @@ -3116,3 +3119,32 @@ void CompletionLookup::getStmtLabelCompletions(SourceLoc Loc, bool isContinue) { Builder.addTextChunk(name.str()); } } + +void CompletionLookup::getOptionalBindingCompletions(SourceLoc Loc) { + ExprType = Type(); + Kind = LookupKind::ValueInDeclContext; + NeedLeadingDot = false; + + AccessFilteringDeclConsumer AccessFilteringConsumer(CurrDeclContext, *this); + + // Suggest only 'Optional' type var decls (incl. parameters) + FilteredDeclConsumer FilteringConsumer( + AccessFilteringConsumer, + [&](ValueDecl *VD, DeclVisibilityKind Reason, + DynamicLookupInfo dynamicLookupInfo) -> bool { + auto *VarD = dyn_cast(VD); + if (!VarD) + return false; + + auto Ty = getTypeOfMember(VD, dynamicLookupInfo); + return Ty->isOptional(); + }); + + // FIXME: Currently, it doesn't include top level decls for performance + // reason. Enabling 'IncludeTopLevel' pulls everything including imported + // modules. For suggesting top level results, we need a way to filter cached + // results. + + lookupVisibleDecls(FilteringConsumer, CurrDeclContext, + /*IncludeTopLevel=*/false, Loc); +} diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index fea7756c5f7..1086b5b94d1 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -1590,6 +1590,13 @@ Parser::parseStmtConditionElement(SmallVectorImpl &result, ThePattern = makeParserResult(Status, P); } + } else if (Tok.is(tok::code_complete)) { + if (CodeCompletion) { + CodeCompletion->completeOptionalBinding(); + } + ThePattern = makeParserResult(new (Context) AnyPattern(Tok.getLoc())); + ThePattern.setHasCodeCompletionAndIsError(); + consumeToken(tok::code_complete); } else { ConditionCtxt.setCreateSyntax(SyntaxKind::OptionalBindingCondition); // Otherwise, this is an implicit optional binding "if let". diff --git a/test/IDE/complete_optional_binding.swift b/test/IDE/complete_optional_binding.swift new file mode 100644 index 00000000000..e283d72cb0d --- /dev/null +++ b/test/IDE/complete_optional_binding.swift @@ -0,0 +1,75 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t + +let topLevelOpt: Int? + +do { + let topLevelLocalOpt: Int? + let topLevelLocalNonOpt: Int + + if let #^TOPLEVEL_IF_LET?check=TOPLEVEL^# +// TOPLEVEL: Begin completions, 1 items +// TOPLEVEL-DAG: Decl[LocalVar]/Local: topLevelLocalOpt[#Int?#]; +// TOPLEVEL: End completions +// FIXME: show 'topLevelOpt' +} + +struct MyStruct { + var propOpt: Int? + var propNonOpt: Int + var propGenOpt: T? + var propGenNonOpt: T + + func testMethod(paramGenOpt: U?, paramGenNonOpt: U, paramOpt: Int?, paramNonOpt: Int) { + var localOpt: Int? + var localNonOpt: Int + var localGenOpt: U? + var localGenNonOpt: U + + do { + if let #^IF_LET?check=IN_FUNC^# + } + do { + if var #^IF_VAR?check=IN_FUNC^# + } + do { + if true {} else if let #^ELSEIF_LET?check=IN_FUNC^# + } + do { + if true {} else if var #^ELSEIF_VAR?check=IN_FUNC^# + } + do { + guard let #^GUARD_LET?check=IN_FUNC^# + } + do { + guard var #^GUARD_VAR?check=IN_FUNC^# + } + do { + while let #^WHILE_LET?check=IN_FUNC^# + } + do { + while var #^WHILE_VAR?check=IN_FUNC^# + } + +// IN_FUNC: Begin completions, 6 items +// IN_FUNC-DAG: Decl[LocalVar]/Local: localOpt[#Int?#]; +// IN_FUNC-DAG: Decl[LocalVar]/Local: localGenOpt[#U?#]; +// IN_FUNC-DAG: Decl[LocalVar]/Local: paramGenOpt[#U?#]; +// IN_FUNC-DAG: Decl[LocalVar]/Local: paramOpt[#Int?#]; +// IN_FUNC-DAG: Decl[InstanceVar]/CurrNominal: propOpt[#Int?#]; +// IN_FUNC-DAG: Decl[InstanceVar]/CurrNominal: propGenOpt[#T?#]; +// IN_FUNC-NOT: NonOpt +// IN_FUNC: End completions + } +} + +func testPreviousElements() { + let localOptOpt: Int?? + + if let localOpt = localOptOpt, let localNonOpt = localOpt, let #^PREV_ELEMENT^# +// PREV_ELEMENT: Begin completions, 2 items +// PREV_ELEMENT-DAG: Decl[LocalVar]/Local: localOptOpt[#Int??#]; +// PREV_ELEMENT-DAG: Decl[LocalVar]/Local: localOpt[#Int?#]; +// PREV_ELEMENT-NOT: NonOpt +// PREV_ELEMENT: End completions +}