1/****************************************************************************
2 * Copyright (C) 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
3 *
4 * This file is part of the Code Browser.
5 *
6 * Commercial License Usage:
7 * Licensees holding valid commercial licenses provided by KDAB may use
8 * this file in accordance with the terms contained in a written agreement
9 * between the licensee and KDAB.
10 * For further information see https://codebrowser.dev/
11 *
12 * Alternatively, this work may be used under a Creative Commons
13 * Attribution-NonCommercial-ShareAlike 3.0 (CC-BY-NC-SA 3.0) License.
14 * http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US
15 * This license does not allow you to use the code browser to assist the
16 * development of your commercial software. If you intent to do so, consider
17 * purchasing a commercial licence.
18 ***************************************************************************
19 * Parts of this code is derived from clangd InlayHints.cpp. License of the file:
20 *
21 * Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
22 * See https://llvm.org/LICENSE.txt for license information.
23 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
24 *******************************************************************************/
25#include "inlayhintannotator.h"
26#include "annotator.h"
27#include "stringbuilder.h"
28
29#include <clang/AST/ExprCXX.h>
30#include <clang/Basic/FileManager.h>
31#include <clang/Basic/SourceManager.h>
32#include <llvm/ADT/DenseMap.h>
33#include <llvm/ADT/DenseSet.h>
34
35static bool isReservedName(llvm::StringRef id)
36{
37 if (id.size() < 2)
38 return false;
39 return id.startswith(Prefix: "__");
40}
41
42static llvm::StringRef getSimpleName(const clang::NamedDecl *d)
43{
44 if (clang::IdentifierInfo *Ident = d->getDeclName().getAsIdentifierInfo()) {
45 return Ident->getName();
46 }
47 return llvm::StringRef();
48}
49
50// If "E" spells a single unqualified identifier, return that name.
51// Otherwise, return an empty string.
52static llvm::StringRef getSpelledIdentifier(const clang::Expr *e)
53{
54 e = e->IgnoreUnlessSpelledInSource();
55
56 if (auto *DRE = llvm::dyn_cast<clang::DeclRefExpr>(Val: e))
57 if (!DRE->getQualifier())
58 return getSimpleName(d: DRE->getDecl());
59
60 if (auto *ME = llvm::dyn_cast<clang::MemberExpr>(Val: e))
61 if (!ME->getQualifier() && ME->isImplicitAccess())
62 return getSimpleName(d: ME->getMemberDecl());
63
64 return {};
65}
66
67// Helper class to iterate over the designator names of an aggregate type.
68// For aggregate classes, yields null for each base, then .field1, .field2,
69class AggregateDesignatorNames
70{
71public:
72 AggregateDesignatorNames(clang::QualType type)
73 {
74 type = type.getCanonicalType();
75 if (const clang::RecordDecl *RD = type->getAsRecordDecl()) {
76 valid = true;
77 fieldsIt = RD->field_begin();
78 fieldsEnd = RD->field_end();
79 if (const auto *CRD = llvm::dyn_cast<clang::CXXRecordDecl>(Val: RD)) {
80 basesIt = CRD->bases_begin();
81 basesEnd = CRD->bases_end();
82 valid = CRD->isAggregate();
83 }
84 oneField = valid && basesIt == basesEnd && fieldsIt != fieldsEnd
85 && std::next(x: fieldsIt) == fieldsEnd;
86 }
87 }
88 // Returns false if the type was not an aggregate.
89 operator bool() const
90 {
91 return valid;
92 }
93 // Advance to the next element in the aggregate.
94 void next()
95 {
96 if (basesIt != basesEnd)
97 ++basesIt;
98 else if (fieldsIt != fieldsEnd)
99 ++fieldsIt;
100 }
101 // Print the designator to Out.
102 // Returns false if we could not produce a designator for this element.
103 bool append(std::string &out, bool forSubobject)
104 {
105 if (basesIt != basesEnd)
106 return false; // Bases can't be designated. Should we make one
107 // up?
108 if (fieldsIt != fieldsEnd) {
109 llvm::StringRef fieldName;
110 if (const clang::IdentifierInfo *ii = fieldsIt->getIdentifier())
111 fieldName = ii->getName();
112
113 // For certain objects, their subobjects may be named directly.
114 if (forSubobject
115 && (fieldsIt->isAnonymousStructOrUnion() ||
116 // std::array<int,3> x = {1,2,3}.
117 // Designators not strictly valid!
118 (oneField && isReservedName(id: fieldName))))
119 return true;
120
121 if (!fieldName.empty() && !isReservedName(id: fieldName)) {
122 out.push_back(c: '.');
123 out.append(first: fieldName.begin(), last: fieldName.end());
124 return true;
125 }
126 return false;
127 }
128 return false;
129 }
130
131private:
132 bool valid = false;
133 bool oneField = false; // e.g. std::array { T __elements[N]; }
134 clang::CXXRecordDecl::base_class_const_iterator basesIt;
135 clang::CXXRecordDecl::base_class_const_iterator basesEnd;
136 clang::RecordDecl::field_iterator fieldsIt;
137 clang::RecordDecl::field_iterator fieldsEnd;
138};
139
140static void collectDesignators(const clang::InitListExpr *sem,
141 llvm::DenseMap<clang::SourceLocation, std::string> &out,
142 const llvm::DenseSet<clang::SourceLocation> &nestedBraces,
143 std::string &prefix)
144{
145 if (!sem || sem->isTransparent() || !sem->isSemanticForm())
146 return;
147 // Nothing to do for arrays
148 if (sem->getType().isNull() || sem->getType().getCanonicalType()->isArrayType())
149 return;
150
151 // The elements of the semantic form all correspond to direct subobjects
152 // of the aggregate type. `Fields` iterates over these subobject names.
153 AggregateDesignatorNames fields(sem->getType());
154 if (!fields)
155 return;
156
157 for (const clang::Expr *init : sem->inits()) {
158 struct RAII
159 {
160 AggregateDesignatorNames &fields;
161 std::string &prefix;
162 const int size = prefix.size();
163 ~RAII()
164 {
165 fields.next(); // Always advance to the next subobject name.
166 prefix.resize(n: size); // Erase any designator we appended.
167 }
168 };
169 auto next = RAII { .fields: fields, .prefix: prefix };
170 // Skip for a broken initializer or if it is a "hole" in a subobject
171 // that was not explicitly initialized.
172 if (!init || llvm::isa<clang::ImplicitValueInitExpr>(Val: init))
173 continue;
174
175 const auto *braceElidedSubobject = llvm::dyn_cast<clang::InitListExpr>(Val: init);
176 if (braceElidedSubobject && nestedBraces.contains(V: braceElidedSubobject->getLBraceLoc()))
177 braceElidedSubobject = nullptr; // there were braces!
178
179 if (!fields.append(out&: prefix, forSubobject: braceElidedSubobject != nullptr))
180 continue; // no designator available for this subobject
181 if (braceElidedSubobject) {
182 // If the braces were elided, this aggregate subobject is
183 // initialized inline in the same syntactic list. Descend into
184 // the semantic list describing the subobject. (NestedBraces are
185 // still correct, they're from the same syntactic list).
186 collectDesignators(sem: braceElidedSubobject, out, nestedBraces, prefix);
187 continue;
188 }
189 out.try_emplace(Key: init->getBeginLoc(), Args&: prefix);
190 }
191}
192
193InlayHintsAnnotatorHelper::InlayHintsAnnotatorHelper(Annotator *annotator)
194 : SM(annotator->getSourceMgr())
195 , mainFileID(SM.getMainFileID())
196{
197}
198
199llvm::DenseMap<clang::SourceLocation, std::string>
200InlayHintsAnnotatorHelper::getDesignatorInlayHints(clang::InitListExpr *Syn)
201{
202 // collectDesignators needs to know which InitListExprs in the semantic
203 // tree were actually written, but InitListExpr::isExplicit() lies.
204 // Instead, record where braces of sub-init-lists occur in the syntactic
205 // form.
206 llvm::DenseSet<clang::SourceLocation> nestedBraces;
207 for (const clang::Expr *init : Syn->inits())
208 if (auto *nested = llvm::dyn_cast<clang::InitListExpr>(Val: init))
209 nestedBraces.insert(V: nested->getLBraceLoc());
210
211 // Traverse the semantic form to find the designators.
212 // We use their SourceLocation to correlate with the syntactic form
213 // later.
214 llvm::DenseMap<clang::SourceLocation, std::string> designators;
215 std::string emptyPrefix;
216 collectDesignators(sem: Syn->isSemanticForm() ? Syn : Syn->getSemanticForm(), out&: designators,
217 nestedBraces, prefix&: emptyPrefix);
218
219 llvm::StringRef Buf = SM.getBufferData(FID: mainFileID);
220 llvm::DenseMap<clang::SourceLocation, std::string> designatorSpans;
221
222 for (const clang::Expr *init : Syn->inits()) {
223 if (llvm::isa<clang::DesignatedInitExpr>(Val: init))
224 continue;
225 auto it = designators.find(Val: init->getBeginLoc());
226 if (it != designators.end() && !isPrecededByParamNameComment(E: init, ParamName: it->second, mainFileBuf: Buf)) {
227 designatorSpans.try_emplace(Key: init->getBeginLoc(), Args: it->second % ":&nbsp;");
228 }
229 }
230 return designatorSpans;
231}
232
233bool InlayHintsAnnotatorHelper::isPrecededByParamNameComment(const clang::Expr *E,
234 llvm::StringRef paramName,
235 llvm::StringRef mainFileBuf)
236{
237 auto exprStartLoc = SM.getTopMacroCallerLoc(Loc: E->getBeginLoc());
238 auto decomposed = SM.getDecomposedLoc(Loc: exprStartLoc);
239 if (decomposed.first != mainFileID)
240 return false;
241
242 llvm::StringRef sourcePrefix = mainFileBuf.substr(Start: 0, N: decomposed.second);
243 // Allow whitespace between comment and expression.
244 sourcePrefix = sourcePrefix.rtrim();
245 // Check for comment ending.
246 if (!sourcePrefix.consume_back(Suffix: "*/"))
247 return false;
248 // Ignore some punctuation and whitespace around comment.
249 // In particular this allows designators to match nicely.
250 llvm::StringLiteral ignoreChars = " =.";
251 sourcePrefix = sourcePrefix.rtrim(Chars: ignoreChars);
252 paramName = paramName.trim(Chars: ignoreChars);
253 // Other than that, the comment must contain exactly ParamName.
254 if (!sourcePrefix.consume_back(Suffix: paramName))
255 return false;
256 sourcePrefix = sourcePrefix.rtrim(Chars: ignoreChars);
257 return sourcePrefix.endswith(Suffix: "/*");
258}
259
260std::string InlayHintsAnnotatorHelper::getParamNameInlayHint(clang::CallExpr *e,
261 clang::ParmVarDecl *paramDecl,
262 clang::Expr *arg)
263{
264 if (e->isCallToStdMove() || llvm::isa<clang::UserDefinedLiteral>(Val: e)
265 || llvm::isa<clang::CXXOperatorCallExpr>(Val: e))
266 return {};
267
268 auto f = e->getDirectCallee();
269 if (!f)
270 return {};
271
272 // simple setter? => ignore
273 if (f->getNumParams() == 1 && getSimpleName(d: f).startswith_insensitive(Prefix: "set"))
274 return {};
275
276 llvm::StringRef paramName = getSimpleName(d: paramDecl);
277 if (paramName.empty() || paramName == getSpelledIdentifier(e: arg))
278 return {};
279
280 paramName = paramName.ltrim(Char: '_');
281 if (isPrecededByParamNameComment(E: arg, paramName, mainFileBuf: SM.getBufferData(FID: mainFileID)))
282 return {};
283
284 auto t = paramDecl->getType();
285 bool isLValueRef = t->isLValueReferenceType() && !t.getNonReferenceType().isConstQualified();
286 llvm::StringRef refStr = isLValueRef ? "&amp;" : llvm::StringRef();
287 return paramName % refStr % ":&nbsp;";
288}
289

source code of codebrowser/generator/inlayhintannotator.cpp