Skip to content

[clang-tidy] Don't suggest tuple_size_v in constraints in type-traits - #235257

Open
yashaggarwal85d wants to merge 2 commits into
llvm:mainfrom
yashaggarwal85d:clang-tidy-type-traits-tuple-size-constraints
Open

yashaggarwal85d wants to merge 2 commits into
llvm:mainfrom
yashaggarwal85d:clang-tidy-type-traits-tuple-size-constraints

Conversation

@yashaggarwal85d

@yashaggarwal85d yashaggarwal85d commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

The Bug reported -
For a T that does not have a tuple_size specialization, std::tuple_size::value results in a substitution failure, so the constraint is simply not satisfied. std::tuple_size_v, on the other hand, can result in a hard error (or be considered satisfied when used as a simple requirement). The same issue applies to std::variant_size. Because of this, applying the modernize-type-traits fix-it in these cases can change the meaning of the code, as in #233277.

The solution approach -
The other value traits handled by this check are defined for every argument, so replacing them with their _v form does not change their behavior in constraints and remains safe to suggest.

This patch therefore skips dependent std::tuple_size<...>::value and std::variant_size<...>::value uses when they appear inside a requires-expression, concept definition, or requires-clause (including template parameter lists, trailing requires-clauses, partial specializations, and lambdas). Uses outside of constraints are still diagnosed as before.

Disclaimer -
This change was written with AI assistance and reviewed by me (Yash).

@github-actions

Copy link
Copy Markdown

Hello @yashaggarwal85d 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@llvmorg-github-actions

llvmorg-github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang-tidy

Author: Yash Aggarwal (yashaggarwal85d)

Changes

std::tuple_size&lt;T&gt;::value and std::tuple_size_v&lt;T&gt; are not interchangeable inside constraints. For a T without a tuple_size specialization, std::tuple_size&lt;T&gt;::value is a substitution failure, so the constraint is simply not satisfied, while std::tuple_size_v&lt;T&gt; either causes a hard error or, as a simple requirement, is always satisfied. The same applies to std::variant_size. Applying the modernize-type-traits fix-it there changes the meaning of the code, as shown in #233277.

The other value traits handled by the check are defined for every argument, so their _v replacement stays correct in constraints and is still suggested.

This patch skips dependent std::tuple_size&lt;...&gt;::value and std::variant_size&lt;...&gt;::value uses that appear inside a requires-expression, a concept definition or a requires-clause (template parameter list, trailing, partial specialization, lambda). Uses outside constraints are diagnosed as before.

Testing: added type-traits-constraints.cpp covering both repros from the issue, requires-clauses in their different positions, and cases that should still be diagnosed. All clang-tidy lit tests pass locally (Release build with assertions), except three C++20 modules tests that fail only because my local %clang is an older system clang; they do not involve this check. The docs build without warnings.

Fixes #233277


This change was written with AI assistance and reviewed by me (Yash).

Review request comment (post on the PR after opening it)

> CC @vbvictor @5chmidti @localspook for review. Thanks!


Full diff: https://github.com/llvm/llvm-project/pull/235257.diff

4 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp (+56-2)
  • (modified) clang-tools-extra/docs/ReleaseNotes.md (+6)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst (+6)
  • (added) clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits-constraints.cpp (+94)
diff --git a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp
index 49e21e8e57ce0f..e612f790e2dac7 100644
--- a/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/TypeTraitsCheck.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "TypeTraitsCheck.h"
+#include "clang/AST/ParentMapContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Lex/Lexer.h"
@@ -159,6 +160,54 @@ static const llvm::StringSet<> TypeTraits = {
     "variant_alternative",
 };
 
+// Value traits whose primary template is not defined for every argument. For
+// these, a dependent `trait<T>::value` can be a substitution failure while
+// `trait_v<T>` is a hard error, so the two are not equivalent in constraints.
+static const llvm::StringSet<> NonSFINAEFriendlyValueTraits = {
+    "tuple_size",
+    "variant_size",
+};
+
+static bool isClause(const Expr *Clause, const Expr *E) {
+  return Clause && E && Clause->IgnoreParens() == E->IgnoreParens();
+}
+
+static bool isRequiresClause(const TemplateParameterList *TPL, const Expr *E) {
+  return TPL && isClause(TPL->getRequiresClause(), E);
+}
+
+// Returns true if E is a requires-clause of the declaration in Node.
+static bool isRequiresClauseOf(const DynTypedNode &Node, const Expr *E) {
+  if (const auto *DD = Node.get<DeclaratorDecl>();
+      DD && isClause(DD->getTrailingRequiresClause().ConstraintExpr, E))
+    return true;
+  if (const auto *TD = Node.get<TemplateDecl>())
+    return isRequiresClause(TD->getTemplateParameters(), E);
+  if (const auto *CTPSD = Node.get<ClassTemplatePartialSpecializationDecl>())
+    return isRequiresClause(CTPSD->getTemplateParameters(), E);
+  if (const auto *VTPSD = Node.get<VarTemplatePartialSpecializationDecl>())
+    return isRequiresClause(VTPSD->getTemplateParameters(), E);
+  return false;
+}
+
+// Returns true if E is part of a requires-expression, a concept definition or
+// a requires-clause.
+static bool isInConstraint(const Expr &E, ASTContext &Ctx) {
+  DynTypedNode Node = DynTypedNode::create(E);
+  while (true) {
+    const DynTypedNodeList Parents = Ctx.getParents(Node);
+    if (Parents.empty())
+      return false;
+    const DynTypedNode &Parent = Parents[0];
+    if (Parent.get<RequiresExpr>() || Parent.get<ConceptDecl>() ||
+        isRequiresClauseOf(Parent, Node.get<Expr>()))
+      return true;
+    if (Parent.get<Decl>())
+      return false;
+    Node = Parent;
+  }
+}
+
 static DeclarationName getName(const DependentScopeDeclRefExpr &D) {
   return D.getDeclName();
 }
@@ -313,8 +362,13 @@ void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) {
 
   if (const auto *DSDRE =
           Result.Nodes.getNodeAs<DependentScopeDeclRefExpr>(Bind)) {
-    if (checkTemplatedDecl(DSDRE->getQualifier(), ValueTraits))
-      EmitValueWarning(DSDRE->getQualifierLoc(), DSDRE->getEndLoc());
+    if (!checkTemplatedDecl(DSDRE->getQualifier(), ValueTraits))
+      return;
+    if (checkTemplatedDecl(DSDRE->getQualifier(),
+                           NonSFINAEFriendlyValueTraits) &&
+        isInConstraint(*DSDRE, *Result.Context))
+      return;
+    EmitValueWarning(DSDRE->getQualifierLoc(), DSDRE->getEndLoc());
     return;
   }
 
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 57ed93042aff2c..516cf3f1671cb4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -284,6 +284,12 @@ infrastructure are described first, followed by tool-specific sections.
   `std::initializer_list` constructor, as the braced form could select a
   different constructor.
 
+- Improved {doc}`modernize-type-traits
+  <clang-tidy/checks/modernize/type-traits>` check to no longer suggest
+  `std::tuple_size_v` and `std::variant_size_v` inside requires-expressions,
+  concept definitions and requires-clauses, where they are not equivalent to
+  the `::value` form because they are not SFINAE-friendly.
+
 - Fixed a crash in {doc}`modernize-use-designated-initializers
   <clang-tidy/checks/modernize/use-designated-initializers>` when analyzing
   malformed code with nested classes and ambiguous initializer.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst
index 3871c6ae3934ba..782f36b3a27769 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/type-traits.rst
@@ -55,3 +55,9 @@ Limitations
 Does not currently diagnose uses of type traits with nested name
 specifiers (e.g. ``std::chrono::is_clock``,
 ``std::chrono::treat_as_floating_point``).
+
+Does not diagnose ``std::tuple_size<T>::value`` and
+``std::variant_size<T>::value`` inside requires-expressions, concept definitions
+and requires-clauses. There, a substitution failure in the ``::value`` form
+makes the constraint unsatisfied, while ``std::tuple_size_v<T>`` and
+``std::variant_size_v<T>`` would cause a hard error instead.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits-constraints.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits-constraints.cpp
new file mode 100644
index 00000000000000..4fe99334a70e0e
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/type-traits-constraints.cpp
@@ -0,0 +1,94 @@
+// RUN: %check_clang_tidy -std=c++20-or-later %s modernize-type-traits %t
+
+namespace std {
+  typedef decltype(sizeof(0)) size_t;
+
+  template <typename T>
+  struct tuple_size;
+
+  template <typename T>
+  inline constexpr size_t tuple_size_v = tuple_size<T>::value; // NOLINT
+
+  template <typename T>
+  struct variant_size;
+
+  template <typename T>
+  inline constexpr size_t variant_size_v = variant_size<T>::value; // NOLINT
+
+  template <typename>
+  struct is_const {
+    static constexpr bool value = true;
+  };
+
+  template <typename T>
+  inline constexpr bool is_const_v = is_const<T>::value; // NOLINT
+} // namespace std
+
+// `std::tuple_size_v<T>` and `std::variant_size_v<T>` are hard errors when
+// `std::tuple_size<T>::value` would be a substitution failure, so they must
+// not be suggested inside constraints.
+
+template <typename T>
+constexpr bool has_tuple_size() {
+  if constexpr (requires { std::tuple_size<T>::value; })
+    return true;
+  return false;
+}
+
+template <typename T>
+concept is_pair = std::tuple_size<T>::value == 2;
+
+template <typename T>
+concept has_variant_size = requires {
+  requires std::variant_size<T>::value > 0;
+};
+
+template <typename T>
+  requires(std::tuple_size<T>::value == 2)
+void requiresClause();
+
+template <typename T>
+void trailingRequiresClause()
+  requires(std::tuple_size<T>::value == 2);
+
+template <typename T>
+struct S {
+  void method()
+    requires(std::tuple_size<T>::value == 2);
+};
+
+template <typename T>
+struct Partial;
+
+template <typename T>
+  requires(std::tuple_size<T>::value == 2)
+struct Partial<T *> {};
+
+template <typename T>
+void lambdas() {
+  auto L1 = []<typename U>(U)
+    requires(std::tuple_size<U>::value == 2)
+  {};
+  auto L2 = [](auto U)
+    requires(std::tuple_size<decltype(U)>::value == 2)
+  {};
+}
+
+// Other traits are always defined, so they are still diagnosed in constraints.
+template <typename T>
+concept is_const_type = std::is_const<T>::value;
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: use c++17 style variable templates
+// CHECK-FIXES: concept is_const_type = std::is_const_v<T>;
+
+// Outside of constraints, the replacement is still suggested.
+template <typename T>
+constexpr std::size_t tupleSize() {
+  return std::tuple_size<T>::value;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use c++17 style variable templates
+  // CHECK-FIXES: return std::tuple_size_v<T>;
+}
+
+template <typename T>
+constexpr std::size_t variantSize = std::variant_size<T>::value;
+// CHECK-MESSAGES: :[[@LINE-1]]:37: warning: use c++17 style variable templates
+// CHECK-FIXES: constexpr std::size_t variantSize = std::variant_size_v<T>;

@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

Thanks! I’ve read the AI Tool Use Policy, the Code-Review Policy and the Developer Policy.

@zwuis zwuis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write comments and the PR description manually.

Comment on lines +58 to +63

Does not diagnose ``std::tuple_size<T>::value`` and
``std::variant_size<T>::value`` inside requires-expressions, concept definitions
and requires-clauses. There, a substitution failure in the ``::value`` form
makes the constraint unsatisfied, while ``std::tuple_size_v<T>`` and
``std::variant_size_v<T>`` would cause a hard error instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You added this to the Limitations section. Did you mean we can handle it correctly, but we don't do it currently?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ig putting this under limitations makes it sound like we could fix this but haven’t

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll move it from limitations.

@yashaggarwal85d
yashaggarwal85d force-pushed the clang-tidy-type-traits-tuple-size-constraints branch from 4793391 to 833a3d1 Compare October 24, 2026 09:04
@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

Sure @zwuis i'll rewrite the commit and description.

@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

I have re-written any and all the content myself as per my best understanding. Also I have made the change you highlighted. Lemme know if there is anything else. Thanks @zwuis

@yashaggarwal85d
yashaggarwal85d requested a review from zwuis October 25, 2026 07:34
@zwuis

This comment has been minimized.

@yashaggarwal85d

yashaggarwal85d commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor Author

... The other value traits handled by this check are defined for every argument, so replacing them with their _v form does not change their behavior in constraints and remains safe to suggest.

What does “defined for every argument” mean? Did you mean that most std::XXX_v traits are SFINAE-friendly except std::tuple_size_v and std::variant_size_v?

Hey @zwuis, you want me to change the wording? Since you resolved the comment I am assuming that you are okay with it.

@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

Hey @zwuis, any other change you want me to make? Or we can merge this PR?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants