[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