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 | |
35 | static bool isReservedName(llvm::StringRef id) |
36 | { |
37 | if (id.size() < 2) |
38 | return false; |
39 | return id.startswith(Prefix: "__" ); |
40 | } |
41 | |
42 | static 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. |
52 | static 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, |
69 | class AggregateDesignatorNames |
70 | { |
71 | public: |
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 | |
131 | private: |
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 | |
140 | static 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 | |
193 | InlayHintsAnnotatorHelper::InlayHintsAnnotatorHelper(Annotator *annotator) |
194 | : SM(annotator->getSourceMgr()) |
195 | , mainFileID(SM.getMainFileID()) |
196 | { |
197 | } |
198 | |
199 | llvm::DenseMap<clang::SourceLocation, std::string> |
200 | InlayHintsAnnotatorHelper::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 % ": " ); |
228 | } |
229 | } |
230 | return designatorSpans; |
231 | } |
232 | |
233 | bool InlayHintsAnnotatorHelper::(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 | |
260 | std::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 ? "&" : llvm::StringRef(); |
287 | return paramName % refStr % ": " ; |
288 | } |
289 | |