Skip to content

Commit c85ea1f

Browse files
authored
Merge pull request #4654 from jrose-apple/testable-protocol-witnesses
Allow @testable witnesses to satisfy protocol requirements. rdar://problem/28173654
2 parents c9d843c + 1c1aed7 commit c85ea1f

File tree

6 files changed

+158
-7
lines changed

6 files changed

+158
-7
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,14 +1240,31 @@ checkWitnessAccessibility(const DeclContext *&requiredAccessScope,
12401240
}
12411241
}
12421242

1243-
if (!witness->isAccessibleFrom(requiredAccessScope))
1244-
return true;
1243+
const DeclContext *actualScopeToCheck = requiredAccessScope;
1244+
if (!witness->isAccessibleFrom(actualScopeToCheck)) {
1245+
// Special case: if we have `@testable import` of the witness's module,
1246+
// allow the witness to match if it would have matched for just this file.
1247+
// That is, if '@testable' allows us to see the witness here, it should
1248+
// allow us to see it anywhere, because any other client could also add
1249+
// their own `@testable import`.
1250+
if (auto parentFile = dyn_cast<SourceFile>(DC->getModuleScopeContext())) {
1251+
const Module *witnessModule = witness->getModuleContext();
1252+
if (parentFile->getParentModule() != witnessModule &&
1253+
parentFile->hasTestableImport(witnessModule) &&
1254+
witness->isAccessibleFrom(parentFile)) {
1255+
actualScopeToCheck = parentFile;
1256+
}
1257+
}
1258+
1259+
if (actualScopeToCheck == requiredAccessScope)
1260+
return true;
1261+
}
12451262

12461263
if (requirement->isSettable(DC)) {
12471264
*isSetter = true;
12481265

12491266
auto ASD = cast<AbstractStorageDecl>(witness);
1250-
if (!ASD->isSetterAccessibleFrom(requiredAccessScope))
1267+
if (!ASD->isSetterAccessibleFrom(actualScopeToCheck))
12511268
return true;
12521269
}
12531270

test/NameBinding/Inputs/has_accessibility.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ public struct StructWithPrivateSetter {
3434
public private(set) var x = 0
3535
public init() {}
3636
}
37+
38+
public protocol HasDefaultImplementation {}
39+
extension HasDefaultImplementation {
40+
internal func foo() {}
41+
}
42+
internal class InternalBase {}

test/NameBinding/accessibility.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
// RUN: rm -rf %t && mkdir -p %t
22
// RUN: cp %s %t/main.swift
33

4-
// RUN: %target-swift-frontend -parse -primary-file %t/main.swift %S/Inputs/accessibility_other.swift -module-name accessibility -enable-source-import -I %S/Inputs -sdk "" -enable-access-control -verify
5-
// RUN: %target-swift-frontend -parse -primary-file %t/main.swift %S/Inputs/accessibility_other.swift -module-name accessibility -enable-source-import -I %S/Inputs -sdk "" -disable-access-control -D DEFINE_VAR_FOR_SCOPED_IMPORT -D ACCESS_DISABLED
6-
74
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/has_accessibility.swift -D DEFINE_VAR_FOR_SCOPED_IMPORT -enable-testing
85
// RUN: %target-swift-frontend -parse -primary-file %t/main.swift %S/Inputs/accessibility_other.swift -module-name accessibility -I %t -sdk "" -enable-access-control -verify
96
// RUN: %target-swift-frontend -parse -primary-file %t/main.swift %S/Inputs/accessibility_other.swift -module-name accessibility -I %t -sdk "" -disable-access-control -D ACCESS_DISABLED
@@ -105,9 +102,9 @@ protocol MethodProto {
105102
}
106103

107104
extension OriginallyEmpty : MethodProto {}
108-
// TESTABLE-NOT: :[[@LINE-1]]:{{[^:]+}}:
109105
#if !ACCESS_DISABLED
110106
extension HiddenMethod : MethodProto {} // expected-error {{type 'HiddenMethod' does not conform to protocol 'MethodProto'}}
107+
// TESTABLE-NOT: :[[@LINE-1]]:{{[^:]+}}:
111108

112109
extension Foo : MethodProto {} // expected-error {{type 'Foo' does not conform to protocol 'MethodProto'}}
113110
#endif
@@ -163,3 +160,16 @@ private struct PrivateConformerByLocalTypeBad : TypeProto {
163160
}
164161
#endif
165162

163+
public protocol Fooable {
164+
func foo() // expected-note * {{protocol requires function 'foo()'}}
165+
}
166+
167+
#if !ACCESS_DISABLED
168+
internal struct FooImpl: Fooable, HasDefaultImplementation {} // expected-error {{type 'FooImpl' does not conform to protocol 'Fooable'}}
169+
public struct PublicFooImpl: Fooable, HasDefaultImplementation {} // expected-error {{type 'PublicFooImpl' does not conform to protocol 'Fooable'}}
170+
// TESTABLE-NOT: method 'foo()'
171+
172+
internal class TestableSub: InternalBase {} // expected-error {{undeclared type 'InternalBase'}}
173+
public class TestablePublicSub: InternalBase {} // expected-error {{undeclared type 'InternalBase'}}
174+
// TESTABLE-NOT: undeclared type 'InternalBase'
175+
#endif
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
public protocol HasDefaultFoo {}
2+
extension HasDefaultFoo {
3+
internal func foo() {}
4+
}
5+
6+
internal class Base {
7+
func foo() {}
8+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// This test is paired with testable-multifile.swift.
2+
3+
// RUN: rm -rf %t && mkdir -p %t
4+
// RUN: %target-swift-frontend -emit-module %S/Inputs/TestableMultifileHelper.swift -enable-testing -o %t
5+
6+
// RUN: %target-swift-frontend -emit-silgen -I %t %s %S/testable-multifile.swift -module-name main | %FileCheck %s
7+
// RUN: %target-swift-frontend -emit-silgen -I %t %S/testable-multifile.swift %s -module-name main | %FileCheck %s
8+
// RUN: %target-swift-frontend -emit-silgen -I %t -primary-file %s %S/testable-multifile.swift -module-name main | %FileCheck %s
9+
10+
// Just make sure we don't crash later on.
11+
// RUN: %target-swift-frontend -emit-ir -I %t -primary-file %s %S/testable-multifile.swift -module-name main -o /dev/null
12+
// RUN: %target-swift-frontend -emit-ir -I %t -O -primary-file %s %S/testable-multifile.swift -module-name main -o /dev/null
13+
14+
func use<F: Fooable>(_ f: F) { f.foo() }
15+
func test(internalFoo: FooImpl, publicFoo: PublicFooImpl) {
16+
use(internalFoo)
17+
use(publicFoo)
18+
19+
internalFoo.foo()
20+
publicFoo.foo()
21+
}
22+
23+
// CHECK-LABEL: sil hidden @_TF4main4testFT11internalFooVS_7FooImpl9publicFooVS_13PublicFooImpl_T_
24+
// CHECK: [[USE_1:%.+]] = function_ref @_TF4main3useuRxS_7FooablerFxT_
25+
// CHECK: = apply [[USE_1]]<FooImpl>({{%.+}}) : $@convention(thin) <τ_0_0 where τ_0_0 : Fooable> (@in τ_0_0) -> ()
26+
// CHECK: [[USE_2:%.+]] = function_ref @_TF4main3useuRxS_7FooablerFxT_
27+
// CHECK: = apply [[USE_2]]<PublicFooImpl>({{%.+}}) : $@convention(thin) <τ_0_0 where τ_0_0 : Fooable> (@in τ_0_0) -> ()
28+
// CHECK: [[IMPL_1:%.+]] = function_ref @_TFE23TestableMultifileHelperPS_13HasDefaultFoo3foofT_T_
29+
// CHECK: = apply [[IMPL_1]]<FooImpl>({{%.+}}) : $@convention(method) <τ_0_0 where τ_0_0 : HasDefaultFoo> (@in_guaranteed τ_0_0) -> ()
30+
// CHECK: [[IMPL_2:%.+]] = function_ref @_TFE23TestableMultifileHelperPS_13HasDefaultFoo3foofT_T_
31+
// CHECK: = apply [[IMPL_2]]<PublicFooImpl>({{%.+}}) : $@convention(method) <τ_0_0 where τ_0_0 : HasDefaultFoo> (@in_guaranteed τ_0_0) -> ()
32+
// CHECK: {{^}$}}
33+
34+
func test(internalSub: Sub, publicSub: PublicSub) {
35+
internalSub.foo()
36+
publicSub.foo()
37+
}
38+
39+
// CHECK-LABEL: sil hidden @_TF4main4testFT11internalSubCS_3Sub9publicSubCS_9PublicSub_T_
40+
// CHECK: = class_method %0 : $Sub, #Sub.foo!1
41+
// CHECK: = class_method %1 : $PublicSub, #PublicSub.foo!1
42+
// CHECK: {{^}$}}
43+

test/SILGen/testable-multifile.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This test is paired with testable-multifile-other.swift.
2+
3+
// RUN: rm -rf %t && mkdir -p %t
4+
// RUN: %target-swift-frontend -emit-module %S/Inputs/TestableMultifileHelper.swift -enable-testing -o %t
5+
6+
// RUN: %target-swift-frontend -emit-silgen -I %t %s %S/testable-multifile-other.swift -module-name main | %FileCheck %s
7+
// RUN: %target-swift-frontend -emit-silgen -I %t %S/testable-multifile-other.swift %s -module-name main | %FileCheck %s
8+
// RUN: %target-swift-frontend -emit-silgen -I %t -primary-file %s %S/testable-multifile-other.swift -module-name main | %FileCheck %s
9+
10+
// Just make sure we don't crash later on.
11+
// RUN: %target-swift-frontend -emit-ir -I %t -primary-file %s %S/testable-multifile-other.swift -module-name main -o /dev/null
12+
// RUN: %target-swift-frontend -emit-ir -I %t -O -primary-file %s %S/testable-multifile-other.swift -module-name main -o /dev/null
13+
14+
@testable import TestableMultifileHelper
15+
16+
public protocol Fooable {
17+
func foo()
18+
}
19+
20+
struct FooImpl: Fooable, HasDefaultFoo {}
21+
public struct PublicFooImpl: Fooable, HasDefaultFoo {}
22+
23+
// CHECK-LABEL: sil{{.*}} @_TTWV4main7FooImplS_7FooableS_FS1_3foofT_T_ : $@convention(witness_method) (@in_guaranteed FooImpl) -> () {
24+
// CHECK: function_ref @_TFE23TestableMultifileHelperPS_13HasDefaultFoo3foofT_T_
25+
// CHECK: {{^}$}}
26+
27+
// CHECK-LABEL: sil{{.*}} @_TTWV4main13PublicFooImplS_7FooableS_FS1_3foofT_T_ : $@convention(witness_method) (@in_guaranteed PublicFooImpl) -> () {
28+
// CHECK: function_ref @_TFE23TestableMultifileHelperPS_13HasDefaultFoo3foofT_T_
29+
// CHECK: {{^}$}}
30+
31+
private class PrivateSub: Base {
32+
fileprivate override func foo() {}
33+
}
34+
class Sub: Base {
35+
internal override func foo() {}
36+
}
37+
public class PublicSub: Base {
38+
public override func foo() {}
39+
}
40+
41+
// CHECK-LABEL: sil_vtable PrivateSub {
42+
// CHECK-NEXT: #Base.foo!1: _TFC4mainP33_F1525133BD493492AD72BF10FBCB1C5210PrivateSub3foofT_T_
43+
// CHECK-NEXT: #Base.init!initializer.1: _TFC4mainP33_F1525133BD493492AD72BF10FBCB1C5210PrivateSubcfT_S0_
44+
// CHECK-NEXT: #PrivateSub.deinit!deallocator: _TFC4mainP33_F1525133BD493492AD72BF10FBCB1C5210PrivateSubD
45+
// CHECK-NEXT: }
46+
47+
// CHECK-LABEL: sil_vtable Sub {
48+
// CHECK-NEXT: #Base.foo!1: _TFC4main3Sub3foofT_T_
49+
// CHECK-NEXT: #Base.init!initializer.1: _TFC4main3SubcfT_S0_
50+
// CHECK-NEXT: #Sub.deinit!deallocator: _TFC4main3SubD
51+
// CHECK-NEXT: }
52+
53+
// CHECK-LABEL: sil_vtable PublicSub {
54+
// CHECK-NEXT: #Base.foo!1: _TFC4main9PublicSub3foofT_T_
55+
// CHECK-NEXT: #Base.init!initializer.1: _TFC4main9PublicSubcfT_S0_
56+
// CHECK-NEXT: #PublicSub.deinit!deallocator: _TFC4main9PublicSubD
57+
// CHECK-NEXT: }
58+
59+
60+
61+
// CHECK-LABEL: sil_witness_table FooImpl: Fooable module main {
62+
// CHECK-NEXT: method #Fooable.foo!1: @_TTWV4main7FooImplS_7FooableS_FS1_3foofT_T_
63+
// CHECK-NEXT: }
64+
65+
// CHECK-LABEL: sil_witness_table [fragile] PublicFooImpl: Fooable module main {
66+
// CHECK-NEXT: method #Fooable.foo!1: @_TTWV4main13PublicFooImplS_7FooableS_FS1_3foofT_T_
67+
// CHECK-NEXT: }

0 commit comments

Comments
 (0)