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 "qmltccompiler.h" |
5 | #include "qmltcoutputir.h" |
6 | #include "qmltccodewriter.h" |
7 | #include "qmltcpropertyutils.h" |
8 | #include "qmltccompilerpieces.h" |
9 | |
10 | #include <QtCore/qloggingcategory.h> |
11 | #include <QtQml/private/qqmlsignalnames_p.h> |
12 | #include <private/qqmljsutils_p.h> |
13 | |
14 | #include <algorithm> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | using namespace Qt::StringLiterals; |
18 | |
19 | bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) |
20 | { |
21 | if (QQmlJSScope::ConstPtr type = p.type()) |
22 | return type->isListProperty(); |
23 | return false; |
24 | } |
25 | |
26 | static QList<QQmlJSMetaProperty> unboundRequiredProperties( |
27 | const QQmlJSScope::ConstPtr &type, |
28 | QmltcTypeResolver *resolver |
29 | ) { |
30 | QList<QQmlJSMetaProperty> requiredProperties{}; |
31 | |
32 | auto isPropertyRequired = [&type, &resolver](const auto &property) { |
33 | if (!type->isPropertyRequired(name: property.propertyName())) |
34 | return false; |
35 | |
36 | if (type->hasPropertyBindings(name: property.propertyName())) |
37 | return false; |
38 | |
39 | if (property.isAlias()) { |
40 | QQmlJSUtils::AliasResolutionVisitor aliasVisitor; |
41 | |
42 | QQmlJSUtils::ResolvedAlias result = |
43 | QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor); |
44 | |
45 | if (result.kind != QQmlJSUtils::AliasTarget_Property) |
46 | return false; |
47 | |
48 | // If the top level alias targets a property that is in |
49 | // the top level scope and that property is required, then |
50 | // we will already pick up the property during one of the |
51 | // iterations. |
52 | // Setting the property or the alias is the same so we |
53 | // discard one of the two, as otherwise we would require |
54 | // the user to pass two values for the same property ,in |
55 | // this case the alias. |
56 | // |
57 | // For example in: |
58 | // |
59 | // ``` |
60 | // Item { |
61 | // id: self |
62 | // required property int foo |
63 | // property alias bar: self.foo |
64 | // } |
65 | // ``` |
66 | // |
67 | // Both foo and bar are required but setting one or the |
68 | // other is the same operation so that we should choose |
69 | // only one. |
70 | if (result.owner == type && |
71 | type->isPropertyRequired(name: result.property.propertyName())) |
72 | return false; |
73 | |
74 | if (result.owner->hasPropertyBindings(name: result.property.propertyName())) |
75 | return false; |
76 | } |
77 | |
78 | return true; |
79 | }; |
80 | |
81 | const auto properties = type->properties(); |
82 | std::copy_if(first: properties.cbegin(), last: properties.cend(), |
83 | result: std::back_inserter(x&: requiredProperties), pred: isPropertyRequired); |
84 | std::sort(first: requiredProperties.begin(), last: requiredProperties.end(), |
85 | comp: [](const auto &left, const auto &right) { |
86 | return left.propertyName() < right.propertyName(); |
87 | }); |
88 | |
89 | return requiredProperties; |
90 | } |
91 | |
92 | |
93 | // Populates the internal representation for a |
94 | // RequiredPropertiesBundle, a class that acts as a bundle of initial |
95 | // values that should be set for the required properties of a type. |
96 | static void compileRequiredPropertiesBundle( |
97 | QmltcType ¤t, |
98 | const QQmlJSScope::ConstPtr &type, |
99 | QmltcTypeResolver *resolver |
100 | ) { |
101 | |
102 | QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver); |
103 | |
104 | if (requiredProperties.isEmpty()) |
105 | return; |
106 | |
107 | current.requiredPropertiesBundle.emplace(); |
108 | current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s; |
109 | |
110 | current.requiredPropertiesBundle->members.reserve(asize: requiredProperties.size()); |
111 | std::transform(first: requiredProperties.cbegin(), last: requiredProperties.cend(), |
112 | result: std::back_inserter(x&: current.requiredPropertiesBundle->members), |
113 | unary_op: [](const QQmlJSMetaProperty &property) { |
114 | QString type = qIsReferenceTypeList(p: property) |
115 | ? u"const QList<%1*>&"_s.arg( |
116 | a: property.type()->valueType()->internalName()) |
117 | : u"passByConstRefOrValue<%1>"_s.arg( |
118 | a: property.type()->augmentedInternalName()); |
119 | return QmltcVariable{ type, property.propertyName() }; |
120 | }); |
121 | } |
122 | |
123 | static void compileRootExternalConstructorBody( |
124 | QmltcType& current, |
125 | const QQmlJSScope::ConstPtr &type |
126 | ) { |
127 | current.externalCtor.body << u"// document root:"_s; |
128 | // if it's document root, we want to create our QQmltcObjectCreationBase |
129 | // that would store all the created objects |
130 | current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg( |
131 | a: type->internalName()); |
132 | current.externalCtor.body |
133 | << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s; |
134 | current.externalCtor.body << u"creator.set(0, this);"_s; // special case |
135 | |
136 | QString initializerName = u"initializer"_s; |
137 | if (current.requiredPropertiesBundle) { |
138 | // Compose new initializer based on the initial values for required properties. |
139 | current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s; |
140 | for (const auto& member : current.requiredPropertiesBundle->members) { |
141 | current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg( |
142 | args: QmltcPropertyData(member.name).write, args: member.name |
143 | ); |
144 | } |
145 | current.externalCtor.body << u" initializer(propertyInitializer);"_s; |
146 | current.externalCtor.body << u"};"_s; |
147 | |
148 | initializerName = u"newInitializer"_s; |
149 | } |
150 | |
151 | // now call init |
152 | current.externalCtor.body << current.init.name |
153 | + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " |
154 | u"endInit */ true, %1);"_s.arg(a: initializerName); |
155 | }; |
156 | |
157 | Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); |
158 | |
159 | const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s; |
160 | const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s; |
161 | |
162 | QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, |
163 | QQmlJSLogger *logger) |
164 | : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger) |
165 | { |
166 | Q_UNUSED(m_typeResolver); |
167 | Q_ASSERT(!hasErrors()); |
168 | } |
169 | |
170 | // needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being |
171 | // incomplete type in the header (~std::unique_ptr<> fails with a static_assert) |
172 | QmltcCompiler::~QmltcCompiler() = default; |
173 | |
174 | QString QmltcCompiler::newSymbol(const QString &base) |
175 | { |
176 | QString symbol = base; |
177 | symbol.replace(before: QLatin1String("."), after: QLatin1String( "_")); |
178 | while (symbol.startsWith(c: QLatin1Char('_')) && symbol.size() >= 2 |
179 | && (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) { |
180 | symbol.remove(i: 0, len: 1); |
181 | } |
182 | if (!m_symbols.contains(key: symbol)) { |
183 | m_symbols.insert(key: symbol, value: 1); |
184 | } else { |
185 | symbol += u"_"+ QString::number(m_symbols[symbol]++); |
186 | } |
187 | return symbol; |
188 | } |
189 | |
190 | void QmltcCompiler::compile(const QmltcCompilerInfo &info) |
191 | { |
192 | m_info = info; |
193 | Q_ASSERT(!m_info.outputCppFile.isEmpty()); |
194 | Q_ASSERT(!m_info.outputHFile.isEmpty()); |
195 | Q_ASSERT(!m_info.resourcePath.isEmpty()); |
196 | |
197 | // Note: we only compile "pure" QML types. any component-wrapped type is |
198 | // expected to appear through a binding |
199 | |
200 | const auto isComponent = [](const QQmlJSScope::ConstPtr &type) { |
201 | auto base = type->baseType(); |
202 | return base && base->internalName() == u"QQmlComponent"_s; |
203 | }; |
204 | |
205 | QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor }; |
206 | |
207 | QmltcMethod urlMethod; |
208 | compileUrlMethod(urlMethod, urlMethodName: generator.urlMethodName()); |
209 | m_urlMethodName = urlMethod.name; |
210 | |
211 | // sort inline components to compile them in the right order |
212 | // a inherits b => b needs to be defined in the cpp file before a! |
213 | // r is the root => r needs to be compiled at the end! |
214 | // otherwise => sort them by inline component names to have consistent output |
215 | auto sortedInlineComponentNames = m_visitor->inlineComponentNames(); |
216 | std::sort(first: sortedInlineComponentNames.begin(), last: sortedInlineComponentNames.end(), |
217 | comp: [&](const InlineComponentOrDocumentRootName &a, |
218 | const InlineComponentOrDocumentRootName &b) { |
219 | const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(ptr: &a); |
220 | const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(ptr: &b); |
221 | |
222 | if (inlineComponentAName == inlineComponentBName) |
223 | return false; |
224 | |
225 | // the root comes at last, so (a < b) == true when b is the root and a is not |
226 | if (inlineComponentAName && !inlineComponentBName) |
227 | return true; |
228 | |
229 | // b requires a to be declared before b when b inherits from a, therefore (a < b) |
230 | // == true |
231 | if (inlineComponentAName && inlineComponentBName) { |
232 | QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(inlineComponentName: a); |
233 | QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(inlineComponentName: b); |
234 | if (inlineComponentB->inherits(base: inlineComponentA)) { |
235 | return true; |
236 | } else if (inlineComponentA->inherits(base: inlineComponentB)) { |
237 | return false; |
238 | } else { |
239 | // fallback to default sorting based on names |
240 | return *inlineComponentAName < *inlineComponentBName; |
241 | } |
242 | } |
243 | Q_ASSERT(!inlineComponentAName || !inlineComponentBName); |
244 | // a is the root or both a and b are the root |
245 | return false; |
246 | }); |
247 | |
248 | QList<QmltcType> compiledTypes; |
249 | for (const auto &inlineComponent : sortedInlineComponentNames) { |
250 | const QList<QQmlJSScope::ConstPtr> &pureTypes = m_visitor->pureQmlTypes(inlineComponent); |
251 | Q_ASSERT(!pureTypes.empty()); |
252 | const QQmlJSScope::ConstPtr &root = pureTypes.front(); |
253 | if (isComponent(root)) { |
254 | compiledTypes.emplaceBack(); // create empty type |
255 | const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
256 | generator.generate_initCodeForTopLevelComponent(current, type); |
257 | }; |
258 | compileType(current&: compiledTypes.back(), type: root, compileElements: compile); |
259 | } else { |
260 | const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
261 | compileTypeElements(current, type); |
262 | }; |
263 | |
264 | for (const auto &type : pureTypes) { |
265 | Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); |
266 | compiledTypes.emplaceBack(); // create empty type |
267 | compileType(current&: compiledTypes.back(), type, compileElements: compile); |
268 | } |
269 | } |
270 | } |
271 | |
272 | if (hasErrors()) |
273 | return; |
274 | |
275 | QmltcProgram program; |
276 | program.url = m_url; |
277 | program.cppPath = m_info.outputCppFile; |
278 | program.hPath = m_info.outputHFile; |
279 | program.outNamespace = m_info.outputNamespace; |
280 | program.exportMacro = m_info.exportMacro; |
281 | program.compiledTypes = compiledTypes; |
282 | program.includes = m_visitor->cppIncludeFiles(); |
283 | if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) |
284 | program.includes += (m_info.exportInclude); |
285 | program.urlMethod = urlMethod; |
286 | |
287 | QmltcOutput out; |
288 | QmltcOutputWrapper code(out); |
289 | QmltcCodeWriter::write(code, program); |
290 | } |
291 | |
292 | void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName) |
293 | { |
294 | urlMethod.name = urlMethodName; |
295 | urlMethod.returnType = u"const QUrl&"_s; |
296 | urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(a: m_info.resourcePath); |
297 | urlMethod.body << u"return url;"_s; |
298 | urlMethod.declarationPrefixes << u"static"_s; |
299 | urlMethod.modifiers << u"noexcept"_s; |
300 | } |
301 | |
302 | void QmltcCompiler::compileType( |
303 | QmltcType ¤t, const QQmlJSScope::ConstPtr &type, |
304 | std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements) |
305 | { |
306 | Q_ASSERT(!type->internalName().isEmpty()); |
307 | current.cppType = type->internalName(); |
308 | Q_ASSERT(!type->baseType()->internalName().isEmpty()); |
309 | const QString baseClass = type->baseType()->internalName(); |
310 | |
311 | const auto rootType = m_visitor->result(); |
312 | const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName(); |
313 | QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(inlineComponentName: name); |
314 | Q_ASSERT(inlineComponentType); |
315 | const bool documentRoot = (type == rootType); |
316 | const bool inlineComponent = type->isInlineComponent(); |
317 | const bool isAnonymous = !documentRoot || type->internalName().at(i: 0).isLower(); |
318 | const bool isSingleton = type->isSingleton(); |
319 | |
320 | QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor }; |
321 | |
322 | current.baseClasses = { baseClass }; |
323 | if (!documentRoot) { |
324 | // make document root a friend to allow it to access init and endInit |
325 | const QString rootInternalName = |
326 | m_visitor->inlineComponent(inlineComponentName: type->enclosingInlineComponentName())->internalName(); |
327 | if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending |
328 | current.otherCode << "friend class %1;"_L1.arg(args: rootInternalName); |
329 | } |
330 | if (documentRoot || inlineComponent) { |
331 | auto name = type->inlineComponentName() |
332 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
333 | : InlineComponentOrDocumentRootName(RootDocumentNameType()); |
334 | // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to |
335 | // be created for the root object |
336 | current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg( |
337 | a: inlineComponentType->internalName()); |
338 | // generate typeCount for all components (root + inlineComponents) |
339 | QmltcMethod typeCountMethod; |
340 | typeCountMethod.name = QmltcCodeGenerator::typeCountName; |
341 | typeCountMethod.returnType = u"uint"_s; |
342 | typeCountMethod.body << u"return "+ generator.generate_typeCount(inlinedComponent: name) + u ";"; |
343 | current.typeCount = typeCountMethod; |
344 | } else { |
345 | // make an immediate parent a friend since that parent |
346 | // would create the object through a non-public constructor |
347 | const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { |
348 | if (scope->isArrayScope()) |
349 | return scope->parentScope(); |
350 | return scope; |
351 | }; |
352 | |
353 | const auto& realScope = realQmlScope(type->parentScope()); |
354 | if (realScope != rootType) { |
355 | current.otherCode << u"friend class %1;"_s.arg(a: realScope->internalName()); |
356 | } |
357 | } |
358 | |
359 | // make QQmltcObjectCreationHelper a friend of every type since it provides |
360 | // useful helper methods for all types |
361 | current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s; |
362 | |
363 | current.mocCode = { |
364 | u"Q_OBJECT"_s, |
365 | // Note: isAnonymous holds for non-root types in the document as well |
366 | type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s.arg(a: *type->inlineComponentName())) |
367 | : (isAnonymous ? u"QML_ANONYMOUS"_s: u "QML_ELEMENT"_s), |
368 | }; |
369 | |
370 | // add special member functions |
371 | current.baselineCtor.access = QQmlJSMetaMethod::Protected; |
372 | if (documentRoot || inlineComponent || isSingleton) { |
373 | current.externalCtor.access = QQmlJSMetaMethod::Public; |
374 | } else { |
375 | current.externalCtor.access = QQmlJSMetaMethod::Protected; |
376 | } |
377 | current.init.access = QQmlJSMetaMethod::Protected; |
378 | current.beginClass.access = QQmlJSMetaMethod::Protected; |
379 | current.endInit.access = QQmlJSMetaMethod::Protected; |
380 | current.setComplexBindings.access = QQmlJSMetaMethod::Protected; |
381 | current.completeComponent.access = QQmlJSMetaMethod::Protected; |
382 | current.finalizeComponent.access = QQmlJSMetaMethod::Protected; |
383 | current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; |
384 | |
385 | current.propertyInitializer.name = u"PropertyInitializer"_s; |
386 | current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public; |
387 | current.propertyInitializer.constructor.name = current.propertyInitializer.name; |
388 | current.propertyInitializer.constructor.parameterList = { |
389 | QmltcVariable(u"%1&"_s.arg(a: current.cppType), u "component"_s) |
390 | }; |
391 | current.propertyInitializer.component.cppType = current.cppType + u"&"; |
392 | current.propertyInitializer.component.name = u"component"_s; |
393 | current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s; |
394 | current.propertyInitializer.initializedCache.name = u"initializedCache"_s; |
395 | |
396 | current.baselineCtor.name = current.cppType; |
397 | current.externalCtor.name = current.cppType; |
398 | current.init.name = u"QML_init"_s; |
399 | current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s; |
400 | current.beginClass.name = u"QML_beginClass"_s; |
401 | current.beginClass.returnType = u"void"_s; |
402 | current.endInit.name = u"QML_endInit"_s; |
403 | current.endInit.returnType = u"void"_s; |
404 | current.setComplexBindings.name = u"QML_setComplexBindings"_s; |
405 | current.setComplexBindings.returnType = u"void"_s; |
406 | current.completeComponent.name = u"QML_completeComponent"_s; |
407 | current.completeComponent.returnType = u"void"_s; |
408 | current.finalizeComponent.name = u"QML_finalizeComponent"_s; |
409 | current.finalizeComponent.returnType = u"void"_s; |
410 | current.handleOnCompleted.name = u"QML_handleOnCompleted"_s; |
411 | current.handleOnCompleted.returnType = u"void"_s; |
412 | QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u "creator"_s); |
413 | QmltcVariable engine(u"QQmlEngine*"_s, u "engine"_s); |
414 | QmltcVariable parent(u"QObject*"_s, u "parent"_s, u "nullptr"_s); |
415 | QmltcVariable initializedCache( |
416 | u"[[maybe_unused]] const QSet<QString>&"_s, |
417 | u"initializedCache"_s, |
418 | u"{}"_s |
419 | ); |
420 | QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u "parentContext"_s); |
421 | QmltcVariable finalizeFlag(u"bool"_s, u "canFinalize"_s); |
422 | current.baselineCtor.parameterList = { parent }; |
423 | current.endInit.parameterList = { creator, engine }; |
424 | current.setComplexBindings.parameterList = { creator, engine, initializedCache }; |
425 | current.handleOnCompleted.parameterList = { creator }; |
426 | |
427 | if (documentRoot || inlineComponent) { |
428 | const QmltcVariable initializer( |
429 | u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s.arg(a: current.propertyInitializer.name), |
430 | u"initializer"_s, |
431 | u"[](%1&){}"_s.arg(a: current.propertyInitializer.name)); |
432 | |
433 | current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer }; |
434 | current.beginClass.parameterList = { creator, finalizeFlag }; |
435 | current.completeComponent.parameterList = { creator, finalizeFlag }; |
436 | current.finalizeComponent.parameterList = { creator, finalizeFlag }; |
437 | |
438 | compileRequiredPropertiesBundle(current, type, resolver: m_typeResolver); |
439 | |
440 | if (current.requiredPropertiesBundle) { |
441 | QmltcVariable bundle{ |
442 | u"const %1&"_s.arg(a: current.requiredPropertiesBundle->name), |
443 | u"requiredPropertiesBundle"_s, |
444 | }; |
445 | current.externalCtor.parameterList = { engine, bundle, parent, initializer }; |
446 | } else { |
447 | current.externalCtor.parameterList = { engine, parent, initializer }; |
448 | } |
449 | } else { |
450 | current.externalCtor.parameterList = { creator, engine, parent }; |
451 | current.init.parameterList = { creator, engine, ctxtdata }; |
452 | current.beginClass.parameterList = { creator }; |
453 | current.completeComponent.parameterList = { creator }; |
454 | current.finalizeComponent.parameterList = { creator }; |
455 | } |
456 | |
457 | current.externalCtor.initializerList = { current.baselineCtor.name + u"("+ parent.name |
458 | + u")"}; |
459 | if (QQmlJSUtils::hasCompositeBase(scope: type)) { |
460 | // call parent's (QML type's) basic ctor from this. that one will take |
461 | // care about QObject::setParent() |
462 | current.baselineCtor.initializerList = { baseClass + u"("+ parent.name + u ")"}; |
463 | } else { |
464 | // default call to ctor is enough, but QQml_setParent_noEvent() is |
465 | // needed (note: faster? version of QObject::setParent()) |
466 | current.baselineCtor.body << u"QQml_setParent_noEvent(this, "+ parent.name + u ");"; |
467 | } |
468 | |
469 | // compilation stub: |
470 | current.externalCtor.body << u"Q_UNUSED(engine)"_s; |
471 | if (documentRoot || inlineComponent) { |
472 | compileRootExternalConstructorBody(current, type); |
473 | } else { |
474 | current.externalCtor.body << u"// not document root:"_s; |
475 | // just call init, we don't do any setup here otherwise |
476 | current.externalCtor.body << current.init.name |
477 | + u"(creator, engine, QQmlData::get(parent)->outerContext);"; |
478 | } |
479 | |
480 | if (isSingleton) { |
481 | // see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context |
482 | current.mocCode.append(t: u"QML_SINGLETON"_s); |
483 | auto &staticCreate = current.staticCreate.emplace(); |
484 | staticCreate.comments |
485 | << u"Used by the engine for singleton creation."_s |
486 | << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s; |
487 | staticCreate.type = QQmlJSMetaMethodType::StaticMethod; |
488 | staticCreate.access = QQmlJSMetaMethod::Public; |
489 | staticCreate.name = u"create"_s; |
490 | staticCreate.returnType = u"%1 *"_s.arg(a: current.cppType); |
491 | QmltcVariable jsEngine(u"QJSEngine*"_s, u "jsEngine"_s); |
492 | staticCreate.parameterList = { engine, jsEngine }; |
493 | staticCreate.body << u"Q_UNUSED(jsEngine);"_s |
494 | << u"%1 *result = new %1(engine, nullptr);"_s.arg(a: current.cppType) |
495 | << u"return result;"_s; |
496 | } |
497 | auto postponedQmlContextSetup = generator.generate_initCode(current, type); |
498 | generator.generate_endInitCode(current, type); |
499 | generator.generate_setComplexBindingsCode(current, type); |
500 | generator.generate_beginClassCode(current, type); |
501 | generator.generate_completeComponentCode(current, type); |
502 | generator.generate_finalizeComponentCode(current, type); |
503 | generator.generate_handleOnCompletedCode(current, type); |
504 | |
505 | compileElements(current, type); |
506 | } |
507 | |
508 | template<typename Iterator> |
509 | static Iterator partitionBindings(Iterator first, Iterator last) |
510 | { |
511 | // NB: the code generator cares about script bindings being processed at a |
512 | // later point, so we should sort or partition the range. we do a stable |
513 | // partition since the relative order of binding evaluation affects the UI |
514 | return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) { |
515 | // we want complex bindings to be at the end, so do the negation |
516 | return !QmltcCompiler::isComplexBinding(binding: b); |
517 | }); |
518 | } |
519 | |
520 | // Populates the propertyInitializer of the current type based on the |
521 | // available properties. |
522 | // |
523 | // A propertyInitializer is a generated class that provides a |
524 | // restricted interface that only allows setting property values and |
525 | // internally keep tracks of which properties where actually set, |
526 | // intended to be used to allow the user to set up the initial values |
527 | // when creating an instance of a component. |
528 | // |
529 | // For each property of the current type that is known, is not private |
530 | // and is writable, a setter method is generated. |
531 | // Each setter method knows how to set a specific property, so as to |
532 | // provide a strongly typed interface to property setting, as if the |
533 | // relevant C++ type was used directly. |
534 | // |
535 | // Each setter uses the write method for the proprerty when available |
536 | // and otherwise falls back to a the more generic |
537 | // `QObject::setProperty` for properties where a WRITE method is not |
538 | // available or in scope. |
539 | static void compilePropertyInitializer(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
540 | static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) { |
541 | return scope->ownerOfProperty(self: scope, name: property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension; |
542 | }; |
543 | |
544 | current.propertyInitializer.constructor.initializerList << u"component{component}"_s; |
545 | |
546 | auto properties = type->properties().values(); |
547 | for (auto& property: properties) { |
548 | if (property.index() == -1) continue; |
549 | if (property.isPrivate()) continue; |
550 | if (!property.isWritable() && !qIsReferenceTypeList(p: property)) continue; |
551 | |
552 | const QString name = property.propertyName(); |
553 | |
554 | current.propertyInitializer.propertySetters.emplace_back(); |
555 | auto& compiledSetter = current.propertyInitializer.propertySetters.back(); |
556 | |
557 | compiledSetter.userVisible = true; |
558 | compiledSetter.returnType = u"void"_s; |
559 | compiledSetter.name = QmltcPropertyData(property).write; |
560 | |
561 | if (qIsReferenceTypeList(p: property)) { |
562 | compiledSetter.parameterList.emplaceBack( |
563 | args: QQmlJSUtils::constRefify(type: u"QList<%1*>"_s.arg(a: property.type()->valueType()->internalName())), |
564 | args: name + u"_", args: QString() |
565 | ); |
566 | } else { |
567 | compiledSetter.parameterList.emplaceBack( |
568 | args: QQmlJSUtils::constRefify(type: getUnderlyingType(p: property)), args: name + u"_", args: QString() |
569 | ); |
570 | } |
571 | |
572 | if (qIsReferenceTypeList(p: property)) { |
573 | compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg( |
574 | args&: current.propertyInitializer.component.name, args: name |
575 | ); |
576 | compiledSetter.body << u"list_ref_.clear();"_s; |
577 | compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(a: name); |
578 | compiledSetter.body << u" list_ref_.append(list_item_);"_s; |
579 | } else if ( |
580 | QQmlJSUtils::bindablePropertyHasDefaultAccessor(p: property, accessor: QQmlJSUtils::PropertyAccessor_Write) |
581 | ) { |
582 | compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg( |
583 | args&: current.propertyInitializer.component.name, args: property.bindable(), args: name); |
584 | } else if (type->hasOwnProperty(name)) { |
585 | compiledSetter.body << u"%1.%2(%3_);"_s.arg( |
586 | args&: current.propertyInitializer.component.name, args: QmltcPropertyData(property).write, args: name); |
587 | } else if (property.write().isEmpty() || isFromExtension(property, type)) { |
588 | // We can end here if a WRITE method is not available or |
589 | // if the method is available but not in this scope, so |
590 | // that we fallback to the string-based setters.. |
591 | // |
592 | // For example, types that makes use of QML_EXTENDED |
593 | // types, will have the extension types properties |
594 | // available and with a WRITE method, but the WRITE method |
595 | // will not be available to the extended type, from C++, |
596 | // as the type does not directly inherit from the |
597 | // extension type. |
598 | // |
599 | // We specifically scope `setProperty` to `QObject` as |
600 | // certain types might have shadowed the method. |
601 | // For example, in QtQuick, some types have a property |
602 | // called `property` with a `setProperty` WRITE method |
603 | // that will produce the shadowing. |
604 | compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg( |
605 | args&: current.propertyInitializer.component.name, args: name); |
606 | } else { |
607 | compiledSetter.body << u"%1.%2(%3_);"_s.arg( |
608 | args&: current.propertyInitializer.component.name, args: property.write(), args: name); |
609 | } |
610 | |
611 | compiledSetter.body << u"%1.insert(QStringLiteral(\"%2\"));"_s.arg( |
612 | args&: current.propertyInitializer.initializedCache.name, args: name); |
613 | } |
614 | } |
615 | |
616 | void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) |
617 | { |
618 | // compile components of a type: |
619 | // - enums |
620 | // - properties |
621 | // - methods |
622 | // - bindings |
623 | |
624 | const auto enums = type->ownEnumerations(); |
625 | current.enums.reserve(asize: enums.size()); |
626 | for (auto it = enums.begin(); it != enums.end(); ++it) |
627 | compileEnum(current, e: it.value()); |
628 | |
629 | auto properties = type->ownProperties().values(); |
630 | current.properties.reserve(asize: properties.size()); |
631 | // Note: index() is the (future) meta property index, so make sure given |
632 | // properties are ordered by that index before compiling |
633 | std::sort(first: properties.begin(), last: properties.end(), |
634 | comp: [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { |
635 | return x.index() < y.index(); |
636 | }); |
637 | for (const QQmlJSMetaProperty &p : std::as_const(t&: properties)) { |
638 | if (p.index() == -1) { |
639 | recordError(location: type->sourceLocation(), |
640 | message: u"Internal error: property '%1' has incomplete information"_s.arg( |
641 | a: p.propertyName())); |
642 | continue; |
643 | } |
644 | if (p.isAlias()) { |
645 | compileAlias(current, alias: p, owner: type); |
646 | } else { |
647 | compileProperty(current, p, owner: type); |
648 | } |
649 | } |
650 | |
651 | const auto methods = type->ownMethods(); |
652 | for (const QQmlJSMetaMethod &m : methods) |
653 | compileMethod(current, m, owner: type); |
654 | |
655 | auto bindings = type->ownPropertyBindingsInQmlIROrder(); |
656 | partitionBindings(first: bindings.begin(), last: bindings.end()); |
657 | |
658 | compilePropertyInitializer(current, type); |
659 | compileBinding(current, bindingStart: bindings.begin(), bindingEnd: bindings.end(), type, accessor: { .scope: type }); |
660 | } |
661 | |
662 | void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) |
663 | { |
664 | const auto intValues = e.values(); |
665 | QStringList values; |
666 | values.reserve(asize: intValues.size()); |
667 | std::transform(first: intValues.cbegin(), last: intValues.cend(), result: std::back_inserter(x&: values), |
668 | unary_op: [](int x) { return QString::number(x); }); |
669 | |
670 | // structure: (C++ type name, enum keys, enum values, MOC line) |
671 | current.enums.emplaceBack(args: e.name(), args: e.keys(), args: std::move(values), |
672 | args: u"Q_ENUM(%1)"_s.arg(a: e.name())); |
673 | } |
674 | |
675 | static QList<QmltcVariable> |
676 | compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool allowUnnamed = false) |
677 | { |
678 | QList<QmltcVariable> parameters; |
679 | const auto size = parameterInfos.size(); |
680 | parameters.reserve(asize: size); |
681 | for (qsizetype i = 0; i < size; ++i) { |
682 | const auto &p = parameterInfos[i]; |
683 | Q_ASSERT(p.type()); // assume verified |
684 | QString name = p.name(); |
685 | Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified |
686 | if (name.isEmpty() && allowUnnamed) |
687 | name = u"unnamed_"+ QString::number(i); |
688 | |
689 | QString internalName; |
690 | const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics(); |
691 | |
692 | switch (semantics) { |
693 | case QQmlJSScope::AccessSemantics::Reference: |
694 | if (p.typeQualifier() == QQmlJSMetaParameter::Const) |
695 | internalName = u"const "_s; |
696 | internalName += u"%1*"_s.arg(a: p.type()->internalName()); |
697 | break; |
698 | case QQmlJSScope::AccessSemantics::Value: |
699 | case QQmlJSScope::AccessSemantics::Sequence: |
700 | internalName = u"passByConstRefOrValue<%1>"_s.arg(a: p.type()->internalName()); |
701 | break; |
702 | case QQmlJSScope::AccessSemantics::None: |
703 | Q_ASSERT(false); // or maybe print an error message |
704 | } |
705 | parameters.emplaceBack(args&: internalName, args&: name, args: QString()); |
706 | } |
707 | return parameters; |
708 | } |
709 | |
710 | static QString figureReturnType(const QQmlJSMetaMethod &m) |
711 | { |
712 | const bool isVoidMethod = |
713 | m.returnTypeName() == u"void"|| m.methodType() == QQmlJSMetaMethodType::Signal; |
714 | Q_ASSERT(isVoidMethod || m.returnType()); |
715 | QString type; |
716 | if (isVoidMethod) { |
717 | type = u"void"_s; |
718 | } else { |
719 | type = m.returnType()->augmentedInternalName(); |
720 | } |
721 | return type; |
722 | } |
723 | |
724 | void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, |
725 | const QQmlJSScope::ConstPtr &owner) |
726 | { |
727 | const auto returnType = figureReturnType(m); |
728 | |
729 | const QList<QmltcVariable> compiledParams = compileMethodParameters(parameterInfos: m.parameters()); |
730 | const auto methodType = m.methodType(); |
731 | |
732 | QStringList code; |
733 | if (methodType != QQmlJSMetaMethodType::Signal) { |
734 | QmltcCodeGenerator urlGenerator { .documentUrl: m_url, .visitor: m_visitor }; |
735 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
736 | block: &code, url: urlGenerator.urlMethodName() + u"()", |
737 | index: owner->ownRuntimeFunctionIndex(index: m.jsFunctionIndex()), accessor: u"this"_s, returnType, |
738 | parameters: compiledParams); |
739 | } |
740 | |
741 | QmltcMethod compiled {}; |
742 | compiled.returnType = returnType; |
743 | compiled.name = m.methodName(); |
744 | compiled.parameterList = std::move(compiledParams); |
745 | compiled.body = std::move(code); |
746 | compiled.type = methodType; |
747 | compiled.access = m.access(); |
748 | if (methodType != QQmlJSMetaMethodType::Signal) { |
749 | compiled.declarationPrefixes << u"Q_INVOKABLE"_s; |
750 | compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; |
751 | } else { |
752 | compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal(); |
753 | } |
754 | current.functions.emplaceBack(args&: compiled); |
755 | } |
756 | |
757 | /*! \internal |
758 | Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++ |
759 | for the user. |
760 | */ |
761 | void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p) |
762 | { |
763 | QmltcPropertyData data(p); |
764 | const QString valueType = p.type()->valueType()->internalName() + u'*'; |
765 | const QString variableName = data.read + u"()"_s; |
766 | const QStringList ownershipWarning = { |
767 | u"\\note {This method does not change the ownership of its argument."_s, |
768 | u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s, |
769 | u"for ensuring that the argument lives long enough."_s, |
770 | u"For example, an argument created with \\c {createObject()} that has no parent"_s, |
771 | u"will eventually be garbage-collected, leaving a dangling pointer.}"_s |
772 | }; |
773 | |
774 | // generate append() sugar for users |
775 | { |
776 | QmltcMethod append{}; |
777 | append.comments.emplaceBack(args: u"\\brief Append an element to %1."_s.arg(a: data.read)); |
778 | append.comments << ownershipWarning; |
779 | append.returnType = u"void"_s; |
780 | append.name = u"%1Append"_s.arg(a: data.read); |
781 | append.parameterList.emplaceBack(args: valueType, args: u"toBeAppended"_s); |
782 | |
783 | append.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
784 | append.body |
785 | << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s; |
786 | // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is |
787 | // resolved |
788 | append.userVisible = true; |
789 | current.functions.emplaceBack(args&: append); |
790 | } |
791 | |
792 | // generate count() sugar for users |
793 | { |
794 | QmltcMethod count{}; |
795 | count.comments.emplaceBack(args: u"\\brief Number of elements in %1."_s.arg(a: data.read)); |
796 | count.returnType = u"int"_s; |
797 | count.name = u"%1Count"_s.arg(a: data.read); |
798 | |
799 | count.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
800 | count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s; |
801 | count.body << u"return result;"_s; |
802 | count.userVisible = true; |
803 | current.functions.emplaceBack(args&: count); |
804 | } |
805 | |
806 | // generate at() sugar for users |
807 | { |
808 | QmltcMethod at{}; |
809 | at.comments.emplaceBack(args: u"\\brief Access an element in %1."_s.arg(a: data.read)); |
810 | at.returnType = valueType; |
811 | at.name = u"%1At"_s.arg(a: data.read); |
812 | at.parameterList.emplaceBack(args: u"qsizetype"_s, args: u "position"_s, args: QString()); |
813 | |
814 | at.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
815 | at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s; |
816 | at.body << u"return result;"_s; |
817 | at.userVisible = true; |
818 | current.functions.emplaceBack(args&: at); |
819 | } |
820 | |
821 | // generate clear() sugar for users |
822 | { |
823 | QmltcMethod clear{}; |
824 | clear.comments.emplaceBack(args: u"\\brief Clear %1."_s.arg(a: data.read)); |
825 | clear.returnType = u"void"_s; |
826 | clear.name = u"%1Clear"_s.arg(a: data.read); |
827 | |
828 | clear.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
829 | clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s; |
830 | // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is |
831 | // resolved |
832 | clear.userVisible = true; |
833 | current.functions.emplaceBack(args&: clear); |
834 | } |
835 | |
836 | // generate replace() sugar for users |
837 | { |
838 | QmltcMethod replace{}; |
839 | replace.comments.emplaceBack(args: u"\\brief Replace an element in %1."_s.arg(a: data.read)); |
840 | replace.comments << ownershipWarning; |
841 | replace.returnType = u"void"_s; |
842 | replace.name = u"%1Replace"_s.arg(a: data.read); |
843 | replace.parameterList.emplaceBack(args: u"qsizetype"_s, args: u "position"_s, args: QString()); |
844 | replace.parameterList.emplaceBack(args: valueType, args: u"element"_s, |
845 | args: QString()); |
846 | |
847 | replace.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
848 | replace.body |
849 | << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s; |
850 | // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 |
851 | // is resolved |
852 | replace.userVisible = true; |
853 | current.functions.emplaceBack(args&: replace); |
854 | } |
855 | |
856 | // generate removeLast() sugar for users |
857 | { |
858 | QmltcMethod removeLast{}; |
859 | removeLast.comments.emplaceBack(args: u"\\brief Remove the last element in %1."_s.arg(a: data.read)); |
860 | removeLast.returnType = u"void"_s; |
861 | removeLast.name = u"%1RemoveLast"_s.arg(a: data.read); |
862 | |
863 | removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName); |
864 | removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s; |
865 | // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when |
866 | // QTBUG-106587 is resolved |
867 | |
868 | removeLast.userVisible = true; |
869 | current.functions.emplaceBack(args&: removeLast); |
870 | } |
871 | } |
872 | |
873 | void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, |
874 | const QQmlJSScope::ConstPtr &owner) |
875 | { |
876 | Q_ASSERT(!p.isAlias()); // will be handled separately |
877 | Q_ASSERT(p.type()); |
878 | |
879 | const QString name = p.propertyName(); |
880 | const QString variableName = u"m_"+ name; |
881 | const QString underlyingType = getUnderlyingType(p); |
882 | if (qIsReferenceTypeList(p)) { |
883 | const QString storageName = variableName + u"_storage"; |
884 | current.variables.emplaceBack( |
885 | args: u"QList<"+ p.type()->valueType()->internalName() + u "*>", args: storageName, |
886 | args: QString()); |
887 | current.baselineCtor.initializerList.emplaceBack(args: variableName + u"("+ underlyingType |
888 | + u"(this, std::addressof("+ storageName |
889 | + u")))"); |
890 | compileExtraListMethods(current, p); |
891 | } |
892 | |
893 | // along with property, also add relevant moc code, so that we can use the |
894 | // property in Qt/QML contexts |
895 | QStringList mocPieces; |
896 | mocPieces.reserve(asize: 10); |
897 | mocPieces << underlyingType << name; |
898 | |
899 | QmltcPropertyData compilationData(p); |
900 | |
901 | // 1. add setter and getter |
902 | // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through |
903 | // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. |
904 | if (p.isWritable() && !qIsReferenceTypeList(p)) { |
905 | QmltcMethod setter {}; |
906 | setter.returnType = u"void"_s; |
907 | setter.name = compilationData.write; |
908 | // QmltcVariable |
909 | setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), args: name + u"_", |
910 | args: u""_s); |
911 | setter.body << variableName + u".setValue("+ name + u "_);"; |
912 | setter.body << u"Q_EMIT "+ compilationData.notify + u "();"; |
913 | setter.userVisible = true; |
914 | current.functions.emplaceBack(args&: setter); |
915 | mocPieces << u"WRITE"_s<< setter.name; |
916 | } |
917 | |
918 | QmltcMethod getter {}; |
919 | getter.returnType = underlyingType; |
920 | getter.name = compilationData.read; |
921 | getter.body << u"return "+ variableName + u ".value();"; |
922 | getter.userVisible = true; |
923 | current.functions.emplaceBack(args&: getter); |
924 | mocPieces << u"READ"_s<< getter.name; |
925 | |
926 | // 2. add bindable |
927 | if (!qIsReferenceTypeList(p)) { |
928 | QmltcMethod bindable {}; |
929 | bindable.returnType = u"QBindable<"+ underlyingType + u ">"; |
930 | bindable.name = compilationData.bindable; |
931 | bindable.body << u"return QBindable<"+ underlyingType + u ">(std::addressof("+ variableName |
932 | + u"));"; |
933 | bindable.userVisible = true; |
934 | current.functions.emplaceBack(args&: bindable); |
935 | mocPieces << u"BINDABLE"_s<< bindable.name; |
936 | } |
937 | |
938 | // 3. add/check notify (actually, this is already done inside QmltcVisitor) |
939 | |
940 | if (owner->isPropertyRequired(name)) |
941 | mocPieces << u"REQUIRED"_s; |
942 | |
943 | // 4. add moc entry |
944 | // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) |
945 | current.mocCode << u"Q_PROPERTY("+ mocPieces.join(sep: u " "_s) + u ")"; |
946 | |
947 | // 5. add extra moc entry if this property is marked default |
948 | if (name == owner->defaultPropertyName()) |
949 | current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: name); |
950 | |
951 | // structure: (C++ type name, name, C++ class name, C++ signal name) |
952 | current.properties.emplaceBack(args: underlyingType, args: variableName, args&: current.cppType, |
953 | args&: compilationData.notify); |
954 | } |
955 | |
956 | /*! |
957 | * \internal |
958 | * |
959 | * Models one step of the alias resolution. If the current alias to be resolved |
960 | * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct |
961 | * contains the information on how to obtain the \c {z} part from \c {x.y}. |
962 | */ |
963 | struct AliasResolutionFrame |
964 | { |
965 | /*! |
966 | * \internal |
967 | * |
968 | * Placeholder for the current resolved state. It is replaced later with |
969 | * the result from previous resolutions from the \c QStack<AliasResolutionFrame>. |
970 | * |
971 | * \sa unpackFrames() |
972 | */ |
973 | static QString inVar; |
974 | |
975 | /*! |
976 | * \internal |
977 | * |
978 | * Steps to access this value as a list of C++ statements, to be used in |
979 | * conjunction with \c {epilogue}. |
980 | */ |
981 | QStringList prologue; |
982 | |
983 | /*! |
984 | * \internal |
985 | * |
986 | * Steps to finish the statements of the \c prologue (e.g. closing brackets). |
987 | */ |
988 | QStringList epilogue; |
989 | |
990 | /*! |
991 | * \internal |
992 | * |
993 | * Instructions on how to write the property, after it was loaded with the |
994 | * instructions from \c prologue. Has to happen before \c epilogue. |
995 | */ |
996 | QStringList epilogueForWrite; |
997 | |
998 | /*! |
999 | * \internal |
1000 | * |
1001 | * Name of the variable holding the result of this resolution step, to be |
1002 | * used in the following resolution steps. |
1003 | */ |
1004 | QString outVar; |
1005 | }; |
1006 | // special string replaced by outVar of the previous frame |
1007 | QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__"); |
1008 | |
1009 | /*! |
1010 | * \internal |
1011 | * |
1012 | * Process the frames by replacing the placeholder \c invar |
1013 | * used in \c epilogueForWrite and \c prologue with the result |
1014 | * obtained from the previous frame. |
1015 | */ |
1016 | static void unpackFrames(QStack<AliasResolutionFrame> &frames) |
1017 | { |
1018 | if (frames.size() < 2) |
1019 | return; |
1020 | |
1021 | // assume first frame is fine |
1022 | auto prev = frames.begin(); |
1023 | for (auto it = std::next(x: prev); it != frames.end(); ++it, ++prev) { |
1024 | for (QString &line : it->prologue) |
1025 | line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
1026 | for (QString &line : it->epilogueForWrite) |
1027 | line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
1028 | it->outVar.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
1029 | } |
1030 | } |
1031 | |
1032 | template<typename Projection> |
1033 | static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project) |
1034 | { |
1035 | QStringList joined; |
1036 | for (const AliasResolutionFrame &frame : frames) |
1037 | joined += project(frame); |
1038 | return joined; |
1039 | } |
1040 | |
1041 | void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, |
1042 | const QQmlJSScope::ConstPtr &owner) |
1043 | { |
1044 | const QString aliasName = alias.propertyName(); |
1045 | Q_ASSERT(!aliasName.isEmpty()); |
1046 | |
1047 | QStringList aliasExprBits = alias.aliasExpression().split(sep: u'.'); |
1048 | Q_ASSERT(!aliasExprBits.isEmpty()); |
1049 | |
1050 | QStack<AliasResolutionFrame> frames; |
1051 | |
1052 | QQmlJSUtils::AliasResolutionVisitor aliasVisitor; |
1053 | qsizetype i = 0; |
1054 | aliasVisitor.reset = [&]() { |
1055 | frames.clear(); |
1056 | i = 0; // we use it in property processing |
1057 | |
1058 | // first frame is a dummy one: |
1059 | frames.push( |
1060 | t: AliasResolutionFrame { .prologue: QStringList(), .epilogue: QStringList(), .epilogueForWrite: QStringList(), .outVar: u"this"_s}); |
1061 | }; |
1062 | aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { |
1063 | Q_ASSERT(type); |
1064 | if (owner != type) { // cannot start at `this`, need to fetch object through context |
1065 | const int id = m_visitor->runtimeId(type); |
1066 | Q_ASSERT(id >= 0); // since the type is found by id, it must have an id |
1067 | |
1068 | AliasResolutionFrame queryIdFrame {}; |
1069 | Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well |
1070 | queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg( |
1071 | a: owner->internalName()); |
1072 | |
1073 | // doing the above allows us to lookup id object by index (fast) |
1074 | queryIdFrame.outVar = u"alias_objectById_"+ aliasExprBits.front(); // unique enough |
1075 | const QString cppType = (m_visitor->qmlComponentIndex(type) == -1) |
1076 | ? type->internalName() |
1077 | : u"QQmlComponent"_s; |
1078 | queryIdFrame.prologue << u"auto "+ queryIdFrame.outVar + u " = static_cast<"+ cppType |
1079 | + u"*>(context->idValue("+ QString::number(id) + u "));"; |
1080 | queryIdFrame.prologue << u"Q_ASSERT("+ queryIdFrame.outVar + u ");"; |
1081 | |
1082 | frames.push(t: queryIdFrame); |
1083 | } |
1084 | }; |
1085 | aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, |
1086 | const QQmlJSScope::ConstPtr &owner) { |
1087 | AliasResolutionFrame queryPropertyFrame {}; |
1088 | |
1089 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
1090 | QmltcCodeGenerator::wrap_extensionType( |
1091 | type: owner, p, |
1092 | accessor: QmltcCodeGenerator::wrap_privateClass(accessor: AliasResolutionFrame::inVar, p)); |
1093 | QString inVar = extensionAccessor; |
1094 | queryPropertyFrame.prologue += extensionPrologue; |
1095 | if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { |
1096 | // we need to read the property to a local variable and then |
1097 | // write the updated value once the actual operation is done |
1098 | const QString aliasVar = u"alias_"+ QString::number(i); // should be fairly unique |
1099 | ++i; |
1100 | queryPropertyFrame.prologue |
1101 | << u"auto "+ aliasVar + u " = "+ inVar + u "->"+ p.read() + u "();"; |
1102 | queryPropertyFrame.epilogueForWrite |
1103 | << inVar + u"->"+ p.write() + u "("+ aliasVar + u ");"; |
1104 | // NB: since accessor becomes a value type, wrap it into an |
1105 | // addressof operator so that we could access it as a pointer |
1106 | inVar = QmltcCodeGenerator::wrap_addressof(addressed: aliasVar); // reset |
1107 | } else { |
1108 | inVar += u"->"+ p.read() + u "()"; // update |
1109 | } |
1110 | queryPropertyFrame.outVar = inVar; |
1111 | queryPropertyFrame.epilogue += extensionEpilogue; |
1112 | |
1113 | frames.push(t: queryPropertyFrame); |
1114 | }; |
1115 | |
1116 | QQmlJSUtils::ResolvedAlias result = |
1117 | QQmlJSUtils::resolveAlias(typeResolver: m_typeResolver, property: alias, owner, visitor: aliasVisitor); |
1118 | Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid); |
1119 | |
1120 | unpackFrames(frames); |
1121 | |
1122 | if (result.kind == QQmlJSUtils::AliasTarget_Property) { |
1123 | // we don't need the last frame here |
1124 | frames.pop(); |
1125 | |
1126 | // instead, add a custom frame |
1127 | AliasResolutionFrame customFinalFrame {}; |
1128 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
1129 | QmltcCodeGenerator::wrap_extensionType( |
1130 | type: result.owner, p: result.property, |
1131 | accessor: QmltcCodeGenerator::wrap_privateClass(accessor: frames.top().outVar, |
1132 | p: result.property)); |
1133 | customFinalFrame.prologue = extensionPrologue; |
1134 | customFinalFrame.outVar = extensionAccessor; |
1135 | customFinalFrame.epilogue = extensionEpilogue; |
1136 | frames.push(t: customFinalFrame); |
1137 | } |
1138 | |
1139 | const QString latestAccessor = frames.top().outVar; |
1140 | const QStringList prologue = |
1141 | joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; }); |
1142 | const QStringList epilogue = |
1143 | joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; }); |
1144 | const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) |
1145 | ? getUnderlyingType(p: result.property) |
1146 | : result.owner->internalName() + u" *"; |
1147 | |
1148 | QStringList mocLines; |
1149 | mocLines.reserve(asize: 10); |
1150 | mocLines << underlyingType << aliasName; |
1151 | |
1152 | QmltcPropertyData compilationData(aliasName); |
1153 | // 1. add setter and getter |
1154 | QmltcMethod getter {}; |
1155 | getter.returnType = underlyingType; |
1156 | getter.name = compilationData.read; |
1157 | getter.body += prologue; |
1158 | if (result.kind == QQmlJSUtils::AliasTarget_Property) { |
1159 | if (QString read = result.property.read(); !read.isEmpty() |
1160 | && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( |
1161 | p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Read)) { |
1162 | getter.body << u"return %1->%2();"_s.arg(args: latestAccessor, args&: read); |
1163 | } else { // use QObject::property() as a fallback when read method is unknown |
1164 | getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg( |
1165 | args: underlyingType, args: latestAccessor, args: result.property.propertyName()); |
1166 | } |
1167 | } else { // AliasTarget_Object |
1168 | getter.body << u"return "+ latestAccessor + u ";"; |
1169 | } |
1170 | getter.body += epilogue; |
1171 | getter.userVisible = true; |
1172 | current.functions.emplaceBack(args&: getter); |
1173 | mocLines << u"READ"_s<< getter.name; |
1174 | |
1175 | if (result.property.isWritable()) { |
1176 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
1177 | QmltcMethod setter {}; |
1178 | setter.returnType = u"void"_s; |
1179 | setter.name = compilationData.write; |
1180 | |
1181 | const QString setName = result.property.write(); |
1182 | QList<QQmlJSMetaMethod> methods = result.owner->methods(name: setName); |
1183 | if (methods.isEmpty()) { // when we are compiling the property as well |
1184 | // QmltcVariable |
1185 | setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), |
1186 | args: aliasName + u"_", args: u ""_s); |
1187 | } else { |
1188 | setter.parameterList = compileMethodParameters(parameterInfos: methods.at(i: 0).parameters(), |
1189 | /* allow unnamed = */ allowUnnamed: true); |
1190 | } |
1191 | |
1192 | setter.body += prologue; |
1193 | QStringList parameterNames; |
1194 | parameterNames.reserve(asize: setter.parameterList.size()); |
1195 | std::transform(first: setter.parameterList.cbegin(), last: setter.parameterList.cend(), |
1196 | result: std::back_inserter(x&: parameterNames), |
1197 | unary_op: [](const QmltcVariable &x) { return x.name; }); |
1198 | QString commaSeparatedParameterNames = parameterNames.join(sep: u", "_s); |
1199 | if (!setName.isEmpty() |
1200 | && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( |
1201 | p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Write)) { |
1202 | setter.body << u"%1->%2(%3);"_s.arg(args: latestAccessor, args: setName, |
1203 | args&: commaSeparatedParameterNames); |
1204 | } else { // use QObject::setProperty() as fallback when write method is unknown |
1205 | Q_ASSERT(parameterNames.size() == 1); |
1206 | const QString variantName = u"var_"+ aliasName; // fairly unique |
1207 | setter.body << u"QVariant %1;"_s.arg(a: variantName); |
1208 | setter.body << u"%1.setValue(%2);"_s.arg(args: variantName, args&: commaSeparatedParameterNames); |
1209 | setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg( |
1210 | args: latestAccessor, args: result.property.propertyName(), args: variantName); |
1211 | } |
1212 | setter.body += joinFrames( |
1213 | frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); |
1214 | setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction |
1215 | setter.userVisible = true; |
1216 | current.functions.emplaceBack(args&: setter); |
1217 | mocLines << u"WRITE"_s<< setter.name; |
1218 | } |
1219 | // 2. add bindable |
1220 | if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) { |
1221 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
1222 | QmltcMethod bindable {}; |
1223 | bindable.returnType = u"QBindable<"+ underlyingType + u ">"; |
1224 | bindable.name = compilationData.bindable; |
1225 | bindable.body += prologue; |
1226 | bindable.body << u"return "+ latestAccessor + u "->"+ bindableName + u "()"+ u ";"; |
1227 | bindable.body += epilogue; |
1228 | bindable.userVisible = true; |
1229 | current.functions.emplaceBack(args&: bindable); |
1230 | mocLines << u"BINDABLE"_s<< bindable.name; |
1231 | } |
1232 | |
1233 | // 3. add notify - which is pretty special |
1234 | // step 1: generate the moc instructions |
1235 | // mimic the engines behavior: do it even if the notify will never be emitted |
1236 | if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) { |
1237 | |
1238 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
1239 | |
1240 | mocLines << u"NOTIFY"_s<< aliasNotifyName; |
1241 | } |
1242 | |
1243 | // step 2: connect the notifier to the aliased property notifier, if this latter exists |
1244 | // otherwise, mimic the engines behavior and generate a useless notify |
1245 | if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) { |
1246 | auto notifyFrames = frames; |
1247 | notifyFrames.pop(); // we don't need the last frame at all in this case |
1248 | |
1249 | const QStringList notifyPrologue = joinFrames( |
1250 | frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; }); |
1251 | const QStringList notifyEpilogue = joinFrames( |
1252 | frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; }); |
1253 | |
1254 | // notify is very special |
1255 | current.endInit.body << u"{ // alias notify connection:"_s; |
1256 | current.endInit.body += notifyPrologue; |
1257 | // TODO: use non-private accessor since signals must exist on the public |
1258 | // type, not on the private one -- otherwise, you can't connect to a |
1259 | // private property signal in C++ and so it is useless (hence, use |
1260 | // public type) |
1261 | const QString cppType = (m_visitor->qmlComponentIndex(type: result.owner) == -1) |
1262 | ? result.owner->internalName() |
1263 | : u"QQmlComponent"_s; |
1264 | const QString latestAccessorNonPrivate = notifyFrames.top().outVar; |
1265 | current.endInit.body << u"QObject::connect("+ latestAccessorNonPrivate + u ", &"+ cppType |
1266 | + u"::"+ notifyName + u ", this, &"+ current.cppType + u "::" |
1267 | + compilationData.notify + u");"; |
1268 | current.endInit.body += notifyEpilogue; |
1269 | current.endInit.body << u"}"_s; |
1270 | } |
1271 | |
1272 | if (QString resetName = result.property.reset(); !resetName.isEmpty()) { |
1273 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
1274 | QmltcMethod reset {}; |
1275 | reset.returnType = u"void"_s; |
1276 | reset.name = compilationData.reset; |
1277 | reset.body += prologue; |
1278 | reset.body << latestAccessor + u"->"+ resetName + u "()"+ u ";"; |
1279 | reset.body += epilogue; |
1280 | reset.userVisible = true; |
1281 | current.functions.emplaceBack(args&: reset); |
1282 | mocLines << u"RESET"_s<< reset.name; |
1283 | } |
1284 | |
1285 | // mimic the engines behavior: aliases are never constants |
1286 | // mocLines << u"CONSTANT"_s; |
1287 | // mimic the engines behavior: aliases are never stored |
1288 | mocLines << u"STORED"_s<< u "false"_s; |
1289 | // mimic the engines behavior: aliases are never designable |
1290 | mocLines << u"DESIGNABLE"_s<< u "false"_s; |
1291 | |
1292 | // 4. add moc entry |
1293 | // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) |
1294 | current.mocCode << u"Q_PROPERTY("+ mocLines.join(sep: u " "_s) + u ")"; |
1295 | |
1296 | // 5. add extra moc entry if this alias is default one |
1297 | if (aliasName == owner->defaultPropertyName()) { |
1298 | // Q_CLASSINFO("DefaultProperty", propertyName) |
1299 | current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: aliasName); |
1300 | } |
1301 | } |
1302 | |
1303 | static QString generate_callCompilationUnit(const QString &urlMethodName) |
1304 | { |
1305 | return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(a: urlMethodName); |
1306 | } |
1307 | |
1308 | static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, |
1309 | const QString &propertyName); |
1310 | |
1311 | /*! |
1312 | * \internal |
1313 | * Helper method used to keep compileBindingByType() readable. |
1314 | */ |
1315 | void QmltcCompiler::compileObjectBinding(QmltcType ¤t, |
1316 | const QQmlJSMetaPropertyBinding &binding, |
1317 | const QQmlJSScope::ConstPtr &type, |
1318 | const BindingAccessorData &accessor) |
1319 | { |
1320 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); |
1321 | |
1322 | const QString &propertyName = binding.propertyName(); |
1323 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
1324 | QQmlJSScope::ConstPtr propertyType = property.type(); |
1325 | |
1326 | // NB: object is compiled with compileType(), here just need to use it |
1327 | auto object = binding.objectType(); |
1328 | |
1329 | // Note: despite a binding being set for `accessor`, we use "this" as a |
1330 | // parent of a created object. Both attached and grouped properties are |
1331 | // parented by "this", so lifetime-wise we should be fine |
1332 | const QString qobjectParent = u"this"_s; |
1333 | |
1334 | if (!propertyType) { |
1335 | recordError(location: binding.sourceLocation(), |
1336 | message: u"Binding on property '"+ propertyName + u "' of unknown type"); |
1337 | return; |
1338 | } |
1339 | |
1340 | const auto addObjectBinding = [&](const QString &value) { |
1341 | if (qIsReferenceTypeList(p: property)) { |
1342 | Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty()); |
1343 | unprocessedListBindings.append(t: value); |
1344 | unprocessedListProperty = property; |
1345 | } else { |
1346 | QmltcCodeGenerator::generate_assignToProperty(block: ¤t.endInit.body, type, p: property, |
1347 | value, accessor: accessor.name, constructFromQObject: true); |
1348 | } |
1349 | }; |
1350 | |
1351 | // special case of implicit or explicit component: |
1352 | if (qsizetype index = m_visitor->qmlComponentIndex(type: object); index >= 0) { |
1353 | const QString objectName = newSymbol(base: u"sc"_s); |
1354 | |
1355 | const qsizetype creationIndex = m_visitor->creationIndex(type: object); |
1356 | |
1357 | QStringList *block = (creationIndex == -1) ? ¤t.endInit.body : ¤t.init.body; |
1358 | *block << u"{"_s; |
1359 | *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;") |
1360 | .arg(a: qobjectParent); |
1361 | *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, " |
1362 | "%2, %3, %4, thisContext);") |
1363 | .arg(args: objectName, args: generate_callCompilationUnit(urlMethodName: m_urlMethodName), |
1364 | args: QString::number(index), args: qobjectParent); |
1365 | *block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), " |
1366 | "QQmlContextData::OrdinaryObject);") |
1367 | .arg(a: objectName); |
1368 | |
1369 | // objects wrapped in implicit components do not have visible ids, |
1370 | // however, explicit components can have an id and that one is going |
1371 | // to be visible in the common document context |
1372 | if (creationIndex != -1) { |
1373 | // explicit component |
1374 | Q_ASSERT(object->isComposite()); |
1375 | Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s); |
1376 | |
1377 | if (int id = m_visitor->runtimeId(type: object); id >= 0) { |
1378 | QString idString = m_visitor->addressableScopes().id(scope: object, referrer: object); |
1379 | if (idString.isEmpty()) |
1380 | idString = u"<unknown>"_s; |
1381 | QmltcCodeGenerator::generate_setIdValue(block, context: u"thisContext"_s, index: id, accessor: objectName, |
1382 | idString); |
1383 | } |
1384 | |
1385 | const QString creationIndexStr = QString::number(creationIndex); |
1386 | *block << QStringLiteral("creator->set(%1, %2);").arg(args: creationIndexStr, args: objectName); |
1387 | Q_ASSERT(block == ¤t.init.body); |
1388 | current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);") |
1389 | .arg(args: objectName, args: u"QQmlComponent"_s, args: creationIndexStr); |
1390 | } |
1391 | addObjectBinding(objectName); |
1392 | *block << u"}"_s; |
1393 | return; |
1394 | } |
1395 | |
1396 | const QString objectName = newSymbol(base: u"o"_s); |
1397 | current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg( |
1398 | args: objectName, args: object->internalName(), args: qobjectParent); |
1399 | current.init.body << u"creator->set(%1, %2);"_s.arg( |
1400 | args: QString::number(m_visitor->creationIndex(type: object)), args: objectName); |
1401 | |
1402 | // refetch the same object during endInit to set the bindings |
1403 | current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg( |
1404 | args: objectName, args: object->internalName(), args: QString::number(m_visitor->creationIndex(type: object))); |
1405 | addObjectBinding(objectName); |
1406 | } |
1407 | |
1408 | /*! |
1409 | * \internal |
1410 | * Helper method used to keep compileBindingByType() readable. |
1411 | */ |
1412 | void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, |
1413 | const QQmlJSMetaPropertyBinding &binding, |
1414 | const QQmlJSScope::ConstPtr &type, |
1415 | const BindingAccessorData &accessor) |
1416 | { |
1417 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource |
1418 | || binding.bindingType() == QQmlSA::BindingType::Interceptor); |
1419 | |
1420 | const QString &propertyName = binding.propertyName(); |
1421 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
1422 | QQmlJSScope::ConstPtr propertyType = property.type(); |
1423 | |
1424 | // NB: object is compiled with compileType(), here just need to use it |
1425 | QSharedPointer<const QQmlJSScope> object; |
1426 | if (binding.bindingType() == QQmlSA::BindingType::Interceptor) |
1427 | object = binding.interceptorType(); |
1428 | else |
1429 | object = binding.valueSourceType(); |
1430 | |
1431 | // Note: despite a binding being set for `accessor`, we use "this" as a |
1432 | // parent of a created object. Both attached and grouped properties are |
1433 | // parented by "this", so lifetime-wise we should be fine |
1434 | const QString qobjectParent = u"this"_s; |
1435 | |
1436 | if (!propertyType) { |
1437 | recordError(location: binding.sourceLocation(), |
1438 | message: u"Binding on property '"+ propertyName + u "' of unknown type"); |
1439 | return; |
1440 | } |
1441 | |
1442 | auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName; |
1443 | if (objectName.isEmpty()) { |
1444 | objectName = u"onAssign_"+ propertyName; |
1445 | |
1446 | current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg( |
1447 | args&: objectName, args: object->internalName(), args: qobjectParent); |
1448 | current.init.body << u"creator->set(%1, %2);"_s.arg( |
1449 | args: QString::number(m_visitor->creationIndex(type: object)), args&: objectName); |
1450 | |
1451 | current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg( |
1452 | args&: objectName, args: object->internalName(), |
1453 | args: QString::number(m_visitor->creationIndex(type: object))); |
1454 | } |
1455 | |
1456 | // NB: we expect one "on" assignment per property, so creating |
1457 | // QQmlProperty each time should be fine (unlike QQmlListReference) |
1458 | current.endInit.body << u"{"_s; |
1459 | current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg( |
1460 | args: accessor.name, args: QQmlJSUtils::toLiteral(s: propertyName)); |
1461 | current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s |
1462 | .arg(a: objectName); |
1463 | current.endInit.body << u"}"_s; |
1464 | } |
1465 | |
1466 | /*! |
1467 | * \internal |
1468 | * Helper method used to keep compileBindingByType() readable. |
1469 | */ |
1470 | void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, |
1471 | const QQmlJSMetaPropertyBinding &binding, |
1472 | const QQmlJSScope::ConstPtr &type, |
1473 | const BindingAccessorData &accessor) |
1474 | { |
1475 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); |
1476 | |
1477 | const QString &propertyName = binding.propertyName(); |
1478 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
1479 | QQmlJSScope::ConstPtr propertyType = property.type(); |
1480 | |
1481 | Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact |
1482 | const auto attachedType = binding.attachingType(); |
1483 | Q_ASSERT(attachedType); |
1484 | |
1485 | const QString attachingTypeName = propertyName; // acts as an identifier |
1486 | auto attachingType = m_typeResolver->typeForName(name: attachingTypeName); |
1487 | |
1488 | QString attachedTypeName = attachedType->baseTypeName(); |
1489 | Q_ASSERT(!attachedTypeName.isEmpty()); |
1490 | |
1491 | auto &attachedMemberName = |
1492 | m_uniques[UniqueStringId(current, propertyName)].attachedVariableName; |
1493 | if (attachedMemberName.isEmpty()) { |
1494 | attachedMemberName = uniqueVariableName(qmlName: attachingTypeName); |
1495 | |
1496 | // add attached type as a member variable to allow noop lookup |
1497 | current.variables.emplaceBack(args: attachedTypeName + u" *", args&: attachedMemberName, args: u "nullptr"_s); |
1498 | |
1499 | if (propertyName == u"Component"_s) { // Component attached type is special |
1500 | current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s; |
1501 | current.endInit.body |
1502 | << u"// attached Component must be added to the object's QQmlData"_s; |
1503 | current.endInit.body |
1504 | << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s; |
1505 | } |
1506 | |
1507 | // Note: getting attached property is fairly expensive |
1508 | const QString getAttachedPropertyLine = u"qobject_cast<"+ attachedTypeName |
1509 | + u" *>(qmlAttachedPropertiesObject<"+ attachingType->internalName() |
1510 | + u">(this, /* create = */ true))"; |
1511 | current.endInit.body << attachedMemberName + u" = "+ getAttachedPropertyLine + u ";"; |
1512 | |
1513 | if (propertyName == u"Component"_s) { |
1514 | // call completed/destruction signals appropriately |
1515 | current.handleOnCompleted.body << u"Q_EMIT "+ attachedMemberName + u "->completed();"; |
1516 | if (!current.dtor) { |
1517 | current.dtor = QmltcDtor{}; |
1518 | current.dtor->name = u"~"+ current.cppType; |
1519 | } |
1520 | current.dtor->body << u"Q_EMIT "+ attachedMemberName + u "->destruction();"; |
1521 | } |
1522 | } |
1523 | |
1524 | auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder(); |
1525 | // compile bindings of the attached property |
1526 | partitionBindings(first: subbindings.begin(), last: subbindings.end()); |
1527 | compileBinding(current, bindingStart: subbindings.begin(), bindingEnd: subbindings.end(), type: attachedType, |
1528 | accessor: { .scope: type, .name: attachedMemberName, .propertyName: propertyName, .isValueType: false }); |
1529 | } |
1530 | |
1531 | /*! |
1532 | * \internal |
1533 | * Helper method used to keep compileBindingByType() readable. |
1534 | */ |
1535 | void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, |
1536 | const QQmlJSMetaPropertyBinding &binding, |
1537 | const QQmlJSScope::ConstPtr &type, |
1538 | const BindingAccessorData &accessor) |
1539 | { |
1540 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); |
1541 | |
1542 | const QString &propertyName = binding.propertyName(); |
1543 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
1544 | QQmlJSScope::ConstPtr propertyType = property.type(); |
1545 | |
1546 | Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact |
1547 | if (property.read().isEmpty()) { |
1548 | recordError(location: binding.sourceLocation(), |
1549 | message: u"READ function of group property '"+ propertyName + u "' is unknown"); |
1550 | return; |
1551 | } |
1552 | |
1553 | auto groupType = binding.groupType(); |
1554 | Q_ASSERT(groupType); |
1555 | |
1556 | const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value; |
1557 | if (!isValueType |
1558 | && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { |
1559 | recordError(location: binding.sourceLocation(), |
1560 | message: u"Group property '"+ propertyName + u "' has unsupported access semantics"); |
1561 | return; |
1562 | } |
1563 | |
1564 | auto subbindings = groupType->ownPropertyBindingsInQmlIROrder(); |
1565 | auto firstScript = partitionBindings(first: subbindings.begin(), last: subbindings.end()); |
1566 | |
1567 | // if we have no non-script bindings, we have no bindings that affect |
1568 | // the value type group, so no reason to generate the wrapping code |
1569 | const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript); |
1570 | |
1571 | QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) + u"->" |
1572 | + property.read() + u"()"; |
1573 | // NB: used when isValueType == true |
1574 | const QString groupPropertyVarName = accessor.name + u"_group_"+ propertyName; |
1575 | // value types are special |
1576 | if (generateValueTypeCode) { |
1577 | if (property.write().isEmpty()) { // just reject this |
1578 | recordError(location: binding.sourceLocation(), |
1579 | message: u"Group property '"+ propertyName + u "' is a value type without a setter"); |
1580 | return; |
1581 | } |
1582 | |
1583 | current.endInit.body << u"auto "+ groupPropertyVarName + u " = "+ groupAccessor + u ";"; |
1584 | // addressof operator is to make the binding logic work, which |
1585 | // expects that `accessor.name` is a pointer type |
1586 | groupAccessor = QmltcCodeGenerator::wrap_addressof(addressed: groupPropertyVarName); |
1587 | } |
1588 | |
1589 | // compile bindings of the grouped property |
1590 | const auto compile = [&](const auto &bStart, const auto &bEnd) { |
1591 | compileBinding(current, bindingStart: bStart, bindingEnd: bEnd, type: groupType, |
1592 | accessor: { type, groupAccessor, propertyName, isValueType }); |
1593 | }; |
1594 | |
1595 | auto it = subbindings.begin(); |
1596 | Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { |
1597 | return x.bindingType() != QQmlSA::BindingType::Script; |
1598 | })); |
1599 | compile(it, firstScript); |
1600 | it = firstScript; |
1601 | |
1602 | // NB: script bindings are special on group properties. if our group is |
1603 | // a value type, the binding would be installed on the *object* that |
1604 | // holds the value type and not on the value type itself. this may cause |
1605 | // subtle side issues (esp. when script binding is actually a simple |
1606 | // enum value assignment - which is not recognized specially): |
1607 | // |
1608 | // auto valueTypeGroupProperty = getCopy(); |
1609 | // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value |
1610 | // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again |
1611 | if (generateValueTypeCode) { // write the value type back |
1612 | current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) |
1613 | + u"->"+ property.write() + u "("+ groupPropertyVarName + u ");"; |
1614 | } |
1615 | |
1616 | // once the value is written back, process the script bindings |
1617 | Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { |
1618 | return x.bindingType() == QQmlSA::BindingType::Script; |
1619 | })); |
1620 | compile(it, subbindings.end()); |
1621 | } |
1622 | |
1623 | /*! |
1624 | * \internal |
1625 | * Helper method used to keep compileBindingByType() readable. |
1626 | */ |
1627 | void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, |
1628 | const QQmlJSMetaPropertyBinding &binding, |
1629 | const QQmlJSScope::ConstPtr &type, |
1630 | const BindingAccessorData &accessor) |
1631 | { |
1632 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation |
1633 | || binding.bindingType() == QQmlSA::BindingType::TranslationById); |
1634 | |
1635 | const QString &propertyName = binding.propertyName(); |
1636 | |
1637 | auto [property, absoluteIndex] = getMetaPropertyIndex(scope: type, propertyName); |
1638 | |
1639 | if (absoluteIndex < 0) { |
1640 | recordError(location: binding.sourceLocation(), |
1641 | message: u"Binding on unknown property '"+ propertyName + u "'"); |
1642 | return; |
1643 | } |
1644 | |
1645 | QString bindingTarget = accessor.name; |
1646 | |
1647 | int valueTypeIndex = -1; |
1648 | if (accessor.isValueType) { |
1649 | Q_ASSERT(accessor.scope != type); |
1650 | bindingTarget = u"this"_s; // TODO: not necessarily "this"? |
1651 | auto [groupProperty, groupPropertyIndex] = |
1652 | getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName); |
1653 | if (groupPropertyIndex < 0) { |
1654 | recordError(location: binding.sourceLocation(), |
1655 | message: u"Binding on group property '"+ accessor.propertyName |
1656 | + u"' of unknown type"); |
1657 | return; |
1658 | } |
1659 | valueTypeIndex = absoluteIndex; |
1660 | absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name |
1661 | } |
1662 | |
1663 | QmltcCodeGenerator::TranslationBindingInfo info; |
1664 | info.unitVarName = generate_callCompilationUnit(urlMethodName: m_urlMethodName); |
1665 | info.scope = u"this"_s; |
1666 | info.target = u"this"_s; |
1667 | info.propertyIndex = absoluteIndex; |
1668 | info.property = property; |
1669 | info.data = binding.translationDataValue(qmlFileNameForContext: m_url); |
1670 | info.valueTypeIndex = valueTypeIndex; |
1671 | info.line = binding.sourceLocation().startLine; |
1672 | info.column = binding.sourceLocation().startColumn; |
1673 | |
1674 | QmltcCodeGenerator::generate_createTranslationBindingOnProperty(block: ¤t.endInit.body, info); |
1675 | } |
1676 | |
1677 | void QmltcCompiler::processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, |
1678 | const BindingAccessorData &accessor) |
1679 | { |
1680 | if (unprocessedListBindings.empty()) |
1681 | return; |
1682 | |
1683 | QmltcCodeGenerator::generate_assignToListProperty( |
1684 | block: ¤t.endInit.body, type, p: unprocessedListProperty, value: unprocessedListBindings, |
1685 | accessor: accessor.name, |
1686 | qmlListVarName&: m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())] |
1687 | .qmlListVariableName); |
1688 | |
1689 | unprocessedListBindings.clear(); |
1690 | } |
1691 | |
1692 | void QmltcCompiler::compileBinding(QmltcType ¤t, |
1693 | QList<QQmlJSMetaPropertyBinding>::iterator bindingStart, |
1694 | QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd, |
1695 | const QQmlJSScope::ConstPtr &type, |
1696 | const BindingAccessorData &accessor) |
1697 | { |
1698 | for (auto it = bindingStart; it != bindingEnd; it++) { |
1699 | const QQmlJSMetaPropertyBinding &binding = *it; |
1700 | const QString &propertyName = binding.propertyName(); |
1701 | Q_ASSERT(!propertyName.isEmpty()); |
1702 | |
1703 | // Note: unlike QQmlObjectCreator, we don't have to do a complicated |
1704 | // deferral logic for bindings: if a binding is deferred, it is not compiled |
1705 | // (potentially, with all the bindings inside of it), period. |
1706 | if (type->isNameDeferred(name: propertyName)) { |
1707 | const auto location = binding.sourceLocation(); |
1708 | // make sure group property is not generalized by checking if type really has a property |
1709 | // called propertyName. If not, it is probably an id. |
1710 | if (binding.bindingType() == QQmlSA::BindingType::GroupProperty |
1711 | && type->hasProperty(name: propertyName)) { |
1712 | qCWarning(lcQmltcCompiler) |
1713 | << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " |
1714 | "binding on a group property.") |
1715 | .arg(args: QString::number(location.startLine), |
1716 | args: QString::number(location.startColumn)); |
1717 | // we do not support PropertyChanges and other types with similar |
1718 | // behavior yet, so this binding is compiled |
1719 | } else { |
1720 | qCDebug(lcQmltcCompiler) |
1721 | << QStringLiteral( |
1722 | "Binding at line %1 column %2 is deferred and thus not compiled") |
1723 | .arg(args: QString::number(location.startLine), |
1724 | args: QString::number(location.startColumn)); |
1725 | continue; |
1726 | } |
1727 | } |
1728 | |
1729 | const QQmlJSMetaProperty metaProperty = type->property(name: propertyName); |
1730 | const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); |
1731 | |
1732 | if (!(qIsReferenceTypeList(p: metaProperty) && unprocessedListProperty == metaProperty)) { |
1733 | processLastListBindings(current, type, accessor); |
1734 | } |
1735 | |
1736 | compileBindingByType(current, binding, type, accessor); |
1737 | } |
1738 | |
1739 | processLastListBindings(current, type, accessor); |
1740 | } |
1741 | |
1742 | void QmltcCompiler::compileBindingByType(QmltcType ¤t, |
1743 | const QQmlJSMetaPropertyBinding &binding, |
1744 | const QQmlJSScope::ConstPtr &type, |
1745 | const BindingAccessorData &accessor) |
1746 | { |
1747 | const QString &propertyName = binding.propertyName(); |
1748 | const QQmlJSMetaProperty metaProperty = type->property(name: propertyName); |
1749 | const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); |
1750 | |
1751 | const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value, |
1752 | bool constructFromQObject = false) { |
1753 | QmltcCodeGenerator::generate_assignToProperty(block: ¤t.endInit.body, type, p, value, |
1754 | accessor: accessor.name, constructFromQObject); |
1755 | }; |
1756 | switch (binding.bindingType()) { |
1757 | case QQmlSA::BindingType::BoolLiteral: { |
1758 | const bool value = binding.boolValue(); |
1759 | assignToProperty(metaProperty, value ? u"true"_s: u "false"_s); |
1760 | break; |
1761 | } |
1762 | case QQmlSA::BindingType::NumberLiteral: { |
1763 | assignToProperty(metaProperty, QString::number(binding.numberValue())); |
1764 | break; |
1765 | } |
1766 | case QQmlSA::BindingType::StringLiteral: { |
1767 | QString value = QQmlJSUtils::toLiteral(s: binding.stringValue()); |
1768 | if (auto type = metaProperty.type()) { |
1769 | if (type->internalName() == u"QUrl"_s) { |
1770 | value = u"QUrl(%1)"_s.arg(a: value); |
1771 | } |
1772 | } |
1773 | assignToProperty(metaProperty, value); |
1774 | break; |
1775 | } |
1776 | case QQmlSA::BindingType::RegExpLiteral: { |
1777 | const QString value = |
1778 | u"QRegularExpression(%1)"_s.arg(a: QQmlJSUtils::toLiteral(s: binding.regExpValue())); |
1779 | assignToProperty(metaProperty, value); |
1780 | break; |
1781 | } |
1782 | case QQmlSA::BindingType::Null: { |
1783 | // poor check: null bindings are only supported for var and objects |
1784 | Q_ASSERT(propertyType->isSameType(m_typeResolver->varType()) |
1785 | || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); |
1786 | if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) |
1787 | assignToProperty(metaProperty, u"nullptr"_s); |
1788 | else |
1789 | assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s); |
1790 | break; |
1791 | } |
1792 | case QQmlSA::BindingType::Script: { |
1793 | QString bindingSymbolName |
1794 | = uniqueVariableName(qmlName: type->internalName() + u'_' + propertyName + u"_binding"); |
1795 | compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, |
1796 | accessor); |
1797 | break; |
1798 | } |
1799 | case QQmlSA::BindingType::Object: { |
1800 | compileObjectBinding(current, binding, type, accessor); |
1801 | break; |
1802 | } |
1803 | case QQmlSA::BindingType::Interceptor: |
1804 | Q_FALLTHROUGH(); |
1805 | case QQmlSA::BindingType::ValueSource: { |
1806 | compileValueSourceOrInterceptorBinding(current, binding, type, accessor); |
1807 | break; |
1808 | } |
1809 | case QQmlSA::BindingType::AttachedProperty: { |
1810 | compileAttachedPropertyBinding(current, binding, type, accessor); |
1811 | break; |
1812 | } |
1813 | case QQmlSA::BindingType::GroupProperty: { |
1814 | compileGroupPropertyBinding(current, binding, type, accessor); |
1815 | break; |
1816 | } |
1817 | |
1818 | case QQmlSA::BindingType::TranslationById: |
1819 | case QQmlSA::BindingType::Translation: { |
1820 | compileTranslationBinding(current, binding, type, accessor); |
1821 | break; |
1822 | } |
1823 | case QQmlSA::BindingType::Invalid: { |
1824 | recordError(location: binding.sourceLocation(), message: u"This binding is invalid"_s); |
1825 | break; |
1826 | } |
1827 | default: { |
1828 | recordError(location: binding.sourceLocation(), message: u"Binding is not supported"_s); |
1829 | break; |
1830 | } |
1831 | } |
1832 | } |
1833 | |
1834 | // returns compiled script binding for "property changed" handler in a form of object type |
1835 | static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding, |
1836 | const QQmlJSScope::ConstPtr &objectType, |
1837 | const QString &urlMethodName, |
1838 | const QString &functorCppType, |
1839 | const QString &objectCppType) |
1840 | { |
1841 | QmltcType bindingFunctor {}; |
1842 | bindingFunctor.cppType = functorCppType; |
1843 | bindingFunctor.ignoreInit = true; |
1844 | |
1845 | // default member variable and ctor: |
1846 | const QString pointerToObject = objectCppType + u" *"; |
1847 | bindingFunctor.variables.emplaceBack( |
1848 | args: QmltcVariable { pointerToObject, u"m_self"_s, u "nullptr"_s}); |
1849 | bindingFunctor.baselineCtor.name = functorCppType; |
1850 | bindingFunctor.baselineCtor.parameterList.emplaceBack( |
1851 | args: QmltcVariable { pointerToObject, u"self"_s, QString() }); |
1852 | bindingFunctor.baselineCtor.initializerList.emplaceBack(args: u"m_self(self)"_s); |
1853 | |
1854 | // call operator: |
1855 | QmltcMethod callOperator {}; |
1856 | callOperator.returnType = u"void"_s; |
1857 | callOperator.name = u"operator()"_s; |
1858 | callOperator.modifiers << u"const"_s; |
1859 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
1860 | block: &callOperator.body, url: urlMethodName + u"()", |
1861 | index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), accessor: u"m_self"_s, returnType: u "void"_s, parameters: {}); |
1862 | |
1863 | bindingFunctor.functions.emplaceBack(args: std::move(callOperator)); |
1864 | |
1865 | return bindingFunctor; |
1866 | } |
1867 | |
1868 | // finds property for given scope and returns it together with the absolute |
1869 | // property index in the property array of the corresponding QMetaObject |
1870 | static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, |
1871 | const QString &propertyName) |
1872 | { |
1873 | auto owner = QQmlJSScope::ownerOfProperty(self: scope, name: propertyName).scope; |
1874 | Q_ASSERT(owner); |
1875 | const QQmlJSMetaProperty p = owner->ownProperty(name: propertyName); |
1876 | if (!p.isValid()) |
1877 | return { p, -1 }; |
1878 | int index = p.index(); |
1879 | if (index < 0) // this property doesn't have index - comes from QML |
1880 | return { p, -1 }; |
1881 | |
1882 | const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) { |
1883 | // owner of property is not included in the offset calculation (relative |
1884 | // index is already added as p.index()) |
1885 | if (type->isSameType(otherScope: owner)) |
1886 | return; |
1887 | |
1888 | // extension namespace and JavaScript properties are ignored |
1889 | if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript) |
1890 | return; |
1891 | |
1892 | index += int(type->ownProperties().size()); |
1893 | }; |
1894 | QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, start: owner, act: increment); |
1895 | return { p, index }; |
1896 | } |
1897 | |
1898 | void QmltcCompiler::compileScriptBinding(QmltcType ¤t, |
1899 | const QQmlJSMetaPropertyBinding &binding, |
1900 | const QString &bindingSymbolName, |
1901 | const QQmlJSScope::ConstPtr &objectType, |
1902 | const QString &propertyName, |
1903 | const QQmlJSScope::ConstPtr &propertyType, |
1904 | const QmltcCompiler::BindingAccessorData &accessor) |
1905 | { |
1906 | const auto compileScriptSignal = [&](const QString &name) { |
1907 | QString This_signal = u"this"_s; |
1908 | QString This_slot = u"this"_s; |
1909 | QString objectClassName_signal = objectType->internalName(); |
1910 | QString objectClassName_slot = objectType->internalName(); |
1911 | |
1912 | // TODO: ugly crutch to make stuff work |
1913 | if (accessor.name != u"this"_s) { // e.g. if attached property |
1914 | This_signal = accessor.name; |
1915 | This_slot = u"this"_s; // still |
1916 | objectClassName_signal = objectType->baseTypeName(); |
1917 | objectClassName_slot = current.cppType; // real base class where slot would go |
1918 | } |
1919 | Q_ASSERT(!objectClassName_signal.isEmpty()); |
1920 | Q_ASSERT(!objectClassName_slot.isEmpty()); |
1921 | |
1922 | const auto signalMethods = objectType->methods(name, type: QQmlJSMetaMethodType::Signal); |
1923 | Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else |
1924 | QQmlJSMetaMethod signal = signalMethods.at(i: 0); |
1925 | Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); |
1926 | |
1927 | const QString signalName = signal.methodName(); |
1928 | const QString slotName = newSymbol(base: signalName + u"_slot"); |
1929 | |
1930 | const QString signalReturnType = figureReturnType(m: signal); |
1931 | const QList<QmltcVariable> slotParameters = |
1932 | compileMethodParameters(parameterInfos: signal.parameters(), /* allow unnamed = */ allowUnnamed: true); |
1933 | |
1934 | // SignalHander specific: |
1935 | QmltcMethod slotMethod {}; |
1936 | slotMethod.returnType = signalReturnType; |
1937 | slotMethod.name = slotName; |
1938 | slotMethod.parameterList = slotParameters; |
1939 | |
1940 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
1941 | block: &slotMethod.body, url: m_urlMethodName + u"()", |
1942 | index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), |
1943 | accessor: u"this"_s, // Note: because script bindings always use current QML object scope |
1944 | returnType: signalReturnType, parameters: slotParameters); |
1945 | slotMethod.type = QQmlJSMetaMethodType::Slot; |
1946 | |
1947 | current.functions << std::move(slotMethod); |
1948 | current.setComplexBindings.body << u"QObject::connect("+ This_signal + u ", "+ u "&" |
1949 | + objectClassName_signal + u"::"+ signalName + u ", "+ This_slot + u ", &" |
1950 | + objectClassName_slot + u"::"+ slotName + u ");"; |
1951 | }; |
1952 | |
1953 | switch (binding.scriptKind()) { |
1954 | case QQmlSA::ScriptBindingKind::PropertyBinding: { |
1955 | if (!propertyType) { |
1956 | recordError(location: binding.sourceLocation(), |
1957 | message: u"Binding on property '"+ propertyName + u "' of unknown type"); |
1958 | return; |
1959 | } |
1960 | |
1961 | auto [property, absoluteIndex] = getMetaPropertyIndex(scope: objectType, propertyName); |
1962 | if (absoluteIndex < 0) { |
1963 | recordError(location: binding.sourceLocation(), |
1964 | message: u"Binding on unknown property '"+ propertyName + u "'"); |
1965 | return; |
1966 | } |
1967 | |
1968 | QString bindingTarget = accessor.name; |
1969 | |
1970 | int valueTypeIndex = -1; |
1971 | if (accessor.isValueType) { |
1972 | Q_ASSERT(accessor.scope != objectType); |
1973 | bindingTarget = u"this"_s; // TODO: not necessarily "this"? |
1974 | auto [groupProperty, groupPropertyIndex] = |
1975 | getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName); |
1976 | if (groupPropertyIndex < 0) { |
1977 | recordError(location: binding.sourceLocation(), |
1978 | message: u"Binding on group property '"+ accessor.propertyName |
1979 | + u"' of unknown type"); |
1980 | return; |
1981 | } |
1982 | valueTypeIndex = absoluteIndex; |
1983 | absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name |
1984 | } |
1985 | |
1986 | QmltcCodeGenerator::generate_createBindingOnProperty( |
1987 | block: ¤t.setComplexBindings.body, unitVarName: generate_callCompilationUnit(urlMethodName: m_urlMethodName), |
1988 | scope: u"this"_s, // NB: always using enclosing object as a scope for the binding |
1989 | functionIndex: static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex())), |
1990 | target: bindingTarget, // binding target |
1991 | // value types are special and are bound through valueTypeIndex |
1992 | targetType: accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, propertyIndex: absoluteIndex, |
1993 | p: property, valueTypeIndex, subTarget: accessor.name); |
1994 | break; |
1995 | } |
1996 | case QQmlSA::ScriptBindingKind::SignalHandler: { |
1997 | const auto name = QQmlSignalNames::handlerNameToSignalName(handler: propertyName); |
1998 | Q_ASSERT(name.has_value()); |
1999 | compileScriptSignal(*name); |
2000 | break; |
2001 | } |
2002 | case QQmlSA ::ScriptBindingKind::ChangeHandler: { |
2003 | const QString objectClassName = objectType->internalName(); |
2004 | const QString bindingFunctorName = newSymbol(base: bindingSymbolName + u"Functor"); |
2005 | |
2006 | const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: propertyName); |
2007 | Q_ASSERT(signalName.has_value()); // an error somewhere else |
2008 | const auto actualProperty = |
2009 | QQmlJSUtils::propertyFromChangedHandler(scope: objectType, changedHandler: propertyName); |
2010 | Q_ASSERT(actualProperty.has_value()); // an error somewhere else |
2011 | const auto actualPropertyType = actualProperty->type(); |
2012 | if (!actualPropertyType) { |
2013 | recordError(location: binding.sourceLocation(), |
2014 | message: u"Binding on property '"+ actualProperty->propertyName() |
2015 | + u"' of unknown type"); |
2016 | return; |
2017 | } |
2018 | |
2019 | // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over |
2020 | // BINDABLE when both are available. thus, test for notify first |
2021 | const QString notifyString = actualProperty->notify(); |
2022 | if (!notifyString.isEmpty()) { |
2023 | compileScriptSignal(notifyString); |
2024 | break; |
2025 | } |
2026 | const QString bindableString = actualProperty->bindable(); |
2027 | QString typeOfQmlBinding = |
2028 | u"std::unique_ptr<QPropertyChangeHandler<"+ bindingFunctorName + u ">>"; |
2029 | |
2030 | current.children << compileScriptBindingPropertyChangeHandler( |
2031 | binding, objectType, urlMethodName: m_urlMethodName, functorCppType: bindingFunctorName, objectCppType: objectClassName); |
2032 | |
2033 | current.setComplexBindings.body << u"if (!%1.contains(QStringLiteral(\"%2\")))"_s.arg( |
2034 | args&: current.propertyInitializer.initializedCache.name, args: propertyName); |
2035 | |
2036 | // TODO: this could be dropped if QQmlEngine::setContextForObject() is |
2037 | // done before currently generated C++ object is constructed |
2038 | current.setComplexBindings.body << u" "_s+ bindingSymbolName + u ".reset(new QPropertyChangeHandler<" |
2039 | + bindingFunctorName + u">(" |
2040 | + QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: *actualProperty) |
2041 | + u"->"+ bindableString + u "().onValueChanged("+ bindingFunctorName + u "(" |
2042 | + accessor.name + u"))));"; |
2043 | |
2044 | current.variables.emplaceBack( |
2045 | args: QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() }); |
2046 | break; |
2047 | } |
2048 | default: |
2049 | recordError(location: binding.sourceLocation(), message: u"Invalid script binding found"_s); |
2050 | break; |
2051 | } |
2052 | } |
2053 | |
2054 | QT_END_NAMESPACE |
2055 |
Definitions
- qIsReferenceTypeList
- unboundRequiredProperties
- compileRequiredPropertiesBundle
- compileRootExternalConstructorBody
- lcQmltcCompiler
- privateEngineName
- typeCountName
- QmltcCompiler
- ~QmltcCompiler
- newSymbol
- compile
- compileUrlMethod
- compileType
- partitionBindings
- compilePropertyInitializer
- compileTypeElements
- compileEnum
- compileMethodParameters
- figureReturnType
- compileMethod
- compileExtraListMethods
- compileProperty
- AliasResolutionFrame
- inVar
- unpackFrames
- joinFrames
- compileAlias
- generate_callCompilationUnit
- compileObjectBinding
- compileValueSourceOrInterceptorBinding
- compileAttachedPropertyBinding
- compileGroupPropertyBinding
- compileTranslationBinding
- processLastListBindings
- compileBinding
- compileBindingByType
- compileScriptBindingPropertyChangeHandler
- getMetaPropertyIndex
Learn to use CMake with our Intro Training
Find out more