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>
33#include <clang-c/Index.h>
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>
48#include "clang/AST/QualTypeNames.h"
58using namespace Qt::Literals::StringLiterals;
68 clang_disposeIndex(
index);
73 CXTranslationUnit
tu =
nullptr;
84 clang_disposeTranslationUnit(
tu);
92static CXTranslationUnit_Flags
flags_ =
static_cast<CXTranslationUnit_Flags>(0);
96#ifndef QT_NO_DEBUG_STREAM
100 QDebugStateSaver saver(debug);
103 const size_t size = v.size();
104 debug <<
"std::vector<>[" << size <<
"](";
105 for (size_t i = 0; i < size; ++i) {
117 if (!lcQdocClang().isDebugEnabled())
120 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
121 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
122 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
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);
134
135
136
137
138
139
140
141
142
143
144
145
147 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
149 return static_cast<
const clang::Decl*>(cursor.data[0]);
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
203 const clang::RecordType *rt = type->getAs<
clang::RecordType>();
208 std::vector<std::string> keywords;
209 const clang::RecordDecl *decl = rt->getDecl();
211 if (decl->getDeclName().isEmpty())
212 keywords.emplace_back(decl->getKindName());
213 const auto *parent = llvm::dyn_cast<
clang::RecordDecl>(decl->getDeclContext());
218 std::reverse(keywords.begin(), keywords.end());
224 static constexpr std::string_view prefixes[] = {
"(unnamed",
"(anonymous" };
225 size_t keywordIndex = 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);
234 foundPrefix = prefix;
237 if (foundPos == std::string::npos)
240 size_t afterPrefix = foundPos + foundPrefix.size();
241 if (afterPrefix < typeName.size() && typeName[afterPrefix] ==
')') {
243 typeName.insert(afterPrefix,
" " + keywords[keywordIndex]);
244 pos = afterPrefix + 1 + keywords[keywordIndex].size() + 1;
247 size_t closePos = typeName.find(
')', afterPrefix);
248 pos = (closePos != std::string::npos) ? closePos + 1 : afterPrefix;
256 auto policy = declaration_context.getPrintingPolicy();
257#if LIBCLANG_VERSION_MAJOR >= 23
258 policy.AnonymousTagNameStyle = llvm::to_underlying(clang::PrintingPolicy::AnonymousTagMode::Plain);
260 policy.AnonymousTagLocations =
false;
263 return ensureAnonymousTagKeyword(
std::move(result), type);
267
268
269
270
271
272
273
274
275
277 if (!typeName.contains(
"(unnamed "_L1) && !typeName.contains(
"(anonymous "_L1))
280 static const QRegularExpression pattern(
281 R"(\((unnamed|anonymous)(\s+\w+)\s+at\s+[^)]+\))"
283 QString cleaned = typeName;
284 cleaned.replace(pattern,
"(\\1\\2)"_L1);
289
290
291
292
293
294
295
296
297
299 QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
300 clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
301 declaration_context.getSourceManager(),
302 declaration_context.getLangOpts()
305 if (default_value.startsWith(
"="))
306 default_value.remove(0, 1);
308 default_value = default_value.trimmed();
310 return default_value.toStdString();
314
315
316
317
318
319
320
321
323 std::vector<std::string> &out)
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());
331 for (
const clang::Stmt *child : node->children())
332 collect_concept_references(child, out);
336
337
338
339
340
341
342
343
345#if LIBCLANG_VERSION_MAJOR >= 19
346 return (parameter && parameter->hasDefaultArgument()) ?
347 get_fully_qualified_type_name(parameter->getDefaultArgument().getArgument().getAsType(), parameter->getASTContext()) :
350 return (parameter && parameter->hasDefaultArgument()) ?
351 get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
358
359
360
361
362
363
364
365
367#if LIBCLANG_VERSION_MAJOR >= 19
368 return (parameter && parameter->hasDefaultArgument()) ?
369 get_expression_as_string(parameter->getDefaultArgument().getSourceExpression(), parameter->getASTContext()) :
"";
371 return (parameter && parameter->hasDefaultArgument()) ?
372 get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) :
"";
378
379
380
381
382
383
384
385
387 std::string default_value{};
389 if (parameter && parameter->hasDefaultArgument()) {
390 const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
392 llvm::raw_string_ostream ss{default_value};
393 template_name.print(ss, parameter->getASTContext().getPrintingPolicy(),
clang::TemplateName::Qualified::AsWritten);
396 return default_value;
400
401
402
403
404
405
406
407
408
409
411 if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
414 return get_expression_as_string(
415 parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
416 parameter->getASTContext()
421
422
423
424
425
426
427
429 if (!declaration)
return "";
431 if (
auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
432 return get_default_value_initializer_as_string(type_template_parameter);
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);
437 if (
auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
438 return get_default_value_initializer_as_string(template_template_parameter);
441 if (
auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
442 return get_default_value_initializer_as_string(function_parameter);
449
450
451
452
456 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
457 CXClientData client_data) -> CXChildVisitResult {
458 return (*
static_cast<T *>(client_data))(c);
460 return clang_visitChildren(cursor, visitor, &lambda);
464
465
468 QString ret = QString::fromUtf8(clang_getCString(string));
469 clang_disposeString(string);
474
475
476
477
478
479
480
482 const clang::Type *type)
486 for (
int depth = 0; depth < 10 && type; ++depth) {
487 if (
auto *tst = llvm::dyn_cast<clang::TemplateSpecializationType>(type))
490#if LIBCLANG_VERSION_MAJOR < 22
492 if (
auto *elaborated = llvm::dyn_cast<clang::ElaboratedType>(type)) {
493 type = elaborated->getNamedType().getTypePtr();
506
507
508
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;
516 return ends_with(qualified_name,
"enable_if_t")
517 || ends_with(qualified_name,
"enable_if");
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
545 const clang::NonTypeTemplateParmDecl *param)
547 if (!param->getName().empty())
550 auto policy = param->getASTContext().getPrintingPolicy();
552 const clang::Type *type = param->getType().getTypePtr();
571 if (!param->hasDefaultArgument())
574 std::string type_name = param->getType().getAsString(policy);
575 if (type_name.find(
'<') != std::string::npos)
581 auto *alias_decl = alias_type->getTemplateName().getAsTemplateDecl();
586 bool found_enable_if =
false;
587 const clang::Type *sugar = alias_type->desugar().getTypePtr();
589 for (
int depth = 0; depth < 10 && sugar; ++depth) {
594 if (
auto *decl = tst->getTemplateName().getAsTemplateDecl()) {
595 if (is_enable_if_name(decl->getQualifiedNameAsString())) {
596 found_enable_if =
true;
601 sugar = tst->desugar().getTypePtr();
604 if (!found_enable_if)
613 param->getType().getAsString(policy)
618
619
620
622 assert(template_declaration);
626 auto template_parameters = template_declaration->getTemplateParameters();
627 for (
auto template_parameter : template_parameters->asArray()) {
628 auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
631 std::optional<SfinaeConstraint> sfinae{};
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());
667 if (QString::fromStdString(type).startsWith(
"typename ")) type.erase(0, std::string(
"typename ").size());
669 sfinae = detect_sfinae_constraint(non_type_template_parameter);
672 auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
673 if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
675 template_declaration_ir.parameters.push_back({
677 template_parameter->isTemplateParameterPack(),
680 template_parameter->getNameAsString(),
681 get_default_value_initializer_as_string(template_parameter)
683 (template_template_parameter ?
684 std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
685 get_template_declaration(template_template_parameter).parameters
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();
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);
734 std::string synthesized;
735 const auto ¶ms = template_declaration_ir.parameters;
737 for (
const auto ¶m : params) {
738 if (param.sfinae_constraint) {
739 if (!synthesized.empty())
740 synthesized +=
" && ";
741 synthesized += param.sfinae_constraint->alias_with_args;
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);
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());
762 return template_declaration_ir;
766
767
770 unsigned int line, column;
772 clang_getPresumedLocation(location, &file, &line, &column);
780
781
786 return Access::Private;
787 case CX_CXXProtected:
788 return Access::Protected;
790 return Access::Public;
792 return Access::Public;
797
798
807 unsigned int offset1,
unsigned int offset2)
809 return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
812static QString
readFile(CXFile cxFile,
unsigned int offset1,
unsigned int offset2)
814 using FileCache = QList<FileCacheEntry>;
815 static FileCache cache;
817 CXString cxFileName = clang_getFileName(cxFile);
818 const QByteArray fileName = clang_getCString(cxFileName);
819 clang_disposeString(cxFileName);
821 for (
const auto &entry : std::as_const(cache)) {
822 if (fileName == entry.fileName)
823 return fromCache(entry.content, offset1, offset2);
826 QFile file(QString::fromUtf8(fileName));
827 if (file.open(QIODeviceBase::ReadOnly)) {
829 cache.prepend(entry);
830 while (cache.size() > 5)
832 return fromCache(entry.content, offset1, offset2);
839 auto start = clang_getRangeStart(range);
840 auto end = clang_getRangeEnd(range);
842 unsigned int offset1, offset2;
843 clang_getFileLocation(start, &file1,
nullptr,
nullptr, &offset1);
844 clang_getFileLocation(end, &file2,
nullptr,
nullptr, &offset2);
846 if (file1 != file2 || offset2 <= offset1)
849 return readFile(file1, offset1, offset2);
853
854
855
856
859 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
863 auto conversion_declaration =
864 static_cast<
const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
866 return QLatin1String(
"operator ") + QString::fromStdString(get_fully_qualified_type_name(
867 conversion_declaration->getConversionType(),
868 conversion_declaration->getASTContext()
872 QString name = fromCXString(clang_getCursorSpelling(cursor));
875 auto ltLoc = name.indexOf(
'<');
876 if (ltLoc > 0 && !name.startsWith(
"operator<"))
877 name = name.left(ltLoc);
882
883
884
888 auto kind = clang_getCursorKind(cur);
889 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
891 case CXCursor_Namespace:
892 case CXCursor_StructDecl:
893 case CXCursor_ClassDecl:
894 case CXCursor_UnionDecl:
895 case CXCursor_ClassTemplate:
897 path.prepend(fromCXString(clang_getCursorSpelling(cur)));
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);
910 cur = clang_getCursorSemanticParent(cur);
911 kind = clang_getCursorKind(cur);
917
918
919
920
921
922
925 param_type = param_type.getNonReferenceType();
926 while (param_type->isPointerType())
927 param_type = param_type->getPointeeType();
928 param_type = param_type.getUnqualifiedType();
930 if (param_type->isBuiltinType())
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());
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();
945 if (class_name.isEmpty() || class_name.contains(
'('_L1) || class_name.contains(
'['_L1))
950 if (
auto angle = class_name.indexOf(
'<'_L1); angle > 0)
951 class_name.truncate(angle);
957
958
959
960
961
962
963
964
965
966
967
971 QSet<ClassNode *> searched_classes;
972 for (
const auto *param : func_decl->parameters()) {
973 auto class_name = classNameFromParameterType(param->getType());
977 auto *class_node = qdb->findClassNode(class_name->split(
"::"_L1));
978 if (!class_node || searched_classes.contains(class_node))
981 searched_classes.insert(class_node);
982 NodeVector class_candidates;
983 class_node->findChildren(funcName, class_candidates);
985 for (Node *candidate : class_candidates) {
986 if (!candidate->isFunction(Genus::CPP))
988 if (
static_cast<FunctionNode *>(candidate)->isHiddenFriend())
989 candidates.append(candidate);
995
996
997
1000 auto kind = clang_getCursorKind(cur);
1001 if (clang_isInvalid(kind))
1003 if (kind == CXCursor_TranslationUnit)
1011 case CXCursor_TemplateTypeParameter:
1012 case CXCursor_NonTypeTemplateParameter:
1013 case CXCursor_TemplateTemplateParameter:
1023 auto parent =
static_cast<Aggregate *>(p);
1026 if (clang_Cursor_isAnonymous(cur)) {
1029 QLatin1String(
"anonymous"));
1031 name = fromCXString(clang_getCursorSpelling(cur));
1034 case CXCursor_Namespace:
1036 case CXCursor_StructDecl:
1037 case CXCursor_ClassDecl:
1038 case CXCursor_UnionDecl:
1039 case CXCursor_ClassTemplate:
1041 case CXCursor_FunctionDecl:
1042 case CXCursor_FunctionTemplate:
1043 case CXCursor_CXXMethod:
1044 case CXCursor_Constructor:
1045 case CXCursor_Destructor:
1046 case CXCursor_ConversionFunction: {
1048 parent->findChildren(functionName(cur), candidates);
1051 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1053 lexical_parent && lexical_parent
->isAggregate() && lexical_parent != parent) {
1054 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
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();
1068 if (!hasHiddenFriend) {
1069 auto *func_decl = cur_decl ? cur_decl->getAsFunction() :
nullptr;
1071 findHiddenFriendCandidates(qdb, functionName(cur), func_decl, candidates);
1074 if (candidates.isEmpty())
1077 CXType funcType = clang_getCursorType(cur);
1078 auto numArg = clang_getNumArgTypes(funcType);
1079 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
1080 QVarLengthArray<QString, 20> args;
1083 if (kind == CXCursor_FunctionTemplate)
1084 relaxed_template_declaration = get_template_declaration(
1088 for (Node *candidate : std::as_const(candidates)) {
1089 if (!candidate->isFunction(Genus::CPP))
1092 auto fn =
static_cast<FunctionNode *>(candidate);
1094 if (!fn->templateDecl() && relaxed_template_declaration)
1097 if (fn->templateDecl() && !relaxed_template_declaration)
1100 if (fn->templateDecl() && relaxed_template_declaration &&
1101 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1104 const Parameters ¶meters = fn->parameters();
1106 if (parameters.count() != numArg + isVariadic) {
1108 if (numArg > 0 && parameters.isPrivateSignal() &&
1109 (parameters.isEmpty() || !parameters.last().type().endsWith(
1110 QLatin1String(
"QPrivateSignal")))) {
1111 if (parameters.count() != --numArg + isVariadic)
1118 if (fn->isConst() !=
bool(clang_CXXMethod_isConst(cur)))
1121 if (isVariadic && parameters.last().type() != QLatin1String(
"..."))
1124 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1127 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1130 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1132 bool typesDiffer =
false;
1133 for (
int i = 0; i < numArg; ++i) {
1134 auto *paramDecl = function_declaration->getParamDecl(i);
1135 auto paramType = paramDecl->getOriginalType();
1137 if (args.size() <= i)
1138 args.append(QString::fromStdString(get_fully_qualified_type_name(
1139 paramType, function_declaration->getASTContext()
1142 QString recordedType = parameters.at(i).type();
1143 QString typeSpelling = args.at(i);
1145 typesDiffer = recordedType != typeSpelling;
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()
1180 case CXCursor_EnumDecl:
1181 return parent->findNonfunctionChild(name, &
Node::isEnumType);
1182 case CXCursor_FieldDecl:
1183 case CXCursor_VarDecl:
1185 case CXCursor_TypedefDecl:
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()) {
1201 fn->setOverridesThis(path);
1205 clang_disposeOverriddenCursors(overridden);
1214 internalFilePatterns_(internalFilePatterns)
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; });
1224 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1225 auto loc = clang_getCursorLocation(cur);
1226 if (clang_Location_isFromMainFile(loc))
1227 return visitSource(cur, loc);
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;
1236 QFileInfo fi(fromCXString(clang_getFileName(file)));
1238 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1239 isInterestingCache_[file] = isInteresting;
1241 if (isInteresting) {
1242 return visitHeader(cur, loc);
1245 return CXChildVisit_Continue;
1247 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1251
1252
1253
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;
1262 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1270
1271
1272
1275 unsigned int line {}, column {};
1276 friend bool operator<(
const SimpleLoc &a,
const SimpleLoc &b)
1278 return a.line != b.line ? a.line < b.line : a.column < b.column;
1282
1283
1284
1285
1286 QMap<SimpleLoc, CXCursor> declMap_;
1290 std::set<QString> allHeaders_;
1291 QHash<CXFile,
bool> isInterestingCache_;
1295
1296
1297 bool ignoredSymbol(
const QString &symbolName)
1299 if (symbolName == QLatin1String(
"QPrivateSignal"))
1302 if (symbolName.startsWith(
"_qt_property_"))
1305 if (symbolName.startsWith(
"<deduction guide"))
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);
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1351 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
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;
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;
1382 return CXChildVisit_Continue;
1389
1390
1391
1392CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1394 auto kind = clang_getCursorKind(cursor);
1395 if (clang_isDeclaration(kind)) {
1397 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1398 declMap_.insert(l, cursor);
1399 return CXChildVisit_Recurse;
1401 return CXChildVisit_Continue;
1405
1406
1407
1408
1411 CXCursor sp = clang_getCursorSemanticParent(cursor);
1412 CXCursor lp = clang_getCursorLexicalParent(cursor);
1413 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1416 return static_cast<Aggregate *>(spn);
1422CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1423 bool &ignoreSignature)
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))) {
1437 ignoreSignature =
true;
1442 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1443 readParameterNamesAndAttributes(fn, cursor);
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()));
1454 QString name = functionName(cursor);
1455 if (ignoredSymbol(name))
1456 return CXChildVisit_Continue;
1457 Aggregate *semanticParent = getSemanticParent(cursor);
1458 if (semanticParent && semanticParent
->isClass()) {
1460 processFunction(candidate, cursor);
1461 if (!candidate->isSpecialMemberFunction()) {
1463 return CXChildVisit_Continue;
1465 candidate->setImplicitlyGenerated(
true);
1475 return CXChildVisit_Continue;
1478CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1480 auto kind = clang_getCursorKind(cursor);
1483 case CXCursor_TypeAliasTemplateDecl:
1484 case CXCursor_TypeAliasDecl: {
1485 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1486 QString aliasedType;
1488 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1492 if (kind == CXCursor_TypeAliasTemplateDecl) {
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());
1502 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1503 if (aliasedCXType.kind != CXType_Invalid) {
1504 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1508 if (!aliasedType.isEmpty()) {
1509 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1514 ta->setTemplateDecl(get_template_declaration(templateDecl));
1516 return CXChildVisit_Continue;
1518 case CXCursor_StructDecl:
1519 case CXCursor_UnionDecl:
1520 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1521 return CXChildVisit_Continue;
1523 case CXCursor_ClassTemplate:
1525 case CXCursor_ClassDecl: {
1526 if (!clang_isCursorDefinition(cursor))
1527 return CXChildVisit_Continue;
1530 return CXChildVisit_Continue;
1532 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1534 Aggregate *semanticParent = getSemanticParent(cursor);
1535 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1536 return CXChildVisit_Continue;
1539 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1540 clang_getTemplateCursorKind(cursor) : kind;
1543 if (actualKind == CXCursor_StructDecl)
1545 else if (actualKind == CXCursor_UnionDecl)
1548 auto *classe =
new ClassNode(type, semanticParent, className);
1552 classe->setLocation(location);
1554 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1555 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1556 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1560 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1561 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1563 if (kind == CXCursor_ClassTemplate) {
1565 classe->setTemplateDecl(get_template_declaration(template_declaration));
1568 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1571 case CXCursor_CXXBaseSpecifier: {
1573 return CXChildVisit_Continue;
1575 auto type = clang_getCursorType(cursor);
1576 auto baseCursor = clang_getTypeDeclaration(type);
1578 auto classe =
static_cast<ClassNode *>(parent_);
1580 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1581 classe->addUnresolvedBaseClass(access,
1582 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1583 return CXChildVisit_Continue;
1585 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1586 classe->addResolvedBaseClass(access, baseClasse);
1587 return CXChildVisit_Continue;
1589 case CXCursor_Namespace: {
1590 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1600 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1603 case CXCursor_LinkageSpec:
1605 case CXCursor_FunctionTemplate:
1607 case CXCursor_FunctionDecl:
1608 case CXCursor_CXXMethod:
1609 case CXCursor_Constructor:
1610 case CXCursor_Destructor:
1611 case CXCursor_ConversionFunction: {
1613 return CXChildVisit_Continue;
1614 QString name = functionName(cursor);
1615 if (ignoredSymbol(name))
1616 return CXChildVisit_Continue;
1619 return CXChildVisit_Continue;
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(
'['));
1628 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1630 fn->setTag(comment.mid(tag, end - tag));
1635 processFunction(fn, cursor);
1637 if (kind == CXCursor_FunctionTemplate) {
1639 fn->setTemplateDecl(get_template_declaration(template_declaration));
1642 if (!clang_Location_isInSystemHeader(loc))
1643 fn->autoGenerateSmfDoc(parent_->name());
1645 return CXChildVisit_Continue;
1647#if CINDEX_VERSION
>= 36
1648 case CXCursor_FriendDecl: {
1652 case CXCursor_EnumDecl: {
1653 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1654 if (en && en->items().size())
1655 return CXChildVisit_Continue;
1657 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1659 if (clang_Cursor_isAnonymous(cursor)) {
1660 enumTypeName =
"anonymous";
1666 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1668 en =
static_cast<EnumNode *>(n);
1672 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1675 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1679 visitChildrenLambda(cursor, [&](CXCursor cur) {
1680 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1681 return CXChildVisit_Continue;
1684 visitChildrenLambda(cur, [&](CXCursor cur) {
1685 if (clang_isExpression(clang_getCursorKind(cur))) {
1686 value = getSpelling(clang_getCursorExtent(cur));
1687 return CXChildVisit_Break;
1689 return CXChildVisit_Continue;
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);
1696 value = QString::number(clang_getEnumConstantDeclValue(cur));
1700 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1701 return CXChildVisit_Continue;
1703 return CXChildVisit_Continue;
1705 case CXCursor_FieldDecl:
1706 case CXCursor_VarDecl: {
1708 return CXChildVisit_Continue;
1710 auto value_declaration =
1712 assert(value_declaration);
1715 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1717 var->setAccess(access);
1719 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1720 value_declaration->getType(),
1721 value_declaration->getASTContext()
1723 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1725 return CXChildVisit_Continue;
1727 case CXCursor_TypedefDecl: {
1729 return CXChildVisit_Continue;
1730 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1734 visitChildrenLambda(cursor, [&](CXCursor cur) {
1735 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1736 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1737 return CXChildVisit_Continue;
1739 visitChildrenLambda(cursor, [&](CXCursor cur) {
1740 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1741 return CXChildVisit_Continue;
1744 if (en && en->isEnumType())
1745 static_cast<EnumNode *>(en)->setFlagsType(td);
1746 return CXChildVisit_Break;
1748 return CXChildVisit_Break;
1750 return CXChildVisit_Continue;
1756 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1759 return CXChildVisit_Continue;
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")) {
1777 if (annotation == QLatin1String(
"qt_invokable"))
1779 }
else if (kind == CXCursor_CXXOverrideAttr) {
1781 }
else if (kind == CXCursor_ParmDecl) {
1783 return CXChildVisit_Break;
1785 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1786 parameters
[i
].setName(name);
1789 Q_ASSERT(parameter_declaration);
1793 if (!default_value.empty())
1794 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1798 return CXChildVisit_Continue;
1804 CXCursorKind kind = clang_getCursorKind(cursor);
1805 CXType funcType = clang_getCursorType(cursor);
1812 : clang_CXXMethod_isPureVirtual(cursor)
1829 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1831 if (kind == CXCursor_Constructor
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()
1843 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1848 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1854 (constructor_declaration && constructor_declaration->isExplicit()) ||
1855 (conversion_declaration && conversion_declaration->isExplicit())
1858 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1863 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1864 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1866 if (function_prototype) {
1867 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
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()
1876 if (exception_specification_spelling !=
"false")
1877 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
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);
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);
1912 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1913 if (refQualKind == CXRefQualifier_LValue)
1915 else if (refQualKind == CXRefQualifier_RValue)
1924 parameters
.reserve(function_declaration->getNumParams()
);
1926 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1927 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1929 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1931 parameter_declaration->getASTContext()
1934 if (!parameter_type.isCanonical())
1935 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1936 parameter_type.getCanonicalType(),
1937 parameter_declaration->getASTContext()
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());
1952 if (!referenced_concepts.isEmpty()) {
1953 referenced_concepts.sort();
1954 referenced_concepts.removeDuplicates();
1955 fn->setReferencedConcepts(
std::move(referenced_concepts));
1959 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
1965 if (clang_isFunctionTypeVariadic(funcType))
1966 parameters.append(QStringLiteral(
"..."));
1967 readParameterNamesAndAttributes(fn, cursor);
1969 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1971 Q_ASSERT(function_declaration);
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;
1979 if (!hasNamespaceScopeRedeclaration)
1986 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
1987 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
1988 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
1991 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
1992 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
1993 if (lpIdx <= 0 || rpIdx <= lpIdx)
1996 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1997 signature = signature.simplified();
1998 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
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";
2009 auto it =
std::find_if(parts.cbegin(), parts.cend(),
2010 [](
const QString &attr) ->
bool {
2011 return attrs.contains(attr);
2014 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
2017 QStringList typeParts;
2018 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
2019 parts.erase(parts.cbegin(), it);
2020 QString name = typeParts.takeLast();
2023 while (!name.isEmpty() && name.front() == QChar(
'*')) {
2024 typeParts.last().push_back(name.front());
2029 if (parts.size() < 2 || name.isEmpty())
2033 property->setAccess(Access::Public);
2034 property->setLocation(loc);
2035 property->setDataType(typeParts.join(QChar(
' ')));
2038 while (i < parts.size()) {
2039 const QString &key = parts.at(i++);
2041 if (key ==
"CONSTANT") {
2042 property->setConstant();
2043 }
else if (key ==
"REQUIRED") {
2044 property->setRequired();
2046 if (i < parts.size()) {
2047 QString value = parts.at(i++);
2048 if (key ==
"READ") {
2050 }
else if (key ==
"WRITE") {
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") {
2060 }
else if (key ==
"RESET") {
2062 }
else if (key ==
"NOTIFY") {
2071
2072
2073
2074
2075
2079 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
2080 auto decl_it = declMap_.upperBound(docloc);
2081 if (decl_it == declMap_.end())
2084 unsigned int declLine = decl_it.key().line;
2085 unsigned int nextCommentLine;
2086 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
2087 if (nextCommentLine < declLine)
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) {
2098 auto parent = clang_getCursorLexicalParent(*decl_it);
2099 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2103 auto *node = findNodeForCursor(qdb_, *decl_it);
2105 if (node && node->isFunction(Genus::CPP))
2106 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2113 const std::vector<QByteArray>& include_paths,
2114 const QList<QByteArray>& defines,
2121 m_allHeaders = config.getHeaderFiles();
2122 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2130 "-fms-compatibility-version=19",
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",
2149 std::vector<
const char *> pointers;
2150 pointers.reserve(args.size());
2151 for (
const auto &arg : args)
2152 pointers.push_back(arg.constData());
2157
2158
2159
2164 args.emplace_back(arg);
2167 for (
const auto &p : std::as_const(defines))
2172
2173
2175 const std::vector<QByteArray>& include_paths,
2176 std::vector<QByteArray>& args
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.";
2184 args.insert(args.end(), include_paths.begin(), include_paths.end());
2189
2190
2191
2192
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
2201 static std::vector<QByteArray> arguments{};
2203 if (module_header.isEmpty())
return std::nullopt;
2205 getDefaultArgs(defines, arguments);
2206 getMoreArgs(include_paths, arguments);
2208 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2209 | CXTranslationUnit_SkipFunctionBodies
2210 | CXTranslationUnit_KeepGoing);
2214 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2215 if (!pch_directory.isValid())
return std::nullopt;
2217 const QByteArray module = module_header.toUtf8();
2220 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2223 struct FindPredicate
2225 enum SearchType { Any, Module };
2226 QByteArray &candidate_;
2227 const QByteArray &module_;
2229 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2230 SearchType type = Any)
2231 : candidate_(candidate), module_(module), type_(type)
2235 bool operator()(
const QByteArray &p)
const
2237 if (type_ != Any && !p.endsWith(module_))
2239 candidate_ = p +
"/";
2240 candidate_.append(module_);
2241 if (p.startsWith(
"-I"))
2242 candidate_ = candidate_.mid(2);
2243 return QFile::exists(QString::fromUtf8(candidate_));
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);
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 "
2263 arguments.push_back(
"-xc++");
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);
2275 if (header_name.endsWith(
"_p.h"_L1))
2276 shouldInclude = shouldInclude && policy.showInternal;
2278 if (shouldInclude) {
2279 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2283 QFileInfo headerFile(header);
2284 if (!headerFile.exists()) {
2285 qWarning() <<
"Could not find module header file" << header;
2286 return std::nullopt;
2289 out <<
"#include \"" << header <<
"\"\n";
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";
2301 const auto argPointers = toConstCharPointers(arguments);
2302 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
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;
2313 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2314 return std::nullopt;
2317 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2318 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2319 clang_defaultSaveOptions(tu));
2321 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2322 return std::nullopt;
2327 CXCursor cur = clang_getTranslationUnitCursor(tu);
2328 auto &config = Config::instance();
2329 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2331 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2333 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2338 if (t.count(QChar(
'.')) > 1)
2339 t.truncate(t.lastIndexOf(QChar(
'.')));
2344
2345
2346
2347
2348
2349
2350
2351
2354 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2355 | CXTranslationUnit_SkipFunctionBodies
2356 | CXTranslationUnit_KeepGoing);
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);
2367 getMoreArgs(m_includePaths, m_args);
2370 const auto argPointers = toConstCharPointers(m_args);
2371 const QByteArray filePathLocal = filePath.toLocal8Bit();
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;
2380 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2384 ParsedCppFileIR parse_result{};
2386 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2387 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
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);
2395 for (
unsigned int i = 0; i < numTokens; ++i) {
2396 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2398 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2399 if (!comment.startsWith(
"/*!"))
2402 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2405 Doc::trimCStyleComment(loc, comment);
2408 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2414 if (i + 1 < numTokens) {
2416 CXSourceLocation nextCommentLoc = commentLoc;
2417 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2419 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2426 bool future =
false;
2428 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2429 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2430 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
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.")
2447 CXCursor cur = clang_getCursor(tu, commentLoc);
2449 CXCursorKind kind = clang_getCursorKind(cur);
2450 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2452 if (kind == CXCursor_Namespace) {
2453 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2455 cur = clang_getCursorLexicalParent(cur);
2460 clang_disposeTokens(tu, tokens, numTokens);
2461 m_namespaceScope.clear();
2464 return parse_result;
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2480 const QString &idTag, QStringList context)
2482 Node *fnNode =
nullptr;
2484
2485
2486
2487
2488
2489 if (!idTag.isEmpty()) {
2490 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2493 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2496
2497
2498
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(
',');
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();
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)));
2528 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2529 | CXTranslationUnit_SkipFunctionBodies
2530 | CXTranslationUnit_KeepGoing);
2534 getDefaultArgs(m_defines, m_args);
2537 m_args.push_back(
"-w");
2538 m_args.push_back(
"-include-pch");
2539 m_args.push_back((*m_pch).get().name);
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(
";"))
2549 s_fn.append(context.size(),
'}');
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;
2561 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2565
2566
2567
2568
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;
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 };
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)
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.
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...
const Location & location() const
Returns the starting location of a qdoc comment.
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
This node is used to represent any kind of function being documented.
void markDeletedAsWritten()
void setVirtualness(Virtualness virtualness)
bool isNonvirtual() const
void setInvokable(bool b)
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.
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 CONFIG_DOCUMENTATIONINHEADERS
bool hasTooManyTopics(const Doc &doc)
Checks if there are too many topic commands in doc.
QmlNativeTypeAttribute
Defines QML-specific attributes affecting QmlTypeNode instances.
Metaness
Specifies the kind of function a FunctionNode represents.
This namespace holds QDoc-internal utility methods.
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
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.
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.
bool isNamespace() const
Returns true if the node type is Namespace.
bool isTypedef() const
Returns true if the node type is Typedef.
bool isVariable() const
Returns true if the node type is Variable.
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
bool isClass() const
Returns true if the node type is Class.
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
A class for parsing and managing a function parameter list.
Parameter & operator[](int index)
Holds the source-level alias with its template arguments for a SFINAE constraint detected in a non-ty...
operator CXTranslationUnit()