Skip to content

Commit 9d3a17c

Browse files
committed
[IDE] Fix superclass constraint handling for extension merging
For a case like: ``` public class C<T> {} public class D {} extension C where T : D { public func foo() {} } ``` We would indadvertedly drop the extension for `C` in the doc info, as the superclass constraint would fail the `isBindableToSuperclassOf` check. Instead, map the subject type of the constraint into the context and check if it could be bound to the superclass. In the example above, this is trivially true, but for cases where we're mirroring a protocol extension onto the type, this will disregard those that don't fulfil the requirements. Resolves rdar://76868074
1 parent a6346f8 commit 9d3a17c

6 files changed

+921
-4
lines changed

lib/IDE/IDETypeChecking.cpp

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ struct SynthesizedExtensionAnalyzer::Implementation {
306306
continue;
307307

308308
if (!BaseType->isExistentialType()) {
309+
// Apply any substitutions we need to map the requirements from a
310+
// a protocol extension to an extension on the conforming type.
309311
First = First.subst(subMap);
310312
Second = Second.subst(subMap);
311313

@@ -317,11 +319,12 @@ struct SynthesizedExtensionAnalyzer::Implementation {
317319
}
318320
}
319321

322+
assert(!First->hasArchetype() && !Second->hasArchetype());
320323
switch (Kind) {
321324
case RequirementKind::Conformance: {
322325
auto *M = DC->getParentModule();
323326
auto *Proto = Second->castTo<ProtocolType>()->getDecl();
324-
if (!First->isTypeParameter() && !First->is<ArchetypeType>() &&
327+
if (!First->isTypeParameter() &&
325328
M->conformsToProtocol(First, Proto).isInvalid())
326329
return true;
327330
if (M->conformsToProtocol(First, Proto).isInvalid())
@@ -330,11 +333,27 @@ struct SynthesizedExtensionAnalyzer::Implementation {
330333
}
331334

332335
case RequirementKind::Superclass:
333-
if (!Second->isBindableToSuperclassOf(First)) {
334-
return true;
335-
} else if (!Second->isExactSuperclassOf(Second)) {
336+
// If the subject type of the requirement is still a type parameter,
337+
// we need to check if the contextual type could possibly be bound to
338+
// the superclass. If not, this extension isn't applicable.
339+
if (First->isTypeParameter()) {
340+
if (!Target->mapTypeIntoContext(First)->isBindableTo(
341+
Target->mapTypeIntoContext(Second))) {
342+
return true;
343+
}
336344
MergeInfo.addRequirement(GenericSig, First, Second, Kind);
345+
break;
337346
}
347+
348+
// If we've substituted in a concrete type for the subject, we can
349+
// check for an exact superclass match, and disregard the extension if
350+
// it missed.
351+
// FIXME: What if it ends being something like `C<U> : C<Int>`?
352+
// Arguably we should allow that to be mirrored with a U == Int
353+
// constraint.
354+
if (!Second->isExactSuperclassOf(First))
355+
return true;
356+
338357
break;
339358

340359
case RequirementKind::SameType:

test/IDE/print_synthesized_extensions.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
// RUN: %FileCheck %s -check-prefix=CHECK12 < %t.syn.txt
1616
// RUN: %FileCheck %s -check-prefix=CHECK13 < %t.syn.txt
1717
// RUN: %FileCheck %s -check-prefix=CHECK14 < %t.syn.txt
18+
// RUN: %FileCheck %s -check-prefix=CHECK15 < %t.syn.txt
19+
// RUN: %FileCheck %s -check-prefix=CHECK16 < %t.syn.txt
1820

1921
public protocol P1 {
2022
associatedtype T1
@@ -339,3 +341,60 @@ extension S13 : P5 {
339341
// CHECK14-NEXT: <decl:Func>/// This should not crash
340342
// CHECK14-NEXT: public func <loc>foo5()</loc></decl>
341343
// CHECK14-NEXT: }</synthesized>
344+
345+
// rdar://76868074: Make sure we print the extensions for C.
346+
public class C<T> {}
347+
public class D {}
348+
public class E {}
349+
350+
extension C where T : D {
351+
public func foo() {}
352+
}
353+
354+
public protocol P8 {
355+
associatedtype T
356+
}
357+
358+
extension P8 where T : D {
359+
public func bar() {}
360+
}
361+
362+
extension P8 where T : E {
363+
public func baz() {}
364+
}
365+
366+
extension C : P8 {}
367+
368+
// CHECK15: <decl:Class>public class <loc>C<<decl:GenericTypeParam>T</decl>></loc> {
369+
// CHECK15-NEXT: }</decl>
370+
371+
// CHECK15: <decl:Extension>extension <loc><ref:Class>C</ref></loc> : <ref:module>print_synthesized_extensions</ref>.<ref:Protocol>P8</ref> {
372+
// CHECK15-NEXT: }</decl>
373+
374+
// CHECK15: <decl:Extension>extension <loc><ref:Class>C</ref></loc> where <ref:GenericTypeParam>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>D</ref> {
375+
// CHECK15-NEXT: <decl:Func>public func <loc>foo()</loc></decl></decl>
376+
// CHECK15-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
377+
// CHECK15-NEXT: }</synthesized>
378+
379+
// CHECK15: <synthesized>extension <ref:Class>C</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>E</ref> {
380+
// CHECK15-NEXT: <decl:Func>public func <loc>baz()</loc></decl>
381+
// CHECK15-NEXT: }</synthesized>
382+
383+
// CHECK15: <decl:Extension>extension <loc><ref:Protocol>P8</ref></loc> where <ref:GenericTypeParam>Self</ref>.<ref:AssociatedType>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>D</ref> {
384+
// CHECK15-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
385+
// CHECK15-NEXT: }</decl>
386+
387+
// CHECK15: <decl:Extension>extension <loc><ref:Protocol>P8</ref></loc> where <ref:GenericTypeParam>Self</ref>.<ref:AssociatedType>T</ref> : <ref:module>print_synthesized_extensions</ref>.<ref:Class>E</ref> {
388+
// CHECK15-NEXT: <decl:Func>public func <loc>baz()</loc></decl>
389+
// CHECK15-NEXT: }</decl>
390+
391+
// For this class, make sure we mirror the extension P8 where T : D, but not
392+
// the extension P8 where T : E.
393+
public class F<T : D> {}
394+
extension F : P8 {}
395+
396+
// CHECK16: <synthesized>extension <ref:Class>F</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>D</ref> {
397+
// CHECK16-NEXT: <decl:Func>public func <loc>bar()</loc></decl>
398+
// CHECK16-NEXT: }</synthesized>
399+
400+
// CHECK16-NOT: <synthesized>extension <ref:Class>F</ref> where <ref:GenericTypeParam>T</ref> : <ref:Class>E</ref> {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module-path %t/print_synthesized_extensions_generics.swiftmodule -emit-module-doc -emit-module-doc-path %t/print_synthesized_extensions_generics.swiftdoc %s
3+
// RUN: %target-swift-ide-test -print-module -synthesize-extension -print-interface -no-empty-line-between-members -module-to-print=print_synthesized_extensions_generics -I %t -source-filename=%s | %FileCheck %s
4+
5+
public protocol P1 {
6+
associatedtype T
7+
associatedtype U
8+
}
9+
10+
public class A {}
11+
public class B<T> : A {}
12+
13+
extension P1 where T : B<U> {
14+
public func qux() {}
15+
}
16+
extension P1 where T : B<Int> {
17+
public func flob() {}
18+
}
19+
20+
public class C<T : A, U> {}
21+
extension C : P1 {}
22+
// CHECK: extension C where T : B<U> {
23+
// CHECK-NEXT: func qux()
24+
// CHECK-NEXT: }
25+
26+
// CHECK: extension C where T : B<Int> {
27+
// CHECK-NEXT: func flob()
28+
// CHECK-NEXT: }
29+
30+
public class D<U> {}
31+
extension D : P1 {
32+
public typealias T = B<U>
33+
}
34+
// CHECK: class D<U> {
35+
// CHECK-NEXT: func qux()
36+
// CHECK-NEXT: }
37+
38+
// FIXME: Arguably we should support this
39+
// CHECK-NOT: extension D where U == Int
40+
41+
public class E {}
42+
extension E: P1 {
43+
public typealias T = B<U>
44+
public typealias U = Int
45+
}
46+
// CHECK: class E {
47+
// CHECK-NEXT: func qux()
48+
// CHECK-NEXT: func flob()
49+
// CHECK-NEXT: }
50+
51+
public class F {}
52+
extension F : P1 {
53+
public typealias T = B<U>
54+
public typealias U = String
55+
}
56+
// CHECK: class F {
57+
// CHECK-NEXT: func qux()
58+
// CHECK-NEXT: }
59+
60+
// CHECK-NOT: extension F where T : B<Int>
61+
62+
public protocol P2 {
63+
associatedtype T : P1
64+
}
65+
66+
extension P2 where T.T : A {
67+
public func blah() {}
68+
}
69+
70+
public class G<T : P1> {}
71+
extension G : P2 {}
72+
73+
// CHECK: extension G where T.T : A {
74+
// CHECK-NEXT: func blah()
75+
// CHECK-NEXT: }
76+
77+
// CHECK: extension P1 where Self.T : print_synthesized_extensions_generics.B<Self.U> {
78+
// CHECK-NEXT: func qux()
79+
// CHECK-NEXT: }
80+
81+
// CHECK: extension P1 where Self.T : print_synthesized_extensions_generics.B<Int> {
82+
// CHECK-NEXT: func flob()
83+
// CHECK-NEXT: }
84+
85+
// CHECK: extension P2 where Self.T.T : print_synthesized_extensions_generics.A {
86+
// CHECK-NEXT: func blah()
87+
// CHECK-NEXT: }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
public class C<T> {}
3+
public class D {}
4+
public class E {}
5+
6+
extension C where T : D {
7+
public func foo() {}
8+
}
9+
10+
public protocol P8 {
11+
associatedtype T
12+
}
13+
14+
extension P8 where T : D {
15+
public func bar() {}
16+
}
17+
18+
extension P8 where T : E {
19+
public func baz() {}
20+
}
21+
22+
extension C : P8 {}
23+
24+
public class F<T : D> {}
25+
extension F : P8 {}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// RUN: %empty-directory(%t.mod)
2+
// RUN: %swift -emit-module -o %t.mod/module_with_class_extension.swiftmodule %S/Inputs/module_with_class_extension.swift -parse-as-library
3+
// RUN: %sourcekitd-test -req=doc-info -module module_with_class_extension -- -Xfrontend -disable-implicit-concurrency-module-import -I %t.mod > %t.response
4+
// RUN: %diff -u %s.response %t.response
5+
6+
// rdar://76868074: Make sure we print the extensions for C.

0 commit comments

Comments
 (0)