diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -74,6 +74,7 @@ struct IncludeInsertionIndicator { std::string Insert = "•"; std::string NoInsert = " "; + std::string Rewrite = "⮞"; } IncludeIndicator; /// Expose origins of completion items in the label (for debugging). @@ -194,6 +195,9 @@ // thse includes may not be accurate for all of them. llvm::SmallVector Includes; + /// True if the name is a description, rather than text to be inserted. + bool AbstractName = false; + /// Holds information about small corrections that needs to be done. Like /// converting '->' to '.' on member access. std::vector FixIts; diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -302,7 +302,12 @@ if (C.SemaResult) { assert(ASTCtx); Completion.Origin |= SymbolOrigin::AST; - Completion.Name = std::string(llvm::StringRef(SemaCCS->getTypedText())); + if (C.SemaResult->Name) { + Completion.Name = C.SemaResult->Name; + Completion.AbstractName = true; + } else { + Completion.Name = std::string(llvm::StringRef(SemaCCS->getTypedText())); + } if (Completion.Scope.empty()) { if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) || (C.SemaResult->Kind == CodeCompletionResult::RK_Pattern)) @@ -402,6 +407,8 @@ bool IsPattern = C.SemaResult->Kind == CodeCompletionResult::RK_Pattern; getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, &Completion.RequiredQualifier, IsPattern); + if (C.SemaResult->Name) // Description is self-contained. + S.Signature.clear(); S.ReturnType = getReturnType(*SemaCCS); } else if (C.IndexResult) { S.Signature = std::string(C.IndexResult->Signature); @@ -847,6 +854,8 @@ // Returns the filtering/sorting name for Result, which must be from Results. // Returned string is owned by this recorder (or the AST). llvm::StringRef getName(const CodeCompletionResult &Result) { + if (Result.Name) + return Result.Name; switch (Result.Kind) { case CodeCompletionResult::RK_Declaration: if (auto *ID = Result.Declaration->getIdentifier()) @@ -2099,9 +2108,17 @@ const auto *InsertInclude = Includes.empty() ? nullptr : &Includes[0]; LSP.label = ((InsertInclude && InsertInclude->Insertion) ? Opts.IncludeIndicator.Insert - : Opts.IncludeIndicator.NoInsert) + - (Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") + - RequiredQualifier + Name + Signature; + : AbstractName ? Opts.IncludeIndicator.Rewrite + : Opts.IncludeIndicator.NoInsert); + if (Opts.ShowOrigins) + LSP.label += "[" + llvm::to_string(Origin) + "]"; + if (AbstractName) { + LSP.label += Name; + } else { + LSP.label += RequiredQualifier; + LSP.label += Name; + LSP.label += Signature; + } LSP.kind = Kind; LSP.detail = BundleSize > 1 @@ -2120,7 +2137,8 @@ } LSP.sortText = sortText(Score.Total, Name); LSP.filterText = Name; - LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name}; + LSP.textEdit = {CompletionTokenRange, + AbstractName ? RequiredQualifier : RequiredQualifier + Name}; // Merge continuous additionalTextEdits into main edit. The main motivation // behind this is to help LSP clients, it seems most of them are confused when // they are provided with additionalTextEdits that are consecutive to main @@ -2129,7 +2147,7 @@ // is mainly to help LSP clients again, so that changes do not effect each // other. for (const auto &FixIt : FixIts) { - if (FixIt.range.end == LSP.textEdit->range.start) { + if (0 && FixIt.range.end == LSP.textEdit->range.start) { LSP.textEdit->newText = FixIt.newText + LSP.textEdit->newText; LSP.textEdit->range.start = FixIt.range.start; } else { diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -780,6 +780,13 @@ const IdentifierInfo *Macro; }; + /// When Kind == RK_Pattern, an optional short name for the pattern. + /// Often he pattern combines with a fixit to rewrite surrounding code. + /// + /// For, example after "foo.", a completion that rewrites to "std::move(foo)" + /// could have Name = "move". + const char *Name = nullptr; + /// The priority of this particular code-completion result. unsigned Priority; diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -20,6 +20,7 @@ #include "clang/AST/ExprConcepts.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/NestedNameSpecifier.h" +#include "clang/AST/OperationKinds.h" #include "clang/AST/QualTypeNames.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Type.h" @@ -2462,15 +2463,19 @@ // 'this', if we're in a non-static member function. addThisCompletion(SemaRef, Results); - // true - Builder.AddResultTypeChunk("bool"); - Builder.AddTypedTextChunk("true"); - Results.AddResult(Result(Builder.TakeString())); + if (Results.getCompletionContext().getPreferredType().isNull() || + Results.getCompletionContext() + .getPreferredType() + .getCanonicalType() == SemaRef.getASTContext().BoolTy) { + Builder.AddResultTypeChunk("bool"); + Builder.AddTypedTextChunk("true"); + Results.AddResult(Result(Builder.TakeString())); - // false - Builder.AddResultTypeChunk("bool"); - Builder.AddTypedTextChunk("false"); - Results.AddResult(Result(Builder.TakeString())); + // false + Builder.AddResultTypeChunk("bool"); + Builder.AddTypedTextChunk("false"); + Results.AddResult(Result(Builder.TakeString())); + } if (SemaRef.getLangOpts().RTTI) { // dynamic_cast < type-id > ( expression ) @@ -5549,6 +5554,405 @@ return Base; } +bool isMovable(Expr *E) { + if (E->getValueKind() != VK_LValue) + return false; + return E->getType().getNonReferenceType()->isStructureOrClassType(); +} + +QualType pointeeType(QualType T, Sema &S) { + T = T.getCanonicalType(); + if (const auto *PT = dyn_cast(T)) + return PT->getPointeeType(); + if (const auto *RT = dyn_cast(T)) { + // If RD is complete, look at its operator*. + // Unfortunately with templates it's often incomplete, and instantiating is + // messy. See fallback cases below. + if (auto *RD = RT->getDecl()->getDefinition()) { + if (auto *Star = + RD->lookup(S.getASTContext().DeclarationNames.getCXXOperatorName( + clang::OO_Star)) + .find_first()) + return Star->getReturnType().getNonReferenceType(); + } else if (const auto *CTSD = + dyn_cast(RT->getDecl())) { + // Heuristic: pointee of Iterator is T. + // XXX this isn't a good heuristic: in libstdc++ vector::iterator + // is __normal_iterator>! + const auto &Args = CTSD->getTemplateArgs(); + if (Args.size() > 0) + return Args.get(0).getAsType(); + } else if (RT->getDecl()->getDeclContext()->getDeclKind() == + Decl::ClassTemplateSpecialization) { + // Heuristic: pointee of Container::Iterator is T. + const auto &Args = + static_cast(RD->getDeclContext()) + ->getTemplateArgs(); + if (Args.size() > 0) + return Args.get(0).getAsType(); + } + } + // Not much point probing into dependent TemplateSpecializationType, + // it's very to yield a dependent type we can't analyze. + return QualType(); +} + +bool isByValue(QualType T, ASTContext &Ctx) { + T = T.getCanonicalType().getNonReferenceType(); + if (T->isLiteralType(Ctx)) { + if (auto Size = Ctx.getTypeSizeInCharsIfKnown(T)) + // Arbitrary threshold. Allows string_view by value. + return Size->getQuantity() < 16; + } + return false; +} + +QualType iteratorType(const RecordDecl *RD, const ASTContext &Ctx) { + if (RD && (RD = RD->getDefinition())) { + auto Begin = RD->lookup(&Ctx.Idents.get("begin")); + if (!Begin.empty() && !RD->lookup(&Ctx.Idents.get("end")).empty()) { + if (const auto *FD = Begin.front()->getAsFunction()) { + if (FD->getMinRequiredArguments() == 0) + return FD->getReturnType(); + } + } + } + return QualType(); +} + +// If T is a valid container for a C++ foreach loop, return its iterator type. +// If it isn't or we can't tell, return null. +// If there's ambiguity (e.g. overloaded begin()) return one arbitrarily. +QualType iteratorType(QualType T, ASTContext &Ctx) { + T = T.getCanonicalType().getNonReferenceType(); + if (const auto *ET = T->getArrayElementTypeNoTypeQual()) + return Ctx.getPointerType(QualType(ET, 0)); + if (const auto *RT = T->getAs()) + return iteratorType(RT->getDecl(), Ctx); + if (const auto *TST = T->getAs()) { + if (auto *CTD = dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl())) + return iteratorType(CTD->getTemplatedDecl(), Ctx); + } + return QualType(); +} + +const char *iterationVariableType(QualType IteratorType, Sema &S) { + if (IteratorType.isNull()) + return nullptr; + QualType Element = pointeeType(IteratorType, S); + llvm::errs() << "element type is " << Element.getAsString() << "\n"; + if (!Element.isNull()) { + if (Element->isPointerType()) + return "const auto*"; + if (isByValue(Element, S.getASTContext())) + return "auto"; + } + return "const auto&"; +} + +// Whether E is a simple reference that we shouldn't extract a variable for. +bool isSyntacticallySimple(const Expr *E) { + if (const auto *DRE = llvm::dyn_cast(E)) { + if (!DRE->getQualifier()) + return true; + } else if (const auto *ME = llvm::dyn_cast(E)) { + if (ME->getBase()->isImplicitCXXThis()) + return true; + } + return false; +} + +bool isConvertibleToBool(const RecordDecl *RD) { + const auto &Ctx = RD->getParentASTContext(); + return !RD->lookup( + Ctx.DeclarationNames.getCXXConversionFunctionName(Ctx.BoolTy)) + .empty(); +} + +struct Cast { + using Set = unsigned; + enum : unsigned { + Static = 1 << 0, + StaticAddRef = 1 << 1, + Dynamic = 1 << 2, + Reinterpret = 1 << 3, + Bit = 1 << 4, + Const = 1 << 5, + C = 1 << 6, + }; +}; +Cast::Set getDowncasts(QualType From, QualType To, ASTContext &Ctx) { + assert(From.isCanonical() && To.isCanonical() && + From.getQualifiers() == To.getQualifiers()); + if (!Ctx.getLangOpts().CPlusPlus) + return 0; + Cast::Set Result = 0; + if (auto *FromRT = dyn_cast(From)) + if (auto *ToRT = dyn_cast(To)) { + if (ToRT->getAsCXXRecordDecl()->isDerivedFrom( + FromRT->getAsCXXRecordDecl())) { + Result |= Cast::Static; + if (FromRT->getAsCXXRecordDecl()->isPolymorphic() && + Ctx.getLangOpts().RTTI) + Result |= Cast::Dynamic; + } + } + return Result; +} + +Cast::Set recommendCasts(Expr *FromExpr, QualType To, ASTContext &Ctx) { + QualType FromNoRef = FromExpr->getType() + .getCanonicalType() + .getNonReferenceType() + .getUnqualifiedType(); + QualType ToNoRef = + To.getCanonicalType().getNonReferenceType().getUnqualifiedType(); + + // Producing a non-const reference has only a few possibilities... + if (To->isLValueReferenceType() && !To->getPointeeType().isConstQualified()) { + // it must always come from an lvalue + if (FromExpr->isLValue()) { + // if it's from a const lvalue, const_cast is possible + if (FromExpr->getType().isConstQualified()) + return FromNoRef == ToNoRef ? Cast::Const : 0; + // otherwise cast between non-const refs can only be inheritance + return getDowncasts(FromNoRef, ToNoRef, Ctx); + } + return 0; + } + // From here, we assume we're producing values and mostly ignore refs. + + // Integral casts. Only emit those that can never be implicit. + if ((FromNoRef->isScopedEnumeralType() && ToNoRef->isIntegerType()) || + (FromNoRef->isIntegerType() && ToNoRef->isEnumeralType())) + return Cast::Static; + auto IsIntPointer = [&](QualType T) { + return Ctx.getIntPtrType() == T || Ctx.getUIntPtrType() == T; + }; + if ((FromNoRef->isPointerType() && IsIntPointer(ToNoRef)) || + (ToNoRef->isPointerType() && IsIntPointer(FromNoRef))) + return Cast::Reinterpret; + + // Downcasts of refs/pointers/values of related class types. + if (auto Downcasts = getDowncasts(FromNoRef, ToNoRef, Ctx)) + return Downcasts; + if (FromNoRef->isPointerType() && ToNoRef->isPointerType()) { + QualType FromPointee = FromNoRef->getPointeeType(); + QualType ToPointee = ToNoRef->getPointeeType(); + if (FromPointee.getQualifiers() == ToPointee.getQualifiers()) + if (auto Downcasts = getDowncasts(FromPointee, ToPointee, Ctx)) + return Downcasts; + } + return 0; +} + +bool isConvertibleToBool(QualType T, ASTContext &Ctx) { + T = T.getCanonicalType(); + if (T->isIntegralOrUnscopedEnumerationType()) + return true; + if (T->isAnyPointerType()) + return true; + if (const auto *RT = dyn_cast(T)) { + if (const auto *RD = RT->getDecl()->getDefinition()) + return isConvertibleToBool(RD); + else if (const auto *CTSD = + llvm::dyn_cast(RD)) + return isConvertibleToBool(CTSD->getTemplateInstantiationPattern()); + } + if (const auto *TST = dyn_cast(T)) { + if (const auto *TD = dyn_cast_or_null( + TST->getTemplateName().getAsTemplateDecl())) + return isConvertibleToBool(TD->getTemplatedDecl()); + } + return false; +} + +// The type of the condition variable that can be extracted from an expression. +// e.g. "auto" or "const auto&". For a simple if (expr), returns nullptr +// If not convertible to boolean, returns nullptr. +const char *conditionVariableType(Expr *E, ASTContext &Ctx) { + if (!Ctx.getLangOpts().CPlusPlus11) + return nullptr; + QualType T = E->getType().getCanonicalType().getUnqualifiedType(); + // No variable extracted if there's nothing extra in it. + // Don't extract a variable if we can just use the original as easily. + if (T->isBooleanType() || isSyntacticallySimple(E)) + return nullptr; + + if (T->isPointerType()) + return "const auto*"; + if (E->isLValue() && !isByValue(T, Ctx)) + return "const auto&"; + return "auto"; +} + +void addPseudoMemberCompletions(Expr *Base, SourceRange Replace, + ResultBuilder &Results, bool BaseIsStmt, + QualType PreferredType) { + ASTContext &Ctx = Results.getSema().getASTContext(); + PrintingPolicy Policy = getCompletionPrintingPolicy(Results.getSema()); + bool Invalid = false; + llvm::StringRef ExprRef = Lexer::getSourceText( + CharSourceRange::getTokenRange(Base->getSourceRange()), + Ctx.getSourceManager(), Ctx.getLangOpts(), &Invalid); + if (Invalid) + return; + const char *ExprCode = Results.getAllocator().CopyString(ExprRef); + + auto TypeString = [&](QualType T) { + return Results.getAllocator().CopyString(T.getAsString(Policy)); + }; + auto AddResult = [&](const char *Name, QualType Type, auto BuildReplacement) { + CodeCompletionBuilder Replacement(Results.getAllocator(), + Results.getCodeCompletionTUInfo()); + BuildReplacement(Replacement); + if (!Type.isNull()) + Replacement.AddResultTypeChunk(TypeString(Type)); + CodeCompletionResult Result(Replacement.TakeString()); + Result.FixIts = {FixItHint::CreateRemoval(Replace)}; + Result.Name = Name; + Results.AddResult(std::move(Result)); + }; + if (Ctx.getLangOpts().CPlusPlus11 && !BaseIsStmt && isMovable(Base)) { + AddResult("move", + Ctx.getRValueReferenceType(Base->getType().getNonReferenceType()), + [&](CodeCompletionBuilder &B) { + B.AddTextChunk("std::move"); + B.AddChunk(CodeCompletionString::CK_LeftParen); + B.AddTextChunk(ExprCode); + B.AddChunk(CodeCompletionString::CK_RightParen); + }); + } + if (Ctx.getLangOpts().CPlusPlus11 && BaseIsStmt) { + QualType Iterator = iteratorType(Base->getType(), Ctx); + llvm::errs() << "iterator type is " << Iterator.getAsString() << "\n"; + if (const char *ElementType = + iterationVariableType(Iterator, Results.getSema())) { + AddResult("foreach", QualType(), [&](CodeCompletionBuilder &B) { + B.AddTextChunk("for"); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftParen); + B.AddTextChunk(ElementType); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddPlaceholderChunk("element"); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_Colon); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddTextChunk(ExprCode); + B.AddChunk(CodeCompletionString::CK_RightParen); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftBrace); + B.AddChunk(CodeCompletionString::CK_VerticalSpace); + B.AddChunk(CodeCompletionString::CK_Placeholder); + B.AddChunk(CodeCompletionString::CK_VerticalSpace); + B.AddChunk(CodeCompletionString::CK_RightBrace); + }); + } + } + + if (!PreferredType.isNull()) { + if (auto Casts = recommendCasts(Base, PreferredType, Ctx)) { + const char *PreferredTypeStr = TypeString(PreferredType); + auto AddCxxCast = [&](const char *Name) { + AddResult(Name, PreferredType, [&](CodeCompletionBuilder &B) { + B.AddTextChunk(Name); + B.AddChunk(CodeCompletionString::CK_LeftAngle); + B.AddTextChunk(PreferredTypeStr); + B.AddChunk(CodeCompletionString::CK_RightAngle); + B.AddChunk(CodeCompletionString::CK_LeftParen); + B.AddTextChunk(ExprCode); + B.AddChunk(CodeCompletionString::CK_RightParen); + }); + }; + auto AddCCast = [&]() { + AddResult("cast", PreferredType, [&](CodeCompletionBuilder &B) { + B.AddChunk(CodeCompletionString::CK_LeftParen); + B.AddTextChunk(PreferredTypeStr); + B.AddChunk(CodeCompletionString::CK_RightParen); + B.AddTextChunk(ExprCode); + }); + }; + if (Ctx.getLangOpts().CPlusPlus) { + if (Casts & Cast::Static) + AddCxxCast("static_cast"); + if (Casts & Cast::Dynamic) + AddCxxCast("dynamic_cast"); + if (Casts & Cast::Reinterpret) + AddCxxCast("reinterpret_cast"); + if (Casts & Cast::Bit) + AddCxxCast("bit_cast"); + if (Casts & Cast::C) + AddCCast(); + } else { + if (Casts & (Cast::Static | Cast::Reinterpret | Cast::C)) + AddCCast(); + } + } + } + + if (Base->getType()->isEnumeralType() && BaseIsStmt) { + EnumDecl *ED = + Base->getType()->getAs()->getDecl()->getDefinition(); + const char *Qualifier = nullptr; + DeclContext *QualifierDC = ED->isScoped() ? ED : ED->getDeclContext(); + if (const auto *NNS = getRequiredQualification( + Ctx, Results.getSema().CurContext, QualifierDC)) { + std::string Str; + llvm::raw_string_ostream OS(Str); + NNS->print(OS, Policy); + Qualifier = Results.getAllocator().CopyString(Str); + } + if (ED && !ED->enumerators().empty()) { + AddResult("switch", QualType(), [&](CodeCompletionBuilder &B) { + B.AddTextChunk("switch"); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftParen); + B.AddTextChunk(ExprCode); + B.AddChunk(CodeCompletionString::CK_RightParen); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftBrace); + B.AddChunk(CodeCompletionString::CK_VerticalSpace); + for (const auto *ECD : ED->enumerators()) { + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddTextChunk("case"); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + if (Qualifier) + B.AddTextChunk(Qualifier); + B.AddTextChunk(ECD->getIdentifier()->getNameStart()); + B.AddChunk(CodeCompletionString::CK_Colon); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddTextChunk("break"); + B.AddChunk(CodeCompletionString::CK_SemiColon); + B.AddChunk(CodeCompletionString::CK_VerticalSpace); + } + B.AddChunk(CodeCompletionString::CK_RightBrace); + }); + } + } + if (BaseIsStmt && isConvertibleToBool(Base->getType(), Ctx)) { + const char *VarType = conditionVariableType(Base, Ctx); + AddResult("if", QualType(), [&](CodeCompletionBuilder &B) { + B.AddTextChunk("if"); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftParen); + if (VarType) { + B.AddTextChunk(VarType); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddPlaceholderChunk("value"); + B.AddChunk(CodeCompletionString::CK_Equal); + } + B.AddTextChunk(ExprCode); + B.AddChunk(CodeCompletionString::CK_RightParen); + B.AddChunk(CodeCompletionString::CK_HorizontalSpace); + B.AddChunk(CodeCompletionString::CK_LeftBrace); + B.AddChunk(CodeCompletionString::CK_VerticalSpace); + B.AddChunk(CodeCompletionString::CK_RightBrace); + }); + } + // const cast +} + } // namespace void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, @@ -5556,10 +5960,11 @@ SourceLocation OpLoc, bool IsArrow, bool IsBaseExprStatement, QualType PreferredType) { - Base = unwrapParenList(Base); - OtherOpBase = unwrapParenList(OtherOpBase); if (!Base || !CodeCompleter) return; + SourceLocation BeginLoc = Base->getBeginLoc(); + Base = unwrapParenList(Base); + OtherOpBase = unwrapParenList(OtherOpBase); ExprResult ConvertedBase = PerformMemberExprBaseConversion(Base, IsArrow); if (ConvertedBase.isInvalid()) @@ -5590,6 +5995,10 @@ CodeCompleter->getCodeCompletionTUInfo(), CCContext, &ResultBuilder::IsMember); + if (CodeCompleter->includeFixIts() && CodeCompleter->includeCodePatterns()) + addPseudoMemberCompletions(Base, SourceRange(BeginLoc, OpLoc), Results, + IsBaseExprStatement, PreferredType); + auto DoCompletion = [&](Expr *Base, bool IsArrow, Optional AccessOpFixIt) -> bool { if (!Base)