Skip to content

Commit 0dcb936

Browse files
Serialize extended nominal separately when serializing an extension.
Instead of computing it from the extended type after deserialization -- which is tricky to do, due to potential presence of protocol compositions -- we obtain the extended nominal directly. Fixes SR-11227 and linked rdar://problem/53712389.
1 parent 968c562 commit 0dcb936

File tree

10 files changed

+106
-35
lines changed

10 files changed

+106
-35
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,11 @@ ERROR(inferred_opaque_type,none,
16011601
// Extensions
16021602
ERROR(non_nominal_extension,none,
16031603
"non-nominal type %0 cannot be extended", (Type))
1604+
WARNING(composition_in_extended_type,none,
1605+
"extending a protocol composition is not supported; extending %0 "
1606+
"instead", (Type))
1607+
NOTE(composition_in_extended_type_alternative,none,
1608+
"did you mean to extend the most specific type %0 instead?", (Type))
16041609
ERROR(extension_access_with_conformances,none,
16051610
"%0 modifier cannot be used with extensions that declare "
16061611
"protocol conformances", (DeclAttribute))

lib/AST/NameLookup.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2142,7 +2142,10 @@ ExtendedNominalRequest::evaluate(Evaluator &evaluator,
21422142
auto nominalTypes
21432143
= resolveTypeDeclsToNominal(evaluator, ctx, referenced, modulesFound,
21442144
anyObject);
2145-
return nominalTypes.empty() ? nullptr : nominalTypes.front();
2145+
2146+
// If there is more than 1 element, we will emit a warning or an error
2147+
// elsewhere, so don't handle that case here.
2148+
return nominalTypes.empty() ? nullptr : nominalTypes[0];
21462149
}
21472150

21482151
llvm::Expected<NominalTypeDecl *>

lib/Sema/TypeCheckDecl.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3083,6 +3083,41 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
30833083

30843084
TC.validateExtension(ED);
30853085

3086+
extType = ED->getExtendedType();
3087+
if (extType && !extType->hasError()) {
3088+
// The first condition catches syntactic forms like
3089+
// protocol A & B { ... } // may be protocols or typealiases
3090+
// The second condition also looks through typealiases and catches
3091+
// typealias T = P1 & P2 // P2 is a refined protocol of P1
3092+
// extension T { ... }
3093+
// However, it is trickier to catch cases like
3094+
// typealias T = P2 & P1 // P2 is a refined protocol of P1
3095+
// extension T { ... }
3096+
// so we don't do that here.
3097+
auto extTypeRepr = ED->getExtendedTypeRepr();
3098+
auto *extTypeNominal = extType->getAnyNominal();
3099+
bool firstNominalIsNotMostSpecific =
3100+
extTypeNominal && extTypeNominal != nominal;
3101+
if (isa<CompositionTypeRepr>(extTypeRepr)
3102+
|| firstNominalIsNotMostSpecific) {
3103+
auto firstNominalType = nominal->getDeclaredType();
3104+
auto diag = ED->diagnose(diag::composition_in_extended_type,
3105+
firstNominalType);
3106+
diag.highlight(extTypeRepr->getSourceRange());
3107+
if (firstNominalIsNotMostSpecific) {
3108+
diag.flush();
3109+
Type mostSpecificProtocol = extTypeNominal->getDeclaredType();
3110+
ED->diagnose(diag::composition_in_extended_type_alternative,
3111+
mostSpecificProtocol)
3112+
.fixItReplace(extTypeRepr->getSourceRange(),
3113+
mostSpecificProtocol->getString());
3114+
} else {
3115+
diag.fixItReplace(extTypeRepr->getSourceRange(),
3116+
firstNominalType->getString());
3117+
}
3118+
}
3119+
}
3120+
30863121
checkInheritanceClause(ED);
30873122

30883123
// Check the raw values of an enum, since we might synthesize

lib/Serialization/Deserialization.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/AST/ForeignErrorConvention.h"
2121
#include "swift/AST/GenericEnvironment.h"
2222
#include "swift/AST/Initializer.h"
23+
#include "swift/AST/NameLookupRequests.h"
2324
#include "swift/AST/Pattern.h"
2425
#include "swift/AST/ParameterList.h"
2526
#include "swift/AST/PrettyStackTrace.h"
@@ -3689,13 +3690,15 @@ class swift::DeclDeserializer {
36893690
Expected<Decl *> deserializeExtension(ArrayRef<uint64_t> scratch,
36903691
StringRef blobData) {
36913692
TypeID extendedTypeID;
3693+
DeclID extendedNominalID;
36923694
DeclContextID contextID;
36933695
bool isImplicit;
36943696
GenericSignatureID genericEnvID;
36953697
unsigned numConformances, numInherited;
36963698
ArrayRef<uint64_t> inheritedAndDependencyIDs;
36973699

3698-
decls_block::ExtensionLayout::readRecord(scratch, extendedTypeID, contextID,
3700+
decls_block::ExtensionLayout::readRecord(scratch, extendedTypeID,
3701+
extendedNominalID, contextID,
36993702
isImplicit, genericEnvID,
37003703
numConformances, numInherited,
37013704
inheritedAndDependencyIDs);
@@ -3735,7 +3738,9 @@ class swift::DeclDeserializer {
37353738
auto extendedType = MF.getType(extendedTypeID);
37363739
ctx.evaluator.cacheOutput(ExtendedTypeRequest{extension},
37373740
std::move(extendedType));
3738-
auto nominal = extension->getExtendedNominal();
3741+
auto nominal = dyn_cast<NominalTypeDecl>(MF.getDecl(extendedNominalID));
3742+
ctx.evaluator.cacheOutput(ExtendedNominalRequest{extension},
3743+
std::move(nominal));
37393744

37403745
if (isImplicit)
37413746
extension->setImplicit();

lib/Serialization/ModuleFormat.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5252
/// describe what change you made. The content of this comment isn't important;
5353
/// it just ensures a conflict if two people change the module format.
5454
/// Don't worry about adhering to the 80-column limit for this line.
55-
const uint16_t SWIFTMODULE_VERSION_MINOR = 517; // better string hash seed
55+
const uint16_t SWIFTMODULE_VERSION_MINOR = 518; // save extended nominal separately when serializing extensions
5656

5757
/// A standard hash seed used for all string hashes in a serialized module.
5858
///
@@ -1284,7 +1284,8 @@ namespace decls_block {
12841284

12851285
using ExtensionLayout = BCRecordLayout<
12861286
EXTENSION_DECL,
1287-
TypeIDField, // base type
1287+
TypeIDField, // extended type
1288+
DeclIDField, // extended nominal
12881289
DeclContextIDField, // context decl
12891290
BCFixed<1>, // implicit flag
12901291
GenericSignatureIDField, // generic environment

lib/Serialization/Serialization.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2654,11 +2654,6 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
26542654
// simpler user model to just always desugar extension types.
26552655
extendedType = extendedType->getCanonicalType();
26562656

2657-
// Make sure the base type has registered itself as a provider of generic
2658-
// parameters.
2659-
auto firstNominalInExtendedType = extendedType->getAnyNominal();
2660-
(void)S.addDeclRef(firstNominalInExtendedType);
2661-
26622657
auto conformances = extension->getLocalConformances(
26632658
ConformanceLookupKind::All, nullptr);
26642659

@@ -2680,8 +2675,10 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
26802675
inheritedAndDependencyTypes.push_back(S.addTypeRef(dependencyTy));
26812676

26822677
unsigned abbrCode = S.DeclTypeAbbrCodes[ExtensionLayout::Code];
2678+
auto extendedNominal = extension->getExtendedNominal();
26832679
ExtensionLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode,
26842680
S.addTypeRef(extendedType),
2681+
S.addDeclRef(extendedNominal),
26852682
contextID.getOpaqueValue(),
26862683
extension->isImplicit(),
26872684
S.addGenericEnvironmentRef(
@@ -2691,9 +2688,9 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
26912688
inheritedAndDependencyTypes);
26922689

26932690
bool isClassExtension = false;
2694-
if (firstNominalInExtendedType) {
2695-
isClassExtension = isa<ClassDecl>(firstNominalInExtendedType) ||
2696-
isa<ProtocolDecl>(firstNominalInExtendedType);
2691+
if (extendedNominal) {
2692+
isClassExtension = isa<ClassDecl>(extendedNominal) ||
2693+
isa<ProtocolDecl>(extendedNominal);
26972694
}
26982695

26992696
// Extensions of nested generic types have multiple generic parameter

test/Sema/composition_extension.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// RUN: %target-typecheck-verify-swift
2+
// RUN: %empty-directory(%t)
3+
// RUN: %target-swift-frontend -primary-file %s %S/Inputs/composition_extension_usage.swift -emit-module-path %t/P-partial.swiftmodule -module-name SR11227 -enable-testing
4+
// RUN: %target-swift-frontend -primary-file %S/Inputs/composition_extension_usage.swift %s -emit-module-path %t/S-partial.swiftmodule -module-name SR11227 -enable-testing
5+
// RUN: %target-swift-frontend -sil-merge-partial-modules %t/P-partial.swiftmodule %t/S-partial.swiftmodule -emit-module -o %t/SR11227.swiftmodule
6+
7+
protocol P1 {}
8+
9+
protocol P1_1 : P1 {
10+
func p1_1()
11+
}
12+
13+
protocol P2 {}
14+
15+
extension P1 & P1_1 where Self : P2 {
16+
// expected-warning@-1 {{extending a protocol composition is not supported; extending 'P1' instead}}
17+
// expected-note@-2 {{did you mean to extend the most specific type 'P1_1' instead?}} {{11-20=P1_1}}
18+
func p1_1() {}
19+
}
20+
21+
extension P1_1 & P1 where Self : P2 {
22+
// expected-warning@-1 {{extending a protocol composition is not supported; extending 'P1_1' instead}} {{11-20=P1_1}}
23+
func p1_1_alt() {}
24+
}
25+
26+
typealias T1 = P1 & P1_1
27+
28+
extension T1 {
29+
// expected-warning@-1 {{extending a protocol composition is not supported; extending 'P1' instead}}
30+
// expected-note@-2 {{did you mean to extend the most specific type 'P1_1' instead?}} {{11-13=P1_1}}
31+
func t1() {}
32+
}
33+
34+
typealias T2 = T1
35+
36+
extension T2 {
37+
// expected-warning@-1 {{extending a protocol composition is not supported; extending 'P1' instead}}
38+
// expected-note@-2 {{did you mean to extend the most specific type 'P1_1' instead?}} {{11-13=P1_1}}
39+
func t2() {}
40+
}
41+
42+
typealias T3 = P1_1 & P1
43+
44+
extension T3 { // Ideally, we should emit a warning here but the current implementation doesn't do that.
45+
func t3() {}
46+
}

test/Sema/composition_extensions.swift

Lines changed: 0 additions & 19 deletions
This file was deleted.

test/decl/ext/protocol.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -991,9 +991,7 @@ class BadClass5 : BadProto5 {} // expected-error{{type 'BadClass5' does not conf
991991
typealias A = BadProto1
992992
typealias B = BadProto1
993993

994-
extension A & B { // okay
995-
996-
}
994+
extension A & B {} // expected-warning {{extending a protocol composition is not supported; extending 'BadProto1' instead}}
997995

998996
// Suppress near-miss warning for unlabeled initializers.
999997
protocol P9 {

0 commit comments

Comments
 (0)