Skip to content

[5.10][SE-0404] Allow protocols to be nested in non-generic contexts #69103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2187,9 +2187,12 @@ ERROR(unsupported_type_nested_in_protocol,none,
ERROR(unsupported_type_nested_in_protocol_extension,none,
"type %0 cannot be nested in protocol extension of %1",
(const NominalTypeDecl *, const ProtocolDecl *))
ERROR(unsupported_nested_protocol,none,
"protocol %0 cannot be nested inside another declaration",
ERROR(unsupported_nested_protocol_in_generic,none,
"protocol %0 cannot be nested in a generic context",
(const NominalTypeDecl *))
ERROR(unsupported_nested_protocol_in_protocol,none,
"protocol %0 cannot be nested in protocol %1",
(const NominalTypeDecl *, const NominalTypeDecl *))
ERROR(where_nongeneric_ctx,none,
"'where' clause on non-generic member declaration requires a "
"generic context", ())
Expand Down
1 change: 0 additions & 1 deletion include/swift/AST/TypeMemberVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class TypeMemberVisitor : public DeclVisitor<ImplClass, RetTy> {
}
BAD_MEMBER(Extension)
BAD_MEMBER(Import)
BAD_MEMBER(Protocol)
BAD_MEMBER(TopLevelCode)
BAD_MEMBER(Operator)
BAD_MEMBER(PrecedenceGroup)
Expand Down
17 changes: 8 additions & 9 deletions lib/AST/ASTScopeLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ void ASTScopeImpl::lookup(const NullablePtr<const ASTScopeImpl> limit,
consumer.startingNextLookupStep();
#endif

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

NullablePtr<const ASTScopeImpl>
NominalTypeScope::getLookupLimitForDecl() const {
if (isa<ProtocolDecl>(decl)) {
// ProtocolDecl can only be legally nested in a SourceFile,
// so any other kind of Decl is illegal
return parentIfNotChildOfTopScope();
}
// AFAICT, a struct, decl, or enum can be nested inside anything
// but a ProtocolDecl.
// If a protocol is (invalidly) nested in a generic context,
// do not look in to those outer generic contexts,
// as types found there may contain implicitly inferred generic parameters.
if (isa<ProtocolDecl>(decl) && decl->getDeclContext()->isGenericContext())
return getLookupParent();

// Otherwise, nominals can be nested inside anything but a ProtocolDecl.
return ancestorWithDeclSatisfying(
[&](const Decl *const d) { return isa<ProtocolDecl>(d); });
}
Expand Down
11 changes: 5 additions & 6 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4868,11 +4868,10 @@ static Type computeNominalType(NominalTypeDecl *decl, DeclTypeKind kind) {
// If `decl` is a nested type, find the parent type.
Type ParentTy;
DeclContext *dc = decl->getDeclContext();
bool isObjCProtocol = isa<ProtocolDecl>(decl) && decl->hasClangNode();
bool isUnsupportedNestedProtocol =
isa<ProtocolDecl>(decl) && decl->getParent()->isGenericContext();

// Objective-C protocols, unlike Swift protocols, could be nested
// in other types.
if ((isObjCProtocol || !isa<ProtocolDecl>(decl)) && dc->isTypeContext()) {
if (!isUnsupportedNestedProtocol && dc->isTypeContext()) {
switch (kind) {
case DeclTypeKind::DeclaredType: {
if (auto *nominal = dc->getSelfNominalTypeDecl())
Expand Down Expand Up @@ -4909,9 +4908,9 @@ static Type computeNominalType(NominalTypeDecl *decl, DeclTypeKind kind) {
}

llvm_unreachable("Unhandled DeclTypeKind in switch.");
} else {
return NominalType::get(decl, ParentTy, ctx);
}

return NominalType::get(decl, ParentTy, ctx);
}

Type NominalTypeDecl::getDeclaredType() const {
Expand Down
14 changes: 12 additions & 2 deletions lib/AST/DeclContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ void DeclContext::forEachGenericContext(
if (auto genericCtx = decl->getAsGenericContext())
if (auto *gpList = genericCtx->getGenericParams())
fn(gpList);

// Protocols do not capture outer generic parameters.
if (isa<ProtocolDecl>(decl))
return;
}
} while ((dc = dc->getParentForLookup()));
}
Expand Down Expand Up @@ -259,12 +263,18 @@ DeclContext *DeclContext::getInnermostSkippedFunctionContext() {
}

DeclContext *DeclContext::getParentForLookup() const {
if (isa<ProtocolDecl>(this) || isa<ExtensionDecl>(this)) {
// If we are inside a protocol or an extension, skip directly
if (isa<ExtensionDecl>(this)) {
// If we are inside an extension, skip directly
// to the module scope context, without looking at any (invalid)
// outer types.
return getModuleScopeContext();
}
if (isa<ProtocolDecl>(this) && getParent()->isGenericContext()) {
// Protocols in generic contexts must not look in to their parents,
// as the parents may contain types with inferred implicit
// generic parameters not present in the protocol's generic signature.
return getModuleScopeContext();
}
if (isa<NominalTypeDecl>(this)) {
// If we are inside a nominal type that is inside a protocol,
// skip the protocol.
Expand Down
4 changes: 3 additions & 1 deletion lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5530,7 +5530,6 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
switch (member->getKind()) {
case DeclKind::Import:
case DeclKind::TopLevelCode:
case DeclKind::Protocol:
case DeclKind::Extension:
case DeclKind::InfixOperator:
case DeclKind::PrefixOperator:
Expand Down Expand Up @@ -5586,6 +5585,9 @@ void IRGenModule::emitNestedTypeDecls(DeclRange members) {
case DeclKind::Class:
emitClassDecl(cast<ClassDecl>(member));
continue;
case DeclKind::Protocol:
emitProtocolDecl(cast<ProtocolDecl>(member));
continue;
case DeclKind::MacroExpansion:
// Expansion already visited as auxiliary decls.
continue;
Expand Down
2 changes: 1 addition & 1 deletion lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9241,7 +9241,7 @@ parseDeclProtocol(ParseDeclOptions Flags, DeclAttributes &Attributes) {
ProtocolDecl(CurDeclContext, ProtocolLoc, NameLoc, ProtocolName,
Context.AllocateCopy(PrimaryAssociatedTypeNames),
Context.AllocateCopy(InheritedProtocols), TrailingWhere);
// No need to setLocalDiscriminator: protocols can't appear in local contexts.
recordLocalType(Proto);

Proto->getAttrs() = Attributes;
if (whereClauseHadCodeCompletion && CodeCompletionCallbacks)
Expand Down
15 changes: 11 additions & 4 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2675,10 +2675,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
return;
}

// We don't support protocols outside the top level of a file.
if (isa<ProtocolDecl>(NTD) &&
!DC->isModuleScopeContext()) {
NTD->diagnose(diag::unsupported_nested_protocol, NTD);
// We don't support protocols nested in generic contexts.
// This includes protocols nested in other protocols.
if (isa<ProtocolDecl>(NTD) && DC->isGenericContext()) {
if (auto *OuterPD = DC->getSelfProtocolDecl())
NTD->diagnose(diag::unsupported_nested_protocol_in_protocol, NTD,
OuterPD);
else
NTD->diagnose(diag::unsupported_nested_protocol_in_generic, NTD);

NTD->setInvalid();
return;
}
Expand All @@ -2691,6 +2696,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
} else {
NTD->diagnose(diag::unsupported_type_nested_in_protocol, NTD, proto);
}
NTD->setInvalid();
}
}

Expand All @@ -2704,6 +2710,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
} else {
NTD->diagnose(diag::unsupported_type_nested_in_generic_closure, NTD);
}
NTD->setInvalid();
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions test/IRGen/protocol_metadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,60 @@ protocol Comprehensive {
// CHECK-SAME: %swift.protocol_requirement { i32 4, i32 0 },
// CHECK-SAME: %swift.protocol_requirement { i32 6, i32 0 }

struct ParentType {

// NESTED: [[NESTED_NAME:@.*]] = private constant [7 x i8] c"Nested\00"
// NESTED: [[NESTED_RETURNVALUE_NAME:@.*]] = private constant [12 x i8] c"ReturnValue\00"

// NESTED: @"$s17protocol_metadata10ParentTypeV6NestedMp" = hidden constant
// NESTED-SAME: i32 65603,
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeVMn"
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 1)
// NESTED-SAME: [7 x i8]* [[NESTED_NAME]]
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 2)
// NESTED-SAME: i32 0,
// NESTED-SAME: i32 2,
// NESTED-SAME: [12 x i8]* [[NESTED_RETURNVALUE_NAME]]
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV6NestedMp", i32 0, i32 5)
// NESTED-SAME: %swift.protocol_requirement { i32 7, i32 0 },
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
protocol Nested {
associatedtype ReturnValue
func doSomething() -> ReturnValue
}
}

extension ParentType {

// NESTED: [[NESTEDVIAEXT_NAME:@.*]] = private constant [19 x i8] c"NestedViaExtension\00"

// NESTED: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp" = hidden constant
// NESTED-SAME: i32 65603,
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeVMn"
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp", i32 0, i32 1)
// NESTED-SAME: [19 x i8]* [[NESTEDVIAEXT_NAME]]
// NESTED-SAME: @"$s17protocol_metadata10ParentTypeV18NestedViaExtensionMp", i32 0, i32 2)
// NESTED-SAME: i32 0,
// NESTED-SAME: i32 1,
// NESTED-SAME: i32 0,
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
protocol NestedViaExtension {
func foo()
}
}

func parentFunc() {
// NESTED: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp" = internal constant
// NESTED-SAME: i32 65603,
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_PMXX"
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp", i32 0, i32 1)
// NESTED-SAME: [7 x i8]* [[NESTED_NAME]]
// NESTED-SAME: @"$s17protocol_metadata10parentFuncyyF6NestedL_Mp", i32 0, i32 2)
// NESTED-SAME: i32 0,
// NESTED-SAME: i32 1,
// NESTED-SAME: i32 0,
// NESTED-SAME: %swift.protocol_requirement { i32 17, i32 0 }
protocol Nested {
func foo()
}
}
6 changes: 6 additions & 0 deletions test/PrintAsObjC/classes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@ class MyObject : NSObject {}
@objc @objcMembers class DeeperIn {}
}

// CHECK-LABEL: SWIFT_CLASS_NAMED("CustomNameInner")
// CHECK-NEXT: @interface MyInnerClass
// CHECK-NEXT: init
// CHECK-NEXT: @end
@objc(MyInnerClass) @objcMembers class CustomNameInner {}

// CHECK-LABEL: @interface AnotherInner : A1
// CHECK-NEXT: init
// CHECK-NEXT: @end
Expand Down
20 changes: 20 additions & 0 deletions test/PrintAsObjC/protocols.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ extension NSString : A, ZZZ {}
@objc optional func f()
}

// NESTED-LABEL: @interface ParentClass
// NESTED-NEXT: @end
@objc class ParentClass {

// NESTED-LABEL: @protocol Nested
// NESTED-NEXT: @end
@objc protocol Nested {}

// NESTED-LABEL: SWIFT_PROTOCOL_NAMED("Nested2")
// NESTED-NEXT: @protocol NestedInParent
// NESTED-NEXT: @end
@objc(NestedInParent) protocol Nested2 {}
}

extension ParentClass {
// NESTED-LABEL: @protocol NestedInExtensionOfParent
// NESTED-NEXT: @end
@objc protocol NestedInExtensionOfParent {}
}

// NEGATIVE-NOT: @protocol PrivateProto
@objc private protocol PrivateProto {}

Expand Down
64 changes: 64 additions & 0 deletions test/Runtime/nested-protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// RUN: %target-run-simple-swift

// REQUIRES: executable_test

enum E {
protocol P {}
}

struct Conforms {}
extension Conforms: E.P {}

struct DoesNotConform {}

struct S<T> {}
extension S: E.P where T: E.P {}

@inline(never)
func castToNested(_ value: Any) -> (any E.P)? {
value as? any E.P
}

// Regular conformance.

precondition(castToNested(Conforms()) != nil)
precondition(castToNested(DoesNotConform()) == nil)

// Conditional conformance.

precondition(castToNested(S<Conforms>()) != nil)
precondition(castToNested(S<DoesNotConform>()) == nil)

// Verify that 'E.P' and a non-nested 'P' are different.

protocol P {}

@inline(never)
func castToNonNested(_ value: Any) -> (any P)? {
value as? any P
}

precondition(castToNonNested(Conforms()) == nil)
precondition(castToNonNested(S<Conforms>()) == nil)
precondition(castToNonNested(DoesNotConform()) == nil)
precondition(castToNonNested(S<DoesNotConform>()) == nil)

// Local protocols.

@inline(never)
func f0(_ input: Any) -> (Any, inputConforms: Bool) {

protocol LocalProto { }
struct ConformsToLocal: LocalProto {}

if let input = input as? any LocalProto {
return (input, true)
} else {
return (ConformsToLocal(), false)
}
}

var result = f0(DoesNotConform())
precondition(result.inputConforms == false)
result = f0(result.0)
precondition(result.inputConforms == true)
11 changes: 11 additions & 0 deletions test/Serialization/Inputs/nested-protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
public struct Outer {
public protocol Inner {
func foo()
}
}

extension Outer {
public protocol InnerInExtension {
func bar()
}
}
12 changes: 12 additions & 0 deletions test/Serialization/nested-protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -module-name nestedprotocolsource -emit-module-path %t/nestedprotocolsource.swiftmodule -primary-file %S/Inputs/nested-protocols.swift
// RUN: %target-swift-frontend -emit-sil %s -I %t | %FileCheck %s
import nestedprotocolsource

// CHECK: usesNested<A>(_:)
// CHECK-NEXT: sil @$s4main10usesNestedyyx20nestedprotocolsource5OuterV5InnerRzlF :
public func usesNested(_ x: some Outer.Inner) {}

// CHECK: usesNestedInExtension<A>(_:)
// CHECK-NEXT: sil @$s4main21usesNestedInExtensionyyx20nestedprotocolsource5OuterV05InnerdE0RzlF :
public func usesNestedInExtension(_ x: some Outer.InnerInExtension) {}
2 changes: 1 addition & 1 deletion test/decl/inherit/initializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func testClassInGenericFunc<T>(t: T) {
class A { init(t: T) {} } // expected-error {{type 'A' cannot be nested in generic function 'testClassInGenericFunc(t:)'}}
class B : A {} // expected-error {{type 'B' cannot be nested in generic function 'testClassInGenericFunc(t:)'}}

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


Expand Down
Loading