Skip to content

Commit 6bb7eea

Browse files
authored
Merge pull request #58654 from tshortli/warn-for-extension-to-potentially-unavailable-type
Sema: Warn about potential unavailability of extended type in resilient library API
2 parents ef1fdaf + 85decfd commit 6bb7eea

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)