Skip to content

Commit b4fca1a

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 b04ba4b commit b4fca1a

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

213241
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)