[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