Skip to content

Commit 38e2bd9

Browse files
author
Nathan Hawes
committed
[Diagnostics] Demote availability errors to warning for decls in ObjC keypaths to prevent source breakage.
The change to resolve ObjC #keyPath expression components caused some source breakage as they are now being checked for availability issues. This change updates availability checking to demote error diagnostics to warnings within #keyPath expressions. There were cases in the source compat suite where unavailble properites were used in #keyPath expressions, but they caused no issues at runtime because the properties' ObjC runtime name was still correct (e.g. the same as its renamed-to property in Swift).
1 parent ef6c374 commit 38e2bd9

File tree

5 files changed

+62
-16
lines changed

5 files changed

+62
-16
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4657,13 +4657,24 @@ ERROR(availability_decl_unavailable, none,
46574657
"%select{ in %3|}2%select{|: %4}4",
46584658
(unsigned, DeclName, bool, StringRef, StringRef))
46594659

4660+
WARNING(availability_decl_unavailable_warn, none,
4661+
"%select{getter for |setter for |}0%1 is unavailable"
4662+
"%select{ in %3|}2%select{|: %4}4",
4663+
(unsigned, DeclName, bool, StringRef, StringRef))
4664+
46604665
#define REPLACEMENT_DECL_KIND_SELECT "select{| instance method| property}"
46614666
ERROR(availability_decl_unavailable_rename, none,
46624667
"%select{getter for |setter for |}0%1 has been "
46634668
"%select{renamed to|replaced by}2%" REPLACEMENT_DECL_KIND_SELECT "3 "
46644669
"'%4'%select{|: %5}5",
46654670
(unsigned, DeclName, bool, unsigned, StringRef, StringRef))
46664671

4672+
WARNING(availability_decl_unavailable_rename_warn, none,
4673+
"%select{getter for |setter for |}0%1 has been "
4674+
"%select{renamed to|replaced by}2%" REPLACEMENT_DECL_KIND_SELECT "3 "
4675+
"'%4'%select{|: %5}5",
4676+
(unsigned, DeclName, bool, unsigned, StringRef, StringRef))
4677+
46674678
NOTE(availability_marked_unavailable, none,
46684679
"%select{getter for |setter for |}0%1 has been explicitly marked "
46694680
"unavailable here", (unsigned, DeclName))

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,7 @@ void swift::diagnoseUnavailableOverride(ValueDecl *override,
20672067

20682068
diagnoseExplicitUnavailability(base, override->getLoc(),
20692069
override->getDeclContext(),
2070+
/*Flags*/None,
20702071
[&](InFlightDiagnostic &diag) {
20712072
ParsedDeclName parsedName = parseDeclName(attr->Rename);
20722073
if (!parsedName || parsedName.isPropertyAccessor() ||
@@ -2097,10 +2098,11 @@ void swift::diagnoseUnavailableOverride(ValueDecl *override,
20972098
/// Emit a diagnostic for references to declarations that have been
20982099
/// marked as unavailable, either through "unavailable" or "obsoleted:".
20992100
bool swift::diagnoseExplicitUnavailability(const ValueDecl *D,
2100-
SourceRange R,
2101-
const DeclContext *DC,
2102-
const ApplyExpr *call) {
2103-
return diagnoseExplicitUnavailability(D, R, DC,
2101+
SourceRange R,
2102+
const DeclContext *DC,
2103+
const ApplyExpr *call,
2104+
DeclAvailabilityFlags Flags) {
2105+
return diagnoseExplicitUnavailability(D, R, DC, Flags,
21042106
[=](InFlightDiagnostic &diag) {
21052107
fixItAvailableAttrRename(diag, R, D, AvailableAttr::isUnavailable(D),
21062108
call);
@@ -2172,6 +2174,7 @@ bool swift::diagnoseExplicitUnavailability(
21722174
const ValueDecl *D,
21732175
SourceRange R,
21742176
const DeclContext *DC,
2177+
DeclAvailabilityFlags Flags,
21752178
llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts) {
21762179
auto *Attr = AvailableAttr::isUnavailable(D);
21772180
if (!Attr)
@@ -2229,6 +2232,14 @@ bool swift::diagnoseExplicitUnavailability(
22292232
break;
22302233
}
22312234

2235+
// TODO: Consider removing this.
2236+
// ObjC keypaths components weren't checked previously, so errors are demoted
2237+
// to warnings to avoid source breakage. In some cases unavailable or
2238+
// obsolete decls still map to valid ObjC runtime names, so behave correctly
2239+
// at runtime, even though their use would produce an error outside of a
2240+
// #keyPath expression.
2241+
bool warnInObjCKeyPath = Flags.contains(DeclAvailabilityFlag::ForObjCKeyPath);
2242+
22322243
if (!Attr->Rename.empty()) {
22332244
SmallString<32> newNameBuf;
22342245
Optional<ReplacementDeclKind> replaceKind =
@@ -2238,7 +2249,9 @@ bool swift::diagnoseExplicitUnavailability(
22382249
StringRef newName = replaceKind ? newNameBuf.str() : Attr->Rename;
22392250
EncodedDiagnosticMessage EncodedMessage(Attr->Message);
22402251
auto diag =
2241-
diags.diagnose(Loc, diag::availability_decl_unavailable_rename,
2252+
diags.diagnose(Loc, warnInObjCKeyPath
2253+
? diag::availability_decl_unavailable_rename_warn
2254+
: diag::availability_decl_unavailable_rename,
22422255
RawAccessorKind, Name, replaceKind.hasValue(),
22432256
rawReplaceKind, newName, EncodedMessage.Message);
22442257
attachRenameFixIts(diag);
@@ -2253,7 +2266,9 @@ bool swift::diagnoseExplicitUnavailability(
22532266
} else {
22542267
EncodedDiagnosticMessage EncodedMessage(Attr->Message);
22552268
diags
2256-
.diagnose(Loc, diag::availability_decl_unavailable, RawAccessorKind,
2269+
.diagnose(Loc, warnInObjCKeyPath
2270+
? diag::availability_decl_unavailable_warn
2271+
: diag::availability_decl_unavailable, RawAccessorKind,
22572272
Name, platform.empty(), platform, EncodedMessage.Message)
22582273
.highlight(R);
22592274
}
@@ -2501,14 +2516,18 @@ class AvailabilityWalker : public ASTWalker {
25012516
/// Walk a keypath expression, checking all of its components for
25022517
/// availability.
25032518
void maybeDiagKeyPath(KeyPathExpr *KP) {
2519+
auto flags = DeclAvailabilityFlags();
2520+
if (KP->isObjC())
2521+
flags = DeclAvailabilityFlag::ForObjCKeyPath;
2522+
25042523
for (auto &component : KP->getComponents()) {
25052524
switch (component.getKind()) {
25062525
case KeyPathExpr::Component::Kind::Property:
25072526
case KeyPathExpr::Component::Kind::Subscript: {
25082527
auto *decl = component.getDeclRef().getDecl();
25092528
auto loc = component.getLoc();
25102529
SourceRange range(loc, loc);
2511-
diagAvailability(decl, range, nullptr);
2530+
diagAvailability(decl, range, nullptr, flags);
25122531
break;
25132532
}
25142533

@@ -2628,7 +2647,7 @@ AvailabilityWalker::diagAvailability(ConcreteDeclRef declRef, SourceRange R,
26282647
if (TypeChecker::diagnoseInlinableDeclRef(R.Start, declRef, DC, FragileKind))
26292648
return true;
26302649

2631-
if (diagnoseExplicitUnavailability(D, R, DC, call))
2650+
if (diagnoseExplicitUnavailability(D, R, DC, call, Flags))
26322651
return true;
26332652

26342653
// Make sure not to diagnose an accessor's deprecation if we already

lib/Sema/TypeCheckAvailability.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ enum class DeclAvailabilityFlag : uint8_t {
5050
/// Do not diagnose uses of declarations in versions before they were
5151
/// introduced. Used to work around availability-checker bugs.
5252
AllowPotentiallyUnavailable = 1 << 3,
53+
54+
/// If an error diagnostic would normally be emitted, demote the error to a
55+
/// warning. Used for ObjC key path components.
56+
ForObjCKeyPath = 1 << 4
5357
};
5458
using DeclAvailabilityFlags = OptionSet<DeclAvailabilityFlag>;
5559

@@ -70,14 +74,16 @@ void diagnoseUnavailableOverride(ValueDecl *override,
7074
bool diagnoseExplicitUnavailability(const ValueDecl *D,
7175
SourceRange R,
7276
const DeclContext *DC,
73-
const ApplyExpr *call);
77+
const ApplyExpr *call,
78+
DeclAvailabilityFlags Flags = None);
7479

7580
/// Emit a diagnostic for references to declarations that have been
7681
/// marked as unavailable, either through "unavailable" or "obsoleted:".
7782
bool diagnoseExplicitUnavailability(
7883
const ValueDecl *D,
7984
SourceRange R,
8085
const DeclContext *DC,
86+
DeclAvailabilityFlags Flags,
8187
llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts);
8288

8389
/// Check if \p decl has a introduction version required by -require-explicit-availability

localization/diagnostics/en.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9232,11 +9232,21 @@
92329232
%select{getter for |setter for |}0%1 is unavailable%select{ in
92339233
%3|}2%select{|: %4}4
92349234
9235+
- id: availability_decl_unavailable_warn
9236+
msg: >-
9237+
%select{getter for |setter for |}0%1 is unavailable%select{ in
9238+
%3|}2%select{|: %4}4
9239+
92359240
- id: availability_decl_unavailable_rename
92369241
msg: >-
92379242
%select{getter for |setter for |}0%1 has been %select{renamed to|replaced
92389243
by}2%select{| instance method| property}3 '%4'%select{|: %5}5
92399244
9245+
- id: availability_decl_unavailable_rename_warn
9246+
msg: >-
9247+
%select{getter for |setter for |}0%1 has been %select{renamed to|replaced
9248+
by}2%select{| instance method| property}3 '%4'%select{|: %5}5
9249+
92409250
- id: availability_marked_unavailable
92419251
msg: >-
92429252
%select{getter for |setter for |}0%1 has been explicitly marked

test/expr/primary/keypath/keypath-objc.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func testKeyPath(a: A, b: B) {
5757
let _: String = #keyPath(A.propString)
5858

5959
// Property of String property (which looks on NSString)
60-
let _: String = #keyPath(A.propString.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
60+
let _: String = #keyPath(A.propString.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
6161

6262
// String property with a suffix
6363
let _: String = #keyPath(A.propString).description
@@ -72,36 +72,36 @@ func testKeyPath(a: A, b: B) {
7272

7373
// Array property (make sure we look at the array element).
7474
let _: String = #keyPath(A.propArray)
75-
let _: String = #keyPath(A.propArray.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
75+
let _: String = #keyPath(A.propArray.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
7676

7777
// Dictionary property (make sure we look at the value type).
7878
let _: String = #keyPath(A.propDict.anyKeyName)
7979
let _: String = #keyPath(A.propDict.anyKeyName.propA)
8080

8181
// Set property (make sure we look at the set element).
8282
let _: String = #keyPath(A.propSet)
83-
let _: String = #keyPath(A.propSet.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
83+
let _: String = #keyPath(A.propSet.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
8484

8585
// AnyObject property
86-
let _: String = #keyPath(A.propAnyObject.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
86+
let _: String = #keyPath(A.propAnyObject.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
8787
let _: String = #keyPath(A.propAnyObject.propA)
8888
let _: String = #keyPath(A.propAnyObject.propB)
8989
let _: String = #keyPath(A.propAnyObject.description)
9090

9191
// NSString property
92-
let _: String = #keyPath(A.propNSString.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
92+
let _: String = #keyPath(A.propNSString.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
9393

9494
// NSArray property (AnyObject array element).
9595
let _: String = #keyPath(A.propNSArray)
96-
let _: String = #keyPath(A.propNSArray.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
96+
let _: String = #keyPath(A.propNSArray.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
9797

9898
// NSDictionary property (AnyObject value type).
9999
let _: String = #keyPath(A.propNSDict.anyKeyName)
100100
let _: String = #keyPath(A.propNSDict.anyKeyName.propA)
101101

102102
// NSSet property (AnyObject set element).
103103
let _: String = #keyPath(A.propNSSet)
104-
let _: String = #keyPath(A.propNSSet.URLsInText) // expected-error{{'URLsInText' has been renamed to 'urlsInText'}}
104+
let _: String = #keyPath(A.propNSSet.URLsInText) // expected-warning{{'URLsInText' has been renamed to 'urlsInText'}}
105105

106106
// Property with keyword name.
107107
let _: String = #keyPath(A.repeat)

0 commit comments

Comments
 (0)