| //===--- Refactoring.cpp ---------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "swift/IDE/Refactoring.h" |
| #include "swift/AST/ASTContext.h" |
| #include "swift/AST/ASTPrinter.h" |
| #include "swift/AST/Decl.h" |
| #include "swift/AST/DiagnosticsRefactoring.h" |
| #include "swift/AST/Expr.h" |
| #include "swift/AST/NameLookup.h" |
| #include "swift/AST/Pattern.h" |
| #include "swift/AST/ProtocolConformance.h" |
| #include "swift/AST/Stmt.h" |
| #include "swift/AST/Types.h" |
| #include "swift/AST/USRGeneration.h" |
| #include "swift/Basic/Edit.h" |
| #include "swift/Basic/StringExtras.h" |
| #include "swift/Frontend/Frontend.h" |
| #include "swift/Index/Index.h" |
| #include "swift/Parse/Lexer.h" |
| #include "swift/Subsystems.h" |
| #include "clang/Rewrite/Core/RewriteBuffer.h" |
| #include "llvm/ADT/StringSet.h" |
| |
| using namespace swift; |
| using namespace swift::ide; |
| using namespace swift::index; |
| |
| namespace { |
| class ContextFinder : public SourceEntityWalker { |
| SourceFile &SF; |
| ASTContext &Ctx; |
| SourceManager &SM; |
| SourceRange Target; |
| llvm::function_ref<bool(ASTNode)> IsContext; |
| SmallVector<ASTNode, 4> AllContexts; |
| bool contains(ASTNode Enclosing) { |
| auto Result = SM.rangeContains(Enclosing.getSourceRange(), Target); |
| if (Result && IsContext(Enclosing)) |
| AllContexts.push_back(Enclosing); |
| return Result; |
| } |
| public: |
| ContextFinder(SourceFile &SF, ASTNode TargetNode, |
| llvm::function_ref<bool(ASTNode)> IsContext = |
| [](ASTNode N) { return true; }) : |
| SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr), |
| Target(TargetNode.getSourceRange()), IsContext(IsContext) {} |
| ContextFinder(SourceFile &SF, SourceLoc TargetLoc, |
| llvm::function_ref<bool(ASTNode)> IsContext = |
| [](ASTNode N) { return true; }) : |
| SF(SF), Ctx(SF.getASTContext()), SM(Ctx.SourceMgr), |
| Target(TargetLoc), IsContext(IsContext) { |
| assert(TargetLoc.isValid() && "Invalid loc to find"); |
| } |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) override { return contains(D); } |
| bool walkToStmtPre(Stmt *S) override { return contains(S); } |
| bool walkToExprPre(Expr *E) override { return contains(E); } |
| void resolve() { walk(SF); } |
| llvm::ArrayRef<ASTNode> getContexts() const { |
| return llvm::makeArrayRef(AllContexts); |
| } |
| }; |
| |
| class Renamer { |
| protected: |
| const SourceManager &SM; |
| |
| protected: |
| Renamer(const SourceManager &SM, StringRef OldName) : SM(SM), Old(OldName) {} |
| |
| // Implementor's interface. |
| virtual void doRenameLabel(CharSourceRange Label, |
| RefactoringRangeKind RangeKind, |
| unsigned NameIndex) = 0; |
| virtual void doRenameBase(CharSourceRange Range, |
| RefactoringRangeKind RangeKind) = 0; |
| |
| public: |
| const DeclNameViewer Old; |
| |
| public: |
| virtual ~Renamer() {} |
| |
| /// Adds a replacement to rename the given base name range |
| /// \return true if the given range does not match the old name |
| bool renameBase(CharSourceRange Range, RefactoringRangeKind RangeKind) { |
| assert(Range.isValid()); |
| |
| StringRef Existing = Range.str(); |
| if (Existing != Old.base()) |
| return true; |
| doRenameBase(Range, RangeKind); |
| return false; |
| } |
| |
| /// Adds replacements to rename the given label ranges |
| /// \return true if the label ranges do not match the old name |
| bool renameLabels(ArrayRef<CharSourceRange> LabelRanges, |
| LabelRangeType RangeType, bool isCallSite) { |
| if (isCallSite) |
| return renameLabelsLenient(LabelRanges, RangeType); |
| |
| ArrayRef<StringRef> OldLabels = Old.args(); |
| |
| if (OldLabels.size() != LabelRanges.size()) |
| return true; |
| |
| size_t Index = 0; |
| for (const auto &LabelRange : LabelRanges) { |
| assert(LabelRange.isValid()); |
| if (!labelRangeMatches(LabelRange, RangeType, OldLabels[Index])) |
| return true; |
| splitAndRenameLabel(LabelRange, RangeType, Index++); |
| } |
| return false; |
| } |
| |
| bool isOperator() const { return Lexer::isOperator(Old.base()); } |
| |
| private: |
| void splitAndRenameLabel(CharSourceRange Range, LabelRangeType RangeType, |
| size_t NameIndex) { |
| switch (RangeType) { |
| case LabelRangeType::CallArg: |
| return splitAndRenameCallArg(Range, NameIndex); |
| case LabelRangeType::Param: |
| return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/true); |
| case LabelRangeType::NoncollapsibleParam: |
| return splitAndRenameParamLabel(Range, NameIndex, /*IsCollapsible=*/false); |
| case LabelRangeType::Selector: |
| return doRenameLabel( |
| Range, RefactoringRangeKind::SelectorArgumentLabel, NameIndex); |
| case LabelRangeType::None: |
| llvm_unreachable("expected a label range"); |
| } |
| } |
| |
| void splitAndRenameParamLabel(CharSourceRange Range, size_t NameIndex, bool IsCollapsible) { |
| // Split parameter range foo([a b]: Int) into decl argument label [a] and |
| // parameter name [b] or noncollapsible parameter name [b] if IsCollapsible |
| // is false (as for subscript decls). If we have only foo([a]: Int), then we |
| // add an empty range for the local name, or for the decl argument label if |
| // IsCollapsible is false. |
| StringRef Content = Range.str(); |
| size_t ExternalNameEnd = Content.find_first_of(" \t\n\v\f\r/"); |
| |
| if (ExternalNameEnd == StringRef::npos) { // foo([a]: Int) |
| if (IsCollapsible) { |
| doRenameLabel(Range, RefactoringRangeKind::DeclArgumentLabel, NameIndex); |
| doRenameLabel(CharSourceRange{Range.getEnd(), 0}, |
| RefactoringRangeKind::ParameterName, NameIndex); |
| } else { |
| doRenameLabel(CharSourceRange{Range.getStart(), 0}, |
| RefactoringRangeKind::DeclArgumentLabel, NameIndex); |
| doRenameLabel(Range, RefactoringRangeKind::NoncollapsibleParameterName, |
| NameIndex); |
| } |
| } else { // foo([a b]: Int) |
| CharSourceRange Ext{Range.getStart(), unsigned(ExternalNameEnd)}; |
| |
| // Note: we consider the leading whitespace part of the parameter name |
| // if the parameter is collapsible, since if the parameter is collapsed |
| // into a matching argument label, we want to remove the whitespace too. |
| // FIXME: handle comments foo(a /*...*/b: Int). |
| size_t LocalNameStart = Content.find_last_of(" \t\n\v\f\r/"); |
| assert(LocalNameStart != StringRef::npos); |
| if (!IsCollapsible) |
| ++LocalNameStart; |
| auto LocalLoc = Range.getStart().getAdvancedLocOrInvalid(LocalNameStart); |
| CharSourceRange Local{LocalLoc, unsigned(Content.size() - LocalNameStart)}; |
| |
| doRenameLabel(Ext, RefactoringRangeKind::DeclArgumentLabel, NameIndex); |
| if (IsCollapsible) { |
| doRenameLabel(Local, RefactoringRangeKind::ParameterName, NameIndex); |
| } else { |
| doRenameLabel(Local, RefactoringRangeKind::NoncollapsibleParameterName, NameIndex); |
| } |
| } |
| } |
| |
| void splitAndRenameCallArg(CharSourceRange Range, size_t NameIndex) { |
| // Split call argument foo([a: ]1) into argument name [a] and the remainder |
| // [: ]. |
| StringRef Content = Range.str(); |
| size_t Colon = Content.find(':'); // FIXME: leading whitespace? |
| if (Colon == StringRef::npos) { |
| assert(Content.empty()); |
| doRenameLabel(Range, RefactoringRangeKind::CallArgumentCombined, |
| NameIndex); |
| return; |
| } |
| |
| // Include any whitespace before the ':'. |
| assert(Colon == Content.substr(0, Colon).size()); |
| Colon = Content.substr(0, Colon).rtrim().size(); |
| |
| CharSourceRange Arg{Range.getStart(), unsigned(Colon)}; |
| doRenameLabel(Arg, RefactoringRangeKind::CallArgumentLabel, NameIndex); |
| |
| auto ColonLoc = Range.getStart().getAdvancedLocOrInvalid(Colon); |
| assert(ColonLoc.isValid()); |
| CharSourceRange Rest{ColonLoc, unsigned(Content.size() - Colon)}; |
| doRenameLabel(Rest, RefactoringRangeKind::CallArgumentColon, NameIndex); |
| } |
| |
| bool labelRangeMatches(CharSourceRange Range, LabelRangeType RangeType, StringRef Expected) { |
| if (Range.getByteLength()) { |
| CharSourceRange ExistingLabelRange = |
| Lexer::getCharSourceRangeFromSourceRange(SM, Range.getStart()); |
| StringRef ExistingLabel = ExistingLabelRange.str(); |
| |
| switch (RangeType) { |
| case LabelRangeType::NoncollapsibleParam: |
| if (ExistingLabelRange == Range && Expected.empty()) // subscript([x]: Int) |
| return true; |
| LLVM_FALLTHROUGH; |
| case LabelRangeType::CallArg: |
| case LabelRangeType::Param: |
| case LabelRangeType::Selector: |
| return ExistingLabel == (Expected.empty() ? "_" : Expected); |
| case LabelRangeType::None: |
| llvm_unreachable("Unhandled label range type"); |
| } |
| } |
| return Expected.empty(); |
| } |
| |
| bool renameLabelsLenient(ArrayRef<CharSourceRange> LabelRanges, |
| LabelRangeType RangeType) { |
| |
| ArrayRef<StringRef> OldNames = Old.args(); |
| |
| size_t NameIndex = 0; |
| |
| for (CharSourceRange Label : LabelRanges) { |
| // empty label |
| if (!Label.getByteLength()) { |
| |
| // first name pos |
| if (!NameIndex) { |
| while (!OldNames[NameIndex].empty()) { |
| if (++NameIndex >= OldNames.size()) |
| return true; |
| } |
| splitAndRenameLabel(Label, RangeType, NameIndex++); |
| continue; |
| } |
| |
| // other name pos |
| if (NameIndex >= OldNames.size() || !OldNames[NameIndex].empty()) { |
| // FIXME: only allow one variadic param |
| continue; // allow for variadic |
| } |
| splitAndRenameLabel(Label, RangeType, NameIndex++); |
| continue; |
| } |
| |
| // non-empty label |
| if (NameIndex >= OldNames.size()) |
| return true; |
| |
| while (!labelRangeMatches(Label, RangeType, OldNames[NameIndex])) { |
| if (++NameIndex >= OldNames.size()) |
| return true; |
| }; |
| splitAndRenameLabel(Label, RangeType, NameIndex++); |
| } |
| return false; |
| } |
| |
| static RegionType getSyntacticRenameRegionType(const ResolvedLoc &Resolved) { |
| if (Resolved.Node.isNull()) |
| return RegionType::Comment; |
| |
| if (Expr *E = Resolved.Node.getAsExpr()) { |
| if (isa<StringLiteralExpr>(E)) |
| return RegionType::String; |
| } |
| if (Resolved.IsInSelector) |
| return RegionType::Selector; |
| if (Resolved.IsActive) |
| return RegionType::ActiveCode; |
| return RegionType::InactiveCode; |
| } |
| |
| public: |
| RegionType addSyntacticRenameRanges(const ResolvedLoc &Resolved, |
| const RenameLoc &Config) { |
| |
| if (!Resolved.Range.isValid()) |
| return RegionType::Unmatched; |
| |
| auto RegionKind = getSyntacticRenameRegionType(Resolved); |
| // Don't include unknown references coming from active code; if we don't |
| // have a semantic NameUsage for them, then they're likely unrelated symbols |
| // that happen to have the same name. |
| if (RegionKind == RegionType::ActiveCode && |
| Config.Usage == NameUsage::Unknown) |
| return RegionType::Unmatched; |
| |
| assert(Config.Usage != NameUsage::Call || Config.IsFunctionLike); |
| |
| // FIXME: handle escaped keyword names `init` |
| bool IsSubscript = Old.base() == "subscript" && Config.IsFunctionLike; |
| bool IsInit = Old.base() == "init" && Config.IsFunctionLike; |
| bool IsKeywordBase = IsInit || IsSubscript; |
| |
| // Filter out non-semantic keyword basename locations with no labels. |
| // We've already filtered out those in active code, so these are |
| // any appearance of just 'init' or 'subscript' in strings, comments, and |
| // inactive code. |
| if (IsKeywordBase && (Config.Usage == NameUsage::Unknown && |
| Resolved.LabelType == LabelRangeType::None)) |
| return RegionType::Unmatched; |
| |
| if (!Config.IsFunctionLike || !IsKeywordBase) { |
| if (renameBase(Resolved.Range, RefactoringRangeKind::BaseName)) |
| return RegionType::Mismatch; |
| |
| } else if (IsKeywordBase && Config.Usage == NameUsage::Definition) { |
| if (renameBase(Resolved.Range, RefactoringRangeKind::KeywordBaseName)) |
| return RegionType::Mismatch; |
| } |
| |
| bool HandleLabels = false; |
| if (Config.IsFunctionLike) { |
| switch (Config.Usage) { |
| case NameUsage::Call: |
| HandleLabels = !isOperator(); |
| break; |
| case NameUsage::Definition: |
| HandleLabels = true; |
| break; |
| case NameUsage::Reference: |
| HandleLabels = Resolved.LabelType == LabelRangeType::Selector || IsSubscript; |
| break; |
| case NameUsage::Unknown: |
| HandleLabels = Resolved.LabelType != LabelRangeType::None; |
| break; |
| } |
| } else if (Resolved.LabelType != LabelRangeType::None && |
| !Config.IsNonProtocolType && |
| // FIXME: Workaround for enum case labels until we support them |
| Config.Usage != NameUsage::Definition) { |
| return RegionType::Mismatch; |
| } |
| |
| if (HandleLabels) { |
| bool isCallSite = Config.Usage != NameUsage::Definition && |
| (Config.Usage != NameUsage::Reference || IsSubscript) && |
| Resolved.LabelType == LabelRangeType::CallArg; |
| |
| if (renameLabels(Resolved.LabelRanges, Resolved.LabelType, isCallSite)) |
| return Config.Usage == NameUsage::Unknown ? |
| RegionType::Unmatched : RegionType::Mismatch; |
| } |
| |
| return RegionKind; |
| } |
| }; |
| |
| class RenameRangeDetailCollector : public Renamer { |
| void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind, |
| unsigned NameIndex) override { |
| Ranges.push_back({Label, RangeKind, NameIndex}); |
| } |
| void doRenameBase(CharSourceRange Range, |
| RefactoringRangeKind RangeKind) override { |
| Ranges.push_back({Range, RangeKind, None}); |
| } |
| |
| public: |
| RenameRangeDetailCollector(const SourceManager &SM, StringRef OldName) |
| : Renamer(SM, OldName) {} |
| std::vector<RenameRangeDetail> Ranges; |
| }; |
| |
| class TextReplacementsRenamer : public Renamer { |
| llvm::StringMap<char> &ReplaceTextContext; |
| std::vector<Replacement> Replacements; |
| |
| public: |
| const DeclNameViewer New; |
| |
| private: |
| StringRef registerText(StringRef Text) { |
| return ReplaceTextContext.insert({Text, char()}).first->getKey(); |
| } |
| |
| StringRef getCallArgLabelReplacement(StringRef OldLabelRange, |
| StringRef NewLabel) { |
| return NewLabel.empty() ? "" : NewLabel; |
| } |
| |
| StringRef getCallArgColonReplacement(StringRef OldLabelRange, |
| StringRef NewLabel) { |
| // Expected OldLabelRange: foo( []3, a[: ]2, b[ : ]3 ...) |
| // FIXME: Preserve comments: foo([a/*:*/ : /*:*/ ]2, ...) |
| if (NewLabel.empty()) |
| return ""; |
| if (OldLabelRange.empty()) |
| return ": "; |
| return registerText(OldLabelRange); |
| } |
| |
| StringRef getCallArgCombinedReplacement(StringRef OldArgLabel, |
| StringRef NewArgLabel) { |
| // This case only happens when going from foo([]1) to foo([a: ]1). |
| assert(OldArgLabel.empty()); |
| if (NewArgLabel.empty()) |
| return ""; |
| return registerText((llvm::Twine(NewArgLabel) + ": ").str()); |
| } |
| |
| StringRef getParamNameReplacement(StringRef OldParam, StringRef OldArgLabel, |
| StringRef NewArgLabel) { |
| // We don't want to get foo(a a: Int), so drop the parameter name if the |
| // argument label will match the original name. |
| // Note: the leading whitespace is part of the parameter range. |
| if (!NewArgLabel.empty() && OldParam.ltrim() == NewArgLabel) |
| return ""; |
| |
| // If we're renaming foo(x: Int) to foo(_:), then use the original argument |
| // label as the parameter name so as to not break references in the body. |
| if (NewArgLabel.empty() && !OldArgLabel.empty() && OldParam.empty()) |
| return registerText((llvm::Twine(" ") + OldArgLabel).str()); |
| |
| return registerText(OldParam); |
| } |
| |
| StringRef getDeclArgumentLabelReplacement(StringRef OldLabelRange, |
| StringRef NewArgLabel) { |
| // OldLabelRange is subscript([]a: Int), foo([a]: Int) or foo([a] b: Int) |
| if (NewArgLabel.empty()) |
| return OldLabelRange.empty() ? "" : "_"; |
| |
| if (OldLabelRange.empty()) |
| return registerText((llvm::Twine(NewArgLabel) + " ").str()); |
| return registerText(NewArgLabel); |
| } |
| |
| StringRef getReplacementText(StringRef LabelRange, |
| RefactoringRangeKind RangeKind, |
| StringRef OldLabel, StringRef NewLabel) { |
| switch (RangeKind) { |
| case RefactoringRangeKind::CallArgumentLabel: |
| return getCallArgLabelReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::CallArgumentColon: |
| return getCallArgColonReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::CallArgumentCombined: |
| return getCallArgCombinedReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::ParameterName: |
| return getParamNameReplacement(LabelRange, OldLabel, NewLabel); |
| case RefactoringRangeKind::NoncollapsibleParameterName: |
| return LabelRange; |
| case RefactoringRangeKind::DeclArgumentLabel: |
| return getDeclArgumentLabelReplacement(LabelRange, NewLabel); |
| case RefactoringRangeKind::SelectorArgumentLabel: |
| return NewLabel.empty() ? "_" : registerText(NewLabel); |
| default: |
| llvm_unreachable("label range type is none but there are labels"); |
| } |
| } |
| |
| void addReplacement(CharSourceRange LabelRange, |
| RefactoringRangeKind RangeKind, StringRef OldLabel, |
| StringRef NewLabel) { |
| StringRef ExistingLabel = LabelRange.str(); |
| StringRef Text = |
| getReplacementText(ExistingLabel, RangeKind, OldLabel, NewLabel); |
| if (Text != ExistingLabel) |
| Replacements.push_back({LabelRange, Text, {}}); |
| } |
| |
| void doRenameLabel(CharSourceRange Label, RefactoringRangeKind RangeKind, |
| unsigned NameIndex) override { |
| addReplacement(Label, RangeKind, Old.args()[NameIndex], |
| New.args()[NameIndex]); |
| } |
| |
| void doRenameBase(CharSourceRange Range, RefactoringRangeKind) override { |
| if (Old.base() != New.base()) |
| Replacements.push_back({Range, registerText(New.base()), {}}); |
| } |
| |
| public: |
| TextReplacementsRenamer(const SourceManager &SM, StringRef OldName, |
| StringRef NewName, |
| llvm::StringMap<char> &ReplaceTextContext) |
| : Renamer(SM, OldName), ReplaceTextContext(ReplaceTextContext), |
| New(NewName) { |
| assert(Old.isValid() && New.isValid()); |
| assert(Old.partsCount() == New.partsCount()); |
| } |
| |
| std::vector<Replacement> getReplacements() const { |
| return std::move(Replacements); |
| } |
| }; |
| |
| static const ValueDecl *getRelatedSystemDecl(const ValueDecl *VD) { |
| if (VD->getModuleContext()->isSystemModule()) |
| return VD; |
| for (auto *Req : VD->getSatisfiedProtocolRequirements()) { |
| if (Req->getModuleContext()->isSystemModule()) |
| return Req; |
| } |
| for (auto Over = VD->getOverriddenDecl(); Over; |
| Over = Over->getOverriddenDecl()) { |
| if (Over->getModuleContext()->isSystemModule()) |
| return Over; |
| } |
| return nullptr; |
| } |
| |
| static Optional<RefactoringKind> getAvailableRenameForDecl(const ValueDecl *VD) { |
| std::vector<RenameAvailabiliyInfo> Scratch; |
| for (auto &Info : collectRenameAvailabilityInfo(VD, Scratch)) { |
| if (Info.AvailableKind == RenameAvailableKind::Available) |
| return Info.Kind; |
| } |
| return None; |
| } |
| |
| class RenameRangeCollector : public IndexDataConsumer { |
| public: |
| RenameRangeCollector(StringRef USR, StringRef newName) |
| : USR(USR.str()), newName(newName.str()) {} |
| |
| RenameRangeCollector(const ValueDecl *D, StringRef newName) |
| : newName(newName.str()) { |
| llvm::raw_string_ostream OS(USR); |
| printDeclUSR(D, OS); |
| } |
| |
| ArrayRef<RenameLoc> results() const { return locations; } |
| |
| private: |
| bool indexLocals() override { return true; } |
| void failed(StringRef error) override {} |
| bool recordHash(StringRef hash, bool isKnown) override { return true; } |
| bool startDependency(StringRef name, StringRef path, bool isClangModule, |
| bool isSystem, StringRef hash) override { |
| return true; |
| } |
| bool finishDependency(bool isClangModule) override { return true; } |
| |
| Action startSourceEntity(const IndexSymbol &symbol) override { |
| if (symbol.USR == USR) { |
| if (auto loc = indexSymbolToRenameLoc(symbol, newName)) { |
| locations.push_back(std::move(*loc)); |
| } |
| } |
| return IndexDataConsumer::Continue; |
| } |
| |
| bool finishSourceEntity(SymbolInfo symInfo, SymbolRoleSet roles) override { |
| return true; |
| } |
| |
| Optional<RenameLoc> indexSymbolToRenameLoc(const index::IndexSymbol &symbol, |
| StringRef NewName); |
| |
| private: |
| std::string USR; |
| std::string newName; |
| StringScratchSpace stringStorage; |
| std::vector<RenameLoc> locations; |
| }; |
| |
| Optional<RenameLoc> |
| RenameRangeCollector::indexSymbolToRenameLoc(const index::IndexSymbol &symbol, |
| StringRef newName) { |
| if (symbol.roles & (unsigned)index::SymbolRole::Implicit) { |
| return None; |
| } |
| |
| NameUsage usage = NameUsage::Unknown; |
| if (symbol.roles & (unsigned)index::SymbolRole::Call) { |
| usage = NameUsage::Call; |
| } else if (symbol.roles & (unsigned)index::SymbolRole::Definition) { |
| usage = NameUsage::Definition; |
| } else if (symbol.roles & (unsigned)index::SymbolRole::Reference) { |
| usage = NameUsage::Reference; |
| } else { |
| llvm_unreachable("unexpected role"); |
| } |
| |
| bool isFunctionLike = false; |
| bool isNonProtocolType = false; |
| |
| switch (symbol.symInfo.Kind) { |
| case index::SymbolKind::EnumConstant: |
| case index::SymbolKind::Function: |
| case index::SymbolKind::Constructor: |
| case index::SymbolKind::ConversionFunction: |
| case index::SymbolKind::InstanceMethod: |
| case index::SymbolKind::ClassMethod: |
| case index::SymbolKind::StaticMethod: |
| isFunctionLike = true; |
| break; |
| case index::SymbolKind::Class: |
| case index::SymbolKind::Enum: |
| case index::SymbolKind::Struct: |
| isNonProtocolType = true; |
| break; |
| default: |
| break; |
| } |
| StringRef oldName = stringStorage.copyString(symbol.name); |
| return RenameLoc{symbol.line, symbol.column, usage, oldName, newName, |
| isFunctionLike, isNonProtocolType}; |
| } |
| |
| ArrayRef<SourceFile*> |
| collectSourceFiles(ModuleDecl *MD, llvm::SmallVectorImpl<SourceFile*> &Scratch) { |
| for (auto Unit : MD->getFiles()) { |
| if (auto SF = dyn_cast<SourceFile>(Unit)) { |
| Scratch.push_back(SF); |
| } |
| } |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| /// Get the source file that contains the given range and belongs to the module. |
| SourceFile *getContainingFile(ModuleDecl *M, RangeConfig Range) { |
| llvm::SmallVector<SourceFile*, 4> Files; |
| for (auto File : collectSourceFiles(M, Files)) { |
| if (File->getBufferID()) { |
| if (File->getBufferID().getValue() == Range.BufferId) { |
| return File; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| class RefactoringAction { |
| protected: |
| ModuleDecl *MD; |
| SourceFile *TheFile; |
| SourceEditConsumer &EditConsumer; |
| ASTContext &Ctx; |
| SourceManager &SM; |
| DiagnosticEngine DiagEngine; |
| SourceLoc StartLoc; |
| StringRef PreferredName; |
| public: |
| RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer); |
| virtual ~RefactoringAction() = default; |
| virtual bool performChange() = 0; |
| }; |
| |
| RefactoringAction:: |
| RefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer): MD(MD), |
| TheFile(getContainingFile(MD, Opts.Range)), |
| EditConsumer(EditConsumer), Ctx(MD->getASTContext()), |
| SM(MD->getASTContext().SourceMgr), DiagEngine(SM), |
| StartLoc(Lexer::getLocForStartOfToken(SM, Opts.Range.getStart(SM))), |
| PreferredName(Opts.PreferredName) { |
| DiagEngine.addConsumer(DiagConsumer); |
| } |
| |
| /// Different from RangeBasedRefactoringAction, TokenBasedRefactoringAction takes |
| /// the input of a given token, e.g., a name or an "if" key word. Contextual |
| /// refactoring kinds can suggest applicable refactorings on that token, e.g. |
| /// rename or reverse if statement. |
| class TokenBasedRefactoringAction : public RefactoringAction { |
| protected: |
| ResolvedCursorInfo CursorInfo; |
| public: |
| TokenBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) : |
| RefactoringAction(MD, Opts, EditConsumer, DiagConsumer) { |
| // We can only proceed with valid location and source file. |
| if (StartLoc.isValid() && TheFile) { |
| // Resolve the sema token and save it for later use. |
| CursorInfoResolver Resolver(*TheFile); |
| CursorInfo = Resolver.resolve(StartLoc); |
| } |
| } |
| }; |
| |
| #define CURSOR_REFACTORING(KIND, NAME, ID) \ |
| class RefactoringAction##KIND: public TokenBasedRefactoringAction { \ |
| public: \ |
| RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \ |
| SourceEditConsumer &EditConsumer, \ |
| DiagnosticConsumer &DiagConsumer) : \ |
| TokenBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \ |
| bool performChange() override; \ |
| static bool isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag); \ |
| bool isApplicable() { \ |
| return RefactoringAction##KIND::isApplicable(CursorInfo, DiagEngine) ; \ |
| } \ |
| }; |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| class RangeBasedRefactoringAction : public RefactoringAction { |
| RangeResolver Resolver; |
| protected: |
| ResolvedRangeInfo RangeInfo; |
| public: |
| RangeBasedRefactoringAction(ModuleDecl *MD, RefactoringOptions &Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) : |
| RefactoringAction(MD, Opts, EditConsumer, DiagConsumer), |
| Resolver(*TheFile, Opts.Range.getStart(SM), Opts.Range.getEnd(SM)), |
| RangeInfo(Resolver.resolve()) {} |
| }; |
| |
| #define RANGE_REFACTORING(KIND, NAME, ID) \ |
| class RefactoringAction##KIND: public RangeBasedRefactoringAction { \ |
| public: \ |
| RefactoringAction##KIND(ModuleDecl *MD, RefactoringOptions &Opts, \ |
| SourceEditConsumer &EditConsumer, \ |
| DiagnosticConsumer &DiagConsumer) : \ |
| RangeBasedRefactoringAction(MD, Opts, EditConsumer, DiagConsumer) {} \ |
| bool performChange() override; \ |
| static bool isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag); \ |
| bool isApplicable() { \ |
| return RefactoringAction##KIND::isApplicable(RangeInfo, DiagEngine) ; \ |
| } \ |
| }; |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| bool RefactoringActionLocalRename:: |
| isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) { |
| if (CursorInfo.Kind != CursorInfoKind::ValueRef) |
| return false; |
| auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD); |
| return RenameOp.hasValue() && |
| RenameOp.getValue() == RefactoringKind::LocalRename; |
| } |
| |
| static void analyzeRenameScope(ValueDecl *VD, DiagnosticEngine &Diags, |
| llvm::SmallVectorImpl<DeclContext *> &Scopes) { |
| Scopes.clear(); |
| if (!getAvailableRenameForDecl(VD).hasValue()) { |
| Diags.diagnose(SourceLoc(), diag::value_decl_no_loc, VD->getFullName()); |
| return; |
| } |
| |
| auto *Scope = VD->getDeclContext(); |
| // If the context is a top-level code decl, there may be other sibling |
| // decls that the renamed symbol is visible from |
| if (isa<TopLevelCodeDecl>(Scope)) |
| Scope = Scope->getParent(); |
| |
| Scopes.push_back(Scope); |
| } |
| |
| bool RefactoringActionLocalRename::performChange() { |
| if (StartLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_location); |
| return true; |
| } |
| if (!DeclNameViewer(PreferredName).isValid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| if (!TheFile) { |
| DiagEngine.diagnose(StartLoc, diag::location_module_mismatch, |
| MD->getNameStr()); |
| return true; |
| } |
| CursorInfoResolver Resolver(*TheFile); |
| ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc); |
| if (CursorInfo.isValid() && CursorInfo.ValueD) { |
| ValueDecl *VD = CursorInfo.ValueD; |
| llvm::SmallVector<DeclContext *, 8> Scopes; |
| analyzeRenameScope(VD, DiagEngine, Scopes); |
| if (Scopes.empty()) |
| return true; |
| RenameRangeCollector rangeCollector(VD, PreferredName); |
| for (DeclContext *DC : Scopes) |
| indexDeclContext(DC, rangeCollector); |
| |
| auto consumers = DiagEngine.takeConsumers(); |
| assert(consumers.size() == 1); |
| return syntacticRename(TheFile, rangeCollector.results(), EditConsumer, |
| *consumers[0]); |
| } else { |
| DiagEngine.diagnose(StartLoc, diag::unresolved_location); |
| return true; |
| } |
| } |
| |
| StringRef getDefaultPreferredName(RefactoringKind Kind) { |
| switch(Kind) { |
| case RefactoringKind::None: |
| llvm_unreachable("Should be a valid refactoring kind"); |
| case RefactoringKind::GlobalRename: |
| case RefactoringKind::LocalRename: |
| return "newName"; |
| case RefactoringKind::ExtractExpr: |
| case RefactoringKind::ExtractRepeatedExpr: |
| return "extractedExpr"; |
| case RefactoringKind::ExtractFunction: |
| return "extractedFunc"; |
| default: |
| return ""; |
| } |
| } |
| |
| enum class CannotExtractReason { |
| Literal, |
| VoidType, |
| }; |
| |
| class ExtractCheckResult { |
| bool KnownFailure; |
| llvm::SmallVector<CannotExtractReason, 2> AllReasons; |
| |
| public: |
| ExtractCheckResult(): KnownFailure(true) {} |
| ExtractCheckResult(ArrayRef<CannotExtractReason> AllReasons): |
| KnownFailure(false), AllReasons(AllReasons.begin(), AllReasons.end()) {} |
| bool success() { return success({}); } |
| bool success(llvm::ArrayRef<CannotExtractReason> ExpectedReasons) { |
| if (KnownFailure) |
| return false; |
| bool Result = true; |
| |
| // Check if any reasons aren't covered by the list of expected reasons |
| // provided by the client. |
| for (auto R: AllReasons) { |
| Result &= llvm::is_contained(ExpectedReasons, R); |
| } |
| return Result; |
| } |
| }; |
| |
| /// Check whether a given range can be extracted. |
| /// Return true on successful condition checking,. |
| /// Return false on failed conditions. |
| ExtractCheckResult checkExtractConditions(ResolvedRangeInfo &RangeInfo, |
| DiagnosticEngine &DiagEngine) { |
| llvm::SmallVector<CannotExtractReason, 2> AllReasons; |
| // If any declared declaration is refered out of the given range, return false. |
| auto Declared = RangeInfo.DeclaredDecls; |
| auto It = std::find_if(Declared.begin(), Declared.end(), |
| [](DeclaredDecl DD) { return DD.ReferredAfterRange; }); |
| if (It != Declared.end()) { |
| DiagEngine.diagnose(It->VD->getLoc(), |
| diag::value_decl_referenced_out_of_range, |
| It->VD->getFullName()); |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract a range with multi entry points. |
| if (!RangeInfo.HasSingleEntry) { |
| DiagEngine.diagnose(SourceLoc(), diag::multi_entry_range); |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract code that is not sure to exit or not. |
| if (RangeInfo.exit() == ExitState::Unsure) { |
| return ExtractCheckResult(); |
| } |
| |
| // We cannot extract expressions of l-value type. |
| if (auto Ty = RangeInfo.getType()) { |
| if (Ty->hasLValueType() || Ty->is<InOutType>()) |
| return ExtractCheckResult(); |
| |
| // Disallow extracting error type expressions/statements |
| // FIXME: diagnose what happened? |
| if (Ty->hasError()) |
| return ExtractCheckResult(); |
| |
| if (Ty->isVoid()) { |
| AllReasons.emplace_back(CannotExtractReason::VoidType); |
| } |
| } |
| |
| // We cannot extract a range with orphaned loop keyword. |
| switch (RangeInfo.Orphan) { |
| case swift::ide::OrphanKind::Continue: |
| DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "continue"); |
| return ExtractCheckResult(); |
| case swift::ide::OrphanKind::Break: |
| DiagEngine.diagnose(SourceLoc(), diag::orphan_loop_keyword, "break"); |
| return ExtractCheckResult(); |
| case swift::ide::OrphanKind::None: |
| break; |
| } |
| |
| // Guard statement can not be extracted. |
| if (llvm::any_of(RangeInfo.ContainedNodes, |
| [](ASTNode N) { return N.isStmt(StmtKind::Guard); })) { |
| return ExtractCheckResult(); |
| } |
| |
| // Disallow extracting literals. |
| if (RangeInfo.Kind == RangeKind::SingleExpression) { |
| Expr *E = RangeInfo.ContainedNodes[0].get<Expr*>(); |
| |
| // Until implementing the performChange() part of extracting trailing |
| // closures, we disable them for now. |
| if (isa<AbstractClosureExpr>(E)) |
| return ExtractCheckResult(); |
| |
| if (isa<LiteralExpr>(E)) |
| AllReasons.emplace_back(CannotExtractReason::Literal); |
| } |
| |
| switch (RangeInfo.RangeContext->getContextKind()) { |
| case swift::DeclContextKind::Initializer: |
| case swift::DeclContextKind::SubscriptDecl: |
| case swift::DeclContextKind::AbstractFunctionDecl: |
| case swift::DeclContextKind::AbstractClosureExpr: |
| case swift::DeclContextKind::TopLevelCodeDecl: |
| break; |
| |
| case swift::DeclContextKind::SerializedLocal: |
| case swift::DeclContextKind::Module: |
| case swift::DeclContextKind::FileUnit: |
| case swift::DeclContextKind::GenericTypeDecl: |
| case swift::DeclContextKind::ExtensionDecl: |
| return ExtractCheckResult(); |
| } |
| return ExtractCheckResult(AllReasons); |
| } |
| |
| bool RefactoringActionExtractFunction:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::MultiTypeMemberDecl: |
| case RangeKind::Invalid: |
| return false; |
| case RangeKind::SingleExpression: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: { |
| return checkExtractConditions(Info, Diag). |
| success({CannotExtractReason::VoidType}); |
| } |
| } |
| } |
| |
| static StringRef correctNameInternal(ASTContext &Ctx, StringRef Name, |
| ArrayRef<ValueDecl*> AllVisibles) { |
| // If we find the collision. |
| bool FoundCollision = false; |
| |
| // The suffixes we cannot use by appending to the original given name. |
| llvm::StringSet<> UsedSuffixes; |
| for (auto VD : AllVisibles) { |
| StringRef S = VD->getBaseName().userFacingName(); |
| if (!S.startswith(Name)) |
| continue; |
| StringRef Suffix = S.substr(Name.size()); |
| if (Suffix.empty()) |
| FoundCollision = true; |
| else |
| UsedSuffixes.insert(Suffix); |
| } |
| if (!FoundCollision) |
| return Name; |
| |
| // Find the first suffix we can use. |
| std::string SuffixToUse; |
| for (unsigned I = 1; ; I ++) { |
| SuffixToUse = std::to_string(I); |
| if (UsedSuffixes.count(SuffixToUse) == 0) |
| break; |
| } |
| return Ctx.getIdentifier((llvm::Twine(Name) + SuffixToUse).str()).str(); |
| } |
| |
| static StringRef correctNewDeclName(DeclContext *DC, StringRef Name) { |
| |
| // Collect all visible decls in the decl context. |
| llvm::SmallVector<ValueDecl*, 16> AllVisibles; |
| VectorDeclConsumer Consumer(AllVisibles); |
| ASTContext &Ctx = DC->getASTContext(); |
| lookupVisibleDecls(Consumer, DC, Ctx.getLazyResolver(), true); |
| return correctNameInternal(Ctx, Name, AllVisibles); |
| } |
| |
| static Type sanitizeType(Type Ty) { |
| // Transform lvalue type to inout type so that we can print it properly. |
| return Ty.transform([](Type Ty) { |
| if (Ty->is<LValueType>()) { |
| return Type(InOutType::get(Ty->getRValueType()->getCanonicalType())); |
| } |
| return Ty; |
| }); |
| } |
| |
| static SourceLoc |
| getNewFuncInsertLoc(DeclContext *DC, DeclContext*& InsertToContext) { |
| if (auto D = DC->getInnermostDeclarationDeclContext()) { |
| |
| // If extracting from a getter/setter, we should skip both the immediate |
| // getter/setter function and the individual var decl. The pattern binding |
| // decl is the position before which we should insert the newly extracted |
| // function. |
| if (auto *FD = dyn_cast<AccessorDecl>(D)) { |
| ValueDecl *SD = FD->getStorage(); |
| switch (SD->getKind()) { |
| case DeclKind::Var: |
| if (auto *PBD = cast<VarDecl>(SD)->getParentPatternBinding()) |
| D = PBD; |
| break; |
| case DeclKind::Subscript: |
| D = SD; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| auto Result = D->getStartLoc(); |
| assert(Result.isValid()); |
| |
| // The insert loc should be before every decl attributes. |
| for (auto Attr : D->getAttrs()) { |
| auto Loc = Attr->getRangeWithAt().Start; |
| if (Loc.isValid() && |
| Loc.getOpaquePointerValue() < Result.getOpaquePointerValue()) |
| Result = Loc; |
| } |
| |
| // The insert loc should be before the doc comments associated with this decl. |
| if (!D->getRawComment().Comments.empty()) { |
| auto Loc = D->getRawComment().Comments.front().Range.getStart(); |
| if (Loc.isValid() && |
| Loc.getOpaquePointerValue() < Result.getOpaquePointerValue()) { |
| Result = Loc; |
| } |
| } |
| InsertToContext = D->getDeclContext(); |
| return Result; |
| } |
| return SourceLoc(); |
| } |
| |
| static std::vector<NoteRegion> |
| getNotableRegions(StringRef SourceText, unsigned NameOffset, StringRef Name, |
| bool IsFunctionLike = false, bool IsNonProtocolType = false) { |
| auto InputBuffer = llvm::MemoryBuffer::getMemBufferCopy(SourceText,"<extract>"); |
| |
| CompilerInvocation Invocation{}; |
| |
| Invocation.getFrontendOptions().InputsAndOutputs.addInput( |
| InputFile("<extract>", true, InputBuffer.get())); |
| Invocation.getFrontendOptions().ModuleName = "extract"; |
| |
| auto Instance = llvm::make_unique<swift::CompilerInstance>(); |
| if (Instance->setup(Invocation)) |
| llvm_unreachable("Failed setup"); |
| |
| Instance->performParseOnly(); |
| |
| unsigned BufferId = Instance->getPrimarySourceFile()->getBufferID().getValue(); |
| SourceManager &SM = Instance->getSourceMgr(); |
| SourceLoc NameLoc = SM.getLocForOffset(BufferId, NameOffset); |
| auto LineAndCol = SM.getLineAndColumn(NameLoc); |
| |
| UnresolvedLoc UnresoledName{NameLoc, true}; |
| |
| NameMatcher Matcher(*Instance->getPrimarySourceFile()); |
| auto Resolved = Matcher.resolve(llvm::makeArrayRef(UnresoledName), None); |
| assert(!Resolved.empty() && "Failed to resolve generated func name loc"); |
| |
| RenameLoc RenameConfig = { |
| LineAndCol.first, LineAndCol.second, |
| NameUsage::Definition, /*OldName=*/Name, /*NewName=*/"", |
| IsFunctionLike, IsNonProtocolType |
| }; |
| RenameRangeDetailCollector Renamer(SM, Name); |
| Renamer.addSyntacticRenameRanges(Resolved.back(), RenameConfig); |
| auto Ranges = Renamer.Ranges; |
| |
| std::vector<NoteRegion> NoteRegions(Renamer.Ranges.size()); |
| std::transform(Ranges.begin(), Ranges.end(), NoteRegions.begin(), |
| [&SM](RenameRangeDetail &Detail) -> NoteRegion { |
| auto Start = SM.getLineAndColumn(Detail.Range.getStart()); |
| auto End = SM.getLineAndColumn(Detail.Range.getEnd()); |
| return {Detail.RangeKind, Start.first, Start.second, End.first, End.second, Detail.Index}; |
| }); |
| |
| return NoteRegions; |
| } |
| |
| bool RefactoringActionExtractFunction::performChange() { |
| // Check if the new name is ok. |
| if (!Lexer::isIdentifier(PreferredName)) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| DeclContext *DC = RangeInfo.RangeContext; |
| DeclContext *InsertToDC = nullptr; |
| SourceLoc InsertLoc = getNewFuncInsertLoc(DC, InsertToDC); |
| |
| // Complain about no inserting position. |
| if (InsertLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::no_insert_position); |
| return true; |
| } |
| |
| // Correct the given name if collision happens. |
| PreferredName = correctNewDeclName(InsertToDC, PreferredName); |
| |
| // Collect the paramters to pass down to the new function. |
| std::vector<ReferencedDecl> Parameters; |
| for (auto &RD: RangeInfo.ReferencedDecls) { |
| // If the referenced decl is declared elsewhere, no need to pass as parameter |
| if (RD.VD->getDeclContext() != DC) |
| continue; |
| |
| // We don't need to pass down implicitly declared variables, e.g. error in |
| // a catch block. |
| if (RD.VD->isImplicit()) { |
| SourceLoc Loc = RD.VD->getStartLoc(); |
| if (Loc.isValid() && |
| SM.isBeforeInBuffer(RangeInfo.ContentRange.getStart(), Loc) && |
| SM.isBeforeInBuffer(Loc, RangeInfo.ContentRange.getEnd())) |
| continue; |
| } |
| |
| // If the referenced decl is declared inside the range, no need to pass |
| // as parameter. |
| if (RangeInfo.DeclaredDecls.end() != |
| std::find_if(RangeInfo.DeclaredDecls.begin(), RangeInfo.DeclaredDecls.end(), |
| [RD](DeclaredDecl DD) { return RD.VD == DD.VD; })) |
| continue; |
| |
| // We don't need to pass down self. |
| if (auto PD = dyn_cast<ParamDecl>(RD.VD)) { |
| if (PD->isSelfParameter()) { |
| continue; |
| } |
| } |
| |
| Parameters.emplace_back(RD.VD, sanitizeType(RD.Ty)); |
| } |
| SmallString<64> Buffer; |
| unsigned FuncBegin = Buffer.size(); |
| unsigned FuncNameOffset; |
| { |
| llvm::raw_svector_ostream OS(Buffer); |
| |
| if (!InsertToDC->isLocalContext()) { |
| // Default to be file private. |
| OS << tok::kw_fileprivate << " "; |
| } |
| |
| // Inherit static if the containing function is. |
| if (DC->getContextKind() == DeclContextKind::AbstractFunctionDecl) { |
| if (auto FD = dyn_cast<FuncDecl>(static_cast<AbstractFunctionDecl*>(DC))) { |
| if (FD->isStatic()) { |
| OS << tok::kw_static << " "; |
| } |
| } |
| } |
| |
| OS << tok::kw_func << " "; |
| FuncNameOffset = Buffer.size() - FuncBegin; |
| OS << PreferredName; |
| OS << "("; |
| for (auto &RD : Parameters) { |
| OS << "_ " << RD.VD->getBaseName().userFacingName() << ": "; |
| RD.Ty->reconstituteSugar(/*Recursive*/true)->print(OS); |
| if (&RD != &Parameters.back()) |
| OS << ", "; |
| } |
| OS << ")"; |
| |
| if (RangeInfo.ThrowingUnhandledError) |
| OS << " " << tok::kw_throws; |
| |
| bool InsertedReturnType = false; |
| if (auto Ty = RangeInfo.getType()) { |
| // If the type of the range is not void, specify the return type. |
| if (!Ty->isVoid()) { |
| OS << " " << tok::arrow << " "; |
| sanitizeType(Ty)->reconstituteSugar(/*Recursive*/true)->print(OS); |
| InsertedReturnType = true; |
| } |
| } |
| |
| OS << " {\n"; |
| |
| // Add "return" if the extracted entity is an expression. |
| if (RangeInfo.Kind == RangeKind::SingleExpression && InsertedReturnType) |
| OS << tok::kw_return << " "; |
| OS << RangeInfo.ContentRange.str() << "\n}\n\n"; |
| } |
| unsigned FuncEnd = Buffer.size(); |
| |
| unsigned ReplaceBegin = Buffer.size(); |
| unsigned CallNameOffset; |
| { |
| llvm::raw_svector_ostream OS(Buffer); |
| if (RangeInfo.exit() == ExitState::Positive) |
| OS << tok::kw_return <<" "; |
| CallNameOffset = Buffer.size() - ReplaceBegin; |
| OS << PreferredName << "("; |
| for (auto &RD : Parameters) { |
| |
| // Inout argument needs "&". |
| if (RD.Ty->is<InOutType>()) |
| OS << "&"; |
| OS << RD.VD->getBaseName().userFacingName(); |
| if (&RD != &Parameters.back()) |
| OS << ", "; |
| } |
| OS << ")"; |
| } |
| unsigned ReplaceEnd = Buffer.size(); |
| |
| std::string ExtractedFuncName = PreferredName.str() + "("; |
| for (size_t i = 0; i < Parameters.size(); ++i) { |
| ExtractedFuncName += "_:"; |
| } |
| ExtractedFuncName += ")"; |
| |
| StringRef DeclStr(Buffer.begin() + FuncBegin, FuncEnd - FuncBegin); |
| auto NotableFuncRegions = getNotableRegions(DeclStr, FuncNameOffset, |
| ExtractedFuncName, |
| /*IsFunctionLike=*/true); |
| |
| StringRef CallStr(Buffer.begin() + ReplaceBegin, ReplaceEnd - ReplaceBegin); |
| auto NotableCallRegions = getNotableRegions(CallStr, CallNameOffset, |
| ExtractedFuncName, |
| /*IsFunctionLike=*/true); |
| |
| // Insert the new function's declaration. |
| EditConsumer.accept(SM, InsertLoc, DeclStr, NotableFuncRegions); |
| |
| // Replace the code to extract with the function call. |
| EditConsumer.accept(SM, RangeInfo.ContentRange, CallStr, NotableCallRegions); |
| |
| return false; |
| } |
| |
| class RefactoringActionExtractExprBase { |
| SourceFile *TheFile; |
| ResolvedRangeInfo RangeInfo; |
| DiagnosticEngine &DiagEngine; |
| const bool ExtractRepeated; |
| StringRef PreferredName; |
| SourceEditConsumer &EditConsumer; |
| |
| ASTContext &Ctx; |
| SourceManager &SM; |
| |
| public: |
| RefactoringActionExtractExprBase(SourceFile *TheFile, |
| ResolvedRangeInfo RangeInfo, |
| DiagnosticEngine &DiagEngine, |
| bool ExtractRepeated, |
| StringRef PreferredName, |
| SourceEditConsumer &EditConsumer) : |
| TheFile(TheFile), RangeInfo(RangeInfo), DiagEngine(DiagEngine), |
| ExtractRepeated(ExtractRepeated), PreferredName(PreferredName), |
| EditConsumer(EditConsumer), Ctx(TheFile->getASTContext()), |
| SM(Ctx.SourceMgr){} |
| bool performChange(); |
| }; |
| |
| /// This is to ensure all decl references in two expressions are identical. |
| struct ReferenceCollector: public SourceEntityWalker { |
| llvm::SmallVector<ValueDecl*, 4> References; |
| |
| ReferenceCollector(Expr *E) { walk(E); } |
| bool visitDeclReference(ValueDecl *D, CharSourceRange Range, |
| TypeDecl *CtorTyRef, ExtensionDecl *ExtTyRef, |
| Type T, ReferenceMetaData Data) override { |
| References.emplace_back(D); |
| return true; |
| } |
| bool operator==(const ReferenceCollector &Other) const { |
| if (References.size() != Other.References.size()) |
| return false; |
| return std::equal(References.begin(), References.end(), |
| Other.References.begin()); |
| } |
| }; |
| |
| struct SimilarExprCollector: public SourceEntityWalker { |
| SourceManager &SM; |
| |
| /// The expression under selection. |
| Expr *SelectedExpr; |
| llvm::ArrayRef<Token> AllTokens; |
| llvm::SetVector<Expr*> &Bucket; |
| |
| /// The tokens included in the expression under selection. |
| llvm::ArrayRef<Token> SelectedTokens; |
| |
| /// The referenced decls in the expression under selection. |
| ReferenceCollector SelectedReferences; |
| |
| bool compareTokenContent(ArrayRef<Token> Left, ArrayRef<Token> Right) { |
| if (Left.size() != Right.size()) |
| return false; |
| return std::equal(Left.begin(), Left.end(), Right.begin(), |
| [](const Token &L, const Token& R) { |
| return L.getText() == R.getText(); |
| }); |
| } |
| |
| /// Find all tokens included by an expression. |
| llvm::ArrayRef<Token> getExprSlice(Expr *E) { |
| return slice_token_array(AllTokens, E->getStartLoc(), E->getEndLoc()); |
| } |
| |
| SimilarExprCollector(SourceManager &SM, Expr* SelectedExpr, |
| llvm::ArrayRef<Token> AllTokens, |
| llvm::SetVector<Expr*> &Bucket): SM(SM), SelectedExpr(SelectedExpr), |
| AllTokens(AllTokens), Bucket(Bucket), |
| SelectedTokens(getExprSlice(SelectedExpr)), |
| SelectedReferences(SelectedExpr){} |
| |
| bool walkToExprPre(Expr *E) override { |
| // We don't extract implicit expressions. |
| if (E->isImplicit()) |
| return true; |
| if (E->getKind() != SelectedExpr->getKind()) |
| return true; |
| |
| // First check the underlying token arrays have the same content. |
| if (compareTokenContent(getExprSlice(E), SelectedTokens)) { |
| ReferenceCollector CurrentReferences(E); |
| |
| // Next, check the referenced decls are same. |
| if (CurrentReferences == SelectedReferences) |
| Bucket.insert(E); |
| } |
| return true; |
| } |
| }; |
| |
| bool RefactoringActionExtractExprBase::performChange() { |
| // Check if the new name is ok. |
| if (!Lexer::isIdentifier(PreferredName)) { |
| DiagEngine.diagnose(SourceLoc(), diag::invalid_name, PreferredName); |
| return true; |
| } |
| |
| // Find the enclosing brace statement; |
| ContextFinder Finder(*TheFile, RangeInfo.ContainedNodes.front(), |
| [](ASTNode N) { return N.isStmt(StmtKind::Brace); }); |
| |
| auto *SelectedExpr = RangeInfo.ContainedNodes[0].get<Expr*>(); |
| Finder.resolve(); |
| SourceLoc InsertLoc; |
| llvm::SetVector<ValueDecl*> AllVisibleDecls; |
| struct DeclCollector: public SourceEntityWalker { |
| llvm::SetVector<ValueDecl*> &Bucket; |
| DeclCollector(llvm::SetVector<ValueDecl*> &Bucket): Bucket(Bucket) {} |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) override { |
| if (auto *VD = dyn_cast<ValueDecl>(D)) |
| Bucket.insert(VD); |
| return true; |
| } |
| } Collector(AllVisibleDecls); |
| |
| llvm::SetVector<Expr*> AllExpressions; |
| |
| if (!Finder.getContexts().empty()) { |
| |
| // Get the innermost brace statement. |
| auto BS = static_cast<BraceStmt*>(Finder.getContexts().back().get<Stmt*>()); |
| |
| // Collect all value decls inside the brace statement. |
| Collector.walk(BS); |
| |
| if (ExtractRepeated) { |
| // Collect all expressions we are going to extract. |
| SimilarExprCollector(SM, SelectedExpr, |
| slice_token_array(TheFile->getAllTokens(), |
| BS->getStartLoc(), |
| BS->getEndLoc()), |
| AllExpressions).walk(BS); |
| } else { |
| AllExpressions.insert(SelectedExpr); |
| } |
| |
| assert(!AllExpressions.empty() && "at least one expression is extracted."); |
| for (auto Ele : BS->getElements()) { |
| // Find the element that encloses the first expression under extraction. |
| if (SM.rangeContains(Ele.getSourceRange(), |
| (*AllExpressions.begin())->getSourceRange())) { |
| |
| // Insert before the enclosing element. |
| InsertLoc = Ele.getStartLoc(); |
| } |
| } |
| } |
| |
| // Complain about no inserting position. |
| if (InsertLoc.isInvalid()) { |
| DiagEngine.diagnose(SourceLoc(), diag::no_insert_position); |
| return true; |
| } |
| |
| // Correct name if collision happens. |
| PreferredName = correctNameInternal(TheFile->getASTContext(), PreferredName, |
| AllVisibleDecls.getArrayRef()); |
| |
| // Print the type name of this expression. |
| llvm::SmallString<16> TyBuffer; |
| |
| // We are not sure about the type of repeated expressions. |
| if (!ExtractRepeated) { |
| if (auto Ty = RangeInfo.getType()) { |
| llvm::raw_svector_ostream OS(TyBuffer); |
| OS << ": "; |
| Ty->getRValueType()->reconstituteSugar(true)->print(OS); |
| } |
| } |
| |
| llvm::SmallString<64> DeclBuffer; |
| llvm::raw_svector_ostream OS(DeclBuffer); |
| unsigned StartOffset, EndOffset; |
| OS << tok::kw_let << " "; |
| StartOffset = DeclBuffer.size(); |
| OS << PreferredName; |
| EndOffset = DeclBuffer.size(); |
| OS << TyBuffer.str() << " = " << RangeInfo.ContentRange.str() << "\n"; |
| |
| NoteRegion DeclNameRegion{ |
| RefactoringRangeKind::BaseName, |
| /*StartLine=*/1, /*StartColumn=*/StartOffset + 1, |
| /*EndLine=*/1, /*EndColumn=*/EndOffset + 1, |
| /*ArgIndex*/None |
| }; |
| |
| // Perform code change. |
| EditConsumer.accept(SM, InsertLoc, DeclBuffer.str(), {DeclNameRegion}); |
| |
| // Replace all occurrences of the extracted expression. |
| for (auto *E : AllExpressions) { |
| EditConsumer.accept(SM, |
| Lexer::getCharSourceRangeFromSourceRange(SM, E->getSourceRange()), |
| PreferredName, |
| {{ |
| RefactoringRangeKind::BaseName, |
| /*StartLine=*/1, /*StartColumn-*/1, /*EndLine=*/1, |
| /*EndColumn=*/static_cast<unsigned int>(PreferredName.size() + 1), |
| /*ArgIndex*/None |
| }}); |
| } |
| return false; |
| } |
| |
| bool RefactoringActionExtractExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::SingleExpression: |
| // We disallow extract literal expression for two reasons: |
| // (1) since we print the type for extracted expression, the type of a |
| // literal may print as "int2048" where it is not typically users' choice; |
| // (2) Extracting one literal provides little value for users. |
| return checkExtractConditions(Info, Diag).success(); |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::MultiTypeMemberDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| |
| bool RefactoringActionExtractExpr::performChange() { |
| return RefactoringActionExtractExprBase(TheFile, RangeInfo, |
| DiagEngine, false, PreferredName, |
| EditConsumer).performChange(); |
| } |
| |
| bool RefactoringActionExtractRepeatedExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::SingleExpression: |
| return checkExtractConditions(Info, Diag). |
| success({CannotExtractReason::Literal}); |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleDecl: |
| case RangeKind::MultiTypeMemberDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| bool RefactoringActionExtractRepeatedExpr::performChange() { |
| return RefactoringActionExtractExprBase(TheFile, RangeInfo, |
| DiagEngine, true, PreferredName, |
| EditConsumer).performChange(); |
| } |
| |
| // Compute a decl context that is the parent context for all decls in |
| // \c DeclaredDecls. Return \c nullptr if no such context exists. |
| DeclContext *getCommonDeclContext(ArrayRef<DeclaredDecl> DeclaredDecls) { |
| if (DeclaredDecls.empty()) |
| return nullptr; |
| |
| DeclContext *CommonDC = DeclaredDecls.front().VD->getDeclContext(); |
| for (auto DD : DeclaredDecls) { |
| auto OtherDC = DD.VD->getDeclContext(); |
| CommonDC = DeclContext::getCommonParentContext(CommonDC, OtherDC); |
| } |
| return CommonDC; |
| } |
| |
| bool RefactoringActionMoveMembersToExtension::isApplicable( |
| ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| switch (Info.Kind) { |
| case RangeKind::SingleDecl: |
| case RangeKind::MultiTypeMemberDecl: { |
| DeclContext *CommonDC = getCommonDeclContext(Info.DeclaredDecls); |
| |
| // The the common decl context is not a nomial type, we cannot create an |
| // extension for it |
| if (!CommonDC || !CommonDC->getInnermostDeclarationDeclContext() || |
| !isa<NominalTypeDecl>(CommonDC->getInnermostDeclarationDeclContext())) |
| return false; |
| |
| // Members of types not declared at top file level cannot be extracted |
| // to an extension at top file level |
| if (CommonDC->getParent()->getContextKind() != DeclContextKind::FileUnit) |
| return false; |
| |
| // We should not move instance variables with storage into the extension |
| // because they are not allowed to be declared there |
| for (auto DD : Info.DeclaredDecls) { |
| if (auto ASD = dyn_cast<AbstractStorageDecl>(DD.VD)) { |
| // Only disallow storages in the common decl context, allow them in |
| // any subtypes |
| if (ASD->hasStorage() && ASD->getDeclContext() == CommonDC) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| case RangeKind::SingleExpression: |
| case RangeKind::PartOfExpression: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| |
| bool RefactoringActionMoveMembersToExtension::performChange() { |
| DeclContext *CommonDC = getCommonDeclContext(RangeInfo.DeclaredDecls); |
| |
| auto CommonTypeDecl = |
| dyn_cast<NominalTypeDecl>(CommonDC->getInnermostDeclarationDeclContext()); |
| assert(CommonTypeDecl && "Not applicable if common parent is no nomial type"); |
| |
| SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| OS << "\n\n"; |
| OS << "extension " << CommonTypeDecl->getName() << " {\n"; |
| OS << RangeInfo.ContentRange.str().trim(); |
| OS << "\n}"; |
| |
| // Insert extension after the type declaration |
| EditConsumer.insertAfter(SM, CommonTypeDecl->getEndLoc(), Buffer); |
| EditConsumer.remove(SM, RangeInfo.ContentRange); |
| |
| return false; |
| } |
| |
| struct CollapsibleNestedIfInfo { |
| IfStmt *OuterIf; |
| IfStmt *InnerIf; |
| bool FinishedOuterIf; |
| bool FoundNonCollapsibleItem; |
| CollapsibleNestedIfInfo(): |
| OuterIf(nullptr), InnerIf(nullptr), |
| FinishedOuterIf(false), FoundNonCollapsibleItem(false) {} |
| bool isValid() { |
| return OuterIf && InnerIf && FinishedOuterIf && !FoundNonCollapsibleItem; |
| } |
| }; |
| |
| static CollapsibleNestedIfInfo findCollapseNestedIfTarget(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::StmtStart) |
| return CollapsibleNestedIfInfo(); |
| struct IfStmtFinder: public SourceEntityWalker { |
| SourceLoc StartLoc; |
| CollapsibleNestedIfInfo IfInfo; |
| IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {} |
| bool finishedInnerIfButNotFinishedOuterIf() { |
| return IfInfo.InnerIf && !IfInfo.FinishedOuterIf; |
| } |
| bool walkToStmtPre(Stmt *S) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| |
| bool StmtIsOuterIfBrace = |
| IfInfo.OuterIf && !IfInfo.InnerIf && S->getKind() == StmtKind::Brace; |
| if (StmtIsOuterIfBrace) { |
| return true; |
| } |
| |
| auto *IFS = dyn_cast<IfStmt>(S); |
| if (!IFS) { |
| return false; |
| } |
| if (!IfInfo.OuterIf) { |
| IfInfo.OuterIf = IFS; |
| return true; |
| } else { |
| IfInfo.InnerIf = IFS; |
| return false; |
| } |
| } |
| bool walkToStmtPost(Stmt *S) { |
| assert(S != IfInfo.InnerIf && "Should not traverse inner if statement"); |
| if (S == IfInfo.OuterIf) { |
| IfInfo.FinishedOuterIf = true; |
| } |
| return true; |
| } |
| bool walkToDeclPre(Decl *D, CharSourceRange Range) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| return true; |
| } |
| bool walkToExprPre(Expr *E) { |
| if (finishedInnerIfButNotFinishedOuterIf()) { |
| IfInfo.FoundNonCollapsibleItem = true; |
| return false; |
| } |
| return true; |
| } |
| |
| } Walker(CursorInfo.TrailingStmt->getStartLoc()); |
| Walker.walk(CursorInfo.TrailingStmt); |
| return Walker.IfInfo; |
| } |
| |
| bool RefactoringActionCollapseNestedIfExpr:: |
| isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { |
| return findCollapseNestedIfTarget(Tok).isValid(); |
| } |
| |
| bool RefactoringActionCollapseNestedIfExpr::performChange() { |
| auto Target = findCollapseNestedIfTarget(CursorInfo); |
| if (!Target.isValid()) |
| return true; |
| auto OuterIfConds = Target.OuterIf->getCond().vec(); |
| auto InnerIfConds = Target.InnerIf->getCond().vec(); |
| |
| EditorConsumerInsertStream OS(EditConsumer, SM, |
| Lexer::getCharSourceRangeFromSourceRange( |
| SM, Target.OuterIf->getSourceRange())); |
| |
| OS << tok::kw_if << " "; |
| for (auto CI = OuterIfConds.begin(); CI != OuterIfConds.end(); ++CI) { |
| OS << (CI != OuterIfConds.begin() ? ", " : ""); |
| OS << Lexer::getCharSourceRangeFromSourceRange( |
| SM, CI->getSourceRange()).str(); |
| } |
| for (auto CI = InnerIfConds.begin(); CI != InnerIfConds.end(); ++CI) { |
| OS << ", " << Lexer::getCharSourceRangeFromSourceRange( |
| SM, CI->getSourceRange()).str(); |
| } |
| auto ThenStatementText = Lexer::getCharSourceRangeFromSourceRange( |
| SM, Target.InnerIf->getThenStmt()->getSourceRange()).str(); |
| OS << " " << ThenStatementText; |
| return false; |
| } |
| |
| static std::unique_ptr<llvm::SetVector<Expr*>> |
| findConcatenatedExpressions(ResolvedRangeInfo Info, ASTContext &Ctx) { |
| Expr *E = nullptr; |
| |
| switch (Info.Kind) { |
| case RangeKind::SingleExpression: |
| // FIXME: the range info kind should imply non-empty list. |
| if (!Info.ContainedNodes.empty()) |
| E = Info.ContainedNodes[0].get<Expr*>(); |
| else |
| return nullptr; |
| break; |
| case RangeKind::PartOfExpression: |
| E = Info.CommonExprParent; |
| break; |
| default: |
| return nullptr; |
| } |
| |
| assert(E); |
| |
| struct StringInterpolationExprFinder: public SourceEntityWalker { |
| std::unique_ptr<llvm::SetVector<Expr*>> Bucket = llvm:: |
| make_unique<llvm::SetVector<Expr*>>(); |
| ASTContext &Ctx; |
| |
| bool IsValidInterpolation = true; |
| StringInterpolationExprFinder(ASTContext &Ctx): Ctx(Ctx) {} |
| |
| bool isConcatenationExpr(DeclRefExpr* Expr) { |
| if (!Expr) |
| return false; |
| auto *FD = dyn_cast<FuncDecl>(Expr->getDecl()); |
| if (FD == nullptr || (FD != Ctx.getPlusFunctionOnString() && |
| FD != Ctx.getPlusFunctionOnRangeReplaceableCollection())) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool walkToExprPre(Expr *E) { |
| if (E->isImplicit()) |
| return true; |
| // FIXME: we should have ErrorType instead of null. |
| if (E->getType().isNull()) |
| return true; |
| auto ExprType = E->getType()->getNominalOrBoundGenericNominal(); |
| //Only binary concatenation operators should exist in expression |
| if (E->getKind() == ExprKind::Binary) { |
| auto *BE = dyn_cast<BinaryExpr>(E); |
| auto *OperatorDeclRef = BE->getSemanticFn()->getMemberOperatorRef(); |
| if (!(isConcatenationExpr(OperatorDeclRef) |
| && ExprType == Ctx.getStringDecl())) { |
| IsValidInterpolation = false; |
| return false; |
| } |
| return true; |
| } |
| // Everything that evaluates to string should be gathered. |
| if (ExprType == Ctx.getStringDecl()) { |
| Bucket->insert(E); |
| return false; |
| } |
| if (auto *DR = dyn_cast<DeclRefExpr>(E)) { |
| // Checks whether all function references in expression are concatenations. |
| auto *FD = dyn_cast<FuncDecl>(DR->getDecl()); |
| auto IsConcatenation = isConcatenationExpr(DR); |
| if (FD && IsConcatenation) { |
| return false; |
| } |
| } |
| // There was non-expected expression, it's not valid interpolation then. |
| IsValidInterpolation = false; |
| return false; |
| } |
| } Walker(Ctx); |
| Walker.walk(E); |
| |
| // There should be two or more expressions to convert. |
| if (!Walker.IsValidInterpolation || Walker.Bucket->size() < 2) |
| return nullptr; |
| |
| return std::move(Walker.Bucket); |
| } |
| |
| static void interpolatedExpressionForm(Expr *E, SourceManager &SM, |
| llvm::raw_ostream &OS) { |
| if (auto *Literal = dyn_cast<StringLiteralExpr>(E)) { |
| OS << Literal->getValue(); |
| return; |
| } |
| auto ExpStr = Lexer::getCharSourceRangeFromSourceRange(SM, |
| E->getSourceRange()).str().str(); |
| if (isa<InterpolatedStringLiteralExpr>(E)) { |
| ExpStr.erase(0, 1); |
| ExpStr.pop_back(); |
| OS << ExpStr; |
| return; |
| } |
| OS << "\\(" << ExpStr << ")"; |
| } |
| |
| bool RefactoringActionConvertStringsConcatenationToInterpolation:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| auto RangeContext = Info.RangeContext; |
| if (RangeContext) { |
| auto &Ctx = Info.RangeContext->getASTContext(); |
| return findConcatenatedExpressions(Info, Ctx) != nullptr; |
| } |
| return false; |
| } |
| |
| bool RefactoringActionConvertStringsConcatenationToInterpolation::performChange() { |
| auto Expressions = findConcatenatedExpressions(RangeInfo, Ctx); |
| if (!Expressions) |
| return true; |
| EditorConsumerInsertStream OS(EditConsumer, SM, RangeInfo.ContentRange); |
| OS << "\""; |
| for (auto It = Expressions->begin(); It != Expressions->end(); It++) { |
| interpolatedExpressionForm(*It, SM, OS); |
| } |
| OS << "\""; |
| return false; |
| } |
| |
| /// Abstract helper class containing info about an IfExpr |
| /// that can be expanded into an IfStmt. |
| class ExpandableTernaryExprInfo { |
| |
| public: |
| virtual ~ExpandableTernaryExprInfo() {} |
| |
| virtual IfExpr *getIf() = 0; |
| |
| virtual SourceRange getNameRange() = 0; |
| |
| virtual Type getType() = 0; |
| |
| virtual bool shouldDeclareNameAndType() { |
| return !getType().isNull(); |
| } |
| |
| virtual bool isValid() { |
| |
| //Ensure all public properties are non-nil and valid |
| if (!getIf() || !getNameRange().isValid()) |
| return false; |
| if (shouldDeclareNameAndType() && getType().isNull()) |
| return false; |
| |
| return true; //valid |
| } |
| |
| CharSourceRange getNameCharRange(const SourceManager &SM) { |
| return Lexer::getCharSourceRangeFromSourceRange(SM, getNameRange()); |
| } |
| }; |
| |
| /// Concrete subclass containing info about an AssignExpr |
| /// where the source is the expandable IfExpr. |
| class ExpandableAssignTernaryExprInfo: public ExpandableTernaryExprInfo { |
| |
| public: |
| ExpandableAssignTernaryExprInfo(AssignExpr *Assign): Assign(Assign) {} |
| |
| IfExpr *getIf() { |
| if (!Assign) |
| return nullptr; |
| |
| return dyn_cast<IfExpr>(Assign->getSrc()); |
| } |
| |
| SourceRange getNameRange() { |
| auto Invalid = SourceRange(); |
| |
| if (!Assign) |
| return Invalid; |
| |
| if (auto dest = Assign->getDest()) |
| return dest->getSourceRange(); |
| |
| return Invalid; |
| } |
| |
| Type getType() { |
| return nullptr; |
| } |
| |
| private: |
| AssignExpr *Assign = nullptr; |
| }; |
| |
| /// Concrete subclass containing info about a PatternBindingDecl |
| /// where the pattern initializer is the expandable IfExpr. |
| class ExpandableBindingTernaryExprInfo: public ExpandableTernaryExprInfo { |
| |
| public: |
| ExpandableBindingTernaryExprInfo(PatternBindingDecl *Binding): |
| Binding(Binding) {} |
| |
| IfExpr *getIf() { |
| if (Binding && Binding->getNumPatternEntries() == 1) { |
| if (auto *Init = Binding->getInit(0)) { |
| return dyn_cast<IfExpr>(Init); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| SourceRange getNameRange() { |
| if (auto Pattern = getNamePattern()) |
| return Pattern->getSourceRange(); |
| |
| return SourceRange(); |
| } |
| |
| Type getType() { |
| if (auto Pattern = getNamePattern()) |
| return Pattern->getType(); |
| |
| return nullptr; |
| } |
| |
| private: |
| Pattern *getNamePattern() { |
| if (!Binding || Binding->getNumPatternEntries() != 1) |
| return nullptr; |
| |
| auto Pattern = Binding->getPattern(0); |
| |
| if (!Pattern) |
| return nullptr; |
| |
| if (auto TyPattern = dyn_cast<TypedPattern>(Pattern)) |
| Pattern = TyPattern->getSubPattern(); |
| |
| return Pattern; |
| } |
| |
| PatternBindingDecl *Binding = nullptr; |
| }; |
| |
| std::unique_ptr<ExpandableTernaryExprInfo> |
| findExpandableTernaryExpression(ResolvedRangeInfo Info) { |
| |
| if (Info.Kind != RangeKind::SingleDecl |
| && Info.Kind != RangeKind:: SingleExpression) |
| return nullptr; |
| |
| if (Info.ContainedNodes.size() != 1) |
| return nullptr; |
| |
| if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>()) |
| if (auto Binding = dyn_cast<PatternBindingDecl>(D)) |
| return llvm::make_unique<ExpandableBindingTernaryExprInfo>(Binding); |
| |
| if (auto E = Info.ContainedNodes[0].dyn_cast<Expr*>()) |
| if (auto Assign = dyn_cast<AssignExpr>(E)) |
| return llvm::make_unique<ExpandableAssignTernaryExprInfo>(Assign); |
| |
| return nullptr; |
| } |
| |
| bool RefactoringActionExpandTernaryExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| auto Target = findExpandableTernaryExpression(Info); |
| return Target && Target->isValid(); |
| } |
| |
| bool RefactoringActionExpandTernaryExpr::performChange() { |
| auto Target = findExpandableTernaryExpression(RangeInfo); |
| |
| if (!Target || !Target->isValid()) |
| return true; //abort |
| |
| auto NameCharRange = Target->getNameCharRange(SM); |
| |
| auto IfRange = Target->getIf()->getSourceRange(); |
| auto IfCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange); |
| |
| auto CondRange = Target->getIf()->getCondExpr()->getSourceRange(); |
| auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange); |
| |
| auto ThenRange = Target->getIf()->getThenExpr()->getSourceRange(); |
| auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange); |
| |
| auto ElseRange = Target->getIf()->getElseExpr()->getSourceRange(); |
| auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange); |
| |
| llvm::SmallString<64> DeclBuffer; |
| llvm::raw_svector_ostream OS(DeclBuffer); |
| |
| llvm::StringRef Space = " "; |
| llvm::StringRef NewLine = "\n"; |
| |
| if (Target->shouldDeclareNameAndType()) { |
| //Specifier will not be replaced; append after specifier |
| OS << NameCharRange.str() << tok::colon << Space; |
| OS << Target->getType() << NewLine; |
| } |
| |
| OS << tok::kw_if << Space; |
| OS << CondCharRange.str() << Space; |
| OS << tok::l_brace << NewLine; |
| |
| OS << NameCharRange.str() << Space; |
| OS << tok::equal << Space; |
| OS << ThenCharRange.str() << NewLine; |
| |
| OS << tok::r_brace << Space; |
| OS << tok::kw_else << Space; |
| OS << tok::l_brace << NewLine; |
| |
| OS << NameCharRange.str() << Space; |
| OS << tok::equal << Space; |
| OS << ElseCharRange.str() << NewLine; |
| |
| OS << tok::r_brace; |
| |
| //Start replacement with name range, skip the specifier |
| auto ReplaceRange(NameCharRange); |
| ReplaceRange.widen(IfCharRange); |
| |
| EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str()); |
| |
| return false; //don't abort |
| } |
| |
| /// Struct containing info about an IfStmt that can be converted into an IfExpr. |
| struct ConvertToTernaryExprInfo { |
| ConvertToTernaryExprInfo() {} |
| |
| Expr *AssignDest() { |
| |
| if (!Then || !Then->getDest() || !Else || !Else->getDest()) |
| return nullptr; |
| |
| auto ThenDest = Then->getDest(); |
| auto ElseDest = Else->getDest(); |
| |
| if (ThenDest->getKind() != ElseDest->getKind()) |
| return nullptr; |
| |
| switch (ThenDest->getKind()) { |
| case ExprKind::DeclRef: { |
| auto ThenRef = dyn_cast<DeclRefExpr>(Then->getDest()); |
| auto ElseRef = dyn_cast<DeclRefExpr>(Else->getDest()); |
| |
| if (!ThenRef || !ThenRef->getDecl() || !ElseRef || !ElseRef->getDecl()) |
| return nullptr; |
| |
| auto ThenName = ThenRef->getDecl()->getFullName(); |
| auto ElseName = ElseRef->getDecl()->getFullName(); |
| |
| if (ThenName.compare(ElseName) != 0) |
| return nullptr; |
| |
| return Then->getDest(); |
| } |
| case ExprKind::Tuple: { |
| auto ThenTuple = dyn_cast<TupleExpr>(Then->getDest()); |
| auto ElseTuple = dyn_cast<TupleExpr>(Else->getDest()); |
| |
| if (!ThenTuple || !ElseTuple) |
| return nullptr; |
| |
| auto ThenNames = ThenTuple->getElementNames(); |
| auto ElseNames = ElseTuple->getElementNames(); |
| |
| if (!ThenNames.equals(ElseNames)) |
| return nullptr; |
| |
| return ThenTuple; |
| } |
| default: |
| return nullptr; |
| } |
| } |
| |
| Expr *ThenSrc() { |
| if (!Then) |
| return nullptr; |
| return Then->getSrc(); |
| } |
| |
| Expr *ElseSrc() { |
| if (!Else) |
| return nullptr; |
| return Else->getSrc(); |
| } |
| |
| bool isValid() { |
| if (!Cond || !AssignDest() || !ThenSrc() || !ElseSrc() |
| || !IfRange.isValid()) |
| return false; |
| |
| return true; |
| } |
| |
| PatternBindingDecl *Binding = nullptr; //optional |
| |
| Expr *Cond = nullptr; //required |
| AssignExpr *Then = nullptr; //required |
| AssignExpr *Else = nullptr; //required |
| SourceRange IfRange; |
| }; |
| |
| ConvertToTernaryExprInfo |
| findConvertToTernaryExpression(ResolvedRangeInfo Info) { |
| |
| auto notFound = ConvertToTernaryExprInfo(); |
| |
| if (Info.Kind != RangeKind::SingleStatement |
| && Info.Kind != RangeKind::MultiStatement) |
| return notFound; |
| |
| if (Info.ContainedNodes.empty()) |
| return notFound; |
| |
| struct AssignExprFinder: public SourceEntityWalker { |
| |
| AssignExpr *Assign = nullptr; |
| |
| AssignExprFinder(Stmt* S) { |
| if (S) |
| walk(S); |
| } |
| |
| virtual bool walkToExprPre(Expr *E) { |
| Assign = dyn_cast<AssignExpr>(E); |
| return false; |
| } |
| }; |
| |
| ConvertToTernaryExprInfo Target; |
| |
| IfStmt *If = nullptr; |
| |
| if (Info.ContainedNodes.size() == 1) { |
| if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>()) |
| If = dyn_cast<IfStmt>(S); |
| } |
| |
| if (Info.ContainedNodes.size() == 2) { |
| if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>()) |
| Target.Binding = dyn_cast<PatternBindingDecl>(D); |
| if (auto S = Info.ContainedNodes[1].dyn_cast<Stmt*>()) |
| If = dyn_cast<IfStmt>(S); |
| } |
| |
| if (!If) |
| return notFound; |
| |
| auto CondList = If->getCond(); |
| |
| if (CondList.size() != 1) |
| return notFound; |
| |
| Target.Cond = CondList[0].getBooleanOrNull(); |
| Target.IfRange = If->getSourceRange(); |
| |
| Target.Then = AssignExprFinder(If->getThenStmt()).Assign; |
| Target.Else = AssignExprFinder(If->getElseStmt()).Assign; |
| |
| return Target; |
| } |
| |
| bool RefactoringActionConvertToTernaryExpr:: |
| isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) { |
| return findConvertToTernaryExpression(Info).isValid(); |
| } |
| |
| bool RefactoringActionConvertToTernaryExpr::performChange() { |
| auto Target = findConvertToTernaryExpression(RangeInfo); |
| |
| if (!Target.isValid()) |
| return true; //abort |
| |
| llvm::SmallString<64> DeclBuffer; |
| llvm::raw_svector_ostream OS(DeclBuffer); |
| |
| llvm::StringRef Space = " "; |
| |
| auto IfRange = Target.IfRange; |
| auto ReplaceRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange); |
| |
| auto CondRange = Target.Cond->getSourceRange(); |
| auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange); |
| |
| auto ThenRange = Target.ThenSrc()->getSourceRange(); |
| auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange); |
| |
| auto ElseRange = Target.ElseSrc()->getSourceRange(); |
| auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange); |
| |
| CharSourceRange DestCharRange; |
| |
| if (Target.Binding) { |
| auto DestRange = Target.Binding->getSourceRange(); |
| DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange); |
| ReplaceRange.widen(DestCharRange); |
| } else { |
| auto DestRange = Target.AssignDest()->getSourceRange(); |
| DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange); |
| } |
| |
| OS << DestCharRange.str() << Space << tok::equal << Space; |
| OS << CondCharRange.str() << Space << tok::question_postfix << Space; |
| OS << ThenCharRange.str() << Space << tok::colon << Space; |
| OS << ElseCharRange.str(); |
| |
| EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str()); |
| |
| return false; //don't abort |
| } |
| |
| /// The helper class analyzes a given nominal decl or an extension decl to |
| /// decide whether stubs are required to filled in and the context in which |
| /// these stubs should be filled. |
| class FillProtocolStubContext { |
| |
| std::vector<ValueDecl*> getUnsatisfiedRequirements(const DeclContext *DC); |
| |
| /// Context in which the content should be filled; this could be either a |
| /// nominal type declaraion or an extension declaration. |
| DeclContext *DC; |
| |
| /// The type that adopts the required protocol stubs. For nominal type decl, this |
| /// should be the declared type itself; for extension decl, this should be the |
| /// extended type at hand. |
| Type Adopter; |
| |
| /// The start location of the decl, either nominal type or extension, for the |
| /// printer to figure out the right indentation. |
| SourceLoc StartLoc; |
| |
| /// The location of '{' for the decl, thus we know where to insert the filling |
| /// stubs. |
| SourceLoc BraceStartLoc; |
| |
| /// The value decls that should be satisfied; this could be either function |
| /// decls, property decls, or required type alias. |
| std::vector<ValueDecl*> FillingContents; |
| |
| public: |
| FillProtocolStubContext(ExtensionDecl *ED) : DC(ED), |
| Adopter(ED->getExtendedType()), StartLoc(ED->getStartLoc()), |
| BraceStartLoc(ED->getBraces().Start), |
| FillingContents(getUnsatisfiedRequirements(ED)) {}; |
| |
| FillProtocolStubContext(NominalTypeDecl *ND) : DC(ND), |
| Adopter(ND->getDeclaredType()), StartLoc(ND->getStartLoc()), |
| BraceStartLoc(ND->getBraces().Start), |
| FillingContents(getUnsatisfiedRequirements(ND)) {}; |
| |
| FillProtocolStubContext() : DC(nullptr), Adopter(), FillingContents({}) {}; |
| |
| static FillProtocolStubContext getContextFromCursorInfo(ResolvedCursorInfo Tok); |
| |
| ArrayRef<ValueDecl*> getFillingContents() const { |
| return llvm::makeArrayRef(FillingContents); |
| } |
| |
| DeclContext *getFillingContext() const { return DC; } |
| |
| bool canProceed() const { |
| return StartLoc.isValid() && BraceStartLoc.isValid() && |
| !getFillingContents().empty(); |
| } |
| |
| Type getAdopter() const { return Adopter; } |
| SourceLoc getContextStartLoc() const { return StartLoc; } |
| SourceLoc getBraceStartLoc() const { return BraceStartLoc; } |
| }; |
| |
| FillProtocolStubContext FillProtocolStubContext:: |
| getContextFromCursorInfo(ResolvedCursorInfo CursorInfo) { |
| if(!CursorInfo.isValid()) |
| return FillProtocolStubContext(); |
| if (!CursorInfo.IsRef) { |
| // If the type name is on the declared nominal, e.g. "class A {}" |
| if (auto ND = dyn_cast<NominalTypeDecl>(CursorInfo.ValueD)) { |
| return FillProtocolStubContext(ND); |
| } |
| } else if (auto *ED = CursorInfo.ExtTyRef) { |
| // If the type ref is on a declared extension, e.g. "extension A {}" |
| return FillProtocolStubContext(ED); |
| } |
| return FillProtocolStubContext(); |
| } |
| |
| std::vector<ValueDecl*> FillProtocolStubContext:: |
| getUnsatisfiedRequirements(const DeclContext *DC) { |
| // The results to return. |
| std::vector<ValueDecl*> NonWitnessedReqs; |
| |
| // For each conformance of the extended nominal. |
| for(ProtocolConformance *Con : DC->getLocalConformances()) { |
| |
| // Collect non-witnessed requirements. |
| Con->forEachNonWitnessedRequirement(DC->getASTContext().getLazyResolver(), |
| [&](ValueDecl *VD) { NonWitnessedReqs.push_back(VD); }); |
| } |
| |
| return NonWitnessedReqs; |
| } |
| |
| bool RefactoringActionFillProtocolStub:: |
| isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { |
| return FillProtocolStubContext::getContextFromCursorInfo(Tok).canProceed(); |
| }; |
| |
| bool RefactoringActionFillProtocolStub::performChange() { |
| // Get the filling protocol context from the input token. |
| FillProtocolStubContext Context = FillProtocolStubContext:: |
| getContextFromCursorInfo(CursorInfo); |
| |
| assert(Context.canProceed()); |
| assert(!Context.getFillingContents().empty()); |
| assert(Context.getFillingContext()); |
| llvm::SmallString<128> Text; |
| { |
| llvm::raw_svector_ostream SS(Text); |
| Type Adopter = Context.getAdopter(); |
| SourceLoc Loc = Context.getContextStartLoc(); |
| auto Contents = Context.getFillingContents(); |
| |
| // For each unsatisfied requirement, print the stub to the buffer. |
| std::for_each(Contents.begin(), Contents.end(), [&](ValueDecl *VD) { |
| printRequirementStub(VD, Context.getFillingContext(), Adopter, Loc, SS); |
| }); |
| } |
| |
| // Insert all stubs after '{' in the extension/nominal type decl. |
| EditConsumer.insertAfter(SM, Context.getBraceStartLoc(), Text); |
| return false; |
| } |
| |
| ArrayRef<RefactoringKind> |
| collectAvailableRefactoringsAtCursor(SourceFile *SF, unsigned Line, |
| unsigned Column, |
| std::vector<RefactoringKind> &Scratch, |
| llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) { |
| // Prepare the tool box. |
| ASTContext &Ctx = SF->getASTContext(); |
| SourceManager &SM = Ctx.SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| std::for_each(DiagConsumers.begin(), DiagConsumers.end(), |
| [&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); }); |
| CursorInfoResolver Resolver(*SF); |
| SourceLoc Loc = SM.getLocForLineCol(SF->getBufferID().getValue(), Line, Column); |
| if (Loc.isInvalid()) |
| return {}; |
| ResolvedCursorInfo Tok = Resolver.resolve(Lexer::getLocForStartOfToken(SM, Loc)); |
| return collectAvailableRefactorings(SF, Tok, Scratch, /*Exclude rename*/false); |
| } |
| |
| static EnumDecl* getEnumDeclFromSwitchStmt(SwitchStmt *SwitchS) { |
| if (auto SubjectTy = SwitchS->getSubjectExpr()->getType()) { |
| return SubjectTy->getAnyNominal()->getAsEnumOrEnumExtensionContext(); |
| } |
| return nullptr; |
| } |
| |
| static bool performCasesExpansionInSwitchStmt(SwitchStmt *SwitchS, |
| DiagnosticEngine &DiagEngine, |
| SourceLoc ExpandedStmtLoc, |
| EditorConsumerInsertStream &OS |
| ) { |
| // Assume enum elements are not handled in the switch statement. |
| auto EnumDecl = getEnumDeclFromSwitchStmt(SwitchS); |
| assert(EnumDecl); |
| llvm::DenseSet<EnumElementDecl*> UnhandledElements; |
| EnumDecl->getAllElements(UnhandledElements); |
| for (auto Current : SwitchS->getCases()) { |
| if (Current->isDefault()) { |
| continue; |
| } |
| // For each handled enum element, remove it from the bucket. |
| for (auto Item : Current->getCaseLabelItems()) { |
| if (auto *EEP = dyn_cast_or_null<EnumElementPattern>(Item.getPattern())) { |
| UnhandledElements.erase(EEP->getElementDecl()); |
| } |
| } |
| } |
| |
| // If all enum elements are handled in the switch statement, issue error. |
| if (UnhandledElements.empty()) { |
| DiagEngine.diagnose(ExpandedStmtLoc, diag::no_remaining_cases); |
| return true; |
| } |
| |
| printEnumElementsAsCases(UnhandledElements, OS); |
| return false; |
| } |
| |
| // Finds SwitchStmt that contains given CaseStmt. |
| static SwitchStmt* findEnclosingSwitchStmt(CaseStmt *CS, |
| SourceFile *SF, |
| DiagnosticEngine &DiagEngine) { |
| auto IsSwitch = [](ASTNode Node) { |
| return Node.is<Stmt*>() && |
| Node.get<Stmt*>()->getKind() == StmtKind::Switch; |
| }; |
| ContextFinder Finder(*SF, CS, IsSwitch); |
| Finder.resolve(); |
| |
| // If failed to find the switch statement, issue error. |
| if (Finder.getContexts().empty()) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch); |
| return nullptr; |
| } |
| auto *SwitchS = static_cast<SwitchStmt*>(Finder.getContexts().back(). |
| get<Stmt*>()); |
| // Make sure that CaseStmt is included in switch that was found. |
| auto Cases = SwitchS->getCases(); |
| auto Default = std::find(Cases.begin(), Cases.end(), CS); |
| if (Default == Cases.end()) { |
| DiagEngine.diagnose(CS->getStartLoc(), diag::no_parent_switch); |
| return nullptr; |
| } |
| return SwitchS; |
| } |
| |
| bool RefactoringActionExpandDefault:: |
| isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) { |
| auto Exit = [&](bool Applicable) { |
| if (!Applicable) |
| Diag.diagnose(SourceLoc(), diag::invalid_default_location); |
| return Applicable; |
| }; |
| if (CursorInfo.Kind != CursorInfoKind::StmtStart) |
| return Exit(false); |
| if (auto *CS = dyn_cast<CaseStmt>(CursorInfo.TrailingStmt)) { |
| auto EnclosingSwitchStmt = findEnclosingSwitchStmt(CS, |
| CursorInfo.SF, |
| Diag); |
| if (!EnclosingSwitchStmt) |
| return false; |
| auto EnumD = getEnumDeclFromSwitchStmt(EnclosingSwitchStmt); |
| auto IsApplicable = CS->isDefault() && EnumD != nullptr; |
| return IsApplicable; |
| } |
| return Exit(false); |
| } |
| |
| bool RefactoringActionExpandDefault::performChange() { |
| // If we've not seen the default statement inside the switch statement, issue |
| // error. |
| auto *CS = static_cast<CaseStmt*>(CursorInfo.TrailingStmt); |
| auto *SwitchS = findEnclosingSwitchStmt(CS, TheFile, DiagEngine); |
| assert(SwitchS); |
| EditorConsumerInsertStream OS(EditConsumer, SM, |
| Lexer::getCharSourceRangeFromSourceRange(SM, |
| CS->getLabelItemsRange())); |
| return performCasesExpansionInSwitchStmt(SwitchS, |
| DiagEngine, |
| CS->getStartLoc(), |
| OS); |
| } |
| |
| bool RefactoringActionExpandSwitchCases:: |
| isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &DiagEngine) { |
| if (!CursorInfo.TrailingStmt) |
| return false; |
| if (auto *Switch = dyn_cast<SwitchStmt>(CursorInfo.TrailingStmt)) { |
| return getEnumDeclFromSwitchStmt(Switch); |
| } |
| return false; |
| } |
| |
| bool RefactoringActionExpandSwitchCases::performChange() { |
| auto *SwitchS = dyn_cast<SwitchStmt>(CursorInfo.TrailingStmt); |
| assert(SwitchS); |
| |
| auto InsertRange = CharSourceRange(); |
| auto Cases = SwitchS->getCases(); |
| auto Default = std::find_if(Cases.begin(), Cases.end(), [](CaseStmt *Stmt) { |
| return Stmt->isDefault(); |
| }); |
| if (Default != Cases.end()) { |
| auto DefaultRange = (*Default)->getLabelItemsRange(); |
| InsertRange = Lexer::getCharSourceRangeFromSourceRange(SM, DefaultRange); |
| } else { |
| auto RBraceLoc = SwitchS->getRBraceLoc(); |
| InsertRange = CharSourceRange(SM, RBraceLoc, RBraceLoc); |
| } |
| EditorConsumerInsertStream OS(EditConsumer, SM, InsertRange); |
| if (SM.getLineNumber(SwitchS->getLBraceLoc()) == |
| SM.getLineNumber(SwitchS->getRBraceLoc())) { |
| OS << "\n"; |
| } |
| auto Result = performCasesExpansionInSwitchStmt(SwitchS, |
| DiagEngine, |
| SwitchS->getStartLoc(), |
| OS); |
| return Result; |
| } |
| |
| static Expr *findLocalizeTarget(ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind != CursorInfoKind::ExprStart) |
| return nullptr; |
| struct StringLiteralFinder: public SourceEntityWalker { |
| SourceLoc StartLoc; |
| Expr *Target; |
| StringLiteralFinder(SourceLoc StartLoc): StartLoc(StartLoc), Target(nullptr) {} |
| bool walkToExprPre(Expr *E) { |
| if (E->getStartLoc() != StartLoc) |
| return false; |
| if (E->getKind() == ExprKind::InterpolatedStringLiteral) |
| return false; |
| if (E->getKind() == ExprKind::StringLiteral) { |
| Target = E; |
| return false; |
| } |
| return true; |
| } |
| } Walker(CursorInfo.TrailingExpr->getStartLoc()); |
| Walker.walk(CursorInfo.TrailingExpr); |
| return Walker.Target; |
| } |
| |
| bool RefactoringActionLocalizeString:: |
| isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { |
| return findLocalizeTarget(Tok); |
| } |
| |
| bool RefactoringActionLocalizeString::performChange() { |
| Expr* Target = findLocalizeTarget(CursorInfo); |
| if (!Target) |
| return true; |
| EditConsumer.accept(SM, Target->getStartLoc(), "NSLocalizedString("); |
| EditConsumer.insertAfter(SM, Target->getEndLoc(), ", comment: \"\")"); |
| return false; |
| } |
| |
| static CharSourceRange |
| findSourceRangeToWrapInCatch(ResolvedCursorInfo CursorInfo, |
| SourceFile *TheFile, |
| SourceManager &SM) { |
| Expr *E = CursorInfo.TrailingExpr; |
| if (!E) |
| return CharSourceRange(); |
| auto Node = ASTNode(E); |
| auto NodeChecker = [](ASTNode N) { return N.isStmt(StmtKind::Brace); }; |
| ContextFinder Finder(*TheFile, Node, NodeChecker); |
| Finder.resolve(); |
| auto Contexts = Finder.getContexts(); |
| if (Contexts.empty()) |
| return CharSourceRange(); |
| auto TargetNode = Contexts.back(); |
| BraceStmt *BStmt = dyn_cast<BraceStmt>(TargetNode.dyn_cast<Stmt*>()); |
| auto ConvertToCharRange = [&SM](SourceRange SR) { |
| return Lexer::getCharSourceRangeFromSourceRange(SM, SR); |
| }; |
| assert(BStmt); |
| auto ExprRange = ConvertToCharRange(E->getSourceRange()); |
| // Check elements of the deepest BraceStmt, pick one that covers expression. |
| for (auto Elem: BStmt->getElements()) { |
| auto ElemRange = ConvertToCharRange(Elem.getSourceRange()); |
| if (ElemRange.contains(ExprRange)) |
| TargetNode = Elem; |
| } |
| return ConvertToCharRange(TargetNode.getSourceRange()); |
| } |
| |
| bool RefactoringActionConvertToDoCatch:: |
| isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { |
| if (!Tok.TrailingExpr) |
| return false; |
| return isa<ForceTryExpr>(Tok.TrailingExpr); |
| } |
| |
| bool RefactoringActionConvertToDoCatch::performChange() { |
| auto *TryExpr = dyn_cast<ForceTryExpr>(CursorInfo.TrailingExpr); |
| assert(TryExpr); |
| auto Range = findSourceRangeToWrapInCatch(CursorInfo, TheFile, SM); |
| if (!Range.isValid()) |
| return true; |
| // Wrap given range in do catch block. |
| EditConsumer.accept(SM, Range.getStart(), "do {\n"); |
| EditorConsumerInsertStream OS(EditConsumer, SM, Range.getEnd()); |
| OS << "\n} catch {\n" << getCodePlaceholder() << "\n}"; |
| |
| // Delete ! from try! expression |
| auto ExclaimLen = getKeywordLen(tok::exclaim_postfix); |
| auto ExclaimRange = CharSourceRange(TryExpr->getExclaimLoc(), ExclaimLen); |
| EditConsumer.remove(SM, ExclaimRange); |
| return false; |
| } |
| |
| /// Given a cursor position, this function tries to collect a number literal |
| /// expression immediately following the cursor. |
| static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) { |
| // This cursor must point to the start of an expression. |
| if (Tok.Kind != CursorInfoKind::ExprStart) |
| return nullptr; |
| Expr *Parent = Tok.TrailingExpr; |
| assert(Parent); |
| |
| // Check if an expression is a number literal. |
| auto IsLiteralNumber = [&](Expr *E) -> NumberLiteralExpr* { |
| if (auto *NL = dyn_cast<NumberLiteralExpr>(E)) { |
| |
| // The sub-expression must have the same start loc with the outermost |
| // expression, i.e. the cursor position. |
| if (Parent->getStartLoc().getOpaquePointerValue() == |
| E->getStartLoc().getOpaquePointerValue()) { |
| return NL; |
| } |
| } |
| return nullptr; |
| }; |
| // For every sub-expression, try to find the literal expression that matches |
| // our criteria. |
| for (auto Pair: Parent->getDepthMap()) { |
| if (auto Result = IsLiteralNumber(Pair.getFirst())) { |
| return Result; |
| } |
| } |
| return nullptr; |
| } |
| |
| static std::string insertUnderscore(StringRef Text) { |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| for (auto It = Text.begin(); It != Text.end(); It++) { |
| unsigned Distance = It - Text.begin(); |
| if (Distance && !(Distance % 3)) { |
| OS << '_'; |
| } |
| OS << *It; |
| } |
| return OS.str().str(); |
| } |
| |
| static void insertUnderscoreInDigits(StringRef Digits, |
| llvm::raw_ostream &OS) { |
| std::string BeforePoint, AfterPoint; |
| std::tie(BeforePoint, AfterPoint) = Digits.split('.'); |
| |
| // Insert '_' for the part before the decimal point. |
| std::reverse(BeforePoint.begin(), BeforePoint.end()); |
| BeforePoint = insertUnderscore(BeforePoint); |
| std::reverse(BeforePoint.begin(), BeforePoint.end()); |
| OS << BeforePoint; |
| |
| // Insert '_' for the part after the decimal point, if necessary. |
| if (!AfterPoint.empty()) { |
| OS << '.'; |
| OS << insertUnderscore(AfterPoint); |
| } |
| } |
| |
| bool RefactoringActionSimplifyNumberLiteral:: |
| isApplicable(ResolvedCursorInfo Tok, DiagnosticEngine &Diag) { |
| if (auto *Literal = getTrailingNumberLiteral(Tok)) { |
| llvm::SmallString<64> Buffer; |
| llvm::raw_svector_ostream OS(Buffer); |
| StringRef Digits = Literal->getDigitsText(); |
| insertUnderscoreInDigits(Digits, OS); |
| |
| // If inserting '_' results in a different digit sequence, this refactoring |
| // is applicable. |
| return OS.str() != Digits; |
| } |
| return false; |
| } |
| |
| bool RefactoringActionSimplifyNumberLiteral::performChange() { |
| if (auto *Literal = getTrailingNumberLiteral(CursorInfo)) { |
| |
| EditorConsumerInsertStream OS(EditConsumer, SM, |
| CharSourceRange(SM, Literal->getDigitsLoc(), |
| Lexer::getLocForEndOfToken(SM, |
| Literal->getEndLoc()))); |
| StringRef Digits = Literal->getDigitsText(); |
| insertUnderscoreInDigits(Digits, OS); |
| return false; |
| } |
| return true; |
| } |
| |
| static CallExpr *findTrailingClosureTarget(SourceManager &SM, |
| ResolvedCursorInfo CursorInfo) { |
| if (CursorInfo.Kind == CursorInfoKind::StmtStart) |
| // StmtStart postion can't be a part of CallExpr. |
| return nullptr; |
| |
| // Find inner most CallExpr |
| ContextFinder |
| Finder(*CursorInfo.SF, CursorInfo.Loc, |
| [](ASTNode N) { |
| return N.isStmt(StmtKind::Brace) || N.isExpr(ExprKind::Call); |
| }); |
| Finder.resolve(); |
| if (Finder.getContexts().empty() |
| || !Finder.getContexts().back().is<Expr*>()) |
| return nullptr; |
| CallExpr *CE = cast<CallExpr>(Finder.getContexts().back().get<Expr*>()); |
| |
| // The last arugment is a closure? |
| Expr *Args = CE->getArg(); |
| if (!Args) |
| return nullptr; |
| Expr *LastArg; |
| if (auto *TSE = dyn_cast<TupleShuffleExpr>(Args)) |
| Args = TSE->getSubExpr(); |
| if (auto *PE = dyn_cast<ParenExpr>(Args)) { |
| LastArg = PE->getSubExpr(); |
| } else { |
| auto *TE = cast<TupleExpr>(Args); |
| if (TE->getNumElements() == 0) |
| return nullptr; |
| LastArg = TE->getElements().back(); |
| } |
| |
| if (auto *ICE = dyn_cast<ImplicitConversionExpr>(LastArg)) |
| LastArg = ICE->getSyntacticSubExpr(); |
| |
| if (isa<ClosureExpr>(LastArg) || isa<CaptureListExpr>(LastArg)) |
| return CE; |
| return nullptr; |
| } |
| |
| bool RefactoringActionTrailingClosure:: |
| isApplicable(ResolvedCursorInfo CursorInfo, DiagnosticEngine &Diag) { |
| SourceManager &SM = CursorInfo.SF->getASTContext().SourceMgr; |
| return findTrailingClosureTarget(SM, CursorInfo); |
| } |
| |
| bool RefactoringActionTrailingClosure::performChange() { |
| auto *CE = findTrailingClosureTarget(SM, CursorInfo); |
| if (!CE) |
| return true; |
| Expr *Args = CE->getArg(); |
| if (auto *TSE = dyn_cast<TupleShuffleExpr>(Args)) |
| Args = TSE; |
| |
| Expr *ClosureArg = nullptr; |
| Expr *PrevArg = nullptr; |
| SourceLoc LPLoc, RPLoc; |
| |
| if (auto *PE = dyn_cast<ParenExpr>(Args)) { |
| ClosureArg = PE->getSubExpr(); |
| LPLoc = PE->getLParenLoc(); |
| RPLoc = PE->getRParenLoc(); |
| } else { |
| auto *TE = cast<TupleExpr>(Args); |
| auto NumArgs = TE->getNumElements(); |
| if (NumArgs == 0) |
| return true; |
| LPLoc = TE->getLParenLoc(); |
| RPLoc = TE->getRParenLoc(); |
| ClosureArg = TE->getElement(NumArgs - 1); |
| if (NumArgs > 1) |
| PrevArg = TE->getElement(NumArgs - 2); |
| } |
| if (auto *ICE = dyn_cast<ImplicitConversionExpr>(ClosureArg)) |
| ClosureArg = ICE->getSyntacticSubExpr(); |
| |
| if (LPLoc.isInvalid() || RPLoc.isInvalid()) |
| return true; |
| |
| // Replace: |
| // * Open paren with ' ' if the closure is sole argument. |
| // * Comma with ') ' otherwise. |
| if (PrevArg) { |
| CharSourceRange PreRange( |
| SM, |
| Lexer::getLocForEndOfToken(SM, PrevArg->getEndLoc()), |
| ClosureArg->getStartLoc()); |
| EditConsumer.accept(SM, PreRange, ") "); |
| } else { |
| CharSourceRange PreRange( |
| SM, LPLoc, ClosureArg->getStartLoc()); |
| EditConsumer.accept(SM, PreRange, " "); |
| } |
| // Remove original closing paren. |
| CharSourceRange PostRange( |
| SM, |
| Lexer::getLocForEndOfToken(SM, ClosureArg->getEndLoc()), |
| Lexer::getLocForEndOfToken(SM, RPLoc)); |
| EditConsumer.remove(SM, PostRange); |
| return false; |
| } |
| |
| static bool rangeStartMayNeedRename(ResolvedRangeInfo Info) { |
| switch(Info.Kind) { |
| case RangeKind::SingleExpression: { |
| Expr *E = Info.ContainedNodes[0].get<Expr*>(); |
| // We should show rename for the selection of "foo()" |
| if (auto *CE = dyn_cast<CallExpr>(E)) { |
| if (CE->getFn()->getKind() == ExprKind::DeclRef) |
| return true; |
| |
| // When callling an instance method inside another instance method, |
| // we have a dot syntax call whose dot and base are both implicit. We |
| // need to explicitly allow the specific case here. |
| if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) { |
| if (DSC->getBase()->isImplicit() && |
| DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc()) |
| return true; |
| } |
| } |
| return false; |
| } |
| case RangeKind::PartOfExpression: { |
| if (auto *CE = dyn_cast<CallExpr>(Info.CommonExprParent)) { |
| if (auto *DSC = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) { |
| if (DSC->getFn()->getStartLoc() == Info.TokensInRange.front().getLoc()) |
| return true; |
| } |
| } |
| return false; |
| } |
| case RangeKind::SingleDecl: |
| case RangeKind::MultiTypeMemberDecl: |
| case RangeKind::SingleStatement: |
| case RangeKind::MultiStatement: |
| case RangeKind::Invalid: |
| return false; |
| } |
| } |
| }// end of anonymous namespace |
| |
| StringRef swift::ide:: |
| getDescriptiveRefactoringKindName(RefactoringKind Kind) { |
| switch(Kind) { |
| case RefactoringKind::None: |
| llvm_unreachable("Should be a valid refactoring kind"); |
| #define REFACTORING(KIND, NAME, ID) case RefactoringKind::KIND: return NAME; |
| #include "swift/IDE/RefactoringKinds.def" |
| } |
| } |
| |
| StringRef swift::ide:: |
| getDescriptiveRenameUnavailableReason(RenameAvailableKind Kind) { |
| switch(Kind) { |
| case RenameAvailableKind::Available: |
| return ""; |
| case RenameAvailableKind::Unavailable_system_symbol: |
| return "symbol from system module cannot be renamed"; |
| case RenameAvailableKind::Unavailable_has_no_location: |
| return "symbol without a declaration location cannot be renamed"; |
| case RenameAvailableKind::Unavailable_has_no_name: |
| return "cannot find the name of the symbol"; |
| case RenameAvailableKind::Unavailable_has_no_accessibility: |
| return "cannot decide the accessibility of the symbol"; |
| case RenameAvailableKind::Unavailable_decl_from_clang: |
| return "cannot rename a Clang symbol from its Swift reference"; |
| } |
| } |
| |
| SourceLoc swift::ide::RangeConfig::getStart(SourceManager &SM) { |
| return SM.getLocForLineCol(BufferId, Line, Column); |
| } |
| |
| SourceLoc swift::ide::RangeConfig::getEnd(SourceManager &SM) { |
| return getStart(SM).getAdvancedLoc(Length); |
| } |
| |
| struct swift::ide::FindRenameRangesAnnotatingConsumer::Implementation { |
| std::unique_ptr<SourceEditConsumer> pRewriter; |
| Implementation(SourceManager &SM, unsigned BufferId, llvm::raw_ostream &OS) |
| : pRewriter(new SourceEditOutputConsumer(SM, BufferId, OS)) {} |
| static StringRef tag(RefactoringRangeKind Kind) { |
| switch (Kind) { |
| case RefactoringRangeKind::BaseName: |
| return "base"; |
| case RefactoringRangeKind::KeywordBaseName: |
| return "keywordBase"; |
| case RefactoringRangeKind::ParameterName: |
| return "param"; |
| case RefactoringRangeKind::NoncollapsibleParameterName: |
| return "noncollapsibleparam"; |
| case RefactoringRangeKind::DeclArgumentLabel: |
| return "arglabel"; |
| case RefactoringRangeKind::CallArgumentLabel: |
| return "callarg"; |
| case RefactoringRangeKind::CallArgumentColon: |
| return "callcolon"; |
| case RefactoringRangeKind::CallArgumentCombined: |
| return "callcombo"; |
| case RefactoringRangeKind::SelectorArgumentLabel: |
| return "sel"; |
| } |
| } |
| void accept(SourceManager &SM, const RenameRangeDetail &Range) { |
| std::string NewText; |
| llvm::raw_string_ostream OS(NewText); |
| StringRef Tag = tag(Range.RangeKind); |
| OS << "<" << Tag; |
| if (Range.Index.hasValue()) |
| OS << " index=" << *Range.Index; |
| OS << ">" << Range.Range.str() << "</" << Tag << ">"; |
| pRewriter->accept(SM, {Range.Range, OS.str(), {}}); |
| } |
| }; |
| |
| swift::ide::FindRenameRangesAnnotatingConsumer:: |
| FindRenameRangesAnnotatingConsumer(SourceManager &SM, unsigned BufferId, |
| llvm::raw_ostream &OS): Impl(*new Implementation(SM, BufferId, OS)) {} |
| |
| swift::ide::FindRenameRangesAnnotatingConsumer::~FindRenameRangesAnnotatingConsumer() { |
| delete &Impl; |
| } |
| |
| void swift::ide::FindRenameRangesAnnotatingConsumer:: |
| accept(SourceManager &SM, RegionType RegionType, |
| ArrayRef<RenameRangeDetail> Ranges) { |
| if (RegionType == RegionType::Mismatch || RegionType == RegionType::Unmatched) |
| return; |
| for (const auto &Range : Ranges) { |
| Impl.accept(SM, Range); |
| } |
| } |
| ArrayRef<RenameAvailabiliyInfo> |
| swift::ide::collectRenameAvailabilityInfo(const ValueDecl *VD, |
| std::vector<RenameAvailabiliyInfo> &Scratch) { |
| RenameAvailableKind AvailKind = RenameAvailableKind::Available; |
| if (getRelatedSystemDecl(VD)){ |
| AvailKind = RenameAvailableKind::Unavailable_system_symbol; |
| } else if (VD->getClangDecl()) { |
| AvailKind = RenameAvailableKind::Unavailable_decl_from_clang; |
| } else if (VD->getStartLoc().isInvalid()) { |
| AvailKind = RenameAvailableKind::Unavailable_has_no_location; |
| } else if (!VD->hasName()) { |
| AvailKind = RenameAvailableKind::Unavailable_has_no_name; |
| } else if (!VD->hasAccess()) { |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| if (isa<AbstractFunctionDecl>(VD)) { |
| // Disallow renaming accessors. |
| if (isa<AccessorDecl>(VD)) |
| return Scratch; |
| |
| // Disallow renaming deinit. |
| if (isa<DestructorDecl>(VD)) |
| return Scratch; |
| |
| // Disallow renaming init with no arguments. |
| if (auto CD = dyn_cast<ConstructorDecl>(VD)) { |
| if (!CD->getParameters()->size()) |
| return Scratch; |
| } |
| } |
| |
| // Always return local rename for parameters. |
| // FIXME: if the cursor is on the argument, we should return global rename. |
| if (isa<ParamDecl>(VD)) { |
| Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind); |
| return Scratch; |
| } |
| |
| // If the indexer considers VD a global symbol, then we apply global rename. |
| if (index::isLocalSymbol(VD)) |
| Scratch.emplace_back(RefactoringKind::LocalRename, AvailKind); |
| else |
| Scratch.emplace_back(RefactoringKind::GlobalRename, AvailKind); |
| |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| ArrayRef<RefactoringKind> swift::ide:: |
| collectAvailableRefactorings(SourceFile *SF, |
| ResolvedCursorInfo CursorInfo, |
| std::vector<RefactoringKind> &Scratch, |
| bool ExcludeRename) { |
| llvm::SmallVector<RefactoringKind, 2> AllKinds; |
| switch(CursorInfo.Kind) { |
| case CursorInfoKind::ModuleRef: |
| case CursorInfoKind::Invalid: |
| case CursorInfoKind::StmtStart: |
| case CursorInfoKind::ExprStart: |
| break; |
| case CursorInfoKind::ValueRef: { |
| auto RenameOp = getAvailableRenameForDecl(CursorInfo.ValueD); |
| if (RenameOp.hasValue() && |
| RenameOp.getValue() == RefactoringKind::GlobalRename) |
| AllKinds.push_back(RenameOp.getValue()); |
| } |
| } |
| DiagnosticEngine DiagEngine(SF->getASTContext().SourceMgr); |
| #define CURSOR_REFACTORING(KIND, NAME, ID) \ |
| if (RefactoringAction##KIND::isApplicable(CursorInfo, DiagEngine)) \ |
| AllKinds.push_back(RefactoringKind::KIND); |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| // Exclude renames. |
| for(auto Kind: AllKinds) { |
| switch (Kind) { |
| case RefactoringKind::LocalRename: |
| case RefactoringKind::GlobalRename: |
| if (ExcludeRename) |
| break; |
| LLVM_FALLTHROUGH; |
| default: |
| Scratch.push_back(Kind); |
| } |
| } |
| |
| return llvm::makeArrayRef(Scratch); |
| } |
| |
| |
| |
| ArrayRef<RefactoringKind> swift::ide:: |
| collectAvailableRefactorings(SourceFile *SF, RangeConfig Range, |
| bool &RangeStartMayNeedRename, |
| std::vector<RefactoringKind> &Scratch, |
| llvm::ArrayRef<DiagnosticConsumer*> DiagConsumers) { |
| |
| if (Range.Length == 0) { |
| return collectAvailableRefactoringsAtCursor(SF, Range.Line, Range.Column, |
| Scratch, DiagConsumers); |
| } |
| // Prepare the tool box. |
| ASTContext &Ctx = SF->getASTContext(); |
| SourceManager &SM = Ctx.SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| std::for_each(DiagConsumers.begin(), DiagConsumers.end(), |
| [&](DiagnosticConsumer *Con) { DiagEngine.addConsumer(*Con); }); |
| |
| RangeResolver Resolver(*SF, Range.getStart(SF->getASTContext().SourceMgr), |
| Range.getEnd(SF->getASTContext().SourceMgr)); |
| ResolvedRangeInfo Result = Resolver.resolve(); |
| |
| #define RANGE_REFACTORING(KIND, NAME, ID) \ |
| if (RefactoringAction##KIND::isApplicable(Result, DiagEngine)) \ |
| Scratch.push_back(RefactoringKind::KIND); |
| #include "swift/IDE/RefactoringKinds.def" |
| |
| RangeStartMayNeedRename = rangeStartMayNeedRename(Result); |
| return Scratch; |
| } |
| |
| bool swift::ide:: |
| refactorSwiftModule(ModuleDecl *M, RefactoringOptions Opts, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(Opts.Kind != RefactoringKind::None && "should have a refactoring kind."); |
| |
| // Use the default name if not specified. |
| if (Opts.PreferredName.empty()) { |
| Opts.PreferredName = getDefaultPreferredName(Opts.Kind); |
| } |
| |
| switch (Opts.Kind) { |
| #define SEMANTIC_REFACTORING(KIND, NAME, ID) \ |
| case RefactoringKind::KIND: { \ |
| RefactoringAction##KIND Action(M, Opts, EditConsumer, DiagConsumer); \ |
| if (RefactoringKind::KIND == RefactoringKind::LocalRename || \ |
| Action.isApplicable()) \ |
| return Action.performChange(); \ |
| return true; \ |
| } |
| #include "swift/IDE/RefactoringKinds.def" |
| case RefactoringKind::GlobalRename: |
| case RefactoringKind::FindGlobalRenameRanges: |
| case RefactoringKind::FindLocalRenameRanges: |
| llvm_unreachable("not a valid refactoring kind"); |
| case RefactoringKind::None: |
| llvm_unreachable("should not enter here."); |
| } |
| } |
| |
| static std::vector<ResolvedLoc> |
| resolveRenameLocations(ArrayRef<RenameLoc> RenameLocs, SourceFile &SF, |
| DiagnosticEngine &Diags) { |
| SourceManager &SM = SF.getASTContext().SourceMgr; |
| unsigned BufferID = SF.getBufferID().getValue(); |
| |
| std::vector<UnresolvedLoc> UnresolvedLocs; |
| for (const RenameLoc &RenameLoc : RenameLocs) { |
| DeclNameViewer OldName(RenameLoc.OldName); |
| SourceLoc Location = SM.getLocForLineCol(BufferID, RenameLoc.Line, |
| RenameLoc.Column); |
| |
| if (!OldName.isValid()) { |
| Diags.diagnose(Location, diag::invalid_name, RenameLoc.OldName); |
| return {}; |
| } |
| |
| if (!RenameLoc.NewName.empty()) { |
| DeclNameViewer NewName(RenameLoc.NewName); |
| ArrayRef<StringRef> ParamNames = NewName.args(); |
| bool newOperator = Lexer::isOperator(NewName.base()); |
| bool NewNameIsValid = NewName.isValid() && |
| (Lexer::isIdentifier(NewName.base()) || newOperator) && |
| std::all_of(ParamNames.begin(), ParamNames.end(), [](StringRef Label) { |
| return Label.empty() || Lexer::isIdentifier(Label); |
| }); |
| |
| if (!NewNameIsValid) { |
| Diags.diagnose(Location, diag::invalid_name, RenameLoc.NewName); |
| return {}; |
| } |
| |
| if (NewName.partsCount() != OldName.partsCount()) { |
| Diags.diagnose(Location, diag::arity_mismatch, RenameLoc.NewName, |
| RenameLoc.OldName); |
| return {}; |
| } |
| |
| if (RenameLoc.Usage == NameUsage::Call && !RenameLoc.IsFunctionLike) { |
| Diags.diagnose(Location, diag::name_not_functionlike, RenameLoc.NewName); |
| return {}; |
| } |
| } |
| |
| bool isOperator = Lexer::isOperator(OldName.base()); |
| UnresolvedLocs.push_back({ |
| Location, |
| (RenameLoc.Usage == NameUsage::Unknown || |
| (RenameLoc.Usage == NameUsage::Call && !isOperator)) |
| }); |
| } |
| |
| NameMatcher Resolver(SF); |
| return Resolver.resolve(UnresolvedLocs, SF.getAllTokens()); |
| } |
| |
| int swift::ide::syntacticRename(SourceFile *SF, ArrayRef<RenameLoc> RenameLocs, |
| SourceEditConsumer &EditConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| DiagEngine.addConsumer(DiagConsumer); |
| |
| auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine); |
| if (ResolvedLocs.size() != RenameLocs.size()) |
| return true; // Already diagnosed. |
| |
| size_t index = 0; |
| llvm::StringMap<char> ReplaceTextContext; |
| for(const RenameLoc &Rename: RenameLocs) { |
| ResolvedLoc &Resolved = ResolvedLocs[index++]; |
| TextReplacementsRenamer Renamer(SM, Rename.OldName, Rename.NewName, |
| ReplaceTextContext); |
| RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename); |
| if (Type == RegionType::Mismatch) { |
| DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename, |
| Rename.NewName); |
| EditConsumer.accept(SM, Type, None); |
| } else { |
| EditConsumer.accept(SM, Type, Renamer.getReplacements()); |
| } |
| } |
| |
| return false; |
| } |
| |
| int swift::ide::findSyntacticRenameRanges( |
| SourceFile *SF, llvm::ArrayRef<RenameLoc> RenameLocs, |
| FindRenameRangesConsumer &RenameConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine DiagEngine(SM); |
| DiagEngine.addConsumer(DiagConsumer); |
| |
| auto ResolvedLocs = resolveRenameLocations(RenameLocs, *SF, DiagEngine); |
| if (ResolvedLocs.size() != RenameLocs.size()) |
| return true; // Already diagnosed. |
| |
| size_t index = 0; |
| for (const RenameLoc &Rename : RenameLocs) { |
| ResolvedLoc &Resolved = ResolvedLocs[index++]; |
| RenameRangeDetailCollector Renamer(SM, Rename.OldName); |
| RegionType Type = Renamer.addSyntacticRenameRanges(Resolved, Rename); |
| if (Type == RegionType::Mismatch) { |
| DiagEngine.diagnose(Resolved.Range.getStart(), diag::mismatched_rename, |
| Rename.NewName); |
| RenameConsumer.accept(SM, Type, None); |
| } else { |
| RenameConsumer.accept(SM, Type, Renamer.Ranges); |
| } |
| } |
| |
| return false; |
| } |
| |
| int swift::ide::findLocalRenameRanges( |
| SourceFile *SF, RangeConfig Range, |
| FindRenameRangesConsumer &RenameConsumer, |
| DiagnosticConsumer &DiagConsumer) { |
| assert(SF && "null source file"); |
| |
| SourceManager &SM = SF->getASTContext().SourceMgr; |
| DiagnosticEngine Diags(SM); |
| Diags.addConsumer(DiagConsumer); |
| |
| auto StartLoc = Lexer::getLocForStartOfToken(SM, Range.getStart(SM)); |
| |
| CursorInfoResolver Resolver(*SF); |
| ResolvedCursorInfo CursorInfo = Resolver.resolve(StartLoc); |
| if (!CursorInfo.isValid() || !CursorInfo.ValueD) { |
| Diags.diagnose(StartLoc, diag::unresolved_location); |
| return true; |
| } |
| ValueDecl *VD = CursorInfo.ValueD; |
| llvm::SmallVector<DeclContext *, 8> Scopes; |
| analyzeRenameScope(VD, Diags, Scopes); |
| if (Scopes.empty()) |
| return true; |
| RenameRangeCollector RangeCollector(VD, StringRef()); |
| for (DeclContext *DC : Scopes) |
| indexDeclContext(DC, RangeCollector); |
| |
| return findSyntacticRenameRanges(SF, RangeCollector.results(), RenameConsumer, |
| DiagConsumer); |
| } |