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 \internal
895
896 Search for hidden friend candidates by inspecting parameter types.
897 When a \fn command uses unqualified syntax for a hidden friend, the
898 initial name lookup won't find it because hidden friends are stored
899 under their enclosing class, not in the global namespace. This
900 function examines the parameter types of \a func_decl to locate
901 classes that may contain hidden friends with matching names.
902
903 Appends any found hidden friend nodes to \a candidates.
904 */
905static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName,
906 const clang::FunctionDecl *func_decl, NodeVector &candidates)
907{
908 QSet<ClassNode *> searched_classes;
909 for (const auto *param : func_decl->parameters()) {
910 auto class_name = classNameFromParameterType(param->getType());
911 if (!class_name)
912 continue;
913
914 auto *class_node = qdb->findClassNode(class_name->split("::"_L1));
915 if (!class_node || searched_classes.contains(class_node))
916 continue;
917
918 searched_classes.insert(class_node);
919 NodeVector class_candidates;
920 class_node->findChildren(funcName, class_candidates);
921
922 for (Node *candidate : class_candidates) {
923 if (!candidate->isFunction(Genus::CPP))
924 continue;
925 if (static_cast<FunctionNode *>(candidate)->isHiddenFriend())
926 candidates.append(candidate);
927 }
928 }
929}
930
931/*!
932 Find the node from the QDocDatabase \a qdb that corresponds to the declaration
933 represented by the cursor \a cur, if it exists.
934 */
935static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
936{
937 auto kind = clang_getCursorKind(cur);
938 if (clang_isInvalid(kind))
939 return nullptr;
940 if (kind == CXCursor_TranslationUnit)
941 return qdb->primaryTreeRoot();
942
943 Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur));
944 // Special case; if the cursor represents a template type|non-type|template parameter
945 // and its semantic parent is a function, return a pointer to the function node.
946 if (p && p->isFunction(Genus::CPP)) {
947 switch (kind) {
948 case CXCursor_TemplateTypeParameter:
949 case CXCursor_NonTypeTemplateParameter:
950 case CXCursor_TemplateTemplateParameter:
951 return p;
952 default:
953 break;
954 }
955 }
956
957 // ...otherwise, the semantic parent must be an Aggregate node.
958 if (!p || !p->isAggregate())
959 return nullptr;
960 auto parent = static_cast<Aggregate *>(p);
961
962 QString name;
963 if (clang_Cursor_isAnonymous(cur)) {
964 name = Utilities::uniqueIdentifier(
965 fromCXSourceLocation(clang_getCursorLocation(cur)),
966 QLatin1String("anonymous"));
967 } else {
968 name = fromCXString(clang_getCursorSpelling(cur));
969 }
970 switch (kind) {
971 case CXCursor_Namespace:
972 return parent->findNonfunctionChild(name, &Node::isNamespace);
973 case CXCursor_StructDecl:
974 case CXCursor_ClassDecl:
975 case CXCursor_UnionDecl:
976 case CXCursor_ClassTemplate:
977 return parent->findNonfunctionChild(name, &Node::isClassNode);
978 case CXCursor_FunctionDecl:
979 case CXCursor_FunctionTemplate:
980 case CXCursor_CXXMethod:
981 case CXCursor_Constructor:
982 case CXCursor_Destructor:
983 case CXCursor_ConversionFunction: {
984 NodeVector candidates;
985 parent->findChildren(functionName(cur), candidates);
986 // Hidden friend functions are recorded under their lexical parent in the database
987 auto *cur_decl = get_cursor_declaration(cur);
988 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() != clang::Decl::FOK_None) {
989 if (auto *lexical_parent = findNodeForCursor(qdb, clang_getCursorLexicalParent(cur));
990 lexical_parent && lexical_parent->isAggregate() && lexical_parent != parent) {
991 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
992 }
993 }
994
995 // Fallback for hidden friends documented with \fn using unqualified syntax.
996 // Hidden friends are stored under their enclosing class, not in the global
997 // namespace, so the initial findChildren won't find them. Search parameter
998 // types to locate them, even when other candidates (e.g. a same-named
999 // template) already exist. (QTBUG-145790)
1000 const bool hasHiddenFriend =
1001 std::any_of(candidates.cbegin(), candidates.cend(), [](const Node *n) {
1002 return n->isFunction(Genus::CPP)
1003 && static_cast<const FunctionNode *>(n)->isHiddenFriend();
1004 });
1005 if (!hasHiddenFriend) {
1006 auto *func_decl = cur_decl ? cur_decl->getAsFunction() : nullptr;
1007 if (func_decl)
1008 findHiddenFriendCandidates(qdb, functionName(cur), func_decl, candidates);
1009 }
1010
1011 if (candidates.isEmpty())
1012 return nullptr;
1013
1014 CXType funcType = clang_getCursorType(cur);
1015 auto numArg = clang_getNumArgTypes(funcType);
1016 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
1017 QVarLengthArray<QString, 20> args;
1018
1019 std::optional<RelaxedTemplateDeclaration> relaxed_template_declaration{std::nullopt};
1020 if (kind == CXCursor_FunctionTemplate)
1021 relaxed_template_declaration = get_template_declaration(
1022 get_cursor_declaration(cur)->getAsFunction()->getDescribedFunctionTemplate()
1023 );
1024
1025 for (Node *candidate : std::as_const(candidates)) {
1026 if (!candidate->isFunction(Genus::CPP))
1027 continue;
1028
1029 auto fn = static_cast<FunctionNode *>(candidate);
1030
1031 if (!fn->templateDecl() && relaxed_template_declaration)
1032 continue;
1033
1034 if (fn->templateDecl() && !relaxed_template_declaration)
1035 continue;
1036
1037 if (fn->templateDecl() && relaxed_template_declaration &&
1038 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1039 continue;
1040
1041 const Parameters &parameters = fn->parameters();
1042
1043 if (parameters.count() != numArg + isVariadic) {
1044 // Ignore possible last argument of type QPrivateSignal as it may have been dropped
1045 if (numArg > 0 && parameters.isPrivateSignal() &&
1046 (parameters.isEmpty() || !parameters.last().type().endsWith(
1047 QLatin1String("QPrivateSignal")))) {
1048 if (parameters.count() != --numArg + isVariadic)
1049 continue;
1050 } else {
1051 continue;
1052 }
1053 }
1054
1055 if (fn->isConst() != bool(clang_CXXMethod_isConst(cur)))
1056 continue;
1057
1058 if (isVariadic && parameters.last().type() != QLatin1String("..."))
1059 continue;
1060
1061 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1062 continue;
1063
1064 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1065 continue;
1066
1067 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1068
1069 bool typesDiffer = false;
1070 for (int i = 0; i < numArg; ++i) {
1071 auto *paramDecl = function_declaration->getParamDecl(i);
1072 auto paramType = paramDecl->getOriginalType();
1073
1074 if (args.size() <= i)
1075 args.append(QString::fromStdString(get_fully_qualified_type_name(
1076 paramType, function_declaration->getASTContext()
1077 )));
1078
1079 QString recordedType = parameters.at(i).type();
1080 QString typeSpelling = args.at(i);
1081
1082 typesDiffer = recordedType != typeSpelling;
1083
1084 // Retry with a canonical type spelling unless the parameter is a bare
1085 // template type parameter, such as T but not const T& or MyContainer<T>.
1086 // Wrapped forms are safe because both sides of the comparison are
1087 // canonicalized in the same way. Exclude bare TemplateTypeParmType
1088 // because canonicalization removes the spelled Q_QDOC template
1089 // parameter name and can make distinct Q_QDOC-declared signatures
1090 // appear identical during matching.
1091 if (typesDiffer) {
1092 const bool isBareTemplateTypeParm =
1093 paramType.getTypePtrOrNull()
1094 && llvm::isa<clang::TemplateTypeParmType>(paramType.getTypePtr());
1095 if (!isBareTemplateTypeParm) {
1096 QStringView canonicalType = parameters.at(i).canonicalType();
1097 if (!canonicalType.isEmpty()) {
1098 typesDiffer = canonicalType !=
1099 QString::fromStdString(get_fully_qualified_type_name(
1100 paramType.getCanonicalType(),
1101 function_declaration->getASTContext()
1102 ));
1103 }
1104 }
1105 }
1106
1107 if (typesDiffer) {
1108 break;
1109 }
1110 }
1111
1112 if (!typesDiffer)
1113 return fn;
1114 }
1115 return nullptr;
1116 }
1117 case CXCursor_EnumDecl:
1118 return parent->findNonfunctionChild(name, &Node::isEnumType);
1119 case CXCursor_FieldDecl:
1120 case CXCursor_VarDecl:
1121 return parent->findNonfunctionChild(name, &Node::isVariable);
1122 case CXCursor_TypedefDecl:
1123 return parent->findNonfunctionChild(name, &Node::isTypedef);
1124 default:
1125 return nullptr;
1126 }
1127}
1128
1129static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
1130{
1131 CXCursor *overridden;
1132 unsigned int numOverridden = 0;
1133 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
1134 for (uint i = 0; i < numOverridden; ++i) {
1135 QString path = reconstructQualifiedPathForCursor(overridden[i]);
1136 if (!path.isEmpty()) {
1137 fn->setOverride(true);
1138 fn->setOverridesThis(path);
1139 break;
1140 }
1141 }
1142 clang_disposeOverriddenCursors(overridden);
1143}
1144
1145/*!
1146 \internal
1147 Auto-generates documentation for explicitly defaulted or deleted
1148 special member functions that don't already have documentation.
1149*/
1150static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
1151{
1152 if (fn->hasDoc())
1153 return;
1155 return;
1157 return;
1158
1159 QString docSource;
1160 if (fn->isDtor()) {
1161 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
1162 if (fn->isVirtual())
1163 docSource += u" This destructor is virtual."_s;
1164 } else if (fn->isCtor()) {
1165 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
1166 } else if (fn->isCCtor()) {
1167 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
1168 } else if (fn->isMCtor()) {
1169 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
1170 } else if (fn->isCAssign() || fn->isMAssign()) {
1171 const auto &params = fn->parameters();
1172 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
1173 ? params.at(0).name()
1174 : u"other"_s;
1175 docSource = fn->isCAssign()
1176 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
1177 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
1178 }
1179
1180 if (docSource.isEmpty())
1181 return;
1182
1183 if (fn->isDeletedAsWritten())
1184 docSource += u" This function is deleted."_s;
1185
1186 static const QSet<QString> noMetaCommands;
1187 static const QSet<QString> noTopics;
1188 Doc doc(fn->location(), fn->location(), docSource, noMetaCommands, noTopics);
1190 fn->setDoc(doc);
1191}
1192
1194{
1195public:
1196 ClangVisitor(QDocDatabase *qdb, const std::set<Config::HeaderFilePath> &allHeaders,
1197 const Config::InternalFilePatterns& internalFilePatterns)
1198 : qdb_(qdb), parent_(qdb->primaryTreeRoot()),
1199 internalFilePatterns_(internalFilePatterns)
1200 {
1201 std::transform(allHeaders.cbegin(), allHeaders.cend(), std::inserter(allHeaders_, allHeaders_.begin()),
1202 [](const auto& header_file_path) -> const QString& { return header_file_path.filename; });
1203 }
1204
1205 QDocDatabase *qdocDB() { return qdb_; }
1206
1207 CXChildVisitResult visitChildren(CXCursor cursor)
1208 {
1209 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1210 auto loc = clang_getCursorLocation(cur);
1211 if (clang_Location_isFromMainFile(loc))
1212 return visitSource(cur, loc);
1213
1214 CXFile file;
1215 clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr);
1216 bool isInteresting = false;
1217 auto it = isInterestingCache_.find(file);
1218 if (it != isInterestingCache_.end()) {
1219 isInteresting = *it;
1220 } else {
1221 QFileInfo fi(fromCXString(clang_getFileName(file)));
1222 // Match by file name in case of PCH/installed headers
1223 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1224 isInterestingCache_[file] = isInteresting;
1225 }
1226 if (isInteresting) {
1227 return visitHeader(cur, loc);
1228 }
1229
1230 return CXChildVisit_Continue;
1231 });
1232 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1233 }
1234
1235 /*
1236 Not sure about all the possibilities, when the cursor
1237 location is not in the main file.
1238 */
1239 CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
1240 {
1241 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1242 auto loc = clang_getCursorLocation(cur);
1243 if (clang_Location_isFromMainFile(loc))
1244 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1245 return CXChildVisit_Continue;
1246 });
1247 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1248 }
1249
1250 Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
1251
1252private:
1253 bool detectQmlSingleton(CXCursor cursor);
1254 /*!
1255 SimpleLoc represents a simple location in the main source file,
1256 which can be used as a key in a QMap.
1257 */
1258 struct SimpleLoc
1259 {
1260 unsigned int line {}, column {};
1261 friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
1262 {
1263 return a.line != b.line ? a.line < b.line : a.column < b.column;
1264 }
1265 };
1266 /*!
1267 \variable ClangVisitor::declMap_
1268 Map of all the declarations in the source file so we can match them
1269 with a documentation comment.
1270 */
1271 QMap<SimpleLoc, CXCursor> declMap_;
1272
1273 QDocDatabase *qdb_;
1274 Aggregate *parent_;
1275 std::set<QString> allHeaders_;
1276 QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
1277 const Config::InternalFilePatterns& internalFilePatterns_;
1278
1279 /*!
1280 Returns true if the symbol should be ignored for the documentation.
1281 */
1282 bool ignoredSymbol(const QString &symbolName)
1283 {
1284 if (symbolName == QLatin1String("QPrivateSignal"))
1285 return true;
1286 // Ignore functions generated by property macros
1287 if (symbolName.startsWith("_qt_property_"))
1288 return true;
1289 // Ignore template argument deduction guides
1290 if (symbolName.startsWith("<deduction guide"))
1291 return true;
1292 return false;
1293 }
1294
1295 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1296 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1297 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
1298 bool &ignoreSignature);
1299 void processFunction(FunctionNode *fn, CXCursor cursor);
1300 bool parseProperty(const QString &spelling, const Location &loc);
1301 void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
1302 Aggregate *getSemanticParent(CXCursor cursor);
1303};
1304
1305/*!
1306 Detects if a class cursor contains the \e QML_SINGLETON macro.
1307 Returns true if the macro is detected, false otherwise.
1308
1309 The \e QML_SINGLETON macro expands to multiple items including:
1310 \list
1311 \li \c {Q_CLASSINFO("QML.Singleton", "true")}
1312 \li \c {enum class QmlIsSingleton}
1313 \endlist
1314
1315 This method looks for these expansion artifacts to detect the macro.
1316*/
1317bool ClangVisitor::detectQmlSingleton(CXCursor cursor)
1318{
1319 bool hasSingletonMacro = false;
1320
1321 visitChildrenLambda(cursor, [&hasSingletonMacro](CXCursor child) -> CXChildVisitResult {
1322 // Look for Q_CLASSINFO calls that indicate QML.Singleton
1323 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1324 CXSourceRange range = clang_getCursorExtent(child);
1325 QString sourceText = getSpelling(range);
1326 // More precise matching: look for the exact Q_CLASSINFO pattern
1327 static const QRegularExpression qmlSingletonPattern(
1328 R"(Q_CLASSINFO\s*\‍(\s*["\']QML\.Singleton["\']\s*,\s*["\']true["\']\s*\‍))");
1329 if (qmlSingletonPattern.match(sourceText).hasMatch()) {
1330 hasSingletonMacro = true;
1331 return CXChildVisit_Break;
1332 }
1333 }
1334
1335 // Also check for enum class QmlIsSingleton which is part of the macro expansion
1336 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1337 QString spelling = fromCXString(clang_getCursorSpelling(child));
1338 if (spelling == "QmlIsSingleton"_L1) {
1339 hasSingletonMacro = true;
1340 return CXChildVisit_Break;
1341 }
1342 }
1343
1344 return CXChildVisit_Continue;
1345 });
1346
1347 return hasSingletonMacro;
1348}
1349
1350/*!
1351 Visits a cursor in the .cpp file.
1352 This fills the declMap_
1353 */
1354CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1355{
1356 auto kind = clang_getCursorKind(cursor);
1357 if (clang_isDeclaration(kind)) {
1358 SimpleLoc l;
1359 clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
1360 declMap_.insert(l, cursor);
1361 return CXChildVisit_Recurse;
1362 }
1363 return CXChildVisit_Continue;
1364}
1365
1366/*!
1367 If the semantic and lexical parent cursors of \a cursor are
1368 not the same, find the Aggregate node for the semantic parent
1369 cursor and return it. Otherwise return the current parent.
1370 */
1371Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
1372{
1373 CXCursor sp = clang_getCursorSemanticParent(cursor);
1374 CXCursor lp = clang_getCursorLexicalParent(cursor);
1375 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1376 Node *spn = findNodeForCursor(qdb_, sp);
1377 if (spn && spn->isAggregate()) {
1378 return static_cast<Aggregate *>(spn);
1379 }
1380 }
1381 return parent_;
1382}
1383
1384CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
1385 bool &ignoreSignature)
1386{
1387 switch (clang_getCursorKind(cursor)) {
1388 case CXCursor_Namespace:
1389 return CXChildVisit_Recurse;
1390 case CXCursor_FunctionDecl:
1391 case CXCursor_FunctionTemplate:
1392 case CXCursor_CXXMethod:
1393 case CXCursor_Constructor:
1394 case CXCursor_Destructor:
1395 case CXCursor_ConversionFunction: {
1396 ignoreSignature = false;
1397 if (ignoredSymbol(functionName(cursor))) {
1398 *fnNode = nullptr;
1399 ignoreSignature = true;
1400 } else {
1401 *fnNode = findNodeForCursor(qdb_, cursor);
1402 if (*fnNode) {
1403 if ((*fnNode)->isFunction(Genus::CPP)) {
1404 auto *fn = static_cast<FunctionNode *>(*fnNode);
1405 readParameterNamesAndAttributes(fn, cursor);
1406
1407 const clang::Decl* declaration = get_cursor_declaration(cursor);
1408 assert(declaration);
1409 if (const auto function_declaration = declaration->getAsFunction()) {
1410 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1411 if (llvm::dyn_cast_if_present<clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1412 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1413 }
1414 }
1415 } else { // Possibly an implicitly generated special member
1416 QString name = functionName(cursor);
1417 if (ignoredSymbol(name))
1418 return CXChildVisit_Continue;
1419 Aggregate *semanticParent = getSemanticParent(cursor);
1420 if (semanticParent && semanticParent->isClass()) {
1421 auto *candidate = new FunctionNode(nullptr, name);
1422 processFunction(candidate, cursor);
1423 if (!candidate->isSpecialMemberFunction()) {
1424 delete candidate;
1425 return CXChildVisit_Continue;
1426 }
1427 candidate->setImplicitlyGenerated(true);
1428 semanticParent->addChild(*fnNode = candidate);
1429 }
1430 }
1431 }
1432 break;
1433 }
1434 default:
1435 break;
1436 }
1437 return CXChildVisit_Continue;
1438}
1439
1440CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1441{
1442 auto kind = clang_getCursorKind(cursor);
1443
1444 switch (kind) {
1445 case CXCursor_TypeAliasTemplateDecl:
1446 case CXCursor_TypeAliasDecl: {
1447 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1448 QString aliasedType;
1449
1450 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1451 ? llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor))
1452 : nullptr;
1453
1454 if (kind == CXCursor_TypeAliasTemplateDecl) {
1455 // For template aliases, get the underlying TypeAliasDecl from the TemplateDecl
1456 if (const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1457 if (const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1458 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1459 aliasedType = QString::fromStdString(underlyingType.getAsString());
1460 }
1461 }
1462 } else {
1463 // For non-template aliases, get the underlying type via C API
1464 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1465 if (aliasedCXType.kind != CXType_Invalid) {
1466 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1467 }
1468 }
1469
1470 if (!aliasedType.isEmpty()) {
1471 auto *ta = new TypeAliasNode(parent_, aliasName, aliasedType);
1472 ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1473 ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1474
1475 if (templateDecl)
1476 ta->setTemplateDecl(get_template_declaration(templateDecl));
1477 }
1478 return CXChildVisit_Continue;
1479 }
1480 case CXCursor_StructDecl:
1481 case CXCursor_UnionDecl:
1482 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
1483 return CXChildVisit_Continue;
1484 Q_FALLTHROUGH();
1485 case CXCursor_ClassTemplate:
1486 Q_FALLTHROUGH();
1487 case CXCursor_ClassDecl: {
1488 if (!clang_isCursorDefinition(cursor))
1489 return CXChildVisit_Continue;
1490
1491 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1492 return CXChildVisit_Continue;
1493
1494 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1495
1496 Aggregate *semanticParent = getSemanticParent(cursor);
1497 if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
1498 return CXChildVisit_Continue;
1499 }
1500
1501 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1502 clang_getTemplateCursorKind(cursor) : kind;
1503
1505 if (actualKind == CXCursor_StructDecl)
1506 type = NodeType::Struct;
1507 else if (actualKind == CXCursor_UnionDecl)
1508 type = NodeType::Union;
1509
1510 auto *classe = new ClassNode(type, semanticParent, className);
1511 classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1512
1513 auto location = fromCXSourceLocation(clang_getCursorLocation(cursor));
1514 classe->setLocation(location);
1515
1516 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1517 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1518 if (Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1519 classe->setStatus(Status::Internal);
1520 }
1521
1522 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1523
1524 if (detectQmlSingleton(cursor)) {
1525 classe->setQmlSingleton(true);
1526 }
1527
1528 if (kind == CXCursor_ClassTemplate) {
1529 auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
1530 classe->setTemplateDecl(get_template_declaration(template_declaration));
1531 }
1532
1533 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1534 return visitChildren(cursor);
1535 }
1536 case CXCursor_CXXBaseSpecifier: {
1537 if (!parent_->isClassNode())
1538 return CXChildVisit_Continue;
1539 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1540 auto type = clang_getCursorType(cursor);
1541 auto baseCursor = clang_getTypeDeclaration(type);
1542 auto baseNode = findNodeForCursor(qdb_, baseCursor);
1543 auto classe = static_cast<ClassNode *>(parent_);
1544 if (baseNode == nullptr || !baseNode->isClassNode()) {
1545 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1546 classe->addUnresolvedBaseClass(access,
1547 bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
1548 return CXChildVisit_Continue;
1549 }
1550 auto baseClasse = static_cast<ClassNode *>(baseNode);
1551 classe->addResolvedBaseClass(access, baseClasse);
1552 return CXChildVisit_Continue;
1553 }
1554 case CXCursor_Namespace: {
1555 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1556 NamespaceNode *ns = nullptr;
1557 if (parent_)
1558 ns = static_cast<NamespaceNode *>(
1559 parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
1560 if (!ns) {
1561 ns = new NamespaceNode(parent_, namespaceName);
1563 ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1564 }
1565 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1566 return visitChildren(cursor);
1567 }
1568 case CXCursor_FunctionTemplate:
1569 Q_FALLTHROUGH();
1570 case CXCursor_FunctionDecl:
1571 case CXCursor_CXXMethod:
1572 case CXCursor_Constructor:
1573 case CXCursor_Destructor:
1574 case CXCursor_ConversionFunction: {
1575 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1576 return CXChildVisit_Continue;
1577 QString name = functionName(cursor);
1578 if (ignoredSymbol(name))
1579 return CXChildVisit_Continue;
1580 // constexpr constructors generate also a global instance; ignore
1581 if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
1582 return CXChildVisit_Continue;
1583
1584 auto *fn = new FunctionNode(parent_, name);
1585 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1586 if (!clang_Range_isNull(range)) {
1587 QString comment = getSpelling(range);
1588 if (comment.startsWith("//!")) {
1589 qsizetype tag = comment.indexOf(QChar('['));
1590 if (tag > 0) {
1591 qsizetype end = comment.indexOf(QChar(']'), ++tag);
1592 if (end > 0)
1593 fn->setTag(comment.mid(tag, end - tag));
1594 }
1595 }
1596 }
1597
1598 processFunction(fn, cursor);
1599
1600 if (kind == CXCursor_FunctionTemplate) {
1601 auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
1602 fn->setTemplateDecl(get_template_declaration(template_declaration));
1603 }
1604
1605 if (!clang_Location_isInSystemHeader(loc))
1606 autoGenerateSmfDoc(fn, parent_->name());
1607
1608 return CXChildVisit_Continue;
1609 }
1610#if CINDEX_VERSION >= 36
1611 case CXCursor_FriendDecl: {
1612 return visitChildren(cursor);
1613 }
1614#endif
1615 case CXCursor_EnumDecl: {
1616 auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1617 if (en && en->items().size())
1618 return CXChildVisit_Continue; // Was already parsed, probably in another TU
1619
1620 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1621
1622 if (clang_Cursor_isAnonymous(cursor)) {
1623 enumTypeName = "anonymous";
1624 // Generate a unique name to enable auto-tying doc comments in headers
1625 // to anonymous enum declarations
1626 if (Config::instance().get(CONFIG_DOCUMENTATIONINHEADERS).asBool())
1627 enumTypeName = Utilities::uniqueIdentifier(fromCXSourceLocation(clang_getCursorLocation(cursor)), enumTypeName);
1628 if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
1629 Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
1630 if (n)
1631 en = static_cast<EnumNode *>(n);
1632 }
1633 }
1634 if (!en) {
1635 en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1636 en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1637 en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1638 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1639 }
1640
1641 // Enum values
1642 visitChildrenLambda(cursor, [&](CXCursor cur) {
1643 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1644 return CXChildVisit_Continue;
1645
1646 QString value;
1647 visitChildrenLambda(cur, [&](CXCursor cur) {
1648 if (clang_isExpression(clang_getCursorKind(cur))) {
1649 value = getSpelling(clang_getCursorExtent(cur));
1650 return CXChildVisit_Break;
1651 }
1652 return CXChildVisit_Continue;
1653 });
1654 if (value.isEmpty()) {
1655 QLatin1String hex("0x");
1656 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1657 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1658 } else {
1659 value = QString::number(clang_getEnumConstantDeclValue(cur));
1660 }
1661 }
1662
1663 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1664 return CXChildVisit_Continue;
1665 });
1666 return CXChildVisit_Continue;
1667 }
1668 case CXCursor_FieldDecl:
1669 case CXCursor_VarDecl: {
1670 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1671 return CXChildVisit_Continue;
1672
1673 auto value_declaration =
1674 llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
1675 assert(value_declaration);
1676
1677 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1678 auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1679
1680 var->setAccess(access);
1681 var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1682 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1683 value_declaration->getType(),
1684 value_declaration->getASTContext()
1685 )));
1686 var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
1687
1688 return CXChildVisit_Continue;
1689 }
1690 case CXCursor_TypedefDecl: {
1691 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1692 return CXChildVisit_Continue;
1693 auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1694 td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1695 td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1696 // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
1697 visitChildrenLambda(cursor, [&](CXCursor cur) {
1698 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1699 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
1700 return CXChildVisit_Continue;
1701 // Found QFlags<XXX>
1702 visitChildrenLambda(cursor, [&](CXCursor cur) {
1703 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1704 return CXChildVisit_Continue;
1705 auto *en =
1706 findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
1707 if (en && en->isEnumType())
1708 static_cast<EnumNode *>(en)->setFlagsType(td);
1709 return CXChildVisit_Break;
1710 });
1711 return CXChildVisit_Break;
1712 });
1713 return CXChildVisit_Continue;
1714 }
1715 default:
1716 if (clang_isDeclaration(kind) && parent_->isClassNode()) {
1717 // may be a property macro or a static_assert
1718 // which is not exposed from the clang API
1719 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1721 }
1722 return CXChildVisit_Continue;
1723 }
1724}
1725
1726void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
1727{
1728 Parameters &parameters = fn->parameters();
1729 // Visit the parameters and attributes
1730 int i = 0;
1731 visitChildrenLambda(cursor, [&](CXCursor cur) {
1732 auto kind = clang_getCursorKind(cur);
1733 if (kind == CXCursor_AnnotateAttr) {
1734 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1735 if (annotation == QLatin1String("qt_slot")) {
1737 } else if (annotation == QLatin1String("qt_signal")) {
1739 }
1740 if (annotation == QLatin1String("qt_invokable"))
1741 fn->setInvokable(true);
1742 } else if (kind == CXCursor_CXXOverrideAttr) {
1743 fn->setOverride(true);
1744 } else if (kind == CXCursor_ParmDecl) {
1745 if (i >= parameters.count())
1746 return CXChildVisit_Break; // Attributes comes before parameters so we can break.
1747
1748 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1749 parameters[i].setName(name);
1750
1751 const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
1752 Q_ASSERT(parameter_declaration);
1753
1754 std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
1755
1756 if (!default_value.empty())
1757 parameters[i].setDefaultValue(QString::fromStdString(default_value));
1758
1759 ++i;
1760 }
1761 return CXChildVisit_Continue;
1762 });
1763}
1764
1765void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
1766{
1767 CXCursorKind kind = clang_getCursorKind(cursor);
1768 CXType funcType = clang_getCursorType(cursor);
1769 fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1770 fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1771 fn->setStatic(clang_CXXMethod_isStatic(cursor));
1772 fn->setConst(clang_CXXMethod_isConst(cursor));
1773 fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
1775 : clang_CXXMethod_isPureVirtual(cursor)
1778
1779 // REMARK: We assume that the following operations and casts are
1780 // generally safe.
1781 // Callers of those methods will generally check at the LibClang
1782 // level the kind of cursor we are dealing with and will pass on
1783 // only valid cursors that are of a function kind and that are at
1784 // least a declaration.
1785 //
1786 // Failure to do so implies a bug in the call chain and should be
1787 // dealt with as such.
1788 const clang::Decl* declaration = get_cursor_declaration(cursor);
1789
1790 assert(declaration);
1791
1792 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1793
1794 if (kind == CXCursor_Constructor
1795 // a constructor template is classified as CXCursor_FunctionTemplate
1796 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1798 else if (kind == CXCursor_Destructor)
1800 else if (kind != CXCursor_ConversionFunction)
1801 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1802 function_declaration->getReturnType(),
1803 function_declaration->getASTContext()
1804 )));
1805
1806 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
1807
1808 if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(FunctionNode::CCtor);
1809 else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(FunctionNode::MCtor);
1810
1811 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
1812
1813 if (function_declaration->isConstexpr()) fn->markConstexpr();
1814 if (function_declaration->isExplicitlyDefaulted()) fn->markExplicitlyDefaulted();
1815 if (function_declaration->isDeletedAsWritten()) fn->markDeletedAsWritten();
1816 if (
1817 (constructor_declaration && constructor_declaration->isExplicit()) ||
1818 (conversion_declaration && conversion_declaration->isExplicit())
1819 ) fn->markExplicit();
1820
1821 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
1822
1823 if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(FunctionNode::CAssign);
1824 else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(FunctionNode::MAssign);
1825
1826 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1827 const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
1828
1829 if (function_prototype) {
1830 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1831
1832 if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
1833 const std::string exception_specification_spelling =
1834 exception_specification.NoexceptExpr ? get_expression_as_string(
1835 exception_specification.NoexceptExpr,
1836 function_declaration->getASTContext()
1837 ) : "";
1838
1839 if (exception_specification_spelling != "false")
1840 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1841 }
1842 }
1843
1844 // Extract trailing requires clause.
1845 // From Clang 21 we get an AssociatedConstraint struct (upstream commit 49fd0bf35d2e).
1846 // Earlier Clang versions return Expr*.
1847#if LIBCLANG_VERSION_MAJOR >= 21
1848 if (const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1849 trailing_requires.ConstraintExpr) {
1850 QString requires_str = QString::fromStdString(
1851 get_expression_as_string(trailing_requires.ConstraintExpr,
1852 function_declaration->getASTContext()));
1853 fn->setTrailingRequiresClause(requires_str.simplified());
1854 }
1855#else
1856 if (const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1857 QString requires_str = QString::fromStdString(
1858 get_expression_as_string(trailing_requires,
1859 function_declaration->getASTContext()));
1860 fn->setTrailingRequiresClause(requires_str.simplified());
1861 }
1862#endif
1863
1864 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1865 if (refQualKind == CXRefQualifier_LValue)
1866 fn->setRef(true);
1867 else if (refQualKind == CXRefQualifier_RValue)
1868 fn->setRefRef(true);
1869 // For virtual functions, determine what it overrides
1870 // (except for destructor for which we do not want to classify as overridden)
1871 if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
1873
1874 Parameters &parameters = fn->parameters();
1875 parameters.clear();
1876 parameters.reserve(function_declaration->getNumParams());
1877
1878 for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
1879 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1880
1881 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1882 parameter_type,
1883 parameter_declaration->getASTContext()
1884 )));
1885
1886 if (!parameter_type.isCanonical())
1887 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1888 parameter_type.getCanonicalType(),
1889 parameter_declaration->getASTContext()
1890 )));
1891 }
1892
1893 if (parameters.count() > 0) {
1894 if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
1895 parameters.pop_back(); // remove the QPrivateSignal argument
1896 parameters.setPrivateSignal();
1897 }
1898 }
1899
1900 if (clang_isFunctionTypeVariadic(funcType))
1901 parameters.append(QStringLiteral("..."));
1902 readParameterNamesAndAttributes(fn, cursor);
1903
1904 if (declaration && declaration->getFriendObjectKind() != clang::Decl::FOK_None) {
1905 fn->setRelatedNonmember(true);
1906 Q_ASSERT(function_declaration);
1907 if (function_declaration->isThisDeclarationADefinition())
1908 fn->setHiddenFriend(true);
1909 }
1910}
1911
1912bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
1913{
1914 if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
1915 && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
1916 && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
1917 return false;
1918
1919 qsizetype lpIdx = spelling.indexOf(QChar('('));
1920 qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
1921 if (lpIdx <= 0 || rpIdx <= lpIdx)
1922 return false;
1923
1924 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1925 signature = signature.simplified();
1926 QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
1927
1928 static const QStringList attrs =
1929 QStringList() << "READ" << "MEMBER" << "WRITE"
1930 << "NOTIFY" << "CONSTANT" << "FINAL"
1931 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
1932 << "RESET" << "REVISION" << "SCRIPTABLE"
1933 << "STORED" << "USER";
1934
1935 // Find the location of the first attribute. All preceding parts
1936 // represent the property type + name.
1937 auto it = std::find_if(parts.cbegin(), parts.cend(),
1938 [](const QString &attr) -> bool {
1939 return attrs.contains(attr);
1940 });
1941
1942 if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
1943 return false;
1944
1945 QStringList typeParts;
1946 std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
1947 parts.erase(parts.cbegin(), it);
1948 QString name = typeParts.takeLast();
1949
1950 // Move the pointer operator(s) from name to type
1951 while (!name.isEmpty() && name.front() == QChar('*')) {
1952 typeParts.last().push_back(name.front());
1953 name.removeFirst();
1954 }
1955
1956 // Need at least READ or MEMBER + getter/member name
1957 if (parts.size() < 2 || name.isEmpty())
1958 return false;
1959
1960 auto *property = new PropertyNode(parent_, name);
1961 property->setAccess(Access::Public);
1962 property->setLocation(loc);
1963 property->setDataType(typeParts.join(QChar(' ')));
1964
1965 int i = 0;
1966 while (i < parts.size()) {
1967 const QString &key = parts.at(i++);
1968 // Keywords with no associated values
1969 if (key == "CONSTANT") {
1970 property->setConstant();
1971 } else if (key == "REQUIRED") {
1972 property->setRequired();
1973 }
1974 if (i < parts.size()) {
1975 QString value = parts.at(i++);
1976 if (key == "READ") {
1977 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
1978 } else if (key == "WRITE") {
1979 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
1980 property->setWritable(true);
1981 } else if (key == "MEMBER") {
1982 property->setWritable(true);
1983 } else if (key == "STORED") {
1984 property->setStored(value.toLower() == "true");
1985 } else if (key == "BINDABLE") {
1986 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
1987 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
1988 } else if (key == "RESET") {
1989 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
1990 } else if (key == "NOTIFY") {
1991 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
1992 }
1993 }
1994 }
1995 return true;
1996}
1997
1998/*!
1999 Given a comment at location \a loc, return a Node for this comment
2000 \a nextCommentLoc is the location of the next comment so the declaration
2001 must be inbetween.
2002 Returns nullptr if no suitable declaration was found between the two comments.
2003 */
2004Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
2005{
2006 ClangVisitor::SimpleLoc docloc;
2007 clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
2008 auto decl_it = declMap_.upperBound(docloc);
2009 if (decl_it == declMap_.end())
2010 return nullptr;
2011
2012 unsigned int declLine = decl_it.key().line;
2013 unsigned int nextCommentLine;
2014 clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
2015 if (nextCommentLine < declLine)
2016 return nullptr; // there is another comment before the declaration, ignore it.
2017
2018 // make sure the previous decl was finished.
2019 if (decl_it != declMap_.begin()) {
2020 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
2021 unsigned int prevDeclLine;
2022 clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
2023 if (prevDeclLine >= docloc.line) {
2024 // The previous declaration was still going. This is only valid if the previous
2025 // declaration is a parent of the next declaration.
2026 auto parent = clang_getCursorLexicalParent(*decl_it);
2027 if (!clang_equalCursors(parent, *(std::prev(decl_it))))
2028 return nullptr;
2029 }
2030 }
2031 auto *node = findNodeForCursor(qdb_, *decl_it);
2032 // borrow the parameter name from the definition
2033 if (node && node->isFunction(Genus::CPP))
2034 readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
2035 return node;
2036}
2037
2039 QDocDatabase* qdb,
2040 Config& config,
2041 const std::vector<QByteArray>& include_paths,
2042 const QList<QByteArray>& defines,
2043 std::optional<std::reference_wrapper<const PCHFile>> pch
2044) : m_qdb{qdb},
2047 m_pch{pch}
2048{
2049 m_allHeaders = config.getHeaderFiles();
2050 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2051}
2052
2053static const char *defaultArgs_[] = {
2054 "-std=c++20",
2055#ifndef Q_OS_WIN
2056 "-fPIC",
2057#else
2058 "-fms-compatibility-version=19",
2059#endif
2060 "-DQ_QDOC",
2061 "-DQ_CLANG_QDOC",
2062 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2063 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2064 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2065 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2066 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2067 "-Wno-constant-logical-operand",
2068 "-Wno-macro-redefined",
2069 "-Wno-nullability-completeness",
2070 "-fvisibility=default",
2071 "-ferror-limit=0",
2072 "-xc++"
2073};
2074
2075static std::vector<const char *> toConstCharPointers(const std::vector<QByteArray> &args)
2076{
2077 std::vector<const char *> pointers;
2078 pointers.reserve(args.size());
2079 for (const auto &arg : args)
2080 pointers.push_back(arg.constData());
2081 return pointers;
2082}
2083
2084/*!
2085 Load the default arguments and the defines into \a args.
2086 Clear \a args first.
2087 */
2088void getDefaultArgs(const QList<QByteArray>& defines, std::vector<QByteArray>& args)
2089{
2090 args.clear();
2091 for (const char *arg : defaultArgs_)
2092 args.emplace_back(arg);
2093
2094 // Add the defines from the qdocconf file.
2095 for (const auto &p : std::as_const(defines))
2096 args.push_back(p);
2097}
2098
2099/*!
2100 Load the include paths into \a args.
2101 */
2103 const std::vector<QByteArray>& include_paths,
2104 std::vector<QByteArray>& args
2105) {
2106 if (include_paths.empty()) {
2107 qCWarning(lcQdoc) << "No include paths provided."
2108 << "Set 'includepaths' in the qdocconf file"
2109 << "or pass -I flags on the command line."
2110 << "C++ parsing may produce incomplete results.";
2111 } else {
2112 args.insert(args.end(), include_paths.begin(), include_paths.end());
2113 }
2114}
2115
2116/*!
2117 Building the PCH must be possible when there are no .cpp
2118 files, so it is moved here to its own member function, and
2119 it is called after the list of header files is complete.
2120 */
2122 QDocDatabase* qdb,
2123 QString module_header,
2124 const std::set<Config::HeaderFilePath>& all_headers,
2125 const std::vector<QByteArray>& include_paths,
2126 const QList<QByteArray>& defines,
2127 const InclusionPolicy& policy
2128) {
2129 static std::vector<QByteArray> arguments{};
2130
2131 if (module_header.isEmpty()) return std::nullopt;
2132
2133 getDefaultArgs(defines, arguments);
2134 getMoreArgs(include_paths, arguments);
2135
2136 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2137 | CXTranslationUnit_SkipFunctionBodies
2138 | CXTranslationUnit_KeepGoing);
2139
2140 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2141
2142 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
2143 if (!pch_directory.isValid()) return std::nullopt;
2144
2145 const QByteArray module = module_header.toUtf8();
2146 QByteArray header;
2147
2148 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
2149 // A predicate for std::find_if() to locate a path to the module's header
2150 // (e.g. QtGui/QtGui) to be used as pre-compiled header
2151 struct FindPredicate
2152 {
2153 enum SearchType { Any, Module };
2154 QByteArray &candidate_;
2155 const QByteArray &module_;
2156 SearchType type_;
2157 FindPredicate(QByteArray &candidate, const QByteArray &module,
2158 SearchType type = Any)
2159 : candidate_(candidate), module_(module), type_(type)
2160 {
2161 }
2162
2163 bool operator()(const QByteArray &p) const
2164 {
2165 if (type_ != Any && !p.endsWith(module_))
2166 return false;
2167 candidate_ = p + "/";
2168 candidate_.append(module_);
2169 if (p.startsWith("-I"))
2170 candidate_ = candidate_.mid(2);
2171 return QFile::exists(QString::fromUtf8(candidate_));
2172 }
2173 };
2174
2175 // First, search for an include path that contains the module name, then any path
2176 QByteArray candidate;
2177 auto it = std::find_if(include_paths.begin(), include_paths.end(),
2178 FindPredicate(candidate, module, FindPredicate::Module));
2179 if (it == include_paths.end())
2180 it = std::find_if(include_paths.begin(), include_paths.end(),
2181 FindPredicate(candidate, module, FindPredicate::Any));
2182 if (it != include_paths.end())
2183 header = std::move(candidate);
2184
2185 if (header.isEmpty()) {
2186 qWarning() << "(qdoc) Could not find the module header in include paths for module"
2187 << module << " (include paths: " << include_paths << ")";
2188 qWarning() << " Artificial module header built from header dirs in qdocconf "
2189 "file";
2190 }
2191 arguments.push_back("-xc++");
2192
2193 TranslationUnit tu;
2194
2195 QString tmpHeader = pch_directory.path() + "/" + module;
2196 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2197 QTextStream out(&tmpHeaderFile);
2198 if (header.isEmpty()) {
2199 for (const auto& [header_path, header_name] : all_headers) {
2200 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2201
2202 // Conditionally include private headers based on showInternal setting
2203 if (header_name.endsWith("_p.h"_L1))
2204 shouldInclude = shouldInclude && policy.showInternal;
2205
2206 if (shouldInclude) {
2207 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2208 }
2209 }
2210 } else {
2211 QFileInfo headerFile(header);
2212 if (!headerFile.exists()) {
2213 qWarning() << "Could not find module header file" << header;
2214 return std::nullopt;
2215 }
2216
2217 out << "#include \"" << header << "\"\n";
2218
2219 if (policy.showInternal) {
2220 for (const auto& [header_path, header_name] : all_headers) {
2221 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2222 if (header_name.endsWith("_p.h"_L1) && shouldInclude)
2223 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2224 }
2225 }
2226 }
2227 }
2228
2229 const auto argPointers = toConstCharPointers(arguments);
2230 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2231 CXErrorCode err =
2232 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2233 static_cast<int>(argPointers.size()), nullptr, 0,
2234 flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
2235 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
2236 << ") returns" << err;
2237
2239
2240 if (err || !tu) {
2241 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
2242 return std::nullopt;
2243 }
2244
2245 QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
2246 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2247 clang_defaultSaveOptions(tu));
2248 if (error) {
2249 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
2250 return std::nullopt;
2251 }
2252
2253 // Visit the header now, as token from pre-compiled header won't be visited
2254 // later
2255 CXCursor cur = clang_getTranslationUnitCursor(tu);
2256 auto &config = Config::instance();
2257 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2258 visitor.visitChildren(cur);
2259 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
2260
2261 return std::make_optional(PCHFile{std::move(pch_directory), std::move(pch_name)});
2262}
2263
2264static float getUnpatchedVersion(QString t)
2265{
2266 if (t.count(QChar('.')) > 1)
2267 t.truncate(t.lastIndexOf(QChar('.')));
2268 return t.toFloat();
2269}
2270
2271/*!
2272 Get ready to parse the C++ cpp file identified by \a filePath
2273 and add its parsed contents to the database. \a location is
2274 used for reporting errors.
2275
2276 If parsing C++ header file as source, do not use the precompiled
2277 header as the source file itself is likely already included in the
2278 PCH and therefore interferes visiting the TU's children.
2279 */
2280ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
2281{
2282 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2283 | CXTranslationUnit_SkipFunctionBodies
2284 | CXTranslationUnit_KeepGoing);
2285
2286 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2287
2288 getDefaultArgs(m_defines, m_args);
2289 if (m_pch && !filePath.endsWith(".mm")
2290 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2291 m_args.push_back("-w");
2292 m_args.push_back("-include-pch");
2293 m_args.push_back((*m_pch).get().name);
2294 }
2295 getMoreArgs(m_includePaths, m_args);
2296
2297 TranslationUnit tu;
2298 const auto argPointers = toConstCharPointers(m_args);
2299 const QByteArray filePathLocal = filePath.toLocal8Bit();
2300 CXErrorCode err =
2301 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2302 static_cast<int>(argPointers.size()), nullptr, 0, flags_, &tu.tu);
2303 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
2304 << ") returns" << err;
2306
2307 if (err || !tu) {
2308 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
2309 return {};
2310 }
2311
2312 ParsedCppFileIR parse_result{};
2313
2314 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2315 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2316 visitor.visitChildren(tuCur);
2317
2318 CXToken *tokens;
2319 unsigned int numTokens = 0;
2320 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2321 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2322
2323 for (unsigned int i = 0; i < numTokens; ++i) {
2324 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2325 continue;
2326 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2327 if (!comment.startsWith("/*!"))
2328 continue;
2329
2330 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2331 auto loc = fromCXSourceLocation(commentLoc);
2332 auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
2333 Doc::trimCStyleComment(loc, comment);
2334
2335 // Doc constructor parses the comment.
2336 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2337 if (hasTooManyTopics(doc))
2338 continue;
2339
2340 if (doc.topicsUsed().isEmpty()) {
2341 Node *n = nullptr;
2342 if (i + 1 < numTokens) {
2343 // Try to find the next declaration.
2344 CXSourceLocation nextCommentLoc = commentLoc;
2345 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2346 ++i; // already skip all the tokens that are not comments
2347 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2348 n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
2349 }
2350
2351 if (n) {
2352 parse_result.tied.emplace_back(TiedDocumentation{doc, n});
2353 } else if (CodeParser::isWorthWarningAbout(doc)) {
2354 bool future = false;
2355 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
2356 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
2357 if (getUnpatchedVersion(std::move(sinceVersion)) >
2358 getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
2359 future = true;
2360 }
2361 if (!future) {
2362 doc.location().warning(
2363 QStringLiteral("Cannot tie this documentation to anything"),
2364 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
2365 "topic command (e.g., '\\%1', '\\%2') in the "
2366 "comment and qdoc could not associate the "
2367 "declaration or definition following the "
2368 "comment with a documented entity.")
2369 .arg(COMMAND_FN, COMMAND_PAGE));
2370 }
2371 }
2372 } else {
2373 parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
2374
2375 CXCursor cur = clang_getCursor(tu, commentLoc);
2376 while (true) {
2377 CXCursorKind kind = clang_getCursorKind(cur);
2378 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2379 break;
2380 if (kind == CXCursor_Namespace) {
2381 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2382 }
2383 cur = clang_getCursorLexicalParent(cur);
2384 }
2385 }
2386 }
2387
2388 clang_disposeTokens(tu, tokens, numTokens);
2389 m_namespaceScope.clear();
2390 s_fn.clear();
2391
2392 return parse_result;
2393}
2394
2395/*!
2396 Use clang to parse the function signature from a function
2397 command. \a location is used for reporting errors. \a fnSignature
2398 is the string to parse. It is always a function decl.
2399 \a idTag is the optional bracketed argument passed to \\fn, or
2400 an empty string.
2401 \a context is a string list representing the scope (namespaces)
2402 under which the function is declared.
2403
2404 Returns a variant that's either a Node instance tied to the
2405 function declaration, or a parsing failure for later processing.
2406 */
2407std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
2408 const QString &idTag, QStringList context)
2409{
2410 Node *fnNode = nullptr;
2411 /*
2412 If the \fn command begins with a tag, then don't try to
2413 parse the \fn command with clang. Use the tag to search
2414 for the correct function node. It is an error if it can
2415 not be found. Return 0 in that case.
2416 */
2417 if (!idTag.isEmpty()) {
2418 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2419 if (!fnNode) {
2420 location.error(
2421 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
2422 } else {
2423 /*
2424 The function node was found. Use the formal
2425 parameter names from the \fn command, because
2426 they will be the names used in the documentation.
2427 */
2428 auto *fn = static_cast<FunctionNode *>(fnNode);
2429 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
2430 if (leftParenSplit.size() > 1) {
2431 QStringList rightParenSplit = leftParenSplit[1].split(')');
2432 if (!rightParenSplit.empty()) {
2433 QString params = rightParenSplit[0];
2434 if (!params.isEmpty()) {
2435 QStringList commaSplit = params.split(',');
2436 Parameters &parameters = fn->parameters();
2437 if (parameters.count() == commaSplit.size()) {
2438 for (int i = 0; i < parameters.count(); ++i) {
2439 QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
2440 if (blankSplit.size() > 1) {
2441 QString pName = blankSplit.last();
2442 // Remove any non-letters from the start of parameter name
2443 auto it = std::find_if(std::begin(pName), std::end(pName),
2444 [](const QChar &c) { return c.isLetter(); });
2445 parameters[i].setName(
2446 pName.remove(0, std::distance(std::begin(pName), it)));
2447 }
2448 }
2449 }
2450 }
2451 }
2452 }
2453 }
2454 return fnNode;
2455 }
2456 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2457 | CXTranslationUnit_SkipFunctionBodies
2458 | CXTranslationUnit_KeepGoing);
2459
2460 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2461
2462 getDefaultArgs(m_defines, m_args);
2463
2464 if (m_pch) {
2465 m_args.push_back("-w");
2466 m_args.push_back("-include-pch");
2467 m_args.push_back((*m_pch).get().name);
2468 }
2469
2470 TranslationUnit tu;
2471 QByteArray s_fn{};
2472 for (const auto &ns : std::as_const(context))
2473 s_fn.prepend("namespace " + ns.toUtf8() + " {");
2474 s_fn += fnSignature.toUtf8();
2475 if (!s_fn.endsWith(";"))
2476 s_fn += "{ }";
2477 s_fn.append(context.size(), '}');
2478
2479 const char *dummyFileName = fnDummyFileName;
2480 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2481 static_cast<unsigned long>(s_fn.size()) };
2482 const auto argPointers = toConstCharPointers(m_args);
2483 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2484 int(argPointers.size()), &unsavedFile, 1, flags, &tu.tu);
2485 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
2486 << ") returns" << err;
2488 if (err || !tu) {
2489 location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
2490 return fnNode;
2491 } else {
2492 /*
2493 Always visit the tu if one is constructed, because
2494 it might be possible to find the correct node, even
2495 if clang detected diagnostics. Only bother to report
2496 the diagnostics if they stop us finding the node.
2497 */
2498 CXCursor cur = clang_getTranslationUnitCursor(tu);
2499 auto &config = Config::instance();
2500 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2501 bool ignoreSignature = false;
2502 visitor.visitFnArg(cur, &fnNode, ignoreSignature);
2503
2504 if (!fnNode) {
2505 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2506 const auto &config = Config::instance();
2507 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2508 return FnMatchError{ fnSignature, location };
2509 }
2510 }
2511 }
2512 return fnNode;
2513}
2514
2515QT_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 void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName, const clang::FunctionDecl *func_decl, NodeVector &candidates)
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
static std::optional< SfinaeConstraint > detect_sfinae_constraint(const clang::NonTypeTemplateParmDecl *param)
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::vector< const char * > toConstCharPointers(const std::vector< QByteArray > &args)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static bool is_enable_if_name(const std::string &qualified_name)
static float getUnpatchedVersion(QString t)
void getMoreArgs(const std::vector< QByteArray > &include_paths, std::vector< QByteArray > &args)
Load the include paths into args.
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< QByteArray > &args)
Load the default arguments and the defines into args.
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
Returns a string representing the name of type as if it was referred to at the end of the translation...
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
QDocDatabase * qdocDB()
ClangVisitor(QDocDatabase *qdb, const std::set< Config::HeaderFilePath > &allHeaders, const Config::InternalFilePatterns &internalFilePatterns)
CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
The ClassNode represents a C++ class.
Definition classnode.h:23
static bool isWorthWarningAbout(const Doc &doc)
Test for whether a doc comment warrants warnings.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h: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