Skip to content

Commit 667fe74

Browse files
[Sema][Diagnostics] Add warning of synthesized == to non-synthesized <
Synthesized `==` is composed of member-wise `==`s, which is likely incorrect if the non-synthesized `<` is not composed of member-wise inequalities.
1 parent bbd516f commit 667fe74

File tree

2 files changed

+50
-10
lines changed

2 files changed

+50
-10
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ ERROR(nominal_type_not_attribute,none,
13731373

13741374
ERROR(mutating_invalid_global_scope,none, "%0 is only valid on methods",
13751375
(SelfAccessKind))
1376-
ERROR(mutating_invalid_classes,none, "%0 is not valid on %1s in "
1376+
ERROR(mutating_invalid_classes,none, "%0 is not valid on %1s in "
13771377
"%select{classes|class-bound protocols}2",
13781378
(SelfAccessKind, DescriptiveDeclKind, bool))
13791379

@@ -2547,7 +2547,7 @@ NOTE(overridden_near_match_here,none,
25472547
ERROR(override_decl_extension,none,
25482548
"%select{|non-@objc}0 %2 %3 %select{"
25492549
"is declared in extension of %4 and cannot be overridden|"
2550-
"declared in %4 cannot be overridden from extension}1",
2550+
"declared in %4 cannot be overridden from extension}1",
25512551
(bool, bool, DescriptiveDeclKind, DeclName, DeclName))
25522552
NOTE(overridden_here,none,
25532553
"overridden declaration is here", ())
@@ -2922,6 +2922,14 @@ ERROR(broken_comparable_requirement,none,
29222922
"Comparable protocol is broken: unexpected requirement", ())
29232923
ERROR(broken_equatable_requirement,none,
29242924
"Equatable protocol is broken: unexpected requirement", ())
2925+
WARNING(synthesized_equatable_nonsythesized_comparable_fixit,none,
2926+
"compiler synthesized implementation of operator '==(lhs:rhs:)' "
2927+
"for 'Equatable' conformance for %0 may not match behavior of "
2928+
"custom implementation of operator '<(lhs:rhs:)'",
2929+
(Type))
2930+
FIXIT(insert_equals_function_declaration,
2931+
"static func == (lhs: %0, rhs: $0) -> Bool {\n<#code#>\n}\n\n",
2932+
(Type))
29252933
ERROR(broken_hashable_requirement,none,
29262934
"Hashable protocol is broken: unexpected requirement", ())
29272935
ERROR(broken_hashable_no_hasher,none,
@@ -3021,7 +3029,7 @@ NOTE(automatic_protocol_synthesis_unsupported,none,
30213029
"automatic synthesis of '%0' is not supported for %select{classes|structs}1",
30223030
(StringRef, unsigned))
30233031
NOTE(comparable_synthesis_raw_value_not_allowed, none,
3024-
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
3032+
"enum declares raw type %0, preventing synthesized conformance of %1 to %2",
30253033
(Type, Type, Type))
30263034

30273035
// Dynamic Self
@@ -3512,15 +3520,15 @@ ERROR(unordered_adjacent_operators,none,
35123520
ERROR(missing_builtin_precedence_group,none,
35133521
"broken standard library: missing builtin precedence group %0",
35143522
(Identifier))
3515-
WARNING(nan_comparison, none,
3523+
WARNING(nan_comparison, none,
35163524
"comparison with '.nan' using %0 is always %select{false|true}1, use "
35173525
"'%2.isNaN' to check if '%3' %select{is not a number|is a number}1",
35183526
(Identifier, bool, StringRef, StringRef))
3519-
WARNING(nan_comparison_without_isnan, none,
3520-
"comparison with '.nan' using %0 is always %select{false|true}1",
3527+
WARNING(nan_comparison_without_isnan, none,
3528+
"comparison with '.nan' using %0 is always %select{false|true}1",
35213529
(Identifier, bool))
3522-
WARNING(nan_comparison_both_nan, none,
3523-
"'.nan' %0 '.nan' is always %select{false|true}1",
3530+
WARNING(nan_comparison_both_nan, none,
3531+
"'.nan' %0 '.nan' is always %select{false|true}1",
35243532
(StringRef, bool))
35253533

35263534
// If you change this, also change enum TryKindForDiagnostics.
@@ -4317,7 +4325,7 @@ NOTE(note_add_nonisolated_to_decl,none,
43174325
"add 'nonisolated' to %0 to make this %1 not isolated to the actor",
43184326
(DeclName, DescriptiveDeclKind))
43194327
NOTE(note_add_globalactor_to_function,none,
4320-
"add '@%0' to make %1 %2 part of global actor %3",
4328+
"add '@%0' to make %1 %2 part of global actor %3",
43214329
(StringRef, DescriptiveDeclKind, DeclName, Type))
43224330
FIXIT(insert_globalactor_attr, "@%0 ", (Type))
43234331
ERROR(not_objc_function_async,none,
@@ -4807,7 +4815,7 @@ ERROR(differentiable_function_type_invalid_result,none,
48074815
"%select{| and satisfy '%0 == %0.TangentVector'}1, but the enclosing "
48084816
"function type is '@differentiable%select{|(_linear)}1'",
48094817
(StringRef, bool))
4810-
ERROR(differentiable_function_type_no_differentiability_parameters,
4818+
ERROR(differentiable_function_type_no_differentiability_parameters,
48114819
none,
48124820
"'@differentiable' function type requires at least one differentiability "
48134821
"parameter, i.e. a non-'@noDerivative' parameter whose type conforms to "

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,38 @@ ValueDecl *DerivedConformance::deriveEquatable(ValueDecl *requirement) {
454454

455455
// Build the necessary decl.
456456
if (requirement->getBaseName() == "==") {
457+
auto NominalType = Nominal->getDeclaredInterfaceType();
458+
auto ComparableProtocol =
459+
Context.getProtocol(KnownProtocolKind::Comparable);
460+
auto ComparableConformance = TypeChecker::conformsToProtocol(
461+
NominalType, ComparableProtocol, getParentModule());
462+
463+
// `Comparable` inherits from `Equatable`, but does not provide a default
464+
// implementation of `==`, so for enums and structs, the compiler
465+
// synthesises one from member-wise `==`s (SE-0185). If the user-implemented
466+
// `<` is not composed of member-wise inequalities, then the synthesised
467+
// `==` is almost certainly incorrect.
468+
//
469+
// Warn the user of synthesised `==` if the type has non-synthesised
470+
// `Comparable conformance.
471+
if (ComparableConformance) {
472+
ConcreteDeclRef LessThanFunctionWitness =
473+
ComparableConformance.getWitnessByName(NominalType,
474+
Context.Id_LessThanOperator);
475+
476+
auto LessThanFunctionDeclaration = LessThanFunctionWitness.getDecl();
477+
478+
if (LessThanFunctionDeclaration &&
479+
!LessThanFunctionDeclaration->isImplicit()) {
480+
LessThanFunctionDeclaration
481+
->diagnose(
482+
diag::synthesized_equatable_nonsythesized_comparable_fixit,
483+
NominalType)
484+
.fixItInsert(LessThanFunctionDeclaration->getStartLoc(),
485+
diag::insert_equals_function_declaration, NominalType);
486+
}
487+
}
488+
457489
if (auto ed = dyn_cast<EnumDecl>(Nominal)) {
458490
auto bodySynthesizer =
459491
!ed->hasCases()

0 commit comments

Comments
 (0)