Skip to content

Commit 9cc80a2

Browse files
committed
[SE-0404] Allow protocols to be nested in non-generic contexts
1 parent 9ad5598 commit 9cc80a2

File tree

17 files changed

+464
-95
lines changed

17 files changed

+464
-95
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2162,9 +2162,12 @@ ERROR(unsupported_type_nested_in_protocol,none,
21622162
ERROR(unsupported_type_nested_in_protocol_extension,none,
21632163
"type %0 cannot be nested in protocol extension of %1",
21642164
(const NominalTypeDecl *, const ProtocolDecl *))
2165-
ERROR(unsupported_nested_protocol,none,
2166-
"protocol %0 cannot be nested inside another declaration",
2165+
ERROR(unsupported_nested_protocol_in_generic,none,
2166+
"protocol %0 cannot be nested in a generic context",
21672167
(const NominalTypeDecl *))
2168+
ERROR(unsupported_nested_protocol_in_protocol,none,
2169+
"protocol %0 cannot be nested in protocol %1",
2170+
(const NominalTypeDecl *, const NominalTypeDecl *))
21682171
ERROR(where_nongeneric_ctx,none,
21692172
"'where' clause on non-generic member declaration requires a "
21702173
"generic context", ())

include/swift/AST/TypeMemberVisitor.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class TypeMemberVisitor : public DeclVisitor<ImplClass, RetTy> {
3737
}
3838
BAD_MEMBER(Extension)
3939
BAD_MEMBER(Import)
40-
BAD_MEMBER(Protocol)
4140
BAD_MEMBER(TopLevelCode)
4241
BAD_MEMBER(Operator)
4342
BAD_MEMBER(PrecedenceGroup)

lib/AST/ASTScopeLookup.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,7 @@ void ASTScopeImpl::lookup(const NullablePtr<const ASTScopeImpl> limit,
191191
consumer.startingNextLookupStep();
192192
#endif
193193

194-
// Certain illegal nestings, e.g. protocol nestled inside a struct,
195-
// require that lookup stop at the outer scope.
194+
// Certain illegal nestings require that lookup stop at the outer scope.
196195
if (this == limit.getPtrOrNull()) {
197196
#ifndef NDEBUG
198197
consumer.finishingLookup("limit return");
@@ -556,13 +555,13 @@ GenericTypeOrExtensionScope::getLookupLimitForDecl() const {
556555

557556
NullablePtr<const ASTScopeImpl>
558557
NominalTypeScope::getLookupLimitForDecl() const {
559-
if (isa<ProtocolDecl>(decl)) {
560-
// ProtocolDecl can only be legally nested in a SourceFile,
561-
// so any other kind of Decl is illegal
562-
return parentIfNotChildOfTopScope();
563-
}
564-
// AFAICT, a struct, decl, or enum can be nested inside anything
565-
// but a ProtocolDecl.
558+
// If a protocol is (invalidly) nested in a generic context,
559+
// do not look in to those outer generic contexts,
560+
// as types found there may contain implicitly inferred generic parameters.
561+
if (isa<ProtocolDecl>(decl) && decl->getDeclContext()->isGenericContext())
562+
return getLookupParent();
563+
564+
// Otherwise, nominals can be nested inside anything but a ProtocolDecl.
566565
return ancestorWithDeclSatisfying(
567566
[&](const Decl *const d) { return isa<ProtocolDecl>(d); });
568567
}

lib/AST/Decl.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4813,11 +4813,10 @@ static Type computeNominalType(NominalTypeDecl *decl, DeclTypeKind kind) {
48134813
// If `decl` is a nested type, find the parent type.
48144814
Type ParentTy;
48154815
DeclContext *dc = decl->getDeclContext();
4816-
bool isObjCProtocol = isa<ProtocolDecl>(decl) && decl->hasClangNode();
4816+
bool isUnsupportedNestedProtocol =
4817+
isa<ProtocolDecl>(decl) && decl->getParent()->isGenericContext();
48174818

4818-
// Objective-C protocols, unlike Swift protocols, could be nested
4819-
// in other types.
4820-
if ((isObjCProtocol || !isa<ProtocolDecl>(decl)) && dc->isTypeContext()) {
4819+
if (!isUnsupportedNestedProtocol && dc->isTypeContext()) {
48214820
switch (kind) {
48224821
case DeclTypeKind::DeclaredType: {
48234822
if (auto *nominal = dc->getSelfNominalTypeDecl())
@@ -4854,9 +4853,9 @@ static Type computeNominalType(NominalTypeDecl *decl, DeclTypeKind kind) {
48544853
}
48554854

48564855
llvm_unreachable("Unhandled DeclTypeKind in switch.");
4857-
} else {
4858-
return NominalType::get(decl, ParentTy, ctx);
48594856
}
4857+
4858+
return NominalType::get(decl, ParentTy, ctx);
48604859
}
48614860

48624861
Type NominalTypeDecl::getDeclaredType() const {

lib/AST/DeclContext.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ void DeclContext::forEachGenericContext(
150150
if (auto genericCtx = decl->getAsGenericContext())
151151
if (auto *gpList = genericCtx->getGenericParams())
152152
fn(gpList);
153+
154+
// Protocols do not capture outer generic parameters.
155+
if (isa<ProtocolDecl>(decl))
156+
return;
153157
}
154158
} while ((dc = dc->getParentForLookup()));
155159
}
@@ -277,8 +281,8 @@ DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
277281
}
278282

279283
DeclContext *DeclContext::getParentForLookup() const {
280-
if (isa<ProtocolDecl>(this) || isa<ExtensionDecl>(this)) {
281-
// If we are inside a protocol or an extension, skip directly
284+
if (isa<ExtensionDecl>(this)) {
285+
// If we are inside an extension, skip directly
282286
// to the module scope context, without looking at any (invalid)
283287
// outer types.
284288
return getModuleScopeContext();

lib/IRGen/GenDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5600,7 +5600,6 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
56005600
switch (member->getKind()) {
56015601
case DeclKind::Import:
56025602
case DeclKind::TopLevelCode:
5603-
case DeclKind::Protocol:
56045603
case DeclKind::Extension:
56055604
case DeclKind::InfixOperator:
56065605
case DeclKind::PrefixOperator:
@@ -5656,6 +5655,9 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
56565655
case DeclKind::Class:
56575656
emitClassDecl(cast<ClassDecl>(member));
56585657
continue;
5658+
case DeclKind::Protocol:
5659+
emitProtocolDecl(cast<ProtocolDecl>(member));
5660+
continue;
56595661
case DeclKind::MacroExpansion:
56605662
// Expansion already visited as auxiliary decls.
56615663
continue;

lib/Parse/ParseDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9315,7 +9315,7 @@ parseDeclProtocol(ParseDeclOptions Flags, DeclAttributes &Attributes) {
93159315
ProtocolDecl(CurDeclContext, ProtocolLoc, NameLoc, ProtocolName,
93169316
Context.AllocateCopy(PrimaryAssociatedTypeNames),
93179317
Context.AllocateCopy(InheritedProtocols), TrailingWhere);
9318-
// No need to setLocalDiscriminator: protocols can't appear in local contexts.
9318+
recordLocalType(Proto);
93199319

93209320
Proto->getAttrs() = Attributes;
93219321
if (whereClauseHadCodeCompletion && CodeCompletionCallbacks)

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2672,10 +2672,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
26722672
kind.getSelector());
26732673
}
26742674

2675-
// We don't support protocols outside the top level of a file.
2676-
if (isa<ProtocolDecl>(NTD) &&
2677-
!NTD->getParent()->isModuleScopeContext()) {
2678-
NTD->diagnose(diag::unsupported_nested_protocol, NTD);
2675+
// We don't support protocols nested in generic contexts.
2676+
// This includes protocols nested in other protocols.
2677+
if (isa<ProtocolDecl>(NTD) && NTD->getParent()->isGenericContext()) {
2678+
if (auto *OuterPD = NTD->getParent()->getSelfProtocolDecl())
2679+
NTD->diagnose(diag::unsupported_nested_protocol_in_protocol, NTD,
2680+
OuterPD);
2681+
else
2682+
NTD->diagnose(diag::unsupported_nested_protocol_in_generic, NTD);
2683+
26792684
NTD->setInvalid();
26802685
return;
26812686
}
@@ -2688,6 +2693,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
26882693
} else {
26892694
NTD->diagnose(diag::unsupported_type_nested_in_protocol, NTD, proto);
26902695
}
2696+
NTD->setInvalid();
26912697
}
26922698

26932699
// We don't support nested types in generic functions yet.
@@ -2700,6 +2706,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
27002706
} else {
27012707
NTD->diagnose(diag::unsupported_type_nested_in_generic_closure, NTD);
27022708
}
2709+
NTD->setInvalid();
27032710
}
27042711
}
27052712
}
@@ -3178,6 +3185,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
31783185
if (!PD->getPrimaryAssociatedTypeNames().empty())
31793186
(void) PD->getPrimaryAssociatedTypes();
31803187

3188+
// We cannot compute the requirement signature of a protocol
3189+
// in a generic context, because superclass constraints
3190+
// may include implicitly inferred generic arguments that are
3191+
// not part of the protocol's generic signature.
3192+
if (PD->getParent()->isGenericContext()) {
3193+
assert(PD->isInvalid());
3194+
return;
3195+
}
3196+
31813197
// Explicitly compute the requirement signature to detect errors.
31823198
// Do this before visiting members, to avoid a request cycle if
31833199
// a member references another declaration whose generic signature

test/IRGen/protocol_metadata.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,60 @@ protocol Comprehensive {
118118
// CHECK-SAME: %swift.protocol_requirement { i32 4, i32 0 },
119119
// CHECK-SAME: %swift.protocol_requirement { i32 6, i32 0 }
120120

121+
struct ParentType {
122+
123+
// NESTED: [[NESTED_NAME:@.*]] = private constant [7 x i8] c"Nested\00"
124+
// NESTED: [[NESTED_RETURNVALUE_NAME:@.*]] = private constant [12 x i8] c"ReturnValue\00"
125+
126+
// NESTED: @"$s17protocol_metadata10ParentTypeV6NestedMp" = hidden constant
127+
// NESTED-SAME: i32 65603,
128+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeVMn"
129+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 1)
130+
// NESTED-SAME: [7 x i8]* [[NESTED_NAME]]
131+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 2)
132+
// NESTED-SAME: i32 0,
133+
// NESTED-SAME: i32 2,
134+
// NESTED-SAME: [12 x i8]* [[NESTED_RETURNVALUE_NAME]]
135+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 5)
136+
// NESTED-SAME: %swift.protocol_requirement { i32 7, i32 0 },
137+
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
138+
protocol Nested {
139+
associatedtype ReturnValue
140+
func doSomething() -> ReturnValue
141+
}
142+
}
143+
144+
extension ParentType {
145+
146+
// NESTED: [[NESTEDVIAEXT_NAME:@.*]] = private constant [19 x i8] c"NestedViaExtension\00"
147+
148+
// NESTED: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp" = hidden constant
149+
// NESTED-SAME: i32 65603,
150+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeVMn"
151+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp", i32 0, i32 1)
152+
// NESTED-SAME: [19 x i8]* [[NESTEDVIAEXT_NAME]]
153+
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp", i32 0, i32 2)
154+
// NESTED-SAME: i32 0,
155+
// NESTED-SAME: i32 1,
156+
// NESTED-SAME: i32 0,
157+
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
158+
protocol NestedViaExtension {
159+
func foo()
160+
}
161+
}
162+
163+
func parentFunc() {
164+
// NESTED: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp" = internal constant
165+
// NESTED-SAME: i32 65603,
166+
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_PMXX"
167+
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp", i32 0, i32 1)
168+
// NESTED-SAME: [7 x i8]* [[NESTED_NAME]]
169+
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp", i32 0, i32 2)
170+
// NESTED-SAME: i32 0,
171+
// NESTED-SAME: i32 1,
172+
// NESTED-SAME: i32 0,
173+
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
174+
protocol Nested {
175+
func foo()
176+
}
177+
}

test/PrintAsObjC/classes.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ class MyObject : NSObject {}
409409
@objc @objcMembers class DeeperIn {}
410410
}
411411

412+
// CHECK-LABEL: SWIFT_CLASS_NAMED("CustomNameInner")
413+
// CHECK-NEXT: @interface MyInnerClass
414+
// CHECK-NEXT: init
415+
// CHECK-NEXT: @end
416+
@objc(MyInnerClass) @objcMembers class CustomNameInner {}
417+
412418
// CHECK-LABEL: @interface AnotherInner : A1
413419
// CHECK-NEXT: init
414420
// CHECK-NEXT: @end

test/PrintAsObjC/protocols.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,26 @@ extension NSString : A, ZZZ {}
137137
@objc optional func f()
138138
}
139139

140+
// NESTED-LABEL: @interface ParentClass
141+
// NESTED-NEXT: @end
142+
@objc class ParentClass {
143+
144+
// NESTED-LABEL: @protocol Nested
145+
// NESTED-NEXT: @end
146+
@objc protocol Nested {}
147+
148+
// NESTED-LABEL: SWIFT_PROTOCOL_NAMED("Nested2")
149+
// NESTED-NEXT: @protocol NestedInParent
150+
// NESTED-NEXT: @end
151+
@objc(NestedInParent) protocol Nested2 {}
152+
}
153+
154+
extension ParentClass {
155+
// NESTED-LABEL: @protocol NestedInExtensionOfParent
156+
// NESTED-NEXT: @end
157+
@objc protocol NestedInExtensionOfParent {}
158+
}
159+
140160
// NEGATIVE-NOT: @protocol PrivateProto
141161
@objc private protocol PrivateProto {}
142162

test/Runtime/nested-protocols.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN: %target-run-simple-swift
2+
3+
// REQUIRES: executable_test
4+
5+
enum E {
6+
protocol P {}
7+
}
8+
9+
struct Conforms {}
10+
extension Conforms: E.P {}
11+
12+
struct DoesNotConform {}
13+
14+
struct S<T> {}
15+
extension S: E.P where T: E.P {}
16+
17+
@inline(never)
18+
func castToNested(_ value: Any) -> (any E.P)? {
19+
value as? any E.P
20+
}
21+
22+
// Regular conformance.
23+
24+
precondition(castToNested(Conforms()) != nil)
25+
precondition(castToNested(DoesNotConform()) == nil)
26+
27+
// Conditional conformance.
28+
29+
precondition(castToNested(S<Conforms>()) != nil)
30+
precondition(castToNested(S<DoesNotConform>()) == nil)
31+
32+
// Verify that 'E.P' and a non-nested 'P' are different.
33+
34+
protocol P {}
35+
36+
@inline(never)
37+
func castToNonNested(_ value: Any) -> (any P)? {
38+
value as? any P
39+
}
40+
41+
precondition(castToNonNested(Conforms()) == nil)
42+
precondition(castToNonNested(S<Conforms>()) == nil)
43+
precondition(castToNonNested(DoesNotConform()) == nil)
44+
precondition(castToNonNested(S<DoesNotConform>()) == nil)
45+
46+
// Local protocols.
47+
48+
@inline(never)
49+
func f0(_ input: Any) -> (Any, inputConforms: Bool) {
50+
51+
protocol LocalProto { }
52+
struct ConformsToLocal: LocalProto {}
53+
54+
if let input = input as? any LocalProto {
55+
return (input, true)
56+
} else {
57+
return (ConformsToLocal(), false)
58+
}
59+
}
60+
61+
var result = f0(DoesNotConform())
62+
precondition(result.inputConforms == false)
63+
result = f0(result.0)
64+
precondition(result.inputConforms == true)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public struct Outer {
2+
public protocol Inner {
3+
func foo()
4+
}
5+
}
6+
7+
extension Outer {
8+
public protocol InnerInExtension {
9+
func bar()
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -module-name nestedprotocolsource -emit-module-path %t/nestedprotocolsource.swiftmodule -primary-file %S/Inputs/nested-protocols.swift
3+
// RUN: %target-swift-frontend -emit-sil %s -I %t | Filecheck %s
4+
import nestedprotocolsource
5+
6+
// CHECK: usesNested<A>(_:)
7+
// CHECK-NEXT: sil @$s4main10usesNestedyyx20nestedprotocolsource5OuterV5InnerRzlF :
8+
public func usesNested(_ x: some Outer.Inner) {}
9+
10+
// CHECK: usesNestedInExtension<A>(_:)
11+
// CHECK-NEXT: sil @$s4main21usesNestedInExtensionyyx20nestedprotocolsource5OuterV05InnerdE0RzlF :
12+
public func usesNestedInExtension(_ x: some Outer.InnerInExtension) {}

test/decl/inherit/initializer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func testClassInGenericFunc<T>(t: T) {
107107
class A { init(t: T) {} } // expected-error {{type 'A' cannot be nested in generic function 'testClassInGenericFunc(t:)'}}
108108
class B : A {} // expected-error {{type 'B' cannot be nested in generic function 'testClassInGenericFunc(t:)'}}
109109

110-
_ = B(t: t)
110+
_ = B(t: t) // expected-error {{'B' cannot be constructed because it has no accessible initializers}}
111111
}
112112

113113

0 commit comments

Comments
 (0)