Skip to content

Commit febf5c9

Browse files
committed
[Sema] Change order of displayed overloads in diagnostics
Make it a strict weak order. Fixes llvm#64121. Current implementation uses the definition of ordering from the C++ Standard. The definition provides only a partial order and cannot be used in sorting algorithms. The debug builds of libc++ are capable of detecting that problem and this failure was found when building Clang with libc++ and those extra checks enabled, see llvm#64121. The new ordering is a strict weak order and still pushes most interesting functions to the start of the list. In some cases, it leads to better results, e.g. ``` struct Foo { operator int(); operator const char*(); }; void test() { Foo() - Foo(); } ``` Now produces a list with two most relevant builtin operators at the top, i.e. `operator-(int, int)` and `operator-(const char*, const char*)`. Previously `operator-(const char*, const char*)` was the first element, but `operator-(int, int)` was only the 13th element in the output. This is a consequence of `stable_sort` now being able to compare those two candidates, which are indistinguishable in the semantic partial order despite being two local minimums in their respective comparable subsets. However, new implementation does not take into account some aspects of C++ semantics, e.g. which function template is more specialized. This can also lead to worse ordering sometimes. Reviewed By: #clang-language-wg, aaron.ballman Differential Revision: https://reviews.llvm.org/D159351
1 parent e558be5 commit febf5c9

File tree

3 files changed

+145
-34
lines changed

3 files changed

+145
-34
lines changed

clang/docs/ReleaseNotes.rst

+24
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,30 @@ Improvements to Clang's diagnostics
356356
nonportable construct that has different semantics from a constant-sized
357357
array. (`#62836 <https://github.com/llvm/llvm-project/issues/62836>`_)
358358

359+
- Clang changed the order in which it displays candidate functions on overloading failures.
360+
Previously, Clang used definition of ordering from the C++ Standard. The order defined in
361+
the Standard is partial and is not suited for sorting. Instead, Clang now uses a strict
362+
order that still attempts to push more relevant functions to the top by comparing their
363+
corresponding conversions. In some cases, this results in better order. E.g., for the
364+
following code
365+
366+
.. code-block:: cpp
367+
struct Foo {
368+
operator int();
369+
operator const char*();
370+
};
371+
372+
void test() { Foo() - Foo(); }
373+
374+
Clang now produces a list with two most relevant builtin operators at the top,
375+
i.e. ``operator-(int, int)`` and ``operator-(const char*, const char*)``.
376+
Previously ``operator-(const char*, const char*)`` was the first element,
377+
but ``operator-(int, int)`` was only the 13th element in the output.
378+
However, new implementation does not take into account some aspects of
379+
C++ semantics, e.g. which function template is more specialized. This
380+
can sometimes lead to worse ordering.
381+
382+
359383
Bug Fixes in This Version
360384
-------------------------
361385
- Fixed an issue where a class template specialization whose declaration is

clang/lib/Sema/SemaOverload.cpp

+67-34
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
#include "llvm/ADT/STLExtras.h"
3939
#include "llvm/ADT/SmallPtrSet.h"
4040
#include "llvm/ADT/SmallString.h"
41+
#include "llvm/ADT/SmallVector.h"
4142
#include "llvm/Support/Casting.h"
4243
#include <algorithm>
44+
#include <cstddef>
4345
#include <cstdlib>
4446
#include <optional>
4547

@@ -12017,6 +12019,7 @@ static unsigned RankDeductionFailure(const DeductionFailureInfo &DFI) {
1201712019
}
1201812020

1201912021
namespace {
12022+
1202012023
struct CompareOverloadCandidatesForDisplay {
1202112024
Sema &S;
1202212025
SourceLocation Loc;
@@ -12054,13 +12057,9 @@ struct CompareOverloadCandidatesForDisplay {
1205412057
if (L->Viable) {
1205512058
if (!R->Viable) return true;
1205612059

12057-
// TODO: introduce a tri-valued comparison for overload
12058-
// candidates. Would be more worthwhile if we had a sort
12059-
// that could exploit it.
12060-
if (isBetterOverloadCandidate(S, *L, *R, SourceLocation(), CSK))
12061-
return true;
12062-
if (isBetterOverloadCandidate(S, *R, *L, SourceLocation(), CSK))
12063-
return false;
12060+
if (int Ord = CompareConversions(*L, *R))
12061+
return Ord < 0;
12062+
// Use other tie breakers.
1206412063
} else if (R->Viable)
1206512064
return false;
1206612065

@@ -12112,30 +12111,8 @@ struct CompareOverloadCandidatesForDisplay {
1211212111
}
1211312112

1211412113
// If there's any ordering between the defined conversions...
12115-
// FIXME: this might not be transitive.
12116-
assert(L->Conversions.size() == R->Conversions.size());
12117-
12118-
int leftBetter = 0;
12119-
unsigned I = (L->IgnoreObjectArgument || R->IgnoreObjectArgument);
12120-
for (unsigned E = L->Conversions.size(); I != E; ++I) {
12121-
switch (CompareImplicitConversionSequences(S, Loc,
12122-
L->Conversions[I],
12123-
R->Conversions[I])) {
12124-
case ImplicitConversionSequence::Better:
12125-
leftBetter++;
12126-
break;
12127-
12128-
case ImplicitConversionSequence::Worse:
12129-
leftBetter--;
12130-
break;
12131-
12132-
case ImplicitConversionSequence::Indistinguishable:
12133-
break;
12134-
}
12135-
}
12136-
if (leftBetter > 0) return true;
12137-
if (leftBetter < 0) return false;
12138-
12114+
if (int Ord = CompareConversions(*L, *R))
12115+
return Ord < 0;
1213912116
} else if (RFailureKind == ovl_fail_bad_conversion)
1214012117
return false;
1214112118

@@ -12157,10 +12134,66 @@ struct CompareOverloadCandidatesForDisplay {
1215712134
SourceLocation RLoc = GetLocationForCandidate(R);
1215812135

1215912136
// Put candidates without locations (e.g. builtins) at the end.
12160-
if (LLoc.isInvalid()) return false;
12161-
if (RLoc.isInvalid()) return true;
12137+
if (LLoc.isValid() && RLoc.isValid())
12138+
return S.SourceMgr.isBeforeInTranslationUnit(LLoc, RLoc);
12139+
if (LLoc.isValid() && !RLoc.isValid())
12140+
return true;
12141+
if (RLoc.isValid() && !LLoc.isValid())
12142+
return false;
12143+
assert(!LLoc.isValid() && !RLoc.isValid());
12144+
// For builtins and other functions without locations, fallback to the order
12145+
// in which they were added into the candidate set.
12146+
return L < R;
12147+
}
1216212148

12163-
return S.SourceMgr.isBeforeInTranslationUnit(LLoc, RLoc);
12149+
private:
12150+
struct ConversionSignals {
12151+
unsigned KindRank = 0;
12152+
ImplicitConversionRank Rank = ICR_Exact_Match;
12153+
12154+
static ConversionSignals ForSequence(ImplicitConversionSequence &Seq) {
12155+
ConversionSignals Sig;
12156+
Sig.KindRank = Seq.getKindRank();
12157+
if (Seq.isStandard())
12158+
Sig.Rank = Seq.Standard.getRank();
12159+
else if (Seq.isUserDefined())
12160+
Sig.Rank = Seq.UserDefined.After.getRank();
12161+
// We intend StaticObjectArgumentConversion to compare the same as
12162+
// StandardConversion with ICR_ExactMatch rank.
12163+
return Sig;
12164+
}
12165+
12166+
static ConversionSignals ForObjectArgument() {
12167+
// We intend StaticObjectArgumentConversion to compare the same as
12168+
// StandardConversion with ICR_ExactMatch rank. Default give us that.
12169+
return {};
12170+
}
12171+
};
12172+
12173+
// Returns -1 if conversions in L are considered better.
12174+
// 0 if they are considered indistinguishable.
12175+
// 1 if conversions in R are better.
12176+
int CompareConversions(const OverloadCandidate &L,
12177+
const OverloadCandidate &R) {
12178+
// We cannot use `isBetterOverloadCandidate` because it is defined
12179+
// according to the C++ standard and provides a partial order, but we need
12180+
// a total order as this function is used in sort.
12181+
assert(L.Conversions.size() == R.Conversions.size());
12182+
for (unsigned I = 0, N = L.Conversions.size(); I != N; ++I) {
12183+
auto LS = L.IgnoreObjectArgument && I == 0
12184+
? ConversionSignals::ForObjectArgument()
12185+
: ConversionSignals::ForSequence(L.Conversions[I]);
12186+
auto RS = R.IgnoreObjectArgument
12187+
? ConversionSignals::ForObjectArgument()
12188+
: ConversionSignals::ForSequence(R.Conversions[I]);
12189+
if (std::tie(LS.KindRank, LS.Rank) != std::tie(RS.KindRank, RS.Rank))
12190+
return std::tie(LS.KindRank, LS.Rank) < std::tie(RS.KindRank, RS.Rank)
12191+
? -1
12192+
: 1;
12193+
}
12194+
// FIXME: find a way to compare templates for being more or less
12195+
// specialized that provides a strict weak ordering.
12196+
return 0;
1216412197
}
1216512198
};
1216612199
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify %s
2+
// RUN: not %clang_cc1 -fsyntax-only %s 2>&1 | FileCheck %s
3+
4+
namespace ambig {
5+
struct Foo {
6+
operator int();
7+
operator const char *();
8+
};
9+
10+
11+
void func(const char*, long);
12+
void func(const char*, const char*);
13+
void func(int, int);
14+
15+
bool doit(Foo x) {
16+
func(x, x); // expected-error {{call to 'func' is ambiguous}}
17+
// expected-note@* 3{{candidate}}
18+
// Check that two functions with best conversions are at the top.
19+
// CHECK: error: call to 'func' is ambiguous
20+
// CHECK-NEXT: func(x, x)
21+
// CHECK-NEXT: ^~~~
22+
// CHECK-NEXT: note: candidate function
23+
// CHECK-NEXT: void func(const char*, const char*)
24+
// CHECK-NEXT: ^
25+
// CHECK-NEXT: note: candidate function
26+
// CHECK-NEXT: void func(int, int)
27+
}
28+
}
29+
30+
namespace bad_conversion {
31+
struct Foo {
32+
operator int();
33+
operator const char *();
34+
};
35+
36+
37+
void func(double*, const char*, long);
38+
void func(double*, const char*, const char*);
39+
void func(double*, int, int);
40+
41+
bool doit(Foo x) {
42+
func((int*)0, x, x); // expected-error {{no matching function for call to 'func'}}
43+
// expected-note@* 3{{candidate}}
44+
// Check that two functions with best conversions are at the top.
45+
// CHECK: error: no matching function for call to 'func'
46+
// CHECK-NEXT: func((int*)0, x, x)
47+
// CHECK-NEXT: ^~~~
48+
// CHECK-NEXT: note: candidate function
49+
// CHECK-NEXT: void func(double*, const char*, const char*)
50+
// CHECK-NEXT: ^
51+
// CHECK-NEXT: note: candidate function
52+
// CHECK-NEXT: void func(double*, int, int)
53+
}
54+
}

0 commit comments

Comments
 (0)