Skip to content

Commit fe1c4a4

Browse files
authored
Merge pull request #34651 from slavapestov/assoc-conformance-availability
Check conformance availability for associated conformances
2 parents 86a8d57 + db394c5 commit fe1c4a4

9 files changed

+205
-117
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,12 +2758,8 @@ ERROR(conformance_from_implementation_only_module,none,
27582758
"the conformance is declared as SPI in %3|"
27592759
"the conformance is declared as SPI}4",
27602760
(Type, Identifier, unsigned, Identifier, unsigned))
2761-
ERROR(assoc_conformance_from_implementation_only_module,none,
2762-
"cannot use conformance of %0 to %1 in associated type %3 (inferred as "
2763-
"%4); %select{%2 has been imported as implementation-only|"
2764-
"the conformance is declared as SPI in %2|"
2765-
"the conformance is declared as SPI}5",
2766-
(Type, Identifier, Identifier, Type, Type, unsigned))
2761+
NOTE(assoc_conformance_from_implementation_only_module,none,
2762+
"in associated type %0 (inferred as %1)", (Type, Type))
27672763
ERROR(unexportable_clang_function_type,none,
27682764
"cannot export the underlying C type of the function type %0; "
27692765
"it may use anonymous types or types defined outside of a module",

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,17 @@ ExportContext ExportContext::forFunctionBody(DeclContext *DC, SourceLoc loc) {
222222
unavailablePlatformKind);
223223
}
224224

225+
ExportContext ExportContext::forConformance(DeclContext *DC,
226+
ProtocolDecl *proto) {
227+
assert(isa<ExtensionDecl>(DC) || isa<NominalTypeDecl>(DC));
228+
auto where = forDeclSignature(DC->getInnermostDeclarationDeclContext());
229+
230+
where.Exported &= proto->getFormalAccessScope(
231+
DC, /*usableFromInlineAsPublic*/true).isPublic();
232+
233+
return where;
234+
}
235+
225236
ExportContext ExportContext::withReason(ExportabilityReason reason) const {
226237
auto copy = *this;
227238
copy.Reason = unsigned(reason);
@@ -1226,7 +1237,8 @@ static const Decl *ancestorMemberLevelDeclForAvailabilityFixit(const Decl *D) {
12261237
while (D) {
12271238
D = relatedDeclForAvailabilityFixit(D);
12281239

1229-
if (D->getDeclContext()->isTypeContext() &&
1240+
if (!D->isImplicit() &&
1241+
D->getDeclContext()->isTypeContext() &&
12301242
DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind::DAK_Available,
12311243
D)) {
12321244
break;
@@ -2172,20 +2184,19 @@ void TypeChecker::diagnoseIfDeprecated(SourceRange ReferenceRange,
21722184
}
21732185
}
21742186

2175-
void TypeChecker::diagnoseIfDeprecated(
2176-
SourceLoc loc,
2177-
const RootProtocolConformance *rootConf,
2178-
const ExtensionDecl *ext,
2179-
const ExportContext &where) {
2187+
bool TypeChecker::diagnoseIfDeprecated(SourceLoc loc,
2188+
const RootProtocolConformance *rootConf,
2189+
const ExtensionDecl *ext,
2190+
const ExportContext &where) {
21802191
const AvailableAttr *attr = TypeChecker::getDeprecated(ext);
21812192
if (!attr)
2182-
return;
2193+
return false;
21832194

21842195
// We match the behavior of clang to not report deprecation warnings
21852196
// inside declarations that are themselves deprecated on all deployment
21862197
// targets.
21872198
if (where.isDeprecated()) {
2188-
return;
2199+
return false;
21892200
}
21902201

21912202
auto *dc = where.getDeclContext();
@@ -2196,7 +2207,7 @@ void TypeChecker::diagnoseIfDeprecated(
21962207
// Suppress a deprecation warning if the availability checking machinery
21972208
// thinks the reference program location will not execute on any
21982209
// deployment target for the current platform.
2199-
return;
2210+
return false;
22002211
}
22012212
}
22022213

@@ -2215,7 +2226,7 @@ void TypeChecker::diagnoseIfDeprecated(
22152226
attr->Deprecated.hasValue(), deprecatedVersion,
22162227
/*message*/ StringRef())
22172228
.highlight(attr->getRange());
2218-
return;
2229+
return true;
22192230
}
22202231

22212232
EncodedDiagnosticMessage encodedMessage(attr->Message);
@@ -2225,6 +2236,7 @@ void TypeChecker::diagnoseIfDeprecated(
22252236
attr->Deprecated.hasValue(), deprecatedVersion,
22262237
encodedMessage.Message)
22272238
.highlight(attr->getRange());
2239+
return true;
22282240
}
22292241

22302242
void swift::diagnoseUnavailableOverride(ValueDecl *override,
@@ -3374,7 +3386,8 @@ void swift::diagnoseTypeAvailability(const TypeRepr *TR, Type T, SourceLoc loc,
33743386
bool
33753387
swift::diagnoseConformanceAvailability(SourceLoc loc,
33763388
ProtocolConformanceRef conformance,
3377-
const ExportContext &where) {
3389+
const ExportContext &where,
3390+
Type depTy, Type replacementTy) {
33783391
assert(!where.isImplicit());
33793392

33803393
if (!conformance.isConcrete())
@@ -3385,29 +3398,55 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
33853398

33863399
auto *DC = where.getDeclContext();
33873400

3401+
auto maybeEmitAssociatedTypeNote = [&]() {
3402+
if (!depTy && !replacementTy)
3403+
return;
3404+
3405+
Type selfTy = rootConf->getProtocol()->getProtocolSelfType();
3406+
if (!depTy->isEqual(selfTy)) {
3407+
auto &ctx = DC->getASTContext();
3408+
ctx.Diags.diagnose(
3409+
loc,
3410+
diag::assoc_conformance_from_implementation_only_module,
3411+
depTy, replacementTy->getCanonicalType());
3412+
}
3413+
};
3414+
33883415
if (auto *ext = dyn_cast<ExtensionDecl>(rootConf->getDeclContext())) {
3389-
if (TypeChecker::diagnoseConformanceExportability(loc, rootConf, ext, where))
3416+
if (TypeChecker::diagnoseConformanceExportability(loc, rootConf, ext, where)) {
3417+
maybeEmitAssociatedTypeNote();
33903418
return true;
3419+
}
33913420

3392-
if (diagnoseExplicitUnavailability(loc, rootConf, ext, where))
3421+
if (diagnoseExplicitUnavailability(loc, rootConf, ext, where)) {
3422+
maybeEmitAssociatedTypeNote();
33933423
return true;
3394-
3395-
// Diagnose for deprecation
3396-
TypeChecker::diagnoseIfDeprecated(loc, rootConf, ext, where);
3424+
}
33973425

33983426
// Diagnose (and possibly signal) for potential unavailability
33993427
auto maybeUnavail = TypeChecker::checkConformanceAvailability(
34003428
rootConf, ext, where);
34013429
if (maybeUnavail.hasValue()) {
34023430
TypeChecker::diagnosePotentialUnavailability(rootConf, ext, loc, DC,
34033431
maybeUnavail.getValue());
3432+
maybeEmitAssociatedTypeNote();
3433+
return true;
3434+
}
3435+
3436+
// Diagnose for deprecation
3437+
if (TypeChecker::diagnoseIfDeprecated(loc, rootConf, ext, where)) {
3438+
maybeEmitAssociatedTypeNote();
3439+
3440+
// Deprecation is just a warning, so keep going with checking the
3441+
// substitution map below.
34043442
}
34053443
}
34063444

34073445
// Now, check associated conformances.
34083446
SubstitutionMap subConformanceSubs =
34093447
concreteConf->getSubstitutions(DC->getParentModule());
3410-
if (diagnoseSubstitutionMapAvailability(loc, subConformanceSubs, where))
3448+
if (diagnoseSubstitutionMapAvailability(loc, subConformanceSubs, where,
3449+
depTy, replacementTy))
34113450
return true;
34123451

34133452
return false;
@@ -3416,10 +3455,12 @@ swift::diagnoseConformanceAvailability(SourceLoc loc,
34163455
bool
34173456
swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
34183457
SubstitutionMap subs,
3419-
const ExportContext &where) {
3458+
const ExportContext &where,
3459+
Type depTy, Type replacementTy) {
34203460
bool hadAnyIssues = false;
34213461
for (ProtocolConformanceRef conformance : subs.getConformances()) {
3422-
if (diagnoseConformanceAvailability(loc, conformance, where))
3462+
if (diagnoseConformanceAvailability(loc, conformance, where,
3463+
depTy, replacementTy))
34233464
hadAnyIssues = true;
34243465
}
34253466
return hadAnyIssues;

lib/Sema/TypeCheckAvailability.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ class ExportContext {
129129
/// it can reference anything.
130130
static ExportContext forFunctionBody(DeclContext *DC, SourceLoc loc);
131131

132+
/// Create an instance describing associated conformances that can be
133+
/// referenced from the the conformance defined by the given DeclContext,
134+
/// which must be a NominalTypeDecl or ExtensionDecl.
135+
static ExportContext forConformance(DeclContext *DC, ProtocolDecl *proto);
136+
132137
/// Produce a new context with the same properties as this one, except
133138
/// changing the ExportabilityReason. This only affects diagnostics.
134139
ExportContext withReason(ExportabilityReason reason) const;
@@ -212,12 +217,16 @@ void diagnoseTypeAvailability(const TypeRepr *TR, Type T, SourceLoc loc,
212217
bool
213218
diagnoseConformanceAvailability(SourceLoc loc,
214219
ProtocolConformanceRef conformance,
215-
const ExportContext &context);
220+
const ExportContext &context,
221+
Type depTy=Type(),
222+
Type replacementTy=Type());
216223

217224
bool
218225
diagnoseSubstitutionMapAvailability(SourceLoc loc,
219226
SubstitutionMap subs,
220-
const ExportContext &context);
227+
const ExportContext &context,
228+
Type depTy=Type(),
229+
Type replacementTy=Type());
221230

222231
/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
223232
/// was emitted.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 15 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4308,56 +4308,6 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
43084308
return ResolveWitnessResult::ExplicitFailed;
43094309
}
43104310

4311-
static void checkExportability(Type depTy, Type replacementTy,
4312-
const ProtocolConformance *conformance,
4313-
NormalProtocolConformance *conformanceBeingChecked,
4314-
DeclContext *DC) {
4315-
SourceFile *SF = DC->getParentSourceFile();
4316-
if (!SF)
4317-
return;
4318-
4319-
SubstitutionMap subs =
4320-
conformance->getSubstitutions(SF->getParentModule());
4321-
for (auto &subConformance : subs.getConformances()) {
4322-
if (!subConformance.isConcrete())
4323-
continue;
4324-
checkExportability(depTy, replacementTy, subConformance.getConcrete(),
4325-
conformanceBeingChecked, DC);
4326-
}
4327-
4328-
const RootProtocolConformance *rootConformance =
4329-
conformance->getRootConformance();
4330-
ModuleDecl *M = rootConformance->getDeclContext()->getParentModule();
4331-
4332-
auto where = ExportContext::forDeclSignature(
4333-
DC->getInnermostDeclarationDeclContext());
4334-
auto originKind = getDisallowedOriginKind(
4335-
rootConformance->getDeclContext()->getAsDecl(),
4336-
where);
4337-
if (originKind == DisallowedOriginKind::None)
4338-
return;
4339-
4340-
ASTContext &ctx = SF->getASTContext();
4341-
4342-
Type selfTy = rootConformance->getProtocol()->getProtocolSelfType();
4343-
if (depTy->isEqual(selfTy)) {
4344-
ctx.Diags.diagnose(
4345-
conformanceBeingChecked->getLoc(),
4346-
diag::conformance_from_implementation_only_module,
4347-
rootConformance->getType(),
4348-
rootConformance->getProtocol()->getName(), 0, M->getName(),
4349-
static_cast<unsigned>(originKind));
4350-
} else {
4351-
ctx.Diags.diagnose(
4352-
conformanceBeingChecked->getLoc(),
4353-
diag::assoc_conformance_from_implementation_only_module,
4354-
rootConformance->getType(),
4355-
rootConformance->getProtocol()->getName(), M->getName(),
4356-
depTy, replacementTy->getCanonicalType(),
4357-
static_cast<unsigned>(originKind));
4358-
}
4359-
}
4360-
43614311
void ConformanceChecker::ensureRequirementsAreSatisfied() {
43624312
Conformance->finishSignatureConformances();
43634313
auto proto = Conformance->getProtocol();
@@ -4403,18 +4353,21 @@ void ConformanceChecker::ensureRequirementsAreSatisfied() {
44034353

44044354
// Now check that our associated conformances are at least as visible as
44054355
// the conformance itself.
4406-
if (getRequiredAccessScope().isPublic() || isUsableFromInlineRequired()) {
4407-
for (auto req : proto->getRequirementSignature()) {
4408-
if (req.getKind() == RequirementKind::Conformance) {
4409-
auto depTy = req.getFirstType();
4410-
auto *proto = req.getSecondType()->castTo<ProtocolType>()->getDecl();
4411-
auto conformance = Conformance->getAssociatedConformance(depTy, proto);
4412-
if (conformance.isConcrete()) {
4413-
auto *concrete = conformance.getConcrete();
4414-
auto replacementTy = DC->mapTypeIntoContext(concrete->getType());
4415-
checkExportability(depTy, replacementTy, concrete,
4416-
Conformance, DC);
4417-
}
4356+
auto where = ExportContext::forConformance(DC, proto);
4357+
if (where.isImplicit())
4358+
return;
4359+
4360+
for (auto req : proto->getRequirementSignature()) {
4361+
if (req.getKind() == RequirementKind::Conformance) {
4362+
auto depTy = req.getFirstType();
4363+
auto *proto = req.getSecondType()->castTo<ProtocolType>()->getDecl();
4364+
auto conformance = Conformance->getAssociatedConformance(depTy, proto);
4365+
if (conformance.isConcrete()) {
4366+
auto *concrete = conformance.getConcrete();
4367+
auto replacementTy = DC->mapTypeIntoContext(concrete->getType());
4368+
diagnoseConformanceAvailability(Conformance->getLoc(),
4369+
conformance, where,
4370+
depTy, replacementTy);
44184371
}
44194372
}
44204373
}

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,10 +1066,10 @@ void diagnoseIfDeprecated(SourceRange SourceRange,
10661066
const ApplyExpr *Call);
10671067

10681068
/// Emits a diagnostic for a reference to a conformnace that is deprecated.
1069-
void diagnoseIfDeprecated(SourceLoc Loc,
1070-
const RootProtocolConformance *DeprecatedConf,
1071-
const ExtensionDecl *Ext,
1072-
const ExportContext &Where);
1069+
bool diagnoseIfDeprecated(SourceLoc loc,
1070+
const RootProtocolConformance *rootConf,
1071+
const ExtensionDecl *ext,
1072+
const ExportContext &where);
10731073
/// @}
10741074

10751075
/// If LangOptions::DebugForbidTypecheckPrefix is set and the given decl

test/SPI/protocol_requirement.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ public protocol Proto {
104104

105105
public struct BadStruct {}
106106
@_spi(Horse) extension BadStruct : OtherProto {}
107-
public struct BadConforms : Proto { // expected-error {{cannot use conformance of 'BadStruct' to 'OtherProto' in associated type 'Self.A.Element' (inferred as 'BadStruct'); the conformance is declared as SPI}}
107+
public struct BadConforms : Proto { // expected-error {{cannot use conformance of 'BadStruct' to 'OtherProto' here; the conformance is declared as SPI}}
108+
// expected-note@-1 {{in associated type 'Self.A.Element' (inferred as 'BadStruct')}}
108109
public typealias A = [BadStruct]
109110
}
110111

test/Sema/conformance_availability.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public struct HasUnavailableConformance1 {}
1616

1717
@available(*, unavailable)
1818
extension HasUnavailableConformance1 : Horse {}
19-
// expected-note@-1 6{{conformance of 'HasUnavailableConformance1' to 'Horse' has been explicitly marked unavailable here}}
19+
// expected-note@-1 7{{conformance of 'HasUnavailableConformance1' to 'Horse' has been explicitly marked unavailable here}}
2020

2121
func passUnavailableConformance1(x: HasUnavailableConformance1) {
2222
takesHorse(x) // expected-error {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
@@ -212,4 +212,51 @@ func passAvailableConformance1a(x: HasAvailableConformance1) {
212212
takesHorse(x)
213213
x.giddyUp()
214214
_ = UsesHorse<HasAvailableConformance1>.self
215+
}
216+
217+
// Associated conformance with unavailability
218+
protocol Rider {
219+
associatedtype H : Horse
220+
}
221+
222+
struct AssocConformanceUnavailable : Rider {
223+
// expected-error@-1 {{conformance of 'HasUnavailableConformance1' to 'Horse' is unavailable}}
224+
// expected-note@-2 {{in associated type 'Self.H' (inferred as 'HasUnavailableConformance1')}}
225+
typealias H = HasUnavailableConformance1
226+
}
227+
228+
// Associated conformance with deprecation
229+
struct AssocConformanceDeprecated : Rider {
230+
// expected-warning@-1 {{conformance of 'HasDeprecatedConformance1' to 'Horse' is deprecated}}
231+
// expected-note@-2 {{in associated type 'Self.H' (inferred as 'HasDeprecatedConformance1')}}
232+
typealias H = HasDeprecatedConformance1
233+
}
234+
235+
// Associated conformance with availability
236+
struct AssocConformanceAvailable1 : Rider {
237+
// expected-error@-1 {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
238+
// expected-note@-2 {{in associated type 'Self.H' (inferred as 'HasAvailableConformance1')}}
239+
// expected-note@-3 {{add @available attribute to enclosing struct}}
240+
typealias H = HasAvailableConformance1
241+
}
242+
243+
@available(macOS 100, *)
244+
struct AssocConformanceAvailable2 : Rider {
245+
typealias H = HasAvailableConformance1
246+
}
247+
248+
struct AssocConformanceAvailable3 {}
249+
250+
extension AssocConformanceAvailable3 : Rider {
251+
// expected-error@-1 {{conformance of 'HasAvailableConformance1' to 'Horse' is only available in macOS 100 or newer}}
252+
// expected-note@-2 {{in associated type 'Self.H' (inferred as 'HasAvailableConformance1')}}
253+
// expected-note@-3 {{add @available attribute to enclosing extension}}
254+
typealias H = HasAvailableConformance1
255+
}
256+
257+
struct AssocConformanceAvailable4 {}
258+
259+
@available(macOS 100, *)
260+
extension AssocConformanceAvailable4 : Rider {
261+
typealias H = HasAvailableConformance1
215262
}

0 commit comments

Comments
 (0)