Skip to content

[CodeComplete] Show completions from constrained protocol extension #37065

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
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
77 changes: 76 additions & 1 deletion lib/Sema/IDETypeCheckingRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,87 @@ void swift::registerIDETypeCheckRequestFunctions(Evaluator &evaluator) {
ideTypeCheckRequestFunctions);
}

/// Consider the following example
///
/// \code
/// protocol FontStyle {}
/// struct FontStyleOne: FontStyle {}
/// extension FontStyle where Self == FontStyleOne {
/// static var one: FontStyleOne { FontStyleOne() }
/// }
/// func foo<T: FontStyle>(x: T) {}
///
/// func case1() {
/// foo(x: .#^COMPLETE^#) // extension should be considered applied here
/// }
/// func case2<T: FontStyle>(x: T) {
/// x.#^COMPLETE_2^# // extension should not be considered applied here
/// }
/// \endcode
/// We want to consider the extension applied in the first case but not the
/// second case. In the first case the constraint `T: FontStyle` from the
/// definition of `foo` should be considered an 'at-least' constraint and any
/// additional constraints on `T` (like `T == FonstStyleOne`) can be
/// fulfilled by picking a more specialized version of `T`.
/// However, in the second case, `T: FontStyle` should be considered an
/// 'at-most' constraint and we can't make the assumption that `x` has a more
/// specialized type.
///
/// After type-checking we cannot easily differentiate the two cases. In both
/// we have a unresolved dot completion on a primary archetype that
/// conforms to `FontStyle`.
///
/// To tell them apart, we apply the following heuristic: If the primary
/// archetype refers to a generic parameter that is not visible in the current
/// decl context (i.e. the current decl context is not a child context of the
/// parameter's decl context), it is not the type of a variable visible
/// in the current decl context. Hence, we must be in the first case and
/// consider all extensions applied, otherwise we should only consider those
/// extensions applied whose requirements are fulfilled.
class ContainsSpecializableArchetype : public TypeWalker {
const DeclContext *DC;
bool Result = false;
ContainsSpecializableArchetype(const DeclContext *DC) : DC(DC) {}

Action walkToTypePre(Type T) override {
if (auto *Archetype = T->getAs<ArchetypeType>()) {
if (auto *GenericTypeParam =
Archetype->mapTypeOutOfContext()->getAs<GenericTypeParamType>()) {
if (auto GenericTypeParamDecl = GenericTypeParam->getDecl()) {
bool ParamMaybeVisibleInCurrentContext =
(DC == GenericTypeParamDecl->getDeclContext() ||
DC->isChildContextOf(GenericTypeParamDecl->getDeclContext()));
if (!ParamMaybeVisibleInCurrentContext) {
Result = true;
return Action::Stop;
}
}
}
}
return Action::Continue;
}

public:
static bool check(const DeclContext *DC, Type T) {
if (!T->hasArchetype()) {
// Fast path, we don't have an archetype to check.
return false;
}
ContainsSpecializableArchetype Checker(DC);
T.walk(Checker);
return Checker.Result;
}
};

static bool isExtensionAppliedInternal(const DeclContext *DC, Type BaseTy,
const ExtensionDecl *ED) {
// We can't do anything if the base type has unbound generic parameters.
// We can't leak type variables into another constraint system.
// For check on specializable archetype see comment on
// ContainsSpecializableArchetype.
if (BaseTy->hasTypeVariable() || BaseTy->hasUnboundGenericType() ||
BaseTy->hasUnresolvedType() || BaseTy->hasError())
BaseTy->hasUnresolvedType() || BaseTy->hasError() ||
ContainsSpecializableArchetype::check(DC, BaseTy))
return true;

if (!ED->isConstrainedExtension())
Expand Down
72 changes: 72 additions & 0 deletions test/IDE/complete_protocol_static_member.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t

protocol FontStyle {}
struct FontStyleOne: FontStyle {}
struct FontStyleTwo: FontStyle {}

extension FontStyle where Self == FontStyleOne {
static var variableDeclaredInConstraintExtension: FontStyleOne { FontStyleOne() }
}

func foo<T: FontStyle>(x: T) {}
func test() {
foo(x: .#^COMPLETE_STATIC_MEMBER?check=EXTENSION_APPLIED^#)
}

// EXTENSION_APPLIED: Begin completions, 1 item
// EXTENSION_APPLIED-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: variableDeclaredInConstraintExtension[#FontStyleOne#];
// EXTENSION_APPLIED: End completions

func test<T: FontStyle>(x: T) {
x.#^COMPLETE_MEMBER_IN_GENERIC_CONTEXT?check=EXTENSION_NOT_APPLIED^#
}

// EXTENSION_NOT_APPLIED: Begin completions, 1 item
// EXTENSION_NOT_APPLIED-DAG: Keyword[self]/CurrNominal: self[#T#];
// EXTENSION_NOT_APPLIED-NOT: variableDeclaredInConstraintExtension
// EXTENSION_NOT_APPLIED: End completions

struct WrapperStruct<T: FontStyle> {
let y: T

func test(x: T) {
x.#^COMPLETE_MEMBER_IN_NESTED_GENERIC_CONTEXT?check=EXTENSION_NOT_APPLIED^#
y.#^COMPLETE_MEMBER_FROM_OUTER_GENERIC_CONTEXT_IN_INNER?check=EXTENSION_NOT_APPLIED^#
test(x: .#^COMPLETE_GENERIC_FUNC_WITH_TYPE_SPECIALIZED^#)
// COMPLETE_GENERIC_FUNC_WITH_TYPE_SPECIALIZED-NOT: variableDeclaredInConstraintExtension
}
}

func bar<T: FontStyle>(x: T) -> T { return x }
func test2<T: FontStyle>(x: T) {
bar(x).#^COMPLETE_ON_GENERIC_FUNC_WITH_GENERIC_ARG?check=EXTENSION_NOT_APPLIED^#
bar(FontStyleTwo()).#^COMPLETE_ON_GENERIC_FUNC^#
// COMPLETE_ON_GENERIC_FUNC: Begin completions, 1 item
// COMPLETE_ON_GENERIC_FUNC-DAG: Keyword[self]/CurrNominal: self[#FontStyleTwo#];
// COMPLETE_ON_GENERIC_FUNC: End completions
}

struct Sr12973 {}
struct Indicator<T> {}
extension Indicator where T == Sr12973 {
static var activity: Indicator<Sr12973> { fatalError() }
}

func receiver<T>(_ inidicator: Indicator<T>) {}

func test() {
receiver(.#^COMPLETE_GENERIC_TYPE^#)
}
// COMPLETE_GENERIC_TYPE: Begin completions, 2 items
// COMPLETE_GENERIC_TYPE: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Indicator<T>#];
// COMPLETE_GENERIC_TYPE: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: activity[#Indicator<Sr12973>#];
// COMPLETE_GENERIC_TYPE: End completions

func testRecursive<T>(_ inidicator: Indicator<T>) {
testRecursive(.#^COMPLETE_RECURSIVE_GENERIC^#)
// FIXME: We should be suggesting `.activity` here because the call to `testRecursive` happens with new generic parameters
// COMPLETE_RECURSIVE_GENERIC: Begin completions, 1 item
// COMPLETE_RECURSIVE_GENERIC-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Indicator<T>#];
// COMPLETE_RECURSIVE_GENERIC: End completions
}