Skip to content

Commit 7b68919

Browse files
committed
[SE-0404] Allow protocols to be nested in non-generic contexts
1 parent f7a8bc2 commit 7b68919

File tree

17 files changed

+475
-106
lines changed

17 files changed

+475
-106
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,9 +2215,12 @@ ERROR(unsupported_type_nested_in_protocol,none,
22152215
ERROR(unsupported_type_nested_in_protocol_extension,none,
22162216
"type %0 cannot be nested in protocol extension of %1",
22172217
(const NominalTypeDecl *, const ProtocolDecl *))
2218-
ERROR(unsupported_nested_protocol,none,
2219-
"protocol %0 cannot be nested inside another declaration",
2218+
ERROR(unsupported_nested_protocol_in_generic,none,
2219+
"protocol %0 cannot be nested in a generic context",
22202220
(const NominalTypeDecl *))
2221+
ERROR(unsupported_nested_protocol_in_protocol,none,
2222+
"protocol %0 cannot be nested in protocol %1",
2223+
(const NominalTypeDecl *, const NominalTypeDecl *))
22212224
ERROR(where_nongeneric_ctx,none,
22222225
"'where' clause on non-generic member declaration requires a "
22232226
"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
@@ -193,8 +193,7 @@ void ASTScopeImpl::lookup(const NullablePtr<const ASTScopeImpl> limit,
193193
consumer.startingNextLookupStep();
194194
#endif
195195

196-
// Certain illegal nestings, e.g. protocol nestled inside a struct,
197-
// require that lookup stop at the outer scope.
196+
// Certain illegal nestings require that lookup stop at the outer scope.
198197
if (this == limit.getPtrOrNull()) {
199198
#ifndef NDEBUG
200199
consumer.finishingLookup("limit return");
@@ -558,13 +557,13 @@ GenericTypeOrExtensionScope::getLookupLimitForDecl() const {
558557

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

lib/AST/Decl.cpp

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

4934-
// Objective-C protocols, unlike Swift protocols, could be nested
4935-
// in other types.
4936-
if ((isObjCProtocol || !isa<ProtocolDecl>(decl)) && dc->isTypeContext()) {
4935+
if (!isUnsupportedNestedProtocol && dc->isTypeContext()) {
49374936
switch (kind) {
49384937
case DeclTypeKind::DeclaredType: {
49394938
if (auto *nominal = dc->getSelfNominalTypeDecl())
@@ -4970,9 +4969,9 @@ static Type computeNominalType(NominalTypeDecl *decl, DeclTypeKind kind) {
49704969
}
49714970

49724971
llvm_unreachable("Unhandled DeclTypeKind in switch.");
4973-
} else {
4974-
return NominalType::get(decl, ParentTy, ctx);
49754972
}
4973+
4974+
return NominalType::get(decl, ParentTy, ctx);
49764975
}
49774976

49784977
Type NominalTypeDecl::getDeclaredType() const {

lib/AST/DeclContext.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ void DeclContext::forEachGenericContext(
132132
if (auto genericCtx = decl->getAsGenericContext())
133133
if (auto *gpList = genericCtx->getGenericParams())
134134
fn(gpList);
135+
136+
// Protocols do not capture outer generic parameters.
137+
if (isa<ProtocolDecl>(decl))
138+
return;
135139
}
136140
} while ((dc = dc->getParentForLookup()));
137141
}
@@ -259,8 +263,8 @@ DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
259263
}
260264

261265
DeclContext *DeclContext::getParentForLookup() const {
262-
if (isa<ProtocolDecl>(this) || isa<ExtensionDecl>(this)) {
263-
// If we are inside a protocol or an extension, skip directly
266+
if (isa<ExtensionDecl>(this)) {
267+
// If we are inside an extension, skip directly
264268
// to the module scope context, without looking at any (invalid)
265269
// outer types.
266270
return getModuleScopeContext();

lib/IRGen/GenDecl.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5594,7 +5594,6 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
55945594
switch (member->getKind()) {
55955595
case DeclKind::Import:
55965596
case DeclKind::TopLevelCode:
5597-
case DeclKind::Protocol:
55985597
case DeclKind::Extension:
55995598
case DeclKind::InfixOperator:
56005599
case DeclKind::PrefixOperator:
@@ -5650,6 +5649,9 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
56505649
case DeclKind::Class:
56515650
emitClassDecl(cast<ClassDecl>(member));
56525651
continue;
5652+
case DeclKind::Protocol:
5653+
emitProtocolDecl(cast<ProtocolDecl>(member));
5654+
continue;
56535655
case DeclKind::MacroExpansion:
56545656
// Expansion already visited as auxiliary decls.
56555657
continue;

lib/Parse/ParseDecl.cpp

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

93869386
Proto->getAttrs() = Attributes;
93879387
if (whereClauseHadCodeCompletion && CodeCompletionCallbacks)

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2674,24 +2674,30 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
26742674
NTD->diagnose(diag::tuple_extension_nested_type, NTD);
26752675
return;
26762676
}
2677+
}
26772678

2678-
// We don't support protocols outside the top level of a file.
2679-
if (isa<ProtocolDecl>(NTD) &&
2680-
!DC->isModuleScopeContext()) {
2681-
NTD->diagnose(diag::unsupported_nested_protocol, NTD);
2682-
NTD->setInvalid();
2683-
return;
2684-
}
2679+
// We don't support protocols nested in generic contexts.
2680+
// This includes protocols nested in other protocols.
2681+
if (isa<ProtocolDecl>(NTD) && NTD->getParent()->isGenericContext()) {
2682+
if (auto *OuterPD = NTD->getParent()->getSelfProtocolDecl())
2683+
NTD->diagnose(diag::unsupported_nested_protocol_in_protocol, NTD,
2684+
OuterPD);
2685+
else
2686+
NTD->diagnose(diag::unsupported_nested_protocol_in_generic, NTD);
26852687

2686-
// We don't support nested types in protocols.
2687-
if (auto proto = dyn_cast<ProtocolDecl>(parentDecl)) {
2688-
if (DC->getExtendedProtocolDecl()) {
2689-
NTD->diagnose(diag::unsupported_type_nested_in_protocol_extension, NTD,
2690-
proto);
2691-
} else {
2692-
NTD->diagnose(diag::unsupported_type_nested_in_protocol, NTD, proto);
2693-
}
2688+
NTD->setInvalid();
2689+
return;
2690+
}
2691+
2692+
// We don't support nested types in protocols.
2693+
if (auto proto = dyn_cast<ProtocolDecl>(parentDecl)) {
2694+
if (DC->getExtendedProtocolDecl()) {
2695+
NTD->diagnose(diag::unsupported_type_nested_in_protocol_extension, NTD,
2696+
proto);
2697+
} else {
2698+
NTD->diagnose(diag::unsupported_type_nested_in_protocol, NTD, proto);
26942699
}
2700+
NTD->setInvalid();
26952701
}
26962702

26972703
// We don't support nested types in generic functions yet.
@@ -2704,6 +2710,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
27042710
} else {
27052711
NTD->diagnose(diag::unsupported_type_nested_in_generic_closure, NTD);
27062712
}
2713+
NTD->setInvalid();
27072714
}
27082715
}
27092716
}
@@ -3182,6 +3189,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
31823189
if (!PD->getPrimaryAssociatedTypeNames().empty())
31833190
(void) PD->getPrimaryAssociatedTypes();
31843191

3192+
// We cannot compute the requirement signature of a protocol
3193+
// in a generic context, because superclass constraints
3194+
// may include implicitly inferred generic arguments that are
3195+
// not part of the protocol's generic signature.
3196+
if (PD->getParent()->isGenericContext()) {
3197+
assert(PD->isInvalid());
3198+
return;
3199+
}
3200+
31853201
// Explicitly compute the requirement signature to detect errors.
31863202
// Do this before visiting members, to avoid a request cycle if
31873203
// 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) {}

0 commit comments

Comments
 (0)