Skip to content

Commit bfa6423

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". Tests have been updated accordingly.
1 parent 680074a commit bfa6423

6 files changed

+108
-122
lines changed

include/swift/AST/DiagnosticsSema.def

+4
Original file line numberDiff line numberDiff line change
@@ -6107,6 +6107,10 @@ FIXIT(insert_available_attr,
61076107
"@available(%0 %1, *)\n%2",
61086108
(StringRef, StringRef, StringRef))
61096109

6110+
NOTE(availability_narrow_nearby_version_check, none,
6111+
"narrow nearby version check from '%0' to '%1'",
6112+
(StringRef, StringRef))
6113+
61106114
ERROR(availability_accessor_only_version_newer, none,
61116115
"%select{getter|setter}0 for %1 is only available in %2 %3"
61126116
" or newer",

lib/Sema/TypeCheckAvailability.cpp

+58-92
Original file line numberDiff line numberDiff line change
@@ -1851,11 +1851,8 @@ static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
18511851
/// version), emit a diagnostic and fixit that narrows the existing TRC
18521852
/// condition to the required range.
18531853
static bool fixAvailabilityByNarrowingNearbyVersionCheck(
1854-
SourceRange ReferenceRange,
1855-
const DeclContext *ReferenceDC,
1856-
const VersionRange &RequiredRange,
1857-
ASTContext &Context,
1858-
InFlightDiagnostic &Err) {
1854+
SourceRange ReferenceRange, const DeclContext *ReferenceDC,
1855+
const VersionRange &RequiredRange, ASTContext &Context) {
18591856
const TypeRefinementContext *TRC = nullptr;
18601857
(void)TypeChecker::overApproximateAvailabilityAtLocation(ReferenceRange.Start,
18611858
ReferenceDC, &TRC);
@@ -1889,8 +1886,13 @@ static bool fixAvailabilityByNarrowingNearbyVersionCheck(
18891886
Platform, RunningVers);
18901887
if (!FixRange.isValid())
18911888
return false;
1889+
18921890
// Have found a nontrivial type refinement context-introducer to narrow.
1893-
Err.fixItReplace(FixRange, RequiredVers.getAsString());
1891+
Context.Diags
1892+
.diagnose(FixRange.Start,
1893+
diag::availability_narrow_nearby_version_check,
1894+
RunningVers.getAsString(), RequiredVers.getAsString())
1895+
.fixItReplace(FixRange, RequiredVers.getAsString());
18941896
return true;
18951897
}
18961898
return false;
@@ -1966,14 +1968,17 @@ static void fixAvailabilityByAddingVersionCheck(
19661968
}
19671969

19681970
/// Emit suggested Fix-Its for a reference with to an unavailable symbol
1969-
/// requiting the given OS version range.
1971+
/// requiring the given OS version range.
19701972
static void fixAvailability(SourceRange ReferenceRange,
19711973
const DeclContext *ReferenceDC,
19721974
const VersionRange &RequiredRange,
19731975
ASTContext &Context) {
19741976
if (ReferenceRange.isInvalid())
19751977
return;
19761978

1979+
fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange, ReferenceDC,
1980+
RequiredRange, Context);
1981+
19771982
Optional<ASTNode> NodeToWrapInVersionCheck;
19781983
const Decl *FoundMemberDecl = nullptr;
19791984
const Decl *FoundTypeLevelDecl = nullptr;
@@ -2006,19 +2011,12 @@ void TypeChecker::diagnosePotentialOpaqueTypeUnavailability(
20062011
ASTContext &Context = ReferenceDC->getASTContext();
20072012

20082013
auto RequiredRange = Reason.getRequiredOSVersionRange();
2009-
{
2010-
auto Err =
2011-
Context.Diags.diagnose(
2012-
ReferenceRange.Start, diag::availability_opaque_types_only_version_newer,
2013-
prettyPlatformString(targetPlatform(Context.LangOpts)),
2014-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2015-
2016-
// Direct a fixit to the error if an existing guard is nearly-correct
2017-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
2018-
ReferenceDC,
2019-
RequiredRange, Context, Err))
2020-
return;
2021-
}
2014+
2015+
Context.Diags.diagnose(ReferenceRange.Start,
2016+
diag::availability_opaque_types_only_version_newer,
2017+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2018+
RequiredRange.getLowerEndpoint());
2019+
20222020
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
20232021
}
20242022

@@ -2028,20 +2026,12 @@ static void diagnosePotentialConcurrencyUnavailability(
20282026
ASTContext &Context = ReferenceDC->getASTContext();
20292027

20302028
auto RequiredRange = Reason.getRequiredOSVersionRange();
2031-
{
2032-
auto Err =
2033-
Context.Diags.diagnose(
2034-
ReferenceRange.Start,
2035-
diag::availability_concurrency_only_version_newer,
2036-
prettyPlatformString(targetPlatform(Context.LangOpts)),
2037-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2038-
2039-
// Direct a fixit to the error if an existing guard is nearly-correct
2040-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
2041-
ReferenceDC,
2042-
RequiredRange, Context, Err))
2043-
return;
2044-
}
2029+
2030+
Context.Diags.diagnose(ReferenceRange.Start,
2031+
diag::availability_concurrency_only_version_newer,
2032+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2033+
RequiredRange.getLowerEndpoint());
2034+
20452035
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
20462036
}
20472037

@@ -2097,21 +2087,15 @@ bool TypeChecker::diagnosePotentialUnavailability(
20972087
bool WarnBeforeDeploymentTarget = false) {
20982088
ASTContext &Context = ReferenceDC->getASTContext();
20992089

2100-
auto RequiredRange = Reason.getRequiredOSVersionRange();
21012090
bool IsError;
2102-
{
2103-
auto Diag = Context.Diags.diagnose(
2104-
ReferenceRange.Start,
2105-
getPotentialUnavailabilityDiagnostic(
2106-
D, ReferenceDC, Reason, WarnBeforeDeploymentTarget, IsError));
2107-
2108-
// Direct a fixit to the error if an existing guard is nearly-correct
2109-
if (fixAvailabilityByNarrowingNearbyVersionCheck(
2110-
ReferenceRange, ReferenceDC, RequiredRange, Context, Diag))
2111-
return IsError;
2112-
}
2091+
Context.Diags.diagnose(
2092+
ReferenceRange.Start,
2093+
getPotentialUnavailabilityDiagnostic(
2094+
D, ReferenceDC, Reason, WarnBeforeDeploymentTarget, IsError));
21132095

2096+
auto RequiredRange = Reason.getRequiredOSVersionRange();
21142097
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2098+
21152099
return IsError;
21162100
}
21172101

@@ -2126,25 +2110,15 @@ void TypeChecker::diagnosePotentialAccessorUnavailability(
21262110
const AbstractStorageDecl *ASD = Accessor->getStorage();
21272111
DeclName Name = ASD->getName();
21282112

2113+
auto RequiredRange = Reason.getRequiredOSVersionRange();
21292114
auto &diag = ForInout ? diag::availability_inout_accessor_only_version_newer
21302115
: diag::availability_accessor_only_version_newer;
21312116

2132-
auto RequiredRange = Reason.getRequiredOSVersionRange();
2133-
{
2134-
auto Err =
2135-
Context.Diags.diagnose(
2136-
ReferenceRange.Start, diag,
2137-
static_cast<unsigned>(Accessor->getAccessorKind()), Name,
2138-
prettyPlatformString(targetPlatform(Context.LangOpts)),
2139-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2140-
2141-
2142-
// Direct a fixit to the error if an existing guard is nearly-correct
2143-
if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
2144-
ReferenceDC,
2145-
RequiredRange, Context, Err))
2146-
return;
2147-
}
2117+
Context.Diags.diagnose(ReferenceRange.Start, diag,
2118+
static_cast<unsigned>(Accessor->getAccessorKind()),
2119+
Name,
2120+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2121+
RequiredRange.getLowerEndpoint());
21482122

21492123
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
21502124
}
@@ -2172,33 +2146,29 @@ void TypeChecker::diagnosePotentialUnavailability(
21722146
const RootProtocolConformance *rootConf,
21732147
const ExtensionDecl *ext,
21742148
SourceLoc loc,
2175-
const DeclContext *dc,
2176-
const UnavailabilityReason &reason) {
2177-
ASTContext &ctx = dc->getASTContext();
2149+
const DeclContext *ReferenceDC,
2150+
const UnavailabilityReason &Reason) {
2151+
ASTContext &Context = ReferenceDC->getASTContext();
21782152

2179-
auto requiredRange = reason.getRequiredOSVersionRange();
2153+
auto RequiredRange = Reason.getRequiredOSVersionRange();
21802154
{
21812155
auto type = rootConf->getType();
21822156
auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();
21832157

2184-
auto diagID = (ctx.LangOpts.EnableConformanceAvailabilityErrors
2185-
? diag::conformance_availability_only_version_newer
2186-
: diag::conformance_availability_only_version_newer_warn);
2187-
auto behavior = behaviorLimitForExplicitUnavailability(rootConf, dc);
2188-
auto err =
2189-
ctx.Diags.diagnose(
2190-
loc, diagID,
2191-
type, proto, prettyPlatformString(targetPlatform(ctx.LangOpts)),
2192-
reason.getRequiredOSVersionRange().getLowerEndpoint());
2158+
const auto &diag =
2159+
(Context.LangOpts.EnableConformanceAvailabilityErrors
2160+
? diag::conformance_availability_only_version_newer
2161+
: diag::conformance_availability_only_version_newer_warn);
2162+
auto behavior =
2163+
behaviorLimitForExplicitUnavailability(rootConf, ReferenceDC);
2164+
auto err = Context.Diags.diagnose(
2165+
loc, diag, type, proto,
2166+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2167+
RequiredRange.getLowerEndpoint());
21932168
err.limitBehavior(behavior);
2194-
2195-
// Direct a fixit to the error if an existing guard is nearly-correct
2196-
if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc,
2197-
requiredRange, ctx, err))
2198-
return;
21992169
}
22002170

2201-
fixAvailability(loc, dc, requiredRange, ctx);
2171+
fixAvailability(loc, ReferenceDC, RequiredRange, Context);
22022172
}
22032173

22042174
const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
@@ -2993,20 +2963,16 @@ static bool diagnosePotentialParameterizedProtocolUnavailability(
29932963
const UnavailabilityReason &Reason) {
29942964
ASTContext &Context = ReferenceDC->getASTContext();
29952965

2966+
Context.Diags.diagnose(
2967+
ReferenceRange.Start,
2968+
diag::availability_parameterized_protocol_only_version_newer,
2969+
prettyPlatformString(targetPlatform(Context.LangOpts)),
2970+
Reason.getRequiredOSVersionRange().getLowerEndpoint());
2971+
29962972
auto RequiredRange = Reason.getRequiredOSVersionRange();
2997-
{
2998-
auto Err = Context.Diags.diagnose(
2999-
ReferenceRange.Start,
3000-
diag::availability_parameterized_protocol_only_version_newer,
3001-
prettyPlatformString(targetPlatform(Context.LangOpts)),
3002-
Reason.getRequiredOSVersionRange().getLowerEndpoint());
30032973

3004-
// Direct a fixit to the error if an existing guard is nearly-correct
3005-
if (fixAvailabilityByNarrowingNearbyVersionCheck(
3006-
ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
3007-
return true;
3008-
}
30092974
fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
2975+
30102976
return true;
30112977
}
30122978

test/Sema/availability_versions_multi.swift

+16-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,25 +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}} {{26:29-34=99.52}}
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}}
3639

3740
_ = OtherIntroduced99_52()
38-
// expected-error@-1 {{'OtherIntroduced99_52' is only available in macOS 99.52 or newer}} {{26:29-34=99.52}}
41+
// expected-error@-1 {{'OtherIntroduced99_52' is only available in macOS 99.52 or newer}}
42+
// expected-note@-2 {{add 'if #available' version check}}
3943

40-
o99_51.extensionMethodOnOtherIntroduced99_51AvailableOn99_52()
41-
// expected-error@-1 {{'extensionMethodOnOtherIntroduced99_51AvailableOn99_52()' is only available in macOS 99.52 or newer}} {{26:29-34=99.52}}
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}}
4247

4348
_ = OtherIntroduced99_51.NestedIntroduced99_52()
44-
// expected-error@-1 {{'NestedIntroduced99_52' is only available in macOS 99.52 or newer}} {{26:29-34=99.52}}
49+
// expected-error@-1 {{'NestedIntroduced99_52' is only available in macOS 99.52 or newer}}
50+
// expected-note@-2 {{add 'if #available' version check}}
4551
}
4652

47-
@available(OSX, introduced: 99.52)
53+
@available(OSX, introduced: 99.52) // expected-note {{narrow nearby version check}} {{29-34=99.53}}
4854
func useFromOtherOn99_52() {
4955
_ = OtherIntroduced99_52()
5056

5157
let n99_52 = OtherIntroduced99_51.NestedIntroduced99_52()
5258
_ = n99_52.returns99_52()
53-
_ = n99_52.returns99_53() // expected-error {{'returns99_53()' is only available in macOS 99.53 or newer}} {{47:29-34=99.53}}
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}}
5462

5563
// This will trigger validation of the global in availability_in_multi_other.swift
5664
_ = globalFromOtherOn99_52

test/attr/attr_availability_narrow.swift

+24-18
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}} {{-1: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'}} {{23-30=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}} {{-1: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'}} {{21-28=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,9 +27,10 @@ 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}} {{-1: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'}} {{35-42=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

@@ -39,25 +42,28 @@ func useFooWayOff() {
3942
}
4043
}
4144

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

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

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

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}} {{-1: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'}} {{29-32=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}} {{-1: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'}} {{32-35=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)