Skip to content

Commit 0c6ad93

Browse files
committed
Diagnostics Sema: add fix-it about narrowing nearby version check
Before, this fix-it was attached to the error. This resulted in error messages like "'foo' is only available in macOS 10.12.2 or newer" with a fix-it like "replace ''10.12.1' with '10.12.2'". There was no clear information to the user why this fix-it was suggested, and it was not even clear what it would do, because the change would almost always be in a different line than the error. Now, the fix-it will appear as a separate note. This way, it can have its own (hopefully more helpful) description. It will now also co-exist with other fix-it options, like "add 'if #available' version check" and "add @available attribute to enclosing global function". Unit tests have been updated accordingly.
1 parent 6eb6921 commit 0c6ad93

6 files changed

+99
-118
lines changed

include/swift/AST/DiagnosticsSema.def

+4
Original file line numberDiff line numberDiff line change
@@ -5423,6 +5423,10 @@ FIXIT(insert_available_attr,
54235423
"@available(%0 %1, *)\n%2",
54245424
(StringRef, StringRef, StringRef))
54255425

5426+
NOTE(availability_narrow_nearby_version_check, none,
5427+
"narrow nearby version check from '%0' to '%1'",
5428+
(StringRef, StringRef))
5429+
54265430
ERROR(availability_accessor_only_version_newer, none,
54275431
"%select{getter|setter}0 for %1 is only available in %2 %3"
54285432
" or newer",

lib/Sema/TypeCheckAvailability.cpp

+51-85
Original file line numberDiff line numberDiff line change
@@ -1523,11 +1523,8 @@ static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
15231523
/// version), emit a diagnostic and fixit that narrows the existing TRC
15241524
/// condition to the required range.
15251525
static bool fixAvailabilityByNarrowingNearbyVersionCheck(
1526-
SourceRange ReferenceRange,
1527-
const DeclContext *ReferenceDC,
1528-
const VersionRange &RequiredRange,
1529-
ASTContext &Context,
1530-
InFlightDiagnostic &Err) {
1526+
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
1527+
const VersionRange &RequiredRange, ASTContext &Context) {
15311528
const TypeRefinementContext *TRC = nullptr;
15321529
AvailabilityContext RunningOSOverApprox =
15331530
TypeChecker::overApproximateAvailabilityAtLocation(ReferenceRange.Start,
@@ -1559,8 +1556,13 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
15591556
Platform, RunningVers);
15601557
if (!FixRange.isValid())
15611558
return false;
1559+
15621560
// Have found a nontrivial type refinement context-introducer to narrow.
1563-
Err.fixItReplace(FixRange, RequiredVers.getAsString());
1561+
Context.Diags
1562+
.diagnose(FixRange.Start,
1563+
diag::availability_narrow_nearby_version_check,
1564+
RunningVers.getAsString(), RequiredVers.getAsString())
1565+
.fixItReplace(FixRange, RequiredVers.getAsString());
15641566
return true;
15651567
}
15661568
return false;
@@ -1625,14 +1627,17 @@ static void fixAvailabilityByAddingVersionCheck(
16251627
}
16261628

16271629
/// Emit suggested Fix-Its for a reference with to an unavailable symbol
1628-
/// requiting the given OS version range.
1630+
/// requiring the given OS version range.
16291631
static void fixAvailability(SourceRange ReferenceRange,
16301632
const DeclContext *ReferenceDC,
16311633
const VersionRange &RequiredRange,
16321634
ASTContext &Context) {
16331635
if (ReferenceRange.isInvalid())
16341636
return;
16351637

1638+
fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange, ReferenceDC,
1639+
RequiredRange, Context);
1640+
16361641
Optional<ASTNode> NodeToWrapInVersionCheck;
16371642
const Decl *FoundMemberDecl = nullptr;
16381643
const Decl *FoundTypeLevelDecl = nullptr;
@@ -1665,19 +1670,12 @@ void TypeChecker::diagnosePotentialOpaqueTypeUnavailability(
16651670
ASTContext &Context = ReferenceDC->getASTContext();
16661671

16671672
auto RequiredRange = Reason.getRequiredOSVersionRange();
1668-
{
1669-
auto Err =
1670-
Context.Diags.diagnose(
1671-
ReferenceRange.Start, diag::availability_opaque_types_only_version_newer,
1672-
prettyPlatformString(targetPlatform(Context.LangOpts)),
1673-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
1674-
1675-
// Direct a fixit to the error if an existing guard is nearly-correct
1676-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
1677-
ReferenceDC,
1678-
RequiredRange, Context, Err))
1679-
return;
1680-
}
1673+
1674+
Context.Diags.diagnose(ReferenceRange.Start,
1675+
diag::availability_opaque_types_only_version_newer,
1676+
prettyPlatformString(targetPlatform(Context.LangOpts)),
1677+
RequiredRange.getLowerEndpoint());
1678+
16811679
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
16821680
}
16831681

@@ -1687,20 +1685,12 @@ static void diagnosePotentialConcurrencyUnavailability(
16871685
ASTContext &Context = ReferenceDC->getASTContext();
16881686

16891687
auto RequiredRange = Reason.getRequiredOSVersionRange();
1690-
{
1691-
auto Err =
1692-
Context.Diags.diagnose(
1693-
ReferenceRange.Start,
1694-
diag::availability_concurrency_only_version_newer,
1695-
prettyPlatformString(targetPlatform(Context.LangOpts)),
1696-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
1697-
1698-
// Direct a fixit to the error if an existing guard is nearly-correct
1699-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
1700-
ReferenceDC,
1701-
RequiredRange, Context, Err))
1702-
return;
1703-
}
1688+
1689+
Context.Diags.diagnose(ReferenceRange.Start,
1690+
diag::availability_concurrency_only_version_newer,
1691+
prettyPlatformString(targetPlatform(Context.LangOpts)),
1692+
RequiredRange.getLowerEndpoint());
1693+
17041694
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
17051695
}
17061696

@@ -1729,19 +1719,11 @@ void TypeChecker::diagnosePotentialUnavailability(
17291719
ASTContext &Context = ReferenceDC->getASTContext();
17301720

17311721
auto RequiredRange = Reason.getRequiredOSVersionRange();
1732-
{
1733-
auto Err =
1734-
Context.Diags.diagnose(
1735-
ReferenceRange.Start, diag::availability_decl_only_version_newer,
1736-
D->getName(), prettyPlatformString(targetPlatform(Context.LangOpts)),
1737-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
1738-
1739-
// Direct a fixit to the error if an existing guard is nearly-correct
1740-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
1741-
ReferenceDC,
1742-
RequiredRange, Context, Err))
1743-
return;
1744-
}
1722+
1723+
Context.Diags.diagnose(
1724+
ReferenceRange.Start, diag::availability_decl_only_version_newer,
1725+
D->getName(), prettyPlatformString(targetPlatform(Context.LangOpts)),
1726+
RequiredRange.getLowerEndpoint());
17451727

17461728
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
17471729
}
@@ -1757,25 +1739,15 @@ void TypeChecker::diagnosePotentialAccessorUnavailability(
17571739
const AbstractStorageDecl *ASD = Accessor->getStorage();
17581740
DeclName Name = ASD->getName();
17591741

1742+
auto RequiredRange = Reason.getRequiredOSVersionRange();
17601743
auto &diag = ForInout ? diag::availability_inout_accessor_only_version_newer
17611744
: diag::availability_accessor_only_version_newer;
17621745

1763-
auto RequiredRange = Reason.getRequiredOSVersionRange();
1764-
{
1765-
auto Err =
1766-
Context.Diags.diagnose(
1767-
ReferenceRange.Start, diag,
1768-
static_cast<unsigned>(Accessor->getAccessorKind()), Name,
1769-
prettyPlatformString(targetPlatform(Context.LangOpts)),
1770-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
1771-
1772-
1773-
// Direct a fixit to the error if an existing guard is nearly-correct
1774-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
1775-
ReferenceDC,
1776-
RequiredRange, Context, Err))
1777-
return;
1778-
}
1746+
Context.Diags.diagnose(ReferenceRange.Start, diag,
1747+
static_cast<unsigned>(Accessor->getAccessorKind()),
1748+
Name,
1749+
prettyPlatformString(targetPlatform(Context.LangOpts)),
1750+
RequiredRange.getLowerEndpoint());
17791751

17801752
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
17811753
}
@@ -1797,36 +1769,30 @@ behaviorLimitForExplicitUnavailability(
17971769
}
17981770

17991771
void TypeChecker::diagnosePotentialUnavailability(
1800-
const RootProtocolConformance *rootConf,
1801-
const ExtensionDecl *ext,
1802-
SourceLoc loc,
1803-
const DeclContext *dc,
1804-
const UnavailabilityReason &reason) {
1805-
ASTContext &ctx = dc->getASTContext();
1772+
const RootProtocolConformance *rootConf, const ExtensionDecl *ext,
1773+
SourceLoc loc, const DeclContext *ReferenceDC,
1774+
const UnavailabilityReason &Reason) {
1775+
ASTContext &Context = ReferenceDC->getASTContext();
18061776

1807-
auto requiredRange = reason.getRequiredOSVersionRange();
1777+
auto RequiredRange = Reason.getRequiredOSVersionRange();
18081778
{
18091779
auto type = rootConf->getType();
18101780
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
18111781

1812-
auto diagID = (ctx.LangOpts.EnableConformanceAvailabilityErrors
1813-
? diag::conformance_availability_only_version_newer
1814-
: diag::conformance_availability_only_version_newer_warn);
1815-
auto behavior = behaviorLimitForExplicitUnavailability(rootConf, dc);
1816-
auto err =
1817-
ctx.Diags.diagnose(
1818-
loc, diagID,
1819-
type, proto, prettyPlatformString(targetPlatform(ctx.LangOpts)),
1820-
reason.getRequiredOSVersionRange().getLowerEndpoint());
1782+
const auto &diag =
1783+
(Context.LangOpts.EnableConformanceAvailabilityErrors
1784+
? diag::conformance_availability_only_version_newer
1785+
: diag::conformance_availability_only_version_newer_warn);
1786+
auto behavior =
1787+
behaviorLimitForExplicitUnavailability(rootConf, ReferenceDC);
1788+
auto err = Context.Diags.diagnose(
1789+
loc, diag, type, proto,
1790+
prettyPlatformString(targetPlatform(Context.LangOpts)),
1791+
RequiredRange.getLowerEndpoint());
18211792
err.limitBehavior(behavior);
1822-
1823-
// Direct a fixit to the error if an existing guard is nearly-correct
1824-
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc,
1825-
requiredRange, ctx, err))
1826-
return;
18271793
}
18281794

1829-
fixAvailability(loc, dc, requiredRange, ctx);
1795+
fixAvailability(loc, ReferenceDC, RequiredRange, Context);
18301796
}
18311797

18321798
const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {

test/Sema/availability_versions_multi.swift

+12-8
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ let ignored2: Int = globalAvailableOn99_51 // expected-error {{'globalAvailableO
2323
let ignored3: Int = globalAvailableOn99_52 // expected-error {{'globalAvailableOn99_52' is only available in macOS 99.52 or newer}}
2424
// expected-note@-1 {{add @available attribute to enclosing let}}
2525

26-
@available(OSX, introduced: 99.51)
26+
@available(OSX, introduced: 99.51) // expected-note 4 {{narrow nearby version check}} {{29-34=99.52}}
2727
func useFromOtherOn99_51() {
2828
// This will trigger validation of OtherIntroduced99_51 in
2929
// in availability_multi_other.swift
@@ -32,29 +32,33 @@ func useFromOtherOn99_51() {
3232

3333
let o10_9 = OtherIntroduced10_9()
3434
o10_9.extensionMethodOnOtherIntroduced10_9AvailableOn99_51(o99_51)
35-
_ = o99_51.returns99_52Introduced99_52() // expected-error {{'returns99_52Introduced99_52()' is only available in macOS 99.52 or newer}}
36-
// expected-note@-1 {{add 'if #available' version check}}
35+
36+
_ = o99_51.returns99_52Introduced99_52()
37+
// expected-error@-1 {{'returns99_52Introduced99_52()' is only available in macOS 99.52 or newer}}
38+
// expected-note@-2 {{add 'if #available' version check}}
3739

3840
_ = OtherIntroduced99_52()
3941
// expected-error@-1 {{'OtherIntroduced99_52' is only available in macOS 99.52 or newer}}
4042
// expected-note@-2 {{add 'if #available' version check}}
4143

42-
o99_51.extensionMethodOnOtherIntroduced99_51AvailableOn99_52() // expected-error {{'extensionMethodOnOtherIntroduced99_51AvailableOn99_52()' is only available in macOS 99.52 or newer}}
43-
// expected-note@-1 {{add 'if #available' version check}}
44+
o99_51.extensionMethodOnOtherIntroduced99_51AvailableOn99_52()
45+
// expected-error@-1 {{'extensionMethodOnOtherIntroduced99_51AvailableOn99_52()' is only available in macOS 99.52 or newer}}
46+
// expected-note@-2 {{add 'if #available' version check}}
4447

4548
_ = OtherIntroduced99_51.NestedIntroduced99_52()
4649
// expected-error@-1 {{'NestedIntroduced99_52' is only available in macOS 99.52 or newer}}
4750
// expected-note@-2 {{add 'if #available' version check}}
4851
}
4952

50-
@available(OSX, introduced: 99.52)
53+
@available(OSX, introduced: 99.52) // expected-note {{narrow nearby version check}} {{29-34=99.53}}
5154
func useFromOtherOn99_52() {
5255
_ = OtherIntroduced99_52()
5356

5457
let n99_52 = OtherIntroduced99_51.NestedIntroduced99_52()
5558
_ = n99_52.returns99_52()
56-
_ = n99_52.returns99_53() // expected-error {{'returns99_53()' is only available in macOS 99.53 or newer}}
57-
// expected-note@-1 {{add 'if #available' version check}}
59+
_ = n99_52.returns99_53()
60+
// expected-error@-1 {{'returns99_53()' is only available in macOS 99.53 or newer}}
61+
// expected-note@-2 {{add 'if #available' version check}}
5862

5963
// This will trigger validation of the global in availability_in_multi_other.swift
6064
_ = globalFromOtherOn99_52

test/attr/attr_availability_narrow.swift

+26-21
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ import Foundation
77
@available(macOS 10.50.2, *)
88
func foo() { }
99

10-
func useFoo() {
11-
if #available(macOS 10.50.1, *) {
12-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{23-30=10.50.2}}
10+
func useFoo() { // expected-note {{add @available attribute to enclosing global function}}
11+
if #available(macOS 10.50.1, *) { // expected-note {{narrow nearby version check from '10.50.1' to '10.50.2'}}
12+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
13+
// expected-note@-1 {{add 'if #available' version check}}
1314
}
1415
}
1516

16-
func useFooDifferentSpelling() {
17-
if #available(OSX 10.50.1, *) {
18-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{21-28=10.50.2}}
17+
func useFooDifferentSpelling() { // expected-note {{add @available attribute to enclosing global function}}
18+
if #available(OSX 10.50.1, *) { // expected-note {{narrow nearby version check from '10.50.1' to '10.50.2'}}
19+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
20+
// expected-note@-1 {{add 'if #available' version check}}
1921
}
2022
}
2123

@@ -25,39 +27,42 @@ func useFooAlreadyOkRange() {
2527
}
2628
}
2729

28-
func useFooUnaffectedSimilarText() {
29-
if #available(iOS 10.50.10, OSX 10.50.1, *) {
30-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{35-42=10.50.2}}
30+
func useFooUnaffectedSimilarText() { // expected-note {{add @available attribute to enclosing global function}}
31+
if #available(iOS 10.50.10, OSX 10.50.1, *) { // expected-note {{narrow nearby version check from '10.50.1' to '10.50.2'}}
32+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
33+
// expected-note@-1 {{add 'if #available' version check}}
3134
}
3235
}
3336

34-
func useFooWayOff() {
35-
// expected-note@-1{{add @available attribute to enclosing global function}}
37+
func useFooWayOff() { // expected-note {{add @available attribute to enclosing global function}}
3638
if #available(OSX 10.10, *) {
3739
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
38-
// expected-note@-1{{add 'if #available' version check}}
40+
// expected-note@-1 {{add 'if #available' version check}}
3941
}
4042
}
4143

42-
@available(OSX 10.50, *)
44+
@available(OSX 10.50, *) // expected-note {{narrow nearby version check from '10.50' to '10.50.2'}}
4345
class FooUser {
44-
func useFoo() {
45-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{16-21=10.50.2}}
46+
func useFoo() { // expected-note {{add @available attribute to enclosing instance method}}
47+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
48+
// expected-note@-1 {{add 'if #available' version check}}
4649
}
4750
}
4851

49-
@available(OSX, introduced: 10.50, obsoleted: 10.50.4)
52+
@available(OSX, introduced: 10.50, obsoleted: 10.50.4) // expected-note {{narrow nearby version check from '10.50' to '10.50.2'}}
5053
class FooUser2 {
51-
func useFoo() {
52-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{29-34=10.50.2}}
54+
func useFoo() { // expected-note {{add @available attribute to enclosing instance method}}
55+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
56+
// expected-note@-1 {{add 'if #available' version check}}
5357
}
5458
}
5559

56-
@available(OSX, introduced: 10.50, obsoleted: 10.50.4)
60+
@available(OSX, introduced: 10.50, obsoleted: 10.50.4) // expected-note {{narrow nearby version check from '10.50' to '10.50.2'}}
5761
@objc
5862
class FooUser3 : NSObject {
59-
func useFoo() {
60-
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}} {{29-34=10.50.2}}
63+
func useFoo() { // expected-note {{add @available attribute to enclosing instance method}}
64+
foo() // expected-error {{'foo()' is only available in macOS 10.50.2 or newer}}
65+
// expected-note@-1 {{add 'if #available' version check}}
6166
}
6267
}
6368

test/attr/attr_availability_tvos.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ if #available(iOS 9.3, *) {
5858
// expected-note@-1 {{add 'if #available' version check}}
5959
}
6060

61-
if #available(iOS 9.3, tvOS 9.1, *) {
62-
functionIntroducedOntvOS9_2() // expected-error {{'functionIntroducedOntvOS9_2()' is only available in tvOS 9.2 or newer}} {{29-32=9.2}}
61+
if #available(iOS 9.3, tvOS 9.1, *) { // expected-note {{narrow nearby version check from '9.1' to '9.2'}}
62+
functionIntroducedOntvOS9_2() // expected-error {{'functionIntroducedOntvOS9_2()' is only available in tvOS 9.2 or newer}}
63+
// expected-note@-1 {{add 'if #available' version check}}
6364
}
6465

6566
if #available(iOS 9.1, tvOS 9.2, *) {

test/attr/attr_availability_watchos.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ if #available(iOS 9.3, *) {
5858
// expected-note@-1 {{add 'if #available' version check}}
5959
}
6060

61-
if #available(iOS 9.3, watchOS 2.1, *) {
62-
functionIntroducedOnwatchOS2_2() // expected-error {{'functionIntroducedOnwatchOS2_2()' is only available in watchOS 2.2 or newer}} {{32-35=2.2}}
61+
if #available(iOS 9.3, watchOS 2.1, *) { // expected-note {{narrow nearby version check from '2.1' to '2.2'}}
62+
functionIntroducedOnwatchOS2_2() // expected-error {{'functionIntroducedOnwatchOS2_2()' is only available in watchOS 2.2 or newer}}
63+
// expected-note@-1 {{add 'if #available' version check}}
6364
}
6465

6566
if #available(iOS 9.1, watchOS 2.2, *) {

0 commit comments

Comments
 (0)