[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 03:17:25 PST 2026


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

>From 1ada7adba6c622452ec3f349dae727343005e3d9 Mon Sep 17 00:00:00 2001
From: Victor Hugo Vianna Silva <victorvianna at google.com>
Date: Wed, 11 Feb 2026 11:11:11 +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 | 236 +++++++++++
 .../UseFromRangeContainerConstructorCheck.h   |  51 +++
 .../docs/clang-tidy/checks/list.rst           |   1 +
 ...e-use-from-range-container-constructor.rst |  16 +
 .../use-from-range-container-constructor.cpp  | 373 ++++++++++++++++++
 7 files changed, 681 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..6ab34381bf706 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -50,6 +50,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseStdFormatCheck.cpp
   UseStdNumbersCheck.cpp
   UseStdPrintCheck.cpp
+  UseFromRangeContainerConstructorCheck.cpp
   UseStringViewCheck.cpp
   UseTrailingReturnTypeCheck.cpp
   UseTransparentFunctorsCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index eb73478b44023..aa8b189de72c8 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"
@@ -131,6 +132,8 @@ class ModernizeModule : public ClangTidyModule {
     CheckFactories.registerCheck<UseNoexceptCheck>("modernize-use-noexcept");
     CheckFactories.registerCheck<UseNullptrCheck>("modernize-use-nullptr");
     CheckFactories.registerCheck<UseOverrideCheck>("modernize-use-override");
+    CheckFactories.registerCheck<UseFromRangeContainerConstructorCheck>(
+        "modernize-use-from-range-container-constructor");
     CheckFactories.registerCheck<UseStringViewCheck>(
         "modernize-use-string-view");
     CheckFactories.registerCheck<UseTrailingReturnTypeCheck>(
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..49c1f7d8a91d5
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.cpp
@@ -0,0 +1,236 @@
+//===--- UseFromRangeContainerConstructorCheck.cpp - clang-tidy -*- C++ -*-===//
+
+#include "UseFromRangeContainerConstructorCheck.h"
+
+#include <functional>
+#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..41a49ac53f1bc
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseFromRangeContainerConstructorCheck.h
@@ -0,0 +1,51 @@
+//===--- UseFromRangeContainerConstructorCheck.h - clang-tidy ---*- C++ -*-===//
+//
+// 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..ac2e76b4f062a
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst
@@ -0,0 +1,16 @@
++.. title:: clang-tidy - modernize-use-from-range-container-constructor
++
++modernize-use-from-range-container-constructor
++=================================
++
++.. 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);
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..e83fab56d57c3
--- /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
+
+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> forward_list(ints.begin(), ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::forward_list<int> forward_list(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>> int_pairs = {{1, 1}, {2, 2}, {3, 3}};
+  std::map<int, int> map(int_pairs.begin(), int_pairs.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::map<int, int> map(std::from_range, int_pairs);
+
+  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(int_pairs.begin(), int_pairs.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, int_pairs);
+
+  std::priority_queue<int> priority_queue(ints.begin(), ints.end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::priority_queue<int> priority_queue(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
+
+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);
+}
+
+void TestWarnsForAllExpressions() {
+  struct HasVectorMember {
+    explicit HasVectorMember(std::set<int> s) : v(s.begin(), s.end()) {}
+    // CHECK-MESSAGES: :[[@LINE-1]]:49: warning: use std::from_range for container construction [modernize-use-from-range-container-constructor]
+    // CHECK-FIXES: explicit HasVectorMember(std::set<int> s) : v(std::from_range, s) {}
+    std::vector<int> v;
+  };
+
+  auto f = [](std::set<int> s) {
+    return std::vector<int>(s.begin(), s.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, s);
+  };
+
+  std::vector<int> v;
+  f(std::set<int>(v.begin(), v.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, v));
+
+  size_t size = std::vector<int>(v.begin(), v.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, v).size();
+
+}
+
+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().
+}
+
+void TestDereferencesCorrectly() {
+  auto unique_ptr = std::make_unique<std::vector<int>>();
+  *unique_ptr = {1};
+
+  std::vector<int> v1(unique_ptr->begin(), unique_ptr->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> v1(std::from_range, *unique_ptr);
+
+  std::vector<int> v2(std::begin(*unique_ptr), std::end(*unique_ptr));
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> v2(std::from_range, *unique_ptr);
+
+  std::vector<int>* raw_ptr = unique_ptr.get();
+  std::vector<int> v3(raw_ptr->begin(), raw_ptr->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> v3(std::from_range, *raw_ptr);
+
+  std::vector<int> arr[2];
+  std::vector<int>* p_arr = &arr[0];
+  std::vector<int> v_complex((p_arr + 1)->begin(), (p_arr + 1)->end());
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use std::from_range for container construction
+  // CHECK-FIXES: std::vector<int> v_complex(std::from_range, *(p_arr + 1));
+}
+
+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.
+  }
+}
+
+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());
+}
+
+void TestDifferentObjectsSameField() {
+  struct Data {
+    std::vector<int> vec;
+  };
+  Data d1, d2;
+  d1.vec = {1, 2};
+  d2.vec = {3, 4};
+
+  // This should NOT warn. It's a valid (though weird) iterator pair.
+  std::vector<int> v(d1.vec.begin(), d2.vec.end());
+}
+
+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);
+}
+
+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);
+}
+
+void TestMapIncompatibility() {
+    std::vector<std::pair<std::string_view, int>> source = {};
+    std::map<std::string, int> m(source.begin(), source.end());
+}
+
+
+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));
+}
+
+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