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