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

source code of qtdeclarative/tools/qmltc/qmltccodewriter.cpp