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