https://github.com/victorvianna updated https://github.com/llvm/llvm-project/pull/180868
>From dc2ea4d1d4e579978c405777a25c3578d8dbf947 Mon Sep 17 00:00:00 2001 From: Victor Hugo Vianna Silva <[email protected]> Date: Wed, 11 Feb 2026 13:54:22 +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 | 79 ++++ .../use-from-range-container-constructor.cpp | 373 ++++++++++++++++++ 7 files changed, 745 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..7bb1cfdd430f9 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/modernize-use-from-range-container-constructor.rst @@ -0,0 +1,79 @@ +.. 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); + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
