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
899 auto kind = clang_getCursorKind(cur);
900 if (clang_isInvalid(kind))
902 if (kind == CXCursor_TranslationUnit)
910 case CXCursor_TemplateTypeParameter:
911 case CXCursor_NonTypeTemplateParameter:
912 case CXCursor_TemplateTemplateParameter:
922 auto parent =
static_cast<Aggregate *>(p);
925 if (clang_Cursor_isAnonymous(cur)) {
928 QLatin1String(
"anonymous"));
930 name = fromCXString(clang_getCursorSpelling(cur));
933 case CXCursor_Namespace:
935 case CXCursor_StructDecl:
936 case CXCursor_ClassDecl:
937 case CXCursor_UnionDecl:
938 case CXCursor_ClassTemplate:
940 case CXCursor_FunctionDecl:
941 case CXCursor_FunctionTemplate:
942 case CXCursor_CXXMethod:
943 case CXCursor_Constructor:
944 case CXCursor_Destructor:
945 case CXCursor_ConversionFunction: {
947 parent->findChildren(functionName(cur), candidates);
950 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() !=
clang::Decl::FOK_None) {
952 lexical_parent && lexical_parent
->isAggregate() && lexical_parent != parent) {
953 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
960 if (candidates.isEmpty()) {
961 auto *func_decl = cur_decl ? cur_decl->getAsFunction() :
nullptr;
965 QString funcName = functionName(cur);
966 QSet<ClassNode *> searched_classes;
968 for (
unsigned i = 0; i < func_decl->getNumParams(); ++i) {
969 auto class_name = classNameFromParameterType(func_decl->getParamDecl(i)->getType());
973 auto *class_node = qdb->findClassNode(class_name->split(
"::"_L1));
974 if (!class_node || searched_classes.contains(class_node))
977 searched_classes.insert(class_node);
979 class_node->findChildren(funcName, class_candidates);
981 for (Node *candidate : class_candidates) {
982 if (!candidate->isFunction(Genus::CPP))
984 auto *fn =
static_cast<FunctionNode *>(candidate);
985 if (fn->isHiddenFriend())
986 candidates.append(candidate);
991 if (candidates.isEmpty())
994 CXType funcType = clang_getCursorType(cur);
995 auto numArg = clang_getNumArgTypes(funcType);
996 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
997 QVarLengthArray<QString, 20> args;
1000 if (kind == CXCursor_FunctionTemplate)
1001 relaxed_template_declaration = get_template_declaration(
1005 for (Node *candidate : std::as_const(candidates)) {
1006 if (!candidate->isFunction(Genus::CPP))
1009 auto fn =
static_cast<FunctionNode *>(candidate);
1011 if (!fn->templateDecl() && relaxed_template_declaration)
1014 if (fn->templateDecl() && !relaxed_template_declaration)
1017 if (fn->templateDecl() && relaxed_template_declaration &&
1018 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1021 const Parameters ¶meters = fn->parameters();
1023 if (parameters.count() != numArg + isVariadic) {
1025 if (numArg > 0 && parameters.isPrivateSignal() &&
1026 (parameters.isEmpty() || !parameters.last().type().endsWith(
1027 QLatin1String(
"QPrivateSignal")))) {
1028 if (parameters.count() != --numArg + isVariadic)
1035 if (fn->isConst() !=
bool(clang_CXXMethod_isConst(cur)))
1038 if (isVariadic && parameters.last().type() != QLatin1String(
"..."))
1041 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1044 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1047 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1049 bool typesDiffer =
false;
1050 for (
int i = 0; i < numArg; ++i) {
1051 auto *paramDecl = function_declaration->getParamDecl(i);
1052 auto paramType = paramDecl->getOriginalType();
1054 if (args.size() <= i)
1055 args.append(QString::fromStdString(get_fully_qualified_type_name(
1056 paramType, function_declaration->getASTContext()
1059 QString recordedType = parameters.at(i).type();
1060 QString typeSpelling = args.at(i);
1062 typesDiffer = recordedType != typeSpelling;
1072 const bool isBareTemplateTypeParm =
1073 paramType.getTypePtrOrNull()
1074 && llvm::isa<clang::TemplateTypeParmType>(paramType.getTypePtr());
1075 if (!isBareTemplateTypeParm) {
1076 QStringView canonicalType = parameters.at(i).canonicalType();
1077 if (!canonicalType.isEmpty()) {
1078 typesDiffer = canonicalType !=
1079 QString::fromStdString(get_fully_qualified_type_name(
1080 paramType.getCanonicalType(),
1081 function_declaration->getASTContext()
1097 case CXCursor_EnumDecl:
1098 return parent->findNonfunctionChild(name, &
Node::isEnumType);
1099 case CXCursor_FieldDecl:
1100 case CXCursor_VarDecl:
1102 case CXCursor_TypedefDecl:
1111 CXCursor *overridden;
1112 unsigned int numOverridden = 0;
1113 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
1114 for (uint i = 0; i < numOverridden; ++i) {
1115 QString path = reconstructQualifiedPathForCursor(overridden[i]);
1116 if (!path.isEmpty()) {
1118 fn->setOverridesThis(path);
1122 clang_disposeOverriddenCursors(overridden);
1126
1127
1128
1129
1141 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
1142 if (fn->isVirtual())
1143 docSource += u" This destructor is virtual."_s;
1145 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
1147 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
1149 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
1152 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
1153 ? params.at(0).name()
1155 docSource = fn->isCAssign()
1156 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
1157 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
1160 if (docSource.isEmpty())
1163 if (fn->isDeletedAsWritten())
1164 docSource += u" This function is deleted."_s;
1166 static const QSet<QString> noMetaCommands;
1167 static const QSet<QString> noTopics;
1179 internalFilePatterns_(internalFilePatterns)
1181 std::transform(allHeaders.cbegin(), allHeaders.cend(),
std::inserter(allHeaders_, allHeaders_.begin()),
1182 [](
const auto& header_file_path) ->
const QString& {
return header_file_path.filename; });
1189 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1190 auto loc = clang_getCursorLocation(cur);
1191 if (clang_Location_isFromMainFile(loc))
1192 return visitSource(cur, loc);
1195 clang_getFileLocation(loc, &file,
nullptr,
nullptr,
nullptr);
1196 bool isInteresting =
false;
1197 auto it = isInterestingCache_.find(file);
1198 if (it != isInterestingCache_.end()) {
1199 isInteresting = *it;
1201 QFileInfo fi(fromCXString(clang_getFileName(file)));
1203 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1204 isInterestingCache_[file] = isInteresting;
1206 if (isInteresting) {
1207 return visitHeader(cur, loc);
1210 return CXChildVisit_Continue;
1212 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1216
1217
1218
1221 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1222 auto loc = clang_getCursorLocation(cur);
1223 if (clang_Location_isFromMainFile(loc))
1224 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1225 return CXChildVisit_Continue;
1227 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1233 bool detectQmlSingleton(CXCursor cursor);
1235
1236
1237
1240 unsigned int line {}, column {};
1241 friend bool operator<(
const SimpleLoc &a,
const SimpleLoc &b)
1243 return a.line != b.line ? a.line < b.line : a.column < b.column;
1247
1248
1249
1250
1251 QMap<SimpleLoc, CXCursor> declMap_;
1255 std::set<QString> allHeaders_;
1256 QHash<CXFile,
bool> isInterestingCache_;
1260
1261
1262 bool ignoredSymbol(
const QString &symbolName)
1264 if (symbolName == QLatin1String(
"QPrivateSignal"))
1267 if (symbolName.startsWith(
"_qt_property_"))
1270 if (symbolName.startsWith(
"<deduction guide"))
1275 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1276 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1277 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc,
Node **fnNode,
1278 bool &ignoreSignature);
1279 void processFunction(
FunctionNode *fn, CXCursor cursor);
1280 bool parseProperty(
const QString &spelling,
const Location &loc);
1281 void readParameterNamesAndAttributes(
FunctionNode *fn, CXCursor cursor);
1282 Aggregate *getSemanticParent(CXCursor cursor);
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1299 bool hasSingletonMacro =
false;
1301 visitChildrenLambda(cursor, [&hasSingletonMacro](CXCursor child) -> CXChildVisitResult {
1303 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1304 CXSourceRange range = clang_getCursorExtent(child);
1305 QString sourceText = getSpelling(range);
1307 static const QRegularExpression qmlSingletonPattern(
1308 R"(Q_CLASSINFO\s*\(\s*["\']QML\.Singleton["\']\s*,\s*["\']true["\']\s*\))");
1309 if (qmlSingletonPattern.match(sourceText).hasMatch()) {
1310 hasSingletonMacro =
true;
1311 return CXChildVisit_Break;
1316 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1317 QString spelling = fromCXString(clang_getCursorSpelling(child));
1318 if (spelling ==
"QmlIsSingleton"_L1) {
1319 hasSingletonMacro =
true;
1320 return CXChildVisit_Break;
1324 return CXChildVisit_Continue;
1327 return hasSingletonMacro;
1331
1332
1333
1334CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1336 auto kind = clang_getCursorKind(cursor);
1337 if (clang_isDeclaration(kind)) {
1339 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1340 declMap_.insert(l, cursor);
1341 return CXChildVisit_Recurse;
1343 return CXChildVisit_Continue;
1347
1348
1349
1350
1353 CXCursor sp = clang_getCursorSemanticParent(cursor);
1354 CXCursor lp = clang_getCursorLexicalParent(cursor);
1355 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1358 return static_cast<Aggregate *>(spn);
1364CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1365 bool &ignoreSignature)
1367 switch (clang_getCursorKind(cursor)) {
1368 case CXCursor_Namespace:
1369 return CXChildVisit_Recurse;
1370 case CXCursor_FunctionDecl:
1371 case CXCursor_FunctionTemplate:
1372 case CXCursor_CXXMethod:
1373 case CXCursor_Constructor:
1374 case CXCursor_Destructor:
1375 case CXCursor_ConversionFunction: {
1376 ignoreSignature =
false;
1377 if (ignoredSymbol(functionName(cursor))) {
1379 ignoreSignature =
true;
1384 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1385 readParameterNamesAndAttributes(fn, cursor);
1389 if (
const auto function_declaration = declaration->getAsFunction()) {
1390 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1391 if (llvm::dyn_cast_if_present<
clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1392 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1396 QString name = functionName(cursor);
1397 if (ignoredSymbol(name))
1398 return CXChildVisit_Continue;
1399 Aggregate *semanticParent = getSemanticParent(cursor);
1400 if (semanticParent && semanticParent
->isClass()) {
1402 processFunction(candidate, cursor);
1403 if (!candidate->isSpecialMemberFunction()) {
1405 return CXChildVisit_Continue;
1407 candidate->setImplicitlyGenerated(
true);
1417 return CXChildVisit_Continue;
1420CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1422 auto kind = clang_getCursorKind(cursor);
1425 case CXCursor_TypeAliasTemplateDecl:
1426 case CXCursor_TypeAliasDecl: {
1427 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1428 QString aliasedType;
1430 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1434 if (kind == CXCursor_TypeAliasTemplateDecl) {
1436 if (
const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1437 if (
const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1438 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1439 aliasedType = QString::fromStdString(underlyingType.getAsString());
1444 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1445 if (aliasedCXType.kind != CXType_Invalid) {
1446 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1450 if (!aliasedType.isEmpty()) {
1451 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1456 ta->setTemplateDecl(get_template_declaration(templateDecl));
1458 return CXChildVisit_Continue;
1460 case CXCursor_StructDecl:
1461 case CXCursor_UnionDecl:
1462 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1463 return CXChildVisit_Continue;
1465 case CXCursor_ClassTemplate:
1467 case CXCursor_ClassDecl: {
1468 if (!clang_isCursorDefinition(cursor))
1469 return CXChildVisit_Continue;
1472 return CXChildVisit_Continue;
1474 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1476 Aggregate *semanticParent = getSemanticParent(cursor);
1477 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1478 return CXChildVisit_Continue;
1481 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1482 clang_getTemplateCursorKind(cursor) : kind;
1485 if (actualKind == CXCursor_StructDecl)
1487 else if (actualKind == CXCursor_UnionDecl)
1490 auto *classe =
new ClassNode(type, semanticParent, className);
1494 classe->setLocation(location);
1496 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1497 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1498 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1502 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1504 if (detectQmlSingleton(cursor)) {
1505 classe->setQmlSingleton(
true);
1508 if (kind == CXCursor_ClassTemplate) {
1510 classe->setTemplateDecl(get_template_declaration(template_declaration));
1513 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1516 case CXCursor_CXXBaseSpecifier: {
1518 return CXChildVisit_Continue;
1520 auto type = clang_getCursorType(cursor);
1521 auto baseCursor = clang_getTypeDeclaration(type);
1523 auto classe =
static_cast<ClassNode *>(parent_);
1525 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1526 classe->addUnresolvedBaseClass(access,
1527 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1528 return CXChildVisit_Continue;
1530 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1531 classe->addResolvedBaseClass(access, baseClasse);
1532 return CXChildVisit_Continue;
1534 case CXCursor_Namespace: {
1535 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1545 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1548 case CXCursor_FunctionTemplate:
1550 case CXCursor_FunctionDecl:
1551 case CXCursor_CXXMethod:
1552 case CXCursor_Constructor:
1553 case CXCursor_Destructor:
1554 case CXCursor_ConversionFunction: {
1556 return CXChildVisit_Continue;
1557 QString name = functionName(cursor);
1558 if (ignoredSymbol(name))
1559 return CXChildVisit_Continue;
1562 return CXChildVisit_Continue;
1565 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1566 if (!clang_Range_isNull(range)) {
1567 QString comment = getSpelling(range);
1568 if (comment.startsWith(
"//!")) {
1569 qsizetype tag = comment.indexOf(QChar(
'['));
1571 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1573 fn->setTag(comment.mid(tag, end - tag));
1578 processFunction(fn, cursor);
1580 if (kind == CXCursor_FunctionTemplate) {
1582 fn->setTemplateDecl(get_template_declaration(template_declaration));
1585 if (!clang_Location_isInSystemHeader(loc))
1586 autoGenerateSmfDoc(fn, parent_->name());
1588 return CXChildVisit_Continue;
1590#if CINDEX_VERSION
>= 36
1591 case CXCursor_FriendDecl: {
1595 case CXCursor_EnumDecl: {
1596 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1597 if (en && en->items().size())
1598 return CXChildVisit_Continue;
1600 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1602 if (clang_Cursor_isAnonymous(cursor)) {
1603 enumTypeName =
"anonymous";
1609 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1611 en =
static_cast<EnumNode *>(n);
1615 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1618 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1622 visitChildrenLambda(cursor, [&](CXCursor cur) {
1623 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1624 return CXChildVisit_Continue;
1627 visitChildrenLambda(cur, [&](CXCursor cur) {
1628 if (clang_isExpression(clang_getCursorKind(cur))) {
1629 value = getSpelling(clang_getCursorExtent(cur));
1630 return CXChildVisit_Break;
1632 return CXChildVisit_Continue;
1634 if (value.isEmpty()) {
1635 QLatin1String hex(
"0x");
1636 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1637 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1639 value = QString::number(clang_getEnumConstantDeclValue(cur));
1643 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1644 return CXChildVisit_Continue;
1646 return CXChildVisit_Continue;
1648 case CXCursor_FieldDecl:
1649 case CXCursor_VarDecl: {
1651 return CXChildVisit_Continue;
1653 auto value_declaration =
1655 assert(value_declaration);
1658 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1660 var->setAccess(access);
1662 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1663 value_declaration->getType(),
1664 value_declaration->getASTContext()
1666 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1668 return CXChildVisit_Continue;
1670 case CXCursor_TypedefDecl: {
1672 return CXChildVisit_Continue;
1673 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1677 visitChildrenLambda(cursor, [&](CXCursor cur) {
1678 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1679 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1680 return CXChildVisit_Continue;
1682 visitChildrenLambda(cursor, [&](CXCursor cur) {
1683 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1684 return CXChildVisit_Continue;
1687 if (en && en->isEnumType())
1688 static_cast<EnumNode *>(en)->setFlagsType(td);
1689 return CXChildVisit_Break;
1691 return CXChildVisit_Break;
1693 return CXChildVisit_Continue;
1699 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1702 return CXChildVisit_Continue;
1711 visitChildrenLambda(cursor, [&](CXCursor cur) {
1712 auto kind = clang_getCursorKind(cur);
1713 if (kind == CXCursor_AnnotateAttr) {
1714 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1715 if (annotation == QLatin1String(
"qt_slot")) {
1717 }
else if (annotation == QLatin1String(
"qt_signal")) {
1720 if (annotation == QLatin1String(
"qt_invokable"))
1722 }
else if (kind == CXCursor_CXXOverrideAttr) {
1724 }
else if (kind == CXCursor_ParmDecl) {
1726 return CXChildVisit_Break;
1728 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1729 parameters
[i
].setName(name);
1732 Q_ASSERT(parameter_declaration);
1736 if (!default_value.empty())
1737 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1741 return CXChildVisit_Continue;
1747 CXCursorKind kind = clang_getCursorKind(cursor);
1748 CXType funcType = clang_getCursorType(cursor);
1755 : clang_CXXMethod_isPureVirtual(cursor)
1772 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1774 if (kind == CXCursor_Constructor
1776 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1778 else if (kind == CXCursor_Destructor)
1780 else if (kind != CXCursor_ConversionFunction)
1781 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1782 function_declaration->getReturnType(),
1783 function_declaration->getASTContext()
1786 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1791 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1797 (constructor_declaration && constructor_declaration->isExplicit()) ||
1798 (conversion_declaration && conversion_declaration->isExplicit())
1801 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1806 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1807 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1809 if (function_prototype) {
1810 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1812 if (exception_specification.Type !=
clang::ExceptionSpecificationType::EST_None) {
1813 const std::string exception_specification_spelling =
1814 exception_specification.NoexceptExpr ? get_expression_as_string(
1815 exception_specification.NoexceptExpr,
1816 function_declaration->getASTContext()
1819 if (exception_specification_spelling !=
"false")
1820 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1827#if LIBCLANG_VERSION_MAJOR >= 21
1828 if (
const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1829 trailing_requires.ConstraintExpr) {
1830 QString requires_str = QString::fromStdString(
1831 get_expression_as_string(trailing_requires.ConstraintExpr,
1832 function_declaration->getASTContext()));
1833 fn->setTrailingRequiresClause(requires_str.simplified());
1836 if (
const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1837 QString requires_str = QString::fromStdString(
1838 get_expression_as_string(trailing_requires,
1839 function_declaration->getASTContext()));
1840 fn->setTrailingRequiresClause(requires_str.simplified());
1844 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1845 if (refQualKind == CXRefQualifier_LValue)
1847 else if (refQualKind == CXRefQualifier_RValue)
1856 parameters
.reserve(function_declaration->getNumParams()
);
1858 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1859 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1861 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1863 parameter_declaration->getASTContext()
1866 if (!parameter_type.isCanonical())
1867 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1868 parameter_type.getCanonicalType(),
1869 parameter_declaration->getASTContext()
1874 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
1880 if (clang_isFunctionTypeVariadic(funcType))
1881 parameters.append(QStringLiteral(
"..."));
1882 readParameterNamesAndAttributes(fn, cursor);
1884 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1886 Q_ASSERT(function_declaration);
1887 if (function_declaration->isThisDeclarationADefinition())
1894 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
1895 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
1896 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
1899 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
1900 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
1901 if (lpIdx <= 0 || rpIdx <= lpIdx)
1904 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1905 signature = signature.simplified();
1906 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
1908 static const QStringList attrs =
1909 QStringList() <<
"READ" <<
"MEMBER" <<
"WRITE"
1910 <<
"NOTIFY" <<
"CONSTANT" <<
"FINAL"
1911 <<
"REQUIRED" <<
"BINDABLE" <<
"DESIGNABLE"
1912 <<
"RESET" <<
"REVISION" <<
"SCRIPTABLE"
1913 <<
"STORED" <<
"USER";
1917 auto it =
std::find_if(parts.cbegin(), parts.cend(),
1918 [](
const QString &attr) ->
bool {
1919 return attrs.contains(attr);
1922 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
1925 QStringList typeParts;
1926 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
1927 parts.erase(parts.cbegin(), it);
1928 QString name = typeParts.takeLast();
1931 while (!name.isEmpty() && name.front() == QChar(
'*')) {
1932 typeParts.last().push_back(name.front());
1937 if (parts.size() < 2 || name.isEmpty())
1942 property->setLocation(loc);
1943 property->setDataType(typeParts.join(QChar(
' ')));
1946 while (i < parts.size()) {
1947 const QString &key = parts.at(i++);
1949 if (key ==
"CONSTANT") {
1950 property->setConstant();
1951 }
else if (key ==
"REQUIRED") {
1952 property->setRequired();
1954 if (i < parts.size()) {
1955 QString value = parts.at(i++);
1956 if (key ==
"READ") {
1958 }
else if (key ==
"WRITE") {
1960 property->setWritable(
true);
1961 }
else if (key ==
"MEMBER") {
1962 property->setWritable(
true);
1963 }
else if (key ==
"STORED") {
1964 property->setStored(value.toLower() ==
"true");
1965 }
else if (key ==
"BINDABLE") {
1968 }
else if (key ==
"RESET") {
1970 }
else if (key ==
"NOTIFY") {
1979
1980
1981
1982
1983
1987 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
1988 auto decl_it = declMap_.upperBound(docloc);
1989 if (decl_it == declMap_.end())
1992 unsigned int declLine = decl_it.key().line;
1993 unsigned int nextCommentLine;
1994 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
1995 if (nextCommentLine < declLine)
1999 if (decl_it != declMap_.begin()) {
2000 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(
std::prev(decl_it))));
2001 unsigned int prevDeclLine;
2002 clang_getPresumedLocation(prevDeclEnd,
nullptr, &prevDeclLine,
nullptr);
2003 if (prevDeclLine >= docloc.line) {
2006 auto parent = clang_getCursorLexicalParent(*decl_it);
2007 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2011 auto *node = findNodeForCursor(qdb_, *decl_it);
2013 if (node && node->isFunction(Genus::CPP))
2014 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2021 const std::vector<QByteArray>& include_paths,
2022 const QList<QByteArray>& defines,
2029 m_allHeaders = config.getHeaderFiles();
2030 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2038 "-fms-compatibility-version=19",
2042 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2043 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2044 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2045 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2046 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2047 "-Wno-constant-logical-operand",
2048 "-Wno-macro-redefined",
2049 "-Wno-nullability-completeness",
2050 "-fvisibility=default",
2056
2057
2058
2065 for (
const auto &p : std::as_const(defines))
2066 args.push_back(p.constData());
2071 QList<QByteArray> result;
2072 for (
const auto& [header_path, _] : allHeaders) {
2073 const QByteArray path =
"-I" + header_path.toLatin1();
2074 const QByteArray parent =
2075 "-I" + QDir::cleanPath(header_path + QLatin1String(
"/../")).toLatin1();
2082
2083
2084
2086 const std::vector<QByteArray>& include_paths,
2087 const std::set<Config::HeaderFilePath>& all_headers,
2088 std::vector<
const char*>& args
2090 if (include_paths.empty()) {
2092
2093
2094
2095
2096 qCWarning(lcQdoc) <<
"No include paths passed to qdoc; guessing reasonable include paths";
2098 QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir +
"/../include"));
2099 args.emplace_back(QByteArray(
"-I" + basicIncludeDir.toLatin1()).constData());
2101 auto include_paths_from_headers = includePathsFromHeaders(all_headers);
2102 args.insert(args.end(), include_paths_from_headers.begin(), include_paths_from_headers.end());
2104 std::copy(include_paths.begin(), include_paths.end(),
std::back_inserter(args));
2109
2110
2111
2112
2115 QString module_header,
2116 const std::set<Config::HeaderFilePath>& all_headers,
2117 const std::vector<QByteArray>& include_paths,
2118 const QList<QByteArray>& defines,
2119 const InclusionPolicy& policy
2121 static std::vector<
const char*> arguments{};
2123 if (module_header.isEmpty())
return std::nullopt;
2125 getDefaultArgs(defines, arguments);
2126 getMoreArgs(include_paths, all_headers, arguments);
2128 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2129 | CXTranslationUnit_SkipFunctionBodies
2130 | CXTranslationUnit_KeepGoing);
2134 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2135 if (!pch_directory.isValid())
return std::nullopt;
2137 const QByteArray module = module_header.toUtf8();
2140 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2143 struct FindPredicate
2145 enum SearchType { Any, Module };
2146 QByteArray &candidate_;
2147 const QByteArray &module_;
2149 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2150 SearchType type = Any)
2151 : candidate_(candidate), module_(module), type_(type)
2155 bool operator()(
const QByteArray &p)
const
2157 if (type_ != Any && !p.endsWith(module_))
2159 candidate_ = p +
"/";
2160 candidate_.append(module_);
2161 if (p.startsWith(
"-I"))
2162 candidate_ = candidate_.mid(2);
2163 return QFile::exists(QString::fromUtf8(candidate_));
2168 QByteArray candidate;
2169 auto it =
std::find_if(include_paths.begin(), include_paths.end(),
2170 FindPredicate(candidate, module, FindPredicate::Module));
2171 if (it == include_paths.end())
2172 it =
std::find_if(include_paths.begin(), include_paths.end(),
2173 FindPredicate(candidate, module, FindPredicate::Any));
2174 if (it != include_paths.end())
2175 header =
std::move(candidate);
2177 if (header.isEmpty()) {
2178 qWarning() <<
"(qdoc) Could not find the module header in include paths for module"
2179 << module <<
" (include paths: " << include_paths <<
")";
2180 qWarning() <<
" Artificial module header built from header dirs in qdocconf "
2183 arguments.push_back(
"-xc++");
2187 QString tmpHeader = pch_directory.path() +
"/" + module;
2188 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2189 QTextStream out(&tmpHeaderFile);
2190 if (header.isEmpty()) {
2191 for (
const auto& [header_path, header_name] : all_headers) {
2192 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2195 if (header_name.endsWith(
"_p.h"_L1))
2196 shouldInclude = shouldInclude && policy.showInternal;
2198 if (shouldInclude) {
2199 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2203 QFileInfo headerFile(header);
2204 if (!headerFile.exists()) {
2205 qWarning() <<
"Could not find module header file" << header;
2206 return std::nullopt;
2209 out <<
"#include \"" << header <<
"\"\n";
2211 if (policy.showInternal) {
2212 for (
const auto& [header_path, header_name] : all_headers) {
2213 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2214 if (header_name.endsWith(
"_p.h"_L1) && shouldInclude)
2215 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2222 clang_parseTranslationUnit2(index, tmpHeader.toLatin1().data(), arguments.data(),
2223 static_cast<
int>(arguments.size()),
nullptr, 0,
2224 flags_ | CXTranslationUnit_ForSerialization, &tu
.tu);
2225 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << tmpHeader << arguments
2226 <<
") returns" << err;
2231 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2232 return std::nullopt;
2235 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2236 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2237 clang_defaultSaveOptions(tu));
2239 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2240 return std::nullopt;
2245 CXCursor cur = clang_getTranslationUnitCursor(tu);
2246 auto &config = Config::instance();
2247 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2249 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2251 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2256 if (t.count(QChar(
'.')) > 1)
2257 t.truncate(t.lastIndexOf(QChar(
'.')));
2262
2263
2264
2265
2266
2267
2268
2269
2272 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2273 | CXTranslationUnit_SkipFunctionBodies
2274 | CXTranslationUnit_KeepGoing);
2278 getDefaultArgs(m_defines, m_args);
2279 if (m_pch && !filePath.endsWith(
".mm")
2280 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2281 m_args.push_back(
"-w");
2282 m_args.push_back(
"-include-pch");
2283 m_args.push_back((*m_pch).get().name.constData());
2285 getMoreArgs(m_includePaths, m_allHeaders, m_args);
2289 clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), m_args.data(),
2290 static_cast<
int>(m_args.size()),
nullptr, 0,
flags_, &tu
.tu);
2291 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << filePath << m_args
2292 <<
") returns" << err;
2296 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2300 ParsedCppFileIR parse_result{};
2302 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2303 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2307 unsigned int numTokens = 0;
2308 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2309 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2311 for (
unsigned int i = 0; i < numTokens; ++i) {
2312 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2314 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2315 if (!comment.startsWith(
"/*!"))
2318 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2321 Doc::trimCStyleComment(loc, comment);
2324 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2330 if (i + 1 < numTokens) {
2332 CXSourceLocation nextCommentLoc = commentLoc;
2333 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2335 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2342 bool future =
false;
2344 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2345 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2346 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
2351 QStringLiteral(
"Cannot tie this documentation to anything"),
2352 QStringLiteral(
"qdoc found a /*! ... */ comment, but there was no "
2353 "topic command (e.g., '\\%1', '\\%2') in the "
2354 "comment and qdoc could not associate the "
2355 "declaration or definition following the "
2356 "comment with a documented entity.")
2363 CXCursor cur = clang_getCursor(tu, commentLoc);
2365 CXCursorKind kind = clang_getCursorKind(cur);
2366 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2368 if (kind == CXCursor_Namespace) {
2369 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2371 cur = clang_getCursorLexicalParent(cur);
2376 clang_disposeTokens(tu, tokens, numTokens);
2377 m_namespaceScope.clear();
2380 return parse_result;
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2396 const QString &idTag, QStringList context)
2398 Node *fnNode =
nullptr;
2400
2401
2402
2403
2404
2405 if (!idTag.isEmpty()) {
2406 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2409 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2412
2413
2414
2415
2416 auto *fn =
static_cast<FunctionNode *>(fnNode);
2417 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split(
'(');
2418 if (leftParenSplit.size() > 1) {
2419 QStringList rightParenSplit = leftParenSplit[1].split(
')');
2420 if (!rightParenSplit.empty()) {
2421 QString params = rightParenSplit[0];
2422 if (!params.isEmpty()) {
2423 QStringList commaSplit = params.split(
',');
2425 if (parameters
.count() == commaSplit.size()) {
2426 for (
int i = 0; i < parameters
.count(); ++i) {
2427 QStringList blankSplit = commaSplit[i].split(
' ', Qt::SkipEmptyParts);
2428 if (blankSplit.size() > 1) {
2429 QString pName = blankSplit.last();
2431 auto it =
std::find_if(
std::begin(pName),
std::end(pName),
2432 [](
const QChar &c) {
return c.isLetter(); });
2433 parameters
[i
].setName(
2434 pName.remove(0,
std::distance(
std::begin(pName), it)));
2444 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2445 | CXTranslationUnit_SkipFunctionBodies
2446 | CXTranslationUnit_KeepGoing);
2450 getDefaultArgs(m_defines, m_args);
2453 m_args.push_back(
"-w");
2454 m_args.push_back(
"-include-pch");
2455 m_args.push_back((*m_pch).get().name.constData());
2460 for (
const auto &ns : std::as_const(context))
2461 s_fn.prepend(
"namespace " + ns.toUtf8() +
" {");
2462 s_fn += fnSignature.toUtf8();
2463 if (!s_fn.endsWith(
";"))
2465 s_fn.append(context.size(),
'}');
2468 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2469 static_cast<
unsigned long>(s_fn.size()) };
2470 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, m_args.data(),
2471 int(m_args.size()), &unsavedFile, 1, flags, &tu
.tu);
2472 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << dummyFileName << m_args
2473 <<
") returns" << err;
2476 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2480
2481
2482
2483
2484
2485 CXCursor cur = clang_getTranslationUnitCursor(tu);
2486 auto &config = Config::instance();
2487 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2488 bool ignoreSignature =
false;
2492 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2493 const auto &config = Config::instance();
2494 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2495 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 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 QList< QByteArray > includePathsFromHeaders(const std::set< Config::HeaderFilePath > &allHeaders)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
void getMoreArgs(const std::vector< QByteArray > &include_paths, const std::set< Config::HeaderFilePath > &all_headers, std::vector< const char * > &args)
Load the include paths into moreArgs.
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< const char * > &args)
Load the default arguments and the defines into args.
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::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)
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)
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()