================ @@ -538,11 +565,254 @@ std::optional<InvalidName> checkName(const NamedDecl &RenameDecl, Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); + return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token &Next, + const SourceManager &SM, + llvm::StringRef SelectorName) { + if (SelectorName.empty()) + return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token &Cur, const syntax::Token &Next) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef<syntax::Token> Tokens, + const SourceManager &SM, unsigned Index, + unsigned Last, Selector Sel, + std::vector<Range> &SelectorPieces) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector<char, 8> Closes; + SelectorPieces.clear(); + while (Index < Last) { + const auto &Tok = Tokens[Index]; + + if (Closes.empty()) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Sel.getNameForSlot(PieceCount))) { + // If 'foo:' instead of ':' (empty selector), we need to skip the ':' + // token after the name. + if (!Sel.getNameForSlot(PieceCount).empty()) { + ++Index; + } + SelectorPieces.push_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); + continue; + } + // If we've found all pieces but the current token looks like another + // selector piece, it means the method being renamed is a strict prefix of + // the selector we've found - should be skipped. + if (SelectorPieces.size() >= NumArgs && + isSelectorLike(Tok, Tokens[Index + 1])) + return false; + } + + switch (Tok.kind()) { + case tok::l_square: + Closes.push_back(']'); + break; + case tok::l_paren: + Closes.push_back(')'); + break; + case tok::l_brace: + Closes.push_back('}'); + break; + case tok::r_square: + if (Closes.empty()) + return SelectorPieces.size() == NumArgs; + + if (Closes.back() != ']') + return false; + Closes.pop_back(); + break; + case tok::r_paren: + if (Closes.empty() || Closes.back() != ')') + return false; + Closes.pop_back(); + break; + case tok::r_brace: + if (Closes.empty() || Closes.back() != '}') + return false; + Closes.pop_back(); + break; + case tok::semi: + // top level ; ends all statements. + if (Closes.empty()) + return false; + break; + default: + break; + } + + ++Index; + } + return false; +} + +/// Collects all ranges of the given identifier/selector in the source code. +/// +/// If a selector is given, this does a full lex of the given source code in +/// order to identify all selector fragments (e.g. in method exprs/decls) since +/// they are non-contiguous. +std::vector<SymbolRange> collectRenameIdentifierRanges( + llvm::StringRef Identifier, llvm::StringRef Content, + const LangOptions &LangOpts, std::optional<Selector> Selector) { + std::vector<SymbolRange> Ranges; + if (!Selector) { + auto IdentifierRanges = + collectIdentifierRanges(Identifier, Content, LangOpts); + for (const auto &R : IdentifierRanges) + Ranges.emplace_back(R); + return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto &SM = FileSM.get(); + + // We track parens and braces to ensure that we don't accidentally try parsing + // a method declaration or definition which isn't at the top level or similar + // looking expressions (e.g. an @selector() expression). + unsigned ParenCount = 0; + unsigned BraceCount = 0; + unsigned NumArgs = Selector->getNumArgs(); + + std::vector<Range> SelectorPieces; + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + for (unsigned Index = 0; Index < Last; ++Index) { + const auto &Tok = Tokens[Index]; + + if (BraceCount == 0 && ParenCount == 0) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Selector->getNameForSlot(PieceCount))) { + // If 'foo:' instead of ':' (empty selector), we need to skip the ':' + // token after the name. + if (!Selector->getNameForSlot(PieceCount).empty()) { + ++Index; + } + SelectorPieces.push_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); + continue; + } + // If we've found all pieces, we still need to try to consume more pieces + // as it's possible the selector being renamed is a prefix of this method + // name. + if (PieceCount >= NumArgs && isSelectorLike(Tok, Tokens[Index + 1])) { + ++Index; + SelectorPieces.push_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); + continue; + } + } + + switch (Tok.kind()) { + case tok::l_square: + if (parseMessageExpression(Tokens, SM, Index + 1, Last, *Selector, + SelectorPieces)) { + Ranges.emplace_back(std::move(SelectorPieces)); + SelectorPieces.clear(); + } + break; + case tok::l_paren: + ParenCount++; + break; + case tok::r_paren: + if (ParenCount > 0) { + --ParenCount; + } + break; + case tok::r_brace: + if (BraceCount > 0) { + --BraceCount; + } + break; + case tok::l_brace: + // At the top level scope we should only have method defs. + if (BraceCount == 0 && ParenCount == 0) { + // All the selector pieces in the method def have been found. + if (SelectorPieces.size() == NumArgs) { + Ranges.emplace_back(std::move(SelectorPieces)); + } + SelectorPieces.clear(); + } + ++BraceCount; + break; + case tok::semi: + // At the top level scope we should only have method decls. + if (BraceCount == 0 && ParenCount == 0) { + // All the selector pieces in the method decl have been found. + if (SelectorPieces.size() == NumArgs) { + Ranges.emplace_back(std::move(SelectorPieces)); + } + SelectorPieces.clear(); + } + break; + default: + break; + } + } + return Ranges; +} + +clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + const auto *Token = AST.getTokens().spelledTokenAt(TokLoc); + assert(Token && "rename expects spelled tokens"); + clangd::Range Result; + Result.start = sourceLocToPosition(SM, Token->location()); + Result.end = sourceLocToPosition(SM, Token->endLocation()); return Result; } +// AST-based ObjC method rename, it renames all occurrences in the main file +// even for selectors which may have multiple tokens. +llvm::Expected<tooling::Replacements> +renameObjCMethodWithinFile(ParsedAST &AST, const ObjCMethodDecl *MD, + llvm::StringRef NewName, + std::vector<SourceLocation> SelectorOccurences) { + const SourceManager &SM = AST.getSourceManager(); + auto Code = SM.getBufferData(SM.getMainFileID()); + auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + llvm::SmallVector<llvm::StringRef, 8> NewNames; + NewName.split(NewNames, ":"); + + std::vector<Range> Ranges; + const auto &LangOpts = MD->getASTContext().getLangOpts(); + for (const auto &Loc : SelectorOccurences) + Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts)); + auto FilePath = AST.tuPath(); + auto RenameRanges = collectRenameIdentifierRanges( + RenameIdentifier, Code, LangOpts, MD->getSelector()); ---------------- ahoppen wrote:
Am I missing something here or is this doing a purely syntactic rename, ignoring `SelectorOccurrences` altogether? https://github.com/llvm/llvm-project/pull/76466 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits