Skip to content

Commit df8eba4

Browse files
committed
[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
1 parent 6c9a724 commit df8eba4

File tree

2 files changed

+44
-14
lines changed

2 files changed

+44
-14
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

0 commit comments

Comments
 (0)