================ @@ -538,11 +564,205 @@ 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 llvm::Error::success(); +} + +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 isMatchingSelectorName(const syntax::Token &Cur, const syntax::Token &Next, + const SourceManager &SM, + llvm::StringRef SelectorName) { + if (SelectorName.empty()) + return Cur.kind() == tok::colon; + return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName; +} + +std::vector<Range> findAllSelectorPieces(llvm::ArrayRef<syntax::Token> Tokens, + const SourceManager &SM, Selector Sel, + tok::TokenKind Terminator) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector<tok::TokenKind, 8> Closes; + std::vector<Range> SelectorPieces; + unsigned Last = Tokens.size() - 1; + for (unsigned Index = 0; Index < Last; ++Index) { + 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 std::vector<Range>(); + } + + if (Tok.kind() == Terminator) + return SelectorPieces.size() == NumArgs ? SelectorPieces + : std::vector<Range>(); + + switch (Tok.kind()) { + case tok::l_square: + Closes.push_back(tok::r_square); + break; + case tok::l_paren: + Closes.push_back(tok::r_paren); + break; + case tok::l_brace: + Closes.push_back(tok::r_brace); + break; + case tok::r_square: + case tok::r_paren: + case tok::r_brace: + if (Closes.empty() || Closes.back() != Tok.kind()) + return std::vector<Range>(); + Closes.pop_back(); + break; + case tok::semi: + // top level ; terminates all statements. + if (Closes.empty()) + return SelectorPieces.size() == NumArgs ? SelectorPieces + : std::vector<Range>(); + break; + default: + break; + } + } + return std::vector<Range>(); +} + +/// 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 brackets 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). + llvm::SmallVector<tok::TokenKind, 8> Closes; + llvm::StringRef FirstSelPiece = Selector->getNameForSlot(0); + + std::vector<Range> SelectorPieces; + std::vector<Range> MsgExprPieces; + 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]; + + // Search for the first selector piece to begin a match, but make sure we're + // not in () to avoid the @selector(foo:bar:) case. + if ((Closes.empty() || Closes.back() == tok::r_square) && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, FirstSelPiece)) { + // We found a candidate for our match, this might be a method call, + // declaration, or unrelated identifier eg: + // - [obj ^sel0: X sel1: Y ... ] + // or + // @interface Foo + // - int ^sel0: X sel1: Y ... ---------------- kadircet wrote:
i think it's worth highlighting that these cases either end with `{` or `;` 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