23#include <QtCore/qdebug.h>
24#include <QtCore/qdir.h>
25#include <QtCore/qelapsedtimer.h>
26#include <QtCore/qfile.h>
27#include <QtCore/qregularexpression.h>
28#include <QtCore/qscopedvaluerollback.h>
29#include <QtCore/qtemporarydir.h>
30#include <QtCore/qtextstream.h>
31#include <QtCore/qvarlengtharray.h>
33#include <clang-c/Index.h>
35#include <clang/AST/ASTConcept.h>
36#include <clang/AST/Decl.h>
37#include <clang/AST/DeclFriend.h>
38#include <clang/AST/DeclTemplate.h>
39#include <clang/AST/Expr.h>
40#include <clang/AST/ExprConcepts.h>
41#include <clang/AST/Type.h>
42#include <clang/AST/TypeLoc.h>
43#include <clang/Basic/SourceLocation.h>
44#include <clang/Frontend/ASTUnit.h>
45#include <clang/Lex/Lexer.h>
46#include <llvm/Support/Casting.h>
48#include "clang/AST/QualTypeNames.h"
58using namespace Qt::Literals::StringLiterals;
68 clang_disposeIndex(
index);
73 CXTranslationUnit
tu =
nullptr;
84 clang_disposeTranslationUnit(
tu);
92static CXTranslationUnit_Flags
flags_ =
static_cast<CXTranslationUnit_Flags>(0);
96#ifndef QT_NO_DEBUG_STREAM
100 QDebugStateSaver saver(debug);
103 const size_t size = v.size();
104 debug <<
"std::vector<>[" << size <<
"](";
105 for (size_t i = 0; i < size; ++i) {
117 if (!lcQdocClang().isDebugEnabled())
120 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
121 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
122 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
124 for (
unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
125 auto diagnostic = clang_getDiagnostic(translationUnit, i);
126 auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
127 qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
128 clang_disposeString(formattedDiagnostic);
129 clang_disposeDiagnostic(diagnostic);
134
135
136
137
138
139
140
141
142
143
144
145
147 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
149 return static_cast<
const clang::Decl*>(cursor.data[0]);
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
203 const clang::RecordType *rt = type->getAs<
clang::RecordType>();
208 std::vector<std::string> keywords;
209 const clang::RecordDecl *decl = rt->getDecl();
211 if (decl->getDeclName().isEmpty())
212 keywords.emplace_back(decl->getKindName());
213 const auto *parent = llvm::dyn_cast<
clang::RecordDecl>(decl->getDeclContext());
218 std::reverse(keywords.begin(), keywords.end());
224 static constexpr std::string_view prefixes[] = {
"(unnamed",
"(anonymous" };
225 size_t keywordIndex = 0;
227 while (pos < typeName.size() && keywordIndex < keywords.size()) {
228 std::string_view foundPrefix;
229 size_t foundPos = std::string::npos;
230 for (
auto prefix : prefixes) {
231 size_t p = typeName.find(prefix, pos);
234 foundPrefix = prefix;
237 if (foundPos == std::string::npos)
240 size_t afterPrefix = foundPos + foundPrefix.size();
241 if (afterPrefix < typeName.size() && typeName[afterPrefix] ==
')') {
243 typeName.insert(afterPrefix,
" " + keywords[keywordIndex]);
244 pos = afterPrefix + 1 + keywords[keywordIndex].size() + 1;
247 size_t closePos = typeName.find(
')', afterPrefix);
248 pos = (closePos != std::string::npos) ? closePos + 1 : afterPrefix;
256 auto policy = declaration_context.getPrintingPolicy();
257 policy.AnonymousTagLocations =
false;
259 return ensureAnonymousTagKeyword(
std::move(result), type);
263
264
265
266
267
268
269
270
271
273 if (!typeName.contains(
"(unnamed "_L1) && !typeName.contains(
"(anonymous "_L1))
276 static const QRegularExpression pattern(
277 R"(\((unnamed|anonymous)(\s+\w+)\s+at\s+[^)]+\))"
279 QString cleaned = typeName;
280 cleaned.replace(pattern,
"(\\1\\2)"_L1);
285
286
287
288
289
290
291
292
293
295 QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
296 clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
297 declaration_context.getSourceManager(),
298 declaration_context.getLangOpts()
301 if (default_value.startsWith(
"="))
302 default_value.remove(0, 1);
304 default_value = default_value.trimmed();
306 return default_value.toStdString();
310
311
312
313
314
315
316
317
319 std::vector<std::string> &out)
323 if (
const auto *cse = llvm::dyn_cast<clang::ConceptSpecializationExpr>(node)) {
324 if (
const auto *concept_decl = cse->getNamedConcept())
325 out.push_back(concept_decl->getQualifiedNameAsString());
327 for (
const clang::Stmt *child : node->children())
328 collect_concept_references(child, out);
332
333
334
335
336
337
338
339
341#if LIBCLANG_VERSION_MAJOR >= 19
342 return (parameter && parameter->hasDefaultArgument()) ?
343 get_fully_qualified_type_name(parameter->getDefaultArgument().getArgument().getAsType(), parameter->getASTContext()) :
346 return (parameter && parameter->hasDefaultArgument()) ?
347 get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
354
355
356
357
358
359
360
361
363#if LIBCLANG_VERSION_MAJOR >= 19
364 return (parameter && parameter->hasDefaultArgument()) ?
365 get_expression_as_string(parameter->getDefaultArgument().getSourceExpression(), parameter->getASTContext()) :
"";
367 return (parameter && parameter->hasDefaultArgument()) ?
368 get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) :
"";
374
375
376
377
378
379
380
381
383 std::string default_value{};
385 if (parameter && parameter->hasDefaultArgument()) {
386 const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
388 llvm::raw_string_ostream ss{default_value};
389 template_name.print(ss, parameter->getASTContext().getPrintingPolicy(),
clang::TemplateName::Qualified::AsWritten);
392 return default_value;
396
397
398
399
400
401
402
403
404
405
407 if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
410 return get_expression_as_string(
411 parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
412 parameter->getASTContext()
417
418
419
420
421
422
423
425 if (!declaration)
return "";
427 if (
auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
428 return get_default_value_initializer_as_string(type_template_parameter);
430 if (
auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
431 return get_default_value_initializer_as_string(non_type_template_parameter);
433 if (
auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
434 return get_default_value_initializer_as_string(template_template_parameter);
437 if (
auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
438 return get_default_value_initializer_as_string(function_parameter);
445
446
447
448
452 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
453 CXClientData client_data) -> CXChildVisitResult {
454 return (*
static_cast<T *>(client_data))(c);
456 return clang_visitChildren(cursor, visitor, &lambda);
460
461
464 QString ret = QString::fromUtf8(clang_getCString(string));
465 clang_disposeString(string);
470
471
472
473
474
475
476
478 const clang::Type *type)
482 for (
int depth = 0; depth < 10 && type; ++depth) {
483 if (
auto *tst = llvm::dyn_cast<clang::TemplateSpecializationType>(type))
486#if LIBCLANG_VERSION_MAJOR < 22
488 if (
auto *elaborated = llvm::dyn_cast<clang::ElaboratedType>(type)) {
489 type = elaborated->getNamedType().getTypePtr();
502
503
504
507 auto ends_with = [](
const std::string &str,
const std::string &suffix) {
508 return str.size() >= suffix.size()
509 && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
512 return ends_with(qualified_name,
"enable_if_t")
513 || ends_with(qualified_name,
"enable_if");
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
541 const clang::NonTypeTemplateParmDecl *param)
543 if (!param->getName().empty())
546 auto policy = param->getASTContext().getPrintingPolicy();
548 const clang::Type *type = param->getType().getTypePtr();
567 if (!param->hasDefaultArgument())
570 std::string type_name = param->getType().getAsString(policy);
571 if (type_name.find(
'<') != std::string::npos)
577 auto *alias_decl = alias_type->getTemplateName().getAsTemplateDecl();
582 bool found_enable_if =
false;
583 const clang::Type *sugar = alias_type->desugar().getTypePtr();
585 for (
int depth = 0; depth < 10 && sugar; ++depth) {
590 if (
auto *decl = tst->getTemplateName().getAsTemplateDecl()) {
591 if (is_enable_if_name(decl->getQualifiedNameAsString())) {
592 found_enable_if =
true;
597 sugar = tst->desugar().getTypePtr();
600 if (!found_enable_if)
609 param->getType().getAsString(policy)
614
615
616
618 assert(template_declaration);
622 auto template_parameters = template_declaration->getTemplateParameters();
623 for (
auto template_parameter : template_parameters->asArray()) {
624 auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
627 std::optional<SfinaeConstraint> sfinae{};
629 if (
auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
630 kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
631 type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
663 if (QString::fromStdString(type).startsWith(
"typename ")) type.erase(0, std::string(
"typename ").size());
665 sfinae = detect_sfinae_constraint(non_type_template_parameter);
668 auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
669 if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
671 template_declaration_ir.parameters.push_back({
673 template_parameter->isTemplateParameterPack(),
676 template_parameter->getNameAsString(),
677 get_default_value_initializer_as_string(template_parameter)
679 (template_template_parameter ?
680 std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
681 get_template_declaration(template_template_parameter).parameters
697 if (
const auto *type_template_parameter =
698 llvm::dyn_cast<clang::TemplateTypeParmDecl>(template_parameter)) {
699 if (type_template_parameter->hasTypeConstraint()) {
700 if (
const clang::TypeConstraint *constraint =
701 type_template_parameter->getTypeConstraint()) {
702 if (
const clang::NamedDecl *concept_decl =
703 constraint->getNamedConcept()) {
704 template_declaration_ir.referenced_concepts.push_back(
705 concept_decl->getQualifiedNameAsString());
706 template_declaration_ir.parameters.back().concept_name =
707 concept_decl->getQualifiedNameAsString();
715 std::string explicit_requires;
716 if (
const clang::Expr *requires_clause = template_parameters->getRequiresClause()) {
717 explicit_requires = QString::fromStdString(get_expression_as_string(
718 requires_clause, template_declaration->getASTContext())).simplified().toStdString();
719 collect_concept_references(requires_clause,
720 template_declaration_ir.referenced_concepts);
730 std::string synthesized;
731 const auto ¶ms = template_declaration_ir.parameters;
733 for (
const auto ¶m : params) {
734 if (param.sfinae_constraint) {
735 if (!synthesized.empty())
736 synthesized +=
" && ";
737 synthesized += param.sfinae_constraint->alias_with_args;
744 if (!synthesized.empty() && !explicit_requires.empty())
745 template_declaration_ir.requires_clause = synthesized +
" && (" + explicit_requires +
")";
746 else if (!synthesized.empty())
747 template_declaration_ir.requires_clause =
std::move(synthesized);
748 else if (!explicit_requires.empty())
749 template_declaration_ir.requires_clause =
std::move(explicit_requires);
753 auto &refs = template_declaration_ir.referenced_concepts;
754 std::sort(refs.begin(), refs.end());
755 refs.erase(
std::unique(refs.begin(), refs.end()), refs.end());
758 return template_declaration_ir;
762
763
766 unsigned int line, column;
768 clang_getPresumedLocation(location, &file, &line, &column);
776
777
782 return Access::Private;
783 case CX_CXXProtected:
784 return Access::Protected;
786 return Access::Public;
788 return Access::Public;
793
794
803 unsigned int offset1,
unsigned int offset2)
805 return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
808static QString
readFile(CXFile cxFile,
unsigned int offset1,
unsigned int offset2)
810 using FileCache = QList<FileCacheEntry>;
811 static FileCache cache;
813 CXString cxFileName = clang_getFileName(cxFile);
814 const QByteArray fileName = clang_getCString(cxFileName);
815 clang_disposeString(cxFileName);
817 for (
const auto &entry : std::as_const(cache)) {
818 if (fileName == entry.fileName)
819 return fromCache(entry.content, offset1, offset2);
822 QFile file(QString::fromUtf8(fileName));
823 if (file.open(QIODeviceBase::ReadOnly)) {
825 cache.prepend(entry);
826 while (cache.size() > 5)
828 return fromCache(entry.content, offset1, offset2);
835 auto start = clang_getRangeStart(range);
836 auto end = clang_getRangeEnd(range);
838 unsigned int offset1, offset2;
839 clang_getFileLocation(start, &file1,
nullptr,
nullptr, &offset1);
840 clang_getFileLocation(end, &file2,
nullptr,
nullptr, &offset2);
842 if (file1 != file2 || offset2 <= offset1)
845 return readFile(file1, offset1, offset2);
849
850
851
852
855 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
859 auto conversion_declaration =
860 static_cast<
const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
862 return QLatin1String(
"operator ") + QString::fromStdString(get_fully_qualified_type_name(
863 conversion_declaration->getConversionType(),
864 conversion_declaration->getASTContext()
868 QString name = fromCXString(clang_getCursorSpelling(cursor));
871 auto ltLoc = name.indexOf(
'<');
872 if (ltLoc > 0 && !name.startsWith(
"operator<"))
873 name = name.left(ltLoc);
878
879
880
884 auto kind = clang_getCursorKind(cur);
885 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
887 case CXCursor_Namespace:
888 case CXCursor_StructDecl:
889 case CXCursor_ClassDecl:
890 case CXCursor_UnionDecl:
891 case CXCursor_ClassTemplate:
893 path.prepend(fromCXString(clang_getCursorSpelling(cur)));
895 case CXCursor_FunctionDecl:
896 case CXCursor_FunctionTemplate:
897 case CXCursor_CXXMethod:
898 case CXCursor_Constructor:
899 case CXCursor_Destructor:
900 case CXCursor_ConversionFunction:
901 path = functionName(cur);
906 cur = clang_getCursorSemanticParent(cur);
907 kind = clang_getCursorKind(cur);
913
914
915
916
917
918
921 param_type = param_type.getNonReferenceType();
922 while (param_type->isPointerType())
923 param_type = param_type->getPointeeType();
924 param_type = param_type.getUnqualifiedType();
926 if (param_type->isBuiltinType())
929 if (
const auto *record_type = param_type->getAs<clang::RecordType>()) {
930 if (
const auto *record_decl = record_type->getDecl())
931 return QString::fromStdString(record_decl->getQualifiedNameAsString());
936 QString class_name = QString::fromStdString(param_type.getAsString());
937 class_name.remove(
"const "_L1).remove(
"volatile "_L1);
938 class_name.remove(
"class "_L1).remove(
"struct "_L1);
939 class_name = class_name.trimmed();
941 if (class_name.isEmpty() || class_name.contains(
'('_L1) || class_name.contains(
'['_L1))
946 if (
auto angle = class_name.indexOf(
'<'_L1); angle > 0)
947 class_name.truncate(angle);
953
954
955
956
957
958
959
960
961
962
963
967 QSet<ClassNode *> searched_classes;
968 for (
const auto *param : func_decl->parameters()) {
969 auto class_name = classNameFromParameterType(param->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);
978 NodeVector class_candidates;
979 class_node->findChildren(funcName, class_candidates);
981 for (Node *candidate : class_candidates) {
982 if (!candidate->isFunction(Genus::CPP))
984 if (
static_cast<FunctionNode *>(candidate)->isHiddenFriend())
985 candidates.append(candidate);
991
992
993
996 auto kind = clang_getCursorKind(cur);
997 if (clang_isInvalid(kind))
999 if (kind == CXCursor_TranslationUnit)
1007 case CXCursor_TemplateTypeParameter:
1008 case CXCursor_NonTypeTemplateParameter:
1009 case CXCursor_TemplateTemplateParameter:
1019 auto parent =
static_cast<Aggregate *>(p);
1022 if (clang_Cursor_isAnonymous(cur)) {
1025 QLatin1String(
"anonymous"));
1027 name = fromCXString(clang_getCursorSpelling(cur));
1030 case CXCursor_Namespace:
1032 case CXCursor_StructDecl:
1033 case CXCursor_ClassDecl:
1034 case CXCursor_UnionDecl:
1035 case CXCursor_ClassTemplate:
1037 case CXCursor_FunctionDecl:
1038 case CXCursor_FunctionTemplate:
1039 case CXCursor_CXXMethod:
1040 case CXCursor_Constructor:
1041 case CXCursor_Destructor:
1042 case CXCursor_ConversionFunction: {
1044 parent->findChildren(functionName(cur), candidates);
1047 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1049 lexical_parent && lexical_parent
->isAggregate() && lexical_parent != parent) {
1050 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
1059 const bool hasHiddenFriend =
1060 std::any_of(candidates.cbegin(), candidates.cend(), [](
const Node *n) {
1061 return n->isFunction(Genus::CPP)
1062 &&
static_cast<
const FunctionNode *>(n)->isHiddenFriend();
1064 if (!hasHiddenFriend) {
1065 auto *func_decl = cur_decl ? cur_decl->getAsFunction() :
nullptr;
1067 findHiddenFriendCandidates(qdb, functionName(cur), func_decl, candidates);
1070 if (candidates.isEmpty())
1073 CXType funcType = clang_getCursorType(cur);
1074 auto numArg = clang_getNumArgTypes(funcType);
1075 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
1076 QVarLengthArray<QString, 20> args;
1079 if (kind == CXCursor_FunctionTemplate)
1080 relaxed_template_declaration = get_template_declaration(
1084 for (Node *candidate : std::as_const(candidates)) {
1085 if (!candidate->isFunction(Genus::CPP))
1088 auto fn =
static_cast<FunctionNode *>(candidate);
1090 if (!fn->templateDecl() && relaxed_template_declaration)
1093 if (fn->templateDecl() && !relaxed_template_declaration)
1096 if (fn->templateDecl() && relaxed_template_declaration &&
1097 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
1100 const Parameters ¶meters = fn->parameters();
1102 if (parameters.count() != numArg + isVariadic) {
1104 if (numArg > 0 && parameters.isPrivateSignal() &&
1105 (parameters.isEmpty() || !parameters.last().type().endsWith(
1106 QLatin1String(
"QPrivateSignal")))) {
1107 if (parameters.count() != --numArg + isVariadic)
1114 if (fn->isConst() !=
bool(clang_CXXMethod_isConst(cur)))
1117 if (isVariadic && parameters.last().type() != QLatin1String(
"..."))
1120 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
1123 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
1126 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
1128 bool typesDiffer =
false;
1129 for (
int i = 0; i < numArg; ++i) {
1130 auto *paramDecl = function_declaration->getParamDecl(i);
1131 auto paramType = paramDecl->getOriginalType();
1133 if (args.size() <= i)
1134 args.append(QString::fromStdString(get_fully_qualified_type_name(
1135 paramType, function_declaration->getASTContext()
1138 QString recordedType = parameters.at(i).type();
1139 QString typeSpelling = args.at(i);
1141 typesDiffer = recordedType != typeSpelling;
1151 const bool isBareTemplateTypeParm =
1152 paramType.getTypePtrOrNull()
1153 && llvm::isa<clang::TemplateTypeParmType>(paramType.getTypePtr());
1154 if (!isBareTemplateTypeParm) {
1155 QStringView canonicalType = parameters.at(i).canonicalType();
1156 if (!canonicalType.isEmpty()) {
1157 typesDiffer = canonicalType !=
1158 QString::fromStdString(get_fully_qualified_type_name(
1159 paramType.getCanonicalType(),
1160 function_declaration->getASTContext()
1176 case CXCursor_EnumDecl:
1177 return parent->findNonfunctionChild(name, &
Node::isEnumType);
1178 case CXCursor_FieldDecl:
1179 case CXCursor_VarDecl:
1181 case CXCursor_TypedefDecl:
1190 CXCursor *overridden;
1191 unsigned int numOverridden = 0;
1192 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
1193 for (uint i = 0; i < numOverridden; ++i) {
1194 QString path = reconstructQualifiedPathForCursor(overridden[i]);
1195 if (!path.isEmpty()) {
1197 fn->setOverridesThis(path);
1201 clang_disposeOverriddenCursors(overridden);
1210 internalFilePatterns_(internalFilePatterns)
1212 std::transform(allHeaders.cbegin(), allHeaders.cend(),
std::inserter(allHeaders_, allHeaders_.begin()),
1213 [](
const auto& header_file_path) ->
const QString& {
return header_file_path.filename; });
1220 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1221 auto loc = clang_getCursorLocation(cur);
1222 if (clang_Location_isFromMainFile(loc))
1223 return visitSource(cur, loc);
1226 clang_getFileLocation(loc, &file,
nullptr,
nullptr,
nullptr);
1227 bool isInteresting =
false;
1228 auto it = isInterestingCache_.find(file);
1229 if (it != isInterestingCache_.end()) {
1230 isInteresting = *it;
1232 QFileInfo fi(fromCXString(clang_getFileName(file)));
1234 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1235 isInterestingCache_[file] = isInteresting;
1237 if (isInteresting) {
1238 return visitHeader(cur, loc);
1241 return CXChildVisit_Continue;
1243 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1247
1248
1249
1252 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1253 auto loc = clang_getCursorLocation(cur);
1254 if (clang_Location_isFromMainFile(loc))
1255 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1256 return CXChildVisit_Continue;
1258 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1266
1267
1268
1271 unsigned int line {}, column {};
1272 friend bool operator<(
const SimpleLoc &a,
const SimpleLoc &b)
1274 return a.line != b.line ? a.line < b.line : a.column < b.column;
1278
1279
1280
1281
1282 QMap<SimpleLoc, CXCursor> declMap_;
1286 std::set<QString> allHeaders_;
1287 QHash<CXFile,
bool> isInterestingCache_;
1291
1292
1293 bool ignoredSymbol(
const QString &symbolName)
1295 if (symbolName == QLatin1String(
"QPrivateSignal"))
1298 if (symbolName.startsWith(
"_qt_property_"))
1301 if (symbolName.startsWith(
"<deduction guide"))
1306 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1307 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1308 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc,
Node **fnNode,
1309 bool &ignoreSignature);
1310 void processFunction(
FunctionNode *fn, CXCursor cursor);
1311 bool parseProperty(
const QString &spelling,
const Location &loc);
1312 void readParameterNamesAndAttributes(
FunctionNode *fn, CXCursor cursor);
1313 Aggregate *getSemanticParent(CXCursor cursor);
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1347 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
1349 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1350 CXSourceRange range = clang_getCursorExtent(child);
1351 QString sourceText = getSpelling(range);
1352 static const QRegularExpression qmlClassInfoPattern(
1353 R"(Q_CLASSINFO\s*\(\s*["\']QML\.(Singleton|Creatable)["\']\s*,\s*["\'](true|false)["\']\s*\))");
1354 const auto match = qmlClassInfoPattern.match(sourceText);
1355 if (match.hasMatch()) {
1356 if (match.captured(1) ==
"Singleton"_L1 && match.captured(2) ==
"true"_L1) {
1358 return CXChildVisit_Break;
1359 }
else if (match.captured(1) ==
"Creatable"_L1 && match.captured(2) ==
"false"_L1) {
1361 return CXChildVisit_Break;
1367 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1368 QString spelling = fromCXString(clang_getCursorSpelling(child));
1369 if (spelling ==
"QmlIsSingleton"_L1) {
1371 return CXChildVisit_Break;
1372 }
else if (spelling ==
"QmlIsUncreatable"_L1) {
1374 return CXChildVisit_Break;
1378 return CXChildVisit_Continue;
1385
1386
1387
1388CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1390 auto kind = clang_getCursorKind(cursor);
1391 if (clang_isDeclaration(kind)) {
1393 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1394 declMap_.insert(l, cursor);
1395 return CXChildVisit_Recurse;
1397 return CXChildVisit_Continue;
1401
1402
1403
1404
1407 CXCursor sp = clang_getCursorSemanticParent(cursor);
1408 CXCursor lp = clang_getCursorLexicalParent(cursor);
1409 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1412 return static_cast<Aggregate *>(spn);
1418CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1419 bool &ignoreSignature)
1421 switch (clang_getCursorKind(cursor)) {
1422 case CXCursor_Namespace:
1423 return CXChildVisit_Recurse;
1424 case CXCursor_FunctionDecl:
1425 case CXCursor_FunctionTemplate:
1426 case CXCursor_CXXMethod:
1427 case CXCursor_Constructor:
1428 case CXCursor_Destructor:
1429 case CXCursor_ConversionFunction: {
1430 ignoreSignature =
false;
1431 if (ignoredSymbol(functionName(cursor))) {
1433 ignoreSignature =
true;
1438 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1439 readParameterNamesAndAttributes(fn, cursor);
1443 if (
const auto function_declaration = declaration->getAsFunction()) {
1444 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1445 if (llvm::dyn_cast_if_present<
clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1446 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1450 QString name = functionName(cursor);
1451 if (ignoredSymbol(name))
1452 return CXChildVisit_Continue;
1453 Aggregate *semanticParent = getSemanticParent(cursor);
1454 if (semanticParent && semanticParent
->isClass()) {
1456 processFunction(candidate, cursor);
1457 if (!candidate->isSpecialMemberFunction()) {
1459 return CXChildVisit_Continue;
1461 candidate->setImplicitlyGenerated(
true);
1471 return CXChildVisit_Continue;
1474CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1476 auto kind = clang_getCursorKind(cursor);
1479 case CXCursor_TypeAliasTemplateDecl:
1480 case CXCursor_TypeAliasDecl: {
1481 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1482 QString aliasedType;
1484 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1488 if (kind == CXCursor_TypeAliasTemplateDecl) {
1490 if (
const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1491 if (
const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1492 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1493 aliasedType = QString::fromStdString(underlyingType.getAsString());
1498 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1499 if (aliasedCXType.kind != CXType_Invalid) {
1500 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1504 if (!aliasedType.isEmpty()) {
1505 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1510 ta->setTemplateDecl(get_template_declaration(templateDecl));
1512 return CXChildVisit_Continue;
1514 case CXCursor_StructDecl:
1515 case CXCursor_UnionDecl:
1516 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1517 return CXChildVisit_Continue;
1519 case CXCursor_ClassTemplate:
1521 case CXCursor_ClassDecl: {
1522 if (!clang_isCursorDefinition(cursor))
1523 return CXChildVisit_Continue;
1526 return CXChildVisit_Continue;
1528 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1530 Aggregate *semanticParent = getSemanticParent(cursor);
1531 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1532 return CXChildVisit_Continue;
1535 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1536 clang_getTemplateCursorKind(cursor) : kind;
1539 if (actualKind == CXCursor_StructDecl)
1541 else if (actualKind == CXCursor_UnionDecl)
1544 auto *classe =
new ClassNode(type, semanticParent, className);
1548 classe->setLocation(location);
1550 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1551 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1552 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1556 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1557 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1559 if (kind == CXCursor_ClassTemplate) {
1561 classe->setTemplateDecl(get_template_declaration(template_declaration));
1564 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1567 case CXCursor_CXXBaseSpecifier: {
1569 return CXChildVisit_Continue;
1571 auto type = clang_getCursorType(cursor);
1572 auto baseCursor = clang_getTypeDeclaration(type);
1574 auto classe =
static_cast<ClassNode *>(parent_);
1576 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1577 classe->addUnresolvedBaseClass(access,
1578 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1579 return CXChildVisit_Continue;
1581 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1582 classe->addResolvedBaseClass(access, baseClasse);
1583 return CXChildVisit_Continue;
1585 case CXCursor_Namespace: {
1586 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1596 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1599 case CXCursor_FunctionTemplate:
1601 case CXCursor_FunctionDecl:
1602 case CXCursor_CXXMethod:
1603 case CXCursor_Constructor:
1604 case CXCursor_Destructor:
1605 case CXCursor_ConversionFunction: {
1607 return CXChildVisit_Continue;
1608 QString name = functionName(cursor);
1609 if (ignoredSymbol(name))
1610 return CXChildVisit_Continue;
1613 return CXChildVisit_Continue;
1616 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1617 if (!clang_Range_isNull(range)) {
1618 QString comment = getSpelling(range);
1619 if (comment.startsWith(
"//!")) {
1620 qsizetype tag = comment.indexOf(QChar(
'['));
1622 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1624 fn->setTag(comment.mid(tag, end - tag));
1629 processFunction(fn, cursor);
1631 if (kind == CXCursor_FunctionTemplate) {
1633 fn->setTemplateDecl(get_template_declaration(template_declaration));
1636 if (!clang_Location_isInSystemHeader(loc))
1637 fn->autoGenerateSmfDoc(parent_->name());
1639 return CXChildVisit_Continue;
1641#if CINDEX_VERSION
>= 36
1642 case CXCursor_FriendDecl: {
1646 case CXCursor_EnumDecl: {
1647 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1648 if (en && en->items().size())
1649 return CXChildVisit_Continue;
1651 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1653 if (clang_Cursor_isAnonymous(cursor)) {
1654 enumTypeName =
"anonymous";
1660 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1662 en =
static_cast<EnumNode *>(n);
1666 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1669 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1673 visitChildrenLambda(cursor, [&](CXCursor cur) {
1674 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1675 return CXChildVisit_Continue;
1678 visitChildrenLambda(cur, [&](CXCursor cur) {
1679 if (clang_isExpression(clang_getCursorKind(cur))) {
1680 value = getSpelling(clang_getCursorExtent(cur));
1681 return CXChildVisit_Break;
1683 return CXChildVisit_Continue;
1685 if (value.isEmpty()) {
1686 QLatin1String hex(
"0x");
1687 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1688 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1690 value = QString::number(clang_getEnumConstantDeclValue(cur));
1694 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1695 return CXChildVisit_Continue;
1697 return CXChildVisit_Continue;
1699 case CXCursor_FieldDecl:
1700 case CXCursor_VarDecl: {
1702 return CXChildVisit_Continue;
1704 auto value_declaration =
1706 assert(value_declaration);
1709 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1711 var->setAccess(access);
1713 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1714 value_declaration->getType(),
1715 value_declaration->getASTContext()
1717 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1719 return CXChildVisit_Continue;
1721 case CXCursor_TypedefDecl: {
1723 return CXChildVisit_Continue;
1724 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1728 visitChildrenLambda(cursor, [&](CXCursor cur) {
1729 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1730 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1731 return CXChildVisit_Continue;
1733 visitChildrenLambda(cursor, [&](CXCursor cur) {
1734 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1735 return CXChildVisit_Continue;
1738 if (en && en->isEnumType())
1739 static_cast<EnumNode *>(en)->setFlagsType(td);
1740 return CXChildVisit_Break;
1742 return CXChildVisit_Break;
1744 return CXChildVisit_Continue;
1750 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1753 return CXChildVisit_Continue;
1762 visitChildrenLambda(cursor, [&](CXCursor cur) {
1763 auto kind = clang_getCursorKind(cur);
1764 if (kind == CXCursor_AnnotateAttr) {
1765 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1766 if (annotation == QLatin1String(
"qt_slot")) {
1768 }
else if (annotation == QLatin1String(
"qt_signal")) {
1771 if (annotation == QLatin1String(
"qt_invokable"))
1773 }
else if (kind == CXCursor_CXXOverrideAttr) {
1775 }
else if (kind == CXCursor_ParmDecl) {
1777 return CXChildVisit_Break;
1779 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1780 parameters
[i
].setName(name);
1783 Q_ASSERT(parameter_declaration);
1787 if (!default_value.empty())
1788 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1792 return CXChildVisit_Continue;
1798 CXCursorKind kind = clang_getCursorKind(cursor);
1799 CXType funcType = clang_getCursorType(cursor);
1806 : clang_CXXMethod_isPureVirtual(cursor)
1823 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1825 if (kind == CXCursor_Constructor
1827 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1829 else if (kind == CXCursor_Destructor)
1831 else if (kind != CXCursor_ConversionFunction)
1832 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1833 function_declaration->getReturnType(),
1834 function_declaration->getASTContext()
1837 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1842 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1848 (constructor_declaration && constructor_declaration->isExplicit()) ||
1849 (conversion_declaration && conversion_declaration->isExplicit())
1852 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1857 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1858 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1860 if (function_prototype) {
1861 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1863 if (exception_specification.Type !=
clang::ExceptionSpecificationType::EST_None) {
1864 const std::string exception_specification_spelling =
1865 exception_specification.NoexceptExpr ? get_expression_as_string(
1866 exception_specification.NoexceptExpr,
1867 function_declaration->getASTContext()
1870 if (exception_specification_spelling !=
"false")
1871 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1880 QStringList referenced_concepts;
1881#if LIBCLANG_VERSION_MAJOR >= 21
1882 if (
const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1883 trailing_requires.ConstraintExpr) {
1884 QString requires_str = QString::fromStdString(
1885 get_expression_as_string(trailing_requires.ConstraintExpr,
1886 function_declaration->getASTContext()));
1887 fn->setTrailingRequiresClause(requires_str.simplified());
1888 std::vector<std::string> refs;
1889 collect_concept_references(trailing_requires.ConstraintExpr, refs);
1890 for (
const auto &ref : refs)
1891 referenced_concepts << QString::fromStdString(ref);
1894 if (
const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1895 QString requires_str = QString::fromStdString(
1896 get_expression_as_string(trailing_requires,
1897 function_declaration->getASTContext()));
1898 fn->setTrailingRequiresClause(requires_str.simplified());
1899 std::vector<std::string> refs;
1900 collect_concept_references(trailing_requires, refs);
1901 for (
const auto &ref : refs)
1902 referenced_concepts << QString::fromStdString(ref);
1906 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1907 if (refQualKind == CXRefQualifier_LValue)
1909 else if (refQualKind == CXRefQualifier_RValue)
1918 parameters
.reserve(function_declaration->getNumParams()
);
1920 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1921 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1923 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1925 parameter_declaration->getASTContext()
1928 if (!parameter_type.isCanonical())
1929 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1930 parameter_type.getCanonicalType(),
1931 parameter_declaration->getASTContext()
1935 if (
const clang::AutoType *auto_type = parameter_type->getContainedAutoType()) {
1936 if (auto_type->isConstrained()) {
1937 if (
const clang::NamedDecl *concept_decl =
1938 auto_type->getTypeConstraintConcept()) {
1939 referenced_concepts << QString::fromStdString(
1940 concept_decl->getQualifiedNameAsString());
1946 if (!referenced_concepts.isEmpty()) {
1947 referenced_concepts.sort();
1948 referenced_concepts.removeDuplicates();
1949 fn->setReferencedConcepts(
std::move(referenced_concepts));
1953 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
1959 if (clang_isFunctionTypeVariadic(funcType))
1960 parameters.append(QStringLiteral(
"..."));
1961 readParameterNamesAndAttributes(fn, cursor);
1963 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
1965 Q_ASSERT(function_declaration);
1967 const bool hasNamespaceScopeRedeclaration =
1968 std::any_of(function_declaration->redecls_begin(),
1969 function_declaration->redecls_end(),
1970 [](
const clang::FunctionDecl *r) {
1971 return r->getFriendObjectKind() == clang::Decl::FOK_None;
1973 if (!hasNamespaceScopeRedeclaration)
1980 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
1981 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
1982 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
1985 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
1986 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
1987 if (lpIdx <= 0 || rpIdx <= lpIdx)
1990 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1991 signature = signature.simplified();
1992 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
1994 static const QStringList attrs =
1995 QStringList() <<
"READ" <<
"MEMBER" <<
"WRITE"
1996 <<
"NOTIFY" <<
"CONSTANT" <<
"FINAL"
1997 <<
"REQUIRED" <<
"BINDABLE" <<
"DESIGNABLE"
1998 <<
"RESET" <<
"REVISION" <<
"SCRIPTABLE"
1999 <<
"STORED" <<
"USER";
2003 auto it =
std::find_if(parts.cbegin(), parts.cend(),
2004 [](
const QString &attr) ->
bool {
2005 return attrs.contains(attr);
2008 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
2011 QStringList typeParts;
2012 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
2013 parts.erase(parts.cbegin(), it);
2014 QString name = typeParts.takeLast();
2017 while (!name.isEmpty() && name.front() == QChar(
'*')) {
2018 typeParts.last().push_back(name.front());
2023 if (parts.size() < 2 || name.isEmpty())
2027 property->setAccess(Access::Public);
2028 property->setLocation(loc);
2029 property->setDataType(typeParts.join(QChar(
' ')));
2032 while (i < parts.size()) {
2033 const QString &key = parts.at(i++);
2035 if (key ==
"CONSTANT") {
2036 property->setConstant();
2037 }
else if (key ==
"REQUIRED") {
2038 property->setRequired();
2040 if (i < parts.size()) {
2041 QString value = parts.at(i++);
2042 if (key ==
"READ") {
2044 }
else if (key ==
"WRITE") {
2046 property->setWritable(
true);
2047 }
else if (key ==
"MEMBER") {
2048 property->setWritable(
true);
2049 }
else if (key ==
"STORED") {
2050 property->setStored(value.toLower() ==
"true");
2051 }
else if (key ==
"BINDABLE") {
2054 }
else if (key ==
"RESET") {
2056 }
else if (key ==
"NOTIFY") {
2065
2066
2067
2068
2069
2073 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
2074 auto decl_it = declMap_.upperBound(docloc);
2075 if (decl_it == declMap_.end())
2078 unsigned int declLine = decl_it.key().line;
2079 unsigned int nextCommentLine;
2080 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
2081 if (nextCommentLine < declLine)
2085 if (decl_it != declMap_.begin()) {
2086 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(
std::prev(decl_it))));
2087 unsigned int prevDeclLine;
2088 clang_getPresumedLocation(prevDeclEnd,
nullptr, &prevDeclLine,
nullptr);
2089 if (prevDeclLine >= docloc.line) {
2092 auto parent = clang_getCursorLexicalParent(*decl_it);
2093 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2097 auto *node = findNodeForCursor(qdb_, *decl_it);
2099 if (node && node->isFunction(Genus::CPP))
2100 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2107 const std::vector<QByteArray>& include_paths,
2108 const QList<QByteArray>& defines,
2115 m_allHeaders = config.getHeaderFiles();
2116 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2124 "-fms-compatibility-version=19",
2128 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2129 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2130 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2131 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2132 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2133 "-Wno-constant-logical-operand",
2134 "-Wno-macro-redefined",
2135 "-Wno-nullability-completeness",
2136 "-fvisibility=default",
2143 std::vector<
const char *> pointers;
2144 pointers.reserve(args.size());
2145 for (
const auto &arg : args)
2146 pointers.push_back(arg.constData());
2151
2152
2153
2158 args.emplace_back(arg);
2161 for (
const auto &p : std::as_const(defines))
2166
2167
2169 const std::vector<QByteArray>& include_paths,
2170 std::vector<QByteArray>& args
2172 if (include_paths.empty()) {
2173 qCWarning(lcQdoc) <<
"No include paths provided."
2174 <<
"Set 'includepaths' in the qdocconf file"
2175 <<
"or pass -I flags on the command line."
2176 <<
"C++ parsing may produce incomplete results.";
2178 args.insert(args.end(), include_paths.begin(), include_paths.end());
2183
2184
2185
2186
2189 QString module_header,
2190 const std::set<Config::HeaderFilePath>& all_headers,
2191 const std::vector<QByteArray>& include_paths,
2192 const QList<QByteArray>& defines,
2193 const InclusionPolicy& policy
2195 static std::vector<QByteArray> arguments{};
2197 if (module_header.isEmpty())
return std::nullopt;
2199 getDefaultArgs(defines, arguments);
2200 getMoreArgs(include_paths, arguments);
2202 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2203 | CXTranslationUnit_SkipFunctionBodies
2204 | CXTranslationUnit_KeepGoing);
2208 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2209 if (!pch_directory.isValid())
return std::nullopt;
2211 const QByteArray module = module_header.toUtf8();
2214 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2217 struct FindPredicate
2219 enum SearchType { Any, Module };
2220 QByteArray &candidate_;
2221 const QByteArray &module_;
2223 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2224 SearchType type = Any)
2225 : candidate_(candidate), module_(module), type_(type)
2229 bool operator()(
const QByteArray &p)
const
2231 if (type_ != Any && !p.endsWith(module_))
2233 candidate_ = p +
"/";
2234 candidate_.append(module_);
2235 if (p.startsWith(
"-I"))
2236 candidate_ = candidate_.mid(2);
2237 return QFile::exists(QString::fromUtf8(candidate_));
2242 QByteArray candidate;
2243 auto it =
std::find_if(include_paths.begin(), include_paths.end(),
2244 FindPredicate(candidate, module, FindPredicate::Module));
2245 if (it == include_paths.end())
2246 it =
std::find_if(include_paths.begin(), include_paths.end(),
2247 FindPredicate(candidate, module, FindPredicate::Any));
2248 if (it != include_paths.end())
2249 header =
std::move(candidate);
2251 if (header.isEmpty()) {
2252 qWarning() <<
"(qdoc) Could not find the module header in include paths for module"
2253 << module <<
" (include paths: " << include_paths <<
")";
2254 qWarning() <<
" Artificial module header built from header dirs in qdocconf "
2257 arguments.push_back(
"-xc++");
2261 QString tmpHeader = pch_directory.path() +
"/" + module;
2262 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2263 QTextStream out(&tmpHeaderFile);
2264 if (header.isEmpty()) {
2265 for (
const auto& [header_path, header_name] : all_headers) {
2266 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2269 if (header_name.endsWith(
"_p.h"_L1))
2270 shouldInclude = shouldInclude && policy.showInternal;
2272 if (shouldInclude) {
2273 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2277 QFileInfo headerFile(header);
2278 if (!headerFile.exists()) {
2279 qWarning() <<
"Could not find module header file" << header;
2280 return std::nullopt;
2283 out <<
"#include \"" << header <<
"\"\n";
2285 if (policy.showInternal) {
2286 for (
const auto& [header_path, header_name] : all_headers) {
2287 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2288 if (header_name.endsWith(
"_p.h"_L1) && shouldInclude)
2289 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2295 const auto argPointers = toConstCharPointers(arguments);
2296 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2298 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2299 static_cast<
int>(argPointers.size()),
nullptr, 0,
2300 flags_ | CXTranslationUnit_ForSerialization, &tu
.tu);
2301 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << tmpHeader << arguments
2302 <<
") returns" << err;
2307 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2308 return std::nullopt;
2311 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2312 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2313 clang_defaultSaveOptions(tu));
2315 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2316 return std::nullopt;
2321 CXCursor cur = clang_getTranslationUnitCursor(tu);
2322 auto &config = Config::instance();
2323 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2325 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2327 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2332 if (t.count(QChar(
'.')) > 1)
2333 t.truncate(t.lastIndexOf(QChar(
'.')));
2338
2339
2340
2341
2342
2343
2344
2345
2348 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2349 | CXTranslationUnit_SkipFunctionBodies
2350 | CXTranslationUnit_KeepGoing);
2354 getDefaultArgs(m_defines, m_args);
2355 if (m_pch && !filePath.endsWith(
".mm")
2356 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2357 m_args.push_back(
"-w");
2358 m_args.push_back(
"-include-pch");
2359 m_args.push_back((*m_pch).get().name);
2361 getMoreArgs(m_includePaths, m_args);
2364 const auto argPointers = toConstCharPointers(m_args);
2365 const QByteArray filePathLocal = filePath.toLocal8Bit();
2367 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2368 static_cast<
int>(argPointers.size()),
nullptr, 0,
flags_, &tu
.tu);
2369 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << filePath << m_args
2370 <<
") returns" << err;
2374 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2378 ParsedCppFileIR parse_result{};
2380 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2381 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2385 unsigned int numTokens = 0;
2386 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2387 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2389 for (
unsigned int i = 0; i < numTokens; ++i) {
2390 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2392 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2393 if (!comment.startsWith(
"/*!"))
2396 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2399 Doc::trimCStyleComment(loc, comment);
2402 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2408 if (i + 1 < numTokens) {
2410 CXSourceLocation nextCommentLoc = commentLoc;
2411 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2413 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2420 bool future =
false;
2422 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2423 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2424 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
2429 QStringLiteral(
"Cannot tie this documentation to anything"),
2430 QStringLiteral(
"qdoc found a /*! ... */ comment, but there was no "
2431 "topic command (e.g., '\\%1', '\\%2') in the "
2432 "comment and qdoc could not associate the "
2433 "declaration or definition following the "
2434 "comment with a documented entity.")
2441 CXCursor cur = clang_getCursor(tu, commentLoc);
2443 CXCursorKind kind = clang_getCursorKind(cur);
2444 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2446 if (kind == CXCursor_Namespace) {
2447 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2449 cur = clang_getCursorLexicalParent(cur);
2454 clang_disposeTokens(tu, tokens, numTokens);
2455 m_namespaceScope.clear();
2458 return parse_result;
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2474 const QString &idTag, QStringList context)
2476 Node *fnNode =
nullptr;
2478
2479
2480
2481
2482
2483 if (!idTag.isEmpty()) {
2484 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2487 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2490
2491
2492
2493
2494 auto *fn =
static_cast<FunctionNode *>(fnNode);
2495 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split(
'(');
2496 if (leftParenSplit.size() > 1) {
2497 QStringList rightParenSplit = leftParenSplit[1].split(
')');
2498 if (!rightParenSplit.empty()) {
2499 QString params = rightParenSplit[0];
2500 if (!params.isEmpty()) {
2501 QStringList commaSplit = params.split(
',');
2503 if (parameters
.count() == commaSplit.size()) {
2504 for (
int i = 0; i < parameters
.count(); ++i) {
2505 QStringList blankSplit = commaSplit[i].split(
' ', Qt::SkipEmptyParts);
2506 if (blankSplit.size() > 1) {
2507 QString pName = blankSplit.last();
2509 auto it =
std::find_if(
std::begin(pName),
std::end(pName),
2510 [](
const QChar &c) {
return c.isLetter(); });
2511 parameters
[i
].setName(
2512 pName.remove(0,
std::distance(
std::begin(pName), it)));
2522 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2523 | CXTranslationUnit_SkipFunctionBodies
2524 | CXTranslationUnit_KeepGoing);
2528 getDefaultArgs(m_defines, m_args);
2531 m_args.push_back(
"-w");
2532 m_args.push_back(
"-include-pch");
2533 m_args.push_back((*m_pch).get().name);
2538 for (
const auto &ns : std::as_const(context))
2539 s_fn.prepend(
"namespace " + ns.toUtf8() +
" {");
2540 s_fn += fnSignature.toUtf8();
2541 if (!s_fn.endsWith(
";"))
2543 s_fn.append(context.size(),
'}');
2546 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2547 static_cast<
unsigned long>(s_fn.size()) };
2548 const auto argPointers = toConstCharPointers(m_args);
2549 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2550 int(argPointers.size()), &unsavedFile, 1, flags, &tu
.tu);
2551 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << dummyFileName << m_args
2552 <<
") returns" << err;
2555 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2559
2560
2561
2562
2563
2564 CXCursor cur = clang_getTranslationUnitCursor(tu);
2565 auto &config = Config::instance();
2566 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2567 bool ignoreSignature =
false;
2571 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2572 const auto &config = Config::instance();
2573 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2574 return FnMatchError{ fnSignature, location };
static const clang::Decl * get_cursor_declaration(CXCursor cursor)
Returns the underlying Decl that cursor represents.
static QString reconstructQualifiedPathForCursor(CXCursor cur)
Reconstruct the qualified path name of a function that is being overridden.
static void findHiddenFriendCandidates(QDocDatabase *qdb, const QString &funcName, const clang::FunctionDecl *func_decl, NodeVector &candidates)
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static const auto kClangDontDisplayDiagnostics
static const clang::TemplateSpecializationType * find_template_specialization_through_sugar(const clang::Type *type)
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
static void collect_concept_references(const clang::Stmt *node, std::vector< std::string > &out)
static std::optional< SfinaeConstraint > detect_sfinae_constraint(const clang::NonTypeTemplateParmDecl *param)
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::vector< const char * > toConstCharPointers(const std::vector< QByteArray > &args)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static bool is_enable_if_name(const std::string &qualified_name)
static float getUnpatchedVersion(QString t)
void getMoreArgs(const std::vector< QByteArray > &include_paths, std::vector< QByteArray > &args)
Load the include paths into args.
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< QByteArray > &args)
Load the default arguments and the defines into args.
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
static std::string ensureAnonymousTagKeyword(std::string typeName, clang::QualType type)
Returns a string representing the name of type as if it was referred to at the end of the translation...
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
ClangVisitor(QDocDatabase *qdb, const std::set< Config::HeaderFilePath > &allHeaders, const Config::InternalFilePatterns &internalFilePatterns)
CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
The ClassNode represents a C++ class.
static bool isWorthWarningAbout(const Doc &doc)
Test for whether a doc comment warrants warnings.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
const Location & location() const
Returns the starting location of a qdoc comment.
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
This node is used to represent any kind of function being documented.
void markDeletedAsWritten()
void setVirtualness(Virtualness virtualness)
bool isNonvirtual() const
void setInvokable(bool b)
void markExplicitlyDefaulted()
void setMetaness(Metaness metaness)
Parameters & parameters()
void setHiddenFriend(bool b)
The Location class provides a way to mark a location in a file.
This class represents a C++ namespace.
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
Status
Specifies the status of the QQmlIncubator.
#define CONFIG_DOCUMENTATIONINHEADERS
bool hasTooManyTopics(const Doc &doc)
Checks if there are too many topic commands in doc.
QmlNativeTypeAttribute
Defines QML-specific attributes affecting QmlTypeNode instances.
Metaness
Specifies the kind of function a FunctionNode represents.
This namespace holds QDoc-internal utility methods.
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
Returns the spelling in the file for a source range.
std::variant< Node *, FnMatchError > operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
Use clang to parse the function signature from a function command.
Encapsulates information about.
The Node class is the base class for all the nodes in QDoc's parse tree.
void setAccess(Access t)
Sets the node's access type to t.
bool isNamespace() const
Returns true if the node type is Namespace.
bool isTypedef() const
Returns true if the node type is Typedef.
bool isVariable() const
Returns true if the node type is Variable.
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
bool isClass() const
Returns true if the node type is Class.
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
A class for parsing and managing a function parameter list.
Parameter & operator[](int index)
Holds the source-level alias with its template arguments for a SFINAE constraint detected in a non-ty...
operator CXTranslationUnit()