From a237fae3897e7cbec724ac297ad277ec785fb4e4 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 15 Mar 2019 11:38:37 -0700 Subject: [PATCH 01/10] [AST] Don't return decls from unsatisfied conformance When a type conditionally conforms to a protocol, it used to provide symbols from extension to that protocol. e.g.: protocol P {} extension P { func foo() {} } struct S {} extension S: P where T == Int {} func test(val: S) { val.#^COMPLETE^# } This should not provide `foo()` method. rdar://problem/36594731 (cherry picked from commit dcb1db26bbfe0a8d8c32a8760df01bf7bb587e4a) --- lib/Sema/LookupVisibleDecls.cpp | 6 ++++++ test/IDE/complete_constrained.swift | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/Sema/LookupVisibleDecls.cpp b/lib/Sema/LookupVisibleDecls.cpp index f41db09f6c3e1..ba85dc8bc9e09 100644 --- a/lib/Sema/LookupVisibleDecls.cpp +++ b/lib/Sema/LookupVisibleDecls.cpp @@ -388,12 +388,18 @@ static void lookupDeclsFromProtocolsBeingConformedTo( NominalTypeDecl *CurrNominal = BaseTy->getAnyNominal(); if (!CurrNominal) return; + ModuleDecl *Module = FromContext->getParentModule(); for (auto Conformance : CurrNominal->getAllConformances()) { auto Proto = Conformance->getProtocol(); if (!Proto->isAccessibleFrom(FromContext)) continue; + // Skip unsatisfied conditional conformances. + if (Conformance->getConditionalRequirementsIfAvailable() && + !Module->conformsToProtocol(BaseTy, Proto)) + continue; + DeclVisibilityKind ReasonForThisProtocol; if (Reason == DeclVisibilityKind::MemberOfCurrentNominal) ReasonForThisProtocol = diff --git a/test/IDE/complete_constrained.swift b/test/IDE/complete_constrained.swift index 3922d3f86393e..61e47121c2b13 100644 --- a/test/IDE/complete_constrained.swift +++ b/test/IDE/complete_constrained.swift @@ -48,19 +48,31 @@ extension MyStruct { func concreteExt_None_TConformsToSomeProto(_ x: U) -> Int where T: SomeProto { return 1 } } +protocol Proto_Int {} +extension Proto_Int { + func conditional_Int() -> Int { return 1 } +} +protocol Proto_String {} +extension Proto_String { + func conditional_String() -> Int { return 1 } +} +extension MyStruct: Proto_Int where T == Int{} +extension MyStruct: Proto_String where T == String {} + func foo(s: MyStruct) { let _ = s.#^MYSTRUCT_INT_DOT^# -// MYSTRUCT_INT_DOT: Begin completions, 6 items +// MYSTRUCT_INT_DOT: Begin completions, 7 items // MYSTRUCT_INT_DOT-DAG: Keyword[self]/CurrNominal: self[#MyStruct#]; name=self // MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/CurrNominal: methodWithConstrainedGenericParam({#x: SomeProto#})[#Int#]; name=methodWithConstrainedGenericParam(x: SomeProto) // MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/CurrNominal: concreteExt_TEqInt_None()[#Int#]; name=concreteExt_TEqInt_None() // MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/CurrNominal: concreteExt_None_TEqInt({#(x): U#})[#Int#]; name=concreteExt_None_TEqInt(x: U) // MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: protoExt_AssocEqInt_None()[#Int#]; name=protoExt_AssocEqInt_None() // MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: protoExt_None_AssocEqInt({#(x): U#})[#Int#]; name=protoExt_None_AssocEqInt(x: U) +// MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: conditional_Int()[#Int#]; name=conditional_Int() // MYSTRUCT_INT_DOT: End completions let _ = MyStruct.#^META_MYSTRUCT_INT_DOT^# -// META_MYSTRUCT_INT_DOT: Begin completions, 10 items +// META_MYSTRUCT_INT_DOT: Begin completions, 11 items // META_MYSTRUCT_INT_DOT-DAG: Keyword[self]/CurrNominal: self[#MyStruct.Type#]; name=self // META_MYSTRUCT_INT_DOT-DAG: Keyword/CurrNominal: Type[#MyStruct.Type#]; name=Type // META_MYSTRUCT_INT_DOT-DAG: Decl[TypeAlias]/CurrNominal: Assoc[#T#]; name=Assoc @@ -71,6 +83,7 @@ func foo(s: MyStruct) { // META_MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/CurrNominal: concreteExt_None_TEqInt({#(self): MyStruct#})[#(U) -> Int#]; name=concreteExt_None_TEqInt(self: MyStruct) // META_MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: protoExt_AssocEqInt_None({#(self): MyStruct#})[#() -> Int#]; name=protoExt_AssocEqInt_None(self: MyStruct) // META_MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: protoExt_None_AssocEqInt({#(self): MyStruct#})[#(U) -> Int#]; name=protoExt_None_AssocEqInt(self: MyStruct) +// META_MYSTRUCT_INT_DOT-DAG: Decl[InstanceMethod]/Super: conditional_Int({#(self): MyStruct#})[#() -> Int#]; name=conditional_Int(self: MyStruct) // META_MYSTRUCT_INT_DOT: End completions } From 91c8a9a67ae1b35bf6ce81b62079ac95e4634e21 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Sun, 17 Mar 2019 16:35:16 -0700 Subject: [PATCH 02/10] [CodeCompletion] Escape declaration base name if needed There should be escaped identifiers in code completion: - As primary expression: Any keyword name except for `self` and `Self`. - After dot: Something named `init`. rdar://problem/16232627 (cherry picked from commit df473f641d997a9c5e7f524cb2617dd48b0c5382) --- lib/IDE/CodeCompletion.cpp | 41 +++++++++-- lib/IDE/CodeCompletionResultBuilder.h | 9 +-- test/IDE/complete_escaped_keyword.swift | 97 +++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 test/IDE/complete_escaped_keyword.swift diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 1490fb4ff6c4b..9a0f5d0ec8099 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -1823,6 +1823,32 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { llvm_unreachable("unhandled kind"); } + void addValueBaseName(CodeCompletionResultBuilder &Builder, + DeclBaseName Name) { + auto NameStr = Name.userFacingName(); + bool shouldEscapeKeywords; + if (Name.isSpecial()) { + // Special names (i.e. 'init') are always displayed as its user facing + // name. + shouldEscapeKeywords = false; + } else if (ExprType) { + // After dot. User can write any keyword after '.' except for `init` and + // `self`. E.g. 'func `init`()' must be called by 'expr.`init`()'. + shouldEscapeKeywords = NameStr == "self" || NameStr == "init"; + } else { + // As primary expresson. We have to escape almost every keywords except + // for 'self' and 'Self'. + shouldEscapeKeywords = NameStr != "self" && NameStr != "Self"; + } + + if (!shouldEscapeKeywords) { + Builder.addTextChunk(NameStr); + } else { + SmallString<16> buffer; + Builder.addTextChunk(Builder.escapeKeyword(NameStr, true, buffer)); + } + } + void addLeadingDot(CodeCompletionResultBuilder &Builder) { if (NeedOptionalUnwrap) { Builder.setNumBytesToErase(NumBytesToEraseForOptionalUnwrap); @@ -1995,7 +2021,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { VD->shouldHideFromEditor()) return; - StringRef Name = VD->getName().get(); + Identifier Name = VD->getName(); assert(!Name.empty() && "name should not be empty"); CommandWordsPairs Pairs; @@ -2005,7 +2031,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { getSemanticContext(VD, Reason), ExpectedTypes); Builder.setAssociatedDecl(VD); addLeadingDot(Builder); - Builder.addTextChunk(Name); + addValueBaseName(Builder, Name); setClangDeclKeywords(VD, Pairs, Builder); if (!VD->hasValidSignature()) @@ -2013,7 +2039,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { // Add a type annotation. Type VarType = getTypeOfMember(VD); - if (VD->getName() != Ctx.Id_self && VD->isInOut()) { + if (Name != Ctx.Id_self && VD->isInOut()) { // It is useful to show inout for function parameters. // But for 'self' it is just noise. VarType = InOutType::get(VarType); @@ -2319,7 +2345,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { return; foundFunction(FD); - StringRef Name = FD->getName().get(); + Identifier Name = FD->getName(); assert(!Name.empty() && "name should not be empty"); Type FunctionType = getTypeOfMember(FD); @@ -2350,7 +2376,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { setClangDeclKeywords(FD, Pairs, Builder); Builder.setAssociatedDecl(FD); addLeadingDot(Builder); - Builder.addTextChunk(Name); + addValueBaseName(Builder, Name); if (IsDynamicLookup) Builder.addDynamicLookupMethodCallTail(); else if (FD->getAttrs().hasAttribute()) @@ -2680,8 +2706,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { Builder.setAssociatedDecl(EED); setClangDeclKeywords(EED, Pairs, Builder); addLeadingDot(Builder); - - Builder.addTextChunk(EED->getName().str()); + addValueBaseName(Builder, EED->getName()); // Enum element is of function type; (Self.type) -> Self or // (Self.Type) -> (Args...) -> Self. @@ -2759,7 +2784,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { // Base name addLeadingDot(Builder); - Builder.addTextChunk(AFD->getBaseName().userFacingName()); + addValueBaseName(Builder, AFD->getBaseName()); // Add the argument labels. auto ArgLabels = AFD->getFullName().getArgumentNames(); diff --git a/lib/IDE/CodeCompletionResultBuilder.h b/lib/IDE/CodeCompletionResultBuilder.h index b28e678fb9853..be36cde1998b0 100644 --- a/lib/IDE/CodeCompletionResultBuilder.h +++ b/lib/IDE/CodeCompletionResultBuilder.h @@ -267,9 +267,8 @@ class CodeCompletionResultBuilder { addTypeAnnotation(Annotation); } - StringRef escapeArgumentLabel(StringRef Word, - bool escapeAllKeywords, - llvm::SmallString<16> &EscapedKeyword) { + StringRef escapeKeyword(StringRef Word, bool escapeAllKeywords, + llvm::SmallString<16> &EscapedKeyword) { bool shouldEscape = false; if (escapeAllKeywords) { #define KEYWORD(kw) .Case(#kw, true) @@ -325,7 +324,7 @@ class CodeCompletionResultBuilder { llvm::SmallString<16> EscapedKeyword; addChunkWithText( CodeCompletionString::Chunk::ChunkKind::CallParameterName, - escapeArgumentLabel(Name.str(), false, EscapedKeyword)); + escapeKeyword(Name.str(), false, EscapedKeyword)); addChunkWithTextNoCopy( CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": "); } else if (!LocalName.empty()) { @@ -333,7 +332,7 @@ class CodeCompletionResultBuilder { llvm::SmallString<16> EscapedKeyword; addChunkWithText( CodeCompletionString::Chunk::ChunkKind::CallParameterInternalName, - escapeArgumentLabel(LocalName.str(), false, EscapedKeyword)); + escapeKeyword(LocalName.str(), false, EscapedKeyword)); addChunkWithTextNoCopy( CodeCompletionString::Chunk::ChunkKind::CallParameterColon, ": "); } diff --git a/test/IDE/complete_escaped_keyword.swift b/test/IDE/complete_escaped_keyword.swift new file mode 100644 index 0000000000000..43563f0a181f6 --- /dev/null +++ b/test/IDE/complete_escaped_keyword.swift @@ -0,0 +1,97 @@ +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_PRIMARY | %FileCheck %s -check-prefix=STATIC_PRIMARY +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_SELF_NODOT | %FileCheck %s -check-prefix=STATIC_SELF_NODOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=STATIC_SELF_DOT | %FileCheck %s -check-prefix=STATIC_SELF_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=META_NODOT | %FileCheck %s -check-prefix=STATIC_SELF_NODOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=META_DOT | %FileCheck %s -check-prefix=STATIC_SELF_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_PRIMARY | %FileCheck %s -check-prefix=INSTANCE_PRIMARY +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_SELF_NODOT | %FileCheck %s -check-prefix=INSTANCE_SELF_NODOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=INSTANCE_SELF_DOT | %FileCheck %s -check-prefix=INSTANCE_SELF_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=VALUE_NODOT | %FileCheck %s -check-prefix=INSTANCE_SELF_NODOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=VALUE_DOT | %FileCheck %s -check-prefix=INSTANCE_SELF_DOT + + +enum MyEnum { + case `class`(struct: String) + case `let`(`var`: String) + + init(`init`: String) {} + static func `public`(private: String) -> Int {} + + func `init`(deinit: String) -> Int {} + func `if`(else: String) -> Int {} + + var `self`: Int { return 0 } + + static func testStatic(meta: MyEnum.Type) { + let _ = #^STATIC_PRIMARY^# +// STATIC_PRIMARY: Begin completion +// STATIC_PRIMARY-DAG: Decl[LocalVar]/Local: self[#MyEnum.Type#]; name=self +// STATIC_PRIMARY-DAG: Decl[EnumElement]/CurrNominal: `class`({#struct: String#})[#MyEnum#]; name=`class`(struct: String) +// STATIC_PRIMARY-DAG: Decl[EnumElement]/CurrNominal: `let`({#`var`: String#})[#MyEnum#]; name=`let`(`var`: String) +// STATIC_PRIMARY-DAG: Decl[StaticMethod]/CurrNominal: `public`({#private: String#})[#Int#]; name=`public`(private: String) +// STATIC_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum) +// STATIC_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `if`({#(self): MyEnum#})[#(else: String) -> Int#]; name=`if`(self: MyEnum) +// STATIC_PRIMARY: End completion + + let _ = self#^STATIC_SELF_NODOT^# +// STATIC_SELF_NODOT: Begin completions +// STATIC_SELF_NODOT-DAG: Keyword[self]/CurrNominal: .self[#MyEnum.Type#]; name=self +// STATIC_SELF_NODOT-DAG: Decl[EnumElement]/CurrNominal: .class({#struct: String#})[#MyEnum#]; name=class(struct: String) +// STATIC_SELF_NODOT-DAG: Decl[EnumElement]/CurrNominal: .let({#`var`: String#})[#MyEnum#]; name=let(`var`: String) +// STATIC_SELF_NODOT-DAG: Decl[Constructor]/CurrNominal: .init({#init: String#})[#MyEnum#]; name=init(init: String) +// STATIC_SELF_NODOT-DAG: Decl[StaticMethod]/CurrNominal: .public({#private: String#})[#Int#]; name=public(private: String) +// STATIC_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .`init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum) +// STATIC_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .if({#(self): MyEnum#})[#(else: String) -> Int#]; name=if(self: MyEnum) +// STATIC_SELF_NODOT: End completion + + let _ = self.#^STATIC_SELF_DOT^# +// STATIC_SELF_DOT: Begin completions +// STATIC_SELF_DOT-DAG: Keyword[self]/CurrNominal: self[#MyEnum.Type#]; name=self +// STATIC_SELF_DOT-DAG: Decl[EnumElement]/CurrNominal: class({#struct: String#})[#MyEnum#]; name=class(struct: String) +// STATIC_SELF_DOT-DAG: Decl[EnumElement]/CurrNominal: let({#`var`: String#})[#MyEnum#]; name=let(`var`: String) +// STATIC_SELF_DOT-DAG: Decl[Constructor]/CurrNominal: init({#init: String#})[#MyEnum#]; name=init(init: String) +// STATIC_SELF_DOT-DAG: Decl[StaticMethod]/CurrNominal: public({#private: String#})[#Int#]; name=public(private: String) +// STATIC_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#(self): MyEnum#})[#(deinit: String) -> Int#]; name=`init`(self: MyEnum) +// STATIC_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: if({#(self): MyEnum#})[#(else: String) -> Int#]; name=if(self: MyEnum) +// STATIC_SELF_DOT: End completion + + let _ = meta#^META_NODOT^# +// SAME AS 'STATIC_SELF_NODOT'. + + let _ = meta.#^META_DOT^# +// SAME AS 'STATIC_SELF_DOT'. + } + + func testInstance(val: MyEnum) { + let _ = #^INSTANCE_PRIMARY^# +// INSTANCE_PRIMARY: Begin completion +// INSTANCE_PRIMARY-DAG: Decl[LocalVar]/Local: self[#MyEnum#]; name=self +// INSTANCE_PRIMARY-DAG: Decl[InstanceVar]/CurrNominal: self[#Int#]; name=self +// FIXME: ^ This is shadowed. We should hide this. +// INSTANCE_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#deinit: String#})[#Int#]; name=`init`(deinit: String) +// INSTANCE_PRIMARY-DAG: Decl[InstanceMethod]/CurrNominal: `if`({#else: String#})[#Int#]; name=`if`(else: String) +// INSTANCE_PRIMARY: End completion + + let _ = self#^INSTANCE_SELF_NODOT^# +// INSTANCE_SELF_NODOT: Begin completions +// INSTANCE_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .`init`({#deinit: String#})[#Int#]; name=`init`(deinit: String) +// INSTANCE_SELF_NODOT-DAG: Decl[InstanceMethod]/CurrNominal: .if({#else: String#})[#Int#]; name=if(else: String) +// INSTANCE_SELF_NODOT-DAG: Decl[InstanceVar]/CurrNominal: .`self`[#Int#]; name=`self` +// INSTANCE_SELF_NODOT-DAG: Keyword[self]/CurrNominal: .self[#MyEnum#]; name=self + +// INSTANCE_SELF_NODOT: End completions + + let _ = self.#^INSTANCE_SELF_DOT^# +// INSTANCE_SELF_DOT: Begin completions +// INSTANCE_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: `init`({#deinit: String#})[#Int#]; name=`init`(deinit: String) +// INSTANCE_SELF_DOT-DAG: Decl[InstanceMethod]/CurrNominal: if({#else: String#})[#Int#]; name=if(else: String) +// INSTANCE_SELF_DOT-DAG: Decl[InstanceVar]/CurrNominal: `self`[#Int#]; name=`self` +// INSTANCE_SELF_DOT-DAG: Keyword[self]/CurrNominal: self[#MyEnum#]; name=self +// INSTANCE_SELF_DOT: End completions + + let _ = val#^VALUE_NODOT^# +// SAME AS 'INSTANCE_SELF_NODOT'. + let _ = val.#^VALUE_DOT^# +// SAME AS 'INSTANCE_SELF_DOT'. + } +} From 2930e5945621592993d7424ea6ab135b30a3a5ee Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 18 Mar 2019 12:31:54 -0700 Subject: [PATCH 03/10] [CodeCompletion] Add already fixed test case rdar://problem/36818556 (cherry picked from commit 3e4ab1fd313408b91f8b7a18ef70a6055dc076fc) --- test/IDE/complete_in_closures.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/IDE/complete_in_closures.swift b/test/IDE/complete_in_closures.swift index 2c5686dc3a8be..4b31be20239cd 100644 --- a/test/IDE/complete_in_closures.swift +++ b/test/IDE/complete_in_closures.swift @@ -55,6 +55,7 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_IIFE_2 | %FileCheck %s -check-prefix=IN_IIFE_1 // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_IIFE_3 | %FileCheck %s -check-prefix=IN_IIFE_1 // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_IIFE_4 | %FileCheck %s -check-prefix=IN_IIFE_1 +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ERROR_IN_CLOSURE_IN_INITIALIZER | %FileCheck %s -check-prefix=ERROR_IN_CLOSURE_IN_INITIALIZER // ERROR_COMMON: found code completion token // ERROR_COMMON-NOT: Begin completions @@ -369,3 +370,22 @@ func testIIFE() { // IN_IIFE_1: Begin completions // IN_IIFE_1-DAG: Decl[EnumElement]/ExprSpecific: north[#SomeEnum#] // IN_IIFE_1-DAG: Decl[EnumElement]/ExprSpecific: south[#SomeEnum#] + +extension Error { + var myErrorNumber: Int { return 0 } +} + +class C { + var foo: String = { + do { + } catch { + error.#^ERROR_IN_CLOSURE_IN_INITIALIZER^# +// ERROR_IN_CLOSURE_IN_INITIALIZER: Begin completions +// ERROR_IN_CLOSURE_IN_INITIALIZER-DAG: Keyword[self]/CurrNominal: self[#Error#]; name=self +// ERROR_IN_CLOSURE_IN_INITIALIZER-DAG: Decl[InstanceVar]/CurrNominal: myErrorNumber[#Int#]; name=myErrorNumber +// ERROR_IN_CLOSURE_IN_INITIALIZER: End completions + } + return "" + }() +} + From b281a3cce7db050efeaeb4af49163983a7468cf1 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 18 Mar 2019 15:51:31 -0700 Subject: [PATCH 04/10] [AST] Prefer available member in lookupVisibleDecls() 'init?()' and 'init()' are considerd conflicting. But user can declare both if only one of them is available. rdar://problem/47408946 (cherry picked from commit c01799cbcf07635e6a20943bad2bcfe917df151d) --- lib/Sema/LookupVisibleDecls.cpp | 5 ++++- test/IDE/complete_constructor.swift | 33 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/Sema/LookupVisibleDecls.cpp b/lib/Sema/LookupVisibleDecls.cpp index ba85dc8bc9e09..9ecfd2bdfbdac 100644 --- a/lib/Sema/LookupVisibleDecls.cpp +++ b/lib/Sema/LookupVisibleDecls.cpp @@ -800,7 +800,10 @@ class OverrideFilteringConsumer : public VisibleDeclConsumer { OtherSignature, OtherSignatureType, /*wouldConflictInSwift5*/nullptr, /*skipProtocolExtensionCheck*/true)) { - if (VD->getFormalAccess() > OtherVD->getFormalAccess()) { + if (VD->getFormalAccess() > OtherVD->getFormalAccess() || + //Prefer available one. + (!AvailableAttr::isUnavailable(VD) && + AvailableAttr::isUnavailable(OtherVD))) { PossiblyConflicting.erase(I); PossiblyConflicting.insert(VD); diff --git a/test/IDE/complete_constructor.swift b/test/IDE/complete_constructor.swift index ca65fd02efc13..06666aaaa109e 100644 --- a/test/IDE/complete_constructor.swift +++ b/test/IDE/complete_constructor.swift @@ -47,6 +47,9 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_IN_INIT_3 | %FileCheck %s -check-prefix=CLOSURE_IN_INIT_1 // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_IN_INIT_4 | %FileCheck %s -check-prefix=CLOSURE_IN_INIT_1 +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=AVAILABLE_1 | %FileCheck %s -check-prefix=AVAILABLE_1 +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=AVAILABLE_2 | %FileCheck %s -check-prefix=AVAILABLE_2 + func freeFunc() {} //===--- @@ -332,3 +335,33 @@ struct ClosureInInit1 { S(#^CLOSURE_IN_INIT_4^# }() } + +public class AvailableTest { + + @available(swift, obsoleted: 4) + init(opt: Int) { } + + @available(swift, introduced: 4) + init?(opt: Int) { } + + init(normal1: Int) { } + init(normal2: Int) { } + + +} +func testAvailable() { + let _ = AvailableTest(#^AVAILABLE_1^# +// AVAILABLE_1: Begin completions, 3 items +// AVAILABLE_1-DAG: Decl[Constructor]/CurrNominal: ['(']{#opt: Int#}[')'][#AvailableTest?#]; name=opt: Int +// AVAILABLE_1-DAG: Decl[Constructor]/CurrNominal: ['(']{#normal1: Int#}[')'][#AvailableTest#]; name=normal1: Int +// AVAILABLE_1-DAG: Decl[Constructor]/CurrNominal: ['(']{#normal2: Int#}[')'][#AvailableTest#]; name=normal2: Int +// AVAILABLE_1: End completions + + let _ = AvailableTest.init(#^AVAILABLE_2^# +// AVAILABLE_2: Begin completions, 3 items +// AVAILABLE_2-DAG: Pattern/CurrModule: ['(']{#opt: Int#}[')'][#AvailableTest?#]; name=opt: Int +// AVAILABLE_2-DAG: Pattern/CurrModule: ['(']{#normal1: Int#}[')'][#AvailableTest#]; name=normal1: Int +// AVAILABLE_2-DAG: Pattern/CurrModule: ['(']{#normal2: Int#}[')'][#AvailableTest#]; name=normal2: Int +// AVAILABLE_2: End completions + +} From c58a15700b33a8f6f4b661954cd8c3345d733963 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 18 Mar 2019 16:58:52 -0700 Subject: [PATCH 05/10] [CodeCompletion] 'case' keyword completion at the top of 'switch' stmt rdar://problem/35943849 (cherry picked from commit fd542e267c6fa9dbd2364dca84e5918c667ffe6b) --- include/swift/IDE/CodeCompletion.h | 1 + include/swift/Parse/CodeCompletionCallbacks.h | 3 +++ lib/IDE/CodeCompletion.cpp | 22 ++++++++++++++++--- lib/Parse/ParseStmt.cpp | 5 +++++ test/IDE/complete_keywords.swift | 16 ++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/include/swift/IDE/CodeCompletion.h b/include/swift/IDE/CodeCompletion.h index bf391272600ed..5cfa91ebb0af5 100644 --- a/include/swift/IDE/CodeCompletion.h +++ b/include/swift/IDE/CodeCompletion.h @@ -492,6 +492,7 @@ enum class CompletionKind { TypeSimpleBeginning, TypeIdentifierWithDot, TypeIdentifierWithoutDot, + CaseStmtKeyword, CaseStmtBeginning, CaseStmtDotPrefix, NominalMemberBeginning, diff --git a/include/swift/Parse/CodeCompletionCallbacks.h b/include/swift/Parse/CodeCompletionCallbacks.h index ebe19822ff1e6..9079a293c4842 100644 --- a/include/swift/Parse/CodeCompletionCallbacks.h +++ b/include/swift/Parse/CodeCompletionCallbacks.h @@ -165,6 +165,9 @@ class CodeCompletionCallbacks { /// Complete a given type-identifier when there is no trailing dot. virtual void completeTypeIdentifierWithoutDot(IdentTypeRepr *ITR) {}; + /// Complete the beginning of a case statement at the top of switch stmt. + virtual void completeCaseStmtKeyword() {}; + /// Complete at the beginning of a case stmt pattern. virtual void completeCaseStmtBeginning() {}; diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 9a0f5d0ec8099..7e997df73467f 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -1348,6 +1348,7 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks { void completeTypeIdentifierWithDot(IdentTypeRepr *ITR) override; void completeTypeIdentifierWithoutDot(IdentTypeRepr *ITR) override; + void completeCaseStmtKeyword() override; void completeCaseStmtBeginning() override; void completeCaseStmtDotPrefix() override; void completeDeclAttrKeyword(Decl *D, bool Sil, bool Param) override; @@ -4442,6 +4443,11 @@ void CodeCompletionCallbacksImpl::completeTypeIdentifierWithoutDot( CurDeclContext = P.CurDeclContext; } +void CodeCompletionCallbacksImpl::completeCaseStmtKeyword() { + Kind = CompletionKind::CaseStmtKeyword; + CurDeclContext = P.CurDeclContext; +} + void CodeCompletionCallbacksImpl::completeCaseStmtBeginning() { assert(!InEnumElementRawValue); @@ -4623,6 +4629,11 @@ static void addStmtKeywords(CodeCompletionResultSink &Sink, bool MaybeFuncBody) #include "swift/Syntax/TokenKinds.def" } +static void addCaseStmtKeywords(CodeCompletionResultSink &Sink) { + addKeyword(Sink, "case", CodeCompletionKeywordKind::kw_case); + addKeyword(Sink, "default", CodeCompletionKeywordKind::kw_default); +} + static void addLetVarKeywords(CodeCompletionResultSink &Sink) { addKeyword(Sink, "let", CodeCompletionKeywordKind::kw_let); addKeyword(Sink, "var", CodeCompletionKeywordKind::kw_var); @@ -4712,6 +4723,10 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink, addAnyTypeKeyword(Sink); break; + case CompletionKind::CaseStmtKeyword: + addCaseStmtKeywords(Sink); + break; + case CompletionKind::PostfixExpr: case CompletionKind::PostfixExprParen: case CompletionKind::SuperExpr: @@ -5214,12 +5229,13 @@ void CodeCompletionCallbacksImpl::doneParsing() { } } break; - case CompletionKind::AfterIfStmtElse: - // Handled earlier by keyword completions. - break; case CompletionKind::PrecedenceGroup: Lookup.getPrecedenceGroupCompletions(SyntxKind); break; + case CompletionKind::AfterIfStmtElse: + case CompletionKind::CaseStmtKeyword: + // Handled earlier by keyword completions. + break; } for (auto &Request: Lookup.RequestedCachedResults) { diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 8f1fabcd1a3e6..d468395f1beb4 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -2273,6 +2273,11 @@ Parser::parseStmtCases(SmallVectorImpl &cases, bool IsActive) { if (auto PDD = PoundDiagnosticResult.getPtrOrNull()) { cases.emplace_back(PDD); } + } else if (Tok.is(tok::code_complete)) { + if (CodeCompletion) + CodeCompletion->completeCaseStmtKeyword(); + consumeToken(tok::code_complete); + return makeParserCodeCompletionStatus(); } else { // If there are non-case-label statements at the start of the switch body, // raise an error and recover by discarding them. diff --git a/test/IDE/complete_keywords.swift b/test/IDE/complete_keywords.swift index df191e55e86f8..8c648c6abec13 100644 --- a/test/IDE/complete_keywords.swift +++ b/test/IDE/complete_keywords.swift @@ -90,6 +90,9 @@ // RUN: %FileCheck %s -check-prefix=KW_EXPR < %t.expr6 // RUN: %FileCheck %s -check-prefix=KW_EXPR_NEG < %t.expr6 +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=SWITCH_TOP | %FileCheck %s -check-prefix=KW_CASE +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=SWITCH_IN_CASE | %FileCheck %s -check-prefix=KW_CASE + // KW_RETURN: Keyword[return]/None: return{{; name=.+$}} // KW_NO_RETURN-NOT: Keyword[return] @@ -401,3 +404,16 @@ func inExpr5() { func inExpr6() -> Int { return #^EXPR_6^# } + +func inSwitch(val: Int) { + switch val { + #^SWITCH_TOP^# + case 1: + foo() + #^SWITCH_IN_CASE^# + } +// Begin completions +// KW_CASE-DAG: Keyword[case]/None: case; name=case +// KW_CASE-DAG: Keyword[default]/None: default; name=default +// End completions +} From 61bf3e29c66ea6a6500d10902459382875167231 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 22 Mar 2019 13:39:49 -0700 Subject: [PATCH 06/10] [CodeCompletion] Look through implicit exprs to find the parsed expr The parsed expression may be wrapped with various implicit expressions. For example, if the expression is only element in array literal, it's wrapped with `(arrayLiteral: )`. (cherry picked from commit bfaa6af57e4ebea8cec399341d96cdfc977aefc0) --- lib/IDE/ExprContextAnalysis.cpp | 4 ++-- test/IDE/complete_value_expr.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/IDE/ExprContextAnalysis.cpp b/lib/IDE/ExprContextAnalysis.cpp index 32a1df2b8cdb0..5f3b2ede153f5 100644 --- a/lib/IDE/ExprContextAnalysis.cpp +++ b/lib/IDE/ExprContextAnalysis.cpp @@ -176,8 +176,8 @@ class ExprFinder : public ASTWalker { Expr *get() const { return FoundExpr; } std::pair walkToExprPre(Expr *E) override { - if (TargetRange == E->getSourceRange() && !isa(E) && - !isa(E) && !isa(E)) { + if (TargetRange == E->getSourceRange() && !E->isImplicit() && + !isa(E)) { assert(!FoundExpr && "non-nullptr for found expr"); FoundExpr = E; return {false, nullptr}; diff --git a/test/IDE/complete_value_expr.swift b/test/IDE/complete_value_expr.swift index 804c965e982ca..39f19545a309f 100644 --- a/test/IDE/complete_value_expr.swift +++ b/test/IDE/complete_value_expr.swift @@ -195,6 +195,11 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_IN_MEMBERDECLINIT_2 | %FileCheck %s -check-prefix=FOO_OBJECT_DOT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CLOSURE_IN_MEMBERDECLINIT_3 | %FileCheck %s -check-prefix=FOO_OBJECT_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_ARRAY_LITERAL_1 | %FileCheck %s -check-prefix=SIMPLE_OBJECT_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_ARRAY_LITERAL_2 | %FileCheck %s -check-prefix=SIMPLE_OBJECT_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_DICTIONARY_LITERAL_1 | %FileCheck %s -check-prefix=SIMPLE_OBJECT_DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_DICTIONARY_LITERAL_2 | %FileCheck %s -check-prefix=SIMPLE_OBJECT_DOT + // Test code completion of expressions that produce a value. struct FooStruct { @@ -2143,3 +2148,25 @@ extension String { obj.#^CLOSURE_IN_MEMBERDECLINIT_3^# } } + +struct SimpleStruct { + func foo() -> SimpleStruct {} +} +// SIMPLE_OBJECT_DOT: Begin completions +// SIMPLE_OBJECT_DOT-DAG: Keyword[self]/CurrNominal: self[#SimpleStruct#]; name=self +// SIMPLE_OBJECT_DOT-DAG: Decl[InstanceMethod]/CurrNominal{{(/TypeRelation\[Identical\])?}}: foo()[#SimpleStruct#]; name=foo() +// SIMPLE_OBJECT_DOT: End completions +func testInCollectionLiteral(value: SimpleStruct) { + let _ = [ + value.#^IN_ARRAY_LITERAL_1^# + ] + let _ = [ + value.#^IN_ARRAY_LITERAL_2^#, + ] + let _: [String: String] = [ + value.#^IN_DICTIONARY_LITERAL_1^# + ] + let _: [String: String] = [ + value.#^IN_DICTIONARY_LITERAL_2^# : "test" + ] +} From 13806506a0afe736469a23346b45a9f3162dff2f Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 22 Mar 2019 10:54:20 -0700 Subject: [PATCH 07/10] [CodeCompletion] Provide empty call pattern after paren This improves discoverability of no-parameter initializers. rdar://problem/46215262 (cherry picked from commit 7ae18ff0f127fd2c1ca9d2847d267d19e9599957) --- lib/IDE/CodeCompletion.cpp | 22 ++++------------------ test/IDE/complete_after_super.swift | 4 +++- test/IDE/complete_call_arg.swift | 5 +++-- test/IDE/complete_constructor.swift | 11 +++++++++-- test/IDE/complete_value_expr.swift | 11 ++++++----- 5 files changed, 25 insertions(+), 28 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 7e997df73467f..704cc15269c49 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -1185,8 +1185,6 @@ void CodeCompletionString::getName(raw_ostream &OS) const { } } } - assert((TextSize > 0) && - "code completion string should have non-empty name!"); } void CodeCompletionContext::sortCompletionResults( @@ -2249,14 +2247,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { else Builder.addAnnotatedLeftParen(); - bool anyParam = addCallArgumentPatterns(Builder, AFT->getParams(), - declParams, includeDefaultArgs); - - if (HaveLParen && !anyParam) { - // Empty result, don't add it. - Builder.cancel(); - return; - } + addCallArgumentPatterns(Builder, AFT->getParams(), declParams, + includeDefaultArgs); // The rparen matches the lparen here so that we insert both or neither. if (!HaveLParen) @@ -2502,14 +2494,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { else Builder.addAnnotatedLeftParen(); - bool anyParam = addCallArgumentPatterns( - Builder, ConstructorType, CD->getParameters(), includeDefaultArgs); - - if (HaveLParen && !anyParam) { - // Empty result, don't add it. - Builder.cancel(); - return; - } + addCallArgumentPatterns(Builder, ConstructorType, CD->getParameters(), + includeDefaultArgs); // The rparen matches the lparen here so that we insert both or neither. if (!HaveLParen) diff --git a/test/IDE/complete_after_super.swift b/test/IDE/complete_after_super.swift index ffedc24cfce09..1089743f4d30e 100644 --- a/test/IDE/complete_after_super.swift +++ b/test/IDE/complete_after_super.swift @@ -262,7 +262,9 @@ class SuperDerivedA : SuperBaseA { } init (b: Float) { super.init(#^CONSTRUCTOR_SUPER_INIT_PAREN_1^# -// CONSTRUCTOR_SUPER_INIT_PAREN_1-NOT: Pattern/ +// CONSTRUCTOR_SUPER_INIT_PAREN_1: Begin completions, 1 items +// CONSTRUCTOR_SUPER_INIT_PAREN_1: Pattern/CurrModule: ['('][')'][#SuperBaseA#]; name= +// CONSTRUCTOR_SUPER_INIT_PAREN_1: End completions } deinit { diff --git a/test/IDE/complete_call_arg.swift b/test/IDE/complete_call_arg.swift index 37f62ea6ac5e4..2583d18a1d17d 100644 --- a/test/IDE/complete_call_arg.swift +++ b/test/IDE/complete_call_arg.swift @@ -483,8 +483,9 @@ _ = EmptyOverload(foo: #^EMPTY_OVERLOAD_2^#) public func fopen() -> TestBoundGeneric1! { fatalError() } func other() { _ = fopen(#^CALLARG_IUO^#) -// CALLARG_IUO-NOT: Begin completions -// CALLARG_IUO-NOT: End completions +// CALLARG_IUO: Begin completions, 1 items +// CALLARG_IUO: Pattern/CurrModule: ['('][')'][#TestBoundGeneric1!#]; name= +// CALLARG_IUO: End completions } class Foo { let x: Int } diff --git a/test/IDE/complete_constructor.swift b/test/IDE/complete_constructor.swift index 06666aaaa109e..f9788ade5f87c 100644 --- a/test/IDE/complete_constructor.swift +++ b/test/IDE/complete_constructor.swift @@ -69,7 +69,9 @@ func testImplicitConstructors1() { } func testImplicitConstructors1P() { ImplicitConstructors1(#^IMPLICIT_CONSTRUCTORS_1P^# -// IMPLICIT_CONSTRUCTORS_1P-NOT: Begin completions +// IMPLICIT_CONSTRUCTORS_1P: Begin completions, 1 items +// IMPLICIT_CONSTRUCTORS_1P: Decl[Constructor]/CurrNominal: ['('][')'][#ImplicitConstructors1#]; name= +// IMPLICIT_CONSTRUCTORS_1P: End completions } struct ImplicitConstructors2 { @@ -89,6 +91,7 @@ func testImplicitConstructors2P() { ImplicitConstructors2(#^IMPLICIT_CONSTRUCTORS_2P^# // IMPLICIT_CONSTRUCTORS_2P: Begin completions // IMPLICIT_CONSTRUCTORS_2P-NEXT: Decl[Constructor]/CurrNominal: ['(']{#instanceVar: Int#}[')'][#ImplicitConstructors2#]{{; name=.+$}} +// IMPLICIT_CONSTRUCTORS_2P-NEXT: Decl[Constructor]/CurrNominal: ['('][')'][#ImplicitConstructors2#]; name= // IMPLICIT_CONSTRUCTORS_2P-NEXT: End completions } @@ -111,6 +114,7 @@ func testExplicitConstructors1() { func testExplicitConstructors1P() { ExplicitConstructors1(#^EXPLICIT_CONSTRUCTORS_1P^# // EXPLICIT_CONSTRUCTORS_1P: Begin completions +// EXPLICIT_CONSTRUCTORS_1P-NEXT: Decl[Constructor]/CurrNominal: ['('][')'][#ExplicitConstructors1#]; name= // EXPLICIT_CONSTRUCTORS_1P-NEXT: Decl[Constructor]/CurrNominal: ['(']{#a: Int#}[')'][#ExplicitConstructors1#]{{; name=.+$}} // EXPLICIT_CONSTRUCTORS_1P-NEXT: Decl[Constructor]/CurrNominal: ['(']{#a: Int#}, {#b: Float#}[')'][#ExplicitConstructors1#]{{; name=.+$}} // EXPLICIT_CONSTRUCTORS_1P-NEXT: End completions @@ -276,13 +280,16 @@ struct ExplicitConstructorsDerived3 { func testHaveRParen1() { ImplicitConstructors1(#^HAVE_RPAREN_1^#) -// HAVE_RPAREN_1-NOT: Decl[Constructor] +// HAVE_RPAREN_1: Begin completions, 1 items +// HAVE_RPAREN_1: Decl[Constructor]/CurrNominal: ['('][')'][#ImplicitConstructors1#]; name= +// HAVE_RPAREN_1: End completions } func testHaveRParen2() { ImplicitConstructors2(#^HAVE_RPAREN_2^#) // HAVE_RPAREN_2-NOT: Decl[Constructor] // HAVE_RPAREN_2: Decl[Constructor]/CurrNominal: ['(']{#instanceVar: Int#}[')'][#ImplicitConstructors2#]{{; name=.+$}} +// HAVE_RPAREN_2: Decl[Constructor]/CurrNominal: ['('][')'][#ImplicitConstructors2#]; name= // HAVE_RPAREN_2-NOT: Decl[Constructor] } diff --git a/test/IDE/complete_value_expr.swift b/test/IDE/complete_value_expr.swift index 39f19545a309f..70756212ba6de 100644 --- a/test/IDE/complete_value_expr.swift +++ b/test/IDE/complete_value_expr.swift @@ -734,9 +734,9 @@ func testInsideFunctionCall0() { func testInsideFunctionCall1() { var a = FooStruct() a.instanceFunc0(#^INSIDE_FUNCTION_CALL_1^# -// There should be no results here because the function call -// unambiguously resolves to overload that takes 0 arguments. -// INSIDE_FUNCTION_CALL_1-NOT: Begin completions +// INSIDE_FUNCTION_CALL_1: Begin completions, 1 items +// INSIDE_FUNCTION_CALL_1: Pattern/CurrModule: ['('][')'][#Void#]; name= +// INSIDE_FUNCTION_CALL_1: End completions } func testInsideFunctionCall2() { @@ -787,8 +787,9 @@ func testInsideFunctionCall7() { func testInsideFunctionCall8(_ x: inout FooStruct) { x.instanceFunc0(#^INSIDE_FUNCTION_CALL_8^#) -// Since we already have '()', there is no pattern to complete. -// INSIDE_FUNCTION_CALL_8-NOT: Pattern/{{.*}}: +// INSIDE_FUNCTION_CALL_8: Begin completions +// INSIDE_FUNCTION_CALL_8: Pattern/CurrModule: ['('][')'][#Void#]; name= +// INSIDE_FUNCTION_CALL_8: End completions } func testInsideFunctionCall9(_ x: inout FooStruct) { x.instanceFunc1(#^INSIDE_FUNCTION_CALL_9^#) From 612297fde6fd5af7ba9238ba49011984d7cbf1e0 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 22 Mar 2019 14:50:58 -0700 Subject: [PATCH 08/10] [CodeCompletion] Don't provide 'init(nilLiteral:)' et al in optional context For unresolved member completion, member decls in `Optional` referenceable by implicit member expression are useless in most cases. - `init(_: <#Wrapped#>)` - `init(nilLiteral: ())` - `.some(<#Wrapped#>)` - `.none` Instead, provide `nil` with erasing `.` instruction. rdar://problem/47806831 (cherry picked from commit 7e3a5dc50be0d4ba565dac397f584d0d6ef133a7) --- lib/IDE/CodeCompletion.cpp | 21 ++++++++++- test/IDE/complete_unresolved_members.swift | 41 +++++++++++++++++----- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 704cc15269c49..174bdc8693e05 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -2717,7 +2717,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { void addKeyword(StringRef Name, Type TypeAnnotation = Type(), SemanticContextKind SK = SemanticContextKind::None, CodeCompletionKeywordKind KeyKind - = CodeCompletionKeywordKind::None) { + = CodeCompletionKeywordKind::None, + unsigned NumBytesToErase = 0) { CodeCompletionResultBuilder Builder( Sink, CodeCompletionResult::ResultKind::Keyword, SK, ExpectedTypes); @@ -2726,6 +2727,8 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { Builder.setKeywordKind(KeyKind); if (TypeAnnotation) addTypeAnnotation(Builder, TypeAnnotation); + if (NumBytesToErase > 0) + Builder.setNumBytesToErase(NumBytesToErase); } void addKeyword(StringRef Name, StringRef TypeAnnotation, @@ -3612,6 +3615,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { return false; } + // In optional context, ignore '.init()', 'init(nilLiteral:)', + // '.some()' and '.none'. They are useless in most cases. + if (T->getOptionalObjectType() && + VD->getModuleContext()->isStdlibModule() && + (isa(VD) || isa(VD))) { + return false; + } + // Enum element decls can always be referenced by implicit member // expression. if (isa(VD)) @@ -3682,6 +3693,14 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { // If this is optional type, perform completion for the object type. // i.e. 'let _: Enum??? = .enumMember' is legal. getUnresolvedMemberCompletions(objT->lookThroughAllOptionalTypes()); + + // Add 'nil' keyword with erasing '.' instruction. + unsigned bytesToErase = 0; + auto &SM = CurrDeclContext->getASTContext().SourceMgr; + if (DotLoc.isValid()) + bytesToErase = SM.getByteDistance(DotLoc, SM.getCodeCompletionLoc()); + addKeyword("nil", T, SemanticContextKind::ExpressionSpecific, + CodeCompletionKeywordKind::kw_nil, bytesToErase); } getUnresolvedMemberCompletions(T); } diff --git a/test/IDE/complete_unresolved_members.swift b/test/IDE/complete_unresolved_members.swift index dd7afdd82ed02..348fbf009ca8b 100644 --- a/test/IDE/complete_unresolved_members.swift +++ b/test/IDE/complete_unresolved_members.swift @@ -14,6 +14,7 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_OPT_1 | %FileCheck %s -check-prefix=UNRESOLVED_3_OPT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_OPT_2 | %FileCheck %s -check-prefix=UNRESOLVED_3_OPT // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_OPT_3 | %FileCheck %s -check-prefix=UNRESOLVED_3_OPTOPTOPT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_OPT_4 | %FileCheck %s -check-prefix=UNRESOLVED_OPT_4 // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_12 | %FileCheck %s -check-prefix=UNRESOLVED_3 // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_13 | %FileCheck %s -check-prefix=UNRESOLVED_3 @@ -251,18 +252,42 @@ class C4 { // UNRESOLVED_3_OPT: Begin completions // UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: North[#SomeEnum1#]; // UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: South[#SomeEnum1#]; -// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: none[#Optional#]; name=none -// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: some({#SomeEnum1#})[#Optional#]; -// UNRESOLVED_3_OPT-DAG: Decl[Constructor]/CurrNominal: init({#(some): SomeEnum1#})[#Optional#]; -// UNRESOLVED_3_OPT-DAG: Decl[Constructor]/CurrNominal: init({#nilLiteral: ()#})[#Optional#]; +// UNRESOLVED_3_OPT-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#SomeEnum1?#]; name=nil +// UNRESOLVED_3_OPT-NOT: none +// UNRESOLVED_3_OPT-NOT: some +// UNRESOLVED_3_OPT-NOT: init({#(some): +// UNRESOLVED_3_OPT-NOT: init({#nilLiteral: // UNRESOLVED_3_OPTOPTOPT: Begin completions // UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: North[#SomeEnum1#]; // UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: South[#SomeEnum1#]; -// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: none[#Optional#]; name=none -// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: some({#SomeEnum1??#})[#Optional#]; -// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[Constructor]/CurrNominal: init({#(some): SomeEnum1??#})[#Optional#]; -// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[Constructor]/CurrNominal: init({#nilLiteral: ()#})[#Optional#]; +// UNRESOLVED_3_OPTOPTOPT-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#SomeEnum1???#]; name=nil +// UNRESOLVED_3_OPTOPTOPT-NOT: none +// UNRESOLVED_3_OPTOPTOPT-NOT: some +// UNRESOLVED_3_OPTOPTOPT-NOT: init({#(some): +// UNRESOLVED_3_OPTOPTOPT-NOT: init({#nilLiteral: + +enum Somewhere { + case earth, mars +} +extension Optional where Wrapped == Somewhere { + init(str: String) { fatalError() } + static var nowhere: Self { return nil } +} +func testOptionalWithCustomExtension() { + var _: Somewhere? = .#^UNRESOLVED_OPT_4^# +// UNRESOLVED_OPT_4: Begin completions +// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: earth[#Somewhere#]; +// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: mars[#Somewhere#]; +// UNRESOLVED_OPT_4-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#Somewhere?#]; name=nil +// UNRESOLVED_OPT_4-DAG: Decl[Constructor]/CurrNominal: init({#str: String#})[#Optional#]; name=init(str: String) +// UNRESOLVED_OPT_4-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Identical]: nowhere[#Optional#]; name=nowhere +// UNRESOLVED_OPT_4-NOT: none +// UNRESOLVED_OPT_4-NOT: some +// UNRESOLVED_OPT_4-NOT: init({#(some): +// UNRESOLVED_OPT_4-NOT: init({#nilLiteral: +} + class C5 { func f1() { From dca057d5d9efc6cad7891cdc1f15f53d2992429d Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 25 Mar 2019 15:57:42 -0700 Subject: [PATCH 09/10] [CodeCompletion] Don't ignore .some(Wrapped) and .none for now with "TODO: ignore them in expression context". They are useful in pattern context, so we should provide them in completion. (cherry picked from commit b1efc21e084e459b998f3293f0d7d7d4bed421b5) --- lib/IDE/CodeCompletion.cpp | 11 ++++++----- test/IDE/complete_unresolved_members.swift | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 174bdc8693e05..44f47562d0c4e 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -3615,12 +3615,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { return false; } - // In optional context, ignore '.init()', 'init(nilLiteral:)', - // '.some()' and '.none'. They are useless in most cases. if (T->getOptionalObjectType() && - VD->getModuleContext()->isStdlibModule() && - (isa(VD) || isa(VD))) { - return false; + VD->getModuleContext()->isStdlibModule()) { + // In optional context, ignore '.init()', 'init(nilLiteral:)', + if (isa(VD)) + return false; + // TODO: Ignore '.some()' and '.none' too *in expression + // context*. They are useful in pattern context though. } // Enum element decls can always be referenced by implicit member diff --git a/test/IDE/complete_unresolved_members.swift b/test/IDE/complete_unresolved_members.swift index 348fbf009ca8b..0695069fbfab4 100644 --- a/test/IDE/complete_unresolved_members.swift +++ b/test/IDE/complete_unresolved_members.swift @@ -241,7 +241,7 @@ class C4 { var _: SomeEnum1??? = .#^UNRESOLVED_OPT_3^# } } -// UNRESOLVED_3: Begin completions +// UNRESOLVED_3: Begin completions, 2 items // UNRESOLVED_3-DAG: Decl[EnumElement]/ExprSpecific: North[#SomeEnum1#]; name=North // UNRESOLVED_3-DAG: Decl[EnumElement]/ExprSpecific: South[#SomeEnum1#]; name=South // UNRESOLVED_3-NOT: SomeOptions1 @@ -249,21 +249,21 @@ class C4 { // UNRESOLVED_3-NOT: none // UNRESOLVED_3-NOT: some( -// UNRESOLVED_3_OPT: Begin completions +// UNRESOLVED_3_OPT: Begin completions, 5 items // UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: North[#SomeEnum1#]; // UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: South[#SomeEnum1#]; // UNRESOLVED_3_OPT-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#SomeEnum1?#]; name=nil -// UNRESOLVED_3_OPT-NOT: none -// UNRESOLVED_3_OPT-NOT: some +// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: none[#Optional#]; name=none +// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/ExprSpecific: some({#SomeEnum1#})[#Optional#]; // UNRESOLVED_3_OPT-NOT: init({#(some): // UNRESOLVED_3_OPT-NOT: init({#nilLiteral: -// UNRESOLVED_3_OPTOPTOPT: Begin completions +// UNRESOLVED_3_OPTOPTOPT: Begin completions, 5 items // UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: North[#SomeEnum1#]; // UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: South[#SomeEnum1#]; // UNRESOLVED_3_OPTOPTOPT-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#SomeEnum1???#]; name=nil -// UNRESOLVED_3_OPTOPTOPT-NOT: none -// UNRESOLVED_3_OPTOPTOPT-NOT: some +// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: none[#Optional#]; name=none +// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/ExprSpecific: some({#SomeEnum1??#})[#Optional#]; // UNRESOLVED_3_OPTOPTOPT-NOT: init({#(some): // UNRESOLVED_3_OPTOPTOPT-NOT: init({#nilLiteral: @@ -276,16 +276,17 @@ extension Optional where Wrapped == Somewhere { } func testOptionalWithCustomExtension() { var _: Somewhere? = .#^UNRESOLVED_OPT_4^# -// UNRESOLVED_OPT_4: Begin completions +// UNRESOLVED_OPT_4: Begin completions, 7 items // UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: earth[#Somewhere#]; // UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: mars[#Somewhere#]; // UNRESOLVED_OPT_4-DAG: Keyword[nil]/ExprSpecific/Erase[1]: nil[#Somewhere?#]; name=nil +// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: none[#Optional#]; name=none +// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/ExprSpecific: some({#Somewhere#})[#Optional#]; // UNRESOLVED_OPT_4-DAG: Decl[Constructor]/CurrNominal: init({#str: String#})[#Optional#]; name=init(str: String) // UNRESOLVED_OPT_4-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Identical]: nowhere[#Optional#]; name=nowhere -// UNRESOLVED_OPT_4-NOT: none -// UNRESOLVED_OPT_4-NOT: some // UNRESOLVED_OPT_4-NOT: init({#(some): // UNRESOLVED_OPT_4-NOT: init({#nilLiteral: +// UNRESOLVED_OPT_4: End completions } From 4d888e55d1ce1c66c9cb4566a938831fe5ded899 Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Mon, 25 Mar 2019 09:54:31 -0700 Subject: [PATCH 10/10] [CodeCompletion] Infer keypath root type from the context type So that this works: ``` func foo(_: KeyPath) {} foo(\.) ``` rdar://problem/46102807 (cherry picked from commit 65d8d29d2a1b4778097101c3aed1b1df963d3441) --- lib/IDE/CodeCompletion.cpp | 27 ++++++++++++++++++-------- test/IDE/complete_swift_key_path.swift | 26 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 44f47562d0c4e..b61b5d967bc33 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -5000,19 +5000,30 @@ void CodeCompletionCallbacksImpl::doneParsing() { bool OnRoot = !KPE->getComponents().front().isValid(); Lookup.setIsSwiftKeyPathExpr(OnRoot); - auto ParsedType = BGT->getGenericArgs()[1]; - auto Components = KPE->getComponents(); - if (Components.back().getKind() == - KeyPathExpr::Component::Kind::OptionalWrap) { + Type baseType = BGT->getGenericArgs()[OnRoot ? 0 : 1]; + if (OnRoot && baseType->is()) { + // Infer the root type of the keypath from the context type. + ExprContextInfo ContextInfo(CurDeclContext, ParsedExpr); + for (auto T : ContextInfo.getPossibleTypes()) { + if (auto unwrapped = T->getOptionalObjectType()) + T = unwrapped; + if (!T->getAnyNominal() || !T->getAnyNominal()->getKeyPathTypeKind() || + T->hasUnresolvedType() || !T->is()) + continue; + // Use the first KeyPath context type found. + baseType = T->castTo()->getGenericArgs()[0]; + break; + } + } + if (!OnRoot && KPE->getComponents().back().getKind() == + KeyPathExpr::Component::Kind::OptionalWrap) { // KeyPath expr with '?' (e.g. '\Ty.[0].prop?.another'). // Althogh expected type is optional, we should unwrap it because it's // unwrapped. - ParsedType = ParsedType->getOptionalObjectType(); + baseType = baseType->getOptionalObjectType(); } - // The second generic type argument of KeyPath should be - // the value we pull code completion results from. - Lookup.getValueExprCompletions(ParsedType); + Lookup.getValueExprCompletions(baseType); break; } diff --git a/test/IDE/complete_swift_key_path.swift b/test/IDE/complete_swift_key_path.swift index afe1963558aa4..f381cc4ec64e9 100644 --- a/test/IDE/complete_swift_key_path.swift +++ b/test/IDE/complete_swift_key_path.swift @@ -21,7 +21,9 @@ // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=EMPTY_2 | %FileCheck %s -check-prefix=INVALID // RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_BASEONLY | %FileCheck %s -check-prefix=PERSONTYPE-DOT -// FIXME: RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_EXPLICIT | %FileCheck %s -check-prefix=INVALID +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_EXPLICIT | %FileCheck %s -check-prefix=PERSONTYPE-DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_GENERIC_RESULT | %FileCheck %s -check-prefix=PERSONTYPE-DOT +// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=CONTEXT_GENERIC_RESULT_OPTIONAL | %FileCheck %s -check-prefix=PERSONTYPE-DOT class Person { var name: String @@ -131,9 +133,19 @@ let _ = \.#^EMPTY_1^# let _ = \.friends.#^EMPTY_2^# // INVALID-NOT: Begin completions -let _: PartialKeyPath = \.#^CONTEXT_BASEONLY^# -// Same as TYPE_DOT. - -let _: KeyPath = \.#^CONTEXT_EXPLICIT^# -// FIXME: Currently, it's suggest nothing. Since we know the base type is -// 'Person', we should suggest at least '.name'. +func recvPartialKP(_ kp: PartialKeyPath) { + recvPartialKP(\.#^CONTEXT_BASEONLY^#) + // Same as TYPE_DOT. +} +func recvExplicitKP(_ kp: KeyPath) { + recvExplicitKP(\.#^CONTEXT_EXPLICIT^#) + // Same as TYPE_DOT. +} +func recvExplicitKPWithGenericResult(_ kp: KeyPath) { + recvExplicitKPWithGenericResult(\.#^CONTEXT_GENERIC_RESULT^#) + // Same as TYPE_DOT. +} +func recvExplicitKPWithGenericResultOpt(_ kp: KeyPath?) { + recvExplicitKPWithGenericResult(\.#^CONTEXT_GENERIC_RESULT_OPTIONAL^# + // Same as TYPE_DOT. +}