Skip to content

Commit 587a9a8

Browse files
authored
Merge pull request #22081 from lorentey/hashvalue-fixit
[Sema] Add dedicated fix-it for NSObject.hashValue overrides
2 parents 982cb0c + 6af814d commit 587a9a8

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
@@ -4245,9 +4245,13 @@ WARNING(non_exhaustive_switch_unknown_only,none,
42454245
"switch covers known cases, but %0 may have additional unknown values"
42464246
"%select{|, possibly added in future versions}1", (Type, bool))
42474247

4248-
WARNING(override_nsobject_hashvalue,none,
4248+
WARNING(override_nsobject_hashvalue_warning,none,
42494249
"override of 'NSObject.hashValue' is deprecated; "
4250-
"override 'NSObject.hash' to get consistent hashing behavior", ())
4250+
"did you mean to override 'NSObject.hash'?", ())
4251+
ERROR(override_nsobject_hashvalue_error,none,
4252+
"'NSObject.hashValue' is not overridable; "
4253+
"did you mean to override 'NSObject.hash'?", ())
4254+
42514255
WARNING(hashvalue_implementation,none,
42524256
"'Hashable.hashValue' is deprecated as a protocol requirement; "
42534257
"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.
@@ -786,9 +802,15 @@ static void checkOverrideAccessControl(ValueDecl *baseDecl, ValueDecl *decl,
786802
baseDecl->getModuleContext() != decl->getModuleContext() &&
787803
!isa<ConstructorDecl>(decl) &&
788804
!isa<ProtocolDecl>(decl->getDeclContext())) {
789-
diags.diagnose(decl, diag::override_of_non_open,
790-
decl->getDescriptiveKind());
791-
805+
// NSObject.hashValue was made non-overridable in Swift 5; one should
806+
// override NSObject.hash instead.
807+
if (isNSObjectHashValue(baseDecl)) {
808+
diags.diagnose(decl, diag::override_nsobject_hashvalue_error)
809+
.fixItReplace(SourceRange(decl->getNameLoc()), "hash");
810+
} else {
811+
diags.diagnose(decl, diag::override_of_non_open,
812+
decl->getDescriptiveKind());
813+
}
792814
} else if (baseHasOpenAccess &&
793815
classDecl->hasOpenAccess(dc) &&
794816
decl->getFormalAccess() != AccessLevel::Open &&
@@ -1526,6 +1548,13 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
15261548
(isa<ExtensionDecl>(base->getDeclContext()) ||
15271549
isa<ExtensionDecl>(override->getDeclContext())) &&
15281550
!base->isObjC()) {
1551+
// Suppress this diagnostic for overrides of a non-open NSObject.hashValue
1552+
// property; these are diagnosed elsewhere. An error message complaining
1553+
// about extensions would be misleading in this case; the correct fix is to
1554+
// override NSObject.hash instead.
1555+
if (isNSObjectHashValue(base) &&
1556+
!base->hasOpenAccess(override->getDeclContext()))
1557+
return true;
15291558
bool baseCanBeObjC = canBeRepresentedInObjC(base);
15301559
diags.diagnose(override, diag::override_decl_extension, baseCanBeObjC,
15311560
!isa<ExtensionDecl>(base->getDeclContext()));
@@ -1626,15 +1655,12 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
16261655

16271656
// Overrides of NSObject.hashValue are deprecated; one should override
16281657
// NSObject.hash instead.
1629-
if (auto baseVar = dyn_cast<VarDecl>(base)) {
1630-
if (auto classDecl = baseVar->getDeclContext()->getSelfClassDecl()) {
1631-
if (baseVar->getName() == ctx.Id_hashValue &&
1632-
classDecl->getName().is("NSObject") &&
1633-
(classDecl->getModuleContext()->getName() == ctx.Id_Foundation ||
1634-
classDecl->getModuleContext()->getName() == ctx.Id_ObjectiveC)) {
1635-
override->diagnose(diag::override_nsobject_hashvalue);
1636-
}
1637-
}
1658+
// FIXME: Remove this when NSObject.hashValue becomes non-open in
1659+
// swift-corelibs-foundation.
1660+
if (isNSObjectHashValue(base) &&
1661+
base->hasOpenAccess(override->getDeclContext())) {
1662+
override->diagnose(diag::override_nsobject_hashvalue_warning)
1663+
.fixItReplace(SourceRange(override->getNameLoc()), "hash");
16381664
}
16391665

16401666
/// 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)