Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltccodewriter.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:critical reason:code-generation
4
6
7#include <QtCore/qfileinfo.h>
8#include <QtCore/qstringbuilder.h>
9#include <QtCore/qstring.h>
10#include <QtCore/qmap.h>
11#include <QtCore/qlist.h>
12
13#include <utility>
14
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19namespace QQmltc {
20
21static QString urlToMacro(const QString &url)
22{
23 QFileInfo fi(url);
24 return u"Q_QMLTC_" + fi.baseName().toUpper();
25}
26
27static QString getFunctionCategory(const MethodBase &method)
28{
29 QString category;
30 switch (method.access) {
31 case QQmlJSMetaMethod::Private:
32 category = u"private"_s;
33 break;
34 case QQmlJSMetaMethod::Protected:
35 category = u"protected"_s;
36 break;
37 case QQmlJSMetaMethod::Public:
38 category = u"public"_s;
39 break;
40 }
41 return category;
42}
43
44static QString getFunctionCategory(const Method &method)
45{
46 QString category = getFunctionCategory(static_cast<const MethodBase &>(method));
47 switch (method.type) {
48 case QQmlJSMetaMethodType::Signal:
49 category = u"Q_SIGNALS"_s;
50 break;
51 case QQmlJSMetaMethodType::Slot:
52 category += u" Q_SLOTS"_s;
53 break;
54 case QQmlJSMetaMethodType::Method:
55 case QQmlJSMetaMethodType::StaticMethod:
56 break;
57 }
58 return category;
59}
60
61static QString appendSpace(const QString &s)
62{
63 if (s.isEmpty())
64 return s;
65 return s + u" ";
66}
67
68static QString prependSpace(const QString &s)
69{
70 if (s.isEmpty())
71 return s;
72 return u" " + s;
73}
74
75static std::pair<QString, QString> functionSignatures(const MethodBase &method)
76{
77 const QString name = method.name;
78 const QList<Variable> &parameterList = method.parameterList;
79
80 QStringList headerParamList;
81 QStringList cppParamList;
82 for (const Variable &variable : parameterList) {
83 const QString commonPart = variable.cppType + u" " + variable.name;
84 cppParamList << commonPart;
85 headerParamList << commonPart;
86 if (!variable.defaultValue.isEmpty())
87 headerParamList.back() += u" = " + variable.defaultValue;
88 }
89
90 const QString headerSignature = name + u"(" + headerParamList.join(u", "_s) + u")"
91 + prependSpace(method.modifiers.join(u" "));
92 const QString cppSignature = name + u"(" + cppParamList.join(u", "_s) + u")"
93 + prependSpace(method.modifiers.join(u" "));
94 return { headerSignature, cppSignature };
95}
96
97static QString functionReturnType(const Method &m)
98{
99 return appendSpace(m.declarationPrefixes.join(u" "_s)) + m.returnType;
100}
101
102void CodeWriter::writeGlobalHeader(OutputWrapper &code, const QString &sourcePath,
103 const QString &hPath, const QString &cppPath,
104 const QString &outNamespace,
105 const QSet<QString> &requiredCppIncludes)
106{
107 Q_UNUSED(cppPath);
108 const QString preamble = u"// This code is auto-generated by the qmltc tool from the file '"
109 + sourcePath + u"'\n// WARNING! All changes made in this file will be lost!\n";
110 code.rawAppendToHeader(preamble);
111 code.rawAppendToCpp(preamble);
112 code.rawAppendToHeader(
113 u"// NOTE: This generated API is to be considered implementation detail.");
114 code.rawAppendToHeader(
115 u"// It may change from version to version and should not be relied upon.");
116
117 const QString headerMacro = urlToMacro(sourcePath);
118 code.rawAppendToHeader(u"#ifndef %1_H"_s.arg(headerMacro));
119 code.rawAppendToHeader(u"#define %1_H"_s.arg(headerMacro));
120
121 code.rawAppendToHeader(u"#include <QtCore/qproperty.h>");
122 code.rawAppendToHeader(u"#include <QtCore/qobject.h>");
123 code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>");
124 code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>");
125 code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>");
126 code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution
127 code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties
128
129 code.rawAppendToHeader(u"#include <private/qqmlengine_p.h>"); // executeRuntimeFunction(), etc.
130 code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); // QmltcSupportLib
131
132 code.rawAppendToHeader(u"#include <QtQml/qqmllist.h>"); // QQmlListProperty
133
134 // include custom C++ includes required by used types
135 code.rawAppendToHeader(u"// BEGIN(custom_cpp_includes)");
136 for (const auto &requiredInclude : requiredCppIncludes)
137 code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\"");
138 code.rawAppendToHeader(u"// END(custom_cpp_includes)");
139
140 code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file
141 code.rawAppendToCpp(u"// qmltc support library:");
142 code.rawAppendToCpp(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib
143 code.rawAppendToCpp(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib
144 code.rawAppendToHeader(u"#include <private/qqmlcpptypehelpers_p.h> "); // QmltcSupportLib
145
146 code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent()
147 code.rawAppendToCpp(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get()
148
149 code.rawAppendToCpp(u"");
150 code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties
151 code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks
152 code.rawAppendToCpp(u"#include <QtQml/qqmlprivate.h>"); // QQmlPrivate::qmlExtendedObject()
153
154 code.rawAppendToCpp(u""); // blank line
155 code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE");
156
157 code.rawAppendToHeader(u""); // blank line
158
159 const QStringList namespaces = outNamespace.split(u"::"_s);
160
161 for (const QString &currentNamespace : namespaces) {
162 code.rawAppendToHeader(u"namespace %1 {"_s.arg(currentNamespace));
163 code.rawAppendToCpp(u"namespace %1 {"_s.arg(currentNamespace));
164 }
165}
166
167void CodeWriter::write(OutputWrapper &code, const PropertyInitializer &propertyInitializer,
168 const Type &wrappedType)
169{
170 code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {");
171
172 {
173 {
174 [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
175
176 code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";");
177 }
178
179 code.rawAppendToHeader(u"public:"_s);
180
181 [[maybe_unused]] OutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name);
182 {
183 [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
184
185 write(code, propertyInitializer.constructor);
186 code.rawAppendToHeader(u""); // blank line
187
188 for (const auto &propertySetter : propertyInitializer.propertySetters) {
189 write(code, propertySetter);
190 }
191 }
192
193 code.rawAppendToHeader(u""); // blank line
194 code.rawAppendToHeader(u"private:"_s);
195
196 {
197 [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
198
199 write(code, propertyInitializer.component);
200 write(code, propertyInitializer.initializedCache);
201 }
202 }
203
204 code.rawAppendToHeader(u"};"_s);
205 code.rawAppendToHeader(u""); // blank line
206}
207
208void CodeWriter::write(OutputWrapper &code, const RequiredPropertiesBundle &requiredPropertiesBundle)
209{
210 code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {");
211
212 {
213 [[maybe_unused]] OutputWrapper::HeaderIndentationScope headerIndent(&code);
214
215 for (const auto &member : requiredPropertiesBundle.members) {
216 write(code, member);
217 }
218 }
219
220 code.rawAppendToHeader(u"};"_s);
221 code.rawAppendToHeader(u""); // blank line
222}
223
224void CodeWriter::writeGlobalFooter(OutputWrapper &code, const QString &sourcePath,
225 const QString &outNamespace)
226{
227 const QStringList namespaces = outNamespace.split(u"::"_s);
228
229 for (auto it = namespaces.crbegin(), end = namespaces.crend(); it != end; it++) {
230 code.rawAppendToCpp(u"} // namespace %1"_s.arg(*it));
231 code.rawAppendToHeader(u"} // namespace %1"_s.arg(*it));
232 }
233
234 code.rawAppendToHeader(u""); // blank line
235 code.rawAppendToHeader(u"#endif // %1_H"_s.arg(urlToMacro(sourcePath)));
236 code.rawAppendToHeader(u""); // blank line
237}
238
239static void writeToFile(const QString &path, const QByteArray &data)
240{
241 // When not using dependency files, changing a single qml invalidates all
242 // qml files and would force the recompilation of everything. To avoid that,
243 // we check if the data is equal to the existing file, if yes, don't touch
244 // it so the build system will not recompile unnecessary things.
245 //
246 // If the build system use dependency file, we should anyway touch the file
247 // so qmltc is not re-run
248 QFileInfo fi(path);
249 if (fi.exists() && fi.size() == data.size()) {
250 QFile oldFile(path);
251 if (oldFile.open(QIODevice::ReadOnly)) {
252 if (oldFile.readAll() == data)
253 return;
254 }
255 }
256 QFile file(path);
257 if (!file.open(QIODevice::WriteOnly))
258 qFatal("Could not open file %s", qPrintable(path));
259 file.write(data);
260}
261
262void CodeWriter::write(OutputWrapper &code, const Program &program)
263{
264 writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace,
265 program.includes);
266
267 // url method comes first
268 writeUrl(code, program.urlMethod);
269
270 // forward declare all the types first
271 for (const Type &type : std::as_const(program.compiledTypes))
272 code.rawAppendToHeader(u"class " + type.cppType + u";");
273 // write all the types and their content
274 for (const Type &type : std::as_const(program.compiledTypes))
275 write(code, type, program.exportMacro);
276
277 // add typeCount definitions. after all types have been written down (so
278 // they are now complete types as per C++). practically, this only concerns
279 // document root type
280 for (const Type &type : std::as_const(program.compiledTypes)) {
281 if (!type.typeCount)
282 continue;
283 code.rawAppendToHeader(u""); // blank line
284 code.rawAppendToHeader(u"constexpr %1 %2::%3()"_s.arg(type.typeCount->returnType,
285 type.cppType, type.typeCount->name));
286 code.rawAppendToHeader(u"{");
287 for (const QString &line : std::as_const(type.typeCount->body))
288 code.rawAppendToHeader(line, 1);
289 code.rawAppendToHeader(u"}");
290 }
291
292 writeGlobalFooter(code, program.url, program.outNamespace);
293
294 writeToFile(program.hPath, code.code().header.toUtf8());
295 writeToFile(program.cppPath, code.code().cpp.toUtf8());
296}
297
298template<typename Predicate>
299static void dumpFunctions(OutputWrapper &code, const QList<Method> &functions, Predicate pred)
300{
301 // functions are _ordered_ by access and kind. ordering is important to
302 // provide consistent output
303 QMap<QString, QList<const Method *>> orderedFunctions;
304 for (const auto &function : functions) {
305 if (pred(function))
306 orderedFunctions[getFunctionCategory(function)].append(std::addressof(function));
307 }
308
309 for (auto it = orderedFunctions.cbegin(); it != orderedFunctions.cend(); ++it) {
310 code.rawAppendToHeader(it.key() + u":", -1);
311 for (const Method *function : std::as_const(it.value()))
312 CodeWriter::write(code, *function);
313 }
314}
315
316void CodeWriter::write(OutputWrapper &code, const Type &type, const QString &exportMacro)
317{
318 const auto constructClassString = [&]() {
319 QString str = u"class "_s;
320 if (!exportMacro.isEmpty())
321 str.append(exportMacro).append(u" "_s);
322 str.append(type.cppType);
323 QStringList nonEmptyBaseClasses;
324 nonEmptyBaseClasses.reserve(type.baseClasses.size());
325 std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(),
326 std::back_inserter(nonEmptyBaseClasses),
327 [](const QString &entry) { return !entry.isEmpty(); });
328 if (!nonEmptyBaseClasses.isEmpty())
329 str += u" : public " + nonEmptyBaseClasses.join(u", public "_s);
330 return str;
331 };
332
333 code.rawAppendToHeader(u""); // blank line
334 code.rawAppendToCpp(u""); // blank line
335
336 code.rawAppendToHeader(constructClassString());
337 code.rawAppendToHeader(u"{");
338 for (const QString &mocLine : std::as_const(type.mocCode))
339 code.rawAppendToHeader(mocLine, 1);
340
341 OutputWrapper::MemberNameScope typeScope(&code, type.cppType);
342 Q_UNUSED(typeScope);
343 {
344 OutputWrapper::HeaderIndentationScope headerIndent(&code);
345 Q_UNUSED(headerIndent);
346
347 // first, write user-visible code, then everything else. someone might
348 // want to look at the generated code, so let's make an effort when
349 // writing it down
350
351 code.rawAppendToHeader(u"/* ----------------- */");
352 code.rawAppendToHeader(u"/* External C++ API */");
353 code.rawAppendToHeader(u"public:", -1);
354
355 if (!type.propertyInitializer.name.isEmpty())
356 write(code, type.propertyInitializer, type);
357
358 if (type.requiredPropertiesBundle)
359 write(code, *type.requiredPropertiesBundle);
360
361 // NB: when non-document root, the externalCtor won't be public - but we
362 // really don't care about the output format of such types
363 if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) {
364 // TODO: ignoreInit must be eliminated
365
366 CodeWriter::write(code, type.externalCtor);
367 if (type.staticCreate)
368 CodeWriter::write(code, *type.staticCreate);
369 }
370
371 // dtor
372 if (type.dtor)
373 CodeWriter::write(code, *type.dtor);
374
375 // enums
376 for (const auto &enumeration : std::as_const(type.enums))
377 CodeWriter::write(code, enumeration);
378
379 // visible functions
380 const auto isUserVisibleFunction = [](const Method &function) {
381 return function.userVisible;
382 };
383 dumpFunctions(code, type.functions, isUserVisibleFunction);
384
385 code.rawAppendToHeader(u"/* ----------------- */");
386 code.rawAppendToHeader(u""); // blank line
387 code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */");
388
389 // below are the hidden parts of the type
390
391 // (rest of the) ctors
392 if (type.ignoreInit) { // TODO: this branch should be eliminated
393 Q_ASSERT(type.baselineCtor.access == QQmlJSMetaMethod::Public);
394 code.rawAppendToHeader(u"public:", -1);
395 CodeWriter::write(code, type.baselineCtor);
396 } else {
397 code.rawAppendToHeader(u"protected:", -1);
398 if (type.externalCtor.access != QQmlJSMetaMethod::Public) {
399 Q_ASSERT(type.externalCtor.access == QQmlJSMetaMethod::Protected);
400 CodeWriter::write(code, type.externalCtor);
401 }
402 CodeWriter::write(code, type.baselineCtor);
403 CodeWriter::write(code, type.init);
404 CodeWriter::write(code, type.endInit);
405 CodeWriter::write(code, type.setComplexBindings);
406 CodeWriter::write(code, type.beginClass);
407 CodeWriter::write(code, type.completeComponent);
408 CodeWriter::write(code, type.finalizeComponent);
409 CodeWriter::write(code, type.handleOnCompleted);
410 }
411
412 // children
413 for (const auto &child : std::as_const(type.children))
414 CodeWriter::write(code, child, exportMacro);
415
416 // (non-visible) functions
417 dumpFunctions(code, type.functions,
418 [&](const auto &m) { return !isUserVisibleFunction(m); });
419
420 // variables and properties
421 if (!type.variables.isEmpty() || !type.properties.isEmpty()) {
422 code.rawAppendToHeader(u""); // blank line
423 code.rawAppendToHeader(u"protected:", -1);
424 }
425 for (const auto &property : std::as_const(type.properties))
426 write(code, property);
427 for (const auto &variable : std::as_const(type.variables))
428 write(code, variable);
429 }
430
431 code.rawAppendToHeader(u"private:", -1);
432 for (const QString &otherLine : std::as_const(type.otherCode))
433 code.rawAppendToHeader(otherLine, 1);
434
435 if (type.typeCount) {
436 // add typeCount declaration, definition is added later
437 code.rawAppendToHeader(u""); // blank line
438 code.rawAppendToHeader(u"protected:");
439 code.rawAppendToHeader(u"constexpr static %1 %2();"_s.arg(type.typeCount->returnType,
440 type.typeCount->name),
441 1);
442 }
443
444 code.rawAppendToHeader(u"};");
445}
446
447void CodeWriter::write(OutputWrapper &code, const Enum &enumeration)
448{
449 code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {");
450 for (qsizetype i = 0; i < enumeration.keys.size(); ++i) {
451 QString str;
452 if (enumeration.values.isEmpty()) {
453 str += enumeration.keys.at(i) + u",";
454 } else {
455 str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u",";
456 }
457 code.rawAppendToHeader(str, 1);
458 }
459 code.rawAppendToHeader(u"};");
460 code.rawAppendToHeader(enumeration.ownMocLine);
461}
462
463void CodeWriter::write(OutputWrapper &code, const Method &method)
464{
465 const auto [hSignature, cppSignature] = functionSignatures(method);
466 // Note: augment return type with preambles in declaration
467 code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod
468 ? u"static " + functionReturnType(method)
469 : functionReturnType(method))
470 + u" " + hSignature + u";");
471
472 // do not generate method implementation if it is a signal
473 const auto methodType = method.type;
474 if (methodType != QQmlJSMetaMethodType::Signal) {
475 code.rawAppendToCpp(u""_s); // blank line
476 if (method.comments.size() > 0) {
477 code.rawAppendToCpp(u"/*! \\internal"_s);
478 for (const auto &comment : method.comments)
479 code.rawAppendToCpp(comment, 1);
480 code.rawAppendToCpp(u"*/"_s);
481 }
482 code.rawAppendToCpp(method.returnType);
483 code.rawAppendSignatureToCpp(cppSignature);
484 code.rawAppendToCpp(u"{");
485 {
486 OutputWrapper::CppIndentationScope cppIndent(&code);
487 Q_UNUSED(cppIndent);
488 for (const QString &line : std::as_const(method.body))
489 code.rawAppendToCpp(line);
490 }
491 code.rawAppendToCpp(u"}");
492 }
493}
494
495template<typename WriteInitialization>
496static void writeSpecialMethod(OutputWrapper &code, const MethodBase &specialMethod,
497 WriteInitialization writeInit)
498{
499 const auto [hSignature, cppSignature] = functionSignatures(specialMethod);
500 code.rawAppendToHeader(hSignature + u";");
501
502 code.rawAppendToCpp(u""); // blank line
503 code.rawAppendSignatureToCpp(cppSignature);
504
505 writeInit(specialMethod);
506
507 code.rawAppendToCpp(u"{");
508 {
509 OutputWrapper::CppIndentationScope cppIndent(&code);
510 Q_UNUSED(cppIndent);
511 for (const QString &line : std::as_const(specialMethod.body))
512 code.rawAppendToCpp(line);
513 }
514 code.rawAppendToCpp(u"}");
515}
516
517void CodeWriter::write(OutputWrapper &code, const Ctor &ctor)
518{
519 const auto writeInitializerList = [&](const MethodBase &ctorBase) {
520 auto ctor = static_cast<const Ctor &>(ctorBase);
521 if (!ctor.initializerList.isEmpty()) {
522 code.rawAppendToCpp(u":", 1);
523 // double \n to make separate initializer list lines stand out more
524 code.rawAppendToCpp(
525 ctor.initializerList.join(u",\n\n" + u" "_s.repeated(code.cppIndent + 1)),
526 1);
527 }
528 };
529
530 writeSpecialMethod(code, ctor, writeInitializerList);
531}
532
533void CodeWriter::write(OutputWrapper &code, const Dtor &dtor)
534{
535 const auto noop = [](const MethodBase &) {};
536 writeSpecialMethod(code, dtor, noop);
537}
538
539void CodeWriter::write(OutputWrapper &code, const Variable &var)
540{
541 const QString optionalPart = var.defaultValue.isEmpty() ? u""_s : u" = " + var.defaultValue;
542 code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";");
543}
544
545void CodeWriter::write(OutputWrapper &code, const Property &prop)
546{
547 Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet (or at all?)
548 code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_s.arg(
549 prop.containingClass, prop.cppType, prop.name, prop.signalName));
550}
551
552void CodeWriter::writeUrl(OutputWrapper &code, const Method &urlMethod)
553{
554 // unlike ordinary methods, url function only exists in .cpp
555 Q_ASSERT(!urlMethod.returnType.isEmpty());
556 const auto [hSignature, _] = functionSignatures(urlMethod);
557 Q_UNUSED(_);
558 // Note: augment return type with preambles in declaration
559 code.rawAppendToCpp(functionReturnType(urlMethod) + u" " + hSignature);
560 code.rawAppendToCpp(u"{");
561 {
562 OutputWrapper::CppIndentationScope cppIndent(&code);
563 Q_UNUSED(cppIndent);
564 for (const QString &line : std::as_const(urlMethod.body))
565 code.rawAppendToCpp(line);
566 }
567 code.rawAppendToCpp(u"}");
568}
569
570} // namespace QQmltc
571
572QT_END_NAMESPACE
static void writeSpecialMethod(OutputWrapper &code, const MethodBase &specialMethod, WriteInitialization writeInit)
static QString appendSpace(const QString &s)
static QString getFunctionCategory(const MethodBase &method)
static QString prependSpace(const QString &s)
static void dumpFunctions(OutputWrapper &code, const QList< Method > &functions, Predicate pred)
static QString functionReturnType(const Method &m)
static std::pair< QString, QString > functionSignatures(const MethodBase &method)
static void writeToFile(const QString &path, const QByteArray &data)
static QString urlToMacro(const QString &url)