Skip to content

Commit 278248e

Browse files
authored
Merge pull request #81576 from slavapestov/fix-rdar145184871
Sema: Don't diagnose implied conformance of imported type to Sendable
2 parents 58adb0e + 8b8aafb commit 278248e

File tree

6 files changed

+132
-86
lines changed

6 files changed

+132
-86
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6826,6 +6826,9 @@ static bool checkSendableInstanceStorage(
68266826

68276827
bool swift::checkSendableConformance(
68286828
ProtocolConformance *conformance, SendableCheck check) {
6829+
ASSERT(conformance->getProtocol()->isSpecificProtocol(
6830+
KnownProtocolKind::Sendable));
6831+
68296832
auto conformanceDC = conformance->getDeclContext();
68306833
auto nominal = conformance->getType()->getAnyNominal();
68316834
if (!nominal)
@@ -6915,13 +6918,16 @@ bool swift::checkSendableConformance(
69156918
return false;
69166919
}
69176920

6921+
// An implied conformance is generated when you state a conformance to
6922+
// a protocol P that inherits from Sendable.
6923+
bool wasImplied = (conformance->getSourceKind() ==
6924+
ConformanceEntryKind::Implied);
6925+
69186926
// Sendable can only be used in the same source file.
69196927
auto conformanceDecl = conformanceDC->getAsDecl();
69206928
SendableCheckContext checkContext(conformanceDC, check);
69216929
DiagnosticBehavior behavior = checkContext.defaultDiagnosticBehavior();
6922-
if (conformance->getSourceKind() == ConformanceEntryKind::Implied &&
6923-
conformance->getProtocol()->isSpecificProtocol(
6924-
KnownProtocolKind::Sendable)) {
6930+
if (wasImplied) {
69256931
if (auto optBehavior = checkContext.preconcurrencyBehavior(
69266932
nominal, /*ignoreExplicitConformance=*/true))
69276933
behavior = *optBehavior;
@@ -6930,12 +6936,14 @@ bool swift::checkSendableConformance(
69306936
if (conformanceDC->getOutermostParentSourceFile() &&
69316937
conformanceDC->getOutermostParentSourceFile() !=
69326938
nominal->getOutermostParentSourceFile()) {
6933-
conformanceDecl->diagnose(diag::concurrent_value_outside_source_file,
6934-
nominal)
6935-
.limitBehaviorUntilSwiftVersion(behavior, 6);
6939+
if (!(nominal->hasClangNode() && wasImplied)) {
6940+
conformanceDecl->diagnose(diag::concurrent_value_outside_source_file,
6941+
nominal)
6942+
.limitBehaviorUntilSwiftVersion(behavior, 6);
69366943

6937-
if (behavior == DiagnosticBehavior::Unspecified)
6938-
return true;
6944+
if (behavior == DiagnosticBehavior::Unspecified)
6945+
return true;
6946+
}
69396947
}
69406948

69416949
if (classDecl && classDecl->getParentSourceFile()) {
@@ -6973,7 +6981,7 @@ bool swift::checkSendableConformance(
69736981
// a Sendable conformance. The implied conformance is unconditional, so check
69746982
// the storage for sendability as if the conformance was declared on the nominal,
69756983
// and not some (possibly constrained) extension.
6976-
if (conformance->getSourceKind() == ConformanceEntryKind::Implied)
6984+
if (wasImplied)
69776985
conformanceDC = nominal;
69786986
return checkSendableInstanceStorage(nominal, conformanceDC, check);
69796987
}

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 86 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1823,106 +1823,120 @@ static void diagnoseRetroactiveConformances(
18231823
return;
18241824
}
18251825

1826-
Type extendedType = ext->getExtendedType();
18271826
NominalTypeDecl *extendedNominalDecl = ext->getExtendedNominal();
1828-
if (!extendedNominalDecl) {
1827+
if (!extendedNominalDecl || isa<BuiltinTupleDecl>(extendedNominalDecl))
18291828
return;
1830-
}
18311829

18321830
ModuleDecl *extTypeModule = extendedNominalDecl->getParentModule();
18331831

18341832
// If the type comes from the __ObjC clang header module, don't warn.
1835-
if (extTypeModule->getName().is(CLANG_HEADER_MODULE_NAME)) {
1833+
if (extTypeModule->getName().is(CLANG_HEADER_MODULE_NAME))
18361834
return;
1837-
}
18381835

18391836
// At this point, we know we're extending a type declared outside this module.
18401837
// We better only be conforming it to protocols declared within this module.
1841-
llvm::SmallSetVector<ProtocolDecl *, 8> externalProtocols;
1838+
llvm::SmallMapVector<ProtocolDecl *, bool, 8> protocols;
18421839
llvm::SmallSet<ProtocolDecl *, 8> protocolsWithRetroactiveAttr;
1843-
for (const InheritedEntry &entry : ext->getInherited().getEntries()) {
1844-
if (entry.getType().isNull() || !entry.getTypeRepr()) {
1845-
continue;
1840+
1841+
for (auto *conformance : ext->getLocalConformances()) {
1842+
auto *proto = conformance->getProtocol();
1843+
bool inserted = protocols.insert(std::make_pair(
1844+
proto, conformance->isRetroactive())).second;
1845+
ASSERT(inserted);
1846+
1847+
if (proto->isSpecificProtocol(KnownProtocolKind::SendableMetatype)) {
1848+
protocolsWithRetroactiveAttr.insert(proto);
18461849
}
18471850

1848-
auto proto =
1849-
dyn_cast_or_null<ProtocolDecl>(entry.getType()->getAnyNominal());
1850-
if (!proto) {
1851-
continue;
1851+
// Implied conformance to Sendable is a special case that should not be
1852+
// diagnosed. Pretend it's always @retroactive.
1853+
if (conformance->getSourceKind() == ConformanceEntryKind::Implied &&
1854+
proto->isSpecificProtocol(KnownProtocolKind::Sendable) &&
1855+
extendedNominalDecl->hasClangNode()) {
1856+
protocolsWithRetroactiveAttr.insert(proto);
18521857
}
1853-
1854-
// As a fallback, to support previous language versions, also allow
1855-
// this through if the protocol has been explicitly module-qualified.
1856-
TypeRepr *repr = unwrapAttributedRepr(entry.getTypeRepr());
1857-
if (isModuleQualified(repr, proto->getParentModule())) {
1858+
}
1859+
1860+
for (const InheritedEntry &entry : ext->getInherited().getEntries()) {
1861+
auto inheritedTy = entry.getType();
1862+
if (inheritedTy.isNull() || !entry.getTypeRepr()) {
18581863
continue;
18591864
}
18601865

1861-
proto->walkInheritedProtocols([&](ProtocolDecl *decl) {
1862-
1863-
// Get the original conformance of the extended type to this protocol.
1864-
auto conformanceRef = lookupConformance(extendedType, decl);
1865-
if (!conformanceRef.isConcrete()) {
1866-
return TypeWalker::Action::Continue;
1867-
}
1868-
auto conformance = conformanceRef.getConcrete();
1866+
SmallVector<ProtocolDecl *, 2> protos;
1867+
if (auto *protoTy = inheritedTy->getAs<ProtocolType>()) {
1868+
auto *proto = protoTy->getDecl();
18691869

1870-
// If that conformance came from this extension, then we warn. Otherwise
1871-
// we will have diagnosed it on the extension that actually declares this
1872-
// specific conformance.
1873-
if (conformance->getDeclContext() != ext) {
1874-
return TypeWalker::Action::Continue;
1870+
// As a fallback, to support previous language versions, also allow
1871+
// this through if the protocol has been explicitly module-qualified.
1872+
TypeRepr *repr = unwrapAttributedRepr(entry.getTypeRepr());
1873+
if (isModuleQualified(repr, proto->getParentModule())) {
1874+
protocolsWithRetroactiveAttr.insert(proto);
1875+
continue;
18751876
}
18761877

1877-
// If this isn't a retroactive conformance, skip it.
1878-
if (!conformance->isRetroactive()) {
1879-
// However, if this is the protocol in the inherited type entry,
1880-
// check to make sure it's not erroneously marked @retroactive when it's
1881-
// not actually retroactive.
1882-
if (decl == proto && entry.isRetroactive()) {
1883-
auto loc =
1884-
entry.getTypeRepr()->findAttrLoc(TypeAttrKind::Retroactive);
1885-
1886-
bool typeInSamePackage = extTypeModule->inSamePackage(module);
1887-
bool typeIsSameModule =
1888-
extTypeModule->isSameModuleLookingThroughOverlays(module);
1889-
1890-
auto declForDiag = (typeIsSameModule || typeInSamePackage)
1891-
? extendedNominalDecl
1892-
: proto;
1893-
bool isSameModule = declForDiag->getParentModule()
1894-
->isSameModuleLookingThroughOverlays(module);
1895-
1896-
diags
1897-
.diagnose(loc, diag::retroactive_attr_does_not_apply, declForDiag,
1898-
isSameModule)
1899-
.warnUntilSwiftVersion(6)
1900-
.fixItRemove(SourceRange(loc, loc.getAdvancedLoc(1)));
1901-
return TypeWalker::Action::Stop;
1878+
protos.push_back(proto);
1879+
} else if (auto *compositionTy = inheritedTy->getAs<ProtocolCompositionType>()) {
1880+
for (auto memberTy : compositionTy->getMembers()) {
1881+
if (auto *protoTy = memberTy->getAs<ProtocolType>()) {
1882+
protos.push_back(protoTy->getDecl());
19021883
}
1903-
return TypeWalker::Action::Continue;
19041884
}
1885+
} else {
1886+
continue;
1887+
}
19051888

1906-
// If it's marked @retroactive, no need to warn.
1907-
if (entry.isRetroactive()) {
1908-
// Note that we encountered this protocol through a conformance marked
1909-
// @retroactive in case multiple clauses cause the protocol to be
1910-
// inherited.
1911-
protocolsWithRetroactiveAttr.insert(decl);
1912-
return TypeWalker::Action::Continue;
1913-
}
1889+
for (auto *proto : protos) {
1890+
proto->walkInheritedProtocols([&](ProtocolDecl *decl) {
1891+
// If this isn't a retroactive conformance, skip it.
1892+
auto found = protocols.find(proto);
1893+
if (found != protocols.end() && !found->second) {
1894+
// However, if this is the protocol in the inherited type entry,
1895+
// check to make sure it's not erroneously marked @retroactive when it's
1896+
// not actually retroactive.
1897+
if (decl == proto && entry.isRetroactive()) {
1898+
auto loc =
1899+
entry.getTypeRepr()->findAttrLoc(TypeAttrKind::Retroactive);
1900+
1901+
bool typeInSamePackage = extTypeModule->inSamePackage(module);
1902+
bool typeIsSameModule =
1903+
extTypeModule->isSameModuleLookingThroughOverlays(module);
1904+
1905+
auto declForDiag = (typeIsSameModule || typeInSamePackage)
1906+
? extendedNominalDecl
1907+
: proto;
1908+
bool isSameModule = declForDiag->getParentModule()
1909+
->isSameModuleLookingThroughOverlays(module);
1910+
1911+
diags
1912+
.diagnose(loc, diag::retroactive_attr_does_not_apply, declForDiag,
1913+
isSameModule)
1914+
.warnUntilSwiftVersion(6)
1915+
.fixItRemove(SourceRange(loc, loc.getAdvancedLoc(1)));
1916+
return TypeWalker::Action::Stop;
1917+
}
1918+
return TypeWalker::Action::Continue;
1919+
}
19141920

1915-
// If we've come this far, we know this extension is the first declaration
1916-
// of the conformance of the extended type to this protocol.
1917-
externalProtocols.insert(decl);
1921+
// If it's marked @retroactive, no need to warn.
1922+
if (entry.isRetroactive()) {
1923+
// Note that we encountered this protocol through a conformance marked
1924+
// @retroactive in case multiple clauses cause the protocol to be
1925+
// inherited.
1926+
protocolsWithRetroactiveAttr.insert(decl);
1927+
return TypeWalker::Action::Continue;
1928+
}
19181929

1919-
return TypeWalker::Action::Continue;
1920-
});
1930+
return TypeWalker::Action::Continue;
1931+
});
1932+
}
19211933
}
19221934

19231935
// Remove protocols that are reachable through a @retroactive conformance.
1924-
for (auto *proto : protocolsWithRetroactiveAttr) {
1925-
externalProtocols.remove(proto);
1936+
SmallSetVector<ProtocolDecl *, 4> externalProtocols;
1937+
for (auto pair : protocols) {
1938+
if (pair.second && !protocolsWithRetroactiveAttr.count(pair.first))
1939+
externalProtocols.insert(pair.first);
19261940
}
19271941

19281942
// If we didn't find any violations, we're done.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-sil -o /dev/null -I %S/Inputs/custom-modules %s -verify -parse-as-library -swift-version 6
2+
3+
// REQUIRES: objc_interop
4+
// REQUIRES: concurrency
5+
6+
import Foundation
7+
8+
extension CGRect: Sendable {}
9+
// expected-warning@-1 {{extension declares a conformance of imported type 'CGRect' to imported protocol 'Sendable'; this will not behave correctly if the owners of 'CoreGraphics' introduce this conformance in the future}}
10+
// expected-note@-2 {{add '@retroactive' to silence this warning}}
11+
// expected-error@-3 {{conformance to 'Sendable' must occur in the same source file as struct 'CGRect'; use '@unchecked Sendable' for retroactive conformance}}
12+
protocol P: Sendable {}
13+
14+
extension CGPoint: P {}
15+

test/Inputs/clang-importer-sdk/swift-modules/CoreFoundation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
protocol _CFObject: Hashable {}
44

55
#if CGFLOAT_IN_COREFOUNDATION
6-
public struct CGFloat {
6+
public struct CGFloat: @unchecked Sendable {
77
#if _pointerBitWidth(_32)
88
public typealias UnderlyingType = Float
99
#elseif _pointerBitWidth(_64)

test/Inputs/clang-importer-sdk/swift-modules/CoreGraphics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public func == (lhs: CGPoint, rhs: CGPoint) -> Bool {
66
}
77

88
#if !CGFLOAT_IN_COREFOUNDATION
9-
public struct CGFloat {
9+
public struct CGFloat: Sendable {
1010
#if _pointerBitWidth(_32)
1111
public typealias UnderlyingType = Float
1212
#elseif _pointerBitWidth(_64)

test/Sema/extension_retroactive_conformances.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public struct Sample3 {}
2020
public struct Sample4 {}
2121
public struct Sample5 {}
2222
public struct Sample6 {}
23+
public struct Sample7 {}
24+
public struct Sample8 {}
2325

2426
public struct SampleAlreadyConforms: SampleProtocol1 {}
2527

@@ -83,9 +85,9 @@ protocol ClientProtocol {}
8385
// ok, conforming a type from another module to a protocol within this module is totally fine
8486
extension Sample1: ClientProtocol {}
8587

86-
struct Sample7: @retroactive SampleProtocol1 {} // expected-error {{'@retroactive' only applies in inheritance clauses in extensions}}{{17-30=}}
88+
struct MySample7: @retroactive SampleProtocol1 {} // expected-error {{'@retroactive' only applies in inheritance clauses in extensions}}{{19-32=}}
8789

88-
extension Sample7: @retroactive ClientProtocol {} // expected-warning {{'retroactive' attribute does not apply; 'Sample7' is declared in this module}}{{20-33=}}
90+
extension MySample7: @retroactive ClientProtocol {} // expected-warning {{'retroactive' attribute does not apply; 'MySample7' is declared in this module}}{{22-35=}}
8991

9092
extension Int: @retroactive ClientProtocol {} // expected-warning {{'retroactive' attribute does not apply; 'ClientProtocol' is declared in this module}}{{16-29=}}
9193

@@ -109,4 +111,11 @@ extension GenericSample1: SampleProtocol1 where T: SampleProtocol1 {}
109111
// expected-warning@-1 {{extension declares a conformance of imported type 'GenericSample1' to imported protocol 'SampleProtocol1'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}}
110112
// expected-note@-2 {{add '@retroactive' to silence this warning}}
111113

112-
#endif
114+
// Don't forget about protocol compositions
115+
extension Sample7: SampleProtocol1 & SampleProtocol2 {}
116+
// expected-warning@-1 {{extension declares a conformance of imported type 'Sample7' to imported protocols 'SampleProtocol1', 'SampleProtocol2'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}}
117+
// expected-note@-2 {{add '@retroactive' to silence this warning}}
118+
119+
extension Sample8: @retroactive SampleProtocol1 & SampleProtocol2 {} // ok
120+
121+
#endif

0 commit comments

Comments
 (0)