[clang-tools-extra] [clang-tidy] Add modernize-use-from-range-container-constructor check (PR #180868)

Victor Vianna via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 11 05:30:20 PST 2026


https://github.com/victorvianna updated https://github.com/llvm/llvm-project/pull/180868

>From 381d7abb18e8a20d24307150e5e371ce7d9deabd Mon Sep 17 00:00:00 2001
From: Victor Hugo Vianna Silva <victorvianna at google.com>
Date: Wed, 11 Feb 2026 13:29:36 +0000
Subject: [PATCH] [clang-tidy] Add
 modernize-use-from-range-container-constructor check

This new check finds container constructions that use a pair of iterators and
replaces them with the more modern and concise `std::from_range` syntax,
available in C++23.

This improves readability and leverages modern C++ features for safer and
more expressive code.

For example:
std::vector<int> v(s.begin(), s.end());

Becomes:
std::vector<int> v(std::from_range, s);
---
 .../clang-tidy/modernize/CMakeLists.txt       |   1 +
 .../modernize/ModernizeTidyModule.cpp         |   3 +
 .../UseFromRangeContainerConstructorCheck.cpp | 237 +++++++++++
 .../UseFromRangeContainerConstructorCheck.h   |  51 +++
 .../docs/clang-tidy/checks/list.rst           |   1 +
 ...e-use-from-range-container-constructor.rst |  78 ++++
 .../use-from-range-container-constructor.cpp  | 373 ++++++++++++++++++
 7 files changed, 744 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-from-range-container-constructor.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 858cf921f9d34..0f9b3623abd7d 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -39,6 +39,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseEmplaceCheck.cpp
   UseEqualsDefaultCheck.cpp
   UseEqualsDeleteCheck.cpp
+  UseFromRangeContainerConstructorCheck.cpp
   UseIntegerSignComparisonCheck.cpp
   UseNodiscardCheck.cpp
   UseNoexceptCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index eb73478b44023..13387c66723a9 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -39,6 +39,7 @@
 #include "UseEmplaceCheck.h"
 #include "UseEqualsDefaultCheck.h"
 #include "UseEqualsDeleteCheck.h"
+#include "UseFromRangeContainerConstructorCheck.h"
 #include "UseIntegerSignComparisonCheck.h"
 #include "UseNodiscardCheck.h"
 #include "UseNoexceptCheck.h"
@@ -88,6 +89,8 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
     CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
         "modernize-use-designated-initializers");
+    CheckFactories.registerCheck<UseFromRangeContainerConstructorCheck>(
+        "modernize-use-from-range-container-constructor");
     CheckFactories.registerCheck<UseIntegerSignComparisonCheck>(
         "modernize-use-integer-sign-comparison");
     CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.cpp
new file mode 100644
index 0000000000000..8f90c1589b2e3
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.cpp
@@ -0,0 +1,237 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseFromRangeContainerConstructorCheck.h"
+
+#include <optional>
+#include <string>
+
+#include "../ClangTidyCheck.h"
+#include "../ClangTidyDiagnosticConsumer.h"
+#include "../ClangTidyOptions.h"
+#include "../utils/ASTUtils.h"
+#include "../utils/IncludeSorter.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/OperatorKinds.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Tooling/FixIt.h"
+
+namespace clang::tidy::modernize {
+
+namespace {
+
+using ast_matchers::argumentCountAtLeast;
+using ast_matchers::cxxConstructExpr;
+using ast_matchers::cxxConstructorDecl;
+using ast_matchers::cxxRecordDecl;
+using ast_matchers::hasAnyName;
+using ast_matchers::hasDeclaration;
+using ast_matchers::ofClass;
+
+struct RangeObjectInfo {
+  const Expr *Object;
+  bool IsArrow;
+  StringRef Name;
+};
+
+} // namespace
+
+static std::optional<RangeObjectInfo> getRangeAndFunctionName(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  const Expr *Base = nullptr;
+  bool IsArrow = false;
+  StringRef Name;
+  if (const auto *MemberCall = dyn_cast<CXXMemberCallExpr>(E)) {
+    if (const auto *ME = dyn_cast<MemberExpr>(
+            MemberCall->getCallee()->IgnoreParenImpCasts())) {
+      Base = ME->getBase()->IgnoreParenImpCasts();
+      IsArrow = ME->isArrow();
+      Name = ME->getMemberDecl()->getName();
+    }
+  } else if (const auto *Call = dyn_cast<CallExpr>(E)) {
+    if (Call->getNumArgs() == 1 && Call->getDirectCallee()) {
+      Base = Call->getArg(0)->IgnoreParenImpCasts();
+      IsArrow = false;
+      Name = Call->getDirectCallee()->getName();
+    }
+  }
+
+  if (!Base)
+    return std::nullopt;
+
+  // PEEL LAYER: Handle Smart Pointers (overloaded operator->)
+  // If the base is an operator call, we want the text of the underlying
+  // pointer.
+  if (const auto *OpCall = dyn_cast<CXXOperatorCallExpr>(Base)) {
+    if (OpCall->getOperator() == OO_Arrow) {
+      Base = OpCall->getArg(0)->IgnoreParenImpCasts();
+      IsArrow = true;
+    }
+  }
+
+  return RangeObjectInfo{Base, IsArrow, Name};
+}
+
+static QualType getValueType(QualType T) {
+  if (const auto *Spec = T->getAs<TemplateSpecializationType>()) {
+    const StringRef Name =
+        Spec->getTemplateName().getAsTemplateDecl()->getName();
+    if (Name == "map" || Name == "unordered_map")
+      return {};
+
+    if (Name == "unique_ptr") {
+      if (!Spec->template_arguments().empty() &&
+          Spec->template_arguments()[0].getKind() == TemplateArgument::Type)
+        return getValueType(Spec->template_arguments()[0].getAsType());
+      return {};
+    }
+
+    const ArrayRef<TemplateArgument> &Args = Spec->template_arguments();
+    if (!Args.empty() && Args[0].getKind() == TemplateArgument::Type)
+      return Args[0].getAsType();
+  }
+  return {};
+}
+
+UseFromRangeContainerConstructorCheck::UseFromRangeContainerConstructorCheck(
+    StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      Inserter(Options.getLocalOrGlobal("IncludeStyle",
+                                        utils::IncludeSorter::IS_LLVM),
+               /*SelfContainedDiags=*/false) {}
+
+void UseFromRangeContainerConstructorCheck::registerPPCallbacks(
+    const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
+  Inserter.registerPreprocessor(PP);
+}
+
+void UseFromRangeContainerConstructorCheck::registerMatchers(
+    ast_matchers::MatchFinder *Finder) {
+  auto ContainerNames =
+      hasAnyName("::std::vector", "::std::deque", "::std::forward_list",
+                 "::std::list", "::std::set", "::std::map",
+                 "::std::unordered_set", "::std::unordered_map",
+                 "::std::priority_queue", "::std::queue", "::std::stack",
+                 "::std::basic_string", "::std::flat_set", "::std::flat_map");
+  Finder->addMatcher(cxxConstructExpr(argumentCountAtLeast(2),
+                                      hasDeclaration(cxxConstructorDecl(ofClass(
+                                          cxxRecordDecl(ContainerNames)))))
+                         .bind("ctor"),
+                     this);
+}
+
+void UseFromRangeContainerConstructorCheck::check(
+    const ast_matchers::MatchFinder::MatchResult &Result) {
+  const auto *CtorExpr = Result.Nodes.getNodeAs<CXXConstructExpr>("ctor");
+  std::optional<RangeObjectInfo> BeginInfo =
+      getRangeAndFunctionName(CtorExpr->getArg(0));
+  std::optional<RangeObjectInfo> EndInfo =
+      getRangeAndFunctionName(CtorExpr->getArg(1));
+  if (!BeginInfo || !EndInfo)
+    return;
+
+  if (!((BeginInfo->Name == "begin" && EndInfo->Name == "end") ||
+        (BeginInfo->Name == "cbegin" && EndInfo->Name == "cend"))) {
+    return;
+  }
+
+  if (!utils::areStatementsIdentical(BeginInfo->Object, EndInfo->Object,
+                                     *Result.Context)) {
+    return;
+  }
+
+  // Type compatibility check.
+  //
+  // 1) Same type, std::from_range works, warn.
+  //
+  //   std::set<std::string> source;
+  //   std::vector<std::string> dest(source.begin(), source.end());
+  //
+  // 2) Needs explicit conversion, std::from_range doesn't work, so don't warn.
+  //
+  //   std::set<std::string_view> source;
+  //   std::vector<std::string> dest(source.begin(), source.end());
+  //
+  // 3) Implicitly convertible, std::from_range works, but do not warn, since
+  //   checking this case is hard in clang-tidy.
+  //
+  //   std::set<std::string> source;
+  //   std::vector<std::string_view> dest(source.begin(), source.end());
+  QualType SourceRangeType = BeginInfo->Object->getType();
+  if (const auto *Type = SourceRangeType->getAs<PointerType>())
+    SourceRangeType = Type->getPointeeType();
+  const QualType SourceValueType = getValueType(SourceRangeType);
+
+  if (const auto *DestSpec =
+          CtorExpr->getType()->getAs<TemplateSpecializationType>()) {
+    const StringRef Name =
+        DestSpec->getTemplateName().getAsTemplateDecl()->getName();
+    if ((Name == "map" || Name == "unordered_map") &&
+        !SourceValueType.isNull()) {
+      if (const auto *SourcePairSpec =
+              SourceValueType->getAs<TemplateSpecializationType>()) {
+        if (SourcePairSpec->getTemplateName().getAsTemplateDecl()->getName() ==
+            "pair") {
+          const QualType DestKeyType =
+              DestSpec->template_arguments()[0].getAsType();
+          const QualType SourceKeyType =
+              SourcePairSpec->template_arguments()[0].getAsType();
+          if (!ASTContext::hasSameUnqualifiedType(DestKeyType, SourceKeyType))
+            return;
+        }
+      }
+    }
+  }
+
+  const QualType DestValueType = getValueType(CtorExpr->getType());
+  if (!DestValueType.isNull() && !SourceValueType.isNull() &&
+      !ASTContext::hasSameUnqualifiedType(DestValueType, SourceValueType)) {
+    return;
+  }
+
+  std::string BaseText =
+      tooling::fixit::getText(*BeginInfo->Object, *Result.Context).str();
+  if (BaseText.empty())
+    return;
+
+  StringRef BaseRef(BaseText);
+  BaseRef.consume_back("->");
+  BaseText = BaseRef.str();
+  std::string Replacement = "std::from_range, ";
+  if (BeginInfo->IsArrow) {
+    // Determine if we need safety parentheses: *(p + 1) vs *p
+    const bool SimpleIdentifier =
+        BaseText.find_first_of(" +-*/%&|^") == std::string::npos;
+    Replacement += SimpleIdentifier ? "*" + BaseText : "*(" + BaseText + ")";
+  } else {
+    Replacement += BaseText;
+  }
+
+  const DiagnosticBuilder Diag =
+      diag(CtorExpr->getBeginLoc(),
+           "use std::from_range for container construction");
+  const SourceRange ArgRange(CtorExpr->getArg(0)->getBeginLoc(),
+                             CtorExpr->getArg(1)->getEndLoc());
+  Diag << FixItHint::CreateReplacement(ArgRange, Replacement);
+  Diag << Inserter.createMainFileIncludeInsertion("<ranges>");
+}
+
+void UseFromRangeContainerConstructorCheck::storeOptions(
+    ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IncludeStyle", Inserter.getStyle());
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.h b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.h
new file mode 100644
index 0000000000000..fe8b578aeb5ec
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEFROMRANGECONTAINERCONSTRUCTORCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEFROMRANGECONTAINERCONSTRUCTORCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../ClangTidyDiagnosticConsumer.h"
+#include "../ClangTidyOptions.h"
+#include "../utils/IncludeInserter.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
+
+namespace clang::tidy::modernize {
+
+/// Finds container constructions from a pair of iterators that can be replaced
+/// with `std::from_range`.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-from-range-container-constructor.html
+class UseFromRangeContainerConstructorCheck : public ClangTidyCheck {
+public:
+  UseFromRangeContainerConstructorCheck(StringRef Name,
+                                        ClangTidyContext *Context);
+
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus23;
+  }
+
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+  utils::IncludeInserter Inserter;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEFROMRANGECONTAINERCONSTRUCTORCHECK_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index d9a6e4fd6593c..cdbb9a9e96a07 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -322,6 +322,7 @@ Clang-Tidy Checks
    :doc:`modernize-use-emplace <modernize/use-emplace>`, "Yes"
    :doc:`modernize-use-equals-default <modernize/use-equals-default>`, "Yes"
    :doc:`modernize-use-equals-delete <modernize/use-equals-delete>`, "Yes"
+   :doc:`modernize-use-from-range-container-constructor <modernize/modernize-use-from-range-container-constructor>`, "Yes"
    :doc:`modernize-use-integer-sign-comparison <modernize/use-integer-sign-comparison>`, "Yes"
    :doc:`modernize-use-nodiscard <modernize/use-nodiscard>`, "Yes"
    :doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst
new file mode 100644
index 0000000000000..aa3452e99bff5
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst
@@ -0,0 +1,78 @@
+.. title:: clang-tidy - modernize-use-from-range-container-constructor
+
+modernize-use-from-range-container-constructor
+=================================================
+
+The ``modernize-use-from-range-container-constructor`` check finds container
+constructions that use a pair of iterators and replaces them with the more
+modern and concise ``std::from_range`` syntax, available in C++23.
+
+This improves readability and leverages modern C++ features for safer and more
+expressive code.
+
+.. code:: c++
+
+  std::set<int> s = {1, 2};
+  std::vector<int> v(s.begin(), s.end());
+
+  // transforms to:
+
+  #include <ranges>
+
+  std::set<int> s = {1, 2};
+  std::vector<int> v(std::from_range, s);
+
+This check handles all standard library containers that support construction
+with std::from_range, such as ``std::vector``, ``std::string``, ``std::map``,
+and ``std::unordered_set``.
+
+It also recognizes different forms of obtaining iterators, such as
+``cbegin()``/``cend()`` and ``std::begin()``/``std::end()``.
+
+Example with ``std::map`` and ``cbegin``/``cend``:
+
+.. code:: c++
+
+  std::vector<std::pair<int, char>> source = {{1, 'a'}, {2, 'b'}};
+  std::map<int, char> dest(source.cbegin(), source.cend());
+
+  // transforms to:
+
+  #include <ranges>
+
+  std::vector<std::pair<int, char>> source = {{1, 'a'}, {2, 'b'}};
+  std::map<int, char> dest(std::from_range, source);
+
+
+The check is also able to handle ranges that are behind pointers or smart
+pointers.
+
+.. code:: c++
+
+  auto ptr = std::make_unique<std::vector<int>>();
+  std::vector<int> v(ptr->begin(), ptr->end());
+
+  // transforms to:
+
+  #include <ranges>
+
+  auto ptr = std::make_unique<std::vector<int>>();
+  std::vector<int> v(std::from_range, *ptr);
+
+Limitations
+-----------
+
+The warning only triggers when the types of the source and target container
+match, even though std::from_range would work in a few extra cases (notably
+when the types can be converted implicitly).
+
+.. code:: c++
+  std::set<std::string_view> source = {"a"};
+  // Attempting to use std::from_range here fails to compile, so no warning.
+  std::vector<std::string> dest(source.begin(), source.end());
+
+
+  std::set<std::string> source = {"a"};
+  // std::from_range would compile, but still, no warning.
+  std::vector<std::string_view> dest(source.begin(), source.end());
+
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-from-range-container-constructor.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-from-range-container-constructor.cpp
new file mode 100644
index 0000000000000..23533e7ed3983
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-from-range-container-constructor.cpp
@@ -0,0 +1,373 @@
+// RUN: %check_clang_tidy -std=c++23-or-later %s modernize-use-from-range-container-constructor %t
+
+#include <stddef.h>
+// CHECK-FIXES: #include <stddef.h>
+// CHECK-FIXES: #include <ranges>
+
+// Stubs of affected std containers and other utilities, since we can't include
+// the necessary headers in this test.
+namespace std {
+
+struct from_range_t { explicit from_range_t() = default; };
+inline constexpr from_range_t from_range{};
+
+template <typename T>
+struct stub_iterator {
+    using iterator_category = void;
+    using value_type = T;
+    using difference_type = ptrdiff_t;
+    using pointer = T*;
+    using reference = T&;
+    T& operator*() const;
+    stub_iterator& operator++();
+    bool operator==(const stub_iterator&) const;
+};
+
+template <typename T>
+class initializer_list {
+  using value_type = T;
+  const T *_M_array;
+  size_t _M_len;
+  inline constexpr initializer_list(const T *__a, size_t __l)
+      : _M_array(__a), _M_len(__l) {}
+ public:
+  inline constexpr initializer_list() noexcept : _M_array(nullptr), _M_len(0) {}
+  inline constexpr size_t size() const noexcept { return _M_len; }
+  inline constexpr const T *begin() const noexcept { return _M_array; }
+  inline constexpr const T *end() const noexcept { return _M_array + _M_len; }
+};
+
+template <typename T1, typename T2>
+struct pair {
+  T1 first;
+  T2 second;
+  pair(T1 f, T2 s) : first(f), second(s) {}
+};
+
+template <typename T, typename Alloc = void>
+struct vector {
+    using value_type = T;
+    typedef stub_iterator<T> iterator;
+    typedef stub_iterator<T> const_iterator;
+    vector() = default;
+    vector(initializer_list<T>) {}
+    template <typename InputIt>
+    vector(InputIt, InputIt) {}
+    vector(from_range_t, auto&&) {}
+    iterator begin(); iterator end();
+    const_iterator begin() const; const_iterator end() const;
+    const_iterator cbegin() const; const_iterator cend() const;
+    iterator rbegin(); iterator rend();
+    size_t size() const { return 0; }
+    void push_back(T t) {}
+};
+
+template <typename T> struct deque : vector<T> { using vector<T>::vector; };
+template <typename T> struct list : vector<T> { using vector<T>::vector; };
+template <typename T> struct forward_list : vector<T> { using vector<T>::vector; };
+template <typename T, typename Compare = void, typename Alloc = void>
+struct set : vector<T> { using vector<T>::vector; };
+template <typename K, typename V, typename Compare = void, typename Alloc = void>
+struct map : vector<pair<K, V>> { using vector<pair<K, V>>::vector; };
+template <typename T, typename Hash = void, typename KeyEqual = void, typename Alloc = void>
+struct unordered_set : vector<T> { using vector<T>::vector; };
+template <typename K, typename V, typename Hash = void, typename KeyEqual = void, typename Alloc = void>
+struct unordered_map : vector<pair<K, V>> { using vector<pair<K, V>>::vector; };
+
+template <typename T, typename Container = vector<T>>
+struct priority_queue {
+    using value_type = T;
+    priority_queue(auto, auto) {}
+    priority_queue(from_range_t, auto&&) {}
+};
+template <typename T> struct queue : priority_queue<T> { using priority_queue<T>::priority_queue; };
+template <typename T> struct stack : priority_queue<T> { using priority_queue<T>::priority_queue; };
+
+template <typename T> struct basic_string : vector<T> {
+  using vector<T>::vector;
+  basic_string(const char*) {}
+};
+using string = basic_string<char>;
+struct string_view { string_view(const char*); string_view(const string&); };
+
+template <typename T> struct greater {};
+template <typename T> struct hash {};
+template <> struct hash<int> { size_t operator()(int) const { return 0; } };
+
+template <typename T> struct unique_ptr {
+    T *operator->();
+    T& operator*();
+    T *get();
+};
+template <typename T> unique_ptr<T> make_unique();
+
+template <typename T> struct shared_ptr {
+    T *operator->();
+    T& operator*();
+    T *get();
+};
+template <typename T> shared_ptr<T> make_shared();
+
+template <typename C> auto begin(C& c) { return c.begin(); }
+template <typename C> auto end(C& c) { return c.end(); }
+
+} // namespace std
+
+static void testWarnsForAllContainerTypes() {
+  std::vector<int> Ints = {1, 2, 3};
+  std::vector<int> Vector(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> Vector(std::from_range, Ints);
+
+  std::deque<int> Deque(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::deque<int> Deque(std::from_range, Ints);
+
+  std::forward_list<int> ForwardList(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::forward_list<int> ForwardList(std::from_range, Ints);
+
+  std::list<int> List(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::list<int> List(std::from_range, Ints);
+
+  std::set<int> Set(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::set<int> Set(std::from_range, Ints);
+
+  std::vector<std::pair<int, int>> IntPairs = {{1, 1}, {2, 2}, {3, 3}};
+  std::map<int, int> Map(IntPairs.begin(), IntPairs.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::map<int, int> Map(std::from_range, IntPairs);
+
+  std::unordered_set<int> Uset(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::unordered_set<int> Uset(std::from_range, Ints);
+
+  std::unordered_map<int, int> Umap(IntPairs.begin(), IntPairs.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::unordered_map<int, int> Umap(std::from_range, IntPairs);
+
+  std::priority_queue<int> PriorityQueue(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::priority_queue<int> PriorityQueue(std::from_range, Ints);
+
+  std::queue<int> Queue(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::queue<int> Queue(std::from_range, Ints);
+
+  std::stack<int> Stack(Ints.begin(), Ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::stack<int> Stack(std::from_range, Ints);
+
+  std::vector<char> Chars = {'a'};
+  std::string String(Chars.begin(), Chars.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::string String(std::from_range, Chars);
+}
+
+class Hashable {
+ public:
+  explicit Hashable(int Value) : Value(Value) {}
+  bool operator==(const Hashable& rhs) const { return Value == rhs.Value; }
+  bool operator<(const Hashable& rhs) const { return Value < rhs.Value; }
+  int Value;
+};
+
+namespace std {
+
+template <>
+struct hash<Hashable> {
+  size_t operator()(const Hashable& h) const { return 1u; }
+};
+
+}  // namespace std
+
+static void testPreservesCustomHashesAndComparators() {
+  struct PairHash {
+    size_t operator()(const std::pair<int, int>& P) const { return 1; }
+  };
+  std::vector<std::pair<int, int>> Pairs = {{1, 1}, {2, 2}, {3, 3}};
+  std::unordered_set<std::pair<int, int>, PairHash> Uset1(Pairs.begin(), Pairs.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:53: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::unordered_set<std::pair<int, int>, PairHash> Uset1(std::from_range, Pairs);
+
+  std::vector<Hashable> Hashables = {{}};
+  std::unordered_set<Hashable> Uset2(Hashables.begin(), Hashables.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::unordered_set<Hashable> Uset2(std::from_range, Hashables);
+
+  std::set<std::pair<int, int>, std::greater<std::pair<int, int>>> Set(Pairs.begin(), Pairs.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:68: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::set<std::pair<int, int>, std::greater<std::pair<int, int>>> Set(std::from_range, Pairs);
+}
+
+static void testWarnsForAllExpressions() {
+  struct HasVectorMember {
+    explicit HasVectorMember(std::set<int> Set) : VectorMember(Set.begin(), Set.end()) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:51: warning: use std::from_range for container construction [modernize-use-from-range-container-constructor]
+    // CHECK-FIXES: explicit HasVectorMember(std::set<int> Set) : VectorMember(std::from_range, Set) {}
+    std::vector<int> VectorMember;
+  };
+
+  auto F = [](std::set<int> SetParam) {
+    return std::vector<int>(SetParam.begin(), SetParam.end());
+    // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use std::from_range for container construction [modernize-use-from-range-container-constructor]
+    // CHECK-FIXES: return std::vector<int>(std::from_range, SetParam);
+  };
+
+  std::vector<int> Vector;
+  F(std::set<int>(Vector.begin(), Vector.end()));
+  // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use std::from_range for container construction [modernize-use-from-range-container-constructor]
+  // CHECK-FIXES: F(std::set<int>(std::from_range, Vector));
+
+  size_t Size = std::vector<int>(Vector.begin(), Vector.end()).size();
+  // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use std::from_range for container construction
+  // CHECK-FIXES: size_t Size = std::vector<int>(std::from_range, Vector).size();
+
+}
+
+static void testWarnsForAllValidIteratorStyles() {
+  std::vector<int> Source = {1, 2};
+  std::vector<int> V1(Source.begin(), Source.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V1(std::from_range, Source);
+
+  std::vector<int> V2(Source.cbegin(), Source.cend());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V2(std::from_range, Source);
+
+  std::vector<int> V3(std::begin(Source), std::end(Source));
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V3(std::from_range, Source);
+
+  // Note: rbegin() is not valid, see TestNegativeCases().
+}
+
+static void testDereferencesCorrectly() {
+  auto UniquePtr = std::make_unique<std::vector<int>>();
+  *UniquePtr = {1};
+
+  std::vector<int> V1(UniquePtr->begin(), UniquePtr->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V1(std::from_range, *UniquePtr);
+
+  std::vector<int> V2(std::begin(*UniquePtr), std::end(*UniquePtr));
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V2(std::from_range, *UniquePtr);
+
+  std::vector<int> *RawPtr = UniquePtr.get();
+  std::vector<int> V3(RawPtr->begin(), RawPtr->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V3(std::from_range, *RawPtr);
+
+  std::vector<int> Arr[2];
+  std::vector<int> *PArr = &Arr[0];
+  std::vector<int> VComplex((PArr + 1)->begin(), (PArr + 1)->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> VComplex(std::from_range, *(PArr + 1));
+}
+
+static void testTypeConversions() {
+  {
+    std::set<std::string_view> Source = {"a"};
+    std::vector<std::string> Dest(Source.begin(), Source.end());
+    // Attempting to use std::from_range here would fail to compile, since
+    // std::string_view needs to be explicitly converted to std::string.
+  }
+  {
+    std::set<std::string> Source = {"a"};
+    std::vector<std::string_view> Dest(Source.begin(), Source.end());
+    // Here std::from_range would succeed - since the conversion from string to
+    // string_view is implicit - but we choose not to warn, in order to keep
+    // the tool check simple.
+  }
+
+  struct ImplicitlyConvertible;
+  struct ExplicitlyConvertible {
+    ExplicitlyConvertible() = default;
+    ExplicitlyConvertible(const ImplicitlyConvertible&) {}
+  };
+  struct ImplicitlyConvertible {
+    ImplicitlyConvertible() = default;
+    explicit ImplicitlyConvertible(const ExplicitlyConvertible&) {}
+  };
+  {
+    std::vector<ExplicitlyConvertible> Source = {{}};
+    std::vector<ImplicitlyConvertible> Dest(Source.begin(), Source.end());
+    // Attempting to use std::from_range here would fail to compile, since
+    // an explicit conversion is required.
+  }
+  {
+    std::vector<ImplicitlyConvertible> Source = {{}};
+    std::vector<ExplicitlyConvertible> Dest(Source.begin(), Source.end());
+    // Here std::from_range would succeed - since the conversion is implicit -
+    // but we choose not to warn, so as to keep the tool check simple.
+  }
+}
+
+static void testShouldNotWarn() {
+  std::vector<int> S1 = {1};
+  std::vector<int> S2 = {2};
+
+  std::vector<int> V1(S1.begin(), S2.end());
+  std::vector<int> V2(S1.rbegin(), S1.rend());
+
+  struct NoFromRangeConstructor {
+    NoFromRangeConstructor(std::vector<int>::iterator Begin, std::vector<int>::iterator End) {}
+  };
+  NoFromRangeConstructor V3(S1.begin(), S1.end());
+}
+
+static void testDifferentObjectsSameMember() {
+  struct Data {
+    std::vector<int> VectorMember;
+  };
+  Data D1, D2;
+  D1.VectorMember = {1, 2};
+  D2.VectorMember = {3, 4};
+
+  // This should NOT warn. It's a valid (though weird) iterator pair.
+  std::vector<int> V(D1.VectorMember.begin(), D2.VectorMember.end());
+}
+
+static void testCommentMidExpression() {
+  auto Ptr = std::make_unique<std::vector<int>>();
+
+  // Test with whitespace and comments
+  std::vector<int> V(Ptr  /* comment */ ->  begin(), Ptr->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> V(std::from_range, *Ptr);
+}
+
+static void testMapFromPairs() {
+  // A vector of pairs, but the first element is NOT const.
+  std::vector<std::pair<int, int>> Source = {{1, 10}};
+
+  // std::map::value_type is std::pair<const int, int>.
+  // The iterator constructor handles the conversion from pair<int, int>
+  // to pair<const int, int> internally.
+  std::map<int, int> M(Source.begin(), Source.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::map<int, int> M(std::from_range, Source);
+}
+
+static void testMapIncompatibility() {
+    std::vector<std::pair<std::string_view, int>> Source = {};
+    std::map<std::string, int> M(Source.begin(), Source.end());
+}
+
+
+static void testOperatorPrecedence(std::vector<int> *P1, std::vector<int> *P2, bool Cond) {
+    std::vector<int> V((Cond ? P1 : P2)->begin(), (Cond ? P1 : P2)->end());
+    // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use std::from_range for container construction
+    // CHECK-FIXES: std::vector<int> V(std::from_range, *(Cond ? P1 : P2));
+}
+
+static void testNoDoubleDereference() {
+    auto Ptr = std::make_shared<std::vector<int>>();
+    std::vector<int> V((*Ptr).begin(), (*Ptr).end());
+    // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use std::from_range for container construction
+    // CHECK-FIXES: std::vector<int> V(std::from_range, *Ptr);
+
+}



More information about the cfe-commits mailing list