Skip to content

Commit 85decfd

Browse files
committed
Sema: Downgrade diagnostics about potential unavailability of the extended type in an extension declaration when the following conditions are met:
1. The extension is missing explicit availability. 2. The required availability is before the deployment target. This exception is needed because there are many existing resilient libraries with extensions containing public members and no availability declared for the extension itself. Under existing rules, these decls are not diagnosed because the deployment target of the library is typically high enough that the extended type is implicitly considered available. Now that we're improving availability type checking for resilient libraries, however, these decls are being diagnosed. We need to land the diagnostic without breaking source compatibility so that library authors can identify and fix the issues. Resolves rdar://92621567
1 parent 0094470 commit 85decfd

File tree

6 files changed

+96
-65
lines changed

6 files changed

+96
-65
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5581,6 +5581,10 @@ ERROR(availability_decl_only_version_newer, none,
55815581
"%0 is only available in %1 %2 or newer",
55825582
(DeclName, StringRef, llvm::VersionTuple))
55835583

5584+
WARNING(availability_decl_only_version_newer_warn, none,
5585+
"%0 is only available in %1 %2 or newer",
5586+
(DeclName, StringRef, llvm::VersionTuple))
5587+
55845588
ERROR(availability_opaque_types_only_version_newer, none,
55855589
"'some' return types are only available in %0 %1 or newer",
55865590
(StringRef, llvm::VersionTuple))

lib/Sema/TypeCheckAccess.cpp

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,7 +1579,7 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
15791579

15801580
void checkType(Type type, const TypeRepr *typeRepr, const Decl *context,
15811581
ExportabilityReason reason=ExportabilityReason::General,
1582-
bool allowUnavailableProtocol=false) {
1582+
DeclAvailabilityFlags flags=None) {
15831583
// Don't bother checking errors.
15841584
if (type && type->hasError())
15851585
return;
@@ -1589,18 +1589,6 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
15891589
if (AvailableAttr::isUnavailable(context))
15901590
return;
15911591

1592-
DeclAvailabilityFlags flags = None;
1593-
1594-
// We allow a type to conform to a protocol that is less available than
1595-
// the type itself. This enables a type to retroactively model or directly
1596-
// conform to a protocol only available on newer OSes and yet still be used on
1597-
// older OSes.
1598-
//
1599-
// To support this, inside inheritance clauses we allow references to
1600-
// protocols that are unavailable in the current type refinement context.
1601-
if (allowUnavailableProtocol)
1602-
flags |= DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol;
1603-
16041592
diagnoseTypeAvailability(typeRepr, type, context->getLoc(),
16051593
Where.withReason(reason), flags);
16061594
}
@@ -1758,20 +1746,17 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
17581746
void visitNominalTypeDecl(const NominalTypeDecl *nominal) {
17591747
checkGenericParams(nominal, nominal);
17601748

1761-
llvm::for_each(nominal->getInherited(),
1762-
[&](TypeLoc inherited) {
1763-
checkType(inherited.getType(), inherited.getTypeRepr(),
1764-
nominal, ExportabilityReason::General,
1765-
/*allowUnavailableProtocol=*/true);
1749+
llvm::for_each(nominal->getInherited(), [&](TypeLoc inherited) {
1750+
checkType(inherited.getType(), inherited.getTypeRepr(), nominal,
1751+
ExportabilityReason::General,
1752+
DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol);
17661753
});
17671754
}
17681755

17691756
void visitProtocolDecl(ProtocolDecl *proto) {
1770-
llvm::for_each(proto->getInherited(),
1771-
[&](TypeLoc requirement) {
1757+
llvm::for_each(proto->getInherited(), [&](TypeLoc requirement) {
17721758
checkType(requirement.getType(), requirement.getTypeRepr(), proto,
1773-
ExportabilityReason::General,
1774-
/*allowUnavailableProtocol=*/false);
1759+
ExportabilityReason::General);
17751760
});
17761761

17771762
if (proto->getTrailingWhereClause()) {
@@ -1846,11 +1831,10 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
18461831
//
18471832
// 1) If the extension defines conformances, the conformed-to protocols
18481833
// must be exported.
1849-
llvm::for_each(ED->getInherited(),
1850-
[&](TypeLoc inherited) {
1851-
checkType(inherited.getType(), inherited.getTypeRepr(),
1852-
ED, ExportabilityReason::General,
1853-
/*allowUnavailableProtocol=*/true);
1834+
llvm::for_each(ED->getInherited(), [&](TypeLoc inherited) {
1835+
checkType(inherited.getType(), inherited.getTypeRepr(), ED,
1836+
ExportabilityReason::General,
1837+
DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol);
18541838
});
18551839

18561840
auto wasWhere = Where;
@@ -1866,8 +1850,21 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
18661850
});
18671851

18681852
Where = wasWhere.withExported(hasExportedMembers);
1853+
1854+
// When diagnosing potential unavailability of the extended type, downgrade
1855+
// the diagnostics to warnings when the extension decl has no declared
1856+
// availability and the required availability is more than the deployment
1857+
// target.
1858+
DeclAvailabilityFlags extendedTypeFlags = None;
1859+
auto annotatedRange =
1860+
AvailabilityInference::annotatedAvailableRange(ED, ED->getASTContext());
1861+
if (!annotatedRange.hasValue())
1862+
extendedTypeFlags |= DeclAvailabilityFlag::
1863+
WarnForPotentialUnavailabilityBeforeDeploymentTarget;
1864+
18691865
checkType(ED->getExtendedType(), ED->getExtendedTypeRepr(), ED,
1870-
ExportabilityReason::ExtensionWithPublicMembers);
1866+
ExportabilityReason::ExtensionWithPublicMembers,
1867+
extendedTypeFlags);
18711868

18721869
// 3) If the extension contains exported members or defines conformances,
18731870
// the 'where' clause must only name exported types.

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,28 +1881,36 @@ void TypeChecker::checkConcurrencyAvailability(SourceRange ReferenceRange,
18811881
}
18821882
}
18831883

1884-
void TypeChecker::diagnosePotentialUnavailability(
1884+
bool TypeChecker::diagnosePotentialUnavailability(
18851885
const ValueDecl *D, SourceRange ReferenceRange,
18861886
const DeclContext *ReferenceDC,
1887-
const UnavailabilityReason &Reason) {
1887+
const UnavailabilityReason &Reason,
1888+
bool WarnBeforeDeploymentTarget) {
18881889
ASTContext &Context = ReferenceDC->getASTContext();
18891890

1891+
bool AsError = true;
18901892
auto RequiredRange = Reason.getRequiredOSVersionRange();
18911893
{
1892-
auto Err =
1893-
Context.Diags.diagnose(
1894-
ReferenceRange.Start, diag::availability_decl_only_version_newer,
1895-
D->getName(), prettyPlatformString(targetPlatform(Context.LangOpts)),
1896-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
1894+
if (WarnBeforeDeploymentTarget &&
1895+
!RequiredRange.isContainedIn(
1896+
AvailabilityContext::forDeploymentTarget(Context).getOSVersion()))
1897+
AsError = false;
1898+
1899+
auto Diag = Context.Diags.diagnose(
1900+
ReferenceRange.Start,
1901+
AsError ? diag::availability_decl_only_version_newer
1902+
: diag::availability_decl_only_version_newer_warn,
1903+
D->getName(), prettyPlatformString(targetPlatform(Context.LangOpts)),
1904+
Reason.getRequiredOSVersionRange().getLowerEndpoint());
18971905

18981906
// Direct a fixit to the error if an existing guard is nearly-correct
1899-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
1900-
ReferenceDC,
1901-
RequiredRange, Context, Err))
1902-
return;
1907+
if (fixAvailabilityByNarrowingNearbyVersionCheck(
1908+
ReferenceRange, ReferenceDC, RequiredRange, Context, Diag))
1909+
return AsError;
19031910
}
19041911

19051912
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
1913+
return AsError;
19061914
}
19071915

19081916
void TypeChecker::diagnosePotentialAccessorUnavailability(
@@ -3406,21 +3414,26 @@ bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
34063414

34073415
// Diagnose (and possibly signal) for potential unavailability
34083416
auto maybeUnavail = TypeChecker::checkDeclarationAvailability(D, Where);
3409-
if (maybeUnavail.hasValue()) {
3410-
auto *DC = Where.getDeclContext();
3417+
if (!maybeUnavail.hasValue())
3418+
return false;
34113419

3412-
if (accessor) {
3413-
bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout);
3414-
TypeChecker::diagnosePotentialAccessorUnavailability(accessor, R, DC,
3415-
maybeUnavail.getValue(),
3416-
forInout);
3417-
} else {
3418-
TypeChecker::diagnosePotentialUnavailability(D, R, DC, maybeUnavail.getValue());
3419-
}
3420-
if (!Flags.contains(DeclAvailabilityFlag::ContinueOnPotentialUnavailability))
3421-
return true;
3420+
auto *DC = Where.getDeclContext();
3421+
3422+
if (accessor) {
3423+
bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout);
3424+
TypeChecker::diagnosePotentialAccessorUnavailability(
3425+
accessor, R, DC, maybeUnavail.getValue(), forInout);
3426+
} else {
3427+
bool downgradeBeforeDeploymentTarget = Flags.contains(
3428+
DeclAvailabilityFlag::
3429+
WarnForPotentialUnavailabilityBeforeDeploymentTarget);
3430+
if (!TypeChecker::diagnosePotentialUnavailability(
3431+
D, R, DC, maybeUnavail.getValue(), downgradeBeforeDeploymentTarget))
3432+
return false;
34223433
}
3423-
return false;
3434+
3435+
return !Flags.contains(
3436+
DeclAvailabilityFlag::ContinueOnPotentialUnavailability);
34243437
}
34253438

34263439
/// Return true if the specified type looks like an integer of floating point

lib/Sema/TypeCheckAvailability.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ namespace swift {
4040

4141
enum class DeclAvailabilityFlag : uint8_t {
4242
/// Do not diagnose uses of protocols in versions before they were introduced.
43-
/// Used when type-checking protocol conformances, since conforming to a
44-
/// protocol that doesn't exist yet is allowed.
43+
/// We allow a type to conform to a protocol that is less available than the
44+
/// type itself. This enables a type to retroactively model or directly conform
45+
/// to a protocol only available on newer OSes and yet still be used on older
46+
/// OSes.
4547
AllowPotentiallyUnavailableProtocol = 1 << 0,
4648

4749
/// Diagnose uses of declarations in versions before they were introduced, but
@@ -54,7 +56,13 @@ enum class DeclAvailabilityFlag : uint8_t {
5456

5557
/// If an error diagnostic would normally be emitted, demote the error to a
5658
/// warning. Used for ObjC key path components.
57-
ForObjCKeyPath = 1 << 3
59+
ForObjCKeyPath = 1 << 3,
60+
61+
/// Downgrade errors about decl availability to warnings when the fix would be
62+
/// to constrain availability to a version that is more available than the
63+
/// current deployment target. This is needed for source compatibility in when
64+
/// checking public extensions in library modules.
65+
WarnForPotentialUnavailabilityBeforeDeploymentTarget = 1 << 4,
5866
};
5967
using DeclAvailabilityFlags = OptionSet<DeclAvailabilityFlag>;
6068

lib/Sema/TypeChecker.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,15 +1072,17 @@ checkConformanceAvailability(const RootProtocolConformance *Conf,
10721072
/// expression or statement.
10731073
void checkIgnoredExpr(Expr *E);
10741074

1075-
// Emits a diagnostic, if necessary, for a reference to a declaration
1076-
// that is potentially unavailable at the given source location.
1077-
void diagnosePotentialUnavailability(const ValueDecl *D,
1075+
// Emits a diagnostic for a reference to a declaration that is potentially
1076+
// unavailable at the given source location. Returns true if an error diagnostic
1077+
// was emitted.
1078+
bool diagnosePotentialUnavailability(const ValueDecl *D,
10781079
SourceRange ReferenceRange,
10791080
const DeclContext *ReferenceDC,
1080-
const UnavailabilityReason &Reason);
1081+
const UnavailabilityReason &Reason,
1082+
bool WarnBeforeDeploymentTarget);
10811083

1082-
// Emits a diagnostic, if necessary, for a reference to a declaration
1083-
// that is potentially unavailable at the given source location.
1084+
// Emits a diagnostic for a protocol conformance that is potentially
1085+
// unavailable at the given source location.
10841086
void diagnosePotentialUnavailability(const RootProtocolConformance *rootConf,
10851087
const ExtensionDecl *ext,
10861088
SourceLoc loc,

test/attr/attr_inlinable_available.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -608,25 +608,32 @@ extension BetweenTargets {
608608
fileprivate func fileprivateFunc1() {}
609609
}
610610

611-
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
611+
// expected-warning@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
612612
extension BetweenTargets {
613613
public func publicFunc1() {}
614614
}
615615

616-
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
616+
// expected-warning@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
617617
extension BetweenTargets {
618618
@usableFromInline
619619
internal func usableFromInlineFunc1() {}
620620
}
621621

622-
// expected-error@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
622+
// expected-warning@+1 {{'BetweenTargets' is only available in}} expected-note@+1 {{add @available attribute to enclosing extension}}
623623
extension BetweenTargets {
624624
internal func internalFunc2() {}
625625
private func privateFunc2() {}
626626
fileprivate func fileprivateFunc2() {}
627627
public func publicFunc2() {}
628628
}
629629

630+
// An extension with more availability than BetweenTargets.
631+
// expected-error@+2 {{'BetweenTargets' is only available in}}
632+
@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 2.0, *)
633+
extension BetweenTargets {
634+
public func publicFunc3() {}
635+
}
636+
630637
// Same availability as BetweenTargets but internal instead of public.
631638
@available(macOS 10.14.5, iOS 12.3, tvOS 12.3, watchOS 5.3, *)
632639
internal struct BetweenTargetsInternal {}
@@ -669,7 +676,7 @@ public protocol PublicProto {}
669676
extension NoAvailable: PublicProto {}
670677
extension BeforeInliningTarget: PublicProto {}
671678
extension AtInliningTarget: PublicProto {}
672-
extension BetweenTargets: PublicProto {} // expected-error {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing extension}}
679+
extension BetweenTargets: PublicProto {} // expected-warning {{'BetweenTargets' is only available in}} expected-note {{add @available attribute to enclosing extension}}
673680
extension AtDeploymentTarget: PublicProto {} // expected-error {{'AtDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
674681
extension AfterDeploymentTarget: PublicProto {} // expected-error {{'AfterDeploymentTarget' is only available in}} expected-note {{add @available attribute to enclosing extension}}
675682

0 commit comments

Comments
 (0)