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