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
723 return Access::Private;
724 case CX_CXXProtected:
725 return Access::Protected;
727 return Access::Public;
729 return Access::Public;
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;
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
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1336 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
1338 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1339 CXSourceRange range = clang_getCursorExtent(child);
1340 QString sourceText = getSpelling(range);
1341 static const QRegularExpression qmlClassInfoPattern(
1342 R"(Q_CLASSINFO\s*\(\s*["\']QML\.(Singleton|Creatable)["\']\s*,\s*["\'](true|false)["\']\s*\))");
1343 const auto match = qmlClassInfoPattern.match(sourceText);
1344 if (match.hasMatch()) {
1345 if (match.captured(1) ==
"Singleton"_L1 && match.captured(2) ==
"true"_L1) {
1347 return CXChildVisit_Break;
1348 }
else if (match.captured(1) ==
"Creatable"_L1 && match.captured(2) ==
"false"_L1) {
1350 return CXChildVisit_Break;
1356 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1357 QString spelling = fromCXString(clang_getCursorSpelling(child));
1358 if (spelling ==
"QmlIsSingleton"_L1) {
1360 return CXChildVisit_Break;
1361 }
else if (spelling ==
"QmlIsUncreatable"_L1) {
1363 return CXChildVisit_Break;
1367 return CXChildVisit_Continue;
1374
1375
1376
1377CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1379 auto kind = clang_getCursorKind(cursor);
1380 if (clang_isDeclaration(kind)) {
1382 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1383 declMap_.insert(l, cursor);
1384 return CXChildVisit_Recurse;
1386 return CXChildVisit_Continue;
1390
1391
1392
1393
1396 CXCursor sp = clang_getCursorSemanticParent(cursor);
1397 CXCursor lp = clang_getCursorLexicalParent(cursor);
1398 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1401 return static_cast<Aggregate *>(spn);
1407CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1408 bool &ignoreSignature)
1410 switch (clang_getCursorKind(cursor)) {
1411 case CXCursor_Namespace:
1412 return CXChildVisit_Recurse;
1413 case CXCursor_FunctionDecl:
1414 case CXCursor_FunctionTemplate:
1415 case CXCursor_CXXMethod:
1416 case CXCursor_Constructor:
1417 case CXCursor_Destructor:
1418 case CXCursor_ConversionFunction: {
1419 ignoreSignature =
false;
1420 if (ignoredSymbol(functionName(cursor))) {
1422 ignoreSignature =
true;
1427 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1428 readParameterNamesAndAttributes(fn, cursor);
1432 if (
const auto function_declaration = declaration->getAsFunction()) {
1433 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1434 if (llvm::dyn_cast_if_present<
clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1435 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1439 QString name = functionName(cursor);
1440 if (ignoredSymbol(name))
1441 return CXChildVisit_Continue;
1442 Aggregate *semanticParent = getSemanticParent(cursor);
1443 if (semanticParent && semanticParent
->isClass()) {
1445 processFunction(candidate, cursor);
1446 if (!candidate->isSpecialMemberFunction()) {
1448 return CXChildVisit_Continue;
1450 candidate->setImplicitlyGenerated(
true);
1460 return CXChildVisit_Continue;
1463CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1465 auto kind = clang_getCursorKind(cursor);
1468 case CXCursor_TypeAliasTemplateDecl:
1469 case CXCursor_TypeAliasDecl: {
1470 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1471 QString aliasedType;
1473 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1477 if (kind == CXCursor_TypeAliasTemplateDecl) {
1479 if (
const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1480 if (
const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1481 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1482 aliasedType = QString::fromStdString(underlyingType.getAsString());
1487 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1488 if (aliasedCXType.kind != CXType_Invalid) {
1489 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1493 if (!aliasedType.isEmpty()) {
1494 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1499 ta->setTemplateDecl(get_template_declaration(templateDecl));
1501 return CXChildVisit_Continue;
1503 case CXCursor_StructDecl:
1504 case CXCursor_UnionDecl:
1505 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1506 return CXChildVisit_Continue;
1508 case CXCursor_ClassTemplate:
1510 case CXCursor_ClassDecl: {
1511 if (!clang_isCursorDefinition(cursor))
1512 return CXChildVisit_Continue;
1515 return CXChildVisit_Continue;
1517 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1519 Aggregate *semanticParent = getSemanticParent(cursor);
1520 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1521 return CXChildVisit_Continue;
1524 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1525 clang_getTemplateCursorKind(cursor) : kind;
1528 if (actualKind == CXCursor_StructDecl)
1530 else if (actualKind == CXCursor_UnionDecl)
1533 auto *classe =
new ClassNode(type, semanticParent, className);
1537 classe->setLocation(location);
1539 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1540 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1541 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1545 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1546 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1548 if (kind == CXCursor_ClassTemplate) {
1550 classe->setTemplateDecl(get_template_declaration(template_declaration));
1553 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1556 case CXCursor_CXXBaseSpecifier: {
1558 return CXChildVisit_Continue;
1560 auto type = clang_getCursorType(cursor);
1561 auto baseCursor = clang_getTypeDeclaration(type);
1563 auto classe =
static_cast<ClassNode *>(parent_);
1565 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1566 classe->addUnresolvedBaseClass(access,
1567 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1568 return CXChildVisit_Continue;
1570 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1571 classe->addResolvedBaseClass(access, baseClasse);
1572 return CXChildVisit_Continue;
1574 case CXCursor_Namespace: {
1575 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1585 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1588 case CXCursor_FunctionTemplate:
1590 case CXCursor_FunctionDecl:
1591 case CXCursor_CXXMethod:
1592 case CXCursor_Constructor:
1593 case CXCursor_Destructor:
1594 case CXCursor_ConversionFunction: {
1596 return CXChildVisit_Continue;
1597 QString name = functionName(cursor);
1598 if (ignoredSymbol(name))
1599 return CXChildVisit_Continue;
1602 return CXChildVisit_Continue;
1605 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1606 if (!clang_Range_isNull(range)) {
1607 QString comment = getSpelling(range);
1608 if (comment.startsWith(
"//!")) {
1609 qsizetype tag = comment.indexOf(QChar(
'['));
1611 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1613 fn->setTag(comment.mid(tag, end - tag));
1618 processFunction(fn, cursor);
1620 if (kind == CXCursor_FunctionTemplate) {
1622 fn->setTemplateDecl(get_template_declaration(template_declaration));
1625 if (!clang_Location_isInSystemHeader(loc))
1626 autoGenerateSmfDoc(fn, parent_->name());
1628 return CXChildVisit_Continue;
1630#if CINDEX_VERSION
>= 36
1631 case CXCursor_FriendDecl: {
1635 case CXCursor_EnumDecl: {
1636 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1637 if (en && en->items().size())
1638 return CXChildVisit_Continue;
1640 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1642 if (clang_Cursor_isAnonymous(cursor)) {
1643 enumTypeName =
"anonymous";
1649 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1651 en =
static_cast<EnumNode *>(n);
1655 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1658 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1662 visitChildrenLambda(cursor, [&](CXCursor cur) {
1663 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1664 return CXChildVisit_Continue;
1667 visitChildrenLambda(cur, [&](CXCursor cur) {
1668 if (clang_isExpression(clang_getCursorKind(cur))) {
1669 value = getSpelling(clang_getCursorExtent(cur));
1670 return CXChildVisit_Break;
1672 return CXChildVisit_Continue;
1674 if (value.isEmpty()) {
1675 QLatin1String hex(
"0x");
1676 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1677 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1679 value = QString::number(clang_getEnumConstantDeclValue(cur));
1683 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1684 return CXChildVisit_Continue;
1686 return CXChildVisit_Continue;
1688 case CXCursor_FieldDecl:
1689 case CXCursor_VarDecl: {
1691 return CXChildVisit_Continue;
1693 auto value_declaration =
1695 assert(value_declaration);
1698 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1700 var->setAccess(access);
1702 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1703 value_declaration->getType(),
1704 value_declaration->getASTContext()
1706 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1708 return CXChildVisit_Continue;
1710 case CXCursor_TypedefDecl: {
1712 return CXChildVisit_Continue;
1713 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1717 visitChildrenLambda(cursor, [&](CXCursor cur) {
1718 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1719 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1720 return CXChildVisit_Continue;
1722 visitChildrenLambda(cursor, [&](CXCursor cur) {
1723 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1724 return CXChildVisit_Continue;
1727 if (en && en->isEnumType())
1728 static_cast<EnumNode *>(en)->setFlagsType(td);
1729 return CXChildVisit_Break;
1731 return CXChildVisit_Break;
1733 return CXChildVisit_Continue;
1739 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1742 return CXChildVisit_Continue;
1751 visitChildrenLambda(cursor, [&](CXCursor cur) {
1752 auto kind = clang_getCursorKind(cur);
1753 if (kind == CXCursor_AnnotateAttr) {
1754 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1755 if (annotation == QLatin1String(
"qt_slot")) {
1757 }
else if (annotation == QLatin1String(
"qt_signal")) {
1760 if (annotation == QLatin1String(
"qt_invokable"))
1762 }
else if (kind == CXCursor_CXXOverrideAttr) {
1764 }
else if (kind == CXCursor_ParmDecl) {
1766 return CXChildVisit_Break;
1768 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1769 parameters
[i
].setName(name);
1772 Q_ASSERT(parameter_declaration);
1776 if (!default_value.empty())
1777 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1781 return CXChildVisit_Continue;
1787 CXCursorKind kind = clang_getCursorKind(cursor);
1788 CXType funcType = clang_getCursorType(cursor);
1795 : clang_CXXMethod_isPureVirtual(cursor)
1812 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1814 if (kind == CXCursor_Constructor
1816 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1818 else if (kind == CXCursor_Destructor)
1820 else if (kind != CXCursor_ConversionFunction)
1821 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1822 function_declaration->getReturnType(),
1823 function_declaration->getASTContext()
1826 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1831 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1837 (constructor_declaration && constructor_declaration->isExplicit()) ||
1838 (conversion_declaration && conversion_declaration->isExplicit())
1841 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1846 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1847 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1849 if (function_prototype) {
1850 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1852 if (exception_specification.Type !=
clang::ExceptionSpecificationType::EST_None) {
1853 const std::string exception_specification_spelling =
1854 exception_specification.NoexceptExpr ? get_expression_as_string(
1855 exception_specification.NoexceptExpr,
1856 function_declaration->getASTContext()
1859 if (exception_specification_spelling !=
"false")
1860 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1867#if LIBCLANG_VERSION_MAJOR >= 21
1868 if (
const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1869 trailing_requires.ConstraintExpr) {
1870 QString requires_str = QString::fromStdString(
1871 get_expression_as_string(trailing_requires.ConstraintExpr,
1872 function_declaration->getASTContext()));
1873 fn->setTrailingRequiresClause(requires_str.simplified());
1876 if (
const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1877 QString requires_str = QString::fromStdString(
1878 get_expression_as_string(trailing_requires,
1879 function_declaration->getASTContext()));
1880 fn->setTrailingRequiresClause(requires_str.simplified());
1884 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1885 if (refQualKind == CXRefQualifier_LValue)
1887 else if (refQualKind == CXRefQualifier_RValue)
1896 parameters
.reserve(function_declaration->getNumParams()
);
1898 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1899 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1901 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1903 parameter_declaration->getASTContext()
1906 if (!parameter_type.isCanonical())
1907 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1908 parameter_type.getCanonicalType(),
1909 parameter_declaration->getASTContext()
1914 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
1920 if (clang_isFunctionTypeVariadic(funcType))
1921 parameters.append(QStringLiteral(
"..."));
1922 readParameterNamesAndAttributes(fn, cursor);
1924 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1926 Q_ASSERT(function_declaration);
1928 const bool hasNamespaceScopeRedeclaration =
1929 std::any_of(function_declaration->redecls_begin(),
1930 function_declaration->redecls_end(),
1931 [](
const clang::FunctionDecl *r) {
1932 return r->getFriendObjectKind() == clang::Decl::FOK_None;
1934 if (!hasNamespaceScopeRedeclaration)
1941 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
1942 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
1943 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
1946 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
1947 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
1948 if (lpIdx <= 0 || rpIdx <= lpIdx)
1951 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1952 signature = signature.simplified();
1953 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
1955 static const QStringList attrs =
1956 QStringList() <<
"READ" <<
"MEMBER" <<
"WRITE"
1957 <<
"NOTIFY" <<
"CONSTANT" <<
"FINAL"
1958 <<
"REQUIRED" <<
"BINDABLE" <<
"DESIGNABLE"
1959 <<
"RESET" <<
"REVISION" <<
"SCRIPTABLE"
1960 <<
"STORED" <<
"USER";
1964 auto it =
std::find_if(parts.cbegin(), parts.cend(),
1965 [](
const QString &attr) ->
bool {
1966 return attrs.contains(attr);
1969 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
1972 QStringList typeParts;
1973 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
1974 parts.erase(parts.cbegin(), it);
1975 QString name = typeParts.takeLast();
1978 while (!name.isEmpty() && name.front() == QChar(
'*')) {
1979 typeParts.last().push_back(name.front());
1984 if (parts.size() < 2 || name.isEmpty())
1988 property->setAccess(Access::Public);
1989 property->setLocation(loc);
1990 property->setDataType(typeParts.join(QChar(
' ')));
1993 while (i < parts.size()) {
1994 const QString &key = parts.at(i++);
1996 if (key ==
"CONSTANT") {
1997 property->setConstant();
1998 }
else if (key ==
"REQUIRED") {
1999 property->setRequired();
2001 if (i < parts.size()) {
2002 QString value = parts.at(i++);
2003 if (key ==
"READ") {
2005 }
else if (key ==
"WRITE") {
2007 property->setWritable(
true);
2008 }
else if (key ==
"MEMBER") {
2009 property->setWritable(
true);
2010 }
else if (key ==
"STORED") {
2011 property->setStored(value.toLower() ==
"true");
2012 }
else if (key ==
"BINDABLE") {
2015 }
else if (key ==
"RESET") {
2017 }
else if (key ==
"NOTIFY") {
2026
2027
2028
2029
2030
2034 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
2035 auto decl_it = declMap_.upperBound(docloc);
2036 if (decl_it == declMap_.end())
2039 unsigned int declLine = decl_it.key().line;
2040 unsigned int nextCommentLine;
2041 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
2042 if (nextCommentLine < declLine)
2046 if (decl_it != declMap_.begin()) {
2047 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(
std::prev(decl_it))));
2048 unsigned int prevDeclLine;
2049 clang_getPresumedLocation(prevDeclEnd,
nullptr, &prevDeclLine,
nullptr);
2050 if (prevDeclLine >= docloc.line) {
2053 auto parent = clang_getCursorLexicalParent(*decl_it);
2054 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2058 auto *node = findNodeForCursor(qdb_, *decl_it);
2060 if (node && node->isFunction(Genus::CPP))
2061 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2068 const std::vector<QByteArray>& include_paths,
2069 const QList<QByteArray>& defines,
2076 m_allHeaders = config.getHeaderFiles();
2077 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2085 "-fms-compatibility-version=19",
2089 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2090 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2091 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2092 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2093 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2094 "-Wno-constant-logical-operand",
2095 "-Wno-macro-redefined",
2096 "-Wno-nullability-completeness",
2097 "-fvisibility=default",
2104 std::vector<
const char *> pointers;
2105 pointers.reserve(args.size());
2106 for (
const auto &arg : args)
2107 pointers.push_back(arg.constData());
2112
2113
2114
2119 args.emplace_back(arg);
2122 for (
const auto &p : std::as_const(defines))
2127
2128
2130 const std::vector<QByteArray>& include_paths,
2131 std::vector<QByteArray>& args
2133 if (include_paths.empty()) {
2134 qCWarning(lcQdoc) <<
"No include paths provided."
2135 <<
"Set 'includepaths' in the qdocconf file"
2136 <<
"or pass -I flags on the command line."
2137 <<
"C++ parsing may produce incomplete results.";
2139 args.insert(args.end(), include_paths.begin(), include_paths.end());
2144
2145
2146
2147
2150 QString module_header,
2151 const std::set<Config::HeaderFilePath>& all_headers,
2152 const std::vector<QByteArray>& include_paths,
2153 const QList<QByteArray>& defines,
2154 const InclusionPolicy& policy
2156 static std::vector<QByteArray> arguments{};
2158 if (module_header.isEmpty())
return std::nullopt;
2160 getDefaultArgs(defines, arguments);
2161 getMoreArgs(include_paths, arguments);
2163 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2164 | CXTranslationUnit_SkipFunctionBodies
2165 | CXTranslationUnit_KeepGoing);
2169 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2170 if (!pch_directory.isValid())
return std::nullopt;
2172 const QByteArray module = module_header.toUtf8();
2175 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2178 struct FindPredicate
2180 enum SearchType { Any, Module };
2181 QByteArray &candidate_;
2182 const QByteArray &module_;
2184 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2185 SearchType type = Any)
2186 : candidate_(candidate), module_(module), type_(type)
2190 bool operator()(
const QByteArray &p)
const
2192 if (type_ != Any && !p.endsWith(module_))
2194 candidate_ = p +
"/";
2195 candidate_.append(module_);
2196 if (p.startsWith(
"-I"))
2197 candidate_ = candidate_.mid(2);
2198 return QFile::exists(QString::fromUtf8(candidate_));
2203 QByteArray candidate;
2204 auto it =
std::find_if(include_paths.begin(), include_paths.end(),
2205 FindPredicate(candidate, module, FindPredicate::Module));
2206 if (it == include_paths.end())
2207 it =
std::find_if(include_paths.begin(), include_paths.end(),
2208 FindPredicate(candidate, module, FindPredicate::Any));
2209 if (it != include_paths.end())
2210 header =
std::move(candidate);
2212 if (header.isEmpty()) {
2213 qWarning() <<
"(qdoc) Could not find the module header in include paths for module"
2214 << module <<
" (include paths: " << include_paths <<
")";
2215 qWarning() <<
" Artificial module header built from header dirs in qdocconf "
2218 arguments.push_back(
"-xc++");
2222 QString tmpHeader = pch_directory.path() +
"/" + module;
2223 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2224 QTextStream out(&tmpHeaderFile);
2225 if (header.isEmpty()) {
2226 for (
const auto& [header_path, header_name] : all_headers) {
2227 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2230 if (header_name.endsWith(
"_p.h"_L1))
2231 shouldInclude = shouldInclude && policy.showInternal;
2233 if (shouldInclude) {
2234 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2238 QFileInfo headerFile(header);
2239 if (!headerFile.exists()) {
2240 qWarning() <<
"Could not find module header file" << header;
2241 return std::nullopt;
2244 out <<
"#include \"" << header <<
"\"\n";
2246 if (policy.showInternal) {
2247 for (
const auto& [header_path, header_name] : all_headers) {
2248 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2249 if (header_name.endsWith(
"_p.h"_L1) && shouldInclude)
2250 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2256 const auto argPointers = toConstCharPointers(arguments);
2257 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2259 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2260 static_cast<
int>(argPointers.size()),
nullptr, 0,
2261 flags_ | CXTranslationUnit_ForSerialization, &tu
.tu);
2262 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << tmpHeader << arguments
2263 <<
") returns" << err;
2268 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2269 return std::nullopt;
2272 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2273 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2274 clang_defaultSaveOptions(tu));
2276 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2277 return std::nullopt;
2282 CXCursor cur = clang_getTranslationUnitCursor(tu);
2283 auto &config = Config::instance();
2284 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2286 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2288 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2293 if (t.count(QChar(
'.')) > 1)
2294 t.truncate(t.lastIndexOf(QChar(
'.')));
2299
2300
2301
2302
2303
2304
2305
2306
2309 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2310 | CXTranslationUnit_SkipFunctionBodies
2311 | CXTranslationUnit_KeepGoing);
2315 getDefaultArgs(m_defines, m_args);
2316 if (m_pch && !filePath.endsWith(
".mm")
2317 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2318 m_args.push_back(
"-w");
2319 m_args.push_back(
"-include-pch");
2320 m_args.push_back((*m_pch).get().name);
2322 getMoreArgs(m_includePaths, m_args);
2325 const auto argPointers = toConstCharPointers(m_args);
2326 const QByteArray filePathLocal = filePath.toLocal8Bit();
2328 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2329 static_cast<
int>(argPointers.size()),
nullptr, 0,
flags_, &tu
.tu);
2330 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << filePath << m_args
2331 <<
") returns" << err;
2335 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2339 ParsedCppFileIR parse_result{};
2341 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2342 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2346 unsigned int numTokens = 0;
2347 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2348 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2350 for (
unsigned int i = 0; i < numTokens; ++i) {
2351 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2353 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2354 if (!comment.startsWith(
"/*!"))
2357 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2360 Doc::trimCStyleComment(loc, comment);
2363 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2369 if (i + 1 < numTokens) {
2371 CXSourceLocation nextCommentLoc = commentLoc;
2372 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2374 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2381 bool future =
false;
2383 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2384 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2385 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
2390 QStringLiteral(
"Cannot tie this documentation to anything"),
2391 QStringLiteral(
"qdoc found a /*! ... */ comment, but there was no "
2392 "topic command (e.g., '\\%1', '\\%2') in the "
2393 "comment and qdoc could not associate the "
2394 "declaration or definition following the "
2395 "comment with a documented entity.")
2402 CXCursor cur = clang_getCursor(tu, commentLoc);
2404 CXCursorKind kind = clang_getCursorKind(cur);
2405 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2407 if (kind == CXCursor_Namespace) {
2408 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2410 cur = clang_getCursorLexicalParent(cur);
2415 clang_disposeTokens(tu, tokens, numTokens);
2416 m_namespaceScope.clear();
2419 return parse_result;
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2435 const QString &idTag, QStringList context)
2437 Node *fnNode =
nullptr;
2439
2440
2441
2442
2443
2444 if (!idTag.isEmpty()) {
2445 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2448 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2451
2452
2453
2454
2455 auto *fn =
static_cast<FunctionNode *>(fnNode);
2456 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split(
'(');
2457 if (leftParenSplit.size() > 1) {
2458 QStringList rightParenSplit = leftParenSplit[1].split(
')');
2459 if (!rightParenSplit.empty()) {
2460 QString params = rightParenSplit[0];
2461 if (!params.isEmpty()) {
2462 QStringList commaSplit = params.split(
',');
2464 if (parameters
.count() == commaSplit.size()) {
2465 for (
int i = 0; i < parameters
.count(); ++i) {
2466 QStringList blankSplit = commaSplit[i].split(
' ', Qt::SkipEmptyParts);
2467 if (blankSplit.size() > 1) {
2468 QString pName = blankSplit.last();
2470 auto it =
std::find_if(
std::begin(pName),
std::end(pName),
2471 [](
const QChar &c) {
return c.isLetter(); });
2472 parameters
[i
].setName(
2473 pName.remove(0,
std::distance(
std::begin(pName), it)));
2483 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2484 | CXTranslationUnit_SkipFunctionBodies
2485 | CXTranslationUnit_KeepGoing);
2489 getDefaultArgs(m_defines, m_args);
2492 m_args.push_back(
"-w");
2493 m_args.push_back(
"-include-pch");
2494 m_args.push_back((*m_pch).get().name);
2499 for (
const auto &ns : std::as_const(context))
2500 s_fn.prepend(
"namespace " + ns.toUtf8() +
" {");
2501 s_fn += fnSignature.toUtf8();
2502 if (!s_fn.endsWith(
";"))
2504 s_fn.append(context.size(),
'}');
2507 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2508 static_cast<
unsigned long>(s_fn.size()) };
2509 const auto argPointers = toConstCharPointers(m_args);
2510 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2511 int(argPointers.size()), &unsavedFile, 1, flags, &tu
.tu);
2512 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << dummyFileName << m_args
2513 <<
") returns" << err;
2516 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2520
2521
2522
2523
2524
2525 CXCursor cur = clang_getTranslationUnitCursor(tu);
2526 auto &config = Config::instance();
2527 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2528 bool ignoreSignature =
false;
2532 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2533 const auto &config = Config::instance();
2534 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2535 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.
QmlNativeTypeAttribute
Defines QML-specific attributes affecting QmlTypeNode instances.
Metaness
Specifies the kind of function a FunctionNode represents.
This namespace holds QDoc-internal utility methods.
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
Returns the spelling in the file for a source range.
std::variant< Node *, FnMatchError > operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
Use clang to parse the function signature from a function command.
Encapsulates information about.
The Node class is the base class for all the nodes in QDoc's parse tree.
void setAccess(Access t)
Sets the node's access type to t.
bool isNamespace() const
Returns true if the node type is Namespace.
bool isTypedef() const
Returns true if the node type is Typedef.
bool isVariable() const
Returns true if the node type is Variable.
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
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()