Skip to content

Commit f86388c

Browse files
lorenteyairspeedswift
authored andcommitted
[5.0][Sema] Add dedicated fix-it for NSObject.hashValue overrides (#22173)
* [Sema] Add dedicated fix-it for NSObject.hashValue overrides NSObject.hashValue used to be declared `@objc open` by historical accident. This has been corrected to `@nonobjc public` in Swift 5’s SDK overlays, to catch accidental overrides. (These never did work correctly, and shouldn’t have been allowed.) Help migration by adding a dedicated error message for NSObject.hashValue overrides, with a nice fix-it. rdar://problem/45674813 (cherry picked from commit df8eba4) * [test] Test new fix-it for NSObject.hashValue overrides (cherry picked from commit 6af814d)
1 parent 822dc02 commit f86388c

File tree

6 files changed

+79
-17
lines changed

6 files changed

+79
-17
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4281,9 +4281,13 @@ WARNING(non_exhaustive_switch_unknown_only,none,
42814281
"switch covers known cases, but %0 may have additional unknown values"
42824282
"%select{|, possibly added in future versions}1", (Type, bool))
42834283

4284-
WARNING(override_nsobject_hashvalue,none,
4284+
WARNING(override_nsobject_hashvalue_warning,none,
42854285
"override of 'NSObject.hashValue' is deprecated; "
4286-
"override 'NSObject.hash' to get consistent hashing behavior", ())
4286+
"did you mean to override 'NSObject.hash'?", ())
4287+
ERROR(override_nsobject_hashvalue_error,none,
4288+
"'NSObject.hashValue' is not overridable; "
4289+
"did you mean to override 'NSObject.hash'?", ())
4290+
42874291
WARNING(hashvalue_implementation,none,
42884292
"'Hashable.hashValue' is deprecated as a protocol requirement; "
42894293
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,22 @@ static bool parameterTypesMatch(const ValueDecl *derivedDecl,
540540
return true;
541541
}
542542

543+
/// Returns true if the given declaration is for the `NSObject.hashValue`
544+
/// property.
545+
static bool isNSObjectHashValue(ValueDecl *baseDecl) {
546+
ASTContext &ctx = baseDecl->getASTContext();
547+
548+
if (auto baseVar = dyn_cast<VarDecl>(baseDecl)) {
549+
if (auto classDecl = baseVar->getDeclContext()->getSelfClassDecl()) {
550+
return baseVar->getName() == ctx.Id_hashValue &&
551+
classDecl->getName().is("NSObject") &&
552+
(classDecl->getModuleContext()->getName() == ctx.Id_Foundation ||
553+
classDecl->getModuleContext()->getName() == ctx.Id_ObjectiveC);
554+
}
555+
}
556+
return false;
557+
}
558+
543559
namespace {
544560
/// Class that handles the checking of a particular declaration against
545561
/// superclass entities that it could override.
@@ -826,9 +842,15 @@ bool OverrideMatcher::checkOverride(ValueDecl *baseDecl,
826842
baseDecl->getModuleContext() != decl->getModuleContext() &&
827843
!isa<ConstructorDecl>(decl) &&
828844
!isa<ProtocolDecl>(decl->getDeclContext())) {
829-
diags.diagnose(decl, diag::override_of_non_open,
830-
decl->getDescriptiveKind());
831-
845+
// NSObject.hashValue was made non-overridable in Swift 5; one should
846+
// override NSObject.hash instead.
847+
if (isNSObjectHashValue(baseDecl)) {
848+
diags.diagnose(decl, diag::override_nsobject_hashvalue_error)
849+
.fixItReplace(SourceRange(decl->getNameLoc()), "hash");
850+
} else {
851+
diags.diagnose(decl, diag::override_of_non_open,
852+
decl->getDescriptiveKind());
853+
}
832854
} else if (baseHasOpenAccess &&
833855
classDecl->hasOpenAccess(dc) &&
834856
decl->getFormalAccess() != AccessLevel::Open &&
@@ -1516,6 +1538,13 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
15161538
(isa<ExtensionDecl>(base->getDeclContext()) ||
15171539
isa<ExtensionDecl>(override->getDeclContext())) &&
15181540
!base->isObjC()) {
1541+
// Suppress this diagnostic for overrides of a non-open NSObject.hashValue
1542+
// property; these are diagnosed elsewhere. An error message complaining
1543+
// about extensions would be misleading in this case; the correct fix is to
1544+
// override NSObject.hash instead.
1545+
if (isNSObjectHashValue(base) &&
1546+
!base->hasOpenAccess(override->getDeclContext()))
1547+
return true;
15191548
bool baseCanBeObjC = canBeRepresentedInObjC(base);
15201549
diags.diagnose(override, diag::override_decl_extension, baseCanBeObjC,
15211550
!isa<ExtensionDecl>(base->getDeclContext()));
@@ -1616,15 +1645,12 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
16161645

16171646
// Overrides of NSObject.hashValue are deprecated; one should override
16181647
// NSObject.hash instead.
1619-
if (auto baseVar = dyn_cast<VarDecl>(base)) {
1620-
if (auto classDecl = baseVar->getDeclContext()->getSelfClassDecl()) {
1621-
if (baseVar->getName() == ctx.Id_hashValue &&
1622-
classDecl->getName().is("NSObject") &&
1623-
(classDecl->getModuleContext()->getName() == ctx.Id_Foundation ||
1624-
classDecl->getModuleContext()->getName() == ctx.Id_ObjectiveC)) {
1625-
override->diagnose(diag::override_nsobject_hashvalue);
1626-
}
1627-
}
1648+
// FIXME: Remove this when NSObject.hashValue becomes non-open in
1649+
// swift-corelibs-foundation.
1650+
if (isNSObjectHashValue(base) &&
1651+
base->hasOpenAccess(override->getDeclContext())) {
1652+
override->diagnose(diag::override_nsobject_hashvalue_warning)
1653+
.fixItReplace(SourceRange(override->getNameLoc()), "hash");
16281654
}
16291655

16301656
/// Check attributes associated with the base; some may need to merged with

test/ClangImporter/objc_override.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ class CallbackSubC : CallbackBase {
110110

111111
//
112112
class MyHashableNSObject: NSObject {
113-
override var hashValue: Int { // expected-error{{overriding non-open property outside of its defining module}} expected-error{{overriding non-@objc declarations from extensions is not supported}}
113+
override var hashValue: Int {
114+
// expected-error@-1 {{'NSObject.hashValue' is not overridable; did you mean to override 'NSObject.hash'?}}
114115
return 0
115116
}
116117
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/fixit-NSObject-hashValue.result
3+
// RUN: diff -u %s.expected %t/fixit-NSObject-hashValue.result
4+
// RUN: %target-swift-frontend -typecheck %s.expected
5+
6+
// REQUIRES: objc_interop
7+
8+
import ObjectiveC
9+
10+
class MyClass: NSObject {
11+
override var hashValue: Int {
12+
return 42
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: not %target-swift-frontend -typecheck -update-code -primary-file %s -emit-migrated-file-path %t/fixit-NSObject-hashValue.result
3+
// RUN: diff -u %s.expected %t/fixit-NSObject-hashValue.result
4+
// RUN: %target-swift-frontend -typecheck %s.expected
5+
6+
// REQUIRES: objc_interop
7+
8+
import ObjectiveC
9+
10+
class MyClass: NSObject {
11+
override var hash: Int {
12+
return 42
13+
}
14+
}

test/stdlib/NSObject-hashing.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
import ObjectiveC
77

88
class Foo: NSObject {
9-
override var hashValue: Int { // expected-error {{overriding non-open property outside of its defining module}} expected-error {{overriding non-@objc declarations from extensions is not supported}}
9+
override var hashValue: Int {
10+
// expected-error@-1 {{'NSObject.hashValue' is not overridable; did you mean to override 'NSObject.hash'?}}
1011
return 0
1112
}
1213

13-
override func hash(into hasher: inout Hasher) { // expected-error {{overriding non-open instance method outside of its defining module}} expected-error {{overriding declarations in extensions is not supported}}
14+
override func hash(into hasher: inout Hasher) {
15+
// expected-error@-1 {{overriding non-open instance method outside of its defining module}}
16+
// expected-error@-2 {{overriding declarations in extensions is not supported}}
1417
}
1518
}

0 commit comments

Comments
 (0)