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/Decl.h>
36#include <clang/AST/DeclFriend.h>
37#include <clang/AST/DeclTemplate.h>
38#include <clang/AST/Expr.h>
39#include <clang/AST/Type.h>
40#include <clang/AST/TypeLoc.h>
41#include <clang/Basic/SourceLocation.h>
42#include <clang/Frontend/ASTUnit.h>
43#include <clang/Lex/Lexer.h>
44#include <llvm/Support/Casting.h>
46#include "clang/AST/QualTypeNames.h"
56using namespace Qt::Literals::StringLiterals;
66 clang_disposeIndex(
index);
71 CXTranslationUnit
tu =
nullptr;
82 clang_disposeTranslationUnit(
tu);
90static CXTranslationUnit_Flags
flags_ =
static_cast<CXTranslationUnit_Flags>(0);
94#ifndef QT_NO_DEBUG_STREAM
98 QDebugStateSaver saver(debug);
101 const size_t size = v.size();
102 debug <<
"std::vector<>[" << size <<
"](";
103 for (size_t i = 0; i < size; ++i) {
115 if (!lcQdocClang().isDebugEnabled())
118 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
119 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
120 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
122 for (
unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
123 auto diagnostic = clang_getDiagnostic(translationUnit, i);
124 auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
125 qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
126 clang_disposeString(formattedDiagnostic);
127 clang_disposeDiagnostic(diagnostic);
132
133
134
135
136
137
138
139
140
141
142
143
145 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
147 return static_cast<
const clang::Decl*>(cursor.data[0]);
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
201 const clang::RecordType *rt = type->getAs<
clang::RecordType>();
206 std::vector<std::string> keywords;
207 const clang::RecordDecl *decl = rt->getDecl();
209 if (decl->getDeclName().isEmpty())
210 keywords.emplace_back(decl->getKindName());
211 const auto *parent = llvm::dyn_cast<
clang::RecordDecl>(decl->getDeclContext());
216 std::reverse(keywords.begin(), keywords.end());
222 static constexpr std::string_view prefixes[] = {
"(unnamed",
"(anonymous" };
223 size_t keywordIndex = 0;
225 while (pos < typeName.size() && keywordIndex < keywords.size()) {
226 std::string_view foundPrefix;
227 size_t foundPos = std::string::npos;
228 for (
auto prefix : prefixes) {
229 size_t p = typeName.find(prefix, pos);
232 foundPrefix = prefix;
235 if (foundPos == std::string::npos)
238 size_t afterPrefix = foundPos + foundPrefix.size();
239 if (afterPrefix < typeName.size() && typeName[afterPrefix] ==
')') {
241 typeName.insert(afterPrefix,
" " + keywords[keywordIndex]);
242 pos = afterPrefix + 1 + keywords[keywordIndex].size() + 1;
245 size_t closePos = typeName.find(
')', afterPrefix);
246 pos = (closePos != std::string::npos) ? closePos + 1 : afterPrefix;
254 auto policy = declaration_context.getPrintingPolicy();
255 policy.AnonymousTagLocations =
false;
257 return ensureAnonymousTagKeyword(
std::move(result), type);
261
262
263
264
265
266
267
268
269
271 if (!typeName.contains(
"(unnamed "_L1) && !typeName.contains(
"(anonymous "_L1))
274 static const QRegularExpression pattern(
275 R"(\((unnamed|anonymous)(\s+\w+)\s+at\s+[^)]+\))"
277 QString cleaned = typeName;
278 cleaned.replace(pattern,
"(\\1\\2)"_L1);
283
284
285
286
287
288
289
290
291
293 QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
294 clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
295 declaration_context.getSourceManager(),
296 declaration_context.getLangOpts()
299 if (default_value.startsWith(
"="))
300 default_value.remove(0, 1);
302 default_value = default_value.trimmed();
304 return default_value.toStdString();
308
309
310
311
312
313
314
315
317#if LIBCLANG_VERSION_MAJOR >= 19
318 return (parameter && parameter->hasDefaultArgument()) ?
319 get_fully_qualified_type_name(parameter->getDefaultArgument().getArgument().getAsType(), parameter->getASTContext()) :
322 return (parameter && parameter->hasDefaultArgument()) ?
323 get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
330
331
332
333
334
335
336
337
339#if LIBCLANG_VERSION_MAJOR >= 19
340 return (parameter && parameter->hasDefaultArgument()) ?
341 get_expression_as_string(parameter->getDefaultArgument().getSourceExpression(), parameter->getASTContext()) :
"";
343 return (parameter && parameter->hasDefaultArgument()) ?
344 get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) :
"";
350
351
352
353
354
355
356
357
359 std::string default_value{};
361 if (parameter && parameter->hasDefaultArgument()) {
362 const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
364 llvm::raw_string_ostream ss{default_value};
365 template_name.print(ss, parameter->getASTContext().getPrintingPolicy(),
clang::TemplateName::Qualified::AsWritten);
368 return default_value;
372
373
374
375
376
377
378
379
380
381
383 if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
386 return get_expression_as_string(
387 parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
388 parameter->getASTContext()
393
394
395
396
397
398
399
401 if (!declaration)
return "";
403 if (
auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
404 return get_default_value_initializer_as_string(type_template_parameter);
406 if (
auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
407 return get_default_value_initializer_as_string(non_type_template_parameter);
409 if (
auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
410 return get_default_value_initializer_as_string(template_template_parameter);
413 if (
auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
414 return get_default_value_initializer_as_string(function_parameter);
421
422
423
424
428 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
429 CXClientData client_data) -> CXChildVisitResult {
430 return (*
static_cast<T *>(client_data))(c);
432 return clang_visitChildren(cursor, visitor, &lambda);
436
437
440 QString ret = QString::fromUtf8(clang_getCString(string));
441 clang_disposeString(string);
446
447
448
449
450
451
452
454 const clang::Type *type)
458 for (
int depth = 0; depth < 10 && type; ++depth) {
459 if (
auto *tst = llvm::dyn_cast<clang::TemplateSpecializationType>(type))
462#if LIBCLANG_VERSION_MAJOR < 22
464 if (
auto *elaborated = llvm::dyn_cast<clang::ElaboratedType>(type)) {
465 type = elaborated->getNamedType().getTypePtr();
478
479
480
483 auto ends_with = [](
const std::string &str,
const std::string &suffix) {
484 return str.size() >= suffix.size()
485 && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
488 return ends_with(qualified_name,
"enable_if_t")
489 || ends_with(qualified_name,
"enable_if");
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
517 const clang::NonTypeTemplateParmDecl *param)
519 if (!param->getName().empty())
522 auto policy = param->getASTContext().getPrintingPolicy();
524 const clang::Type *type = param->getType().getTypePtr();
543 if (!param->hasDefaultArgument())
546 std::string type_name = param->getType().getAsString(policy);
547 if (type_name.find(
'<') != std::string::npos)
553 auto *alias_decl = alias_type->getTemplateName().getAsTemplateDecl();
558 bool found_enable_if =
false;
559 const clang::Type *sugar = alias_type->desugar().getTypePtr();
561 for (
int depth = 0; depth < 10 && sugar; ++depth) {
566 if (
auto *decl = tst->getTemplateName().getAsTemplateDecl()) {
567 if (is_enable_if_name(decl->getQualifiedNameAsString())) {
568 found_enable_if =
true;
573 sugar = tst->desugar().getTypePtr();
576 if (!found_enable_if)
585 param->getType().getAsString(policy)
590
591
592
594 assert(template_declaration);
598 auto template_parameters = template_declaration->getTemplateParameters();
599 for (
auto template_parameter : template_parameters->asArray()) {
600 auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
603 std::optional<SfinaeConstraint> sfinae{};
605 if (
auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
606 kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
607 type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
639 if (QString::fromStdString(type).startsWith(
"typename ")) type.erase(0, std::string(
"typename ").size());
641 sfinae = detect_sfinae_constraint(non_type_template_parameter);
644 auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
645 if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
647 template_declaration_ir.parameters.push_back({
649 template_parameter->isTemplateParameterPack(),
652 template_parameter->getNameAsString(),
653 get_default_value_initializer_as_string(template_parameter)
655 (template_template_parameter ?
656 std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
657 get_template_declaration(template_template_parameter).parameters
664 std::string explicit_requires;
665 if (
const clang::Expr *requires_clause = template_parameters->getRequiresClause()) {
666 explicit_requires = QString::fromStdString(get_expression_as_string(
667 requires_clause, template_declaration->getASTContext())).simplified().toStdString();
677 std::string synthesized;
678 const auto ¶ms = template_declaration_ir.parameters;
680 for (
const auto ¶m : params) {
681 if (param.sfinae_constraint) {
682 if (!synthesized.empty())
683 synthesized +=
" && ";
684 synthesized += param.sfinae_constraint->alias_with_args;
691 if (!synthesized.empty() && !explicit_requires.empty())
692 template_declaration_ir.requires_clause = synthesized +
" && (" + explicit_requires +
")";
693 else if (!synthesized.empty())
694 template_declaration_ir.requires_clause =
std::move(synthesized);
695 else if (!explicit_requires.empty())
696 template_declaration_ir.requires_clause =
std::move(explicit_requires);
699 return template_declaration_ir;
703
704
707 unsigned int line, column;
709 clang_getPresumedLocation(location, &file, &line, &column);
717
718
724 case CX_CXXProtected:
734
735
744 unsigned int offset1,
unsigned int offset2)
746 return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
749static QString
readFile(CXFile cxFile,
unsigned int offset1,
unsigned int offset2)
751 using FileCache = QList<FileCacheEntry>;
752 static FileCache cache;
754 CXString cxFileName = clang_getFileName(cxFile);
755 const QByteArray fileName = clang_getCString(cxFileName);
756 clang_disposeString(cxFileName);
758 for (
const auto &entry : std::as_const(cache)) {
759 if (fileName == entry.fileName)
760 return fromCache(entry.content, offset1, offset2);
763 QFile file(QString::fromUtf8(fileName));
764 if (file.open(QIODeviceBase::ReadOnly)) {
766 cache.prepend(entry);
767 while (cache.size() > 5)
769 return fromCache(entry.content, offset1, offset2);
776 auto start = clang_getRangeStart(range);
777 auto end = clang_getRangeEnd(range);
779 unsigned int offset1, offset2;
780 clang_getFileLocation(start, &file1,
nullptr,
nullptr, &offset1);
781 clang_getFileLocation(end, &file2,
nullptr,
nullptr, &offset2);
783 if (file1 != file2 || offset2 <= offset1)
786 return readFile(file1, offset1, offset2);
790
791
792
793
796 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
800 auto conversion_declaration =
801 static_cast<
const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
803 return QLatin1String(
"operator ") + QString::fromStdString(get_fully_qualified_type_name(
804 conversion_declaration->getConversionType(),
805 conversion_declaration->getASTContext()
809 QString name = fromCXString(clang_getCursorSpelling(cursor));
812 auto ltLoc = name.indexOf(
'<');
813 if (ltLoc > 0 && !name.startsWith(
"operator<"))
814 name = name.left(ltLoc);
819
820
821
825 auto kind = clang_getCursorKind(cur);
826 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
828 case CXCursor_Namespace:
829 case CXCursor_StructDecl:
830 case CXCursor_ClassDecl:
831 case CXCursor_UnionDecl:
832 case CXCursor_ClassTemplate:
834 path.prepend(fromCXString(clang_getCursorSpelling(cur)));
836 case CXCursor_FunctionDecl:
837 case CXCursor_FunctionTemplate:
838 case CXCursor_CXXMethod:
839 case CXCursor_Constructor:
840 case CXCursor_Destructor:
841 case CXCursor_ConversionFunction:
842 path = functionName(cur);
847 cur = clang_getCursorSemanticParent(cur);
848 kind = clang_getCursorKind(cur);
854
855
856
857
858
859
862 param_type = param_type.getNonReferenceType();
863 while (param_type->isPointerType())
864 param_type = param_type->getPointeeType();
865 param_type = param_type.getUnqualifiedType();
867 if (param_type->isBuiltinType())
870 if (
const auto *record_type = param_type->getAs<clang::RecordType>()) {
871 if (
const auto *record_decl = record_type->getDecl())
872 return QString::fromStdString(record_decl->getQualifiedNameAsString());
877 QString class_name = QString::fromStdString(param_type.getAsString());
878 class_name.remove(
"const "_L1).remove(
"volatile "_L1);
879 class_name.remove(
"class "_L1).remove(
"struct "_L1);
880 class_name = class_name.trimmed();
882 if (class_name.isEmpty() || class_name.contains(
'('_L1) || class_name.contains(
'['_L1))
887 if (
auto angle = class_name.indexOf(
'<'_L1); angle > 0)
888 class_name.truncate(angle);
894
895
896
897
898
899
900
901
902
903
904
908 QSet<ClassNode *> searched_classes;
909 for (
const auto *param : func_decl->parameters()) {
910 auto class_name = classNameFromParameterType(param->getType());
914 auto *class_node = qdb->findClassNode(class_name->split(
"::"_L1));
915 if (!class_node || searched_classes.contains(class_node))
918 searched_classes.insert(class_node);
919 NodeVector class_candidates;
920 class_node->findChildren(funcName, class_candidates);
922 for (Node *candidate : class_candidates) {
923 if (!candidate->isFunction(Genus::CPP))
925 if (
static_cast<FunctionNode *>(candidate)->isHiddenFriend())
926 candidates.append(candidate);
932
933
934
937 auto kind = clang_getCursorKind(cur);
938 if (clang_isInvalid(kind))
940 if (kind == CXCursor_TranslationUnit)
948 case CXCursor_TemplateTypeParameter:
949 case CXCursor_NonTypeTemplateParameter:
950 case CXCursor_TemplateTemplateParameter:
960 auto parent =
static_cast<Aggregate *>(p);
963 if (clang_Cursor_isAnonymous(cur)) {
966 QLatin1String(
"anonymous"));
968 name = fromCXString(clang_getCursorSpelling(cur));
971 case CXCursor_Namespace:
973 case CXCursor_StructDecl:
974 case CXCursor_ClassDecl:
975 case CXCursor_UnionDecl:
976 case CXCursor_ClassTemplate:
978 case CXCursor_FunctionDecl:
979 case CXCursor_FunctionTemplate:
980 case CXCursor_CXXMethod:
981 case CXCursor_Constructor:
982 case CXCursor_Destructor:
983 case CXCursor_ConversionFunction: {
985 parent->findChildren(functionName(cur), candidates);
988 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() !=
clang::Decl::FOK_None) {
990 lexical_parent && lexical_parent
->isAggregate() && lexical_parent != parent) {
991 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
1000 const bool hasHiddenFriend =
1001 std::any_of(candidates.cbegin(), candidates.cend(), [](
const Node *n) {
1002 return n->isFunction(Genus::CPP)
1003 &&
static_cast<
const FunctionNode *>(n)->isHiddenFriend();
1005 if (!hasHiddenFriend) {
1006 auto *func_decl = cur_decl ? cur_decl->getAsFunction() :
nullptr;
1008 findHiddenFriendCandidates(qdb, functionName(cur), func_decl, candidates);
1011 if (candidates.isEmpty())
1014 CXType funcType = clang_getCursorType(cur);
1015 auto numArg = clang_getNumArgTypes(funcType);
1016 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
1017 QVarLengthArray<QString, 20> args;
1020 if (kind == CXCursor_FunctionTemplate)
1021 relaxed_template_declaration = get_template_declaration(
1025 for (Node *candidate : std::as_const(candidates)) {
1026 if (!candidate->isFunction(Genus::CPP))
1029 auto fn =
static_cast<FunctionNode *>(candidate);
1031 if (!fn->templateDecl() && relaxed_template_declaration)
1034 if (fn->templateDecl() && !relaxed_template_declaration)
1037 if (fn->templateDecl() && relaxed_template_declaration &&
1038 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1041 const Parameters ¶meters = fn->parameters();
1043 if (parameters.count() != numArg + isVariadic) {
1045 if (numArg > 0 && parameters.isPrivateSignal() &&
1046 (parameters.isEmpty() || !parameters.last().type().endsWith(
1047 QLatin1String(
"QPrivateSignal")))) {
1048 if (parameters.count() != --numArg + isVariadic)
1055 if (fn->isConst() !=
bool(clang_CXXMethod_isConst(cur)))
1058 if (isVariadic && parameters.last().type() != QLatin1String(
"..."))
1061 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1064 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1067 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1069 bool typesDiffer =
false;
1070 for (
int i = 0; i < numArg; ++i) {
1071 auto *paramDecl = function_declaration->getParamDecl(i);
1072 auto paramType = paramDecl->getOriginalType();
1074 if (args.size() <= i)
1075 args.append(QString::fromStdString(get_fully_qualified_type_name(
1076 paramType, function_declaration->getASTContext()
1079 QString recordedType = parameters.at(i).type();
1080 QString typeSpelling = args.at(i);
1082 typesDiffer = recordedType != typeSpelling;
1092 const bool isBareTemplateTypeParm =
1093 paramType.getTypePtrOrNull()
1094 && llvm::isa<clang::TemplateTypeParmType>(paramType.getTypePtr());
1095 if (!isBareTemplateTypeParm) {
1096 QStringView canonicalType = parameters.at(i).canonicalType();
1097 if (!canonicalType.isEmpty()) {
1098 typesDiffer = canonicalType !=
1099 QString::fromStdString(get_fully_qualified_type_name(
1100 paramType.getCanonicalType(),
1101 function_declaration->getASTContext()
1117 case CXCursor_EnumDecl:
1118 return parent->findNonfunctionChild(name, &
Node::isEnumType);
1119 case CXCursor_FieldDecl:
1120 case CXCursor_VarDecl:
1122 case CXCursor_TypedefDecl:
1131 CXCursor *overridden;
1132 unsigned int numOverridden = 0;
1133 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
1134 for (uint i = 0; i < numOverridden; ++i) {
1135 QString path = reconstructQualifiedPathForCursor(overridden[i]);
1136 if (!path.isEmpty()) {
1138 fn->setOverridesThis(path);
1142 clang_disposeOverriddenCursors(overridden);
1146
1147
1148
1149
1161 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
1162 if (fn->isVirtual())
1163 docSource += u" This destructor is virtual."_s;
1165 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
1167 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
1169 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
1172 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
1173 ? params.at(0).name()
1175 docSource = fn->isCAssign()
1176 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
1177 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
1180 if (docSource.isEmpty())
1183 if (fn->isDeletedAsWritten())
1184 docSource += u" This function is deleted."_s;
1186 static const QSet<QString> noMetaCommands;
1187 static const QSet<QString> noTopics;
1199 internalFilePatterns_(internalFilePatterns)
1201 std::transform(allHeaders.cbegin(), allHeaders.cend(),
std::inserter(allHeaders_, allHeaders_.begin()),
1202 [](
const auto& header_file_path) ->
const QString& {
return header_file_path.filename; });
1209 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1210 auto loc = clang_getCursorLocation(cur);
1211 if (clang_Location_isFromMainFile(loc))
1212 return visitSource(cur, loc);
1215 clang_getFileLocation(loc, &file,
nullptr,
nullptr,
nullptr);
1216 bool isInteresting =
false;
1217 auto it = isInterestingCache_.find(file);
1218 if (it != isInterestingCache_.end()) {
1219 isInteresting = *it;
1221 QFileInfo fi(fromCXString(clang_getFileName(file)));
1223 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1224 isInterestingCache_[file] = isInteresting;
1226 if (isInteresting) {
1227 return visitHeader(cur, loc);
1230 return CXChildVisit_Continue;
1232 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1236
1237
1238
1241 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1242 auto loc = clang_getCursorLocation(cur);
1243 if (clang_Location_isFromMainFile(loc))
1244 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1245 return CXChildVisit_Continue;
1247 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1253 bool detectQmlSingleton(CXCursor cursor);
1255
1256
1257
1260 unsigned int line {}, column {};
1261 friend bool operator<(
const SimpleLoc &a,
const SimpleLoc &b)
1263 return a.line != b.line ? a.line < b.line : a.column < b.column;
1267
1268
1269
1270
1271 QMap<SimpleLoc, CXCursor> declMap_;
1275 std::set<QString> allHeaders_;
1276 QHash<CXFile,
bool> isInterestingCache_;
1280
1281
1282 bool ignoredSymbol(
const QString &symbolName)
1284 if (symbolName == QLatin1String(
"QPrivateSignal"))
1287 if (symbolName.startsWith(
"_qt_property_"))
1290 if (symbolName.startsWith(
"<deduction guide"))
1295 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1296 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1297 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc,
Node **fnNode,
1298 bool &ignoreSignature);
1299 void processFunction(
FunctionNode *fn, CXCursor cursor);
1300 bool parseProperty(
const QString &spelling,
const Location &loc);
1301 void readParameterNamesAndAttributes(
FunctionNode *fn, CXCursor cursor);
1302 Aggregate *getSemanticParent(CXCursor cursor);
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1319 bool hasSingletonMacro =
false;
1321 visitChildrenLambda(cursor, [&hasSingletonMacro](CXCursor child) -> CXChildVisitResult {
1323 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1324 CXSourceRange range = clang_getCursorExtent(child);
1325 QString sourceText = getSpelling(range);
1327 static const QRegularExpression qmlSingletonPattern(
1328 R"(Q_CLASSINFO\s*\(\s*["\']QML\.Singleton["\']\s*,\s*["\']true["\']\s*\))");
1329 if (qmlSingletonPattern.match(sourceText).hasMatch()) {
1330 hasSingletonMacro =
true;
1331 return CXChildVisit_Break;
1336 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1337 QString spelling = fromCXString(clang_getCursorSpelling(child));
1338 if (spelling ==
"QmlIsSingleton"_L1) {
1339 hasSingletonMacro =
true;
1340 return CXChildVisit_Break;
1344 return CXChildVisit_Continue;
1347 return hasSingletonMacro;
1351
1352
1353
1354CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1356 auto kind = clang_getCursorKind(cursor);
1357 if (clang_isDeclaration(kind)) {
1359 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1360 declMap_.insert(l, cursor);
1361 return CXChildVisit_Recurse;
1363 return CXChildVisit_Continue;
1367
1368
1369
1370
1373 CXCursor sp = clang_getCursorSemanticParent(cursor);
1374 CXCursor lp = clang_getCursorLexicalParent(cursor);
1375 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1378 return static_cast<Aggregate *>(spn);
1384CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1385 bool &ignoreSignature)
1387 switch (clang_getCursorKind(cursor)) {
1388 case CXCursor_Namespace:
1389 return CXChildVisit_Recurse;
1390 case CXCursor_FunctionDecl:
1391 case CXCursor_FunctionTemplate:
1392 case CXCursor_CXXMethod:
1393 case CXCursor_Constructor:
1394 case CXCursor_Destructor:
1395 case CXCursor_ConversionFunction: {
1396 ignoreSignature =
false;
1397 if (ignoredSymbol(functionName(cursor))) {
1399 ignoreSignature =
true;
1404 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1405 readParameterNamesAndAttributes(fn, cursor);
1409 if (
const auto function_declaration = declaration->getAsFunction()) {
1410 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1411 if (llvm::dyn_cast_if_present<
clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1412 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1416 QString name = functionName(cursor);
1417 if (ignoredSymbol(name))
1418 return CXChildVisit_Continue;
1419 Aggregate *semanticParent = getSemanticParent(cursor);
1420 if (semanticParent && semanticParent
->isClass()) {
1422 processFunction(candidate, cursor);
1423 if (!candidate->isSpecialMemberFunction()) {
1425 return CXChildVisit_Continue;
1427 candidate->setImplicitlyGenerated(
true);
1437 return CXChildVisit_Continue;
1440CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1442 auto kind = clang_getCursorKind(cursor);
1445 case CXCursor_TypeAliasTemplateDecl:
1446 case CXCursor_TypeAliasDecl: {
1447 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1448 QString aliasedType;
1450 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1454 if (kind == CXCursor_TypeAliasTemplateDecl) {
1456 if (
const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1457 if (
const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1458 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1459 aliasedType = QString::fromStdString(underlyingType.getAsString());
1464 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1465 if (aliasedCXType.kind != CXType_Invalid) {
1466 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1470 if (!aliasedType.isEmpty()) {
1471 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1476 ta->setTemplateDecl(get_template_declaration(templateDecl));
1478 return CXChildVisit_Continue;
1480 case CXCursor_StructDecl:
1481 case CXCursor_UnionDecl:
1482 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1483 return CXChildVisit_Continue;
1485 case CXCursor_ClassTemplate:
1487 case CXCursor_ClassDecl: {
1488 if (!clang_isCursorDefinition(cursor))
1489 return CXChildVisit_Continue;
1492 return CXChildVisit_Continue;
1494 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1496 Aggregate *semanticParent = getSemanticParent(cursor);
1497 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1498 return CXChildVisit_Continue;
1501 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1502 clang_getTemplateCursorKind(cursor) : kind;
1505 if (actualKind == CXCursor_StructDecl)
1507 else if (actualKind == CXCursor_UnionDecl)
1510 auto *classe =
new ClassNode(type, semanticParent, className);
1514 classe->setLocation(location);
1516 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1517 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1518 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1522 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1524 if (detectQmlSingleton(cursor)) {
1525 classe->setQmlSingleton(
true);
1528 if (kind == CXCursor_ClassTemplate) {
1530 classe->setTemplateDecl(get_template_declaration(template_declaration));
1533 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1536 case CXCursor_CXXBaseSpecifier: {
1538 return CXChildVisit_Continue;
1540 auto type = clang_getCursorType(cursor);
1541 auto baseCursor = clang_getTypeDeclaration(type);
1543 auto classe =
static_cast<ClassNode *>(parent_);
1545 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1546 classe->addUnresolvedBaseClass(access,
1547 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1548 return CXChildVisit_Continue;
1550 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1551 classe->addResolvedBaseClass(access, baseClasse);
1552 return CXChildVisit_Continue;
1554 case CXCursor_Namespace: {
1555 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1565 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1568 case CXCursor_FunctionTemplate:
1570 case CXCursor_FunctionDecl:
1571 case CXCursor_CXXMethod:
1572 case CXCursor_Constructor:
1573 case CXCursor_Destructor:
1574 case CXCursor_ConversionFunction: {
1576 return CXChildVisit_Continue;
1577 QString name = functionName(cursor);
1578 if (ignoredSymbol(name))
1579 return CXChildVisit_Continue;
1582 return CXChildVisit_Continue;
1585 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1586 if (!clang_Range_isNull(range)) {
1587 QString comment = getSpelling(range);
1588 if (comment.startsWith(
"//!")) {
1589 qsizetype tag = comment.indexOf(QChar(
'['));
1591 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1593 fn->setTag(comment.mid(tag, end - tag));
1598 processFunction(fn, cursor);
1600 if (kind == CXCursor_FunctionTemplate) {
1602 fn->setTemplateDecl(get_template_declaration(template_declaration));
1605 if (!clang_Location_isInSystemHeader(loc))
1606 autoGenerateSmfDoc(fn, parent_->name());
1608 return CXChildVisit_Continue;
1610#if CINDEX_VERSION
>= 36
1611 case CXCursor_FriendDecl: {
1615 case CXCursor_EnumDecl: {
1616 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1617 if (en && en->items().size())
1618 return CXChildVisit_Continue;
1620 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1622 if (clang_Cursor_isAnonymous(cursor)) {
1623 enumTypeName =
"anonymous";
1629 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1631 en =
static_cast<EnumNode *>(n);
1635 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1638 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1642 visitChildrenLambda(cursor, [&](CXCursor cur) {
1643 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1644 return CXChildVisit_Continue;
1647 visitChildrenLambda(cur, [&](CXCursor cur) {
1648 if (clang_isExpression(clang_getCursorKind(cur))) {
1649 value = getSpelling(clang_getCursorExtent(cur));
1650 return CXChildVisit_Break;
1652 return CXChildVisit_Continue;
1654 if (value.isEmpty()) {
1655 QLatin1String hex(
"0x");
1656 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1657 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1659 value = QString::number(clang_getEnumConstantDeclValue(cur));
1663 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1664 return CXChildVisit_Continue;
1666 return CXChildVisit_Continue;
1668 case CXCursor_FieldDecl:
1669 case CXCursor_VarDecl: {
1671 return CXChildVisit_Continue;
1673 auto value_declaration =
1675 assert(value_declaration);
1678 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1680 var->setAccess(access);
1682 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1683 value_declaration->getType(),
1684 value_declaration->getASTContext()
1686 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1688 return CXChildVisit_Continue;
1690 case CXCursor_TypedefDecl: {
1692 return CXChildVisit_Continue;
1693 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1697 visitChildrenLambda(cursor, [&](CXCursor cur) {
1698 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1699 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1700 return CXChildVisit_Continue;
1702 visitChildrenLambda(cursor, [&](CXCursor cur) {
1703 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1704 return CXChildVisit_Continue;
1707 if (en && en->isEnumType())
1708 static_cast<EnumNode *>(en)->setFlagsType(td);
1709 return CXChildVisit_Break;
1711 return CXChildVisit_Break;
1713 return CXChildVisit_Continue;
1719 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1722 return CXChildVisit_Continue;
1731 visitChildrenLambda(cursor, [&](CXCursor cur) {
1732 auto kind = clang_getCursorKind(cur);
1733 if (kind == CXCursor_AnnotateAttr) {
1734 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1735 if (annotation == QLatin1String(
"qt_slot")) {
1737 }
else if (annotation == QLatin1String(
"qt_signal")) {
1740 if (annotation == QLatin1String(
"qt_invokable"))
1742 }
else if (kind == CXCursor_CXXOverrideAttr) {
1744 }
else if (kind == CXCursor_ParmDecl) {
1746 return CXChildVisit_Break;
1748 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1749 parameters
[i
].setName(name);
1752 Q_ASSERT(parameter_declaration);
1756 if (!default_value.empty())
1757 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1761 return CXChildVisit_Continue;
1767 CXCursorKind kind = clang_getCursorKind(cursor);
1768 CXType funcType = clang_getCursorType(cursor);
1775 : clang_CXXMethod_isPureVirtual(cursor)
1792 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1794 if (kind == CXCursor_Constructor
1796 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1798 else if (kind == CXCursor_Destructor)
1800 else if (kind != CXCursor_ConversionFunction)
1801 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1802 function_declaration->getReturnType(),
1803 function_declaration->getASTContext()
1806 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1811 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1817 (constructor_declaration && constructor_declaration->isExplicit()) ||
1818 (conversion_declaration && conversion_declaration->isExplicit())
1821 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1826 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1827 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1829 if (function_prototype) {
1830 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1832 if (exception_specification.Type !=
clang::ExceptionSpecificationType::EST_None) {
1833 const std::string exception_specification_spelling =
1834 exception_specification.NoexceptExpr ? get_expression_as_string(
1835 exception_specification.NoexceptExpr,
1836 function_declaration->getASTContext()
1839 if (exception_specification_spelling !=
"false")
1840 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1847#if LIBCLANG_VERSION_MAJOR >= 21
1848 if (
const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1849 trailing_requires.ConstraintExpr) {
1850 QString requires_str = QString::fromStdString(
1851 get_expression_as_string(trailing_requires.ConstraintExpr,
1852 function_declaration->getASTContext()));
1853 fn->setTrailingRequiresClause(requires_str.simplified());
1856 if (
const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1857 QString requires_str = QString::fromStdString(
1858 get_expression_as_string(trailing_requires,
1859 function_declaration->getASTContext()));
1860 fn->setTrailingRequiresClause(requires_str.simplified());
1864 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1865 if (refQualKind == CXRefQualifier_LValue)
1867 else if (refQualKind == CXRefQualifier_RValue)
1876 parameters
.reserve(function_declaration->getNumParams()
);
1878 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1879 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1881 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1883 parameter_declaration->getASTContext()
1886 if (!parameter_type.isCanonical())
1887 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1888 parameter_type.getCanonicalType(),
1889 parameter_declaration->getASTContext()
1894 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
1900 if (clang_isFunctionTypeVariadic(funcType))
1901 parameters.append(QStringLiteral(
"..."));
1902 readParameterNamesAndAttributes(fn, cursor);
1904 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1906 Q_ASSERT(function_declaration);
1907 if (function_declaration->isThisDeclarationADefinition())
1914 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
1915 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
1916 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
1919 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
1920 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
1921 if (lpIdx <= 0 || rpIdx <= lpIdx)
1924 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1925 signature = signature.simplified();
1926 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
1928 static const QStringList attrs =
1929 QStringList() <<
"READ" <<
"MEMBER" <<
"WRITE"
1930 <<
"NOTIFY" <<
"CONSTANT" <<
"FINAL"
1931 <<
"REQUIRED" <<
"BINDABLE" <<
"DESIGNABLE"
1932 <<
"RESET" <<
"REVISION" <<
"SCRIPTABLE"
1933 <<
"STORED" <<
"USER";
1937 auto it =
std::find_if(parts.cbegin(), parts.cend(),
1938 [](
const QString &attr) ->
bool {
1939 return attrs.contains(attr);
1942 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
1945 QStringList typeParts;
1946 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
1947 parts.erase(parts.cbegin(), it);
1948 QString name = typeParts.takeLast();
1951 while (!name.isEmpty() && name.front() == QChar(
'*')) {
1952 typeParts.last().push_back(name.front());
1957 if (parts.size() < 2 || name.isEmpty())
1962 property->setLocation(loc);
1963 property->setDataType(typeParts.join(QChar(
' ')));
1966 while (i < parts.size()) {
1967 const QString &key = parts.at(i++);
1969 if (key ==
"CONSTANT") {
1970 property->setConstant();
1971 }
else if (key ==
"REQUIRED") {
1972 property->setRequired();
1974 if (i < parts.size()) {
1975 QString value = parts.at(i++);
1976 if (key ==
"READ") {
1978 }
else if (key ==
"WRITE") {
1980 property->setWritable(
true);
1981 }
else if (key ==
"MEMBER") {
1982 property->setWritable(
true);
1983 }
else if (key ==
"STORED") {
1984 property->setStored(value.toLower() ==
"true");
1985 }
else if (key ==
"BINDABLE") {
1988 }
else if (key ==
"RESET") {
1990 }
else if (key ==
"NOTIFY") {
1999
2000
2001
2002
2003
2007 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
2008 auto decl_it = declMap_.upperBound(docloc);
2009 if (decl_it == declMap_.end())
2012 unsigned int declLine = decl_it.key().line;
2013 unsigned int nextCommentLine;
2014 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
2015 if (nextCommentLine < declLine)
2019 if (decl_it != declMap_.begin()) {
2020 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(
std::prev(decl_it))));
2021 unsigned int prevDeclLine;
2022 clang_getPresumedLocation(prevDeclEnd,
nullptr, &prevDeclLine,
nullptr);
2023 if (prevDeclLine >= docloc.line) {
2026 auto parent = clang_getCursorLexicalParent(*decl_it);
2027 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2031 auto *node = findNodeForCursor(qdb_, *decl_it);
2033 if (node && node->isFunction(Genus::CPP))
2034 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2041 const std::vector<QByteArray>& include_paths,
2042 const QList<QByteArray>& defines,
2049 m_allHeaders = config.getHeaderFiles();
2050 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2058 "-fms-compatibility-version=19",
2062 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2063 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2064 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2065 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2066 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2067 "-Wno-constant-logical-operand",
2068 "-Wno-macro-redefined",
2069 "-Wno-nullability-completeness",
2070 "-fvisibility=default",
2077 std::vector<
const char *> pointers;
2078 pointers.reserve(args.size());
2079 for (
const auto &arg : args)
2080 pointers.push_back(arg.constData());
2085
2086
2087
2092 args.emplace_back(arg);
2095 for (
const auto &p : std::as_const(defines))
2100
2101
2103 const std::vector<QByteArray>& include_paths,
2104 std::vector<QByteArray>& args
2106 if (include_paths.empty()) {
2107 qCWarning(lcQdoc) <<
"No include paths provided."
2108 <<
"Set 'includepaths' in the qdocconf file"
2109 <<
"or pass -I flags on the command line."
2110 <<
"C++ parsing may produce incomplete results.";
2112 args.insert(args.end(), include_paths.begin(), include_paths.end());
2117
2118
2119
2120
2123 QString module_header,
2124 const std::set<Config::HeaderFilePath>& all_headers,
2125 const std::vector<QByteArray>& include_paths,
2126 const QList<QByteArray>& defines,
2127 const InclusionPolicy& policy
2129 static std::vector<QByteArray> arguments{};
2131 if (module_header.isEmpty())
return std::nullopt;
2133 getDefaultArgs(defines, arguments);
2134 getMoreArgs(include_paths, arguments);
2136 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2137 | CXTranslationUnit_SkipFunctionBodies
2138 | CXTranslationUnit_KeepGoing);
2142 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2143 if (!pch_directory.isValid())
return std::nullopt;
2145 const QByteArray module = module_header.toUtf8();
2148 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2151 struct FindPredicate
2153 enum SearchType { Any, Module };
2154 QByteArray &candidate_;
2155 const QByteArray &module_;
2157 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2158 SearchType type = Any)
2159 : candidate_(candidate), module_(module), type_(type)
2163 bool operator()(
const QByteArray &p)
const
2165 if (type_ != Any && !p.endsWith(module_))
2167 candidate_ = p +
"/";
2168 candidate_.append(module_);
2169 if (p.startsWith(
"-I"))
2170 candidate_ = candidate_.mid(2);
2171 return QFile::exists(QString::fromUtf8(candidate_));
2176 QByteArray candidate;
2177 auto it =
std::find_if(include_paths.begin(), include_paths.end(),
2178 FindPredicate(candidate, module, FindPredicate::Module));
2179 if (it == include_paths.end())
2180 it =
std::find_if(include_paths.begin(), include_paths.end(),
2181 FindPredicate(candidate, module, FindPredicate::Any));
2182 if (it != include_paths.end())
2183 header =
std::move(candidate);
2185 if (header.isEmpty()) {
2186 qWarning() <<
"(qdoc) Could not find the module header in include paths for module"
2187 << module <<
" (include paths: " << include_paths <<
")";
2188 qWarning() <<
" Artificial module header built from header dirs in qdocconf "
2191 arguments.push_back(
"-xc++");
2195 QString tmpHeader = pch_directory.path() +
"/" + module;
2196 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2197 QTextStream out(&tmpHeaderFile);
2198 if (header.isEmpty()) {
2199 for (
const auto& [header_path, header_name] : all_headers) {
2200 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2203 if (header_name.endsWith(
"_p.h"_L1))
2204 shouldInclude = shouldInclude && policy.showInternal;
2206 if (shouldInclude) {
2207 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2211 QFileInfo headerFile(header);
2212 if (!headerFile.exists()) {
2213 qWarning() <<
"Could not find module header file" << header;
2214 return std::nullopt;
2217 out <<
"#include \"" << header <<
"\"\n";
2219 if (policy.showInternal) {
2220 for (
const auto& [header_path, header_name] : all_headers) {
2221 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2222 if (header_name.endsWith(
"_p.h"_L1) && shouldInclude)
2223 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2229 const auto argPointers = toConstCharPointers(arguments);
2230 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2232 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2233 static_cast<
int>(argPointers.size()),
nullptr, 0,
2234 flags_ | CXTranslationUnit_ForSerialization, &tu
.tu);
2235 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << tmpHeader << arguments
2236 <<
") returns" << err;
2241 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2242 return std::nullopt;
2245 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2246 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2247 clang_defaultSaveOptions(tu));
2249 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2250 return std::nullopt;
2255 CXCursor cur = clang_getTranslationUnitCursor(tu);
2256 auto &config = Config::instance();
2257 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2259 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2261 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2266 if (t.count(QChar(
'.')) > 1)
2267 t.truncate(t.lastIndexOf(QChar(
'.')));
2272
2273
2274
2275
2276
2277
2278
2279
2282 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2283 | CXTranslationUnit_SkipFunctionBodies
2284 | CXTranslationUnit_KeepGoing);
2288 getDefaultArgs(m_defines, m_args);
2289 if (m_pch && !filePath.endsWith(
".mm")
2290 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2291 m_args.push_back(
"-w");
2292 m_args.push_back(
"-include-pch");
2293 m_args.push_back((*m_pch).get().name);
2295 getMoreArgs(m_includePaths, m_args);
2298 const auto argPointers = toConstCharPointers(m_args);
2299 const QByteArray filePathLocal = filePath.toLocal8Bit();
2301 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2302 static_cast<
int>(argPointers.size()),
nullptr, 0,
flags_, &tu
.tu);
2303 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << filePath << m_args
2304 <<
") returns" << err;
2308 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2312 ParsedCppFileIR parse_result{};
2314 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2315 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2319 unsigned int numTokens = 0;
2320 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2321 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2323 for (
unsigned int i = 0; i < numTokens; ++i) {
2324 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2326 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2327 if (!comment.startsWith(
"/*!"))
2330 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2333 Doc::trimCStyleComment(loc, comment);
2336 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2342 if (i + 1 < numTokens) {
2344 CXSourceLocation nextCommentLoc = commentLoc;
2345 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2347 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2354 bool future =
false;
2356 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2357 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2358 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
2363 QStringLiteral(
"Cannot tie this documentation to anything"),
2364 QStringLiteral(
"qdoc found a /*! ... */ comment, but there was no "
2365 "topic command (e.g., '\\%1', '\\%2') in the "
2366 "comment and qdoc could not associate the "
2367 "declaration or definition following the "
2368 "comment with a documented entity.")
2375 CXCursor cur = clang_getCursor(tu, commentLoc);
2377 CXCursorKind kind = clang_getCursorKind(cur);
2378 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2380 if (kind == CXCursor_Namespace) {
2381 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2383 cur = clang_getCursorLexicalParent(cur);
2388 clang_disposeTokens(tu, tokens, numTokens);
2389 m_namespaceScope.clear();
2392 return parse_result;
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2408 const QString &idTag, QStringList context)
2410 Node *fnNode =
nullptr;
2412
2413
2414
2415
2416
2417 if (!idTag.isEmpty()) {
2418 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2421 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2424
2425
2426
2427
2428 auto *fn =
static_cast<FunctionNode *>(fnNode);
2429 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split(
'(');
2430 if (leftParenSplit.size() > 1) {
2431 QStringList rightParenSplit = leftParenSplit[1].split(
')');
2432 if (!rightParenSplit.empty()) {
2433 QString params = rightParenSplit[0];
2434 if (!params.isEmpty()) {
2435 QStringList commaSplit = params.split(
',');
2437 if (parameters
.count() == commaSplit.size()) {
2438 for (
int i = 0; i < parameters
.count(); ++i) {
2439 QStringList blankSplit = commaSplit[i].split(
' ', Qt::SkipEmptyParts);
2440 if (blankSplit.size() > 1) {
2441 QString pName = blankSplit.last();
2443 auto it =
std::find_if(
std::begin(pName),
std::end(pName),
2444 [](
const QChar &c) {
return c.isLetter(); });
2445 parameters
[i
].setName(
2446 pName.remove(0,
std::distance(
std::begin(pName), it)));
2456 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2457 | CXTranslationUnit_SkipFunctionBodies
2458 | CXTranslationUnit_KeepGoing);
2462 getDefaultArgs(m_defines, m_args);
2465 m_args.push_back(
"-w");
2466 m_args.push_back(
"-include-pch");
2467 m_args.push_back((*m_pch).get().name);
2472 for (
const auto &ns : std::as_const(context))
2473 s_fn.prepend(
"namespace " + ns.toUtf8() +
" {");
2474 s_fn += fnSignature.toUtf8();
2475 if (!s_fn.endsWith(
";"))
2477 s_fn.append(context.size(),
'}');
2480 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2481 static_cast<
unsigned long>(s_fn.size()) };
2482 const auto argPointers = toConstCharPointers(m_args);
2483 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2484 int(argPointers.size()), &unsavedFile, 1, flags, &tu
.tu);
2485 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << dummyFileName << m_args
2486 <<
") returns" << err;
2489 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2493
2494
2495
2496
2497
2498 CXCursor cur = clang_getTranslationUnitCursor(tu);
2499 auto &config = Config::instance();
2500 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2501 bool ignoreSignature =
false;
2505 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2506 const auto &config = Config::instance();
2507 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2508 return FnMatchError{ fnSignature, location };
static const clang::Decl * get_cursor_declaration(CXCursor cursor)
Returns the underlying Decl that cursor represents.
static QString reconstructQualifiedPathForCursor(CXCursor cur)
Reconstruct the qualified path name of a function that is being overridden.
static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName, const clang::FunctionDecl *func_decl, NodeVector &candidates)
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
static std::optional< SfinaeConstraint > detect_sfinae_constraint(const clang::NonTypeTemplateParmDecl *param)
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::vector< const char * > toConstCharPointers(const std::vector< QByteArray > &args)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static bool is_enable_if_name(const std::string &qualified_name)
static float getUnpatchedVersion(QString t)
void getMoreArgs(const std::vector< QByteArray > &include_paths, std::vector< QByteArray > &args)
Load the include paths into args.
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< QByteArray > &args)
Load the default arguments and the defines into args.
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
Returns a string representing the name of type as if it was referred to at the end of the translation...
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
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.
void markAutoGenerated()
Marks this documentation as auto-generated by QDoc.
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)
bool isSpecialMemberFunction() const
bool isDeletedAsWritten() const
void markExplicitlyDefaulted()
void setMetaness(Metaness metaness)
bool isExplicitlyDefaulted() const
Parameters & parameters()
void setHiddenFriend(bool b)
The Location class provides a way to mark a location in a file.
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.
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.
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
void setDoc(const Doc &doc, bool replace=false)
Sets this Node's Doc to doc.
bool isClass() const
Returns true if the node type is Class.
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
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()