https://github.com/desyatok created https://github.com/llvm/llvm-project/pull/95867
This is a gitlab mirror (LLVM repo is too large to directly create one) >From 6ac71484e0974f6f928fcab43e02ced5f184ce3c Mon Sep 17 00:00:00 2001 From: Pavel Desyatnikov <desyatnikov...@gmail.com> Date: Tue, 18 Jun 2024 02:41:55 +0300 Subject: [PATCH] Implement PrimitivesInit refactoring --- .../clang/Basic/DiagnosticRefactoringKinds.td | 6 + .../RefactoringActionRuleRequirements.h | 17 ++ .../Refactoring/VarInits/PrimitiveVarDecl.h | 26 +++ .../Refactoring/VarInits/PrimitivesInit.h | 40 +++++ clang/lib/Tooling/Refactoring/CMakeLists.txt | 3 + .../Refactoring/RefactoringActions.cpp | 18 +++ .../Refactoring/VarInits/PrimitiveVarDecl.cpp | 55 +++++++ .../VarInits/PrimitiveVarDeclRequirement.cpp | 32 ++++ .../Refactoring/VarInits/PrimitivesInit.cpp | 65 ++++++++ clang/tools/clang-refactor/ClangRefactor.cpp | 149 +++++++++++++++++- 10 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 clang/include/clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h create mode 100644 clang/include/clang/Tooling/Refactoring/VarInits/PrimitivesInit.h create mode 100644 clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDecl.cpp create mode 100644 clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDeclRequirement.cpp create mode 100644 clang/lib/Tooling/Refactoring/VarInits/PrimitivesInit.cpp diff --git a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td index 5446b32efbdd4..39366bd1492bb 100644 --- a/clang/include/clang/Basic/DiagnosticRefactoringKinds.td +++ b/clang/include/clang/Basic/DiagnosticRefactoringKinds.td @@ -28,6 +28,12 @@ def err_refactor_extract_simple_expression : Error<"the selected expression " def err_refactor_extract_prohibited_expression : Error<"the selected " "expression can't be extracted">; +def err_refactor_no_vardecl : Error<"refactoring action can't be initiated " + "without a vardecl">; +def err_refactor_initialized_variable : Error<"the provided vardecl is already initialized">; +def err_refactor_global_variable_init : Error<"no need to initialize global variable">; +def err_refactor_non_primitive_variable : Error<"refactoring action can't be initiated " + "with non-primitive variable">; } } // end of Refactoring diagnostics diff --git a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h index 1a318da3acca1..bd410d43abd12 100644 --- a/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h +++ b/clang/include/clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLING_REFACTORING_REFACTORINGACTIONRULEREQUIREMENTS_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Tooling/Refactoring/ASTSelection.h" #include "clang/Tooling/Refactoring/RefactoringDiagnostic.h" #include "clang/Tooling/Refactoring/RefactoringOption.h" @@ -77,6 +78,17 @@ class CodeRangeASTSelectionRequirement : public ASTSelectionRequirement { evaluate(RefactoringRuleContext &Context) const; }; +/// A base class for any requirement that expects source code position +/// (or the refactoring tool with the -location option). +class SourceLocationRequirement : public RefactoringActionRuleRequirement { +public: + Expected<SourceLocation> evaluate(RefactoringRuleContext &Context) const { + if (Context.getLocation().isValid()) + return Context.getLocation(); + return Context.createDiagnosticError(diag::err_refactor_no_location); + } +}; + /// A base class for any requirement that requires some refactoring options. class RefactoringOptionsRequirement : public RefactoringActionRuleRequirement { public: @@ -116,6 +128,11 @@ class OptionRequirement : public RefactoringOptionsRequirement { std::shared_ptr<RefactoringOption> Opt; }; +class PrimitiveVarDeclRequirement : public SourceLocationRequirement { +public: + Expected<VarDecl *> evaluate(RefactoringRuleContext &Context) const; +}; + } // end namespace tooling } // end namespace clang diff --git a/clang/include/clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h b/clang/include/clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h new file mode 100644 index 0000000000000..dd9ec755d30c7 --- /dev/null +++ b/clang/include/clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h @@ -0,0 +1,26 @@ +//===--- PrimitiveVarDecl.h - Clang refactoring library ----------------------------===// +// +// 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_TOOLING_REFACTORING_VARINITS_PRIMITIVEVARDECL_H +#define LLVM_CLANG_TOOLING_REFACTORING_VARINITS_PRIMITIVEVARDECL_H + + +namespace clang { +namespace tooling { +/// \returns a ptr to concrete DeclRefExpr (it is basically a pointer to +/// VarDecl) if given SourceLocation is in between +/// a DeclRefExpr start location and end location +/// and nullptr otherwise +DeclRefExpr *getDeclRefExprFromSourceLocation(ASTContext &AST, + SourceLocation Location); + +} // namespace tooling +} // namespace clang + + +#endif // LLVM_CLANG_TOOLING_REFACTORING_VARINITS_PRIMITIVEVARDECL_H diff --git a/clang/include/clang/Tooling/Refactoring/VarInits/PrimitivesInit.h b/clang/include/clang/Tooling/Refactoring/VarInits/PrimitivesInit.h new file mode 100644 index 0000000000000..744422b396459 --- /dev/null +++ b/clang/include/clang/Tooling/Refactoring/VarInits/PrimitivesInit.h @@ -0,0 +1,40 @@ +//===--- PrimitivesInit.h - Clang refactoring library ----------------------------===// +// +// 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_TOOLING_REFACTORING_VARINITS_PRIMITIVES_INIT_H +#define LLVM_CLANG_TOOLING_REFACTORING_VARINITS_PRIMITIVES_INIT_H + +#include "clang/Tooling/Refactoring/RefactoringActionRules.h" + +namespace clang { +namespace tooling { + +/// \c PrimitivesInit performs the initialization of +/// a selected primitive variable with a default value +class PrimitivesInit final : public SourceChangeRefactoringRule { +public: + /// \param Variable declaration-only VarDecl + static Expected<PrimitivesInit> + initiate(RefactoringRuleContext &Context, VarDecl *Variable); + + static const RefactoringDescriptor &describe(); + +private: + PrimitivesInit(VarDecl *Variable) + : Variable(std::move(Variable)) {} + + Expected<AtomicChanges> + createSourceReplacements(RefactoringRuleContext &Context) override; + + VarDecl *Variable; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_TOOLING_REFACTORING_VARINITS_PRIMITIVES_INIT_H diff --git a/clang/lib/Tooling/Refactoring/CMakeLists.txt b/clang/lib/Tooling/Refactoring/CMakeLists.txt index d3077be8810aa..63b9dd700ba35 100644 --- a/clang/lib/Tooling/Refactoring/CMakeLists.txt +++ b/clang/lib/Tooling/Refactoring/CMakeLists.txt @@ -13,6 +13,9 @@ add_clang_library(clangToolingRefactoring Rename/USRFinder.cpp Rename/USRFindingAction.cpp Rename/USRLocFinder.cpp + VarInits/PrimitivesInit.cpp + VarInits/PrimitiveVarDeclRequirement.cpp + VarInits/PrimitiveVarDecl.cpp LINK_LIBS clangAST diff --git a/clang/lib/Tooling/Refactoring/RefactoringActions.cpp b/clang/lib/Tooling/Refactoring/RefactoringActions.cpp index bf98941f568b3..f68b4daf346ff 100644 --- a/clang/lib/Tooling/Refactoring/RefactoringActions.cpp +++ b/clang/lib/Tooling/Refactoring/RefactoringActions.cpp @@ -10,6 +10,7 @@ #include "clang/Tooling/Refactoring/RefactoringAction.h" #include "clang/Tooling/Refactoring/RefactoringOptions.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "clang/Tooling/Refactoring/VarInits/PrimitivesInit.h" namespace clang { namespace tooling { @@ -93,6 +94,22 @@ class LocalRename final : public RefactoringAction { } }; +class PrimitivesInitAction : public RefactoringAction { +public: + StringRef getCommand() const override { return "init-primitives"; } + + StringRef getDescription() const override { + return "Initialization of declared-only primitives"; + } + + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back(createRefactoringActionRule<PrimitivesInit>( + PrimitiveVarDeclRequirement())); + return Rules; + } +}; + } // end anonymous namespace std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() { @@ -100,6 +117,7 @@ std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() { Actions.push_back(std::make_unique<LocalRename>()); Actions.push_back(std::make_unique<ExtractRefactoring>()); + Actions.push_back(std::make_unique<PrimitivesInitAction>()); return Actions; } diff --git a/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDecl.cpp b/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDecl.cpp new file mode 100644 index 0000000000000..1d416e9bf944e --- /dev/null +++ b/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDecl.cpp @@ -0,0 +1,55 @@ +//===--- PrimitiveVarDecl.cpp - Clang refactoring library ------===// +// +// 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 "clang/AST/LexicallyOrderedRecursiveASTVisitor.h" +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" +#include "clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h" + +using namespace clang; +using namespace tooling; + +namespace { + +class VarDeclFinder + : public LexicallyOrderedRecursiveASTVisitor<VarDeclFinder> { +public: + VarDeclFinder(SourceLocation Location, FileID TargetFile, + const ASTContext &AST) + : LexicallyOrderedRecursiveASTVisitor(AST.getSourceManager()), + Location(Location), TargetFile(TargetFile), AST(AST) {} + + bool VisitDeclRefExpr(DeclRefExpr *Ref) { + const SourceManager &SM = AST.getSourceManager(); + if (SM.isPointWithin(Location, Ref->getBeginLoc(), + Ref->getEndLoc())) { + this->VariableReference = Ref; + return false; + } + return true; + } + + DeclRefExpr *getDeclRefExpr() { return VariableReference; } + +private: + const SourceLocation Location; + FileID TargetFile; + const ASTContext &AST; + DeclRefExpr *VariableReference = nullptr; +}; + +} // end anonymous namespace + +DeclRefExpr * +clang::tooling::getDeclRefExprFromSourceLocation(ASTContext &AST, + SourceLocation Location) { + + FileID TargetFile = AST.getSourceManager().getFileID(Location); + + VarDeclFinder Visitor(Location, TargetFile, AST); + Visitor.TraverseAST(AST); + return Visitor.getDeclRefExpr(); +} diff --git a/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDeclRequirement.cpp b/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDeclRequirement.cpp new file mode 100644 index 0000000000000..68e31ba147060 --- /dev/null +++ b/clang/lib/Tooling/Refactoring/VarInits/PrimitiveVarDeclRequirement.cpp @@ -0,0 +1,32 @@ +//===--- PrimitiveVarDeclRequirement.cpp - Clang refactoring library ------===// +// +// 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 "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" +#include "clang/Tooling/Refactoring/VarInits/PrimitiveVarDecl.h" +#include <optional> + +using namespace clang; +using namespace tooling; + +Expected<VarDecl *> +PrimitiveVarDeclRequirement::evaluate(RefactoringRuleContext &Context) const { + Expected<SourceLocation> Location = + SourceLocationRequirement::evaluate(Context); + if (!Location) + return Location.takeError(); + + DeclRefExpr *VariableReference = getDeclRefExprFromSourceLocation( + Context.getASTContext(), Location.get()); + + if (!VariableReference) + return Context.createDiagnosticError( + Location.get(), diag::err_refactor_no_vardecl); + + VarDecl *Variable = VariableReference->getDecl()-> + getPotentiallyDecomposedVarDecl(); + return std::move(Variable); +} diff --git a/clang/lib/Tooling/Refactoring/VarInits/PrimitivesInit.cpp b/clang/lib/Tooling/Refactoring/VarInits/PrimitivesInit.cpp new file mode 100644 index 0000000000000..76926281794f4 --- /dev/null +++ b/clang/lib/Tooling/Refactoring/VarInits/PrimitivesInit.cpp @@ -0,0 +1,65 @@ +//===--- PrimitivesInit.cpp - Clang refactoring library -------------===// +// +// 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 "clang/Tooling/Refactoring/VarInits/PrimitivesInit.h" +#include "clang/AST/ASTContext.h" + +using namespace clang; +using namespace tooling; + +static inline bool VariableHasPrimitiveType(VarDecl *Variable) { + return Variable->getType()->isCharType() || + Variable->getType()->isIntegerType() || + Variable->getType()->isFloatingType(); +} + +Expected<PrimitivesInit> +PrimitivesInit::initiate(RefactoringRuleContext &Context, + VarDecl *Variable) { + // Checks whether provided VarDecl is valid + if (!Variable->isLocalVarDecl()) { + return Context.createDiagnosticError( + diag::err_refactor_global_variable_init); + } + + if (!VariableHasPrimitiveType(Variable)) { + return Context.createDiagnosticError( + diag::err_refactor_non_primitive_variable); + } + + if (Variable->hasInit()) { + return Context.createDiagnosticError( + diag::err_refactor_initialized_variable); + } + + return PrimitivesInit(std::move(Variable)); +} +const RefactoringDescriptor &PrimitivesInit::describe() { + static const RefactoringDescriptor Descriptor = { + "primitives-init", + "Primitives Initialization", + "Initializes a primitive variable with default value", + }; + return Descriptor; +} + +Expected<AtomicChanges> +PrimitivesInit::createSourceReplacements(RefactoringRuleContext &Context) { + ASTContext &AST = Context.getASTContext(); + SourceManager &SM = AST.getSourceManager(); + AtomicChange Replacement(SM, Variable->getLocation()); + std::string VarName = Variable->getNameAsString(); + std::string InitWithDefaultValue = (Variable->getType()->isCharType() ? + " = \'\\0\'" : " = 0"); + + auto Error = Replacement.replace(SM, Variable->getEndLoc(), + VarName.length(), + VarName + InitWithDefaultValue); + if (Error) return std::move(Error); + return AtomicChanges{std::move(Replacement)}; +} diff --git a/clang/tools/clang-refactor/ClangRefactor.cpp b/clang/tools/clang-refactor/ClangRefactor.cpp index 175a2b8234e9a..c42baba3ef9cd 100644 --- a/clang/tools/clang-refactor/ClangRefactor.cpp +++ b/clang/tools/clang-refactor/ClangRefactor.cpp @@ -164,6 +164,83 @@ SourceSelectionArgument::fromString(StringRef Value) { return nullptr; } +/// Stores the parsed `-location` argument. +class SourceLocationArgument { +public: + virtual ~SourceLocationArgument() {} + + /// Parse the `-location` argument. + /// + /// \returns A valid argument when the parse succedeed, null otherwise. + static std::unique_ptr<SourceLocationArgument> fromString(StringRef Value); + + /// Prints any additional state associated with the location argument to + /// the given output stream. + virtual void print(raw_ostream &OS) {} + + /// Returns a replacement refactoring result consumer (if any) that should + /// consume the results of a refactoring operation. + /// + /// The replacement refactoring result consumer is used by \c + /// TestSourceLocationArgument to inject a test-specific result handling + /// logic into the refactoring operation. The test-specific consumer + /// ensures that the individual results in a particular test group are + /// identical. + virtual std::unique_ptr<ClangRefactorToolConsumerInterface> + createCustomConsumer() { + return nullptr; + } + + /// Runs the given refactoring function for each specified location. + /// + /// \returns true if an error occurred, false otherwise. + virtual bool + forAllLocations(const SourceManager &SM, + llvm::function_ref<void(SourceLocation L)> Callback) = 0; +}; + +/// Stores the parsed -location=filename:line:column option. +class SourceLocLocationArgument final : public SourceLocationArgument { +public: + SourceLocLocationArgument(ParsedSourceLocation Location) + : Location(std::move(Location)) {} + + bool forAllLocations(const SourceManager &SM, + llvm::function_ref<void(SourceLocation L)> Callback) override { + auto FE = SM.getFileManager().getFile(Location.FileName); + FileID FID = FE ? SM.translateFile(*FE) : FileID(); + if (!FE || FID.isInvalid()) { + llvm::errs() << "error: -location=" << Location.FileName + << ":... : given file is not in the target TU\n"; + return true; + } + + SourceLocation Loc = SM.getMacroArgExpandedLocation( + SM.translateLineCol(FID, Location.Line, Location.Column)); + if (Loc.isInvalid()) { + llvm::errs() << "error: -location=" << Location.FileName << ':' + << Location.Line << ':' << Location.Column + << " : invalid source location\n"; + return true; + } + Callback(Loc); + return false; + } + +private: + ParsedSourceLocation Location; +}; + +std::unique_ptr<SourceLocationArgument> +SourceLocationArgument::fromString(StringRef Value) { + std::optional<ParsedSourceLocation> Location = ParsedSourceLocation::FromString(Value); + if (Location.value().FileName != "") + return std::make_unique<SourceLocLocationArgument>(std::move(*Location)); + llvm::errs() << "error: '-location' option must be specified using " + "<file>:<line>:<column>\n"; + return nullptr; +} + /// A container that stores the command-line options used by a single /// refactoring option. class RefactoringActionCommandLineOptions { @@ -272,6 +349,18 @@ class RefactoringActionSubcommand : public cl::SubCommand { break; } } + // Check if the location option is supported. + for (const auto &Rule : this->ActionRules) { + if (Rule->hasLocationRequirement()) { + Location = std::make_unique<cl::opt<std::string>>( + "location", + cl::desc( + "Location where refactoring should " + "be initiated (<file>:<line>:<column>)"), + cl::cat(Category), cl::sub(*this)); + break; + } + } // Create the refactoring options. for (const auto &Rule : this->ActionRules) { CommandLineRefactoringOptionCreator OptionCreator(Category, *this, @@ -296,11 +385,28 @@ class RefactoringActionSubcommand : public cl::SubCommand { return false; } + /// Parses the "-location" command-line argument. + /// + /// \returns true on error, false otherwise. + bool parseLocationArgument() { + if (Location) { + ParsedLocation = SourceLocationArgument::fromString(*Location); + if (!ParsedLocation) + return true; + } + return false; + } + SourceSelectionArgument *getSelection() const { assert(Selection && "selection not supported!"); return ParsedSelection.get(); } + SourceLocationArgument *getLocation() const { + assert(Location && "location not supported!"); + return ParsedLocation.get(); + } + const RefactoringActionCommandLineOptions &getOptions() const { return Options; } @@ -309,7 +415,9 @@ class RefactoringActionSubcommand : public cl::SubCommand { std::unique_ptr<RefactoringAction> Action; RefactoringActionRules ActionRules; std::unique_ptr<cl::opt<std::string>> Selection; + std::unique_ptr<cl::opt<std::string>> Location; std::unique_ptr<SourceSelectionArgument> ParsedSelection; + std::unique_ptr<SourceLocationArgument> ParsedLocation; RefactoringActionCommandLineOptions Options; }; @@ -399,6 +507,7 @@ class ClangRefactorTool { // consumer. std::unique_ptr<ClangRefactorToolConsumerInterface> TestConsumer; bool HasSelection = MatchingRule->hasSelectionRequirement(); + bool HasLocation = MatchingRule->hasLocationRequirement(); if (HasSelection) TestConsumer = SelectedSubcommand->getSelection()->createCustomConsumer(); ClangRefactorToolConsumerInterface *ActiveConsumer = @@ -424,6 +533,20 @@ class ClangRefactorTool { ActiveConsumer->endTU(); return; } + if (HasLocation) { + assert(SelectedSubcommand->getLocation() && + "Missing location argument?"); + if (opts::Verbose) + SelectedSubcommand->getLocation()->print(llvm::outs()); + if (SelectedSubcommand->getLocation()->forAllLocations( + Context.getSources(), [&](SourceLocation L) { + Context.setLocation(L); + InvokeRule(*ActiveConsumer); + })) + HasFailed = true; + ActiveConsumer->endTU(); + return; + } InvokeRule(*ActiveConsumer); ActiveConsumer->endTU(); } @@ -528,6 +651,12 @@ class ClangRefactorTool { R.getEnd().print(llvm::outs(), Context.getSources()); llvm::outs() << "\n"; } + if (Context.getLocation().isValid()) { + SourceLocation L = Context.getLocation(); + llvm::outs() << " -location="; + L.print(llvm::outs(), Context.getSources()); + llvm::outs() << "\n"; + } } llvm::Expected<RefactoringActionRule *> @@ -539,16 +668,24 @@ class ClangRefactorTool { CommandLineRefactoringOptionVisitor Visitor(Subcommand.getOptions()); Rule->visitRefactoringOptions(Visitor); if (Visitor.getMissingRequiredOptions().empty()) { - if (!Rule->hasSelectionRequirement()) { - MatchingRules.push_back(Rule.get()); - } else { + bool HasMissingOptions = false; + if (Rule->hasSelectionRequirement()) { Subcommand.parseSelectionArgument(); - if (Subcommand.getSelection()) { - MatchingRules.push_back(Rule.get()); - } else { + if (!Subcommand.getSelection()) { MissingOptions.insert("selection"); + HasMissingOptions = true; } } + if (Rule->hasLocationRequirement()) { + Subcommand.parseLocationArgument(); + if (!Subcommand.getLocation()) { + MissingOptions.insert("location"); + HasMissingOptions = true; + } + } + if (!HasMissingOptions) { + MatchingRules.push_back(Rule.get()); + } } for (const RefactoringOption *Opt : Visitor.getMissingRequiredOptions()) MissingOptions.insert(Opt->getName()); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits