Skip to content

Commit ceb8675

Browse files
committed
Sema: (Mostly) check conformance availability
If a conformance is defined in an extension, we now look for references to the conformance in types and expressions and respect's the extension's availability (or deprecation, etc). The conformance checker itself still needs to check conformance availability of associated conformances and the like; that will be a separate change. Note that conformances defined on types don't require any special handling, since they are as available as the intersection of the conforming type and the protocol. By default, we diagnose conformance availability violations where the OS version is not sufficiently new as warnings, to avoid breaking source compatibility. Stricter behavior where these violations are diagnosed as errors is enabled by passing the -enable-conformance-availability-errors flag. There are test cases that run both with and without this flag. In the future, we hope to make the stricter behavior the default, since after all, violations here can result in link errors and runtime crashes. Uses of completely unavailable conformances are still always diagnosed as errors, even when this flag is not passed in. Progress on <rdar://problem/35158274>.
1 parent f150bdd commit ceb8675

10 files changed

+560
-13
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5040,6 +5040,39 @@ ERROR(availabilty_string_subscript_migration, none,
50405040
"subscripts returning String were obsoleted in Swift 4; explicitly "
50415041
"construct a String from subscripted result", ())
50425042

5043+
// Conformance availability checking diagnostics
5044+
5045+
ERROR(conformance_availability_unavailable, none,
5046+
"conformance of %0 to %1 is unavailable"
5047+
"%select{ in %3|}2%select{|: %4}4",
5048+
(Type, Type, bool, StringRef, StringRef))
5049+
5050+
NOTE(conformance_availability_marked_unavailable, none,
5051+
"conformance of %0 to %1 has been explicitly marked "
5052+
"unavailable here", (Type, Type))
5053+
5054+
NOTE(conformance_availability_introduced_in_version, none,
5055+
"conformance of %0 to %1 was introduced in %2 %3",
5056+
(Type, Type, StringRef, llvm::VersionTuple))
5057+
5058+
NOTE(conformance_availability_obsoleted, none,
5059+
"conformance of %0 to %1 was obsoleted in %2 %3",
5060+
(Type, Type, StringRef, llvm::VersionTuple))
5061+
5062+
WARNING(conformance_availability_deprecated, none,
5063+
"conformance of %0 to %1 %select{is|%select{is|was}4}2 "
5064+
"deprecated%select{| in %3%select{| %5}4}2%select{|: %6}6",
5065+
(Type, Type, bool, StringRef, bool, llvm::VersionTuple,
5066+
StringRef))
5067+
5068+
ERROR(conformance_availability_only_version_newer, none,
5069+
"conformance of %0 to %1 is only available in %2 %3 or newer",
5070+
(Type, Type, StringRef, llvm::VersionTuple))
5071+
5072+
WARNING(conformance_availability_only_version_newer_warn, none,
5073+
"conformance of %0 to %1 is only available in %2 %3 or newer",
5074+
(Type, Type, StringRef, llvm::VersionTuple))
5075+
50435076
//------------------------------------------------------------------------------
50445077
// MARK: @discardableResult
50455078
//------------------------------------------------------------------------------

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ namespace swift {
9494
/// Disable API availability checking.
9595
bool DisableAvailabilityChecking = false;
9696

97+
/// Should conformance availability violations be diagnosed as errors?
98+
bool EnableConformanceAvailabilityErrors = false;
99+
97100
/// Maximum number of typo corrections we are allowed to perform.
98101
/// This is disabled by default until we can get typo-correction working within acceptable performance bounds.
99102
unsigned TypoCorrectionLimit = 0;

include/swift/Option/FrontendOptions.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,14 @@ def disable_availability_checking : Flag<["-"],
405405
"disable-availability-checking">,
406406
HelpText<"Disable checking for potentially unavailable APIs">;
407407

408+
def enable_conformance_availability_errors : Flag<["-"],
409+
"enable-conformance-availability-errors">,
410+
HelpText<"Diagnose conformance availability violations as errors">;
411+
412+
def disable_conformance_availability_errors : Flag<["-"],
413+
"disable-conformance-availability-errors">,
414+
HelpText<"Diagnose conformance availability violations as warnings">;
415+
408416
def report_errors_to_debugger : Flag<["-"], "report-errors-to-debugger">,
409417
HelpText<"Deprecated, will be removed in future versions.">;
410418

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
391391
Opts.DisableAvailabilityChecking |=
392392
Args.hasArg(OPT_disable_availability_checking);
393393

394+
if (auto A = Args.getLastArg(OPT_enable_conformance_availability_errors,
395+
OPT_disable_conformance_availability_errors)) {
396+
Opts.EnableConformanceAvailabilityErrors
397+
= A->getOption().matches(OPT_enable_conformance_availability_errors);
398+
}
399+
394400
if (auto A = Args.getLastArg(OPT_enable_access_control,
395401
OPT_disable_access_control)) {
396402
Opts.EnableAccessControl

lib/Sema/ResilienceDiagnostics.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,16 @@ TypeChecker::diagnoseDeclRefExportability(SourceLoc loc,
160160
bool
161161
TypeChecker::diagnoseConformanceExportability(SourceLoc loc,
162162
const RootProtocolConformance *rootConf,
163+
const ExtensionDecl *ext,
163164
const ExportContext &where) {
164165
if (!where.mustOnlyReferenceExportedDecls())
165166
return false;
166167

167-
auto originKind = getDisallowedOriginKind(
168-
rootConf->getDeclContext()->getAsDecl(),
169-
where);
168+
auto originKind = getDisallowedOriginKind(ext, where);
170169
if (originKind == DisallowedOriginKind::None)
171170
return false;
172171

173-
ModuleDecl *M = rootConf->getDeclContext()->getParentModule();
172+
ModuleDecl *M = ext->getParentModule();
174173
ASTContext &ctx = M->getASTContext();
175174

176175
auto reason = where.getExportabilityReason();

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 217 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,13 @@ TypeChecker::checkDeclarationAvailability(const Decl *D,
958958
return UnavailabilityReason::requiresVersionRange(version);
959959
}
960960

961+
Optional<UnavailabilityReason>
962+
TypeChecker::checkConformanceAvailability(const RootProtocolConformance *conf,
963+
const ExtensionDecl *ext,
964+
const ExportContext &where) {
965+
return checkDeclarationAvailability(ext, where);
966+
}
967+
961968
/// A class that walks the AST to find the innermost (i.e., deepest) node that
962969
/// contains a target SourceRange and matches a particular criterion.
963970
/// This class finds the innermost nodes of interest by walking
@@ -1638,6 +1645,37 @@ void TypeChecker::diagnosePotentialAccessorUnavailability(
16381645
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
16391646
}
16401647

1648+
void TypeChecker::diagnosePotentialUnavailability(
1649+
const RootProtocolConformance *rootConf,
1650+
const ExtensionDecl *ext,
1651+
SourceLoc loc,
1652+
const DeclContext *dc,
1653+
const UnavailabilityReason &reason) {
1654+
ASTContext &ctx = dc->getASTContext();
1655+
1656+
auto requiredRange = reason.getRequiredOSVersionRange();
1657+
{
1658+
auto type = rootConf->getType();
1659+
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
1660+
1661+
auto diagID = (ctx.LangOpts.EnableConformanceAvailabilityErrors
1662+
? diag::conformance_availability_only_version_newer
1663+
: diag::conformance_availability_only_version_newer_warn);
1664+
auto err =
1665+
ctx.Diags.diagnose(
1666+
loc, diagID,
1667+
type, proto, prettyPlatformString(targetPlatform(ctx.LangOpts)),
1668+
reason.getRequiredOSVersionRange().getLowerEndpoint());
1669+
1670+
// Direct a fixit to the error if an existing guard is nearly-correct
1671+
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc,
1672+
requiredRange, ctx, err))
1673+
return;
1674+
}
1675+
1676+
fixAvailability(loc, dc, requiredRange, ctx);
1677+
}
1678+
16411679
const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
16421680
if (auto *Attr = D->getAttrs().getDeprecated(D->getASTContext()))
16431681
return Attr;
@@ -1655,7 +1693,7 @@ const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
16551693
/// Returns true if the reference or any of its parents is an
16561694
/// unconditional unavailable declaration for the same platform.
16571695
static bool isInsideCompatibleUnavailableDeclaration(
1658-
const ValueDecl *D, const ExportContext &where,
1696+
const Decl *D, const ExportContext &where,
16591697
const AvailableAttr *attr) {
16601698
auto referencedPlatform = where.getUnavailablePlatformKind();
16611699
if (!referencedPlatform)
@@ -2134,6 +2172,60 @@ void TypeChecker::diagnoseIfDeprecated(SourceRange ReferenceRange,
21342172
}
21352173
}
21362174

2175+
void TypeChecker::diagnoseIfDeprecated(
2176+
SourceLoc loc,
2177+
const RootProtocolConformance *rootConf,
2178+
const ExtensionDecl *ext,
2179+
const ExportContext &where) {
2180+
const AvailableAttr *attr = TypeChecker::getDeprecated(ext);
2181+
if (!attr)
2182+
return;
2183+
2184+
// We match the behavior of clang to not report deprecation warnings
2185+
// inside declarations that are themselves deprecated on all deployment
2186+
// targets.
2187+
if (where.isDeprecated()) {
2188+
return;
2189+
}
2190+
2191+
auto *dc = where.getDeclContext();
2192+
auto &ctx = dc->getASTContext();
2193+
if (!ctx.LangOpts.DisableAvailabilityChecking) {
2194+
AvailabilityContext runningOSVersion = where.getAvailabilityContext();
2195+
if (runningOSVersion.isKnownUnreachable()) {
2196+
// Suppress a deprecation warning if the availability checking machinery
2197+
// thinks the reference program location will not execute on any
2198+
// deployment target for the current platform.
2199+
return;
2200+
}
2201+
}
2202+
2203+
auto type = rootConf->getType();
2204+
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
2205+
2206+
StringRef platform = attr->prettyPlatformString();
2207+
llvm::VersionTuple deprecatedVersion;
2208+
if (attr->Deprecated)
2209+
deprecatedVersion = attr->Deprecated.getValue();
2210+
2211+
if (attr->Message.empty()) {
2212+
ctx.Diags.diagnose(
2213+
loc, diag::conformance_availability_deprecated,
2214+
type, proto, attr->hasPlatform(), platform,
2215+
attr->Deprecated.hasValue(), deprecatedVersion,
2216+
/*message*/ StringRef())
2217+
.highlight(attr->getRange());
2218+
return;
2219+
}
2220+
2221+
EncodedDiagnosticMessage encodedMessage(attr->Message);
2222+
ctx.Diags.diagnose(
2223+
loc, diag::conformance_availability_deprecated,
2224+
type, proto, attr->hasPlatform(), platform,
2225+
attr->Deprecated.hasValue(), deprecatedVersion,
2226+
encodedMessage.Message)
2227+
.highlight(attr->getRange());
2228+
}
21372229

21382230
void swift::diagnoseUnavailableOverride(ValueDecl *override,
21392231
const ValueDecl *base,
@@ -2198,6 +2290,102 @@ bool swift::diagnoseExplicitUnavailability(const ValueDecl *D,
21982290
});
21992291
}
22002292

2293+
/// Emit a diagnostic for references to declarations that have been
2294+
/// marked as unavailable, either through "unavailable" or "obsoleted:".
2295+
bool swift::diagnoseExplicitUnavailability(SourceLoc loc,
2296+
const RootProtocolConformance *rootConf,
2297+
const ExtensionDecl *ext,
2298+
const ExportContext &where) {
2299+
auto *attr = AvailableAttr::isUnavailable(ext);
2300+
if (!attr)
2301+
return false;
2302+
2303+
// Calling unavailable code from within code with the same
2304+
// unavailability is OK -- the eventual caller can't call the
2305+
// enclosing code in the same situations it wouldn't be able to
2306+
// call this code.
2307+
if (isInsideCompatibleUnavailableDeclaration(ext, where, attr))
2308+
return false;
2309+
2310+
ASTContext &ctx = ext->getASTContext();
2311+
auto &diags = ctx.Diags;
2312+
2313+
auto type = rootConf->getType();
2314+
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
2315+
2316+
StringRef platform;
2317+
switch (attr->getPlatformAgnosticAvailability()) {
2318+
case PlatformAgnosticAvailabilityKind::Deprecated:
2319+
llvm_unreachable("shouldn't see deprecations in explicit unavailability");
2320+
2321+
case PlatformAgnosticAvailabilityKind::None:
2322+
case PlatformAgnosticAvailabilityKind::Unavailable:
2323+
if (attr->Platform != PlatformKind::none) {
2324+
// This was platform-specific; indicate the platform.
2325+
platform = attr->prettyPlatformString();
2326+
break;
2327+
}
2328+
LLVM_FALLTHROUGH;
2329+
2330+
case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
2331+
case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
2332+
// We don't want to give further detail about these.
2333+
platform = "";
2334+
break;
2335+
2336+
case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
2337+
// This API is explicitly unavailable in Swift.
2338+
platform = "Swift";
2339+
break;
2340+
}
2341+
2342+
EncodedDiagnosticMessage EncodedMessage(attr->Message);
2343+
diags.diagnose(loc, diag::conformance_availability_unavailable,
2344+
type, proto,
2345+
platform.empty(), platform, EncodedMessage.Message);
2346+
2347+
switch (attr->getVersionAvailability(ctx)) {
2348+
case AvailableVersionComparison::Available:
2349+
case AvailableVersionComparison::PotentiallyUnavailable:
2350+
llvm_unreachable("These aren't considered unavailable");
2351+
2352+
case AvailableVersionComparison::Unavailable:
2353+
if ((attr->isLanguageVersionSpecific() ||
2354+
attr->isPackageDescriptionVersionSpecific())
2355+
&& attr->Introduced.hasValue())
2356+
diags.diagnose(ext, diag::conformance_availability_introduced_in_version,
2357+
type, proto,
2358+
(attr->isLanguageVersionSpecific() ?
2359+
"Swift" : "PackageDescription"),
2360+
*attr->Introduced)
2361+
.highlight(attr->getRange());
2362+
else
2363+
diags.diagnose(ext, diag::conformance_availability_marked_unavailable,
2364+
type, proto)
2365+
.highlight(attr->getRange());
2366+
break;
2367+
2368+
case AvailableVersionComparison::Obsoleted:
2369+
// FIXME: Use of the platformString here is non-awesome for application
2370+
// extensions.
2371+
2372+
StringRef platformDisplayString;
2373+
if (attr->isLanguageVersionSpecific()) {
2374+
platformDisplayString = "Swift";
2375+
} else if (attr->isPackageDescriptionVersionSpecific()) {
2376+
platformDisplayString = "PackageDescription";
2377+
} else {
2378+
platformDisplayString = platform;
2379+
}
2380+
2381+
diags.diagnose(ext, diag::conformance_availability_obsoleted,
2382+
type, proto, platformDisplayString, *attr->Obsoleted)
2383+
.highlight(attr->getRange());
2384+
break;
2385+
}
2386+
return true;
2387+
}
2388+
22012389
/// Check if this is a subscript declaration inside String or
22022390
/// Substring that returns String, and if so return true.
22032391
bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
@@ -3187,19 +3375,42 @@ bool
31873375
swift::diagnoseConformanceAvailability(SourceLoc loc,
31883376
ProtocolConformanceRef conformance,
31893377
const ExportContext &where) {
3378+
assert(!where.isImplicit());
3379+
31903380
if (!conformance.isConcrete())
31913381
return false;
3382+
31923383
const ProtocolConformance *concreteConf = conformance.getConcrete();
3384+
const RootProtocolConformance *rootConf = concreteConf->getRootConformance();
31933385

31943386
auto *DC = where.getDeclContext();
3387+
3388+
if (auto *ext = dyn_cast<ExtensionDecl>(rootConf->getDeclContext())) {
3389+
if (TypeChecker::diagnoseConformanceExportability(loc, rootConf, ext, where))
3390+
return true;
3391+
3392+
if (diagnoseExplicitUnavailability(loc, rootConf, ext, where))
3393+
return true;
3394+
3395+
// Diagnose for deprecation
3396+
TypeChecker::diagnoseIfDeprecated(loc, rootConf, ext, where);
3397+
3398+
// Diagnose (and possibly signal) for potential unavailability
3399+
auto maybeUnavail = TypeChecker::checkConformanceAvailability(
3400+
rootConf, ext, where);
3401+
if (maybeUnavail.hasValue()) {
3402+
TypeChecker::diagnosePotentialUnavailability(rootConf, ext, loc, DC,
3403+
maybeUnavail.getValue());
3404+
}
3405+
}
3406+
3407+
// Now, check associated conformances.
31953408
SubstitutionMap subConformanceSubs =
31963409
concreteConf->getSubstitutions(DC->getParentModule());
3197-
diagnoseSubstitutionMapAvailability(loc, subConformanceSubs, where);
3198-
const RootProtocolConformance *rootConf =
3199-
concreteConf->getRootConformance();
3410+
if (diagnoseSubstitutionMapAvailability(loc, subConformanceSubs, where))
3411+
return true;
32003412

3201-
return TypeChecker::diagnoseConformanceExportability(
3202-
loc, rootConf, where);
3413+
return false;
32033414
}
32043415

32053416
bool

lib/Sema/TypeCheckAvailability.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace swift {
3030
class InFlightDiagnostic;
3131
class Decl;
3232
class ProtocolConformanceRef;
33+
class RootProtocolConformance;
3334
class Stmt;
3435
class SubstitutionMap;
3536
class Type;
@@ -247,6 +248,14 @@ bool diagnoseExplicitUnavailability(
247248
DeclAvailabilityFlags Flags,
248249
llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts);
249250

251+
/// Emit a diagnostic for references to declarations that have been
252+
/// marked as unavailable, either through "unavailable" or "obsoleted:".
253+
bool diagnoseExplicitUnavailability(
254+
SourceLoc loc,
255+
const RootProtocolConformance *rootConf,
256+
const ExtensionDecl *ext,
257+
const ExportContext &where);
258+
250259
/// Check if \p decl has a introduction version required by -require-explicit-availability
251260
void checkExplicitAvailability(Decl *decl);
252261

0 commit comments

Comments
 (0)