Skip to content

Commit c6a7920

Browse files
committed
Allow @testable witnesses to satisfy protocol requirements.
That is, if a would-be witness to a protocol requirement is only accessible because its module has been imported with '@testable', consider that "good enough" to satisfy the rule that a witness must be available everywhere the protocol and conforming type are both available (because those other contexts /could/ have done their own testable import). rdar://problem/28173654
1 parent a7fef22 commit c6a7920

File tree

6 files changed

+108
-7
lines changed

6 files changed

+108
-7
lines changed

lib/Sema/TypeCheckProtocol.cpp

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

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

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

12481265
auto ASD = cast<AbstractStorageDecl>(witness);
1249-
if (!ASD->isSetterAccessibleFrom(requiredAccessScope))
1266+
if (!ASD->isSetterAccessibleFrom(actualScopeToCheck))
12501267
return true;
12511268
}
12521269

test/NameBinding/Inputs/has_accessibility.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@ 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+
}

test/NameBinding/accessibility.swift

Lines changed: 10 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,12 @@ 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+
#endif
171+
// TESTABLE-NOT: method 'foo()'
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
public protocol HasDefaultFoo {}
2+
extension HasDefaultFoo {
3+
internal func foo() {}
4+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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: {{^}$}}

test/SILGen/testable-multifile.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
// CHECK-LABEL: sil_witness_table FooImpl: Fooable module main {
32+
// CHECK-NEXT: method #Fooable.foo!1: @_TTWV4main7FooImplS_7FooableS_FS1_3foofT_T_
33+
// CHECK-NEXT: }
34+
35+
// CHECK-LABEL: sil_witness_table [fragile] PublicFooImpl: Fooable module main {
36+
// CHECK-NEXT: method #Fooable.foo!1: @_TTWV4main13PublicFooImplS_7FooableS_FS1_3foofT_T_
37+
// CHECK-NEXT: }

0 commit comments

Comments
 (0)