Skip to content

Commit bebc8b8

Browse files
authored
Merge pull request #37080 from ahoppen/pr-5.5/complete-from-constrained-extension
[5.5][CodeComplete] Show completions from constrained protocol extension
2 parents c30b098 + 1619b8d commit bebc8b8

File tree

2 files changed

+148
-1
lines changed

2 files changed

+148
-1
lines changed

lib/Sema/IDETypeCheckingRequests.cpp

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,87 @@ void swift::registerIDETypeCheckRequestFunctions(Evaluator &evaluator) {
4444
ideTypeCheckRequestFunctions);
4545
}
4646

47+
/// Consider the following example
48+
///
49+
/// \code
50+
/// protocol FontStyle {}
51+
/// struct FontStyleOne: FontStyle {}
52+
/// extension FontStyle where Self == FontStyleOne {
53+
/// static var one: FontStyleOne { FontStyleOne() }
54+
/// }
55+
/// func foo<T: FontStyle>(x: T) {}
56+
///
57+
/// func case1() {
58+
/// foo(x: .#^COMPLETE^#) // extension should be considered applied here
59+
/// }
60+
/// func case2<T: FontStyle>(x: T) {
61+
/// x.#^COMPLETE_2^# // extension should not be considered applied here
62+
/// }
63+
/// \endcode
64+
/// We want to consider the extension applied in the first case but not the
65+
/// second case. In the first case the constraint `T: FontStyle` from the
66+
/// definition of `foo` should be considered an 'at-least' constraint and any
67+
/// additional constraints on `T` (like `T == FonstStyleOne`) can be
68+
/// fulfilled by picking a more specialized version of `T`.
69+
/// However, in the second case, `T: FontStyle` should be considered an
70+
/// 'at-most' constraint and we can't make the assumption that `x` has a more
71+
/// specialized type.
72+
///
73+
/// After type-checking we cannot easily differentiate the two cases. In both
74+
/// we have a unresolved dot completion on a primary archetype that
75+
/// conforms to `FontStyle`.
76+
///
77+
/// To tell them apart, we apply the following heuristic: If the primary
78+
/// archetype refers to a generic parameter that is not visible in the current
79+
/// decl context (i.e. the current decl context is not a child context of the
80+
/// parameter's decl context), it is not the type of a variable visible
81+
/// in the current decl context. Hence, we must be in the first case and
82+
/// consider all extensions applied, otherwise we should only consider those
83+
/// extensions applied whose requirements are fulfilled.
84+
class ContainsSpecializableArchetype : public TypeWalker {
85+
const DeclContext *DC;
86+
bool Result = false;
87+
ContainsSpecializableArchetype(const DeclContext *DC) : DC(DC) {}
88+
89+
Action walkToTypePre(Type T) override {
90+
if (auto *Archetype = T->getAs<ArchetypeType>()) {
91+
if (auto *GenericTypeParam =
92+
Archetype->mapTypeOutOfContext()->getAs<GenericTypeParamType>()) {
93+
if (auto GenericTypeParamDecl = GenericTypeParam->getDecl()) {
94+
bool ParamMaybeVisibleInCurrentContext =
95+
(DC == GenericTypeParamDecl->getDeclContext() ||
96+
DC->isChildContextOf(GenericTypeParamDecl->getDeclContext()));
97+
if (!ParamMaybeVisibleInCurrentContext) {
98+
Result = true;
99+
return Action::Stop;
100+
}
101+
}
102+
}
103+
}
104+
return Action::Continue;
105+
}
106+
107+
public:
108+
static bool check(const DeclContext *DC, Type T) {
109+
if (!T->hasArchetype()) {
110+
// Fast path, we don't have an archetype to check.
111+
return false;
112+
}
113+
ContainsSpecializableArchetype Checker(DC);
114+
T.walk(Checker);
115+
return Checker.Result;
116+
}
117+
};
118+
47119
static bool isExtensionAppliedInternal(const DeclContext *DC, Type BaseTy,
48120
const ExtensionDecl *ED) {
49121
// We can't do anything if the base type has unbound generic parameters.
50122
// We can't leak type variables into another constraint system.
123+
// For check on specializable archetype see comment on
124+
// ContainsSpecializableArchetype.
51125
if (BaseTy->hasTypeVariable() || BaseTy->hasUnboundGenericType() ||
52-
BaseTy->hasUnresolvedType() || BaseTy->hasError())
126+
BaseTy->hasUnresolvedType() || BaseTy->hasError() ||
127+
ContainsSpecializableArchetype::check(DC, BaseTy))
53128
return true;
54129

55130
if (!ED->isConstrainedExtension())
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-ide-test -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t
3+
4+
protocol FontStyle {}
5+
struct FontStyleOne: FontStyle {}
6+
struct FontStyleTwo: FontStyle {}
7+
8+
extension FontStyle where Self == FontStyleOne {
9+
static var variableDeclaredInConstraintExtension: FontStyleOne { FontStyleOne() }
10+
}
11+
12+
func foo<T: FontStyle>(x: T) {}
13+
func test() {
14+
foo(x: .#^COMPLETE_STATIC_MEMBER?check=EXTENSION_APPLIED^#)
15+
}
16+
17+
// EXTENSION_APPLIED: Begin completions, 1 item
18+
// EXTENSION_APPLIED-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: variableDeclaredInConstraintExtension[#FontStyleOne#];
19+
// EXTENSION_APPLIED: End completions
20+
21+
func test<T: FontStyle>(x: T) {
22+
x.#^COMPLETE_MEMBER_IN_GENERIC_CONTEXT?check=EXTENSION_NOT_APPLIED^#
23+
}
24+
25+
// EXTENSION_NOT_APPLIED: Begin completions, 1 item
26+
// EXTENSION_NOT_APPLIED-DAG: Keyword[self]/CurrNominal: self[#T#];
27+
// EXTENSION_NOT_APPLIED-NOT: variableDeclaredInConstraintExtension
28+
// EXTENSION_NOT_APPLIED: End completions
29+
30+
struct WrapperStruct<T: FontStyle> {
31+
let y: T
32+
33+
func test(x: T) {
34+
x.#^COMPLETE_MEMBER_IN_NESTED_GENERIC_CONTEXT?check=EXTENSION_NOT_APPLIED^#
35+
y.#^COMPLETE_MEMBER_FROM_OUTER_GENERIC_CONTEXT_IN_INNER?check=EXTENSION_NOT_APPLIED^#
36+
test(x: .#^COMPLETE_GENERIC_FUNC_WITH_TYPE_SPECIALIZED^#)
37+
// COMPLETE_GENERIC_FUNC_WITH_TYPE_SPECIALIZED-NOT: variableDeclaredInConstraintExtension
38+
}
39+
}
40+
41+
func bar<T: FontStyle>(x: T) -> T { return x }
42+
func test2<T: FontStyle>(x: T) {
43+
bar(x).#^COMPLETE_ON_GENERIC_FUNC_WITH_GENERIC_ARG?check=EXTENSION_NOT_APPLIED^#
44+
bar(FontStyleTwo()).#^COMPLETE_ON_GENERIC_FUNC^#
45+
// COMPLETE_ON_GENERIC_FUNC: Begin completions, 1 item
46+
// COMPLETE_ON_GENERIC_FUNC-DAG: Keyword[self]/CurrNominal: self[#FontStyleTwo#];
47+
// COMPLETE_ON_GENERIC_FUNC: End completions
48+
}
49+
50+
struct Sr12973 {}
51+
struct Indicator<T> {}
52+
extension Indicator where T == Sr12973 {
53+
static var activity: Indicator<Sr12973> { fatalError() }
54+
}
55+
56+
func receiver<T>(_ inidicator: Indicator<T>) {}
57+
58+
func test() {
59+
receiver(.#^COMPLETE_GENERIC_TYPE^#)
60+
}
61+
// COMPLETE_GENERIC_TYPE: Begin completions, 2 items
62+
// COMPLETE_GENERIC_TYPE: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Indicator<T>#];
63+
// COMPLETE_GENERIC_TYPE: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: activity[#Indicator<Sr12973>#];
64+
// COMPLETE_GENERIC_TYPE: End completions
65+
66+
func testRecursive<T>(_ inidicator: Indicator<T>) {
67+
testRecursive(.#^COMPLETE_RECURSIVE_GENERIC^#)
68+
// FIXME: We should be suggesting `.activity` here because the call to `testRecursive` happens with new generic parameters
69+
// COMPLETE_RECURSIVE_GENERIC: Begin completions, 1 item
70+
// COMPLETE_RECURSIVE_GENERIC-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Indicator<T>#];
71+
// COMPLETE_RECURSIVE_GENERIC: End completions
72+
}

0 commit comments

Comments
 (0)