Skip to content

[CodeCompletion] Dedupe completion results if multiple lookups were made #34448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions lib/IDE/CodeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
bool PreferFunctionReferencesToCalls = false;
bool HaveLeadingSpace = false;

bool CheckForDuplicates = false;
llvm::DenseSet<std::pair<const Decl *, Type>> PreviouslySeen;

bool IncludeInstanceMembers = false;

/// True if we are code completing inside a static method.
Expand Down Expand Up @@ -1998,6 +2001,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
IsKeyPathExpr = true;
}

void shouldCheckForDuplicates(bool value = true) {
CheckForDuplicates = value;
}

void setIsSwiftKeyPathExpr(bool onRoot) {
IsSwiftKeyPathExpr = true;
IsAfterSwiftKeyPathRoot = onRoot;
Expand Down Expand Up @@ -2895,8 +2902,13 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
IsImplicitlyCurriedInstanceMethod = isImplicitlyCurriedInstanceMethod(FD);

// Strip off '(_ self: Self)' if needed.
if (AFT && !IsImplicitlyCurriedInstanceMethod)
if (AFT && !IsImplicitlyCurriedInstanceMethod) {
AFT = AFT->getResult()->getAs<AnyFunctionType>();

// Check for duplicates with the adjusted type too.
if (isDuplicate(FD, AFT))
return;
}
}

bool trivialTrailingClosure = false;
Expand Down Expand Up @@ -3369,10 +3381,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
DynamicLookupInfo dynamicLookupInfo) {
auto funcTy =
getTypeOfMember(AFD, dynamicLookupInfo)->getAs<AnyFunctionType>();
if (funcTy && AFD->getDeclContext()->isTypeContext() &&
!isImplicitlyCurriedInstanceMethod(AFD)) {
bool dropCurryLevel = funcTy && AFD->getDeclContext()->isTypeContext() &&
!isImplicitlyCurriedInstanceMethod(AFD);
if (dropCurryLevel)
funcTy = funcTy->getResult()->getAs<AnyFunctionType>();
}

bool useFunctionReference = PreferFunctionReferencesToCalls;
if (!useFunctionReference && funcTy) {
Expand All @@ -3384,6 +3396,10 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
if (!useFunctionReference)
return false;

// Check for duplicates with the adjusted type too.
if (dropCurryLevel && isDuplicate(AFD, funcTy))
return true;

CommandWordsPairs Pairs;
CodeCompletionResultBuilder Builder(
Sink, CodeCompletionResult::ResultKind::Declaration,
Expand Down Expand Up @@ -3421,6 +3437,29 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
return true;
}

private:

/// Returns true if duplicate checking is enabled (via
/// \c shouldCheckForDuplicates) and this decl + type combination has been
/// checked previously. Returns false otherwise.
bool isDuplicate(const ValueDecl *D, Type Ty) {
if (!CheckForDuplicates)
return false;
return !PreviouslySeen.insert({D, Ty}).second;
}

/// Returns true if duplicate checking is enabled (via
/// \c shouldCheckForDuplicates) and this decl has been checked previously
/// with the type according to \c getTypeOfMember. Returns false otherwise.
bool isDuplicate(const ValueDecl *D, DynamicLookupInfo dynamicLookupInfo) {
if (!CheckForDuplicates)
return false;
Type Ty = getTypeOfMember(D, dynamicLookupInfo);
return !PreviouslySeen.insert({D, Ty}).second;
}

public:

// Implement swift::VisibleDeclConsumer.
void foundDecl(ValueDecl *D, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo) override {
Expand All @@ -3436,6 +3475,11 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {

if (IsSwiftKeyPathExpr && !SwiftKeyPathFilter(D, Reason))
return;

// If we've seen this decl+type before (possible when multiple lookups are
// performed e.g. because of ambiguous base types), bail.
if (isDuplicate(D, dynamicLookupInfo))
return;

// FIXME(InterfaceTypeRequest): Remove this.
(void)D->getInterfaceType();
Expand Down Expand Up @@ -3526,6 +3570,11 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
Type funcType = getTypeOfMember(FD, dynamicLookupInfo)
->castTo<AnyFunctionType>()
->getResult();

// Check for duplicates with the adjusted type too.
if (isDuplicate(FD, funcType))
return;

addFunctionCallPattern(
funcType->castTo<AnyFunctionType>(), FD,
getSemanticContext(FD, Reason, dynamicLookupInfo));
Expand Down Expand Up @@ -6039,6 +6088,7 @@ void deliverDotExprResults(
Lookup.setPreferFunctionReferencesToCalls();
}

Lookup.shouldCheckForDuplicates(Results.size() > 1);
for (auto &Result: Results) {
Lookup.setIsStaticMetatype(Result.BaseIsStaticMetaType);
Lookup.getPostfixKeywordCompletions(Result.BaseTy, BaseExpr);
Expand Down
36 changes: 36 additions & 0 deletions test/IDE/complete_ambiguous.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SIMPLE | %FileCheck %s --check-prefix=SIMPLE
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SIMPLE_EXTRAARG | %FileCheck %s --check-prefix=SIMPLE
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SIMPLE_MEMBERS | %FileCheck %s --check-prefix=SIMPLE
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SKIP_DUPLICATES > %t
// RUN: %FileCheck %s --input-file %t --check-prefixes=SKIP_DUPLICATES,SKIP_DUPLICATES_PROPERTY
// RUN: %FileCheck %s --input-file %t --check-prefixes=SKIP_DUPLICATES,SKIP_DUPLICATES_METHOD
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SKIP_COMPOUND_DUPLICATES | %FileCheck %s --check-prefix=SKIP_COMPOUND_DUPLICATES
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=SKIP_CALLASFUNCTION_DUPLICATES | %FileCheck %s --check-prefix=SKIP_CALLASFUNCTION_DUPLICATES
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED | %FileCheck %s --check-prefix=RELATED
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED_EXTRAARG | %FileCheck %s --check-prefix=RELATED
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=RELATED_INERROREXPR | %FileCheck %s --check-prefix=RELATED
Expand Down Expand Up @@ -50,6 +55,37 @@ HasMembers().overloadedReturn().#^SIMPLE_MEMBERS^#
func givenErrorExpr(_ a: String) -> A {}
func givenErrorExpr(_ b: Int) -> B {}

func arrayWrapper<T>(a: T) -> [T]
arrayWrapper(overloadedReturn()).#^SKIP_DUPLICATES^#

// SKIP_DUPLICATES: Begin completions
// SKIP_DUPLICATES_PROPERTY: Decl[InstanceVar]/CurrNominal/IsSystem: count[#Int#]{{; name=.+$}}
// SKIP_DUPLICATES_PROPERTY-NOT: count[#Int#]
// SKIP_DUPLICATES_METHOD: Decl[InstanceMethod]/Super/IsSystem: formIndex({#(i): &Int#}, {#offsetBy: Int#})[#Void#]{{; name=.+$}}
// SKIP_DUPLICATES_METHOD-NOT: formIndex({#(i): &Int#}, {#offsetBy: Int#})[#Void#]
// SKIP_DUPLICATES: End completions

let x: (inout Int, Int) -> () = arrayWrapper(overloadedReturn()).#^SKIP_COMPOUND_DUPLICATES^#

// SKIP_COMPOUND_DUPLICATES: Begin completions
// SKIP_COMPOUND_DUPLICATES: Decl[InstanceMethod]/Super/IsSystem/TypeRelation[Identical]: formIndex(_:offsetBy:)[#(inout Int, Int) -> ()#]{{; name=.+$}}
// SKIP_COMPOUND_DUPLICATES-NOT: formIndex(_:offsetBy:)[#(inout Int, Int) -> ()#]
// SKIP_COMPOUND_DUPLICATES: End completions

func testCallAsFunctionDeduplication() {
struct Test<T> {
func callAsFunction(x: Int) {}
}

func overloaded() -> Test<A> { fatalError() }
func overloaded() -> Test<B> { fatalError() }

overloaded()#^SKIP_CALLASFUNCTION_DUPLICATES^#
}

// FIXME: Update this to check the callAsFunction pattern only appears once when PostfixExpr completion is migrated to the solver-based implementation (which handles ambiguity).
// SKIP_CALLASFUNCTION_DUPLICATES-NOT: Begin completions

givenErrorExpr(undefined).#^ERROR_IN_BASE^#

// SIMPLE: Begin completions, 4 items
Expand Down