================
@@ -0,0 +1,190 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseSpanParamCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+// Methods on std::vector that only read data (compatible with std::span).
+static bool isReadOnlyVectorMethod(StringRef Name) {
+  return Name == "operator[]" || Name == "at" || Name == "data" ||
+         Name == "size" || Name == "empty" || Name == "begin" ||
+         Name == "end" || Name == "cbegin" || Name == "cend" ||
+         Name == "rbegin" || Name == "rend" || Name == "crbegin" ||
+         Name == "crend" || Name == "front" || Name == "back";
+}
+
+// Check if all uses of the parameter in the function body are read-only.
+static bool allUsesAreReadOnly(const ParmVarDecl *Param,
+                               const FunctionDecl *Func, ASTContext &Context) {
+  const Stmt *Body = Func->getBody();
+  if (!Body)
+    return false;
+
+  const auto Refs = match(
+      findAll(declRefExpr(to(equalsNode(Param))).bind("ref")), *Body, Context);
+
+  for (const auto &Ref : Refs) {
+    const auto *DRE = Ref.getNodeAs<DeclRefExpr>("ref");
+    if (!DRE)
+      return false;
+
+    // Walk up through implicit casts to find the "real" parent.
+    const Expr *Current = DRE;
+    while (true) {
+      const auto Parents = Context.getParents(*Current);
+      if (Parents.empty())
+        return false;
+      const auto &Parent = Parents[0];
+      if (const auto *ICE = Parent.get<ImplicitCastExpr>()) {
+        Current = ICE;
+        continue;
+      }
+
+      // Member call on the vector: check it's a read-only method.
+      if (const auto *MCE = Parent.get<CXXMemberCallExpr>()) {
+        const CXXMethodDecl *Method = MCE->getMethodDecl();
+        if (!Method || !isReadOnlyVectorMethod(Method->getName()))
+          return false;
+        break;
+      }
+
+      // Operator[] via CXXOperatorCallExpr.
+      if (const auto *OCE = Parent.get<CXXOperatorCallExpr>()) {
+        if (OCE->getOperator() == OO_Subscript)
+          break;
+        return false;
+      }
+
+      // Used in a range-based for loop: the DRE is inside the implicit
+      // __range variable's initializer, so the parent is a VarDecl.
+      if (const auto *VD = Parent.get<VarDecl>()) {
+        if (VD->isImplicit()) {
+          // Check that the implicit VarDecl is the range variable of a
+          // CXXForRangeStmt.
+          const auto VDParents = Context.getParents(*VD);
+          for (const auto &VDP : VDParents) {
+            if (const auto *DS = VDP.get<DeclStmt>()) {
+              const auto DSParents = Context.getParents(*DS);
+              for (const auto &DSP : DSParents)
+                if (DSP.get<CXXForRangeStmt>())
+                  goto range_ok;
+            }
+          }
+        }
+        return false;
+      range_ok:
+        break;
+      }
+
+      // Member expression (e.g. v.size()) - walk further up.
+      if (Parent.get<MemberExpr>()) {
+        Current = Parent.get<MemberExpr>();
+        continue;
+      }
+
+      // Passed as argument to a function - check parameter type.
+      if (const auto *CE = Parent.get<CallExpr>()) {
+        const FunctionDecl *Callee = CE->getDirectCallee();
+        if (!Callee)
+          return false;
+        // Find which argument position this is.
+        bool Found = false;
+        for (unsigned I = 0; I < CE->getNumArgs(); ++I) {
+          if (CE->getArg(I)->IgnoreParenImpCasts() == DRE) {
+            if (I < Callee->getNumParams()) {
+              const QualType PT = Callee->getParamDecl(I)->getType();
+              // Accept const vector<T>&, const T*, span<const T>.
+              if (PT->isReferenceType() &&
+                  PT.getNonReferenceType().isConstQualified()) {
+                Found = true;
+                break;
+              }
+              if (PT->isPointerType() &&
+                  PT->getPointeeType().isConstQualified()) {
+                Found = true;
+                break;
+              }
+            }
+            break;
+          }
+        }
+        if (!Found)
+          return false;
+        break;
+      }
+
+      // Anything else is not read-only.
+      return false;
+    }
+  }
+  return true;
+}
+
+void UseSpanParamCheck::registerMatchers(MatchFinder *Finder) {
+  // Match functions with const std::vector<T>& parameters.
+  Finder->addMatcher(
+      functionDecl(
+          isDefinition(), unless(isExpansionInSystemHeader()),
+          unless(isImplicit()), unless(isDeleted()),
+          has(typeLoc(forEach(
+              parmVarDecl(hasType(qualType(references(qualType(
+                              isConstQualified(),
+                              hasDeclaration(classTemplateSpecializationDecl(
+                                  hasName("::std::vector"))))))))
+                  .bind("param")))))
+          .bind("func"),
+      this);
+}
+
+void UseSpanParamCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func");
+  const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param");
+  if (!Func || !Param)
+    return;
+
+  // Skip if this is a virtual function (can't change signature).
+  if (const auto *Method = dyn_cast<CXXMethodDecl>(Func))
+    if (Method->isVirtual())
+      return;
+
+  // Skip if function has other overloads (changing signature is risky).
+  // Skip template functions for now (type deduction complexity).
+  if (Func->isTemplated())
+    return;
+
+  if (!allUsesAreReadOnly(Param, Func, *Result.Context))
+    return;
+
+  // Determine the element type from vector<T>.
+  const QualType ParamType = Param->getType().getNonReferenceType();
+  const auto *Spec =
+      dyn_cast<ClassTemplateSpecializationDecl>(ParamType->getAsRecordDecl());
+  if (!Spec || Spec->getTemplateArgs().size() < 1)
+    return;
+
+  const QualType ElemType = Spec->getTemplateArgs()[0].getAsType();
+  const std::string SpanType =
+      "std::span<const " + ElemType.getAsString() + ">";
+
+  diag(Param->getLocation(),
+       "parameter %0 can be changed to 'std::span'; it is only used for "
+       "read-only access")
+      << Param
+      << FixItHint::CreateReplacement(
+             Param->getTypeSourceInfo()->getTypeLoc().getSourceRange(),
+             SpanType);
----------------
localspook wrote:

We should add logic to insert a `<span>` include

https://github.com/llvm/llvm-project/pull/182027
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to