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 <[email protected]> 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); + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
