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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | using namespace Qt::StringLiterals; |
13 | |
14 | static 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 | |
25 | QmltcCodeGenerator::PreparedValue |
26 | QmltcCodeGenerator::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 | |
87 | void 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 | |
115 | void 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 | |
167 | void 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 | |
178 | void QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
179 | QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index, |
180 | const QString &accessor, const QString &returnType, const QList<QmltcVariable> ¶meters) |
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 | |
215 | void 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 | |
253 | void 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 | |
293 | QmltcCodeGenerator::PreparedValue |
294 | QmltcCodeGenerator::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 | |
325 | QString 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 | |
334 | QString QmltcCodeGenerator::wrap_qOverload(const QList<QmltcVariable> ¶meters, |
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 | |
344 | QString QmltcCodeGenerator::wrap_addressof(const QString &addressed) |
345 | { |
346 | return u"std::addressof(" + addressed + u")" ; |
347 | } |
348 | |
349 | QT_END_NAMESPACE |
350 | |