1// Copyright (C) 2022 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 "qmltccompilerpieces.h"
5
6#include <private/qqmljsutils_p.h>
7
8#include <tuple>
9
10QT_BEGIN_NAMESPACE
11
12using namespace Qt::StringLiterals;
13
14static QString scopeName(const QQmlJSScope::ConstPtr &scope)
15{
16 Q_ASSERT(scope->isFullyResolved());
17 const auto scopeType = scope->scopeType();
18 if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope
19 || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) {
20 return scope->baseType()->internalName();
21 }
22 return scope->internalName();
23}
24
25QmltcCodeGenerator::PreparedValue
26QmltcCodeGenerator::wrap_extensionType(const QQmlJSScope::ConstPtr &type,
27 const QQmlJSMetaProperty &p, const QString &accessor)
28{
29 Q_ASSERT(type->isFullyResolved());
30
31 QStringList prologue;
32 QString value = accessor;
33 QStringList epilogue;
34
35 auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(self: type, name: p.propertyName());
36 Q_ASSERT(owner);
37 Q_ASSERT(owner->isFullyResolved());
38
39 // properties are only visible when we use QML_{NAMESPACE_}EXTENDED
40 if (ownerKind == QQmlJSScope::ExtensionType) {
41 // extensions is a C++-only feature:
42 Q_ASSERT(!owner->isComposite());
43
44 // have to wrap the property into an extension, but we need to figure
45 // out whether the type is QObject-based or not
46 prologue << u"{"_s;
47 const QString extensionObjectName = u"extObject"_s;
48 if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
49 // we have a Q_OBJECT. in this case, we call qmlExtendedObject()
50 // function that should return us the extension object. for that we
51 // have to figure out which specific extension we want here
52
53 int extensionIndex = 0;
54 auto cppBase = QQmlJSScope::nonCompositeBaseType(type);
55 for (auto t = cppBase; t; t = t->baseType()) {
56 if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) {
57 if (ext->isSameType(otherScope: owner))
58 break;
59 ++extensionIndex;
60 }
61 }
62
63 prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg(args: u"QObject"_s,
64 args: scopeName(scope: type));
65 prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s
66 .arg(args: extensionObjectName, args: owner->internalName(), args: accessor,
67 args: QString::number(extensionIndex));
68 } else {
69 // we have a Q_GADGET. the assumption for extension types is that we
70 // can reinterpret_cast a Q_GADGET object into an extension type
71 // object and then interact with the extension object right away
72 prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s.arg(args: scopeName(scope: type),
73 args: owner->internalName());
74 prologue << u"static_assert(alignof(%1) == alignof(%2));"_s.arg(args: scopeName(scope: type),
75 args: owner->internalName());
76 prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s.arg(
77 args: extensionObjectName, args: owner->internalName(), args: accessor);
78 }
79 prologue << u"Q_ASSERT(%1);"_s.arg(a: extensionObjectName);
80 value = extensionObjectName;
81 epilogue << u"}"_s;
82 }
83
84 return { .prologue: prologue, .value: value, .epilogue: epilogue };
85}
86
87void QmltcCodeGenerator::generate_assignToListProperty(
88 QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p,
89 const QStringList &values, const QString &accessor, QString &qmlListVarName)
90{
91 Q_UNUSED(type); // might be needed
92 const bool populateLocalListProperty = qmlListVarName.isEmpty();
93
94 if (populateLocalListProperty) {
95 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
96 QmltcCodeGenerator::wrap_extensionType(
97 type, p, accessor: QmltcCodeGenerator::wrap_privateClass(accessor, p));
98
99 qmlListVarName = u"listprop_%1"_s.arg(a: p.propertyName());
100 QQmlJSScope::ConstPtr valueType = p.type()->valueType();
101 *block << u"QQmlListProperty<%1> %2;"_s.arg(args: valueType->internalName(), args&: qmlListVarName);
102 *block << extensionPrologue;
103 *block << u"%1 = %2->%3();"_s.arg(args&: qmlListVarName, args&: extensionAccessor, args: p.read());
104 *block << extensionEpilogue;
105 }
106 for (const QString &value : values) {
107 auto [prologue, wrappedValue, epilogue] =
108 QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value);
109 *block << prologue;
110 *block << u"%1.append(std::addressof(%1), %2);"_s.arg(args&: qmlListVarName, args&: wrappedValue);
111 *block << epilogue;
112 }
113}
114
115void QmltcCodeGenerator::generate_assignToProperty(QStringList *block,
116 const QQmlJSScope::ConstPtr &type,
117 const QQmlJSMetaProperty &p,
118 const QString &value, const QString &accessor,
119 bool constructFromQObject)
120{
121 Q_ASSERT(block);
122 Q_ASSERT(p.isValid());
123 Q_ASSERT(!p.isList()); // NB: this code does not handle list properties
124
125 const QString propertyName = p.propertyName();
126
127 if (type->hasOwnProperty(name: p.propertyName()) && !p.isAlias()) {
128 Q_ASSERT(!p.isPrivate());
129 // this object is compiled, so just assignment should work fine
130 auto [prologue, wrappedValue, epilogue] =
131 QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value);
132 *block += prologue;
133 *block << u"%1->m_%2 = %3;"_s.arg(args: accessor, args: propertyName, args&: wrappedValue);
134 *block += epilogue;
135 } else if (QString propertySetter = p.write(); !propertySetter.isEmpty()
136 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
137 p, accessor: QQmlJSUtils::PropertyAccessor_Write)) {
138 // there's a WRITE function
139 auto [prologue, wrappedValue, epilogue] =
140 QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value);
141 *block += prologue;
142
143 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
144 QmltcCodeGenerator::wrap_extensionType(
145 type, p, accessor: QmltcCodeGenerator::wrap_privateClass(accessor, p));
146 *block += extensionPrologue;
147 *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");";
148 *block += extensionEpilogue;
149
150 *block += epilogue;
151 } else {
152 // this property is weird, fallback to `setProperty`
153 *block << u"{ // couldn't find property setter, so using QObject::setProperty()"_s;
154 QString val = value;
155 if (constructFromQObject) {
156 const QString variantName = u"var_" + propertyName;
157 *block << u"QVariant " + variantName + u";";
158 *block << variantName + u".setValue(" + val + u");";
159 val = u"std::move(" + variantName + u")";
160 }
161 // NB: setProperty() would handle private properties
162 *block << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");";
163 *block << u"}"_s;
164 }
165}
166
167void QmltcCodeGenerator::generate_setIdValue(QStringList *block, const QString &context,
168 qsizetype index, const QString &accessor,
169 const QString &idString)
170{
171 Q_ASSERT(index >= 0);
172 *block << u"Q_ASSERT(%1 < %2->numIdValues()); // make sure Id is in bounds"_s.arg(a: index).arg(
173 a: context);
174 *block << u"%1->setIdValue(%2 /* id: %3 */, %4);"_s.arg(args: context, args: QString::number(index),
175 args: idString, args: accessor);
176}
177
178void QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
179 QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index,
180 const QString &accessor, const QString &returnType, const QList<QmltcVariable> &parameters)
181{
182 *block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));";
183
184 const QString returnValueName = u"_ret"_s;
185 QStringList args;
186 args.reserve(asize: parameters.size() + 1);
187 QStringList types;
188 types.reserve(asize: parameters.size() + 1);
189 if (returnType == u"void"_s) {
190 args << u"nullptr"_s;
191 types << u"QMetaType::fromType<void>()"_s;
192 } else {
193 *block << returnType + u" " + returnValueName + u"{};"; // TYPE _ret{};
194 args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof("
195 + returnValueName + u")))";
196 types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()";
197 }
198
199 for (const QmltcVariable &p : parameters) {
200 args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + p.name
201 + u")))";
202 types << u"QMetaType::fromType<std::decay_t<" + p.cppType + u">>()";
203 }
204
205 *block << u"void *_a[] = { " + args.join(sep: u", "_s) + u" };";
206 *block << u"QMetaType _t[] = { " + types.join(sep: u", "_s) + u" };";
207 const qsizetype runtimeIndex = static_cast<qsizetype>(index);
208 Q_ASSERT(runtimeIndex >= 0);
209 *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", "
210 + accessor + u", " + QString::number(parameters.size()) + u", _a, _t);";
211 if (returnType != u"void"_s)
212 *block << u"return " + returnValueName + u";";
213}
214
215void QmltcCodeGenerator::generate_createBindingOnProperty(
216 QStringList *block, const QString &unitVarName, const QString &scope,
217 qsizetype functionIndex, const QString &target, const QQmlJSScope::ConstPtr &targetType,
218 int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex,
219 const QString &subTarget)
220{
221 const QString propName = QQmlJSUtils::toLiteral(s: p.propertyName());
222 if (QString bindable = p.bindable(); !bindable.isEmpty()) {
223 // TODO: test that private properties are bindable
224 QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::"
225 u"createBindingForBindable("
226 + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", "
227 + target + u", " + QString::number(propertyIndex) + u", "
228 + QString::number(valueTypeIndex) + u", " + propName + u")";
229 const QString accessor = (valueTypeIndex == -1) ? target : subTarget;
230
231 QStringList prologue;
232 QString value = QmltcCodeGenerator::wrap_privateClass(accessor, p);
233 QStringList epilogue;
234 if (targetType) {
235 auto [pro, v, epi] = QmltcCodeGenerator::wrap_extensionType(type: targetType, p, accessor: value);
236 std::tie(args&: prologue, args&: value, args&: epilogue) = std::make_tuple(args&: pro, args&: v, args&: epi);
237 }
238
239 *block += prologue;
240 *block << value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");";
241 *block += epilogue;
242 } else {
243 QString createBindingForNonBindable =
244 u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName
245 + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", "
246 + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", "
247 + propName + u")";
248 // Note: in this version, the binding is set implicitly
249 *block << createBindingForNonBindable + u";";
250 }
251}
252
253void QmltcCodeGenerator::generate_createTranslationBindingOnProperty(
254 QStringList *block, const TranslationBindingInfo &info)
255{
256 const QString propName = QQmlJSUtils::toLiteral(s: info.property.propertyName());
257 const QString qqmlTranslation = info.data.serializeForQmltc();
258
259 if (QString bindable = info.property.bindable(); !bindable.isEmpty()) {
260 // TODO: test that private properties are bindable
261 QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding)
262 ::createTranslationBindingForBindable(%1, %2, %3, %4, %5))"_s
263 .arg(args: info.unitVarName, args: info.target)
264 .arg(a: info.propertyIndex)
265 .arg(args: qqmlTranslation, args: propName);
266
267 *block << QmltcCodeGenerator::wrap_privateClass(accessor: info.target, p: info.property) + u"->"
268 + bindable + u"().setBinding(" + createTranslationCode + u");";
269 } else {
270 QString locationString =
271 u"QQmlSourceLocation(%1->fileName(), %2, %3)"_s.arg(a: info.unitVarName)
272 .arg(a: info.line)
273 .arg(a: info.column);
274 QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding)
275 ::createTranslationBindingForNonBindable(
276 %1, //unit
277 %2, //location
278 %3, //translationData
279 %4, //thisObject
280 %5, //bindingTarget
281 %6, //metaPropertyIndex
282 %7, //propertyName
283 %8) //valueTypePropertyIndex
284 )"_s.arg(args: info.unitVarName, args&: locationString, args: qqmlTranslation, args: info.scope, args: info.target)
285 .arg(a: info.propertyIndex)
286 .arg(a: propName)
287 .arg(a: info.valueTypeIndex);
288 // Note: in this version, the binding is set implicitly
289 *block << createTranslationCode + u";";
290 }
291}
292
293QmltcCodeGenerator::PreparedValue
294QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value)
295{
296 auto isDerivedFromBuiltin = [](QQmlJSScope::ConstPtr t, const QString &builtin) {
297 for (; t; t = t->baseType()) {
298 if (t->internalName() == builtin)
299 return true;
300 }
301 return false;
302 };
303 QStringList prologue;
304 QStringList epilogue;
305 auto propType = p.type();
306 if (isDerivedFromBuiltin(propType, u"QVariant"_s)) {
307 const QString variantName = u"var_" + p.propertyName();
308 prologue << u"{ // accepts QVariant"_s;
309 prologue << u"QVariant " + variantName + u";";
310 prologue << variantName + u".setValue(" + value + u");";
311 epilogue << u"}"_s;
312 value = u"std::move(" + variantName + u")";
313 } else if (isDerivedFromBuiltin(propType, u"QJSValue"_s)) {
314 const QString jsvalueName = u"jsvalue_" + p.propertyName();
315 prologue << u"{ // accepts QJSValue"_s;
316 // Note: do not assume we have the engine, acquire it from `this`
317 prologue << u"auto e = qmlEngine(this);"_s;
318 prologue << u"QJSValue " + jsvalueName + u" = e->toScriptValue(" + value + u");";
319 epilogue << u"}"_s;
320 value = u"std::move(" + jsvalueName + u")";
321 }
322 return { .prologue: prologue, .value: value, .epilogue: epilogue };
323}
324
325QString QmltcCodeGenerator::wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p)
326{
327 if (!p.isPrivate())
328 return accessor;
329
330 const QString privateType = p.privateClass();
331 return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))";
332}
333
334QString QmltcCodeGenerator::wrap_qOverload(const QList<QmltcVariable> &parameters,
335 const QString &overloaded)
336{
337 QStringList types;
338 types.reserve(asize: parameters.size());
339 for (const QmltcVariable &p : parameters)
340 types.emplaceBack(args: p.cppType);
341 return u"qOverload<" + types.join(sep: u", "_s) + u">(" + overloaded + u")";
342}
343
344QString QmltcCodeGenerator::wrap_addressof(const QString &addressed)
345{
346 return u"std::addressof(" + addressed + u")";
347}
348
349QT_END_NAMESPACE
350

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