#include "InlayHints.h"
#include "../clang-tidy/utils/DesignatedInitializers.h"
#include "AST.h"
#include "Config.h"
#include "HeuristicResolver.h"
#include "ParsedAST.h"
#include "SourceCode.h"
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclarationName.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <string>
namespace clang {
namespace clangd {
namespace {
enum class HintSide { Left, Right };
void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); }
template <typename Ty, typename = decltype(((Ty *)nullptr)->getDecl())>
const NamedDecl *getDeclForTypeImpl(const Ty *T) {
return T->getDecl();
}
const NamedDecl *getDeclForTypeImpl(const void *T) { return nullptr; }
const NamedDecl *getDeclForType(const Type *T) {
switch (T->getTypeClass()) {
#define ABSTRACT_TYPE(TY, BASE)
#define TYPE(TY, BASE) \
case Type::TY: \
return getDeclForTypeImpl(llvm::cast<TY##Type>(T));
#include "clang/AST/TypeNodes.inc"
}
llvm_unreachable("Unknown TypeClass enum");
}
llvm::StringRef getSimpleName(const DeclarationName &DN) {
if (IdentifierInfo *Ident = DN.getAsIdentifierInfo())
return Ident->getName();
return "";
}
llvm::StringRef getSimpleName(const NamedDecl &D) {
return getSimpleName(D.getDeclName());
}
llvm::StringRef getSimpleName(QualType T) {
if (const auto *ET = llvm::dyn_cast<ElaboratedType>(T))
return getSimpleName(ET->getNamedType());
if (const auto *BT = llvm::dyn_cast<BuiltinType>(T)) {
PrintingPolicy PP(LangOptions{});
PP.adjustForCPlusPlus();
return BT->getName(PP);
}
if (const auto *D = getDeclForType(T.getTypePtr()))
return getSimpleName(D->getDeclName());
return "";
}
std::string summarizeExpr(const Expr *E) {
struct Namer : ConstStmtVisitor<Namer, std::string> {
std::string Visit(const Expr *E) {
if (E == nullptr)
return "";
return ConstStmtVisitor::Visit(E->IgnoreImplicit());
}
std::string VisitMemberExpr(const MemberExpr *E) {
return getSimpleName(*E->getMemberDecl()).str();
}
std::string VisitDeclRefExpr(const DeclRefExpr *E) {
return getSimpleName(*E->getFoundDecl()).str();
}
std::string VisitCallExpr(const CallExpr *E) {
return Visit(E->getCallee());
}
std::string
VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *E) {
return getSimpleName(E->getMember()).str();
}
std::string
VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr *E) {
return getSimpleName(E->getDeclName()).str();
}
std::string VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *E) {
return getSimpleName(E->getType()).str();
}
std::string VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr *E) {
return getSimpleName(E->getType()).str();
}
std::string VisitCXXMemberCallExpr(const CXXMemberCallExpr *E) {
if (E->getNumArgs() == 0 && E->getMethodDecl() &&
E->getMethodDecl()->getDeclName().getNameKind() ==
DeclarationName::CXXConversionFunctionName &&
E->getSourceRange() ==
E->getImplicitObjectArgument()->getSourceRange())
return Visit(E->getImplicitObjectArgument());
return ConstStmtVisitor::VisitCXXMemberCallExpr(E);
}
std::string VisitCXXConstructExpr(const CXXConstructExpr *E) {
if (E->getNumArgs() == 1)
return Visit(E->getArg(0));
return "";
}
std::string VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E) {
return E->getValue() ? "true" : "false";
}
std::string VisitIntegerLiteral(const IntegerLiteral *E) {
return llvm::to_string(E->getValue());
}
std::string VisitFloatingLiteral(const FloatingLiteral *E) {
std::string Result;
llvm::raw_string_ostream OS(Result);
E->getValue().print(OS);
Result.resize(llvm::StringRef(Result).rtrim().size());
return Result;
}
std::string VisitStringLiteral(const StringLiteral *E) {
std::string Result = "\"";
if (E->containsNonAscii()) {
Result += "...";
} else if (E->getLength() > 10) {
Result += E->getString().take_front(7);
Result += "...";
} else {
llvm::raw_string_ostream OS(Result);
llvm::printEscapedString(E->getString(), OS);
}
Result.push_back('"');
return Result;
}
std::string printUnary(llvm::StringRef Spelling, const Expr *Operand,
bool Prefix) {
std::string Sub = Visit(Operand);
if (Sub.empty())
return "";
if (Prefix)
return (Spelling + Sub).str();
Sub += Spelling;
return Sub;
}
bool InsideBinary = false;
std::string printBinary(llvm::StringRef Spelling, const Expr *LHSOp,
const Expr *RHSOp) {
if (InsideBinary)
return "";
llvm::SaveAndRestore InBinary(InsideBinary, true);
std::string LHS = Visit(LHSOp);
std::string RHS = Visit(RHSOp);
if (LHS.empty() && RHS.empty())
return "";
if (LHS.empty())
LHS = "...";
LHS.push_back(' ');
LHS += Spelling;
LHS.push_back(' ');
if (RHS.empty())
LHS += "...";
else
LHS += RHS;
return LHS;
}
std::string VisitUnaryOperator(const UnaryOperator *E) {
return printUnary(E->getOpcodeStr(E->getOpcode()), E->getSubExpr(),
!E->isPostfix());
}
std::string VisitBinaryOperator(const BinaryOperator *E) {
return printBinary(E->getOpcodeStr(E->getOpcode()), E->getLHS(),
E->getRHS());
}
std::string VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *E) {
const char *Spelling = getOperatorSpelling(E->getOperator());
if ((E->getOperator() == OO_PlusPlus ||
E->getOperator() == OO_MinusMinus) &&
E->getNumArgs() == 2)
return printUnary(Spelling, E->getArg(0), false);
if (E->isInfixBinaryOp())
return printBinary(Spelling, E->getArg(0), E->getArg(1));
if (E->getNumArgs() == 1) {
switch (E->getOperator()) {
case OO_Plus:
case OO_Minus:
case OO_Star:
case OO_Amp:
case OO_Tilde:
case OO_Exclaim:
case OO_PlusPlus:
case OO_MinusMinus:
return printUnary(Spelling, E->getArg(0), true);
default:
break;
}
}
return "";
}
};
return Namer{}.Visit(E);
}
bool isSugaredTemplateParameter(QualType QT) {
static auto PeelWrapper = [](QualType QT) {
QualType Peeled = QT->getPointeeType();
return Peeled.isNull() ? QT : Peeled;
};
while (true) {
if (QT->getAs<SubstTemplateTypeParmType>())
return true;
QualType Desugared = QT->getLocallyUnqualifiedSingleStepDesugaredType();
if (Desugared != QT)
QT = Desugared;
else if (auto Peeled = PeelWrapper(Desugared); Peeled != QT)
QT = Peeled;
else
break;
}
return false;
}
std::optional<QualType> desugar(ASTContext &AST, QualType QT) {
bool ShouldAKA = false;
auto Desugared = clang::desugarForDiagnostic(AST, QT, ShouldAKA);
if (!ShouldAKA)
return std::nullopt;
return Desugared;
}
QualType maybeDesugar(ASTContext &AST, QualType QT) {
if (isSugaredTemplateParameter(QT))
return desugar(AST, QT).value_or(QT);
if (QT->isDecltypeType())
return QT.getCanonicalType();
if (const AutoType *AT = QT->getContainedAutoType())
if (!AT->getDeducedType().isNull() &&
AT->getDeducedType()->isDecltypeType())
return QT.getCanonicalType();
return QT;
}
static FunctionProtoTypeLoc getPrototypeLoc(Expr *Fn) {
TypeLoc Target;
Expr *NakedFn = Fn->IgnoreParenCasts();
if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
} else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
const auto *D = DR->getDecl();
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
Target = VD->getTypeSourceInfo()->getTypeLoc();
}
}
if (!Target)
return {};
while (true) {
if (auto P = Target.getAs<PointerTypeLoc>()) {
Target = P.getPointeeLoc();
continue;
}
if (auto A = Target.getAs<AttributedTypeLoc>()) {
Target = A.getModifiedLoc();
continue;
}
if (auto P = Target.getAs<ParenTypeLoc>()) {
Target = P.getInnerLoc();
continue;
}
break;
}
if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
return F;
}
return {};
}
ArrayRef<const ParmVarDecl *>
maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
if (!Params.empty() && Params.front()->isExplicitObjectParameter())
Params = Params.drop_front(1);
return Params;
}
struct Callee {
const FunctionDecl *Decl = nullptr;
FunctionProtoTypeLoc Loc;
};
class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
public:
InlayHintVisitor(std::vector<InlayHint> &Results, ParsedAST &AST,
const Config &Cfg, std::optional<Range> RestrictRange)
: Results(Results), AST(AST.getASTContext()), Tokens(AST.getTokens()),
Cfg(Cfg), RestrictRange(std::move(RestrictRange)),
MainFileID(AST.getSourceManager().getMainFileID()),
Resolver(AST.getHeuristicResolver()),
TypeHintPolicy(this->AST.getPrintingPolicy()) {
bool Invalid = false;
llvm::StringRef Buf =
AST.getSourceManager().getBufferData(MainFileID, &Invalid);
MainFileBuf = Invalid ? StringRef{} : Buf;
TypeHintPolicy.SuppressScope = true;
TypeHintPolicy.AnonymousTagLocations =
false;
}
bool VisitTypeLoc(TypeLoc TL) {
if (const auto *DT = llvm::dyn_cast<DecltypeType>(TL.getType()))
if (QualType UT = DT->getUnderlyingType(); !UT->isDependentType())
addTypeHint(TL.getSourceRange(), UT, ": ");
return true;
}
bool VisitCXXConstructExpr(CXXConstructExpr *E) {
if (!E->getParenOrBraceRange().isValid() ||
E->isStdInitListInitialization()) {
return true;
}
Callee Callee;
Callee.Decl = E->getConstructor();
if (!Callee.Decl)
return true;
processCall(Callee, {E->getArgs(), E->getNumArgs()});
return true;
}
bool TraversePseudoObjectExpr(PseudoObjectExpr *E) {
Expr *SyntacticExpr = E->getSyntacticForm();
if (isa<CallExpr>(SyntacticExpr))
return RecursiveASTVisitor<InlayHintVisitor>::TraverseStmt(SyntacticExpr);
if (isa<BinaryOperator>(SyntacticExpr))
return true;
return RecursiveASTVisitor<InlayHintVisitor>::TraversePseudoObjectExpr(E);
}
bool VisitCallExpr(CallExpr *E) {
if (!Cfg.InlayHints.Parameters)
return true;
bool IsFunctor = isFunctionObjectCallExpr(E);
if ((isa<CXXOperatorCallExpr>(E) && !IsFunctor) ||
isa<UserDefinedLiteral>(E))
return true;
auto CalleeDecls = Resolver->resolveCalleeOfCallExpr(E);
if (CalleeDecls.size() != 1)
return true;
Callee Callee;
if (const auto *FD = dyn_cast<FunctionDecl>(CalleeDecls[0]))
Callee.Decl = FD;
else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0]))
Callee.Decl = FTD->getTemplatedDecl();
else if (FunctionProtoTypeLoc Loc = getPrototypeLoc(E->getCallee()))
Callee.Loc = Loc;
else
return true;
llvm::ArrayRef<const Expr *> Args = {E->getArgs(), E->getNumArgs()};
if (const CXXMethodDecl *Method =
dyn_cast_or_null<CXXMethodDecl>(Callee.Decl))
if (IsFunctor || Method->hasCXXExplicitFunctionObjectParameter())
Args = Args.drop_front(1);
processCall(Callee, Args);
return true;
}
bool VisitFunctionDecl(FunctionDecl *D) {
if (auto *FPT =
llvm::dyn_cast<FunctionProtoType>(D->getType().getTypePtr())) {
if (!FPT->hasTrailingReturn()) {
if (auto FTL = D->getFunctionTypeLoc())
addReturnTypeHint(D, FTL.getRParenLoc());
}
}
if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {
if (const Stmt *Body = D->getBody())
addBlockEndHint(Body->getSourceRange(), "", printName(AST, *D), "");
}
return true;
}
bool VisitForStmt(ForStmt *S) {
if (Cfg.InlayHints.BlockEnd) {
std::string Name;
if (auto *DS = llvm::dyn_cast_or_null<DeclStmt>(S->getInit());
DS && DS->isSingleDecl())
Name = getSimpleName(llvm::cast<NamedDecl>(*DS->getSingleDecl()));
else
Name = summarizeExpr(S->getCond());
markBlockEnd(S->getBody(), "for", Name);
}
return true;
}
bool VisitCXXForRangeStmt(CXXForRangeStmt *S) {
if (Cfg.InlayHints.BlockEnd)
markBlockEnd(S->getBody(), "for", getSimpleName(*S->getLoopVariable()));
return true;
}
bool VisitWhileStmt(WhileStmt *S) {
if (Cfg.InlayHints.BlockEnd)
markBlockEnd(S->getBody(), "while", summarizeExpr(S->getCond()));
return true;
}
bool VisitSwitchStmt(SwitchStmt *S) {
if (Cfg.InlayHints.BlockEnd)
markBlockEnd(S->getBody(), "switch", summarizeExpr(S->getCond()));
return true;
}
llvm::DenseSet<const IfStmt *> ElseIfs;
bool VisitIfStmt(IfStmt *S) {
if (Cfg.InlayHints.BlockEnd) {
if (const auto *ElseIf = llvm::dyn_cast_or_null<IfStmt>(S->getElse()))
ElseIfs.insert(ElseIf);
if (const auto *EndCS = llvm::dyn_cast<CompoundStmt>(
S->getElse() ? S->getElse() : S->getThen())) {
addBlockEndHint(
{S->getThen()->getBeginLoc(), EndCS->getRBracLoc()}, "if",
ElseIfs.contains(S) ? "" : summarizeExpr(S->getCond()), "");
}
}
return true;
}
void markBlockEnd(const Stmt *Body, llvm::StringRef Label,
llvm::StringRef Name = "") {
if (const auto *CS = llvm::dyn_cast_or_null<CompoundStmt>(Body))
addBlockEndHint(CS->getSourceRange(), Label, Name, "");
}
bool VisitTagDecl(TagDecl *D) {
if (Cfg.InlayHints.BlockEnd && D->isThisDeclarationADefinition()) {
std::string DeclPrefix = D->getKindName().str();
if (const auto *ED = dyn_cast<EnumDecl>(D)) {
if (ED->isScoped())
DeclPrefix += ED->isScopedUsingClassTag() ? " class" : " struct";
};
addBlockEndHint(D->getBraceRange(), DeclPrefix, getSimpleName(*D), ";");
}
return true;
}
bool VisitNamespaceDecl(NamespaceDecl *D) {
if (Cfg.InlayHints.BlockEnd) {
addBlockEndHint(D->getSourceRange(), "namespace", getSimpleName(*D), "");
}
return true;
}
bool VisitLambdaExpr(LambdaExpr *E) {
FunctionDecl *D = E->getCallOperator();
if (!E->hasExplicitResultType())
addReturnTypeHint(D, E->hasExplicitParameters()
? D->getFunctionTypeLoc().getRParenLoc()
: E->getIntroducerRange().getEnd());
return true;
}
void addReturnTypeHint(FunctionDecl *D, SourceRange Range) {
auto *AT = D->getReturnType()->getContainedAutoType();
if (!AT || AT->getDeducedType().isNull())
return;
addTypeHint(Range, D->getReturnType(), "-> ");
}
bool VisitVarDecl(VarDecl *D) {
if (auto *DD = dyn_cast<DecompositionDecl>(D)) {
for (auto *Binding : DD->bindings()) {
if (auto Type = Binding->getType();
!Type.isNull() && !Type->isDependentType())
addTypeHint(Binding->getLocation(), Type.getCanonicalType(),
": ");
}
return true;
}
if (auto *AT = D->getType()->getContainedAutoType()) {
if (AT->isDeduced() && !D->getType()->isDependentType()) {
addTypeHint(D->getLocation(), D->getType(), ": ");
}
}
if (auto *PVD = llvm::dyn_cast<ParmVarDecl>(D)) {
if (D->getIdentifier() && PVD->getType()->isDependentType() &&
!getContainedAutoParamType(D->getTypeSourceInfo()->getTypeLoc())
.isNull()) {
if (auto *IPVD = getOnlyParamInstantiation(PVD))
addTypeHint(D->getLocation(), IPVD->getType(), ": ");
}
}
return true;
}
ParmVarDecl *getOnlyParamInstantiation(ParmVarDecl *D) {
auto *TemplateFunction = llvm::dyn_cast<FunctionDecl>(D->getDeclContext());
if (!TemplateFunction)
return nullptr;
auto *InstantiatedFunction = llvm::dyn_cast_or_null<FunctionDecl>(
getOnlyInstantiation(TemplateFunction));
if (!InstantiatedFunction)
return nullptr;
unsigned ParamIdx = 0;
for (auto *Param : TemplateFunction->parameters()) {
if (Param->isParameterPack())
return nullptr;
if (Param == D)
break;
++ParamIdx;
}
assert(ParamIdx < TemplateFunction->getNumParams() &&
"Couldn't find param in list?");
assert(ParamIdx < InstantiatedFunction->getNumParams() &&
"Instantiated function has fewer (non-pack) parameters?");
return InstantiatedFunction->getParamDecl(ParamIdx);
}
bool VisitInitListExpr(InitListExpr *Syn) {
assert(Syn->isSyntacticForm() && "RAV should not visit implicit code!");
if (!Cfg.InlayHints.Designators)
return true;
if (Syn->isIdiomaticZeroInitializer(AST.getLangOpts()))
return true;
llvm::DenseMap<SourceLocation, std::string> Designators =
tidy::utils::getUnwrittenDesignators(Syn);
for (const Expr *Init : Syn->inits()) {
if (llvm::isa<DesignatedInitExpr>(Init))
continue;
auto It = Designators.find(Init->getBeginLoc());
if (It != Designators.end() &&
!isPrecededByParamNameComment(Init, It->second))
addDesignatorHint(Init->getSourceRange(), It->second);
}
return true;
}
private:
using NameVec = SmallVector<StringRef, 8>;
void processCall(Callee Callee, llvm::ArrayRef<const Expr *> Args) {
assert(Callee.Decl || Callee.Loc);
if (!Cfg.InlayHints.Parameters || Args.size() == 0)
return;
if (Callee.Decl)
if (auto *Ctor = dyn_cast<CXXConstructorDecl>(Callee.Decl))
if (Ctor->isCopyOrMoveConstructor())
return;
ArrayRef<const ParmVarDecl *> Params, ForwardedParams;
SmallVector<const ParmVarDecl *> ForwardedParamsStorage;
if (Callee.Decl) {
Params = maybeDropCxxExplicitObjectParameters(Callee.Decl->parameters());
ForwardedParamsStorage = resolveForwardingParameters(Callee.Decl);
ForwardedParams =
maybeDropCxxExplicitObjectParameters(ForwardedParamsStorage);
} else {
Params = maybeDropCxxExplicitObjectParameters(Callee.Loc.getParams());
ForwardedParams = {Params.begin(), Params.end()};
}
NameVec ParameterNames = chooseParameterNames(ForwardedParams);
if (Callee.Decl &&
(isSetter(Callee.Decl, ParameterNames) || isSimpleBuiltin(Callee.Decl)))
return;
for (size_t I = 0; I < ParameterNames.size() && I < Args.size(); ++I) {
if (isa<PackExpansionExpr>(Args[I])) {
break;
}
StringRef Name = ParameterNames[I];
bool NameHint = shouldHintName(Args[I], Name);
bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]);
if (NameHint || ReferenceHint) {
addInlayHint(Args[I]->getSourceRange(), HintSide::Left,
InlayHintKind::Parameter, ReferenceHint ? "&" : "",
NameHint ? Name : "", ": ");
}
}
}
static bool isSetter(const FunctionDecl *Callee, const NameVec &ParamNames) {
if (ParamNames.size() != 1)
return false;
StringRef Name = getSimpleName(*Callee);
if (!Name.starts_with_insensitive("set"))
return false;
StringRef WhatItIsSetting = Name.substr(3).ltrim("_");
return WhatItIsSetting.equals_insensitive(ParamNames[0]);
}
static bool isSimpleBuiltin(const FunctionDecl *Callee) {
switch (Callee->getBuiltinID()) {
case Builtin::BIaddressof:
case Builtin::BIas_const:
case Builtin::BIforward:
case Builtin::BImove:
case Builtin::BImove_if_noexcept:
return true;
default:
return false;
}
}
bool shouldHintName(const Expr *Arg, StringRef ParamName) {
if (ParamName.empty())
return false;
if (ParamName == getSpelledIdentifier(Arg))
return false;
if (isPrecededByParamNameComment(Arg, ParamName))
return false;
return true;
}
bool shouldHintReference(const ParmVarDecl *Param,
const ParmVarDecl *ForwardedParam) {
auto Type = Param->getType();
auto ForwardedType = ForwardedParam->getType();
return Type->isLValueReferenceType() &&
ForwardedType->isLValueReferenceType() &&
!ForwardedType.getNonReferenceType().isConstQualified() &&
!isExpandedFromParameterPack(ForwardedParam);
}
bool isPrecededByParamNameComment(const Expr *E, StringRef ParamName) {
auto &SM = AST.getSourceManager();
auto FileLoc = SM.getFileLoc(E->getBeginLoc());
auto Decomposed = SM.getDecomposedLoc(FileLoc);
if (Decomposed.first != MainFileID)
return false;
StringRef SourcePrefix = MainFileBuf.substr(0, Decomposed.second);
SourcePrefix = SourcePrefix.rtrim();
if (!SourcePrefix.consume_back("*/"))
return false;
llvm::StringLiteral IgnoreChars = " =.";
SourcePrefix = SourcePrefix.rtrim(IgnoreChars);
ParamName = ParamName.trim(IgnoreChars);
if (!SourcePrefix.consume_back(ParamName))
return false;
SourcePrefix = SourcePrefix.rtrim(IgnoreChars);
return SourcePrefix.ends_with("/*");
}
static StringRef getSpelledIdentifier(const Expr *E) {
E = E->IgnoreUnlessSpelledInSource();
if (auto *DRE = dyn_cast<DeclRefExpr>(E))
if (!DRE->getQualifier())
return getSimpleName(*DRE->getDecl());
if (auto *ME = dyn_cast<MemberExpr>(E))
if (!ME->getQualifier() && ME->isImplicitAccess())
return getSimpleName(*ME->getMemberDecl());
return {};
}
NameVec chooseParameterNames(ArrayRef<const ParmVarDecl *> Parameters) {
NameVec ParameterNames;
for (const auto *P : Parameters) {
if (isExpandedFromParameterPack(P)) {
ParameterNames.emplace_back();
} else {
auto SimpleName = getSimpleName(*P);
if (SimpleName.empty()) {
if (const auto *PD = getParamDefinition(P)) {
SimpleName = getSimpleName(*PD);
}
}
ParameterNames.emplace_back(SimpleName);
}
}
for (auto &Name : ParameterNames)
stripLeadingUnderscores(Name);
return ParameterNames;
}
static const ParmVarDecl *getParamDefinition(const ParmVarDecl *P) {
if (auto *Callee = dyn_cast<FunctionDecl>(P->getDeclContext())) {
if (auto *Def = Callee->getDefinition()) {
auto I = std::distance(Callee->param_begin(),
llvm::find(Callee->parameters(), P));
if (I < (int)Callee->getNumParams()) {
return Def->getParamDecl(I);
}
}
}
return nullptr;
}
void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind,
llvm::StringRef Prefix, llvm::StringRef Label,
llvm::StringRef Suffix) {
auto LSPRange = getHintRange(R);
if (!LSPRange)
return;
addInlayHint(*LSPRange, Side, Kind, Prefix, Label, Suffix);
}
void addInlayHint(Range LSPRange, HintSide Side, InlayHintKind Kind,
llvm::StringRef Prefix, llvm::StringRef Label,
llvm::StringRef Suffix) {
assert(Cfg.InlayHints.Enabled && "Shouldn't get here if disabled!");
switch (Kind) {
#define CHECK_KIND(Enumerator, ConfigProperty) \
case InlayHintKind::Enumerator: \
assert(Cfg.InlayHints.ConfigProperty && \
"Shouldn't get here if kind is disabled!"); \
if (!Cfg.InlayHints.ConfigProperty) \
return; \
break
CHECK_KIND(Parameter, Parameters);
CHECK_KIND(Type, DeducedTypes);
CHECK_KIND(Designator, Designators);
CHECK_KIND(BlockEnd, BlockEnd);
#undef CHECK_KIND
}
Position LSPPos = Side == HintSide::Left ? LSPRange.start : LSPRange.end;
if (RestrictRange &&
(LSPPos < RestrictRange->start || !(LSPPos < RestrictRange->end)))
return;
bool PadLeft = Prefix.consume_front(" ");
bool PadRight = Suffix.consume_back(" ");
Results.push_back(InlayHint{LSPPos,
{(Prefix + Label + Suffix).str()},
Kind, PadLeft, PadRight, LSPRange});
}
std::optional<Range> getHintRange(SourceRange R) {
const auto &SM = AST.getSourceManager();
auto Spelled = Tokens.spelledForExpanded(Tokens.expandedTokens(R));
if (!Spelled || Spelled->empty())
return std::nullopt;
if (SM.getFileID(Spelled->front().location()) != SM.getMainFileID() ||
SM.getFileID(Spelled->back().location()) != SM.getMainFileID())
return std::nullopt;
return Range{sourceLocToPosition(SM, Spelled->front().location()),
sourceLocToPosition(SM, Spelled->back().endLocation())};
}
void addTypeHint(SourceRange R, QualType T, llvm::StringRef Prefix) {
if (!Cfg.InlayHints.DeducedTypes || T.isNull())
return;
auto Desugared = maybeDesugar(AST, T);
std::string TypeName = Desugared.getAsString(TypeHintPolicy);
if (T != Desugared && !shouldPrintTypeHint(TypeName)) {
TypeName = T.getAsString(TypeHintPolicy);
}
if (shouldPrintTypeHint(TypeName))
addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, TypeName,
"");
}
void addDesignatorHint(SourceRange R, llvm::StringRef Text) {
addInlayHint(R, HintSide::Left, InlayHintKind::Designator,
"", Text, "=");
}
bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept {
return Cfg.InlayHints.TypeNameLimit == 0 ||
TypeName.size() < Cfg.InlayHints.TypeNameLimit;
}
void addBlockEndHint(SourceRange BraceRange, StringRef DeclPrefix,
StringRef Name, StringRef OptionalPunctuation) {
auto HintRange = computeBlockEndHintRange(BraceRange, OptionalPunctuation);
if (!HintRange)
return;
std::string Label = DeclPrefix.str();
if (!Label.empty() && !Name.empty())
Label += ' ';
Label += Name;
constexpr unsigned HintMaxLengthLimit = 60;
if (Label.length() > HintMaxLengthLimit)
return;
addInlayHint(*HintRange, HintSide::Right, InlayHintKind::BlockEnd, " // ",
Label, "");
}
std::optional<Range> computeBlockEndHintRange(SourceRange BraceRange,
StringRef OptionalPunctuation) {
constexpr unsigned HintMinLineLimit = 2;
auto &SM = AST.getSourceManager();
auto [BlockBeginFileId, BlockBeginOffset] =
SM.getDecomposedLoc(SM.getFileLoc(BraceRange.getBegin()));
auto RBraceLoc = SM.getFileLoc(BraceRange.getEnd());
auto [RBraceFileId, RBraceOffset] = SM.getDecomposedLoc(RBraceLoc);
if (BlockBeginFileId != MainFileID || RBraceFileId != MainFileID)
return std::nullopt;
StringRef RestOfLine = MainFileBuf.substr(RBraceOffset).split('\n').first;
if (!RestOfLine.starts_with("}"))
return std::nullopt;
StringRef TrimmedTrailingText = RestOfLine.drop_front().trim();
if (!TrimmedTrailingText.empty() &&
TrimmedTrailingText != OptionalPunctuation)
return std::nullopt;
auto BlockBeginLine = SM.getLineNumber(BlockBeginFileId, BlockBeginOffset);
auto RBraceLine = SM.getLineNumber(RBraceFileId, RBraceOffset);
if (BlockBeginLine + HintMinLineLimit - 1 > RBraceLine)
return std::nullopt;
StringRef HintRangeText = RestOfLine.take_front(
TrimmedTrailingText.empty()
? 1
: TrimmedTrailingText.bytes_end() - RestOfLine.bytes_begin());
Position HintStart = sourceLocToPosition(SM, RBraceLoc);
Position HintEnd = sourceLocToPosition(
SM, RBraceLoc.getLocWithOffset(HintRangeText.size()));
return Range{HintStart, HintEnd};
}
static bool isFunctionObjectCallExpr(CallExpr *E) noexcept {
if (auto *CallExpr = dyn_cast<CXXOperatorCallExpr>(E))
return CallExpr->getOperator() == OverloadedOperatorKind::OO_Call;
return false;
}
std::vector<InlayHint> &Results;
ASTContext &AST;
const syntax::TokenBuffer &Tokens;
const Config &Cfg;
std::optional<Range> RestrictRange;
FileID MainFileID;
StringRef MainFileBuf;
const HeuristicResolver *Resolver;
PrintingPolicy TypeHintPolicy;
};
}
std::vector<InlayHint> inlayHints(ParsedAST &AST,
std::optional<Range> RestrictRange) {
std::vector<InlayHint> Results;
const auto &Cfg = Config::current();
if (!Cfg.InlayHints.Enabled)
return Results;
InlayHintVisitor Visitor(Results, AST, Cfg, std::move(RestrictRange));
Visitor.TraverseAST(AST.getASTContext());
llvm::sort(Results);
Results.erase(std::unique(Results.begin(), Results.end()), Results.end());
return Results;
}
}
}