Skip to content

Commit 0c44681

Browse files
authored
Merge pull request #81694 from slavapestov/fix-rdar145184871-6.2
[6.2] Sema: Don't diagnose implied conformance of imported type to Sendable
2 parents e2ee46d + 58a9435 commit 0c44681

File tree

6 files changed

+128
-85
lines changed

6 files changed

+128
-85
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6934,13 +6934,16 @@ bool swift::checkSendableConformance(
69346934
return false;
69356935
}
69366936

6937+
// An implied conformance is generated when you state a conformance to
6938+
// a protocol P that inherits from Sendable.
6939+
bool wasImplied = (conformance->getSourceKind() ==
6940+
ConformanceEntryKind::Implied);
6941+
69376942
// Sendable can only be used in the same source file.
69386943
auto conformanceDecl = conformanceDC->getAsDecl();
69396944
SendableCheckContext checkContext(conformanceDC, check);
69406945
DiagnosticBehavior behavior = checkContext.defaultDiagnosticBehavior();
6941-
if (conformance->getSourceKind() == ConformanceEntryKind::Implied &&
6942-
conformance->getProtocol()->isSpecificProtocol(
6943-
KnownProtocolKind::Sendable)) {
6946+
if (wasImplied) {
69446947
if (auto optBehavior = checkContext.preconcurrencyBehavior(
69456948
nominal, /*ignoreExplicitConformance=*/true))
69466949
behavior = *optBehavior;
@@ -6949,12 +6952,14 @@ bool swift::checkSendableConformance(
69496952
if (conformanceDC->getOutermostParentSourceFile() &&
69506953
conformanceDC->getOutermostParentSourceFile() !=
69516954
nominal->getOutermostParentSourceFile()) {
6952-
conformanceDecl->diagnose(diag::concurrent_value_outside_source_file,
6953-
nominal)
6954-
.limitBehaviorUntilSwiftVersion(behavior, 6);
6955+
if (!(nominal->hasClangNode() && wasImplied)) {
6956+
conformanceDecl->diagnose(diag::concurrent_value_outside_source_file,
6957+
nominal)
6958+
.limitBehaviorUntilSwiftVersion(behavior, 6);
69556959

6956-
if (behavior == DiagnosticBehavior::Unspecified)
6957-
return true;
6960+
if (behavior == DiagnosticBehavior::Unspecified)
6961+
return true;
6962+
}
69586963
}
69596964

69606965
if (classDecl && classDecl->getParentSourceFile()) {
@@ -6992,7 +6997,7 @@ bool swift::checkSendableConformance(
69926997
// a Sendable conformance. The implied conformance is unconditional, so check
69936998
// the storage for sendability as if the conformance was declared on the nominal,
69946999
// and not some (possibly constrained) extension.
6995-
if (conformance->getSourceKind() == ConformanceEntryKind::Implied)
7000+
if (wasImplied)
69967001
conformanceDC = nominal;
69977002
return checkSendableInstanceStorage(nominal, conformanceDC, check);
69987003
}

lib/Sema/TypeCheckDeclPrimary.cpp

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

1827-
Type extendedType = ext->getExtendedType();
18281827
NominalTypeDecl *extendedNominalDecl = ext->getExtendedNominal();
1829-
if (!extendedNominalDecl) {
1828+
if (!extendedNominalDecl || isa<BuiltinTupleDecl>(extendedNominalDecl))
18301829
return;
1831-
}
18321830

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

18351833
// If the type comes from the __ObjC clang header module, don't warn.
1836-
if (extTypeModule->getName().is(CLANG_HEADER_MODULE_NAME)) {
1834+
if (extTypeModule->getName().is(CLANG_HEADER_MODULE_NAME))
18371835
return;
1838-
}
18391836

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

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

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

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

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

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

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

1920-
return TypeWalker::Action::Continue;
1921-
});
1931+
return TypeWalker::Action::Continue;
1932+
});
1933+
}
19221934
}
19231935

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

19291943
// 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: 11 additions & 2 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' attribute only applies in inheritance clauses in extensions}}{{17-30=}}
88+
struct MySample7: @retroactive SampleProtocol1 {} // expected-error {{'retroactive' attribute only applies in inheritance clauses in extensions}}
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

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+
112121
#endif

0 commit comments

Comments
 (0)