Skip to content

Commit 6dad5a0

Browse files
committed
[CodeCompletion] Avoid creating circles in the USRBasedType supertype hierarchy
You would think that superclass + conformances form a DAG. You are wrong! We can achieve a circular supertype hierarcy with ```swift protocol Proto : Class {} class Class : Proto {} ``` USRBasedType is not set up for this. Serialization of code completion results from global modules can't handle cycles in the supertype hierarchy because it writes the DAG leaf to root(s) and needs to know the type offsets. To get consistent results independent of where we start constructing USRBasedTypes, ignore superclasses of protocols. If we kept track of already visited types, we would get different results depending on whether we start constructing the USRBasedType hierarchy from Proto or Class. Ignoring superclasses of protocols is safe to do because USRBasedType is an under-approximation anyway. rdar://91765262
1 parent 1cf0345 commit 6dad5a0

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

lib/IDE/CodeCompletionResultType.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,38 @@ const USRBasedType *USRBasedType::fromType(Type Ty, USRBasedTypeArena &Arena) {
203203
Conformance->getProtocol()->getDeclaredInterfaceType(), Arena));
204204
}
205205
}
206-
Type Superclass = Ty->getSuperclass();
206+
207+
// You would think that superclass + conformances form a DAG. You are wrong!
208+
// We can achieve a circular supertype hierarcy with
209+
//
210+
// protocol Proto : Class {}
211+
// class Class : Proto {}
212+
//
213+
// USRBasedType is not set up for this. Serialization of code completion
214+
// results from global modules can't handle cycles in the supertype hierarchy
215+
// because it writes the DAG leaf to root(s) and needs to know the type
216+
// offsets. To get consistent results independent of where we start
217+
// constructing USRBasedTypes, ignore superclasses of protocols. If we kept
218+
// track of already visited types, we would get different results depending on
219+
// whether we start constructing the USRBasedType hierarchy from Proto or
220+
// Class.
221+
// Ignoring superclasses of protocols is safe to do because USRBasedType is an
222+
// under-approximation anyway.
223+
224+
/// If `Ty` is a class type and has a superclass, return that. In all other
225+
/// cases, return null.
226+
auto getSuperclass = [](Type Ty) -> Type {
227+
if (isa_and_nonnull<ClassDecl>(Ty->getAnyNominal())) {
228+
return Ty->getSuperclass();
229+
} else {
230+
return Type();
231+
}
232+
};
233+
234+
Type Superclass = getSuperclass(Ty);
207235
while (Superclass) {
208236
Supertypes.push_back(USRBasedType::fromType(Superclass, Arena));
209-
Superclass = Superclass->getSuperclass();
237+
Superclass = getSuperclass(Superclass);
210238
}
211239

212240
assert(llvm::all_of(Supertypes, [&USR](const USRBasedType *Ty) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/ImportPath)
3+
// RUN: %{python} %utils/split_file.py -o %t %s
4+
5+
// RUN: %target-swift-frontend -disable-availability-checking -emit-module %t/Lib.swift -o %t/ImportPath/Lib.swiftmodule -emit-module-interface-path %t/ImportPath/Lib.swiftinterface
6+
7+
// BEGIN Lib.swift
8+
9+
// Proto and Class have a circular supertype relationship.
10+
11+
public protocol Proto : Class {}
12+
public class Class : Proto {}
13+
14+
// BEGIN test.swift
15+
16+
import Lib
17+
18+
// RUN: %empty-directory(%t/completion-cache)
19+
// RUN: %target-swift-ide-test -code-completion -source-filename %t/test.swift -code-completion-token COMPLETE -I %t/ImportPath
20+
21+
func test() -> Proto {
22+
return #^COMPLETE^#
23+
}

0 commit comments

Comments
 (0)