Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
clangcodeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
6
7#include "access.h"
8#include "classnode.h"
9#include "config.h"
10#include "doc.h"
11#include "enumnode.h"
12#include "functionnode.h"
13#include "genustypes.h"
15#include "namespacenode.h"
16#include "propertynode.h"
17#include "qdocdatabase.h"
18#include "typedefnode.h"
19#include "variablenode.h"
21#include "utilities.h"
22
23#include <QtCore/qdebug.h>
24#include <QtCore/qdir.h>
25#include <QtCore/qelapsedtimer.h>
26#include <QtCore/qfile.h>
27#include <QtCore/qregularexpression.h>
28#include <QtCore/qscopedvaluerollback.h>
29#include <QtCore/qtemporarydir.h>
30#include <QtCore/qtextstream.h>
31#include <QtCore/qvarlengtharray.h>
32
33#include <clang-c/Index.h>
34
35#include <clang/AST/ASTConcept.h>
36#include <clang/AST/Decl.h>
37#include <clang/AST/DeclFriend.h>
38#include <clang/AST/DeclTemplate.h>
39#include <clang/AST/Expr.h>
40#include <clang/AST/ExprConcepts.h>
41#include <clang/AST/Type.h>
42#include <clang/AST/TypeLoc.h>
43#include <clang/Basic/SourceLocation.h>
44#include <clang/Frontend/ASTUnit.h>
45#include <clang/Lex/Lexer.h>
46#include <llvm/Support/Casting.h>
47
48#include "clang/AST/QualTypeNames.h"
50
51#include <algorithm>
52#include <cstdio>
53#include <optional>
54#include <string_view>
55
56QT_BEGIN_NAMESPACE
57
58using namespace Qt::Literals::StringLiterals;
59
61 CXIndex index = nullptr;
62
63 operator CXIndex() {
64 return index;
65 }
66
68 clang_disposeIndex(index);
69 }
70};
71
73 CXTranslationUnit tu = nullptr;
74
75 operator CXTranslationUnit() {
76 return tu;
77 }
78
79 operator bool() {
80 return tu;
81 }
82
84 clang_disposeTranslationUnit(tu);
85 }
86};
87
88// We're printing diagnostics in ClangCodeParser::printDiagnostics,
89// so avoid clang itself printing them.
90static const auto kClangDontDisplayDiagnostics = 0;
91
92static CXTranslationUnit_Flags flags_ = static_cast<CXTranslationUnit_Flags>(0);
93
94constexpr const char fnDummyFileName[] = "/fn_dummyfile.cpp";
95
96#ifndef QT_NO_DEBUG_STREAM
97template<class T>
98static QDebug operator<<(QDebug debug, const std::vector<T> &v)
99{
100 QDebugStateSaver saver(debug);
101 debug.noquote();
102 debug.nospace();
103 const size_t size = v.size();
104 debug << "std::vector<>[" << size << "](";
105 for (size_t i = 0; i < size; ++i) {
106 if (i)
107 debug << ", ";
108 debug << v[i];
109 }
110 debug << ')';
111 return debug;
112}
113#endif // !QT_NO_DEBUG_STREAM
114
115static void printDiagnostics(const CXTranslationUnit &translationUnit)
116{
117 if (!lcQdocClang().isDebugEnabled())
118 return;
119
120 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
121 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
122 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
123
124 for (unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
125 auto diagnostic = clang_getDiagnostic(translationUnit, i);
126 auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
127 qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
128 clang_disposeString(formattedDiagnostic);
129 clang_disposeDiagnostic(diagnostic);
130 }
131}
132
133/*!
134 * Returns the underlying Decl that \a cursor represents.
135 *
136 * This can be used to drop back down from a LibClang's CXCursor to
137 * the underlying C++ AST that Clang provides.
138 *
139 * It should be used when LibClang does not expose certain
140 * functionalities that are available in the C++ AST.
141 *
142 * The CXCursor should represent a declaration. Usages of this
143 * function on CXCursors that do not represent a declaration may
144 * produce undefined results.
145 */
146static const clang::Decl* get_cursor_declaration(CXCursor cursor) {
147 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
148
149 return static_cast<const clang::Decl*>(cursor.data[0]);
150}
151
152
153/*!
154 * Returns a string representing the name of \a type as if it was
155 * referred to at the end of the translation unit that it was parsed
156 * from.
157 *
158 * For example, given the following code:
159 *
160 * \code
161 * namespace foo {
162 * template<typename T>
163 * struct Bar {
164 * using Baz = const T&;
165 *
166 * void bam(Baz);
167 * };
168 * }
169 * \endcode
170 *
171 * Given a parsed translation unit and an AST node, say \e {decl},
172 * representing the parameter declaration of the first argument of \c {bam},
173 * calling \c{get_fully_qualified_name(decl->getType(), * decl->getASTContext())}
174 * would result in the string \c {foo::Bar<T>::Baz}.
175 *
176 * This should generally be used every time the stringified
177 * representation of a type is acquired as part of parsing with Clang,
178 * so as to ensure a consistent behavior and output.
179 */
180/*
181 * Ensures that bare "(unnamed)" or "(anonymous)" markers in \a typeName
182 * include the record keyword (struct, union, class). With
183 * AnonymousTagLocations disabled, some LLVM versions omit the keyword
184 * for some or all anonymous scopes. This function recovers the correct
185 * keyword for each scope from the RecordDecl hierarchy.
186 *
187 * Only anonymous record types produce scope components in fully qualified
188 * names — anonymous enums don't create "(unnamed enum)::" segments
189 * because their enumerators are injected into the enclosing scope.
190 *
191 * For nested anonymous records such as "(unnamed)::(unnamed)" where the
192 * outer scope is a union and the inner is a struct, each marker receives
193 * its own keyword. The parent walk follows only RecordDecl contexts,
194 * which is sufficient because only anonymous records produce these
195 * scope components in Clang's fully qualified name output.
196 *
197 * The function assumes Clang produces structurally well-formed anonymous
198 * markers: either bare "(unnamed)" or with a keyword "(unnamed struct)".
199 * Malformed spellings would silently consume a keyword entry.
200 */
201static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
202{
203 const clang::RecordType *rt = type->getAs<clang::RecordType>();
204 if (!rt)
205 return typeName;
206
207 // Collect keywords from innermost to outermost anonymous scope.
208 std::vector<std::string> keywords;
209 const clang::RecordDecl *decl = rt->getDecl();
210 while (decl) {
211 if (decl->getDeclName().isEmpty())
212 keywords.emplace_back(decl->getKindName());
213 const auto *parent = llvm::dyn_cast<clang::RecordDecl>(decl->getDeclContext());
214 decl = parent;
215 }
216 // Reverse so index 0 is the outermost anonymous scope,
217 // matching left-to-right marker order in the type string.
218 std::reverse(keywords.begin(), keywords.end());
219
220 // Scan left-to-right for "(unnamed" / "(anonymous" prefixes.
221 // Each prefix corresponds to one anonymous scope in the keyword list.
222 // Some LLVM versions already include the keyword (e.g., "(unnamed union)")
223 // while others produce bare "(unnamed)". Only inject when missing.
224 static constexpr std::string_view prefixes[] = { "(unnamed", "(anonymous" };
225 size_t keywordIndex = 0;
226 size_t pos = 0;
227 while (pos < typeName.size() && keywordIndex < keywords.size()) {
228 std::string_view foundPrefix;
229 size_t foundPos = std::string::npos;
230 for (auto prefix : prefixes) {
231 size_t p = typeName.find(prefix, pos);
232 if (p < foundPos) {
233 foundPos = p;
234 foundPrefix = prefix;
235 }
236 }
237 if (foundPos == std::string::npos)
238 break;
239
240 size_t afterPrefix = foundPos + foundPrefix.size();
241 if (afterPrefix < typeName.size() && typeName[afterPrefix] == ')') {
242 // Bare marker — inject the keyword before ')'.
243 typeName.insert(afterPrefix, " " + keywords[keywordIndex]);
244 pos = afterPrefix + 1 + keywords[keywordIndex].size() + 1;
245 } else {
246 // Already has a keyword — skip past the closing ')'.
247 size_t closePos = typeName.find(')', afterPrefix);
248 pos = (closePos != std::string::npos) ? closePos + 1 : afterPrefix;
249 }
250 ++keywordIndex;
251 }
252 return typeName;
253}
254
255static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext& declaration_context) {
256 auto policy = declaration_context.getPrintingPolicy();
257 policy.AnonymousTagLocations = false;
258 std::string result = clang::TypeName::getFullyQualifiedName(type, declaration_context, policy);
259 return ensureAnonymousTagKeyword(std::move(result), type);
260}
261
262/*
263 * Normalizes anonymous type names in strings that do not come through
264 * get_fully_qualified_type_name(), such as cursor spelling results.
265 * Strips file-path locations from anonymous type names, transforming
266 * patterns such as "(unnamed struct at /path/file.h:67)" into
267 * "(unnamed struct)". The single-word token between the marker and
268 * " at " is preserved as-is — this is intentionally broader than
269 * just C++ record keywords so that any Clang spelling passes through
270 * without an exhaustive keyword list.
271 */
272static QString cleanAnonymousTypeName(const QString &typeName) {
273 if (!typeName.contains("(unnamed "_L1) && !typeName.contains("(anonymous "_L1))
274 return typeName;
275
276 static const QRegularExpression pattern(
277 R"(\‍((unnamed|anonymous)(\s+\w+)\s+at\s+[^)]+\‍))"
278 );
279 QString cleaned = typeName;
280 cleaned.replace(pattern, "(\\1\\2)"_L1);
281 return cleaned;
282}
283
284/*
285 * Retrieves expression as written in the original source code.
286 *
287 * declaration_context should be the ASTContext of the declaration
288 * from which the expression was extracted from.
289 *
290 * If the expression contains a leading equal sign it will be removed.
291 *
292 * Leading and trailing spaces will be similarly removed from the expression.
293 */
294static std::string get_expression_as_string(const clang::Expr* expression, const clang::ASTContext& declaration_context) {
295 QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
296 clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
297 declaration_context.getSourceManager(),
298 declaration_context.getLangOpts()
299 ).str());
300
301 if (default_value.startsWith("="))
302 default_value.remove(0, 1);
303
304 default_value = default_value.trimmed();
305
306 return default_value.toStdString();
307}
308
309/*
310 * Recursively walks a constraint expression and collects the fully-qualified
311 * name of every concept referenced by a ConceptSpecializationExpr in the
312 * subtree.
313 *
314 * The walker shares the AST traversal context that the existing requires-clause
315 * extraction already runs through, so the cost is one extra recursive descent
316 * per constrained item — no second visitor and no second translation-unit pass.
317 */
318static void collect_concept_references(const clang::Stmt *node,
319 std::vector<std::string> &out)
320{
321 if (!node)
322 return;
323 if (const auto *cse = llvm::dyn_cast<clang::ConceptSpecializationExpr>(node)) {
324 if (const auto *concept_decl = cse->getNamedConcept())
325 out.push_back(concept_decl->getQualifiedNameAsString());
326 }
327 for (const clang::Stmt *child : node->children())
328 collect_concept_references(child, out);
329}
330
331/*
332 * Retrieves the default value of the passed in type template parameter as a string.
333 *
334 * The default value of a type template parameter is always a type,
335 * and its stringified representation will be return as the fully
336 * qualified version of the type.
337 *
338 * If the parameter has no default value the empty string will be returned.
339 */
340static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl* parameter) {
341#if LIBCLANG_VERSION_MAJOR >= 19
342 return (parameter && parameter->hasDefaultArgument()) ?
343 get_fully_qualified_type_name(parameter->getDefaultArgument().getArgument().getAsType(), parameter->getASTContext()) :
344 "";
345#else
346 return (parameter && parameter->hasDefaultArgument()) ?
347 get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
348 "";
349#endif
350
351}
352
353/*
354 * Retrieves the default value of the passed in non-type template parameter as a string.
355 *
356 * The default value of a non-type template parameter is an expression
357 * and its stringified representation will be return as it was written
358 * in the original code.
359 *
360 * If the parameter as no default value the empty string will be returned.
361 */
362static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl* parameter) {
363#if LIBCLANG_VERSION_MAJOR >= 19
364 return (parameter && parameter->hasDefaultArgument()) ?
365 get_expression_as_string(parameter->getDefaultArgument().getSourceExpression(), parameter->getASTContext()) : "";
366#else
367 return (parameter && parameter->hasDefaultArgument()) ?
368 get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) : "";
369#endif
370
371}
372
373/*
374 * Retrieves the default value of the passed in template template parameter as a string.
375 *
376 * The default value of a template template parameter is a template
377 * name and its stringified representation will be returned as a fully
378 * qualified version of that name.
379 *
380 * If the parameter as no default value the empty string will be returned.
381 */
382static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl* parameter) {
383 std::string default_value{};
384
385 if (parameter && parameter->hasDefaultArgument()) {
386 const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
387
388 llvm::raw_string_ostream ss{default_value};
389 template_name.print(ss, parameter->getASTContext().getPrintingPolicy(), clang::TemplateName::Qualified::AsWritten);
390 }
391
392 return default_value;
393}
394
395/*
396 * Retrieves the default value of the passed in function parameter as
397 * a string.
398 *
399 * The default value of a function parameter is an expression and its
400 * stringified representation will be returned as it was written in
401 * the original code.
402 *
403 * If the parameter as no default value or Clang was not able to yet
404 * parse it at this time the empty string will be returned.
405 */
406static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl* parameter) {
407 if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
408 return "";
409
410 return get_expression_as_string(
411 parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
412 parameter->getASTContext()
413 );
414}
415
416/*
417 * Retrieves the default value of the passed in declaration, based on
418 * its concrete type, as a string.
419 *
420 * If the declaration is a nullptr or the concrete type of the
421 * declaration is not a supported one, the returned string will be the
422 * empty string.
423 */
424static std::string get_default_value_initializer_as_string(const clang::NamedDecl* declaration) {
425 if (!declaration) return "";
426
427 if (auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
428 return get_default_value_initializer_as_string(type_template_parameter);
429
430 if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
431 return get_default_value_initializer_as_string(non_type_template_parameter);
432
433 if (auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
434 return get_default_value_initializer_as_string(template_template_parameter);
435 }
436
437 if (auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
438 return get_default_value_initializer_as_string(function_parameter);
439 }
440
441 return "";
442}
443
444/*!
445 Call clang_visitChildren on the given cursor with the lambda as a callback
446 T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult
447 (in other word compatible with function<CXChildVisitResult(CXCursor)>
448 */
449template<typename T>
450bool visitChildrenLambda(CXCursor cursor, T &&lambda)
451{
452 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
453 CXClientData client_data) -> CXChildVisitResult {
454 return (*static_cast<T *>(client_data))(c);
455 };
456 return clang_visitChildren(cursor, visitor, &lambda);
457}
458
459/*!
460 convert a CXString to a QString, and dispose the CXString
461 */
462static QString fromCXString(CXString &&string)
463{
464 QString ret = QString::fromUtf8(clang_getCString(string));
465 clang_disposeString(string);
466 return ret;
467}
468
469/*
470 * Unwraps ElaboratedType (LLVM <= 21 only) to find the first
471 * TemplateSpecializationType at or just below the given type.
472 *
473 * This does not perform a general desugar walk. It handles the
474 * specific sugar shape that Clang produces for type alias template
475 * specializations used in Qt SFINAE patterns.
476 */
477static const clang::TemplateSpecializationType *find_template_specialization_through_sugar(
478 const clang::Type *type)
479{
480 // Qt's deepest SFINAE alias nesting is 2–3 levels. The limit
481 // guards against pathological types that could loop indefinitely.
482 for (int depth = 0; depth < 10 && type; ++depth) {
483 if (auto *tst = llvm::dyn_cast<clang::TemplateSpecializationType>(type))
484 return tst;
485
486#if LIBCLANG_VERSION_MAJOR < 22
487 // LLVM <= 21 wraps TemplateSpecializationType in ElaboratedType
488 if (auto *elaborated = llvm::dyn_cast<clang::ElaboratedType>(type)) {
489 type = elaborated->getNamedType().getTypePtr();
490 continue;
491 }
492#endif
493
494 // Not a type we can unwrap further
495 break;
496 }
497
498 return nullptr;
499}
500
501/*
502 * Returns true if the given qualified name ends with "enable_if"
503 * or "enable_if_t".
504 */
505static bool is_enable_if_name(const std::string &qualified_name)
506{
507 auto ends_with = [](const std::string &str, const std::string &suffix) {
508 return str.size() >= suffix.size()
509 && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
510 };
511
512 return ends_with(qualified_name, "enable_if_t")
513 || ends_with(qualified_name, "enable_if");
514}
515
516/*
517 * Detects whether a non-type template parameter encodes a SFINAE
518 * constraint via std::enable_if_t.
519 *
520 * Qt uses SFINAE constraints as unnamed non-type template parameters
521 * with a default value of true, where the parameter type is a
522 * type alias that resolves through enable_if_t. For example:
523 *
524 * template <typename T, if_integral<T> = true>
525 *
526 * where if_integral<T> is an alias for
527 * std::enable_if_t<std::is_integral_v<T>, bool>.
528 *
529 * Detection targets unnamed NTTPs specifically. Named non-type
530 * template parameters are not treated as SFINAE constraints, even
531 * if their type resolves through enable_if_t, because named
532 * parameters carry explicit meaning that should be preserved in
533 * the rendered signature. A default value is not required — \fn
534 * commands often omit the "= true" default.
535 *
536 * After finding the outermost TemplateSpecializationType (unwrapping
537 * ElaboratedType on LLVM <= 21), the detection desugars inward to
538 * verify that enable_if or enable_if_t appears in the chain.
539 */
541 const clang::NonTypeTemplateParmDecl *param)
542{
543 if (!param->getName().empty())
544 return std::nullopt;
545
546 auto policy = param->getASTContext().getPrintingPolicy();
547
548 const clang::Type *type = param->getType().getTypePtr();
549
551 if (!alias_type) {
552 // Heuristic fallback for dependent nested-alias cases. When
553 // the outer template parameter is dependent, Clang represents
554 // the type as DependentNameType rather than
555 // TemplateSpecializationType, so the sugar chain cannot be
556 // walked to verify enable_if. For example:
557 //
558 // template <class T>
559 // template <typename X, QPointer<T>::if_convertible<X> = true>
560 //
561 // Clang cannot resolve QPointer<T>::if_convertible<X> because
562 // T is dependent. The fallback requires both a default value
563 // (SFINAE parameters always have one — the caller never
564 // provides the argument) and angle brackets in the printed
565 // type name (indicating a template specialization applied to
566 // type parameters).
567 if (!param->hasDefaultArgument())
568 return std::nullopt;
569
570 std::string type_name = param->getType().getAsString(policy);
571 if (type_name.find('<') != std::string::npos)
572 return SfinaeConstraint{ std::move(type_name) };
573
574 return std::nullopt;
575 }
576
577 auto *alias_decl = alias_type->getTemplateName().getAsTemplateDecl();
578 if (!alias_decl)
579 return std::nullopt;
580
581 // Walk the sugar chain to verify enable_if / enable_if_t is present
582 bool found_enable_if = false;
583 const clang::Type *sugar = alias_type->desugar().getTypePtr();
584
585 for (int depth = 0; depth < 10 && sugar; ++depth) {
587 if (!tst)
588 break;
589
590 if (auto *decl = tst->getTemplateName().getAsTemplateDecl()) {
591 if (is_enable_if_name(decl->getQualifiedNameAsString())) {
592 found_enable_if = true;
593 break;
594 }
595 }
596
597 sugar = tst->desugar().getTypePtr();
598 }
599
600 if (!found_enable_if)
601 return std::nullopt;
602
603 // Print from the original QualType (not the unwrapped TST) to
604 // preserve scope qualification. On LLVM <= 21 the ElaboratedType
605 // sugar carries the namespace qualifier; on LLVM 22+ the qualifier
606 // is embedded in the type name directly. The printed output is
607 // the same either way.
608 return SfinaeConstraint{
609 param->getType().getAsString(policy)
610 };
611}
612
613/*
614 * Returns an intermediate representation that models the the given
615 * template declaration.
616 */
617static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl* template_declaration) {
618 assert(template_declaration);
619
620 RelaxedTemplateDeclaration template_declaration_ir{};
621
622 auto template_parameters = template_declaration->getTemplateParameters();
623 for (auto template_parameter : template_parameters->asArray()) {
624 auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
625 std::string type{};
626
627 std::optional<SfinaeConstraint> sfinae{};
628
629 if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
630 kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
631 type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
632
633 // REMARK: QDoc uses this information to match a user
634 // provided documentation (for example from an "\fn"
635 // command) with a `Node` that was extracted from the
636 // code-base.
637 //
638 // Due to how QDoc obtains an AST for documentation that
639 // is provided by the user, there might be a mismatch in
640 // the type of certain non type template parameters.
641 //
642 // QDoc generally builds a fake out-of-line definition for
643 // a callable provided through an "\fn" command, when it
644 // needs to match it.
645 // In that context, certain type names may be dependent
646 // names, while they may not be when the element they
647 // represent is extracted from the code-base.
648 //
649 // This in turn makes their stringified representation
650 // different in the two contextes, as a dependent name may
651 // require the "typename" keyword to precede it.
652 //
653 // Since QDoc uses a very simplified model, and it
654 // generally doesn't need care about the exact name
655 // resolution rules for C++, since it passes by
656 // Clang-validated data, we remove the "typename" keyword
657 // if it prefixes the type representation, so that it
658 // doesn't impact the matching procedure..
659
660 // KLUDGE: Waiting for C++20 to avoid the conversion.
661 // Doesn't really impact performance in a
662 // meaningful way so it can be kept while waiting.
663 if (QString::fromStdString(type).startsWith("typename ")) type.erase(0, std::string("typename ").size());
664
665 sfinae = detect_sfinae_constraint(non_type_template_parameter);
666 }
667
668 auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
669 if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
670
671 template_declaration_ir.parameters.push_back({
672 kind,
673 template_parameter->isTemplateParameterPack(),
674 {
675 std::move(type),
676 template_parameter->getNameAsString(),
677 get_default_value_initializer_as_string(template_parameter)
678 },
679 (template_template_parameter ?
680 std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
681 get_template_declaration(template_template_parameter).parameters
682 }) : std::nullopt),
683 std::move(sfinae),
684 std::nullopt
685 });
686
687 // Direct concept-on-template-parameter form, such as
688 // \c {template<Sortable T>}. The constraint hangs off the
689 // \c {TemplateTypeParmDecl} rather than appearing in a requires clause.
690 // The constraint's named concept is reachable as a \c {NamedDecl}, so
691 // its qualified name is available via getQualifiedNameAsString().
692 //
693 // Scope note: this extracts the type-template-parameter form only.
694 // A constrained-auto non-type template parameter, such as
695 // \c {template <Integral auto N>}, surfaces as a NonTypeTemplateParmDecl
696 // whose type contains a constrained AutoType, and is not covered here.
697 if (const auto *type_template_parameter =
698 llvm::dyn_cast<clang::TemplateTypeParmDecl>(template_parameter)) {
699 if (type_template_parameter->hasTypeConstraint()) {
700 if (const clang::TypeConstraint *constraint =
701 type_template_parameter->getTypeConstraint()) {
702 if (const clang::NamedDecl *concept_decl =
703 constraint->getNamedConcept()) {
704 template_declaration_ir.referenced_concepts.push_back(
705 concept_decl->getQualifiedNameAsString());
706 template_declaration_ir.parameters.back().concept_name =
707 concept_decl->getQualifiedNameAsString();
708 }
709 }
710 }
711 }
712 }
713
714 // Collect the explicit requires clause first, if present.
715 std::string explicit_requires;
716 if (const clang::Expr *requires_clause = template_parameters->getRequiresClause()) {
717 explicit_requires = QString::fromStdString(get_expression_as_string(
718 requires_clause, template_declaration->getASTContext())).simplified().toStdString();
719 collect_concept_references(requires_clause,
720 template_declaration_ir.referenced_concepts);
721 }
722
723 // Synthesize a requires clause from detected SFINAE constraints.
724 // SFINAE parameters are annotated but kept in the parameter list
725 // so that \fn matching (which compares parameter counts and types)
726 // still works when detection succeeds on one path but not the
727 // other. Rendering functions skip annotated parameters and emit
728 // the synthesized requires clause instead.
729 {
730 std::string synthesized;
731 const auto &params = template_declaration_ir.parameters;
732
733 for (const auto &param : params) {
734 if (param.sfinae_constraint) {
735 if (!synthesized.empty())
736 synthesized += " && ";
737 synthesized += param.sfinae_constraint->alias_with_args;
738 }
739 }
740
741 // Combine synthesized SFINAE constraints with explicit requires
742 // clause when both are present. The explicit clause is wrapped
743 // in parentheses to preserve its precedence.
744 if (!synthesized.empty() && !explicit_requires.empty())
745 template_declaration_ir.requires_clause = synthesized + " && (" + explicit_requires + ")";
746 else if (!synthesized.empty())
747 template_declaration_ir.requires_clause = std::move(synthesized);
748 else if (!explicit_requires.empty())
749 template_declaration_ir.requires_clause = std::move(explicit_requires);
750 }
751
752 {
753 auto &refs = template_declaration_ir.referenced_concepts;
754 std::sort(refs.begin(), refs.end());
755 refs.erase(std::unique(refs.begin(), refs.end()), refs.end());
756 }
757
758 return template_declaration_ir;
759}
760
761/*!
762 convert a CXSourceLocation to a qdoc Location
763 */
764static Location fromCXSourceLocation(CXSourceLocation location)
765{
766 unsigned int line, column;
767 CXString file;
768 clang_getPresumedLocation(location, &file, &line, &column);
769 Location l(fromCXString(std::move(file)));
770 l.setColumnNo(column);
771 l.setLineNo(line);
772 return l;
773}
774
775/*!
776 convert a CX_CXXAccessSpecifier to Node::Access
777 */
778static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
779{
780 switch (spec) {
781 case CX_CXXPrivate:
782 return Access::Private;
783 case CX_CXXProtected:
784 return Access::Protected;
785 case CX_CXXPublic:
786 return Access::Public;
787 default:
788 return Access::Public;
789 }
790}
791
792/*!
793 Returns the spelling in the file for a source range
794 */
795
801
802static inline QString fromCache(const QByteArray &cache,
803 unsigned int offset1, unsigned int offset2)
804{
805 return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
806}
807
808static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
809{
810 using FileCache = QList<FileCacheEntry>;
811 static FileCache cache;
812
813 CXString cxFileName = clang_getFileName(cxFile);
814 const QByteArray fileName = clang_getCString(cxFileName);
815 clang_disposeString(cxFileName);
816
817 for (const auto &entry : std::as_const(cache)) {
818 if (fileName == entry.fileName)
819 return fromCache(entry.content, offset1, offset2);
820 }
821
822 QFile file(QString::fromUtf8(fileName));
823 if (file.open(QIODeviceBase::ReadOnly)) { // binary to match clang offsets
824 FileCacheEntry entry{std::move(fileName), file.readAll()};
825 cache.prepend(entry);
826 while (cache.size() > 5)
827 cache.removeLast();
828 return fromCache(entry.content, offset1, offset2);
829 }
830 return {};
831}
832
833static QString getSpelling(CXSourceRange range)
834{
835 auto start = clang_getRangeStart(range);
836 auto end = clang_getRangeEnd(range);
837 CXFile file1, file2;
838 unsigned int offset1, offset2;
839 clang_getFileLocation(start, &file1, nullptr, nullptr, &offset1);
840 clang_getFileLocation(end, &file2, nullptr, nullptr, &offset2);
841
842 if (file1 != file2 || offset2 <= offset1)
843 return QString();
844
845 return readFile(file1, offset1, offset2);
846}
847
848/*!
849 Returns the function name from a given cursor representing a
850 function declaration. This is usually clang_getCursorSpelling, but
851 not for the conversion function in which case it is a bit more complicated
852 */
853QString functionName(CXCursor cursor)
854{
855 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
856 // For a CXCursor_ConversionFunction we don't want the spelling which would be something
857 // like "operator type-parameter-0-0" or "operator unsigned int". we want the actual name as
858 // spelled;
859 auto conversion_declaration =
860 static_cast<const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
861
862 return QLatin1String("operator ") + QString::fromStdString(get_fully_qualified_type_name(
863 conversion_declaration->getConversionType(),
864 conversion_declaration->getASTContext()
865 ));
866 }
867
868 QString name = fromCXString(clang_getCursorSpelling(cursor));
869
870 // Remove template stuff from constructor and destructor but not from operator<
871 auto ltLoc = name.indexOf('<');
872 if (ltLoc > 0 && !name.startsWith("operator<"))
873 name = name.left(ltLoc);
874 return name;
875}
876
877/*!
878 Reconstruct the qualified path name of a function that is
879 being overridden.
880 */
881static QString reconstructQualifiedPathForCursor(CXCursor cur)
882{
883 QString path;
884 auto kind = clang_getCursorKind(cur);
885 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
886 switch (kind) {
887 case CXCursor_Namespace:
888 case CXCursor_StructDecl:
889 case CXCursor_ClassDecl:
890 case CXCursor_UnionDecl:
891 case CXCursor_ClassTemplate:
892 path.prepend("::");
893 path.prepend(fromCXString(clang_getCursorSpelling(cur)));
894 break;
895 case CXCursor_FunctionDecl:
896 case CXCursor_FunctionTemplate:
897 case CXCursor_CXXMethod:
898 case CXCursor_Constructor:
899 case CXCursor_Destructor:
900 case CXCursor_ConversionFunction:
901 path = functionName(cur);
902 break;
903 default:
904 break;
905 }
906 cur = clang_getCursorSemanticParent(cur);
907 kind = clang_getCursorKind(cur);
908 }
909 return path;
910}
911
912/*!
913 \internal
914
915 Extract a class name from a Clang parameter type, stripping references,
916 pointers, and qualifiers. Returns \c {std::nullopt} if the type doesn't
917 represent a class.
918 */
919static std::optional<QString> classNameFromParameterType(clang::QualType param_type)
920{
921 param_type = param_type.getNonReferenceType();
922 while (param_type->isPointerType())
923 param_type = param_type->getPointeeType();
924 param_type = param_type.getUnqualifiedType();
925
926 if (param_type->isBuiltinType())
927 return std::nullopt;
928
929 if (const auto *record_type = param_type->getAs<clang::RecordType>()) {
930 if (const auto *record_decl = record_type->getDecl())
931 return QString::fromStdString(record_decl->getQualifiedNameAsString());
932 }
933
934 // The type may be incomplete (forward-declared or unknown during \fn parsing).
935 // Extract the class name from the type spelling if it looks like a class.
936 QString class_name = QString::fromStdString(param_type.getAsString());
937 class_name.remove("const "_L1).remove("volatile "_L1);
938 class_name.remove("class "_L1).remove("struct "_L1);
939 class_name = class_name.trimmed();
940
941 if (class_name.isEmpty() || class_name.contains('('_L1) || class_name.contains('['_L1))
942 return std::nullopt;
943
944 // Strip template arguments (e.g. "QList<MyClass>" becomes "QList") as
945 // hidden friends are declared in the primary type.
946 if (auto angle = class_name.indexOf('<'_L1); angle > 0)
947 class_name.truncate(angle);
948
949 return class_name;
950}
951
952/*!
953 \internal
954
955 Search for hidden friend candidates by inspecting parameter types.
956 When a \fn command uses unqualified syntax for a hidden friend, the
957 initial name lookup won't find it because hidden friends are stored
958 under their enclosing class, not in the global namespace. This
959 function examines the parameter types of \a func_decl to locate
960 classes that may contain hidden friends with matching names.
961
962 Appends any found hidden friend nodes to \a candidates.
963 */
964static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName,
965 const clang::FunctionDecl *func_decl, NodeVector &candidates)
966{
967 QSet<ClassNode *> searched_classes;
968 for (const auto *param : func_decl->parameters()) {
969 auto class_name = classNameFromParameterType(param->getType());
970 if (!class_name)
971 continue;
972
973 auto *class_node = qdb->findClassNode(class_name->split("::"_L1));
974 if (!class_node || searched_classes.contains(class_node))
975 continue;
976
977 searched_classes.insert(class_node);
978 NodeVector class_candidates;
979 class_node->findChildren(funcName, class_candidates);
980
981 for (Node *candidate : class_candidates) {
982 if (!candidate->isFunction(Genus::CPP))
983 continue;
984 if (static_cast<FunctionNode *>(candidate)->isHiddenFriend())
985 candidates.append(candidate);
986 }
987 }
988}
989
990/*!
991 Find the node from the QDocDatabase \a qdb that corresponds to the declaration
992 represented by the cursor \a cur, if it exists.
993 */
994static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
995{
996 auto kind = clang_getCursorKind(cur);
997 if (clang_isInvalid(kind))
998 return nullptr;
999 if (kind == CXCursor_TranslationUnit)
1000 return qdb->primaryTreeRoot();
1001
1002 Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur));
1003 // Special case; if the cursor represents a template type|non-type|template parameter
1004 // and its semantic parent is a function, return a pointer to the function node.
1005 if (p && p->isFunction(Genus::CPP)) {
1006 switch (kind) {
1007 case CXCursor_TemplateTypeParameter:
1008 case CXCursor_NonTypeTemplateParameter:
1009 case CXCursor_TemplateTemplateParameter:
1010 return p;
1011 default:
1012 break;
1013 }
1014 }
1015
1016 // ...otherwise, the semantic parent must be an Aggregate node.
1017 if (!p || !p->isAggregate())
1018 return nullptr;
1019 auto parent = static_cast<Aggregate *>(p);
1020
1021 QString name;
1022 if (clang_Cursor_isAnonymous(cur)) {
1023 name = Utilities::uniqueIdentifier(
1024 fromCXSourceLocation(clang_getCursorLocation(cur)),
1025 QLatin1String("anonymous"));
1026 } else {
1027 name = fromCXString(clang_getCursorSpelling(cur));
1028 }
1029 switch (kind) {
1030 case CXCursor_Namespace:
1031 return parent->findNonfunctionChild(name, &Node::isNamespace);
1032 case CXCursor_StructDecl:
1033 case CXCursor_ClassDecl:
1034 case CXCursor_UnionDecl:
1035 case CXCursor_ClassTemplate:
1036 return parent->findNonfunctionChild(name, &Node::isClassNode);
1037 case CXCursor_FunctionDecl:
1038 case CXCursor_FunctionTemplate:
1039 case CXCursor_CXXMethod:
1040 case CXCursor_Constructor:
1041 case CXCursor_Destructor:
1042 case CXCursor_ConversionFunction: {
1043 NodeVector candidates;
1044 parent->findChildren(functionName(cur), candidates);
1045 // Hidden friend functions are recorded under their lexical parent in the database
1046 auto *cur_decl = get_cursor_declaration(cur);
1047 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() != clang::Decl::FOK_None) {
1048 if (auto *lexical_parent = findNodeForCursor(qdb, clang_getCursorLexicalParent(cur));
1049 lexical_parent && lexical_parent->isAggregate() && lexical_parent != parent) {
1050 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
1051 }
1052 }
1053
1054 // Fallback for hidden friends documented with \fn using unqualified syntax.
1055 // Hidden friends are stored under their enclosing class, not in the global
1056 // namespace, so the initial findChildren won't find them. Search parameter
1057 // types to locate them, even when other candidates (e.g. a same-named
1058 // template) already exist. (QTBUG-145790)
1059 const bool hasHiddenFriend =
1060 std::any_of(candidates.cbegin(), candidates.cend(), [](const Node *n) {
1061 return n->isFunction(Genus::CPP)
1062 && static_cast<const FunctionNode *>(n)->isHiddenFriend();
1063 });
1064 if (!hasHiddenFriend) {
1065 auto *func_decl = cur_decl ? cur_decl->getAsFunction() : nullptr;
1066 if (func_decl)
1067 findHiddenFriendCandidates(qdb, functionName(cur), func_decl, candidates);
1068 }
1069
1070 if (candidates.isEmpty())
1071 return nullptr;
1072
1073 CXType funcType = clang_getCursorType(cur);
1074 auto numArg = clang_getNumArgTypes(funcType);
1075 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
1076 QVarLengthArray<QString, 20> args;
1077
1078 std::optional<RelaxedTemplateDeclaration> relaxed_template_declaration{std::nullopt};
1079 if (kind == CXCursor_FunctionTemplate)
1080 relaxed_template_declaration = get_template_declaration(
1081 get_cursor_declaration(cur)->getAsFunction()->getDescribedFunctionTemplate()
1082 );
1083
1084 for (Node *candidate : std::as_const(candidates)) {
1085 if (!candidate->isFunction(Genus::CPP))
1086 continue;
1087
1088 auto fn = static_cast<FunctionNode *>(candidate);
1089
1090 if (!fn->templateDecl() && relaxed_template_declaration)
1091 continue;
1092
1093 if (fn->templateDecl() && !relaxed_template_declaration)
1094 continue;
1095
1096 if (fn->templateDecl() && relaxed_template_declaration &&
1097 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1098 continue;
1099
1100 const Parameters &parameters = fn->parameters();
1101
1102 if (parameters.count() != numArg + isVariadic) {
1103 // Ignore possible last argument of type QPrivateSignal as it may have been dropped
1104 if (numArg > 0 && parameters.isPrivateSignal() &&
1105 (parameters.isEmpty() || !parameters.last().type().endsWith(
1106 QLatin1String("QPrivateSignal")))) {
1107 if (parameters.count() != --numArg + isVariadic)
1108 continue;
1109 } else {
1110 continue;
1111 }
1112 }
1113
1114 if (fn->isConst() != bool(clang_CXXMethod_isConst(cur)))
1115 continue;
1116
1117 if (isVariadic && parameters.last().type() != QLatin1String("..."))
1118 continue;
1119
1120 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1121 continue;
1122
1123 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1124 continue;
1125
1126 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1127
1128 bool typesDiffer = false;
1129 for (int i = 0; i < numArg; ++i) {
1130 auto *paramDecl = function_declaration->getParamDecl(i);
1131 auto paramType = paramDecl->getOriginalType();
1132
1133 if (args.size() <= i)
1134 args.append(QString::fromStdString(get_fully_qualified_type_name(
1135 paramType, function_declaration->getASTContext()
1136 )));
1137
1138 QString recordedType = parameters.at(i).type();
1139 QString typeSpelling = args.at(i);
1140
1141 typesDiffer = recordedType != typeSpelling;
1142
1143 // Retry with a canonical type spelling unless the parameter is a bare
1144 // template type parameter, such as T but not const T& or MyContainer<T>.
1145 // Wrapped forms are safe because both sides of the comparison are
1146 // canonicalized in the same way. Exclude bare TemplateTypeParmType
1147 // because canonicalization removes the spelled Q_QDOC template
1148 // parameter name and can make distinct Q_QDOC-declared signatures
1149 // appear identical during matching.
1150 if (typesDiffer) {
1151 const bool isBareTemplateTypeParm =
1152 paramType.getTypePtrOrNull()
1153 && llvm::isa<clang::TemplateTypeParmType>(paramType.getTypePtr());
1154 if (!isBareTemplateTypeParm) {
1155 QStringView canonicalType = parameters.at(i).canonicalType();
1156 if (!canonicalType.isEmpty()) {
1157 typesDiffer = canonicalType !=
1158 QString::fromStdString(get_fully_qualified_type_name(
1159 paramType.getCanonicalType(),
1160 function_declaration->getASTContext()
1161 ));
1162 }
1163 }
1164 }
1165
1166 if (typesDiffer) {
1167 break;
1168 }
1169 }
1170
1171 if (!typesDiffer)
1172 return fn;
1173 }
1174 return nullptr;
1175 }
1176 case CXCursor_EnumDecl:
1177 return parent->findNonfunctionChild(name, &Node::isEnumType);
1178 case CXCursor_FieldDecl:
1179 case CXCursor_VarDecl:
1180 return parent->findNonfunctionChild(name, &Node::isVariable);
1181 case CXCursor_TypedefDecl:
1182 return parent->findNonfunctionChild(name, &Node::isTypedef);
1183 default:
1184 return nullptr;
1185 }
1186}
1187
1188static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
1189{
1190 CXCursor *overridden;
1191 unsigned int numOverridden = 0;
1192 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
1193 for (uint i = 0; i < numOverridden; ++i) {
1194 QString path = reconstructQualifiedPathForCursor(overridden[i]);
1195 if (!path.isEmpty()) {
1196 fn->setOverride(true);
1197 fn->setOverridesThis(path);
1198 break;
1199 }
1200 }
1201 clang_disposeOverriddenCursors(overridden);
1202}
1203
1204/*!
1205 \internal
1206 Auto-generates documentation for explicitly defaulted or deleted
1207 special member functions that don't already have documentation.
1208*/
1209static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
1210{
1211 if (fn->hasDoc())
1212 return;
1214 return;
1216 return;
1217
1218 QString docSource;
1219 if (fn->isDtor()) {
1220 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
1221 if (fn->isVirtual())
1222 docSource += u" This destructor is virtual."_s;
1223 } else if (fn->isCtor()) {
1224 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
1225 } else if (fn->isCCtor()) {
1226 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
1227 } else if (fn->isMCtor()) {
1228 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
1229 } else if (fn->isCAssign() || fn->isMAssign()) {
1230 const auto &params = fn->parameters();
1231 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
1232 ? params.at(0).name()
1233 : u"other"_s;
1234 docSource = fn->isCAssign()
1235 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
1236 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
1237 }
1238
1239 if (docSource.isEmpty())
1240 return;
1241
1242 if (fn->isDeletedAsWritten())
1243 docSource += u" This function is deleted."_s;
1244
1245 static const QSet<QString> noMetaCommands;
1246 static const QSet<QString> noTopics;
1247 Doc doc(fn->location(), fn->location(), docSource, noMetaCommands, noTopics);
1249 fn->setDoc(doc);
1250}
1251
1253{
1254public:
1255 ClangVisitor(QDocDatabase *qdb, const std::set<Config::HeaderFilePath> &allHeaders,
1256 const Config::InternalFilePatterns& internalFilePatterns)
1257 : qdb_(qdb), parent_(qdb->primaryTreeRoot()),
1258 internalFilePatterns_(internalFilePatterns)
1259 {
1260 std::transform(allHeaders.cbegin(), allHeaders.cend(), std::inserter(allHeaders_, allHeaders_.begin()),
1261 [](const auto& header_file_path) -> const QString& { return header_file_path.filename; });
1262 }
1263
1264 QDocDatabase *qdocDB() { return qdb_; }
1265
1266 CXChildVisitResult visitChildren(CXCursor cursor)
1267 {
1268 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1269 auto loc = clang_getCursorLocation(cur);
1270 if (clang_Location_isFromMainFile(loc))
1271 return visitSource(cur, loc);
1272
1273 CXFile file;
1274 clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr);
1275 bool isInteresting = false;
1276 auto it = isInterestingCache_.find(file);
1277 if (it != isInterestingCache_.end()) {
1278 isInteresting = *it;
1279 } else {
1280 QFileInfo fi(fromCXString(clang_getFileName(file)));
1281 // Match by file name in case of PCH/installed headers
1282 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1283 isInterestingCache_[file] = isInteresting;
1284 }
1285 if (isInteresting) {
1286 return visitHeader(cur, loc);
1287 }
1288
1289 return CXChildVisit_Continue;
1290 });
1291 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1292 }
1293
1294 /*
1295 Not sure about all the possibilities, when the cursor
1296 location is not in the main file.
1297 */
1298 CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
1299 {
1300 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1301 auto loc = clang_getCursorLocation(cur);
1302 if (clang_Location_isFromMainFile(loc))
1303 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1304 return CXChildVisit_Continue;
1305 });
1306 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1307 }
1308
1309 Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
1310
1311private:
1312 QmlNativeTypeAttribute detectQmlNativeTypeAttribute(CXCursor cursor);
1313 /*!
1314 SimpleLoc represents a simple location in the main source file,
1315 which can be used as a key in a QMap.
1316 */
1317 struct SimpleLoc
1318 {
1319 unsigned int line {}, column {};
1320 friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
1321 {
1322 return a.line != b.line ? a.line < b.line : a.column < b.column;
1323 }
1324 };
1325 /*!
1326 \variable ClangVisitor::declMap_
1327 Map of all the declarations in the source file so we can match them
1328 with a documentation comment.
1329 */
1330 QMap<SimpleLoc, CXCursor> declMap_;
1331
1332 QDocDatabase *qdb_;
1333 Aggregate *parent_;
1334 std::set<QString> allHeaders_;
1335 QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
1336 const Config::InternalFilePatterns& internalFilePatterns_;
1337
1338 /*!
1339 Returns true if the symbol should be ignored for the documentation.
1340 */
1341 bool ignoredSymbol(const QString &symbolName)
1342 {
1343 if (symbolName == QLatin1String("QPrivateSignal"))
1344 return true;
1345 // Ignore functions generated by property macros
1346 if (symbolName.startsWith("_qt_property_"))
1347 return true;
1348 // Ignore template argument deduction guides
1349 if (symbolName.startsWith("<deduction guide"))
1350 return true;
1351 return false;
1352 }
1353
1354 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1355 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1356 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
1357 bool &ignoreSignature);
1358 void processFunction(FunctionNode *fn, CXCursor cursor);
1359 bool parseProperty(const QString &spelling, const Location &loc);
1360 void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
1361 Aggregate *getSemanticParent(CXCursor cursor);
1362};
1363
1364/*!
1365 Detects if a class cursor contains declarations specific to QML types:
1366
1367 \details {QML_SINGLETON macro}
1368 Returns QmlNativeTypeAttribute::Singleton if the macro is detected.
1369
1370 The \e QML_SINGLETON macro expands to multiple items including:
1371 \list
1372 \li \c {Q_CLASSINFO("QML.Singleton", "true")}
1373 \li \c {enum class QmlIsSingleton}
1374 \endlist
1375 \enddetails
1376
1377 \details {QML_UNCREATABLE macro}
1378 Returns ClassNode::QmlNativeTypeAttribute::Uncreatable if the macro is detected.
1379
1380 The \e QML_UNCREATABLE macro expands to multiple items including:
1381 \list
1382 \li \c {Q_CLASSINFO("QML.Creatable", "false")}
1383 \li \c {enum class QmlIsUncreatable}
1384 \endlist
1385 \enddetails
1386
1387 This method looks for the above expansion artifacts to detect the macros.
1388 If no artifacts are found, returns QmlNativeTypeAttribute::None
1389 (that is, a standard instantiable QML type).
1390*/
1391QmlNativeTypeAttribute ClangVisitor::detectQmlNativeTypeAttribute(CXCursor cursor)
1392{
1394
1395 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
1396 // Look for Q_CLASSINFO calls that indicate QML.Singleton or QML.Creatable = false
1397 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1398 CXSourceRange range = clang_getCursorExtent(child);
1399 QString sourceText = getSpelling(range);
1400 static const QRegularExpression qmlClassInfoPattern(
1401 R"(Q_CLASSINFO\s*\‍(\s*["\']QML\.(Singleton|Creatable)["\']\s*,\s*["\'](true|false)["\']\s*\‍))");
1402 const auto match = qmlClassInfoPattern.match(sourceText);
1403 if (match.hasMatch()) {
1404 if (match.captured(1) == "Singleton"_L1 && match.captured(2) == "true"_L1) {
1406 return CXChildVisit_Break;
1407 } else if (match.captured(1) == "Creatable"_L1 && match.captured(2) == "false"_L1) {
1409 return CXChildVisit_Break;
1410 }
1411 }
1412 }
1413
1414 // Also check for enum class QmlIsSingleton which is part of the macro expansion
1415 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1416 QString spelling = fromCXString(clang_getCursorSpelling(child));
1417 if (spelling == "QmlIsSingleton"_L1) {
1419 return CXChildVisit_Break;
1420 } else if (spelling == "QmlIsUncreatable"_L1) {
1422 return CXChildVisit_Break;
1423 }
1424 }
1425
1426 return CXChildVisit_Continue;
1427 });
1428
1429 return attr;
1430}
1431
1432/*!
1433 Visits a cursor in the .cpp file.
1434 This fills the declMap_
1435 */
1436CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1437{
1438 auto kind = clang_getCursorKind(cursor);
1439 if (clang_isDeclaration(kind)) {
1440 SimpleLoc l;
1441 clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
1442 declMap_.insert(l, cursor);
1443 return CXChildVisit_Recurse;
1444 }
1445 return CXChildVisit_Continue;
1446}
1447
1448/*!
1449 If the semantic and lexical parent cursors of \a cursor are
1450 not the same, find the Aggregate node for the semantic parent
1451 cursor and return it. Otherwise return the current parent.
1452 */
1453Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
1454{
1455 CXCursor sp = clang_getCursorSemanticParent(cursor);
1456 CXCursor lp = clang_getCursorLexicalParent(cursor);
1457 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1458 Node *spn = findNodeForCursor(qdb_, sp);
1459 if (spn && spn->isAggregate()) {
1460 return static_cast<Aggregate *>(spn);
1461 }
1462 }
1463 return parent_;
1464}
1465
1466CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
1467 bool &ignoreSignature)
1468{
1469 switch (clang_getCursorKind(cursor)) {
1470 case CXCursor_Namespace:
1471 return CXChildVisit_Recurse;
1472 case CXCursor_FunctionDecl:
1473 case CXCursor_FunctionTemplate:
1474 case CXCursor_CXXMethod:
1475 case CXCursor_Constructor:
1476 case CXCursor_Destructor:
1477 case CXCursor_ConversionFunction: {
1478 ignoreSignature = false;
1479 if (ignoredSymbol(functionName(cursor))) {
1480 *fnNode = nullptr;
1481 ignoreSignature = true;
1482 } else {
1483 *fnNode = findNodeForCursor(qdb_, cursor);
1484 if (*fnNode) {
1485 if ((*fnNode)->isFunction(Genus::CPP)) {
1486 auto *fn = static_cast<FunctionNode *>(*fnNode);
1487 readParameterNamesAndAttributes(fn, cursor);
1488
1489 const clang::Decl* declaration = get_cursor_declaration(cursor);
1490 assert(declaration);
1491 if (const auto function_declaration = declaration->getAsFunction()) {
1492 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1493 if (llvm::dyn_cast_if_present<clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1494 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1495 }
1496 }
1497 } else { // Possibly an implicitly generated special member
1498 QString name = functionName(cursor);
1499 if (ignoredSymbol(name))
1500 return CXChildVisit_Continue;
1501 Aggregate *semanticParent = getSemanticParent(cursor);
1502 if (semanticParent && semanticParent->isClass()) {
1503 auto *candidate = new FunctionNode(nullptr, name);
1504 processFunction(candidate, cursor);
1505 if (!candidate->isSpecialMemberFunction()) {
1506 delete candidate;
1507 return CXChildVisit_Continue;
1508 }
1509 candidate->setImplicitlyGenerated(true);
1510 semanticParent->addChild(*fnNode = candidate);
1511 }
1512 }
1513 }
1514 break;
1515 }
1516 default:
1517 break;
1518 }
1519 return CXChildVisit_Continue;
1520}
1521
1522CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1523{
1524 auto kind = clang_getCursorKind(cursor);
1525
1526 switch (kind) {
1527 case CXCursor_TypeAliasTemplateDecl:
1528 case CXCursor_TypeAliasDecl: {
1529 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1530 QString aliasedType;
1531
1532 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1533 ? llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor))
1534 : nullptr;
1535
1536 if (kind == CXCursor_TypeAliasTemplateDecl) {
1537 // For template aliases, get the underlying TypeAliasDecl from the TemplateDecl
1538 if (const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1539 if (const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1540 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1541 aliasedType = QString::fromStdString(underlyingType.getAsString());
1542 }
1543 }
1544 } else {
1545 // For non-template aliases, get the underlying type via C API
1546 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1547 if (aliasedCXType.kind != CXType_Invalid) {
1548 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1549 }
1550 }
1551
1552 if (!aliasedType.isEmpty()) {
1553 auto *ta = new TypeAliasNode(parent_, aliasName, aliasedType);
1554 ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1555 ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1556
1557 if (templateDecl)
1558 ta->setTemplateDecl(get_template_declaration(templateDecl));
1559 }
1560 return CXChildVisit_Continue;
1561 }
1562 case CXCursor_StructDecl:
1563 case CXCursor_UnionDecl:
1564 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
1565 return CXChildVisit_Continue;
1566 Q_FALLTHROUGH();
1567 case CXCursor_ClassTemplate:
1568 Q_FALLTHROUGH();
1569 case CXCursor_ClassDecl: {
1570 if (!clang_isCursorDefinition(cursor))
1571 return CXChildVisit_Continue;
1572
1573 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1574 return CXChildVisit_Continue;
1575
1576 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1577
1578 Aggregate *semanticParent = getSemanticParent(cursor);
1579 if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
1580 return CXChildVisit_Continue;
1581 }
1582
1583 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1584 clang_getTemplateCursorKind(cursor) : kind;
1585
1587 if (actualKind == CXCursor_StructDecl)
1588 type = NodeType::Struct;
1589 else if (actualKind == CXCursor_UnionDecl)
1590 type = NodeType::Union;
1591
1592 auto *classe = new ClassNode(type, semanticParent, className);
1593 classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1594
1595 auto location = fromCXSourceLocation(clang_getCursorLocation(cursor));
1596 classe->setLocation(location);
1597
1598 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1599 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1600 if (Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1601 classe->setStatus(Status::Internal);
1602 }
1603
1604 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1605 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1606
1607 if (kind == CXCursor_ClassTemplate) {
1608 auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
1609 classe->setTemplateDecl(get_template_declaration(template_declaration));
1610 }
1611
1612 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1613 return visitChildren(cursor);
1614 }
1615 case CXCursor_CXXBaseSpecifier: {
1616 if (!parent_->isClassNode())
1617 return CXChildVisit_Continue;
1618 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1619 auto type = clang_getCursorType(cursor);
1620 auto baseCursor = clang_getTypeDeclaration(type);
1621 auto baseNode = findNodeForCursor(qdb_, baseCursor);
1622 auto classe = static_cast<ClassNode *>(parent_);
1623 if (baseNode == nullptr || !baseNode->isClassNode()) {
1624 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1625 classe->addUnresolvedBaseClass(access,
1626 bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
1627 return CXChildVisit_Continue;
1628 }
1629 auto baseClasse = static_cast<ClassNode *>(baseNode);
1630 classe->addResolvedBaseClass(access, baseClasse);
1631 return CXChildVisit_Continue;
1632 }
1633 case CXCursor_Namespace: {
1634 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1635 NamespaceNode *ns = nullptr;
1636 if (parent_)
1637 ns = static_cast<NamespaceNode *>(
1638 parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
1639 if (!ns) {
1640 ns = new NamespaceNode(parent_, namespaceName);
1641 ns->setAccess(Access::Public);
1642 ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1643 }
1644 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1645 return visitChildren(cursor);
1646 }
1647 case CXCursor_FunctionTemplate:
1648 Q_FALLTHROUGH();
1649 case CXCursor_FunctionDecl:
1650 case CXCursor_CXXMethod:
1651 case CXCursor_Constructor:
1652 case CXCursor_Destructor:
1653 case CXCursor_ConversionFunction: {
1654 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1655 return CXChildVisit_Continue;
1656 QString name = functionName(cursor);
1657 if (ignoredSymbol(name))
1658 return CXChildVisit_Continue;
1659 // constexpr constructors generate also a global instance; ignore
1660 if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
1661 return CXChildVisit_Continue;
1662
1663 auto *fn = new FunctionNode(parent_, name);
1664 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1665 if (!clang_Range_isNull(range)) {
1666 QString comment = getSpelling(range);
1667 if (comment.startsWith("//!")) {
1668 qsizetype tag = comment.indexOf(QChar('['));
1669 if (tag > 0) {
1670 qsizetype end = comment.indexOf(QChar(']'), ++tag);
1671 if (end > 0)
1672 fn->setTag(comment.mid(tag, end - tag));
1673 }
1674 }
1675 }
1676
1677 processFunction(fn, cursor);
1678
1679 if (kind == CXCursor_FunctionTemplate) {
1680 auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
1681 fn->setTemplateDecl(get_template_declaration(template_declaration));
1682 }
1683
1684 if (!clang_Location_isInSystemHeader(loc))
1685 autoGenerateSmfDoc(fn, parent_->name());
1686
1687 return CXChildVisit_Continue;
1688 }
1689#if CINDEX_VERSION >= 36
1690 case CXCursor_FriendDecl: {
1691 return visitChildren(cursor);
1692 }
1693#endif
1694 case CXCursor_EnumDecl: {
1695 auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1696 if (en && en->items().size())
1697 return CXChildVisit_Continue; // Was already parsed, probably in another TU
1698
1699 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1700
1701 if (clang_Cursor_isAnonymous(cursor)) {
1702 enumTypeName = "anonymous";
1703 // Generate a unique name to enable auto-tying doc comments in headers
1704 // to anonymous enum declarations
1705 if (Config::instance().get(CONFIG_DOCUMENTATIONINHEADERS).asBool())
1706 enumTypeName = Utilities::uniqueIdentifier(fromCXSourceLocation(clang_getCursorLocation(cursor)), enumTypeName);
1707 if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
1708 Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
1709 if (n)
1710 en = static_cast<EnumNode *>(n);
1711 }
1712 }
1713 if (!en) {
1714 en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1715 en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1716 en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1717 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1718 }
1719
1720 // Enum values
1721 visitChildrenLambda(cursor, [&](CXCursor cur) {
1722 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1723 return CXChildVisit_Continue;
1724
1725 QString value;
1726 visitChildrenLambda(cur, [&](CXCursor cur) {
1727 if (clang_isExpression(clang_getCursorKind(cur))) {
1728 value = getSpelling(clang_getCursorExtent(cur));
1729 return CXChildVisit_Break;
1730 }
1731 return CXChildVisit_Continue;
1732 });
1733 if (value.isEmpty()) {
1734 QLatin1String hex("0x");
1735 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1736 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1737 } else {
1738 value = QString::number(clang_getEnumConstantDeclValue(cur));
1739 }
1740 }
1741
1742 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1743 return CXChildVisit_Continue;
1744 });
1745 return CXChildVisit_Continue;
1746 }
1747 case CXCursor_FieldDecl:
1748 case CXCursor_VarDecl: {
1749 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1750 return CXChildVisit_Continue;
1751
1752 auto value_declaration =
1753 llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
1754 assert(value_declaration);
1755
1756 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1757 auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1758
1759 var->setAccess(access);
1760 var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1761 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1762 value_declaration->getType(),
1763 value_declaration->getASTContext()
1764 )));
1765 var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
1766
1767 return CXChildVisit_Continue;
1768 }
1769 case CXCursor_TypedefDecl: {
1770 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1771 return CXChildVisit_Continue;
1772 auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1773 td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1774 td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1775 // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
1776 visitChildrenLambda(cursor, [&](CXCursor cur) {
1777 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1778 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
1779 return CXChildVisit_Continue;
1780 // Found QFlags<XXX>
1781 visitChildrenLambda(cursor, [&](CXCursor cur) {
1782 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1783 return CXChildVisit_Continue;
1784 auto *en =
1785 findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
1786 if (en && en->isEnumType())
1787 static_cast<EnumNode *>(en)->setFlagsType(td);
1788 return CXChildVisit_Break;
1789 });
1790 return CXChildVisit_Break;
1791 });
1792 return CXChildVisit_Continue;
1793 }
1794 default:
1795 if (clang_isDeclaration(kind) && parent_->isClassNode()) {
1796 // may be a property macro or a static_assert
1797 // which is not exposed from the clang API
1798 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1800 }
1801 return CXChildVisit_Continue;
1802 }
1803}
1804
1805void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
1806{
1807 Parameters &parameters = fn->parameters();
1808 // Visit the parameters and attributes
1809 int i = 0;
1810 visitChildrenLambda(cursor, [&](CXCursor cur) {
1811 auto kind = clang_getCursorKind(cur);
1812 if (kind == CXCursor_AnnotateAttr) {
1813 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1814 if (annotation == QLatin1String("qt_slot")) {
1816 } else if (annotation == QLatin1String("qt_signal")) {
1818 }
1819 if (annotation == QLatin1String("qt_invokable"))
1820 fn->setInvokable(true);
1821 } else if (kind == CXCursor_CXXOverrideAttr) {
1822 fn->setOverride(true);
1823 } else if (kind == CXCursor_ParmDecl) {
1824 if (i >= parameters.count())
1825 return CXChildVisit_Break; // Attributes comes before parameters so we can break.
1826
1827 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1828 parameters[i].setName(name);
1829
1830 const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
1831 Q_ASSERT(parameter_declaration);
1832
1833 std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
1834
1835 if (!default_value.empty())
1836 parameters[i].setDefaultValue(QString::fromStdString(default_value));
1837
1838 ++i;
1839 }
1840 return CXChildVisit_Continue;
1841 });
1842}
1843
1844void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
1845{
1846 CXCursorKind kind = clang_getCursorKind(cursor);
1847 CXType funcType = clang_getCursorType(cursor);
1848 fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1849 fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1850 fn->setStatic(clang_CXXMethod_isStatic(cursor));
1851 fn->setConst(clang_CXXMethod_isConst(cursor));
1852 fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
1854 : clang_CXXMethod_isPureVirtual(cursor)
1857
1858 // REMARK: We assume that the following operations and casts are
1859 // generally safe.
1860 // Callers of those methods will generally check at the LibClang
1861 // level the kind of cursor we are dealing with and will pass on
1862 // only valid cursors that are of a function kind and that are at
1863 // least a declaration.
1864 //
1865 // Failure to do so implies a bug in the call chain and should be
1866 // dealt with as such.
1867 const clang::Decl* declaration = get_cursor_declaration(cursor);
1868
1869 assert(declaration);
1870
1871 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1872
1873 if (kind == CXCursor_Constructor
1874 // a constructor template is classified as CXCursor_FunctionTemplate
1875 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1877 else if (kind == CXCursor_Destructor)
1879 else if (kind != CXCursor_ConversionFunction)
1880 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1881 function_declaration->getReturnType(),
1882 function_declaration->getASTContext()
1883 )));
1884
1885 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
1886
1887 if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(Metaness::CCtor);
1888 else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(Metaness::MCtor);
1889
1890 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
1891
1892 if (function_declaration->isConstexpr()) fn->markConstexpr();
1893 if (function_declaration->isExplicitlyDefaulted()) fn->markExplicitlyDefaulted();
1894 if (function_declaration->isDeletedAsWritten()) fn->markDeletedAsWritten();
1895 if (
1896 (constructor_declaration && constructor_declaration->isExplicit()) ||
1897 (conversion_declaration && conversion_declaration->isExplicit())
1898 ) fn->markExplicit();
1899
1900 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
1901
1902 if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(Metaness::CAssign);
1903 else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(Metaness::MAssign);
1904
1905 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1906 const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
1907
1908 if (function_prototype) {
1909 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1910
1911 if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
1912 const std::string exception_specification_spelling =
1913 exception_specification.NoexceptExpr ? get_expression_as_string(
1914 exception_specification.NoexceptExpr,
1915 function_declaration->getASTContext()
1916 ) : "";
1917
1918 if (exception_specification_spelling != "false")
1919 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1920 }
1921 }
1922
1923 // Collect every concept references a function carries (trailing requires
1924 // clause, any constrained-auto parameter types).
1925 // From Clang 21 we get an AssociatedConstraint struct for the trailing
1926 // clause (upstream commit 49fd0bf35d2e); earlier Clang versions return
1927 // a bare Expr*.
1928 QStringList referenced_concepts;
1929#if LIBCLANG_VERSION_MAJOR >= 21
1930 if (const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1931 trailing_requires.ConstraintExpr) {
1932 QString requires_str = QString::fromStdString(
1933 get_expression_as_string(trailing_requires.ConstraintExpr,
1934 function_declaration->getASTContext()));
1935 fn->setTrailingRequiresClause(requires_str.simplified());
1936 std::vector<std::string> refs;
1937 collect_concept_references(trailing_requires.ConstraintExpr, refs);
1938 for (const auto &ref : refs)
1939 referenced_concepts << QString::fromStdString(ref);
1940 }
1941#else
1942 if (const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1943 QString requires_str = QString::fromStdString(
1944 get_expression_as_string(trailing_requires,
1945 function_declaration->getASTContext()));
1946 fn->setTrailingRequiresClause(requires_str.simplified());
1947 std::vector<std::string> refs;
1948 collect_concept_references(trailing_requires, refs);
1949 for (const auto &ref : refs)
1950 referenced_concepts << QString::fromStdString(ref);
1951 }
1952#endif
1953
1954 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1955 if (refQualKind == CXRefQualifier_LValue)
1956 fn->setRef(true);
1957 else if (refQualKind == CXRefQualifier_RValue)
1958 fn->setRefRef(true);
1959 // For virtual functions, determine what it overrides
1960 // (except for destructor for which we do not want to classify as overridden)
1961 if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
1963
1964 Parameters &parameters = fn->parameters();
1965 parameters.clear();
1966 parameters.reserve(function_declaration->getNumParams());
1967
1968 for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
1969 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1970
1971 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1972 parameter_type,
1973 parameter_declaration->getASTContext()
1974 )));
1975
1976 if (!parameter_type.isCanonical())
1977 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1978 parameter_type.getCanonicalType(),
1979 parameter_declaration->getASTContext()
1980 )));
1981
1982 // Constrained-auto parameter form, e.g. \c {void f(Sortable auto x)}.
1983 if (const clang::AutoType *auto_type = parameter_type->getContainedAutoType()) {
1984 if (auto_type->isConstrained()) {
1985 if (const clang::NamedDecl *concept_decl =
1986 auto_type->getTypeConstraintConcept()) {
1987 referenced_concepts << QString::fromStdString(
1988 concept_decl->getQualifiedNameAsString());
1989 }
1990 }
1991 }
1992 }
1993
1994 if (!referenced_concepts.isEmpty()) {
1995 referenced_concepts.sort();
1996 referenced_concepts.removeDuplicates();
1997 fn->setReferencedConcepts(std::move(referenced_concepts));
1998 }
1999
2000 if (parameters.count() > 0) {
2001 if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
2002 parameters.pop_back(); // remove the QPrivateSignal argument
2003 parameters.setPrivateSignal();
2004 }
2005 }
2006
2007 if (clang_isFunctionTypeVariadic(funcType))
2008 parameters.append(QStringLiteral("..."));
2009 readParameterNamesAndAttributes(fn, cursor);
2010
2011 if (declaration && declaration->getFriendObjectKind() != clang::Decl::FOK_None) {
2012 fn->setRelatedNonmember(true);
2013 Q_ASSERT(function_declaration);
2014
2015 const bool hasNamespaceScopeRedeclaration =
2016 std::any_of(function_declaration->redecls_begin(),
2017 function_declaration->redecls_end(),
2018 [](const clang::FunctionDecl *r) {
2019 return r->getFriendObjectKind() == clang::Decl::FOK_None;
2020 });
2021 if (!hasNamespaceScopeRedeclaration)
2022 fn->setHiddenFriend(true);
2023 }
2024}
2025
2026bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
2027{
2028 if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
2029 && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
2030 && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
2031 return false;
2032
2033 qsizetype lpIdx = spelling.indexOf(QChar('('));
2034 qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
2035 if (lpIdx <= 0 || rpIdx <= lpIdx)
2036 return false;
2037
2038 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
2039 signature = signature.simplified();
2040 QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
2041
2042 static const QStringList attrs =
2043 QStringList() << "READ" << "MEMBER" << "WRITE"
2044 << "NOTIFY" << "CONSTANT" << "FINAL"
2045 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
2046 << "RESET" << "REVISION" << "SCRIPTABLE"
2047 << "STORED" << "USER";
2048
2049 // Find the location of the first attribute. All preceding parts
2050 // represent the property type + name.
2051 auto it = std::find_if(parts.cbegin(), parts.cend(),
2052 [](const QString &attr) -> bool {
2053 return attrs.contains(attr);
2054 });
2055
2056 if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
2057 return false;
2058
2059 QStringList typeParts;
2060 std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
2061 parts.erase(parts.cbegin(), it);
2062 QString name = typeParts.takeLast();
2063
2064 // Move the pointer operator(s) from name to type
2065 while (!name.isEmpty() && name.front() == QChar('*')) {
2066 typeParts.last().push_back(name.front());
2067 name.removeFirst();
2068 }
2069
2070 // Need at least READ or MEMBER + getter/member name
2071 if (parts.size() < 2 || name.isEmpty())
2072 return false;
2073
2074 auto *property = new PropertyNode(parent_, name);
2075 property->setAccess(Access::Public);
2076 property->setLocation(loc);
2077 property->setDataType(typeParts.join(QChar(' ')));
2078
2079 int i = 0;
2080 while (i < parts.size()) {
2081 const QString &key = parts.at(i++);
2082 // Keywords with no associated values
2083 if (key == "CONSTANT") {
2084 property->setConstant();
2085 } else if (key == "REQUIRED") {
2086 property->setRequired();
2087 }
2088 if (i < parts.size()) {
2089 QString value = parts.at(i++);
2090 if (key == "READ") {
2091 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
2092 } else if (key == "WRITE") {
2093 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
2094 property->setWritable(true);
2095 } else if (key == "MEMBER") {
2096 property->setWritable(true);
2097 } else if (key == "STORED") {
2098 property->setStored(value.toLower() == "true");
2099 } else if (key == "BINDABLE") {
2100 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
2101 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
2102 } else if (key == "RESET") {
2103 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
2104 } else if (key == "NOTIFY") {
2105 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
2106 }
2107 }
2108 }
2109 return true;
2110}
2111
2112/*!
2113 Given a comment at location \a loc, return a Node for this comment
2114 \a nextCommentLoc is the location of the next comment so the declaration
2115 must be inbetween.
2116 Returns nullptr if no suitable declaration was found between the two comments.
2117 */
2118Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
2119{
2120 ClangVisitor::SimpleLoc docloc;
2121 clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
2122 auto decl_it = declMap_.upperBound(docloc);
2123 if (decl_it == declMap_.end())
2124 return nullptr;
2125
2126 unsigned int declLine = decl_it.key().line;
2127 unsigned int nextCommentLine;
2128 clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
2129 if (nextCommentLine < declLine)
2130 return nullptr; // there is another comment before the declaration, ignore it.
2131
2132 // make sure the previous decl was finished.
2133 if (decl_it != declMap_.begin()) {
2134 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
2135 unsigned int prevDeclLine;
2136 clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
2137 if (prevDeclLine >= docloc.line) {
2138 // The previous declaration was still going. This is only valid if the previous
2139 // declaration is a parent of the next declaration.
2140 auto parent = clang_getCursorLexicalParent(*decl_it);
2141 if (!clang_equalCursors(parent, *(std::prev(decl_it))))
2142 return nullptr;
2143 }
2144 }
2145 auto *node = findNodeForCursor(qdb_, *decl_it);
2146 // borrow the parameter name from the definition
2147 if (node && node->isFunction(Genus::CPP))
2148 readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
2149 return node;
2150}
2151
2153 QDocDatabase* qdb,
2154 Config& config,
2155 const std::vector<QByteArray>& include_paths,
2156 const QList<QByteArray>& defines,
2157 std::optional<std::reference_wrapper<const PCHFile>> pch
2158) : m_qdb{qdb},
2161 m_pch{pch}
2162{
2163 m_allHeaders = config.getHeaderFiles();
2164 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2165}
2166
2167static const char *defaultArgs_[] = {
2168 "-std=c++20",
2169#ifndef Q_OS_WIN
2170 "-fPIC",
2171#else
2172 "-fms-compatibility-version=19",
2173#endif
2174 "-DQ_QDOC",
2175 "-DQ_CLANG_QDOC",
2176 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2177 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2178 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2179 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2180 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2181 "-Wno-constant-logical-operand",
2182 "-Wno-macro-redefined",
2183 "-Wno-nullability-completeness",
2184 "-fvisibility=default",
2185 "-ferror-limit=0",
2186 "-xc++"
2187};
2188
2189static std::vector<const char *> toConstCharPointers(const std::vector<QByteArray> &args)
2190{
2191 std::vector<const char *> pointers;
2192 pointers.reserve(args.size());
2193 for (const auto &arg : args)
2194 pointers.push_back(arg.constData());
2195 return pointers;
2196}
2197
2198/*!
2199 Load the default arguments and the defines into \a args.
2200 Clear \a args first.
2201 */
2202void getDefaultArgs(const QList<QByteArray>& defines, std::vector<QByteArray>& args)
2203{
2204 args.clear();
2205 for (const char *arg : defaultArgs_)
2206 args.emplace_back(arg);
2207
2208 // Add the defines from the qdocconf file.
2209 for (const auto &p : std::as_const(defines))
2210 args.push_back(p);
2211}
2212
2213/*!
2214 Load the include paths into \a args.
2215 */
2217 const std::vector<QByteArray>& include_paths,
2218 std::vector<QByteArray>& args
2219) {
2220 if (include_paths.empty()) {
2221 qCWarning(lcQdoc) << "No include paths provided."
2222 << "Set 'includepaths' in the qdocconf file"
2223 << "or pass -I flags on the command line."
2224 << "C++ parsing may produce incomplete results.";
2225 } else {
2226 args.insert(args.end(), include_paths.begin(), include_paths.end());
2227 }
2228}
2229
2230/*!
2231 Building the PCH must be possible when there are no .cpp
2232 files, so it is moved here to its own member function, and
2233 it is called after the list of header files is complete.
2234 */
2236 QDocDatabase* qdb,
2237 QString module_header,
2238 const std::set<Config::HeaderFilePath>& all_headers,
2239 const std::vector<QByteArray>& include_paths,
2240 const QList<QByteArray>& defines,
2241 const InclusionPolicy& policy
2242) {
2243 static std::vector<QByteArray> arguments{};
2244
2245 if (module_header.isEmpty()) return std::nullopt;
2246
2247 getDefaultArgs(defines, arguments);
2248 getMoreArgs(include_paths, arguments);
2249
2250 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2251 | CXTranslationUnit_SkipFunctionBodies
2252 | CXTranslationUnit_KeepGoing);
2253
2254 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2255
2256 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
2257 if (!pch_directory.isValid()) return std::nullopt;
2258
2259 const QByteArray module = module_header.toUtf8();
2260 QByteArray header;
2261
2262 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
2263 // A predicate for std::find_if() to locate a path to the module's header
2264 // (e.g. QtGui/QtGui) to be used as pre-compiled header
2265 struct FindPredicate
2266 {
2267 enum SearchType { Any, Module };
2268 QByteArray &candidate_;
2269 const QByteArray &module_;
2270 SearchType type_;
2271 FindPredicate(QByteArray &candidate, const QByteArray &module,
2272 SearchType type = Any)
2273 : candidate_(candidate), module_(module), type_(type)
2274 {
2275 }
2276
2277 bool operator()(const QByteArray &p) const
2278 {
2279 if (type_ != Any && !p.endsWith(module_))
2280 return false;
2281 candidate_ = p + "/";
2282 candidate_.append(module_);
2283 if (p.startsWith("-I"))
2284 candidate_ = candidate_.mid(2);
2285 return QFile::exists(QString::fromUtf8(candidate_));
2286 }
2287 };
2288
2289 // First, search for an include path that contains the module name, then any path
2290 QByteArray candidate;
2291 auto it = std::find_if(include_paths.begin(), include_paths.end(),
2292 FindPredicate(candidate, module, FindPredicate::Module));
2293 if (it == include_paths.end())
2294 it = std::find_if(include_paths.begin(), include_paths.end(),
2295 FindPredicate(candidate, module, FindPredicate::Any));
2296 if (it != include_paths.end())
2297 header = std::move(candidate);
2298
2299 if (header.isEmpty()) {
2300 qWarning() << "(qdoc) Could not find the module header in include paths for module"
2301 << module << " (include paths: " << include_paths << ")";
2302 qWarning() << " Artificial module header built from header dirs in qdocconf "
2303 "file";
2304 }
2305 arguments.push_back("-xc++");
2306
2307 TranslationUnit tu;
2308
2309 QString tmpHeader = pch_directory.path() + "/" + module;
2310 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2311 QTextStream out(&tmpHeaderFile);
2312 if (header.isEmpty()) {
2313 for (const auto& [header_path, header_name] : all_headers) {
2314 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2315
2316 // Conditionally include private headers based on showInternal setting
2317 if (header_name.endsWith("_p.h"_L1))
2318 shouldInclude = shouldInclude && policy.showInternal;
2319
2320 if (shouldInclude) {
2321 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2322 }
2323 }
2324 } else {
2325 QFileInfo headerFile(header);
2326 if (!headerFile.exists()) {
2327 qWarning() << "Could not find module header file" << header;
2328 return std::nullopt;
2329 }
2330
2331 out << "#include \"" << header << "\"\n";
2332
2333 if (policy.showInternal) {
2334 for (const auto& [header_path, header_name] : all_headers) {
2335 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2336 if (header_name.endsWith("_p.h"_L1) && shouldInclude)
2337 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2338 }
2339 }
2340 }
2341 }
2342
2343 const auto argPointers = toConstCharPointers(arguments);
2344 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2345 CXErrorCode err =
2346 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2347 static_cast<int>(argPointers.size()), nullptr, 0,
2348 flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
2349 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
2350 << ") returns" << err;
2351
2353
2354 if (err || !tu) {
2355 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
2356 return std::nullopt;
2357 }
2358
2359 QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
2360 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2361 clang_defaultSaveOptions(tu));
2362 if (error) {
2363 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
2364 return std::nullopt;
2365 }
2366
2367 // Visit the header now, as token from pre-compiled header won't be visited
2368 // later
2369 CXCursor cur = clang_getTranslationUnitCursor(tu);
2370 auto &config = Config::instance();
2371 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2372 visitor.visitChildren(cur);
2373 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
2374
2375 return std::make_optional(PCHFile{std::move(pch_directory), std::move(pch_name)});
2376}
2377
2378static float getUnpatchedVersion(QString t)
2379{
2380 if (t.count(QChar('.')) > 1)
2381 t.truncate(t.lastIndexOf(QChar('.')));
2382 return t.toFloat();
2383}
2384
2385/*!
2386 Get ready to parse the C++ cpp file identified by \a filePath
2387 and add its parsed contents to the database. \a location is
2388 used for reporting errors.
2389
2390 If parsing C++ header file as source, do not use the precompiled
2391 header as the source file itself is likely already included in the
2392 PCH and therefore interferes visiting the TU's children.
2393 */
2394ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
2395{
2396 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2397 | CXTranslationUnit_SkipFunctionBodies
2398 | CXTranslationUnit_KeepGoing);
2399
2400 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2401
2402 getDefaultArgs(m_defines, m_args);
2403 if (m_pch && !filePath.endsWith(".mm")
2404 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2405 m_args.push_back("-w");
2406 m_args.push_back("-include-pch");
2407 m_args.push_back((*m_pch).get().name);
2408 }
2409 getMoreArgs(m_includePaths, m_args);
2410
2411 TranslationUnit tu;
2412 const auto argPointers = toConstCharPointers(m_args);
2413 const QByteArray filePathLocal = filePath.toLocal8Bit();
2414 CXErrorCode err =
2415 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2416 static_cast<int>(argPointers.size()), nullptr, 0, flags_, &tu.tu);
2417 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
2418 << ") returns" << err;
2420
2421 if (err || !tu) {
2422 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
2423 return {};
2424 }
2425
2426 ParsedCppFileIR parse_result{};
2427
2428 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2429 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2430 visitor.visitChildren(tuCur);
2431
2432 CXToken *tokens;
2433 unsigned int numTokens = 0;
2434 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2435 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2436
2437 for (unsigned int i = 0; i < numTokens; ++i) {
2438 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2439 continue;
2440 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2441 if (!comment.startsWith("/*!"))
2442 continue;
2443
2444 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2445 auto loc = fromCXSourceLocation(commentLoc);
2446 auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
2447 Doc::trimCStyleComment(loc, comment);
2448
2449 // Doc constructor parses the comment.
2450 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2451 if (hasTooManyTopics(doc))
2452 continue;
2453
2454 if (doc.topicsUsed().isEmpty()) {
2455 Node *n = nullptr;
2456 if (i + 1 < numTokens) {
2457 // Try to find the next declaration.
2458 CXSourceLocation nextCommentLoc = commentLoc;
2459 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2460 ++i; // already skip all the tokens that are not comments
2461 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2462 n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
2463 }
2464
2465 if (n) {
2466 parse_result.tied.emplace_back(TiedDocumentation{doc, n});
2467 } else if (CodeParser::isWorthWarningAbout(doc)) {
2468 bool future = false;
2469 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
2470 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
2471 if (getUnpatchedVersion(std::move(sinceVersion)) >
2472 getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
2473 future = true;
2474 }
2475 if (!future) {
2476 doc.location().warning(
2477 QStringLiteral("Cannot tie this documentation to anything"),
2478 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
2479 "topic command (e.g., '\\%1', '\\%2') in the "
2480 "comment and qdoc could not associate the "
2481 "declaration or definition following the "
2482 "comment with a documented entity.")
2483 .arg(COMMAND_FN, COMMAND_PAGE));
2484 }
2485 }
2486 } else {
2487 parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
2488
2489 CXCursor cur = clang_getCursor(tu, commentLoc);
2490 while (true) {
2491 CXCursorKind kind = clang_getCursorKind(cur);
2492 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2493 break;
2494 if (kind == CXCursor_Namespace) {
2495 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2496 }
2497 cur = clang_getCursorLexicalParent(cur);
2498 }
2499 }
2500 }
2501
2502 clang_disposeTokens(tu, tokens, numTokens);
2503 m_namespaceScope.clear();
2504 s_fn.clear();
2505
2506 return parse_result;
2507}
2508
2509/*!
2510 Use clang to parse the function signature from a function
2511 command. \a location is used for reporting errors. \a fnSignature
2512 is the string to parse. It is always a function decl.
2513 \a idTag is the optional bracketed argument passed to \\fn, or
2514 an empty string.
2515 \a context is a string list representing the scope (namespaces)
2516 under which the function is declared.
2517
2518 Returns a variant that's either a Node instance tied to the
2519 function declaration, or a parsing failure for later processing.
2520 */
2521std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
2522 const QString &idTag, QStringList context)
2523{
2524 Node *fnNode = nullptr;
2525 /*
2526 If the \fn command begins with a tag, then don't try to
2527 parse the \fn command with clang. Use the tag to search
2528 for the correct function node. It is an error if it can
2529 not be found. Return 0 in that case.
2530 */
2531 if (!idTag.isEmpty()) {
2532 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2533 if (!fnNode) {
2534 location.error(
2535 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
2536 } else {
2537 /*
2538 The function node was found. Use the formal
2539 parameter names from the \fn command, because
2540 they will be the names used in the documentation.
2541 */
2542 auto *fn = static_cast<FunctionNode *>(fnNode);
2543 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
2544 if (leftParenSplit.size() > 1) {
2545 QStringList rightParenSplit = leftParenSplit[1].split(')');
2546 if (!rightParenSplit.empty()) {
2547 QString params = rightParenSplit[0];
2548 if (!params.isEmpty()) {
2549 QStringList commaSplit = params.split(',');
2550 Parameters &parameters = fn->parameters();
2551 if (parameters.count() == commaSplit.size()) {
2552 for (int i = 0; i < parameters.count(); ++i) {
2553 QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
2554 if (blankSplit.size() > 1) {
2555 QString pName = blankSplit.last();
2556 // Remove any non-letters from the start of parameter name
2557 auto it = std::find_if(std::begin(pName), std::end(pName),
2558 [](const QChar &c) { return c.isLetter(); });
2559 parameters[i].setName(
2560 pName.remove(0, std::distance(std::begin(pName), it)));
2561 }
2562 }
2563 }
2564 }
2565 }
2566 }
2567 }
2568 return fnNode;
2569 }
2570 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2571 | CXTranslationUnit_SkipFunctionBodies
2572 | CXTranslationUnit_KeepGoing);
2573
2574 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2575
2576 getDefaultArgs(m_defines, m_args);
2577
2578 if (m_pch) {
2579 m_args.push_back("-w");
2580 m_args.push_back("-include-pch");
2581 m_args.push_back((*m_pch).get().name);
2582 }
2583
2584 TranslationUnit tu;
2585 QByteArray s_fn{};
2586 for (const auto &ns : std::as_const(context))
2587 s_fn.prepend("namespace " + ns.toUtf8() + " {");
2588 s_fn += fnSignature.toUtf8();
2589 if (!s_fn.endsWith(";"))
2590 s_fn += "{ }";
2591 s_fn.append(context.size(), '}');
2592
2593 const char *dummyFileName = fnDummyFileName;
2594 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2595 static_cast<unsigned long>(s_fn.size()) };
2596 const auto argPointers = toConstCharPointers(m_args);
2597 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2598 int(argPointers.size()), &unsavedFile, 1, flags, &tu.tu);
2599 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
2600 << ") returns" << err;
2602 if (err || !tu) {
2603 location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
2604 return fnNode;
2605 } else {
2606 /*
2607 Always visit the tu if one is constructed, because
2608 it might be possible to find the correct node, even
2609 if clang detected diagnostics. Only bother to report
2610 the diagnostics if they stop us finding the node.
2611 */
2612 CXCursor cur = clang_getTranslationUnitCursor(tu);
2613 auto &config = Config::instance();
2614 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2615 bool ignoreSignature = false;
2616 visitor.visitFnArg(cur, &fnNode, ignoreSignature);
2617
2618 if (!fnNode) {
2619 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2620 const auto &config = Config::instance();
2621 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2622 return FnMatchError{ fnSignature, location };
2623 }
2624 }
2625 }
2626 return fnNode;
2627}
2628
2629QT_END_NAMESPACE
static const clang::Decl * get_cursor_declaration(CXCursor cursor)
Returns the underlying Decl that cursor represents.
static QString reconstructQualifiedPathForCursor(CXCursor cur)
Reconstruct the qualified path name of a function that is being overridden.
static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName, const clang::FunctionDecl *func_decl, NodeVector &candidates)
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
static void collect_concept_references(const clang::Stmt *node, std::vector< std::string > &out)
static std::optional< SfinaeConstraint > detect_sfinae_constraint(const clang::NonTypeTemplateParmDecl *param)
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::vector< const char * > toConstCharPointers(const std::vector< QByteArray > &args)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static bool is_enable_if_name(const std::string &qualified_name)
static float getUnpatchedVersion(QString t)
void getMoreArgs(const std::vector< QByteArray > &include_paths, std::vector< QByteArray > &args)
Load the include paths into args.
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< QByteArray > &args)
Load the default arguments and the defines into args.
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
Returns a string representing the name of type as if it was referred to at the end of the translation...
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
QDocDatabase * qdocDB()
ClangVisitor(QDocDatabase *qdb, const std::set< Config::HeaderFilePath > &allHeaders, const Config::InternalFilePatterns &internalFilePatterns)
CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
The ClassNode represents a C++ class.
Definition classnode.h:23
static bool isWorthWarningAbout(const Doc &doc)
Test for whether a doc comment warrants warnings.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:95
Definition doc.h:32
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:89
void markAutoGenerated()
Marks this documentation as auto-generated by QDoc.
Definition doc.cpp:250
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
Definition doc.cpp:270
This node is used to represent any kind of function being documented.
void setConst(bool b)
void markDeletedAsWritten()
void setStatic(bool b)
void setVirtualness(Virtualness virtualness)
bool isNonvirtual() const
bool isMAssign() const
void setInvokable(bool b)
bool isCAssign() const
void setRef(bool b)
bool isDtor() const
void setOverride(bool b)
void setRefRef(bool b)
bool isSpecialMemberFunction() const
void markConstexpr()
bool isDeletedAsWritten() const
void markExplicit()
void markExplicitlyDefaulted()
bool isCCtor() const
bool isMCtor() const
bool isCtor() const
void setMetaness(Metaness metaness)
bool isExplicitlyDefaulted() const
Parameters & parameters()
void setHiddenFriend(bool b)
The Location class provides a way to mark a location in a file.
Definition location.h:20
void setColumnNo(int no)
Definition location.h:43
void setLineNo(int no)
Definition location.h:42
This class represents a C++ namespace.
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
Status
Specifies the status of the QQmlIncubator.
#define COMMAND_SINCE
Definition codeparser.h:77
#define COMMAND_FN
Definition codeparser.h:27
#define COMMAND_PAGE
Definition codeparser.h:45
#define CONFIG_VERSION
Definition config.h:459
#define CONFIG_DOCUMENTATIONINHEADERS
Definition config.h:389
bool hasTooManyTopics(const Doc &doc)
Checks if there are too many topic commands in doc.
NodeType
Definition genustypes.h:154
QmlNativeTypeAttribute
Defines QML-specific attributes affecting QmlTypeNode instances.
Definition genustypes.h:203
Metaness
Specifies the kind of function a FunctionNode represents.
Definition genustypes.h:231
This namespace holds QDoc-internal utility methods.
Definition utilities.h:21
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
Definition node.h:47
#define assert
@ Internal
Definition status.h:15
Returns the spelling in the file for a source range.
std::variant< Node *, FnMatchError > operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
Use clang to parse the function signature from a function command.
Encapsulates information about.
Definition parsererror.h:13
The Node class is the base class for all the nodes in QDoc's parse tree.
void setAccess(Access t)
Sets the node's access type to t.
Definition node.h:172
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:110
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:128
bool isVariable() const
Returns true if the node type is Variable.
Definition node.h:133
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
Definition node.cpp:912
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:138
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
Definition node.h:187
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:233
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
void setDoc(const Doc &doc, bool replace=false)
Sets this Node's Doc to doc.
Definition node.cpp:560
bool isClass() const
Returns true if the node type is Class.
Definition node.h:91
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:945
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:145
A class for parsing and managing a function parameter list.
Definition main.cpp:28
Parameter & operator[](int index)
Definition parameters.h:39
void pop_back()
Definition parameters.h:43
void reserve(int count)
Definition parameters.h:35
void clear()
Definition parameters.h:24
Parameter & last()
Definition parameters.h:37
int count() const
Definition parameters.h:34
void setPrivateSignal()
Definition parameters.h:44
Holds the source-level alias with its template arguments for a SFINAE constraint detected in a non-ty...
CXTranslationUnit tu