Skip to content

Commit 317c932

Browse files
authored
Suppress errors from well-formed-testing type traits in SFINAE contexts (#135390)
There are several type traits that produce a boolean value or type based on the well-formedness of some expression (more precisely, the immediate context, i.e. for example excluding nested template instantiation): * `__is_constructible` and variants, * `__is_convertible` and variants, * `__is_assignable` and variants, * `__reference_{binds_to,{constructs,converts}_from}_temporary`, * `__is_trivially_equality_comparable`, * `__builtin_common_type`. (It should be noted that the standard doesn't always base this on the immediate context being well-formed: for `std::common_type` it's based on whether some expression "denotes a valid type." But I assume that's an editorial issue and means the same thing.) Errors in the immediate context are suppressed, instead the type traits return another value or produce a different type if the expression is not well-formed. This is achieved using an `SFINAETrap` with `AccessCheckingSFINAE` set to true. If the type trait is used outside of an SFINAE context, errors are discarded because in that case the `SFINAETrap` sets `InNonInstantiationSFINAEContext`, which makes `isSFINAEContext` return an `optional(nullptr)`, which causes the errors to be discarded in `EmitDiagnostic`. However, in an SFINAE context this doesn't happen, and errors are added to `SuppressedDiagnostics` in the `TemplateDeductionInfo` returned by `isSFINAEContext`. Once we're done with deducing template arguments and have decided which template is going to be instantiated, the errors corresponding to the chosen template are then emitted. At this point we get errors from those type traits that we wouldn't have seen if used with the same arguments outside of an SFINAE context. That doesn't seem right. So what we want to do is always set `InNonInstantiationSFINAEContext` when evaluating these well-formed-testing type traits, regardless of whether we're in an SFINAE context or not. This should only affect the immediate context, as nested contexts add a new `CodeSynthesisContext` that resets `InNonInstantiationSFINAEContext` for the time it's active. Going through uses of `SFINAETrap` with `AccessCheckingSFINAE` = `true`, it occurred to me that all of them want this behavior and we can just use this parameter to decide whether to use a non-instantiation context. The uses are precisely the type traits mentioned above plus the `TentativeAnalysisScope`, where I think it is also fine. (Though I think we don't do tentative analysis in SFINAE contexts anyway.) Because the parameter no longer just sets `AccessCheckingSFINAE` in Sema but also `InNonInstantiationSFINAEContext`, I think it should be renamed (along with uses, which also point the reviewer to the affected places). Since we're testing for validity of some expression, `ForValidityCheck` seems to be a good name. The added tests should more or less correspond to the users of `SFINAETrap` with `AccessCheckingSFINAE` = `true`. I added a test for errors outside of the immediate context for only one type trait, because it requires some setup and is relatively noisy. We put the `ForValidityCheck` condition first because it's constant in all uses and this would then allow the compiler to prune the call to `isSFINAEContext` when true. Fixes #132044.
1 parent 3f196e0 commit 317c932

File tree

7 files changed

+95
-11
lines changed

7 files changed

+95
-11
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,19 @@ Bug Fixes to C++ Support
683683
- Clang is now better at keeping track of friend function template instance contexts. (#GH55509)
684684
- Clang now prints the correct instantiation context for diagnostics suppressed
685685
by template argument deduction.
686+
- Errors that occur during evaluation of certain type traits and builtins are
687+
no longer incorrectly emitted when they are used in an SFINAE context. The
688+
type traits are:
689+
690+
- ``__is_constructible`` and variants,
691+
- ``__is_convertible`` and variants,
692+
- ``__is_assignable`` and variants,
693+
- ``__reference_binds_to_temporary``,
694+
``__reference_constructs_from_temporary``,
695+
``__reference_converts_from_temporary``,
696+
- ``__is_trivially_equality_comparable``.
697+
698+
The builtin is ``__builtin_common_type``. (#GH132044)
686699
- Clang is now better at instantiating the function definition after its use inside
687700
of a constexpr lambda. (#GH125747)
688701
- Fixed a local class member function instantiation bug inside dependent lambdas. (#GH59734), (#GH132208)

clang/include/clang/Sema/Sema.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12365,16 +12365,19 @@ class Sema final : public SemaBase {
1236512365
bool PrevLastDiagnosticIgnored;
1236612366

1236712367
public:
12368-
explicit SFINAETrap(Sema &SemaRef, bool AccessCheckingSFINAE = false)
12368+
/// \param ForValidityCheck If true, discard all diagnostics (from the
12369+
/// immediate context) instead of adding them to the currently active
12370+
/// \ref TemplateDeductionInfo (as returned by \ref isSFINAEContext).
12371+
explicit SFINAETrap(Sema &SemaRef, bool ForValidityCheck = false)
1236912372
: SemaRef(SemaRef), PrevSFINAEErrors(SemaRef.NumSFINAEErrors),
1237012373
PrevInNonInstantiationSFINAEContext(
1237112374
SemaRef.InNonInstantiationSFINAEContext),
1237212375
PrevAccessCheckingSFINAE(SemaRef.AccessCheckingSFINAE),
1237312376
PrevLastDiagnosticIgnored(
1237412377
SemaRef.getDiagnostics().isLastDiagnosticIgnored()) {
12375-
if (!SemaRef.isSFINAEContext())
12378+
if (ForValidityCheck || !SemaRef.isSFINAEContext())
1237612379
SemaRef.InNonInstantiationSFINAEContext = true;
12377-
SemaRef.AccessCheckingSFINAE = AccessCheckingSFINAE;
12380+
SemaRef.AccessCheckingSFINAE = ForValidityCheck;
1237812381
}
1237912382

1238012383
~SFINAETrap() {
@@ -12404,7 +12407,7 @@ class Sema final : public SemaBase {
1240412407

1240512408
public:
1240612409
explicit TentativeAnalysisScope(Sema &SemaRef)
12407-
: SemaRef(SemaRef), Trap(SemaRef, true),
12410+
: SemaRef(SemaRef), Trap(SemaRef, /*ForValidityCheck=*/true),
1240812411
PrevDisableTypoCorrection(SemaRef.DisableTypoCorrection) {
1240912412
SemaRef.DisableTypoCorrection = true;
1241012413
}

clang/lib/Sema/SemaConcept.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ static const Expr *SubstituteConstraintExpressionWithoutSatisfaction(
907907
if (MLTAL.getNumSubstitutedLevels() == 0)
908908
return ConstrExpr;
909909

910-
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/false);
910+
Sema::SFINAETrap SFINAE(S);
911911

912912
Sema::InstantiatingTemplate Inst(
913913
S, DeclInfo.getLocation(),

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5523,7 +5523,7 @@ static bool HasNonDeletedDefaultedEqualityComparison(Sema &S,
55235523
{
55245524
EnterExpressionEvaluationContext UnevaluatedContext(
55255525
S, Sema::ExpressionEvaluationContext::Unevaluated);
5526-
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
5526+
Sema::SFINAETrap SFINAE(S, /*ForValidityCheck=*/true);
55275527
Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
55285528

55295529
// const ClassT& obj;
@@ -6232,7 +6232,7 @@ static ExprResult CheckConvertibilityForTypeTraits(
62326232
// trap at translation unit scope.
62336233
EnterExpressionEvaluationContext Unevaluated(
62346234
Self, Sema::ExpressionEvaluationContext::Unevaluated);
6235-
Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true);
6235+
Sema::SFINAETrap SFINAE(Self, /*ForValidityCheck=*/true);
62366236
Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
62376237
InitializationSequence Init(Self, To, Kind, From);
62386238
if (Init.Failed())
@@ -6354,7 +6354,7 @@ static bool EvaluateBooleanTypeTrait(Sema &S, TypeTrait Kind,
63546354
// trap at translation unit scope.
63556355
EnterExpressionEvaluationContext Unevaluated(
63566356
S, Sema::ExpressionEvaluationContext::Unevaluated);
6357-
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
6357+
Sema::SFINAETrap SFINAE(S, /*ForValidityCheck=*/true);
63586358
Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
63596359
InitializedEntity To(
63606360
InitializedEntity::InitializeTemporary(S.Context, Args[0]));
@@ -6697,7 +6697,7 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, const TypeSourceI
66976697
// trap at translation unit scope.
66986698
EnterExpressionEvaluationContext Unevaluated(
66996699
Self, Sema::ExpressionEvaluationContext::Unevaluated);
6700-
Sema::SFINAETrap SFINAE(Self, /*AccessCheckingSFINAE=*/true);
6700+
Sema::SFINAETrap SFINAE(Self, /*ForValidityCheck=*/true);
67016701
Sema::ContextRAII TUContext(Self, Self.Context.getTranslationUnitDecl());
67026702
ExprResult Result = Self.BuildBinOp(/*S=*/nullptr, KeyLoc, BO_Assign, &Lhs,
67036703
&Rhs);

clang/lib/Sema/SemaTemplate.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3118,7 +3118,7 @@ static QualType builtinCommonTypeImpl(Sema &S, TemplateName BaseTemplate,
31183118

31193119
EnterExpressionEvaluationContext UnevaluatedContext(
31203120
S, Sema::ExpressionEvaluationContext::Unevaluated);
3121-
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
3121+
Sema::SFINAETrap SFINAE(S, /*ForValidityCheck=*/true);
31223122
Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
31233123

31243124
QualType BaseTemplateInst =
@@ -3164,7 +3164,7 @@ static QualType builtinCommonTypeImpl(Sema &S, TemplateName BaseTemplate,
31643164
auto CheckConditionalOperands = [&](bool ConstRefQual) -> QualType {
31653165
EnterExpressionEvaluationContext UnevaluatedContext(
31663166
S, Sema::ExpressionEvaluationContext::Unevaluated);
3167-
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
3167+
Sema::SFINAETrap SFINAE(S, /*ForValidityCheck=*/true);
31683168
Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());
31693169

31703170
// false

clang/test/SemaCXX/type-trait-common-type.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void test_vla() {
3434

3535
template <class... Args>
3636
using common_type_base = __builtin_common_type<common_type_t, type_identity, empty_type, Args...>;
37+
// expected-note@-1 {{in instantiation of default function argument expression for 'InvalidConversion<void>' required here}}
3738

3839
template <class... Args>
3940
struct common_type : common_type_base<Args...> {};
@@ -208,3 +209,36 @@ struct common_type<PrivateTypeMember, PrivateTypeMember>
208209
};
209210

210211
static_assert(__is_same(common_type_base<PrivateTypeMember, PrivateTypeMember, PrivateTypeMember>, empty_type));
212+
213+
class PrivateConstructor {
214+
private:
215+
PrivateConstructor(int);
216+
};
217+
218+
static_assert(__is_same(common_type_base<int, PrivateConstructor>, empty_type));
219+
220+
// expected-note@+1 {{in instantiation of template type alias 'common_type_base' requested here}}
221+
template<typename A, typename B, typename Res = common_type_base<A, B>>
222+
static Res common_type_sfinae();
223+
// expected-note@-1 {{in instantiation of default argument for 'common_type_sfinae<int, InvalidConversion>' required here}}
224+
225+
// Make sure we don't emit "calling a private constructor" in SFINAE context ...
226+
static_assert(__is_same(decltype(common_type_sfinae<int, PrivateConstructor>()), empty_type));
227+
228+
// ... but we still emit errors outside of the immediate context.
229+
template<typename T>
230+
struct Member {
231+
T t; // expected-error {{field has incomplete type 'void'}}
232+
};
233+
234+
// The conversion from int has a non-SFINAE error.
235+
class InvalidConversion {
236+
private:
237+
template<typename T = void>
238+
InvalidConversion(int, Member<T> = {});
239+
// expected-note@-1 {{in instantiation of template class 'Member<void>' requested here}}
240+
// expected-note@-2 {{passing argument to parameter here}}
241+
};
242+
243+
// expected-note@+1 {{while substituting deduced template arguments into function template 'common_type_sfinae'}}
244+
static_assert(__is_same(decltype(common_type_sfinae<int, InvalidConversion>()), empty_type));

clang/test/SemaCXX/type-traits.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2673,6 +2673,9 @@ struct FloatWrapper
26732673
}
26742674
};
26752675

2676+
template<typename A, typename B, bool result = __is_convertible(A, B)>
2677+
static constexpr bool is_convertible_sfinae() { return result; }
2678+
26762679
void is_convertible()
26772680
{
26782681
static_assert(__is_convertible(IntWrapper, IntWrapper));
@@ -2697,6 +2700,10 @@ void is_convertible()
26972700
static_assert(__is_convertible(FloatWrapper, const float&));
26982701
static_assert(__is_convertible(float, FloatWrapper&&));
26992702
static_assert(__is_convertible(float, const FloatWrapper&));
2703+
2704+
static_assert(!__is_convertible(AllPrivate, AllPrivate));
2705+
// Make sure we don't emit "calling a private constructor" in SFINAE context.
2706+
static_assert(!is_convertible_sfinae<AllPrivate, AllPrivate>());
27002707
}
27012708

27022709
void is_nothrow_convertible()
@@ -2822,6 +2829,9 @@ void is_trivial()
28222829

28232830
template<typename T> struct TriviallyConstructibleTemplate {};
28242831

2832+
template<typename A, typename B, bool result = __is_assignable(A, B)>
2833+
static constexpr bool is_assignable_sfinae() { return result; }
2834+
28252835
void trivial_checks()
28262836
{
28272837
static_assert(__is_trivially_copyable(int));
@@ -2995,6 +3005,10 @@ void trivial_checks()
29953005
static_assert(!__is_assignable(AnIncompleteType[1], AnIncompleteType[1])); // expected-error {{incomplete type}}
29963006
static_assert(!__is_assignable(void, void));
29973007
static_assert(!__is_assignable(const volatile void, const volatile void));
3008+
3009+
static_assert(!__is_assignable(AllPrivate, AllPrivate));
3010+
// Make sure we don't emit "'operator=' is a private member" in SFINAE context.
3011+
static_assert(!is_assignable_sfinae<AllPrivate, AllPrivate>());
29983012
}
29993013

30003014
void constructible_checks() {
@@ -3191,6 +3205,9 @@ void reference_constructs_from_temporary_checks() {
31913205

31923206
}
31933207

3208+
template<typename A, typename B, bool result = __reference_converts_from_temporary(A, B)>
3209+
static constexpr bool reference_converts_from_temporary_sfinae() { return result; }
3210+
31943211
void reference_converts_from_temporary_checks() {
31953212
static_assert(!__reference_converts_from_temporary(int &, int &));
31963213
static_assert(!__reference_converts_from_temporary(int &, int &&));
@@ -3241,6 +3258,9 @@ void reference_converts_from_temporary_checks() {
32413258
static_assert(__reference_converts_from_temporary(const int&, ExplicitConversionRef));
32423259
static_assert(__reference_converts_from_temporary(int&&, ExplicitConversionRvalueRef));
32433260

3261+
static_assert(!__reference_converts_from_temporary(AllPrivate, AllPrivate));
3262+
// Make sure we don't emit "calling a private constructor" in SFINAE context.
3263+
static_assert(!reference_converts_from_temporary_sfinae<AllPrivate, AllPrivate>());
32443264
}
32453265

32463266
void array_rank() {
@@ -4085,6 +4105,20 @@ struct NotTriviallyEqualityComparableNonTriviallyEqualityComparableArrs2 {
40854105

40864106
static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableNonTriviallyEqualityComparableArrs2));
40874107

4108+
struct NotTriviallyEqualityComparablePrivateComparison {
4109+
int i;
4110+
4111+
private:
4112+
bool operator==(const NotTriviallyEqualityComparablePrivateComparison&) const = default;
4113+
};
4114+
static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparablePrivateComparison));
4115+
4116+
template<typename T, bool result = __is_trivially_equality_comparable(T)>
4117+
static constexpr bool is_trivially_equality_comparable_sfinae() { return result; }
4118+
4119+
// Make sure we don't emit "'operator==' is a private member" in SFINAE context.
4120+
static_assert(!is_trivially_equality_comparable_sfinae<NotTriviallyEqualityComparablePrivateComparison>());
4121+
40884122
template<bool B>
40894123
struct MaybeTriviallyEqualityComparable {
40904124
int i;

0 commit comments

Comments
 (0)