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);
1205
1206
1207
1208
1220 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
1221 if (fn->isVirtual())
1222 docSource += u" This destructor is virtual."_s;
1224 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
1226 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
1228 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
1231 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
1232 ? params.at(0).name()
1234 docSource = fn->isCAssign()
1235 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
1236 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
1239 if (docSource.isEmpty())
1242 if (fn->isDeletedAsWritten())
1243 docSource += u" This function is deleted."_s;
1245 static const QSet<QString> noMetaCommands;
1246 static const QSet<QString> noTopics;
1258 internalFilePatterns_(internalFilePatterns)
1260 std::transform(allHeaders.cbegin(), allHeaders.cend(),
std::inserter(allHeaders_, allHeaders_.begin()),
1261 [](
const auto& header_file_path) ->
const QString& {
return header_file_path.filename; });
1268 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1269 auto loc = clang_getCursorLocation(cur);
1270 if (clang_Location_isFromMainFile(loc))
1271 return visitSource(cur, loc);
1274 clang_getFileLocation(loc, &file,
nullptr,
nullptr,
nullptr);
1275 bool isInteresting =
false;
1276 auto it = isInterestingCache_.find(file);
1277 if (it != isInterestingCache_.end()) {
1278 isInteresting = *it;
1280 QFileInfo fi(fromCXString(clang_getFileName(file)));
1282 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
1283 isInterestingCache_[file] = isInteresting;
1285 if (isInteresting) {
1286 return visitHeader(cur, loc);
1289 return CXChildVisit_Continue;
1291 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1295
1296
1297
1300 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
1301 auto loc = clang_getCursorLocation(cur);
1302 if (clang_Location_isFromMainFile(loc))
1303 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
1304 return CXChildVisit_Continue;
1306 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
1314
1315
1316
1319 unsigned int line {}, column {};
1320 friend bool operator<(
const SimpleLoc &a,
const SimpleLoc &b)
1322 return a.line != b.line ? a.line < b.line : a.column < b.column;
1326
1327
1328
1329
1330 QMap<SimpleLoc, CXCursor> declMap_;
1334 std::set<QString> allHeaders_;
1335 QHash<CXFile,
bool> isInterestingCache_;
1339
1340
1341 bool ignoredSymbol(
const QString &symbolName)
1343 if (symbolName == QLatin1String(
"QPrivateSignal"))
1346 if (symbolName.startsWith(
"_qt_property_"))
1349 if (symbolName.startsWith(
"<deduction guide"))
1354 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1355 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1356 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc,
Node **fnNode,
1357 bool &ignoreSignature);
1358 void processFunction(
FunctionNode *fn, CXCursor cursor);
1359 bool parseProperty(
const QString &spelling,
const Location &loc);
1360 void readParameterNamesAndAttributes(
FunctionNode *fn, CXCursor cursor);
1361 Aggregate *getSemanticParent(CXCursor cursor);
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1395 visitChildrenLambda(cursor, [&attr](CXCursor child) -> CXChildVisitResult {
1397 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1398 CXSourceRange range = clang_getCursorExtent(child);
1399 QString sourceText = getSpelling(range);
1400 static const QRegularExpression qmlClassInfoPattern(
1401 R"(Q_CLASSINFO\s*\(\s*["\']QML\.(Singleton|Creatable)["\']\s*,\s*["\'](true|false)["\']\s*\))");
1402 const auto match = qmlClassInfoPattern.match(sourceText);
1403 if (match.hasMatch()) {
1404 if (match.captured(1) ==
"Singleton"_L1 && match.captured(2) ==
"true"_L1) {
1406 return CXChildVisit_Break;
1407 }
else if (match.captured(1) ==
"Creatable"_L1 && match.captured(2) ==
"false"_L1) {
1409 return CXChildVisit_Break;
1415 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1416 QString spelling = fromCXString(clang_getCursorSpelling(child));
1417 if (spelling ==
"QmlIsSingleton"_L1) {
1419 return CXChildVisit_Break;
1420 }
else if (spelling ==
"QmlIsUncreatable"_L1) {
1422 return CXChildVisit_Break;
1426 return CXChildVisit_Continue;
1433
1434
1435
1436CXChildVisitResult
ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1438 auto kind = clang_getCursorKind(cursor);
1439 if (clang_isDeclaration(kind)) {
1441 clang_getPresumedLocation(loc,
nullptr, &l.line, &l.column);
1442 declMap_.insert(l, cursor);
1443 return CXChildVisit_Recurse;
1445 return CXChildVisit_Continue;
1449
1450
1451
1452
1455 CXCursor sp = clang_getCursorSemanticParent(cursor);
1456 CXCursor lp = clang_getCursorLexicalParent(cursor);
1457 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1460 return static_cast<Aggregate *>(spn);
1466CXChildVisitResult
ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation,
Node **fnNode,
1467 bool &ignoreSignature)
1469 switch (clang_getCursorKind(cursor)) {
1470 case CXCursor_Namespace:
1471 return CXChildVisit_Recurse;
1472 case CXCursor_FunctionDecl:
1473 case CXCursor_FunctionTemplate:
1474 case CXCursor_CXXMethod:
1475 case CXCursor_Constructor:
1476 case CXCursor_Destructor:
1477 case CXCursor_ConversionFunction: {
1478 ignoreSignature =
false;
1479 if (ignoredSymbol(functionName(cursor))) {
1481 ignoreSignature =
true;
1486 auto *fn =
static_cast<FunctionNode *>(*fnNode);
1487 readParameterNamesAndAttributes(fn, cursor);
1491 if (
const auto function_declaration = declaration->getAsFunction()) {
1492 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1493 if (llvm::dyn_cast_if_present<
clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1494 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1498 QString name = functionName(cursor);
1499 if (ignoredSymbol(name))
1500 return CXChildVisit_Continue;
1501 Aggregate *semanticParent = getSemanticParent(cursor);
1502 if (semanticParent && semanticParent
->isClass()) {
1504 processFunction(candidate, cursor);
1505 if (!candidate->isSpecialMemberFunction()) {
1507 return CXChildVisit_Continue;
1509 candidate->setImplicitlyGenerated(
true);
1519 return CXChildVisit_Continue;
1522CXChildVisitResult
ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1524 auto kind = clang_getCursorKind(cursor);
1527 case CXCursor_TypeAliasTemplateDecl:
1528 case CXCursor_TypeAliasDecl: {
1529 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1530 QString aliasedType;
1532 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1536 if (kind == CXCursor_TypeAliasTemplateDecl) {
1538 if (
const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1539 if (
const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1540 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1541 aliasedType = QString::fromStdString(underlyingType.getAsString());
1546 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1547 if (aliasedCXType.kind != CXType_Invalid) {
1548 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1552 if (!aliasedType.isEmpty()) {
1553 auto *ta =
new TypeAliasNode(parent_, aliasName, aliasedType);
1558 ta->setTemplateDecl(get_template_declaration(templateDecl));
1560 return CXChildVisit_Continue;
1562 case CXCursor_StructDecl:
1563 case CXCursor_UnionDecl:
1564 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty())
1565 return CXChildVisit_Continue;
1567 case CXCursor_ClassTemplate:
1569 case CXCursor_ClassDecl: {
1570 if (!clang_isCursorDefinition(cursor))
1571 return CXChildVisit_Continue;
1574 return CXChildVisit_Continue;
1576 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1578 Aggregate *semanticParent = getSemanticParent(cursor);
1579 if (semanticParent && semanticParent->findNonfunctionChild(className, &
Node::isClassNode)) {
1580 return CXChildVisit_Continue;
1583 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1584 clang_getTemplateCursorKind(cursor) : kind;
1587 if (actualKind == CXCursor_StructDecl)
1589 else if (actualKind == CXCursor_UnionDecl)
1592 auto *classe =
new ClassNode(type, semanticParent, className);
1596 classe->setLocation(location);
1598 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1599 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1600 if (
Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1604 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1605 classe->setQmlNativeTypeAttribute(detectQmlNativeTypeAttribute(cursor));
1607 if (kind == CXCursor_ClassTemplate) {
1609 classe->setTemplateDecl(get_template_declaration(template_declaration));
1612 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1615 case CXCursor_CXXBaseSpecifier: {
1617 return CXChildVisit_Continue;
1619 auto type = clang_getCursorType(cursor);
1620 auto baseCursor = clang_getTypeDeclaration(type);
1622 auto classe =
static_cast<ClassNode *>(parent_);
1624 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1625 classe->addUnresolvedBaseClass(access,
1626 bcName.split(QLatin1String(
"::"), Qt::SkipEmptyParts));
1627 return CXChildVisit_Continue;
1629 auto baseClasse =
static_cast<ClassNode *>(baseNode);
1630 classe->addResolvedBaseClass(access, baseClasse);
1631 return CXChildVisit_Continue;
1633 case CXCursor_Namespace: {
1634 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1644 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1647 case CXCursor_FunctionTemplate:
1649 case CXCursor_FunctionDecl:
1650 case CXCursor_CXXMethod:
1651 case CXCursor_Constructor:
1652 case CXCursor_Destructor:
1653 case CXCursor_ConversionFunction: {
1655 return CXChildVisit_Continue;
1656 QString name = functionName(cursor);
1657 if (ignoredSymbol(name))
1658 return CXChildVisit_Continue;
1661 return CXChildVisit_Continue;
1664 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1665 if (!clang_Range_isNull(range)) {
1666 QString comment = getSpelling(range);
1667 if (comment.startsWith(
"//!")) {
1668 qsizetype tag = comment.indexOf(QChar(
'['));
1670 qsizetype end = comment.indexOf(QChar(
']'), ++tag);
1672 fn->setTag(comment.mid(tag, end - tag));
1677 processFunction(fn, cursor);
1679 if (kind == CXCursor_FunctionTemplate) {
1681 fn->setTemplateDecl(get_template_declaration(template_declaration));
1684 if (!clang_Location_isInSystemHeader(loc))
1685 autoGenerateSmfDoc(fn, parent_->name());
1687 return CXChildVisit_Continue;
1689#if CINDEX_VERSION
>= 36
1690 case CXCursor_FriendDecl: {
1694 case CXCursor_EnumDecl: {
1695 auto *en =
static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1696 if (en && en->items().size())
1697 return CXChildVisit_Continue;
1699 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1701 if (clang_Cursor_isAnonymous(cursor)) {
1702 enumTypeName =
"anonymous";
1708 Node *n = parent_->findNonfunctionChild(enumTypeName, &
Node::isEnumType);
1710 en =
static_cast<EnumNode *>(n);
1714 en =
new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1717 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1721 visitChildrenLambda(cursor, [&](CXCursor cur) {
1722 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1723 return CXChildVisit_Continue;
1726 visitChildrenLambda(cur, [&](CXCursor cur) {
1727 if (clang_isExpression(clang_getCursorKind(cur))) {
1728 value = getSpelling(clang_getCursorExtent(cur));
1729 return CXChildVisit_Break;
1731 return CXChildVisit_Continue;
1733 if (value.isEmpty()) {
1734 QLatin1String hex(
"0x");
1735 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1736 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1738 value = QString::number(clang_getEnumConstantDeclValue(cur));
1742 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1743 return CXChildVisit_Continue;
1745 return CXChildVisit_Continue;
1747 case CXCursor_FieldDecl:
1748 case CXCursor_VarDecl: {
1750 return CXChildVisit_Continue;
1752 auto value_declaration =
1754 assert(value_declaration);
1757 auto var =
new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1759 var->setAccess(access);
1761 var->setLeftType(QString::fromStdString(get_fully_qualified_type_name(
1762 value_declaration->getType(),
1763 value_declaration->getASTContext()
1765 var->setStatic(kind == CXCursor_VarDecl && parent_
->isClassNode());
1767 return CXChildVisit_Continue;
1769 case CXCursor_TypedefDecl: {
1771 return CXChildVisit_Continue;
1772 auto *td =
new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1776 visitChildrenLambda(cursor, [&](CXCursor cur) {
1777 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1778 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String(
"QFlags"))
1779 return CXChildVisit_Continue;
1781 visitChildrenLambda(cursor, [&](CXCursor cur) {
1782 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1783 return CXChildVisit_Continue;
1786 if (en && en->isEnumType())
1787 static_cast<EnumNode *>(en)->setFlagsType(td);
1788 return CXChildVisit_Break;
1790 return CXChildVisit_Break;
1792 return CXChildVisit_Continue;
1798 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1801 return CXChildVisit_Continue;
1810 visitChildrenLambda(cursor, [&](CXCursor cur) {
1811 auto kind = clang_getCursorKind(cur);
1812 if (kind == CXCursor_AnnotateAttr) {
1813 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1814 if (annotation == QLatin1String(
"qt_slot")) {
1816 }
else if (annotation == QLatin1String(
"qt_signal")) {
1819 if (annotation == QLatin1String(
"qt_invokable"))
1821 }
else if (kind == CXCursor_CXXOverrideAttr) {
1823 }
else if (kind == CXCursor_ParmDecl) {
1825 return CXChildVisit_Break;
1827 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1828 parameters
[i
].setName(name);
1831 Q_ASSERT(parameter_declaration);
1835 if (!default_value.empty())
1836 parameters
[i
].setDefaultValue(QString::fromStdString(default_value));
1840 return CXChildVisit_Continue;
1846 CXCursorKind kind = clang_getCursorKind(cursor);
1847 CXType funcType = clang_getCursorType(cursor);
1854 : clang_CXXMethod_isPureVirtual(cursor)
1871 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1873 if (kind == CXCursor_Constructor
1875 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1877 else if (kind == CXCursor_Destructor)
1879 else if (kind != CXCursor_ConversionFunction)
1880 fn->setReturnType(QString::fromStdString(get_fully_qualified_type_name(
1881 function_declaration->getReturnType(),
1882 function_declaration->getASTContext()
1885 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<
const clang::CXXConstructorDecl>(function_declaration);
1890 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<
const clang::CXXConversionDecl>(function_declaration);
1896 (constructor_declaration && constructor_declaration->isExplicit()) ||
1897 (conversion_declaration && conversion_declaration->isExplicit())
1900 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<
const clang::CXXMethodDecl>(function_declaration);
1905 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1906 const clang::FunctionProtoType* function_prototype =
static_cast<
const clang::FunctionProtoType*>(function_type);
1908 if (function_prototype) {
1909 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1911 if (exception_specification.Type !=
clang::ExceptionSpecificationType::EST_None) {
1912 const std::string exception_specification_spelling =
1913 exception_specification.NoexceptExpr ? get_expression_as_string(
1914 exception_specification.NoexceptExpr,
1915 function_declaration->getASTContext()
1918 if (exception_specification_spelling !=
"false")
1919 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1928 QStringList referenced_concepts;
1929#if LIBCLANG_VERSION_MAJOR >= 21
1930 if (
const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1931 trailing_requires.ConstraintExpr) {
1932 QString requires_str = QString::fromStdString(
1933 get_expression_as_string(trailing_requires.ConstraintExpr,
1934 function_declaration->getASTContext()));
1935 fn->setTrailingRequiresClause(requires_str.simplified());
1936 std::vector<std::string> refs;
1937 collect_concept_references(trailing_requires.ConstraintExpr, refs);
1938 for (
const auto &ref : refs)
1939 referenced_concepts << QString::fromStdString(ref);
1942 if (
const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1943 QString requires_str = QString::fromStdString(
1944 get_expression_as_string(trailing_requires,
1945 function_declaration->getASTContext()));
1946 fn->setTrailingRequiresClause(requires_str.simplified());
1947 std::vector<std::string> refs;
1948 collect_concept_references(trailing_requires, refs);
1949 for (
const auto &ref : refs)
1950 referenced_concepts << QString::fromStdString(ref);
1954 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1955 if (refQualKind == CXRefQualifier_LValue)
1957 else if (refQualKind == CXRefQualifier_RValue)
1966 parameters
.reserve(function_declaration->getNumParams()
);
1968 for (clang::ParmVarDecl*
const parameter_declaration : function_declaration->parameters()) {
1969 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1971 parameters.append(QString::fromStdString(get_fully_qualified_type_name(
1973 parameter_declaration->getASTContext()
1976 if (!parameter_type.isCanonical())
1977 parameters.last().setCanonicalType(QString::fromStdString(get_fully_qualified_type_name(
1978 parameter_type.getCanonicalType(),
1979 parameter_declaration->getASTContext()
1983 if (
const clang::AutoType *auto_type = parameter_type->getContainedAutoType()) {
1984 if (auto_type->isConstrained()) {
1985 if (
const clang::NamedDecl *concept_decl =
1986 auto_type->getTypeConstraintConcept()) {
1987 referenced_concepts << QString::fromStdString(
1988 concept_decl->getQualifiedNameAsString());
1994 if (!referenced_concepts.isEmpty()) {
1995 referenced_concepts.sort();
1996 referenced_concepts.removeDuplicates();
1997 fn->setReferencedConcepts(
std::move(referenced_concepts));
2001 if (parameters
.last().type().endsWith(QLatin1String(
"QPrivateSignal"))) {
2007 if (clang_isFunctionTypeVariadic(funcType))
2008 parameters.append(QStringLiteral(
"..."));
2009 readParameterNamesAndAttributes(fn, cursor);
2011 if (declaration && declaration->getFriendObjectKind() !=
clang::Decl::FOK_None) {
2013 Q_ASSERT(function_declaration);
2015 const bool hasNamespaceScopeRedeclaration =
2016 std::any_of(function_declaration->redecls_begin(),
2017 function_declaration->redecls_end(),
2018 [](
const clang::FunctionDecl *r) {
2019 return r->getFriendObjectKind() == clang::Decl::FOK_None;
2021 if (!hasNamespaceScopeRedeclaration)
2028 if (!spelling.startsWith(QLatin1String(
"Q_PROPERTY"))
2029 && !spelling.startsWith(QLatin1String(
"QDOC_PROPERTY"))
2030 && !spelling.startsWith(QLatin1String(
"Q_OVERRIDE")))
2033 qsizetype lpIdx = spelling.indexOf(QChar(
'('));
2034 qsizetype rpIdx = spelling.lastIndexOf(QChar(
')'));
2035 if (lpIdx <= 0 || rpIdx <= lpIdx)
2038 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
2039 signature = signature.simplified();
2040 QStringList parts = signature.split(QChar(
' '), Qt::SkipEmptyParts);
2042 static const QStringList attrs =
2043 QStringList() <<
"READ" <<
"MEMBER" <<
"WRITE"
2044 <<
"NOTIFY" <<
"CONSTANT" <<
"FINAL"
2045 <<
"REQUIRED" <<
"BINDABLE" <<
"DESIGNABLE"
2046 <<
"RESET" <<
"REVISION" <<
"SCRIPTABLE"
2047 <<
"STORED" <<
"USER";
2051 auto it =
std::find_if(parts.cbegin(), parts.cend(),
2052 [](
const QString &attr) ->
bool {
2053 return attrs.contains(attr);
2056 if (it == parts.cend() ||
std::distance(parts.cbegin(), it) < 2)
2059 QStringList typeParts;
2060 std::copy(parts.cbegin(), it,
std::back_inserter(typeParts));
2061 parts.erase(parts.cbegin(), it);
2062 QString name = typeParts.takeLast();
2065 while (!name.isEmpty() && name.front() == QChar(
'*')) {
2066 typeParts.last().push_back(name.front());
2071 if (parts.size() < 2 || name.isEmpty())
2075 property->setAccess(Access::Public);
2076 property->setLocation(loc);
2077 property->setDataType(typeParts.join(QChar(
' ')));
2080 while (i < parts.size()) {
2081 const QString &key = parts.at(i++);
2083 if (key ==
"CONSTANT") {
2084 property->setConstant();
2085 }
else if (key ==
"REQUIRED") {
2086 property->setRequired();
2088 if (i < parts.size()) {
2089 QString value = parts.at(i++);
2090 if (key ==
"READ") {
2092 }
else if (key ==
"WRITE") {
2094 property->setWritable(
true);
2095 }
else if (key ==
"MEMBER") {
2096 property->setWritable(
true);
2097 }
else if (key ==
"STORED") {
2098 property->setStored(value.toLower() ==
"true");
2099 }
else if (key ==
"BINDABLE") {
2102 }
else if (key ==
"RESET") {
2104 }
else if (key ==
"NOTIFY") {
2113
2114
2115
2116
2117
2121 clang_getPresumedLocation(loc,
nullptr, &docloc.line, &docloc.column);
2122 auto decl_it = declMap_.upperBound(docloc);
2123 if (decl_it == declMap_.end())
2126 unsigned int declLine = decl_it.key().line;
2127 unsigned int nextCommentLine;
2128 clang_getPresumedLocation(nextCommentLoc,
nullptr, &nextCommentLine,
nullptr);
2129 if (nextCommentLine < declLine)
2133 if (decl_it != declMap_.begin()) {
2134 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(
std::prev(decl_it))));
2135 unsigned int prevDeclLine;
2136 clang_getPresumedLocation(prevDeclEnd,
nullptr, &prevDeclLine,
nullptr);
2137 if (prevDeclLine >= docloc.line) {
2140 auto parent = clang_getCursorLexicalParent(*decl_it);
2141 if (!clang_equalCursors(parent, *(
std::prev(decl_it))))
2145 auto *node = findNodeForCursor(qdb_, *decl_it);
2147 if (node && node->isFunction(Genus::CPP))
2148 readParameterNamesAndAttributes(
static_cast<
FunctionNode *>(node), *decl_it);
2155 const std::vector<QByteArray>& include_paths,
2156 const QList<QByteArray>& defines,
2163 m_allHeaders = config.getHeaderFiles();
2164 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
2172 "-fms-compatibility-version=19",
2176 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
2177 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
2178 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
2179 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
2180 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
2181 "-Wno-constant-logical-operand",
2182 "-Wno-macro-redefined",
2183 "-Wno-nullability-completeness",
2184 "-fvisibility=default",
2191 std::vector<
const char *> pointers;
2192 pointers.reserve(args.size());
2193 for (
const auto &arg : args)
2194 pointers.push_back(arg.constData());
2199
2200
2201
2206 args.emplace_back(arg);
2209 for (
const auto &p : std::as_const(defines))
2214
2215
2217 const std::vector<QByteArray>& include_paths,
2218 std::vector<QByteArray>& args
2220 if (include_paths.empty()) {
2221 qCWarning(lcQdoc) <<
"No include paths provided."
2222 <<
"Set 'includepaths' in the qdocconf file"
2223 <<
"or pass -I flags on the command line."
2224 <<
"C++ parsing may produce incomplete results.";
2226 args.insert(args.end(), include_paths.begin(), include_paths.end());
2231
2232
2233
2234
2237 QString module_header,
2238 const std::set<Config::HeaderFilePath>& all_headers,
2239 const std::vector<QByteArray>& include_paths,
2240 const QList<QByteArray>& defines,
2241 const InclusionPolicy& policy
2243 static std::vector<QByteArray> arguments{};
2245 if (module_header.isEmpty())
return std::nullopt;
2247 getDefaultArgs(defines, arguments);
2248 getMoreArgs(include_paths, arguments);
2250 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2251 | CXTranslationUnit_SkipFunctionBodies
2252 | CXTranslationUnit_KeepGoing);
2256 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String(
"/qdoc_pch")};
2257 if (!pch_directory.isValid())
return std::nullopt;
2259 const QByteArray module = module_header.toUtf8();
2262 qCDebug(lcQdoc) <<
"Build and visit PCH for" << module_header;
2265 struct FindPredicate
2267 enum SearchType { Any, Module };
2268 QByteArray &candidate_;
2269 const QByteArray &module_;
2271 FindPredicate(QByteArray &candidate,
const QByteArray &module,
2272 SearchType type = Any)
2273 : candidate_(candidate), module_(module), type_(type)
2277 bool operator()(
const QByteArray &p)
const
2279 if (type_ != Any && !p.endsWith(module_))
2281 candidate_ = p +
"/";
2282 candidate_.append(module_);
2283 if (p.startsWith(
"-I"))
2284 candidate_ = candidate_.mid(2);
2285 return QFile::exists(QString::fromUtf8(candidate_));
2290 QByteArray candidate;
2291 auto it =
std::find_if(include_paths.begin(), include_paths.end(),
2292 FindPredicate(candidate, module, FindPredicate::Module));
2293 if (it == include_paths.end())
2294 it =
std::find_if(include_paths.begin(), include_paths.end(),
2295 FindPredicate(candidate, module, FindPredicate::Any));
2296 if (it != include_paths.end())
2297 header =
std::move(candidate);
2299 if (header.isEmpty()) {
2300 qWarning() <<
"(qdoc) Could not find the module header in include paths for module"
2301 << module <<
" (include paths: " << include_paths <<
")";
2302 qWarning() <<
" Artificial module header built from header dirs in qdocconf "
2305 arguments.push_back(
"-xc++");
2309 QString tmpHeader = pch_directory.path() +
"/" + module;
2310 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
2311 QTextStream out(&tmpHeaderFile);
2312 if (header.isEmpty()) {
2313 for (
const auto& [header_path, header_name] : all_headers) {
2314 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2317 if (header_name.endsWith(
"_p.h"_L1))
2318 shouldInclude = shouldInclude && policy.showInternal;
2320 if (shouldInclude) {
2321 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2325 QFileInfo headerFile(header);
2326 if (!headerFile.exists()) {
2327 qWarning() <<
"Could not find module header file" << header;
2328 return std::nullopt;
2331 out <<
"#include \"" << header <<
"\"\n";
2333 if (policy.showInternal) {
2334 for (
const auto& [header_path, header_name] : all_headers) {
2335 bool shouldInclude = !header_name.startsWith(
"moc_"_L1);
2336 if (header_name.endsWith(
"_p.h"_L1) && shouldInclude)
2337 out <<
"#include \"" << header_path <<
"/" << header_name <<
"\"\n";
2343 const auto argPointers = toConstCharPointers(arguments);
2344 const QByteArray tmpHeaderLocal = tmpHeader.toLatin1();
2346 clang_parseTranslationUnit2(index, tmpHeaderLocal.constData(), argPointers.data(),
2347 static_cast<
int>(argPointers.size()),
nullptr, 0,
2348 flags_ | CXTranslationUnit_ForSerialization, &tu
.tu);
2349 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << tmpHeader << arguments
2350 <<
") returns" << err;
2355 qCCritical(lcQdoc) <<
"Could not create PCH file for " << module_header;
2356 return std::nullopt;
2359 QByteArray pch_name = pch_directory.path().toUtf8() +
"/" + module +
".pch";
2360 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
2361 clang_defaultSaveOptions(tu));
2363 qCCritical(lcQdoc) <<
"Could not save PCH file for" << module_header;
2364 return std::nullopt;
2369 CXCursor cur = clang_getTranslationUnitCursor(tu);
2370 auto &config = Config::instance();
2371 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
2373 qCDebug(lcQdoc) <<
"PCH built and visited for" << module_header;
2375 return std::make_optional(
PCHFile{
std::move(pch_directory),
std::move(pch_name)});
2380 if (t.count(QChar(
'.')) > 1)
2381 t.truncate(t.lastIndexOf(QChar(
'.')));
2386
2387
2388
2389
2390
2391
2392
2393
2396 flags_ =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2397 | CXTranslationUnit_SkipFunctionBodies
2398 | CXTranslationUnit_KeepGoing);
2402 getDefaultArgs(m_defines, m_args);
2403 if (m_pch && !filePath.endsWith(
".mm")
2404 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2405 m_args.push_back(
"-w");
2406 m_args.push_back(
"-include-pch");
2407 m_args.push_back((*m_pch).get().name);
2409 getMoreArgs(m_includePaths, m_args);
2412 const auto argPointers = toConstCharPointers(m_args);
2413 const QByteArray filePathLocal = filePath.toLocal8Bit();
2415 clang_parseTranslationUnit2(index, filePathLocal.constData(), argPointers.data(),
2416 static_cast<
int>(argPointers.size()),
nullptr, 0,
flags_, &tu
.tu);
2417 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << filePath << m_args
2418 <<
") returns" << err;
2422 qWarning() <<
"(qdoc) Could not parse source file" << filePath <<
" error code:" << err;
2426 ParsedCppFileIR parse_result{};
2428 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2429 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2433 unsigned int numTokens = 0;
2434 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2435 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2437 for (
unsigned int i = 0; i < numTokens; ++i) {
2438 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2440 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2441 if (!comment.startsWith(
"/*!"))
2444 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2447 Doc::trimCStyleComment(loc, comment);
2450 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2456 if (i + 1 < numTokens) {
2458 CXSourceLocation nextCommentLoc = commentLoc;
2459 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2461 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2468 bool future =
false;
2470 QString sinceVersion = doc.metaCommandArgs(
COMMAND_SINCE).at(0).first;
2471 if (getUnpatchedVersion(
std::move(sinceVersion)) >
2472 getUnpatchedVersion(Config::instance().get(
CONFIG_VERSION).asString()))
2477 QStringLiteral(
"Cannot tie this documentation to anything"),
2478 QStringLiteral(
"qdoc found a /*! ... */ comment, but there was no "
2479 "topic command (e.g., '\\%1', '\\%2') in the "
2480 "comment and qdoc could not associate the "
2481 "declaration or definition following the "
2482 "comment with a documented entity.")
2489 CXCursor cur = clang_getCursor(tu, commentLoc);
2491 CXCursorKind kind = clang_getCursorKind(cur);
2492 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2494 if (kind == CXCursor_Namespace) {
2495 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2497 cur = clang_getCursorLexicalParent(cur);
2502 clang_disposeTokens(tu, tokens, numTokens);
2503 m_namespaceScope.clear();
2506 return parse_result;
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2522 const QString &idTag, QStringList context)
2524 Node *fnNode =
nullptr;
2526
2527
2528
2529
2530
2531 if (!idTag.isEmpty()) {
2532 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2535 QStringLiteral(
"tag \\fn [%1] not used in any include file in current module").arg(idTag));
2538
2539
2540
2541
2542 auto *fn =
static_cast<FunctionNode *>(fnNode);
2543 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split(
'(');
2544 if (leftParenSplit.size() > 1) {
2545 QStringList rightParenSplit = leftParenSplit[1].split(
')');
2546 if (!rightParenSplit.empty()) {
2547 QString params = rightParenSplit[0];
2548 if (!params.isEmpty()) {
2549 QStringList commaSplit = params.split(
',');
2551 if (parameters
.count() == commaSplit.size()) {
2552 for (
int i = 0; i < parameters
.count(); ++i) {
2553 QStringList blankSplit = commaSplit[i].split(
' ', Qt::SkipEmptyParts);
2554 if (blankSplit.size() > 1) {
2555 QString pName = blankSplit.last();
2557 auto it =
std::find_if(
std::begin(pName),
std::end(pName),
2558 [](
const QChar &c) {
return c.isLetter(); });
2559 parameters
[i
].setName(
2560 pName.remove(0,
std::distance(
std::begin(pName), it)));
2570 auto flags =
static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2571 | CXTranslationUnit_SkipFunctionBodies
2572 | CXTranslationUnit_KeepGoing);
2576 getDefaultArgs(m_defines, m_args);
2579 m_args.push_back(
"-w");
2580 m_args.push_back(
"-include-pch");
2581 m_args.push_back((*m_pch).get().name);
2586 for (
const auto &ns : std::as_const(context))
2587 s_fn.prepend(
"namespace " + ns.toUtf8() +
" {");
2588 s_fn += fnSignature.toUtf8();
2589 if (!s_fn.endsWith(
";"))
2591 s_fn.append(context.size(),
'}');
2594 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2595 static_cast<
unsigned long>(s_fn.size()) };
2596 const auto argPointers = toConstCharPointers(m_args);
2597 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, argPointers.data(),
2598 int(argPointers.size()), &unsavedFile, 1, flags, &tu
.tu);
2599 qCDebug(lcQdoc) <<
__FUNCTION__ <<
"clang_parseTranslationUnit2(" << dummyFileName << m_args
2600 <<
") returns" << err;
2603 location.error(QStringLiteral(
"clang could not parse \\fn %1").arg(fnSignature));
2607
2608
2609
2610
2611
2612 CXCursor cur = clang_getTranslationUnitCursor(tu);
2613 auto &config = Config::instance();
2614 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2615 bool ignoreSignature =
false;
2619 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2620 const auto &config = Config::instance();
2621 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2622 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 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()