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