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 QmlNativeTypeAttribute detectQmlNativeTypeAttribute(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 declarations specific to QML types:
1307
1308 \details {QML_SINGLETON macro}
1309 Returns QmlNativeTypeAttribute::Singleton if the macro is detected.
1310
1311 The \e QML_SINGLETON macro expands to multiple items including:
1312 \list
1313 \li \c {Q_CLASSINFO("QML.Singleton", "true")}
1314 \li \c {enum class QmlIsSingleton}
1315 \endlist
1316 \enddetails
1317
1318 \details {QML_UNCREATABLE macro}
1319 Returns ClassNode::QmlNativeTypeAttribute::Uncreatable if the macro is detected.
1320
1321 The \e QML_UNCREATABLE macro expands to multiple items including:
1322 \list
1323 \li \c {Q_CLASSINFO("QML.Creatable", "false")}
1324 \li \c {enum class QmlIsUncreatable}
1325 \endlist
1326 \enddetails
1327
1328 This method looks for the above expansion artifacts to detect the macros.
1329 If no artifacts are found, returns QmlNativeTypeAttribute::None
1330 (that is, a standard instantiable QML type).
1331*/
1332QmlNativeTypeAttribute ClangVisitor::detectQmlNativeTypeAttribute(CXCursor cursor)
1333{
1335
1336 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
1337 // Look for Q_CLASSINFO calls that indicate QML.Singleton or QML.Creatable = false
1338 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1339 CXSourceRange range = clang_getCursorExtent(child);
1340 QString sourceText = getSpelling(range);
1341 static const QRegularExpression qmlClassInfoPattern(
1342 R"(Q_CLASSINFO\s*\‍(\s*["\']QML\.(Singleton|Creatable)["\']\s*,\s*["\'](true|false)["\']\s*\‍))");
1343 const auto match = qmlClassInfoPattern.match(sourceText);
1344 if (match.hasMatch()) {
1345 if (match.captured(1) == "Singleton"_L1 && match.captured(2) == "true"_L1) {
1347 return CXChildVisit_Break;
1348 } else if (match.captured(1) == "Creatable"_L1 && match.captured(2) == "false"_L1) {
1350 return CXChildVisit_Break;
1351 }
1352 }
1353 }
1354
1355 // Also check for enum class QmlIsSingleton which is part of the macro expansion
1356 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1357 QString spelling = fromCXString(clang_getCursorSpelling(child));
1358 if (spelling == "QmlIsSingleton"_L1) {
1360 return CXChildVisit_Break;
1361 } else if (spelling == "QmlIsUncreatable"_L1) {
1363 return CXChildVisit_Break;
1364 }
1365 }
1366
1367 return CXChildVisit_Continue;
1368 });
1369
1370 return attr;
1371}
1372
1373/*!
1374 Visits a cursor in the .cpp file.
1375 This fills the declMap_
1376 */
1377CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1378{
1379 auto kind = clang_getCursorKind(cursor);
1380 if (clang_isDeclaration(kind)) {
1381 SimpleLoc l;
1382 clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
1383 declMap_.insert(l, cursor);
1384 return CXChildVisit_Recurse;
1385 }
1386 return CXChildVisit_Continue;
1387}
1388
1389/*!
1390 If the semantic and lexical parent cursors of \a cursor are
1391 not the same, find the Aggregate node for the semantic parent
1392 cursor and return it. Otherwise return the current parent.
1393 */
1394Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
1395{
1396 CXCursor sp = clang_getCursorSemanticParent(cursor);
1397 CXCursor lp = clang_getCursorLexicalParent(cursor);
1398 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1399 Node *spn = findNodeForCursor(qdb_, sp);
1400 if (spn && spn->isAggregate()) {
1401 return static_cast<Aggregate *>(spn);
1402 }
1403 }
1404 return parent_;
1405}
1406
1407CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
1408 bool &ignoreSignature)
1409{
1410 switch (clang_getCursorKind(cursor)) {
1411 case CXCursor_Namespace:
1412 return CXChildVisit_Recurse;
1413 case CXCursor_FunctionDecl:
1414 case CXCursor_FunctionTemplate:
1415 case CXCursor_CXXMethod:
1416 case CXCursor_Constructor:
1417 case CXCursor_Destructor:
1418 case CXCursor_ConversionFunction: {
1419 ignoreSignature = false;
1420 if (ignoredSymbol(functionName(cursor))) {
1421 *fnNode = nullptr;
1422 ignoreSignature = true;
1423 } else {
1424 *fnNode = findNodeForCursor(qdb_, cursor);
1425 if (*fnNode) {
1426 if ((*fnNode)->isFunction(Genus::CPP)) {
1427 auto *fn = static_cast<FunctionNode *>(*fnNode);
1428 readParameterNamesAndAttributes(fn, cursor);
1429
1430 const clang::Decl* declaration = get_cursor_declaration(cursor);
1431 assert(declaration);
1432 if (const auto function_declaration = declaration->getAsFunction()) {
1433 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1434 if (llvm::dyn_cast_if_present<clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1435 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1436 }
1437 }
1438 } else { // Possibly an implicitly generated special member
1439 QString name = functionName(cursor);
1440 if (ignoredSymbol(name))
1441 return CXChildVisit_Continue;
1442 Aggregate *semanticParent = getSemanticParent(cursor);
1443 if (semanticParent && semanticParent->isClass()) {
1444 auto *candidate = new FunctionNode(nullptr, name);
1445 processFunction(candidate, cursor);
1446 if (!candidate->isSpecialMemberFunction()) {
1447 delete candidate;
1448 return CXChildVisit_Continue;
1449 }
1450 candidate->setImplicitlyGenerated(true);
1451 semanticParent->addChild(*fnNode = candidate);
1452 }
1453 }
1454 }
1455 break;
1456 }
1457 default:
1458 break;
1459 }
1460 return CXChildVisit_Continue;
1461}
1462
1463CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1464{
1465 auto kind = clang_getCursorKind(cursor);
1466
1467 switch (kind) {
1468 case CXCursor_TypeAliasTemplateDecl:
1469 case CXCursor_TypeAliasDecl: {
1470 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1471 QString aliasedType;
1472
1473 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1474 ? llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor))
1475 : nullptr;
1476
1477 if (kind == CXCursor_TypeAliasTemplateDecl) {
1478 // For template aliases, get the underlying TypeAliasDecl from the TemplateDecl
1479 if (const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1480 if (const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1481 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1482 aliasedType = QString::fromStdString(underlyingType.getAsString());
1483 }
1484 }
1485 } else {
1486 // For non-template aliases, get the underlying type via C API
1487 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1488 if (aliasedCXType.kind != CXType_Invalid) {
1489 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1490 }
1491 }
1492
1493 if (!aliasedType.isEmpty()) {
1494 auto *ta = new TypeAliasNode(parent_, aliasName, aliasedType);
1495 ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1496 ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1497
1498 if (templateDecl)
1499 ta->setTemplateDecl(get_template_declaration(templateDecl));
1500 }
1501 return CXChildVisit_Continue;
1502 }
1503 case CXCursor_StructDecl:
1504 case CXCursor_UnionDecl:
1505 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
1506 return CXChildVisit_Continue;
1507 Q_FALLTHROUGH();
1508 case CXCursor_ClassTemplate:
1509 Q_FALLTHROUGH();
1510 case CXCursor_ClassDecl: {
1511 if (!clang_isCursorDefinition(cursor))
1512 return CXChildVisit_Continue;
1513
1514 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1515 return CXChildVisit_Continue;
1516
1517 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1518
1519 Aggregate *semanticParent = getSemanticParent(cursor);
1520 if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
1521 return CXChildVisit_Continue;
1522 }
1523
1524 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1525 clang_getTemplateCursorKind(cursor) : kind;
1526
1528 if (actualKind == CXCursor_StructDecl)
1529 type = NodeType::Struct;
1530 else if (actualKind == CXCursor_UnionDecl)
1531 type = NodeType::Union;
1532
1533 auto *classe = new ClassNode(type, semanticParent, className);
1534 classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1535
1536 auto location = fromCXSourceLocation(clang_getCursorLocation(cursor));
1537 classe->setLocation(location);
1538
1539 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1540 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1541 if (Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1542 classe->setStatus(Status::Internal);
1543 }
1544
1545 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1546 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1547
1548 if (kind == CXCursor_ClassTemplate) {
1549 auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
1550 classe->setTemplateDecl(get_template_declaration(template_declaration));
1551 }
1552
1553 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1554 return visitChildren(cursor);
1555 }
1556 case CXCursor_CXXBaseSpecifier: {
1557 if (!parent_->isClassNode())
1558 return CXChildVisit_Continue;
1559 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1560 auto type = clang_getCursorType(cursor);
1561 auto baseCursor = clang_getTypeDeclaration(type);
1562 auto baseNode = findNodeForCursor(qdb_, baseCursor);
1563 auto classe = static_cast<ClassNode *>(parent_);
1564 if (baseNode == nullptr || !baseNode->isClassNode()) {
1565 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1566 classe->addUnresolvedBaseClass(access,
1567 bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
1568 return CXChildVisit_Continue;
1569 }
1570 auto baseClasse = static_cast<ClassNode *>(baseNode);
1571 classe->addResolvedBaseClass(access, baseClasse);
1572 return CXChildVisit_Continue;
1573 }
1574 case CXCursor_Namespace: {
1575 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1576 NamespaceNode *ns = nullptr;
1577 if (parent_)
1578 ns = static_cast<NamespaceNode *>(
1579 parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
1580 if (!ns) {
1581 ns = new NamespaceNode(parent_, namespaceName);
1582 ns->setAccess(Access::Public);
1583 ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1584 }
1585 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1586 return visitChildren(cursor);
1587 }
1588 case CXCursor_FunctionTemplate:
1589 Q_FALLTHROUGH();
1590 case CXCursor_FunctionDecl:
1591 case CXCursor_CXXMethod:
1592 case CXCursor_Constructor:
1593 case CXCursor_Destructor:
1594 case CXCursor_ConversionFunction: {
1595 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1596 return CXChildVisit_Continue;
1597 QString name = functionName(cursor);
1598 if (ignoredSymbol(name))
1599 return CXChildVisit_Continue;
1600 // constexpr constructors generate also a global instance; ignore
1601 if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
1602 return CXChildVisit_Continue;
1603
1604 auto *fn = new FunctionNode(parent_, name);
1605 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1606 if (!clang_Range_isNull(range)) {
1607 QString comment = getSpelling(range);
1608 if (comment.startsWith("//!")) {
1609 qsizetype tag = comment.indexOf(QChar('['));
1610 if (tag > 0) {
1611 qsizetype end = comment.indexOf(QChar(']'), ++tag);
1612 if (end > 0)
1613 fn->setTag(comment.mid(tag, end - tag));
1614 }
1615 }
1616 }
1617
1618 processFunction(fn, cursor);
1619
1620 if (kind == CXCursor_FunctionTemplate) {
1621 auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
1622 fn->setTemplateDecl(get_template_declaration(template_declaration));
1623 }
1624
1625 if (!clang_Location_isInSystemHeader(loc))
1626 autoGenerateSmfDoc(fn, parent_->name());
1627
1628 return CXChildVisit_Continue;
1629 }
1630#if CINDEX_VERSION >= 36
1631 case CXCursor_FriendDecl: {
1632 return visitChildren(cursor);
1633 }
1634#endif
1635 case CXCursor_EnumDecl: {
1636 auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1637 if (en && en->items().size())
1638 return CXChildVisit_Continue; // Was already parsed, probably in another TU
1639
1640 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1641
1642 if (clang_Cursor_isAnonymous(cursor)) {
1643 enumTypeName = "anonymous";
1644 // Generate a unique name to enable auto-tying doc comments in headers
1645 // to anonymous enum declarations
1646 if (Config::instance().get(CONFIG_DOCUMENTATIONINHEADERS).asBool())
1647 enumTypeName = Utilities::uniqueIdentifier(fromCXSourceLocation(clang_getCursorLocation(cursor)), enumTypeName);
1648 if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
1649 Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
1650 if (n)
1651 en = static_cast<EnumNode *>(n);
1652 }
1653 }
1654 if (!en) {
1655 en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1656 en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1657 en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1658 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1659 }
1660
1661 // Enum values
1662 visitChildrenLambda(cursor, [&](CXCursor cur) {
1663 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1664 return CXChildVisit_Continue;
1665
1666 QString value;
1667 visitChildrenLambda(cur, [&](CXCursor cur) {
1668 if (clang_isExpression(clang_getCursorKind(cur))) {
1669 value = getSpelling(clang_getCursorExtent(cur));
1670 return CXChildVisit_Break;
1671 }
1672 return CXChildVisit_Continue;
1673 });
1674 if (value.isEmpty()) {
1675 QLatin1String hex("0x");
1676 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1677 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1678 } else {
1679 value = QString::number(clang_getEnumConstantDeclValue(cur));
1680 }
1681 }
1682
1683 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1684 return CXChildVisit_Continue;
1685 });
1686 return CXChildVisit_Continue;
1687 }
1688 case CXCursor_FieldDecl:
1689 case CXCursor_VarDecl: {
1690 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1691 return CXChildVisit_Continue;
1692
1693 auto value_declaration =
1694 llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
1695 assert(value_declaration);
1696
1697 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1698 auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1699
1700 var->setAccess(access);
1701 var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1702 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1703 value_declaration->getType(),
1704 value_declaration->getASTContext()
1705 )));
1706 var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
1707
1708 return CXChildVisit_Continue;
1709 }
1710 case CXCursor_TypedefDecl: {
1711 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1712 return CXChildVisit_Continue;
1713 auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1714 td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1715 td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1716 // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
1717 visitChildrenLambda(cursor, [&](CXCursor cur) {
1718 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1719 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
1720 return CXChildVisit_Continue;
1721 // Found QFlags<XXX>
1722 visitChildrenLambda(cursor, [&](CXCursor cur) {
1723 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1724 return CXChildVisit_Continue;
1725 auto *en =
1726 findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
1727 if (en && en->isEnumType())
1728 static_cast<EnumNode *>(en)->setFlagsType(td);
1729 return CXChildVisit_Break;
1730 });
1731 return CXChildVisit_Break;
1732 });
1733 return CXChildVisit_Continue;
1734 }
1735 default:
1736 if (clang_isDeclaration(kind) && parent_->isClassNode()) {
1737 // may be a property macro or a static_assert
1738 // which is not exposed from the clang API
1739 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1741 }
1742 return CXChildVisit_Continue;
1743 }
1744}
1745
1746void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
1747{
1748 Parameters &parameters = fn->parameters();
1749 // Visit the parameters and attributes
1750 int i = 0;
1751 visitChildrenLambda(cursor, [&](CXCursor cur) {
1752 auto kind = clang_getCursorKind(cur);
1753 if (kind == CXCursor_AnnotateAttr) {
1754 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1755 if (annotation == QLatin1String("qt_slot")) {
1757 } else if (annotation == QLatin1String("qt_signal")) {
1759 }
1760 if (annotation == QLatin1String("qt_invokable"))
1761 fn->setInvokable(true);
1762 } else if (kind == CXCursor_CXXOverrideAttr) {
1763 fn->setOverride(true);
1764 } else if (kind == CXCursor_ParmDecl) {
1765 if (i >= parameters.count())
1766 return CXChildVisit_Break; // Attributes comes before parameters so we can break.
1767
1768 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1769 parameters[i].setName(name);
1770
1771 const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
1772 Q_ASSERT(parameter_declaration);
1773
1774 std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
1775
1776 if (!default_value.empty())
1777 parameters[i].setDefaultValue(QString::fromStdString(default_value));
1778
1779 ++i;
1780 }
1781 return CXChildVisit_Continue;
1782 });
1783}
1784
1785void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
1786{
1787 CXCursorKind kind = clang_getCursorKind(cursor);
1788 CXType funcType = clang_getCursorType(cursor);
1789 fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1790 fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1791 fn->setStatic(clang_CXXMethod_isStatic(cursor));
1792 fn->setConst(clang_CXXMethod_isConst(cursor));
1793 fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
1795 : clang_CXXMethod_isPureVirtual(cursor)
1798
1799 // REMARK: We assume that the following operations and casts are
1800 // generally safe.
1801 // Callers of those methods will generally check at the LibClang
1802 // level the kind of cursor we are dealing with and will pass on
1803 // only valid cursors that are of a function kind and that are at
1804 // least a declaration.
1805 //
1806 // Failure to do so implies a bug in the call chain and should be
1807 // dealt with as such.
1808 const clang::Decl* declaration = get_cursor_declaration(cursor);
1809
1810 assert(declaration);
1811
1812 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1813
1814 if (kind == CXCursor_Constructor
1815 // a constructor template is classified as CXCursor_FunctionTemplate
1816 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1818 else if (kind == CXCursor_Destructor)
1820 else if (kind != CXCursor_ConversionFunction)
1821 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1822 function_declaration->getReturnType(),
1823 function_declaration->getASTContext()
1824 )));
1825
1826 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
1827
1828 if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(Metaness::CCtor);
1829 else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(Metaness::MCtor);
1830
1831 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
1832
1833 if (function_declaration->isConstexpr()) fn->markConstexpr();
1834 if (function_declaration->isExplicitlyDefaulted()) fn->markExplicitlyDefaulted();
1835 if (function_declaration->isDeletedAsWritten()) fn->markDeletedAsWritten();
1836 if (
1837 (constructor_declaration && constructor_declaration->isExplicit()) ||
1838 (conversion_declaration && conversion_declaration->isExplicit())
1839 ) fn->markExplicit();
1840
1841 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
1842
1843 if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(Metaness::CAssign);
1844 else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(Metaness::MAssign);
1845
1846 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1847 const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
1848
1849 if (function_prototype) {
1850 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1851
1852 if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
1853 const std::string exception_specification_spelling =
1854 exception_specification.NoexceptExpr ? get_expression_as_string(
1855 exception_specification.NoexceptExpr,
1856 function_declaration->getASTContext()
1857 ) : "";
1858
1859 if (exception_specification_spelling != "false")
1860 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1861 }
1862 }
1863
1864 // Extract trailing requires clause.
1865 // From Clang 21 we get an AssociatedConstraint struct (upstream commit 49fd0bf35d2e).
1866 // Earlier Clang versions return Expr*.
1867#if LIBCLANG_VERSION_MAJOR >= 21
1868 if (const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1869 trailing_requires.ConstraintExpr) {
1870 QString requires_str = QString::fromStdString(
1871 get_expression_as_string(trailing_requires.ConstraintExpr,
1872 function_declaration->getASTContext()));
1873 fn->setTrailingRequiresClause(requires_str.simplified());
1874 }
1875#else
1876 if (const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1877 QString requires_str = QString::fromStdString(
1878 get_expression_as_string(trailing_requires,
1879 function_declaration->getASTContext()));
1880 fn->setTrailingRequiresClause(requires_str.simplified());
1881 }
1882#endif
1883
1884 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1885 if (refQualKind == CXRefQualifier_LValue)
1886 fn->setRef(true);
1887 else if (refQualKind == CXRefQualifier_RValue)
1888 fn->setRefRef(true);
1889 // For virtual functions, determine what it overrides
1890 // (except for destructor for which we do not want to classify as overridden)
1891 if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
1893
1894 Parameters &parameters = fn->parameters();
1895 parameters.clear();
1896 parameters.reserve(function_declaration->getNumParams());
1897
1898 for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
1899 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1900
1901 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1902 parameter_type,
1903 parameter_declaration->getASTContext()
1904 )));
1905
1906 if (!parameter_type.isCanonical())
1907 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1908 parameter_type.getCanonicalType(),
1909 parameter_declaration->getASTContext()
1910 )));
1911 }
1912
1913 if (parameters.count() > 0) {
1914 if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
1915 parameters.pop_back(); // remove the QPrivateSignal argument
1916 parameters.setPrivateSignal();
1917 }
1918 }
1919
1920 if (clang_isFunctionTypeVariadic(funcType))
1921 parameters.append(QStringLiteral("..."));
1922 readParameterNamesAndAttributes(fn, cursor);
1923
1924 if (declaration && declaration->getFriendObjectKind() != clang::Decl::FOK_None) {
1925 fn->setRelatedNonmember(true);
1926 Q_ASSERT(function_declaration);
1927
1928 const bool hasNamespaceScopeRedeclaration =
1929 std::any_of(function_declaration->redecls_begin(),
1930 function_declaration->redecls_end(),
1931 [](const clang::FunctionDecl *r) {
1932 return r->getFriendObjectKind() == clang::Decl::FOK_None;
1933 });
1934 if (!hasNamespaceScopeRedeclaration)
1935 fn->setHiddenFriend(true);
1936 }
1937}
1938
1939bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
1940{
1941 if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
1942 && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
1943 && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
1944 return false;
1945
1946 qsizetype lpIdx = spelling.indexOf(QChar('('));
1947 qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
1948 if (lpIdx <= 0 || rpIdx <= lpIdx)
1949 return false;
1950
1951 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1952 signature = signature.simplified();
1953 QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
1954
1955 static const QStringList attrs =
1956 QStringList() << "READ" << "MEMBER" << "WRITE"
1957 << "NOTIFY" << "CONSTANT" << "FINAL"
1958 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
1959 << "RESET" << "REVISION" << "SCRIPTABLE"
1960 << "STORED" << "USER";
1961
1962 // Find the location of the first attribute. All preceding parts
1963 // represent the property type + name.
1964 auto it = std::find_if(parts.cbegin(), parts.cend(),
1965 [](const QString &attr) -> bool {
1966 return attrs.contains(attr);
1967 });
1968
1969 if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
1970 return false;
1971
1972 QStringList typeParts;
1973 std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
1974 parts.erase(parts.cbegin(), it);
1975 QString name = typeParts.takeLast();
1976
1977 // Move the pointer operator(s) from name to type
1978 while (!name.isEmpty() && name.front() == QChar('*')) {
1979 typeParts.last().push_back(name.front());
1980 name.removeFirst();
1981 }
1982
1983 // Need at least READ or MEMBER + getter/member name
1984 if (parts.size() < 2 || name.isEmpty())
1985 return false;
1986
1987 auto *property = new PropertyNode(parent_, name);
1988 property->setAccess(Access::Public);
1989 property->setLocation(loc);
1990 property->setDataType(typeParts.join(QChar(' ')));
1991
1992 int i = 0;
1993 while (i < parts.size()) {
1994 const QString &key = parts.at(i++);
1995 // Keywords with no associated values
1996 if (key == "CONSTANT") {
1997 property->setConstant();
1998 } else if (key == "REQUIRED") {
1999 property->setRequired();
2000 }
2001 if (i < parts.size()) {
2002 QString value = parts.at(i++);
2003 if (key == "READ") {
2004 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
2005 } else if (key == "WRITE") {
2006 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
2007 property->setWritable(true);
2008 } else if (key == "MEMBER") {
2009 property->setWritable(true);
2010 } else if (key == "STORED") {
2011 property->setStored(value.toLower() == "true");
2012 } else if (key == "BINDABLE") {
2013 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
2014 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
2015 } else if (key == "RESET") {
2016 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
2017 } else if (key == "NOTIFY") {
2018 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
2019 }
2020 }
2021 }
2022 return true;
2023}
2024
2025/*!
2026 Given a comment at location \a loc, return a Node for this comment
2027 \a nextCommentLoc is the location of the next comment so the declaration
2028 must be inbetween.
2029 Returns nullptr if no suitable declaration was found between the two comments.
2030 */
2031Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
2032{
2033 ClangVisitor::SimpleLoc docloc;
2034 clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
2035 auto decl_it = declMap_.upperBound(docloc);
2036 if (decl_it == declMap_.end())
2037 return nullptr;
2038
2039 unsigned int declLine = decl_it.key().line;
2040 unsigned int nextCommentLine;
2041 clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
2042 if (nextCommentLine < declLine)
2043 return nullptr; // there is another comment before the declaration, ignore it.
2044
2045 // make sure the previous decl was finished.
2046 if (decl_it != declMap_.begin()) {
2047 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
2048 unsigned int prevDeclLine;
2049 clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
2050 if (prevDeclLine >= docloc.line) {
2051 // The previous declaration was still going. This is only valid if the previous
2052 // declaration is a parent of the next declaration.
2053 auto parent = clang_getCursorLexicalParent(*decl_it);
2054 if (!clang_equalCursors(parent, *(std::prev(decl_it))))
2055 return nullptr;
2056 }
2057 }
2058 auto *node = findNodeForCursor(qdb_, *decl_it);
2059 // borrow the parameter name from the definition
2060 if (node && node->isFunction(Genus::CPP))
2061 readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
2062 return node;
2063}
2064
2066 QDocDatabase* qdb,
2067 Config& config,
2068 const std::vector<QByteArray>& include_paths,
2069 const QList<QByteArray>& defines,
2070 std::optional<std::reference_wrapper<const PCHFile>> pch
2071) : m_qdb{qdb},
2074 m_pch{pch}
2075{
2076 m_allHeaders = config.getHeaderFiles();
2077 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2078}
2079
2080static const char *defaultArgs_[] = {
2081 "-std=c++20",
2082#ifndef Q_OS_WIN
2083 "-fPIC",
2084#else
2085 "-fms-compatibility-version=19",
2086#endif
2087 "-DQ_QDOC",
2088 "-DQ_CLANG_QDOC",
2089 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2090 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2091 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2092 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2093 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2094 "-Wno-constant-logical-operand",
2095 "-Wno-macro-redefined",
2096 "-Wno-nullability-completeness",
2097 "-fvisibility=default",
2098 "-ferror-limit=0",
2099 "-xc++"
2100};
2101
2102static std::vector<const char *> toConstCharPointers(const std::vector<QByteArray> &args)
2103{
2104 std::vector<const char *> pointers;
2105 pointers.reserve(args.size());
2106 for (const auto &arg : args)
2107 pointers.push_back(arg.constData());
2108 return pointers;
2109}
2110
2111/*!
2112 Load the default arguments and the defines into \a args.
2113 Clear \a args first.
2114 */
2115void getDefaultArgs(const QList<QByteArray>& defines, std::vector<QByteArray>& args)
2116{
2117 args.clear();
2118 for (const char *arg : defaultArgs_)
2119 args.emplace_back(arg);
2120
2121 // Add the defines from the qdocconf file.
2122 for (const auto &p : std::as_const(defines))
2123 args.push_back(p);
2124}
2125
2126/*!
2127 Load the include paths into \a args.
2128 */
2130 const std::vector<QByteArray>& include_paths,
2131 std::vector<QByteArray>& args
2132) {
2133 if (include_paths.empty()) {
2134 qCWarning(lcQdoc) << "No include paths provided."
2135 << "Set 'includepaths' in the qdocconf file"
2136 << "or pass -I flags on the command line."
2137 << "C++ parsing may produce incomplete results.";
2138 } else {
2139 args.insert(args.end(), include_paths.begin(), include_paths.end());
2140 }
2141}
2142
2143/*!
2144 Building the PCH must be possible when there are no .cpp
2145 files, so it is moved here to its own member function, and
2146 it is called after the list of header files is complete.
2147 */
2149 QDocDatabase* qdb,
2150 QString module_header,
2151 const std::set<Config::HeaderFilePath>& all_headers,
2152 const std::vector<QByteArray>& include_paths,
2153 const QList<QByteArray>& defines,
2154 const InclusionPolicy& policy
2155) {
2156 static std::vector<QByteArray> arguments{};
2157
2158 if (module_header.isEmpty()) return std::nullopt;
2159
2160 getDefaultArgs(defines, arguments);
2161 getMoreArgs(include_paths, arguments);
2162
2163 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2164 | CXTranslationUnit_SkipFunctionBodies
2165 | CXTranslationUnit_KeepGoing);
2166
2167 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2168
2169 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
2170 if (!pch_directory.isValid()) return std::nullopt;
2171
2172 const QByteArray module = module_header.toUtf8();
2173 QByteArray header;
2174
2175 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
2176 // A predicate for std::find_if() to locate a path to the module's header
2177 // (e.g. QtGui/QtGui) to be used as pre-compiled header
2178 struct FindPredicate
2179 {
2180 enum SearchType { Any, Module };
2181 QByteArray &candidate_;
2182 const QByteArray &module_;
2183 SearchType type_;
2184 FindPredicate(QByteArray &candidate, const QByteArray &module,
2185 SearchType type = Any)
2186 : candidate_(candidate), module_(module), type_(type)
2187 {
2188 }
2189
2190 bool operator()(const QByteArray &p) const
2191 {
2192 if (type_ != Any && !p.endsWith(module_))
2193 return false;
2194 candidate_ = p + "/";
2195 candidate_.append(module_);
2196 if (p.startsWith("-I"))
2197 candidate_ = candidate_.mid(2);
2198 return QFile::exists(QString::fromUtf8(candidate_));
2199 }
2200 };
2201
2202 // First, search for an include path that contains the module name, then any path
2203 QByteArray candidate;
2204 auto it = std::find_if(include_paths.begin(), include_paths.end(),
2205 FindPredicate(candidate, module, FindPredicate::Module));
2206 if (it == include_paths.end())
2207 it = std::find_if(include_paths.begin(), include_paths.end(),
2208 FindPredicate(candidate, module, FindPredicate::Any));
2209 if (it != include_paths.end())
2210 header = std::move(candidate);
2211
2212 if (header.isEmpty()) {
2213 qWarning() << "(qdoc) Could not find the module header in include paths for module"
2214 << module << " (include paths: " << include_paths << ")";
2215 qWarning() << " Artificial module header built from header dirs in qdocconf "
2216 "file";
2217 }
2218 arguments.push_back("-xc++");
2219
2220 TranslationUnit tu;
2221
2222 QString tmpHeader = pch_directory.path() + "/" + module;
2223 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2224 QTextStream out(&tmpHeaderFile);
2225 if (header.isEmpty()) {
2226 for (const auto& [header_path, header_name] : all_headers) {
2227 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2228
2229 // Conditionally include private headers based on showInternal setting
2230 if (header_name.endsWith("_p.h"_L1))
2231 shouldInclude = shouldInclude && policy.showInternal;
2232
2233 if (shouldInclude) {
2234 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2235 }
2236 }
2237 } else {
2238 QFileInfo headerFile(header);
2239 if (!headerFile.exists()) {
2240 qWarning() << "Could not find module header file" << header;
2241 return std::nullopt;
2242 }
2243
2244 out << "#include \"" << header << "\"\n";
2245
2246 if (policy.showInternal) {
2247 for (const auto& [header_path, header_name] : all_headers) {
2248 bool shouldInclude = !header_name.startsWith("moc_"_L1);
2249 if (header_name.endsWith("_p.h"_L1) && shouldInclude)
2250 out << "#include \"" << header_path << "/" << header_name << "\"\n";
2251 }
2252 }
2253 }
2254 }
2255
2256 const auto argPointers = toConstCharPointers(arguments);
2257 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2258 CXErrorCode err =
2259 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2260 static_cast<int>(argPointers.size()), nullptr, 0,
2261 flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
2262 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
2263 << ") returns" << err;
2264
2266
2267 if (err || !tu) {
2268 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
2269 return std::nullopt;
2270 }
2271
2272 QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
2273 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2274 clang_defaultSaveOptions(tu));
2275 if (error) {
2276 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
2277 return std::nullopt;
2278 }
2279
2280 // Visit the header now, as token from pre-compiled header won't be visited
2281 // later
2282 CXCursor cur = clang_getTranslationUnitCursor(tu);
2283 auto &config = Config::instance();
2284 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2285 visitor.visitChildren(cur);
2286 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
2287
2288 return std::make_optional(PCHFile{std::move(pch_directory), std::move(pch_name)});
2289}
2290
2291static float getUnpatchedVersion(QString t)
2292{
2293 if (t.count(QChar('.')) > 1)
2294 t.truncate(t.lastIndexOf(QChar('.')));
2295 return t.toFloat();
2296}
2297
2298/*!
2299 Get ready to parse the C++ cpp file identified by \a filePath
2300 and add its parsed contents to the database. \a location is
2301 used for reporting errors.
2302
2303 If parsing C++ header file as source, do not use the precompiled
2304 header as the source file itself is likely already included in the
2305 PCH and therefore interferes visiting the TU's children.
2306 */
2307ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
2308{
2309 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2310 | CXTranslationUnit_SkipFunctionBodies
2311 | CXTranslationUnit_KeepGoing);
2312
2313 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2314
2315 getDefaultArgs(m_defines, m_args);
2316 if (m_pch && !filePath.endsWith(".mm")
2317 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2318 m_args.push_back("-w");
2319 m_args.push_back("-include-pch");
2320 m_args.push_back((*m_pch).get().name);
2321 }
2322 getMoreArgs(m_includePaths, m_args);
2323
2324 TranslationUnit tu;
2325 const auto argPointers = toConstCharPointers(m_args);
2326 const QByteArray filePathLocal = filePath.toLocal8Bit();
2327 CXErrorCode err =
2328 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2329 static_cast<int>(argPointers.size()), nullptr, 0, flags_, &tu.tu);
2330 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
2331 << ") returns" << err;
2333
2334 if (err || !tu) {
2335 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
2336 return {};
2337 }
2338
2339 ParsedCppFileIR parse_result{};
2340
2341 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2342 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2343 visitor.visitChildren(tuCur);
2344
2345 CXToken *tokens;
2346 unsigned int numTokens = 0;
2347 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2348 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2349
2350 for (unsigned int i = 0; i < numTokens; ++i) {
2351 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2352 continue;
2353 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2354 if (!comment.startsWith("/*!"))
2355 continue;
2356
2357 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2358 auto loc = fromCXSourceLocation(commentLoc);
2359 auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
2360 Doc::trimCStyleComment(loc, comment);
2361
2362 // Doc constructor parses the comment.
2363 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2364 if (hasTooManyTopics(doc))
2365 continue;
2366
2367 if (doc.topicsUsed().isEmpty()) {
2368 Node *n = nullptr;
2369 if (i + 1 < numTokens) {
2370 // Try to find the next declaration.
2371 CXSourceLocation nextCommentLoc = commentLoc;
2372 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2373 ++i; // already skip all the tokens that are not comments
2374 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2375 n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
2376 }
2377
2378 if (n) {
2379 parse_result.tied.emplace_back(TiedDocumentation{doc, n});
2380 } else if (CodeParser::isWorthWarningAbout(doc)) {
2381 bool future = false;
2382 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
2383 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
2384 if (getUnpatchedVersion(std::move(sinceVersion)) >
2385 getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
2386 future = true;
2387 }
2388 if (!future) {
2389 doc.location().warning(
2390 QStringLiteral("Cannot tie this documentation to anything"),
2391 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
2392 "topic command (e.g., '\\%1', '\\%2') in the "
2393 "comment and qdoc could not associate the "
2394 "declaration or definition following the "
2395 "comment with a documented entity.")
2396 .arg(COMMAND_FN, COMMAND_PAGE));
2397 }
2398 }
2399 } else {
2400 parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
2401
2402 CXCursor cur = clang_getCursor(tu, commentLoc);
2403 while (true) {
2404 CXCursorKind kind = clang_getCursorKind(cur);
2405 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2406 break;
2407 if (kind == CXCursor_Namespace) {
2408 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2409 }
2410 cur = clang_getCursorLexicalParent(cur);
2411 }
2412 }
2413 }
2414
2415 clang_disposeTokens(tu, tokens, numTokens);
2416 m_namespaceScope.clear();
2417 s_fn.clear();
2418
2419 return parse_result;
2420}
2421
2422/*!
2423 Use clang to parse the function signature from a function
2424 command. \a location is used for reporting errors. \a fnSignature
2425 is the string to parse. It is always a function decl.
2426 \a idTag is the optional bracketed argument passed to \\fn, or
2427 an empty string.
2428 \a context is a string list representing the scope (namespaces)
2429 under which the function is declared.
2430
2431 Returns a variant that's either a Node instance tied to the
2432 function declaration, or a parsing failure for later processing.
2433 */
2434std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
2435 const QString &idTag, QStringList context)
2436{
2437 Node *fnNode = nullptr;
2438 /*
2439 If the \fn command begins with a tag, then don't try to
2440 parse the \fn command with clang. Use the tag to search
2441 for the correct function node. It is an error if it can
2442 not be found. Return 0 in that case.
2443 */
2444 if (!idTag.isEmpty()) {
2445 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2446 if (!fnNode) {
2447 location.error(
2448 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
2449 } else {
2450 /*
2451 The function node was found. Use the formal
2452 parameter names from the \fn command, because
2453 they will be the names used in the documentation.
2454 */
2455 auto *fn = static_cast<FunctionNode *>(fnNode);
2456 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
2457 if (leftParenSplit.size() > 1) {
2458 QStringList rightParenSplit = leftParenSplit[1].split(')');
2459 if (!rightParenSplit.empty()) {
2460 QString params = rightParenSplit[0];
2461 if (!params.isEmpty()) {
2462 QStringList commaSplit = params.split(',');
2463 Parameters &parameters = fn->parameters();
2464 if (parameters.count() == commaSplit.size()) {
2465 for (int i = 0; i < parameters.count(); ++i) {
2466 QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
2467 if (blankSplit.size() > 1) {
2468 QString pName = blankSplit.last();
2469 // Remove any non-letters from the start of parameter name
2470 auto it = std::find_if(std::begin(pName), std::end(pName),
2471 [](const QChar &c) { return c.isLetter(); });
2472 parameters[i].setName(
2473 pName.remove(0, std::distance(std::begin(pName), it)));
2474 }
2475 }
2476 }
2477 }
2478 }
2479 }
2480 }
2481 return fnNode;
2482 }
2483 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2484 | CXTranslationUnit_SkipFunctionBodies
2485 | CXTranslationUnit_KeepGoing);
2486
2487 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2488
2489 getDefaultArgs(m_defines, m_args);
2490
2491 if (m_pch) {
2492 m_args.push_back("-w");
2493 m_args.push_back("-include-pch");
2494 m_args.push_back((*m_pch).get().name);
2495 }
2496
2497 TranslationUnit tu;
2498 QByteArray s_fn{};
2499 for (const auto &ns : std::as_const(context))
2500 s_fn.prepend("namespace " + ns.toUtf8() + " {");
2501 s_fn += fnSignature.toUtf8();
2502 if (!s_fn.endsWith(";"))
2503 s_fn += "{ }";
2504 s_fn.append(context.size(), '}');
2505
2506 const char *dummyFileName = fnDummyFileName;
2507 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2508 static_cast<unsigned long>(s_fn.size()) };
2509 const auto argPointers = toConstCharPointers(m_args);
2510 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2511 int(argPointers.size()), &unsavedFile, 1, flags, &tu.tu);
2512 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
2513 << ") returns" << err;
2515 if (err || !tu) {
2516 location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
2517 return fnNode;
2518 } else {
2519 /*
2520 Always visit the tu if one is constructed, because
2521 it might be possible to find the correct node, even
2522 if clang detected diagnostics. Only bother to report
2523 the diagnostics if they stop us finding the node.
2524 */
2525 CXCursor cur = clang_getTranslationUnitCursor(tu);
2526 auto &config = Config::instance();
2527 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2528 bool ignoreSignature = false;
2529 visitor.visitFnArg(cur, &fnNode, ignoreSignature);
2530
2531 if (!fnNode) {
2532 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2533 const auto &config = Config::instance();
2534 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2535 return FnMatchError{ fnSignature, location };
2536 }
2537 }
2538 }
2539 return fnNode;
2540}
2541
2542QT_END_NAMESPACE
static const clang::Decl * get_cursor_declaration(CXCursor cursor)
Returns the underlying Decl that cursor represents.
static QString reconstructQualifiedPathForCursor(CXCursor cur)
Reconstruct the qualified path name of a function that is being overridden.
static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName, const clang::FunctionDecl *func_decl, NodeVector &candidates)
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
static std::optional< SfinaeConstraint > detect_sfinae_constraint(const clang::NonTypeTemplateParmDecl *param)
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::vector< const char * > toConstCharPointers(const std::vector< QByteArray > &args)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static bool is_enable_if_name(const std::string &qualified_name)
static float getUnpatchedVersion(QString t)
void getMoreArgs(const std::vector< QByteArray > &include_paths, std::vector< QByteArray > &args)
Load the include paths into args.
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< QByteArray > &args)
Load the default arguments and the defines into args.
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
Returns a string representing the name of type as if it was referred to at the end of the translation...
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
QDocDatabase * qdocDB()
ClangVisitor(QDocDatabase *qdb, const std::set< Config::HeaderFilePath > &allHeaders, const Config::InternalFilePatterns &internalFilePatterns)
CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
The ClassNode represents a C++ class.
Definition classnode.h:23
static bool isWorthWarningAbout(const Doc &doc)
Test for whether a doc comment warrants warnings.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:95
Definition doc.h:32
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:89
void markAutoGenerated()
Marks this documentation as auto-generated by QDoc.
Definition doc.cpp:250
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
Definition doc.cpp:270
This node is used to represent any kind of function being documented.
void setConst(bool b)
void markDeletedAsWritten()
void setStatic(bool b)
void setVirtualness(Virtualness virtualness)
bool isNonvirtual() const
bool isMAssign() const
void setInvokable(bool b)
bool isCAssign() const
void setRef(bool b)
bool isDtor() const
void setOverride(bool b)
void setRefRef(bool b)
bool isSpecialMemberFunction() const
void markConstexpr()
bool isDeletedAsWritten() const
void markExplicit()
void markExplicitlyDefaulted()
bool isCCtor() const
bool isMCtor() const
bool isCtor() const
void setMetaness(Metaness metaness)
bool isExplicitlyDefaulted() const
Parameters & parameters()
void setHiddenFriend(bool b)
The Location class provides a way to mark a location in a file.
Definition location.h:20
void setColumnNo(int no)
Definition location.h:43
void setLineNo(int no)
Definition location.h:42
This class represents a C++ namespace.
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
Status
Specifies the status of the QQmlIncubator.
#define COMMAND_SINCE
Definition codeparser.h:76
#define COMMAND_FN
Definition codeparser.h:26
#define COMMAND_PAGE
Definition codeparser.h:44
#define CONFIG_VERSION
Definition config.h:459
#define CONFIG_DOCUMENTATIONINHEADERS
Definition config.h:389
bool hasTooManyTopics(const Doc &doc)
Checks if there are too many topic commands in doc.
NodeType
Definition genustypes.h:150
QmlNativeTypeAttribute
Defines QML-specific attributes affecting QmlTypeNode instances.
Definition genustypes.h:198
Metaness
Specifies the kind of function a FunctionNode represents.
Definition genustypes.h:226
This namespace holds QDoc-internal utility methods.
Definition utilities.h:21
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
Definition node.h:47
#define assert
@ Internal
Definition status.h:15
Returns the spelling in the file for a source range.
std::variant< Node *, FnMatchError > operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
Use clang to parse the function signature from a function command.
Encapsulates information about.
Definition parsererror.h:13
The Node class is the base class for all the nodes in QDoc's parse tree.
void setAccess(Access t)
Sets the node's access type to t.
Definition node.h:171
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:109
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:127
bool isVariable() const
Returns true if the node type is Variable.
Definition node.h:132
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:909
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:137
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
Definition node.h:186
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:232
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:101
void setDoc(const Doc &doc, bool replace=false)
Sets this Node's Doc to doc.
Definition node.cpp:560
bool isClass() const
Returns true if the node type is Class.
Definition node.h:91
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:942
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:144
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