1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qqmltypecompiler_p.h" |
41 | |
42 | #include <private/qqmlobjectcreator_p.h> |
43 | #include <private/qqmlcustomparser_p.h> |
44 | #include <private/qqmlvmemetaobject_p.h> |
45 | #include <private/qqmlcomponent_p.h> |
46 | #include <private/qqmlpropertyresolver_p.h> |
47 | |
48 | #define COMPILE_EXCEPTION(token, desc) \ |
49 | { \ |
50 | recordError((token)->location, desc); \ |
51 | return false; \ |
52 | } |
53 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, |
57 | QmlIR::Document *parsedQML, const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, |
58 | QV4::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) |
59 | : resolvedTypes(resolvedTypeCache) |
60 | , engine(engine) |
61 | , dependencyHasher(dependencyHasher) |
62 | , document(parsedQML) |
63 | , typeNameCache(typeNameCache) |
64 | , typeData(typeData) |
65 | { |
66 | } |
67 | |
68 | QQmlRefPointer<QV4::ExecutableCompilationUnit> QQmlTypeCompiler::compile() |
69 | { |
70 | // Build property caches and VME meta object data |
71 | |
72 | for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd(); |
73 | it != end; ++it) { |
74 | QQmlCustomParser *customParser = (*it)->type.customParser(); |
75 | if (customParser) |
76 | customParsers.insert(akey: it.key(), avalue: customParser); |
77 | } |
78 | |
79 | QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; |
80 | |
81 | |
82 | { |
83 | QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, &pendingGroupPropertyBindings, |
84 | engine, this, imports(), typeData->typeClassName()); |
85 | QQmlError error = propertyCacheBuilder.buildMetaObjects(); |
86 | if (error.isValid()) { |
87 | recordError(e: error); |
88 | return nullptr; |
89 | } |
90 | } |
91 | |
92 | { |
93 | QQmlDefaultPropertyMerger merger(this); |
94 | merger.mergeDefaultProperties(); |
95 | } |
96 | |
97 | { |
98 | SignalHandlerConverter converter(this); |
99 | if (!converter.convertSignalHandlerExpressionsToFunctionDeclarations()) |
100 | return nullptr; |
101 | } |
102 | |
103 | { |
104 | QQmlEnumTypeResolver enumResolver(this); |
105 | if (!enumResolver.resolveEnumBindings()) |
106 | return nullptr; |
107 | } |
108 | |
109 | { |
110 | QQmlCustomParserScriptIndexer cpi(this); |
111 | cpi.annotateBindingsWithScriptStrings(); |
112 | } |
113 | |
114 | { |
115 | QQmlAliasAnnotator annotator(this); |
116 | annotator.annotateBindingsToAliases(); |
117 | } |
118 | |
119 | // Resolve component boundaries and aliases |
120 | |
121 | { |
122 | // Scan for components, determine their scopes and resolve aliases within the scope. |
123 | QQmlComponentAndAliasResolver resolver(this); |
124 | if (!resolver.resolve()) |
125 | return nullptr; |
126 | |
127 | pendingGroupPropertyBindings.resolveMissingPropertyCaches(enginePrivate: engine, propertyCaches: &m_propertyCaches); |
128 | } |
129 | |
130 | { |
131 | QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); |
132 | if (!deferredAndCustomParserBindingScanner.scanObject()) |
133 | return nullptr; |
134 | } |
135 | |
136 | if (!document->javaScriptCompilationUnit.unitData()) { |
137 | // Compile JS binding expressions and signal handlers if necessary |
138 | { |
139 | // We can compile script strings ahead of time, but they must be compiled |
140 | // without type optimizations as their scope is always entirely dynamic. |
141 | QQmlScriptStringScanner sss(this); |
142 | sss.scan(); |
143 | } |
144 | |
145 | document->jsModule.fileName = typeData->urlString(); |
146 | document->jsModule.finalUrl = typeData->finalUrlString(); |
147 | QmlIR::JSCodeGen v4CodeGenerator(document, engine->v4engine()->illegalNames()); |
148 | if (!v4CodeGenerator.generateCodeForComponents(componentRoots: componentRoots())) { |
149 | recordError(message: v4CodeGenerator.error()); |
150 | return nullptr; |
151 | } |
152 | |
153 | document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/generateUnitData: false); |
154 | } |
155 | |
156 | // Generate QML compiled type data structures |
157 | |
158 | QmlIR::QmlUnitGenerator qmlGenerator; |
159 | qmlGenerator.generate(output&: *document, dependencyHasher); |
160 | |
161 | if (!errors.isEmpty()) |
162 | return nullptr; |
163 | |
164 | QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit |
165 | = QV4::ExecutableCompilationUnit::create(compilationUnit: std::move( |
166 | document->javaScriptCompilationUnit)); |
167 | compilationUnit->typeNameCache = typeNameCache; |
168 | compilationUnit->resolvedTypes = *resolvedTypes; |
169 | compilationUnit->propertyCaches = std::move(m_propertyCaches); |
170 | Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->objectCount())); |
171 | return compilationUnit; |
172 | } |
173 | |
174 | void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) |
175 | { |
176 | QQmlError error; |
177 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: location.line)); |
178 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: location.column)); |
179 | error.setDescription(description); |
180 | error.setUrl(url()); |
181 | errors << error; |
182 | } |
183 | |
184 | void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message) |
185 | { |
186 | QQmlError error; |
187 | error.setDescription(message.message); |
188 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startLine)); |
189 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startColumn)); |
190 | error.setUrl(url()); |
191 | errors << error; |
192 | } |
193 | |
194 | void QQmlTypeCompiler::recordError(const QQmlError &e) |
195 | { |
196 | QQmlError error = e; |
197 | error.setUrl(url()); |
198 | errors << error; |
199 | } |
200 | |
201 | QString QQmlTypeCompiler::stringAt(int idx) const |
202 | { |
203 | return document->stringAt(index: idx); |
204 | } |
205 | |
206 | int QQmlTypeCompiler::registerString(const QString &str) |
207 | { |
208 | return document->jsGenerator.registerString(str); |
209 | } |
210 | |
211 | int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v) |
212 | { |
213 | return document->jsGenerator.registerConstant(v); |
214 | } |
215 | |
216 | const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const |
217 | { |
218 | return document->javaScriptCompilationUnit.unitData(); |
219 | } |
220 | |
221 | const QQmlImports *QQmlTypeCompiler::imports() const |
222 | { |
223 | return &typeData->imports(); |
224 | } |
225 | |
226 | QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const |
227 | { |
228 | return &document->objects; |
229 | } |
230 | |
231 | void QQmlTypeCompiler::setPropertyCaches(QQmlPropertyCacheVector &&caches) |
232 | { |
233 | m_propertyCaches = std::move(caches); |
234 | Q_ASSERT(m_propertyCaches.count() > 0); |
235 | } |
236 | |
237 | const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const |
238 | { |
239 | return &m_propertyCaches; |
240 | } |
241 | |
242 | QQmlPropertyCacheVector &&QQmlTypeCompiler::takePropertyCaches() |
243 | { |
244 | return std::move(m_propertyCaches); |
245 | } |
246 | |
247 | QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() |
248 | { |
249 | return document->jsParserEngine.pool(); |
250 | } |
251 | |
252 | QStringRef QQmlTypeCompiler::newStringRef(const QString &string) |
253 | { |
254 | return document->jsParserEngine.newStringRef(s: string); |
255 | } |
256 | |
257 | const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const |
258 | { |
259 | return &document->jsGenerator.stringTable; |
260 | } |
261 | |
262 | QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const |
263 | { |
264 | return object->bindingAsString(doc: document, scriptIndex); |
265 | } |
266 | |
267 | void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, int majorVersion, int minorVersion) |
268 | { |
269 | const quint32 moduleIdx = registerString(str: module); |
270 | const quint32 qualifierIdx = registerString(str: qualifier); |
271 | |
272 | for (int i = 0, count = document->imports.count(); i < count; ++i) { |
273 | const QV4::CompiledData::Import *existingImport = document->imports.at(i); |
274 | if (existingImport->type == QV4::CompiledData::Import::ImportLibrary |
275 | && existingImport->uriIndex == moduleIdx |
276 | && existingImport->qualifierIndex == qualifierIdx) |
277 | return; |
278 | } |
279 | auto pool = memoryPool(); |
280 | QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>(); |
281 | import->type = QV4::CompiledData::Import::ImportLibrary; |
282 | import->majorVersion = majorVersion; |
283 | import->minorVersion = minorVersion; |
284 | import->uriIndex = moduleIdx; |
285 | import->qualifierIndex = qualifierIdx; |
286 | document->imports.append(t: import); |
287 | } |
288 | |
289 | CompositeMetaTypeIds QQmlTypeCompiler::typeIdsForComponent(int objectId) const |
290 | { |
291 | return typeData->typeIds(objectId); |
292 | } |
293 | |
294 | QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) |
295 | : compiler(typeCompiler) |
296 | { |
297 | } |
298 | |
299 | |
300 | |
301 | SignalHandlerConverter::SignalHandlerConverter(QQmlTypeCompiler *typeCompiler) |
302 | : QQmlCompilePass(typeCompiler) |
303 | , enginePrivate(typeCompiler->enginePrivate()) |
304 | , qmlObjects(*typeCompiler->qmlObjects()) |
305 | , imports(typeCompiler->imports()) |
306 | , customParsers(typeCompiler->customParserCache()) |
307 | , illegalNames(typeCompiler->enginePrivate()->v4engine()->illegalNames()) |
308 | , propertyCaches(typeCompiler->propertyCaches()) |
309 | { |
310 | } |
311 | |
312 | bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations() |
313 | { |
314 | for (int objectIndex = 0; objectIndex < qmlObjects.count(); ++objectIndex) { |
315 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
316 | QQmlPropertyCache *cache = propertyCaches->at(index: objectIndex); |
317 | if (!cache) |
318 | continue; |
319 | if (QQmlCustomParser *customParser = customParsers.value(akey: obj->inheritedTypeNameIndex)) { |
320 | if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) |
321 | continue; |
322 | } |
323 | const QString elementName = stringAt(idx: obj->inheritedTypeNameIndex); |
324 | if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj, typeName: elementName, propertyCache: cache)) |
325 | return false; |
326 | } |
327 | return true; |
328 | } |
329 | |
330 | bool SignalHandlerConverter::convertSignalHandlerExpressionsToFunctionDeclarations(const QmlIR::Object *obj, const QString &typeName, QQmlPropertyCache *propertyCache) |
331 | { |
332 | // map from signal name defined in qml itself to list of parameters |
333 | QHash<QString, QStringList> customSignals; |
334 | |
335 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
336 | QString propertyName = stringAt(idx: binding->propertyNameIndex); |
337 | // Attached property? |
338 | if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { |
339 | const QmlIR::Object *attachedObj = qmlObjects.at(i: binding->value.objectIndex); |
340 | auto *typeRef = resolvedType(id: binding->propertyNameIndex); |
341 | QQmlType type = typeRef ? typeRef->type : QQmlType(); |
342 | if (!type.isValid()) |
343 | imports->resolveType(type: propertyName, type_return: &type, version_major: nullptr, version_minor: nullptr, ns_return: nullptr); |
344 | |
345 | const QMetaObject *attachedType = type.attachedPropertiesType(engine: enginePrivate); |
346 | if (!attachedType) |
347 | COMPILE_EXCEPTION(binding, tr("Non-existent attached object" )); |
348 | QQmlPropertyCache *cache = compiler->enginePrivate()->cache(metaObject: attachedType); |
349 | if (!convertSignalHandlerExpressionsToFunctionDeclarations(obj: attachedObj, typeName: propertyName, propertyCache: cache)) |
350 | return false; |
351 | continue; |
352 | } |
353 | |
354 | if (!QmlIR::IRBuilder::isSignalPropertyName(name: propertyName)) |
355 | continue; |
356 | |
357 | QQmlPropertyResolver resolver(propertyCache); |
358 | |
359 | Q_ASSERT(propertyName.startsWith(QLatin1String("on" ))); |
360 | propertyName.remove(i: 0, len: 2); |
361 | |
362 | // Note that the property name could start with any alpha or '_' or '$' character, |
363 | // so we need to do the lower-casing of the first alpha character. |
364 | for (int firstAlphaIndex = 0; firstAlphaIndex < propertyName.size(); ++firstAlphaIndex) { |
365 | if (propertyName.at(i: firstAlphaIndex).isUpper()) { |
366 | propertyName[firstAlphaIndex] = propertyName.at(i: firstAlphaIndex).toLower(); |
367 | break; |
368 | } |
369 | } |
370 | |
371 | QList<QString> parameters; |
372 | |
373 | bool notInRevision = false; |
374 | QQmlPropertyData *signal = resolver.signal(name: propertyName, notInRevision: ¬InRevision); |
375 | if (signal) { |
376 | int sigIndex = propertyCache->methodIndexToSignalIndex(index: signal->coreIndex()); |
377 | sigIndex = propertyCache->originalClone(index: sigIndex); |
378 | |
379 | bool unnamedParameter = false; |
380 | |
381 | QList<QByteArray> parameterNames = propertyCache->signalParameterNames(index: sigIndex); |
382 | for (int i = 0; i < parameterNames.count(); ++i) { |
383 | const QString param = QString::fromUtf8(str: parameterNames.at(i)); |
384 | if (param.isEmpty()) |
385 | unnamedParameter = true; |
386 | else if (unnamedParameter) { |
387 | COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter." )); |
388 | } else if (illegalNames.contains(value: param)) { |
389 | COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable." ).arg(param)); |
390 | } |
391 | parameters += param; |
392 | } |
393 | } else { |
394 | if (notInRevision) { |
395 | // Try assinging it as a property later |
396 | if (resolver.property(name: propertyName, /*notInRevision ptr*/notInRevision: nullptr)) |
397 | continue; |
398 | |
399 | const QString &originalPropertyName = stringAt(idx: binding->propertyNameIndex); |
400 | |
401 | auto *typeRef = resolvedType(id: obj->inheritedTypeNameIndex); |
402 | const QQmlType type = typeRef ? typeRef->type : QQmlType(); |
403 | if (type.isValid()) { |
404 | COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5." ).arg(typeName).arg(originalPropertyName).arg(type.module()).arg(type.majorVersion()).arg(type.minorVersion())); |
405 | } else { |
406 | COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning." ).arg(typeName).arg(originalPropertyName)); |
407 | } |
408 | } |
409 | |
410 | // Try to look up the signal parameter names in the object itself |
411 | |
412 | // build cache if necessary |
413 | if (customSignals.isEmpty()) { |
414 | for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) { |
415 | const QString &signalName = stringAt(idx: signal->nameIndex); |
416 | customSignals.insert(akey: signalName, avalue: signal->parameterStringList(stringPool: compiler->stringPool())); |
417 | } |
418 | |
419 | for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) { |
420 | const QString propName = stringAt(idx: property->nameIndex); |
421 | customSignals.insert(akey: propName, avalue: QStringList()); |
422 | } |
423 | } |
424 | |
425 | QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(akey: propertyName); |
426 | if (entry == customSignals.constEnd() && propertyName.endsWith(s: QLatin1String("Changed" ))) { |
427 | QString alternateName = propertyName.mid(position: 0, n: propertyName.length() - static_cast<int>(strlen(s: "Changed" ))); |
428 | entry = customSignals.constFind(akey: alternateName); |
429 | } |
430 | |
431 | if (entry == customSignals.constEnd()) { |
432 | // Can't find even a custom signal, then just don't do anything and try |
433 | // keeping the binding as a regular property assignment. |
434 | continue; |
435 | } |
436 | |
437 | parameters = entry.value(); |
438 | } |
439 | |
440 | // Binding object to signal means connect the signal to the object's default method. |
441 | if (binding->type == QV4::CompiledData::Binding::Type_Object) { |
442 | binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerObject; |
443 | continue; |
444 | } |
445 | |
446 | if (binding->type != QV4::CompiledData::Binding::Type_Script) { |
447 | if (binding->type < QV4::CompiledData::Binding::Type_Script) { |
448 | COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)" )); |
449 | } else { |
450 | COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment" )); |
451 | } |
452 | } |
453 | |
454 | QQmlJS::MemoryPool *pool = compiler->memoryPool(); |
455 | |
456 | QQmlJS::AST::FormalParameterList *paramList = nullptr; |
457 | for (const QString ¶m : qAsConst(t&: parameters)) { |
458 | QStringRef paramNameRef = compiler->newStringRef(string: param); |
459 | |
460 | QQmlJS::AST::PatternElement *b = new (pool) QQmlJS::AST::PatternElement(paramNameRef, nullptr); |
461 | paramList = new (pool) QQmlJS::AST::FormalParameterList(paramList, b); |
462 | } |
463 | |
464 | if (paramList) |
465 | paramList = paramList->finish(pool); |
466 | |
467 | QmlIR::CompiledFunctionOrExpression *foe = obj->functionsAndExpressions->slowAt(index: binding->value.compiledScriptIndex); |
468 | QQmlJS::AST::FunctionDeclaration *functionDeclaration = nullptr; |
469 | if (QQmlJS::AST::ExpressionStatement *es = QQmlJS::AST::cast<QQmlJS::AST::ExpressionStatement*>(ast: foe->node)) { |
470 | if (QQmlJS::AST::FunctionExpression *fe = QQmlJS::AST::cast<QQmlJS::AST::FunctionExpression*>(ast: es->expression)) { |
471 | functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(fe->name, fe->formals, fe->body); |
472 | functionDeclaration->functionToken = fe->functionToken; |
473 | functionDeclaration->identifierToken = fe->identifierToken; |
474 | functionDeclaration->lparenToken = fe->lparenToken; |
475 | functionDeclaration->rparenToken = fe->rparenToken; |
476 | functionDeclaration->lbraceToken = fe->lbraceToken; |
477 | functionDeclaration->rbraceToken = fe->rbraceToken; |
478 | } |
479 | } |
480 | if (!functionDeclaration) { |
481 | QQmlJS::AST::Statement *statement = static_cast<QQmlJS::AST::Statement*>(foe->node); |
482 | QQmlJS::AST::StatementList *body = new (pool) QQmlJS::AST::StatementList(statement); |
483 | body = body->finish(); |
484 | |
485 | functionDeclaration = new (pool) QQmlJS::AST::FunctionDeclaration(compiler->newStringRef(string: stringAt(idx: binding->propertyNameIndex)), paramList, body); |
486 | functionDeclaration->lbraceToken = functionDeclaration->functionToken |
487 | = foe->node->firstSourceLocation(); |
488 | functionDeclaration->rbraceToken = foe->node->lastSourceLocation(); |
489 | } |
490 | foe->node = functionDeclaration; |
491 | binding->propertyNameIndex = compiler->registerString(str: propertyName); |
492 | binding->flags |= QV4::CompiledData::Binding::IsSignalHandlerExpression; |
493 | } |
494 | return true; |
495 | } |
496 | |
497 | QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) |
498 | : QQmlCompilePass(typeCompiler) |
499 | , qmlObjects(*typeCompiler->qmlObjects()) |
500 | , propertyCaches(typeCompiler->propertyCaches()) |
501 | , imports(typeCompiler->imports()) |
502 | { |
503 | } |
504 | |
505 | bool QQmlEnumTypeResolver::resolveEnumBindings() |
506 | { |
507 | for (int i = 0; i < qmlObjects.count(); ++i) { |
508 | QQmlPropertyCache *propertyCache = propertyCaches->at(index: i); |
509 | if (!propertyCache) |
510 | continue; |
511 | const QmlIR::Object *obj = qmlObjects.at(i); |
512 | |
513 | QQmlPropertyResolver resolver(propertyCache); |
514 | |
515 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
516 | if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression |
517 | || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) |
518 | continue; |
519 | |
520 | if (binding->type != QV4::CompiledData::Binding::Type_Script) |
521 | continue; |
522 | |
523 | const QString propertyName = stringAt(idx: binding->propertyNameIndex); |
524 | bool notInRevision = false; |
525 | QQmlPropertyData *pd = resolver.property(name: propertyName, notInRevision: ¬InRevision); |
526 | if (!pd) |
527 | continue; |
528 | |
529 | if (!pd->isEnum() && pd->propType() != QMetaType::Int) |
530 | continue; |
531 | |
532 | if (!tryQualifiedEnumAssignment(obj, propertyCache, prop: pd, binding)) |
533 | return false; |
534 | } |
535 | } |
536 | |
537 | return true; |
538 | } |
539 | |
540 | struct StaticQtMetaObject : public QObject |
541 | { |
542 | static const QMetaObject *get() |
543 | { return &staticQtMetaObject; } |
544 | }; |
545 | |
546 | bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, const QStringRef &enumName, int enumValue, bool isQtObject) |
547 | { |
548 | if (enumName.length() > 0 && enumName[0].isLower() && !isQtObject) { |
549 | COMPILE_EXCEPTION(binding, tr("Invalid property assignment: Enum value \"%1\" cannot start with a lowercase letter" ).arg(enumName.toString())); |
550 | } |
551 | binding->type = QV4::CompiledData::Binding::Type_Number; |
552 | binding->value.constantValueIndex = compiler->registerConstant(v: QV4::Encode((double)enumValue)); |
553 | // binding->setNumberValueInternal((double)enumValue); |
554 | binding->flags |= QV4::CompiledData::Binding::IsResolvedEnum; |
555 | return true; |
556 | } |
557 | |
558 | bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(const QmlIR::Object *obj, const QQmlPropertyCache *propertyCache, const QQmlPropertyData *prop, QmlIR::Binding *binding) |
559 | { |
560 | bool isIntProp = (prop->propType() == QMetaType::Int) && !prop->isEnum(); |
561 | if (!prop->isEnum() && !isIntProp) |
562 | return true; |
563 | |
564 | if (!prop->isWritable() && !(binding->flags & QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration)) |
565 | COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property" ).arg(stringAt(binding->propertyNameIndex))); |
566 | |
567 | Q_ASSERT(binding->type == QV4::CompiledData::Binding::Type_Script); |
568 | const QString string = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
569 | if (!string.constData()->isUpper()) |
570 | return true; |
571 | |
572 | // we support one or two '.' in the enum phrase: |
573 | // * <TypeName>.<EnumValue> |
574 | // * <TypeName>.<ScopedEnumName>.<EnumValue> |
575 | |
576 | int dot = string.indexOf(c: QLatin1Char('.')); |
577 | if (dot == -1 || dot == string.length()-1) |
578 | return true; |
579 | |
580 | int dot2 = string.indexOf(c: QLatin1Char('.'), from: dot+1); |
581 | if (dot2 != -1 && dot2 != string.length()-1) { |
582 | if (!string.at(i: dot+1).isUpper()) |
583 | return true; |
584 | if (string.indexOf(c: QLatin1Char('.'), from: dot2+1) != -1) |
585 | return true; |
586 | } |
587 | |
588 | QHashedStringRef typeName(string.constData(), dot); |
589 | const bool isQtObject = (typeName == QLatin1String("Qt" )); |
590 | const QStringRef scopedEnumName = (dot2 != -1 ? string.midRef(position: dot + 1, n: dot2 - dot - 1) : QStringRef()); |
591 | // ### consider supporting scoped enums in Qt namespace |
592 | const QStringRef enumValue = string.midRef(position: !isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1); |
593 | |
594 | if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here? |
595 | // Allow enum assignment to ints. |
596 | bool ok; |
597 | int enumval = evaluateEnum(scope: typeName.toString(), enumName: scopedEnumName, enumValue, ok: &ok); |
598 | if (ok) { |
599 | if (!assignEnumToBinding(binding, enumName: enumValue, enumValue: enumval, isQtObject)) |
600 | return false; |
601 | } |
602 | return true; |
603 | } |
604 | QQmlType type; |
605 | imports->resolveType(type: typeName, type_return: &type, version_major: nullptr, version_minor: nullptr, ns_return: nullptr); |
606 | |
607 | if (!type.isValid() && !isQtObject) |
608 | return true; |
609 | |
610 | int value = 0; |
611 | bool ok = false; |
612 | |
613 | auto *tr = resolvedType(id: obj->inheritedTypeNameIndex); |
614 | if (type.isValid() && tr && tr->type == type) { |
615 | // When these two match, we can short cut the search |
616 | QMetaProperty mprop = propertyCache->firstCppMetaObject()->property(index: prop->coreIndex()); |
617 | QMetaEnum = mprop.enumerator(); |
618 | QByteArray enumName = enumValue.toUtf8(); |
619 | if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8()) |
620 | return true; |
621 | |
622 | if (mprop.isFlagType()) { |
623 | value = menum.keysToValue(keys: enumName.constData(), ok: &ok); |
624 | } else { |
625 | value = menum.keyToValue(key: enumName.constData(), ok: &ok); |
626 | } |
627 | } else { |
628 | // Otherwise we have to search the whole type |
629 | if (type.isValid()) { |
630 | if (!scopedEnumName.isEmpty()) |
631 | value = type.scopedEnumValue(engine: compiler->enginePrivate(), scopedEnumName, enumValue, ok: &ok); |
632 | else |
633 | value = type.enumValue(engine: compiler->enginePrivate(), QHashedStringRef(enumValue), ok: &ok); |
634 | } else { |
635 | QByteArray enumName = enumValue.toUtf8(); |
636 | const QMetaObject *metaObject = StaticQtMetaObject::get(); |
637 | for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) { |
638 | QMetaEnum e = metaObject->enumerator(index: ii); |
639 | value = e.keyToValue(key: enumName.constData(), ok: &ok); |
640 | } |
641 | } |
642 | } |
643 | |
644 | if (!ok) |
645 | return true; |
646 | |
647 | return assignEnumToBinding(binding, enumName: enumValue, enumValue: value, isQtObject); |
648 | } |
649 | |
650 | int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, const QStringRef &enumName, const QStringRef &enumValue, bool *ok) const |
651 | { |
652 | Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum" , "ok must not be a null pointer" ); |
653 | *ok = false; |
654 | |
655 | if (scope != QLatin1String("Qt" )) { |
656 | QQmlType type; |
657 | imports->resolveType(type: scope, type_return: &type, version_major: nullptr, version_minor: nullptr, ns_return: nullptr); |
658 | if (!type.isValid()) |
659 | return -1; |
660 | if (!enumName.isEmpty()) |
661 | return type.scopedEnumValue(engine: compiler->enginePrivate(), enumName, enumValue, ok); |
662 | return type.enumValue(engine: compiler->enginePrivate(), QHashedStringRef(enumValue.constData(), enumValue.length()), ok); |
663 | } |
664 | |
665 | const QMetaObject *mo = StaticQtMetaObject::get(); |
666 | int i = mo->enumeratorCount(); |
667 | const QByteArray ba = enumValue.toUtf8(); |
668 | while (i--) { |
669 | int v = mo->enumerator(index: i).keyToValue(key: ba.constData(), ok); |
670 | if (*ok) |
671 | return v; |
672 | } |
673 | return -1; |
674 | } |
675 | |
676 | QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler) |
677 | : QQmlCompilePass(typeCompiler) |
678 | , qmlObjects(*typeCompiler->qmlObjects()) |
679 | , customParsers(typeCompiler->customParserCache()) |
680 | { |
681 | } |
682 | |
683 | void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings() |
684 | { |
685 | scanObjectRecursively(/*root object*/objectIndex: 0); |
686 | for (int i = 0; i < qmlObjects.size(); ++i) |
687 | if (qmlObjects.at(i)->isInlineComponent) |
688 | scanObjectRecursively(objectIndex: i); |
689 | } |
690 | |
691 | void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings) |
692 | { |
693 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
694 | if (!annotateScriptBindings) |
695 | annotateScriptBindings = customParsers.contains(akey: obj->inheritedTypeNameIndex); |
696 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
697 | if (binding->type >= QV4::CompiledData::Binding::Type_Object) { |
698 | scanObjectRecursively(objectIndex: binding->value.objectIndex, annotateScriptBindings); |
699 | continue; |
700 | } else if (binding->type != QV4::CompiledData::Binding::Type_Script) |
701 | continue; |
702 | if (!annotateScriptBindings) |
703 | continue; |
704 | const QString script = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
705 | binding->stringIndex = compiler->registerString(str: script); |
706 | } |
707 | } |
708 | |
709 | QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) |
710 | : QQmlCompilePass(typeCompiler) |
711 | , qmlObjects(*typeCompiler->qmlObjects()) |
712 | , propertyCaches(typeCompiler->propertyCaches()) |
713 | { |
714 | } |
715 | |
716 | void QQmlAliasAnnotator::annotateBindingsToAliases() |
717 | { |
718 | for (int i = 0; i < qmlObjects.count(); ++i) { |
719 | QQmlPropertyCache *propertyCache = propertyCaches->at(index: i); |
720 | if (!propertyCache) |
721 | continue; |
722 | |
723 | const QmlIR::Object *obj = qmlObjects.at(i); |
724 | |
725 | QQmlPropertyResolver resolver(propertyCache); |
726 | QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
727 | |
728 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
729 | if (!binding->isValueBinding()) |
730 | continue; |
731 | bool notInRevision = false; |
732 | QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
733 | if (pd && pd->isAlias()) |
734 | binding->flags |= QV4::CompiledData::Binding::IsBindingToAlias; |
735 | } |
736 | } |
737 | } |
738 | |
739 | QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler) |
740 | : QQmlCompilePass(typeCompiler) |
741 | , qmlObjects(*typeCompiler->qmlObjects()) |
742 | , propertyCaches(typeCompiler->propertyCaches()) |
743 | { |
744 | |
745 | } |
746 | |
747 | void QQmlScriptStringScanner::scan() |
748 | { |
749 | const int scriptStringMetaType = qMetaTypeId<QQmlScriptString>(); |
750 | for (int i = 0; i < qmlObjects.count(); ++i) { |
751 | QQmlPropertyCache *propertyCache = propertyCaches->at(index: i); |
752 | if (!propertyCache) |
753 | continue; |
754 | |
755 | const QmlIR::Object *obj = qmlObjects.at(i); |
756 | |
757 | QQmlPropertyResolver resolver(propertyCache); |
758 | QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
759 | |
760 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
761 | if (binding->type != QV4::CompiledData::Binding::Type_Script) |
762 | continue; |
763 | bool notInRevision = false; |
764 | QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
765 | if (!pd || pd->propType() != scriptStringMetaType) |
766 | continue; |
767 | |
768 | QString script = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
769 | binding->stringIndex = compiler->registerString(str: script); |
770 | } |
771 | } |
772 | } |
773 | |
774 | QQmlComponentAndAliasResolver::QQmlComponentAndAliasResolver(QQmlTypeCompiler *typeCompiler) |
775 | : QQmlCompilePass(typeCompiler) |
776 | , enginePrivate(typeCompiler->enginePrivate()) |
777 | , pool(typeCompiler->memoryPool()) |
778 | , qmlObjects(typeCompiler->qmlObjects()) |
779 | , propertyCaches(std::move(typeCompiler->takePropertyCaches())) |
780 | { |
781 | } |
782 | |
783 | static bool isUsableComponent(const QMetaObject *metaObject) |
784 | { |
785 | // The metaObject is a component we're interested in if it either is a QQmlComponent itself |
786 | // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include |
787 | // qqmldelegatecomponent_p.h because it belongs to QtQmlModels. |
788 | |
789 | if (metaObject == &QQmlComponent::staticMetaObject) |
790 | return true; |
791 | |
792 | for (; metaObject; metaObject = metaObject->superClass()) { |
793 | if (qstrcmp(str1: metaObject->className(), str2: "QQmlAbstractDelegateComponent" ) == 0) |
794 | return true; |
795 | } |
796 | |
797 | return false; |
798 | } |
799 | |
800 | void QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents(const QmlIR::Object *obj, QQmlPropertyCache *propertyCache) |
801 | { |
802 | QQmlPropertyResolver propertyResolver(propertyCache); |
803 | |
804 | QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
805 | |
806 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
807 | if (binding->type != QV4::CompiledData::Binding::Type_Object) |
808 | continue; |
809 | if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) |
810 | continue; |
811 | |
812 | const QmlIR::Object *targetObject = qmlObjects->at(i: binding->value.objectIndex); |
813 | auto *tr = resolvedType(id: targetObject->inheritedTypeNameIndex); |
814 | Q_ASSERT(tr); |
815 | |
816 | const QMetaObject *firstMetaObject = nullptr; |
817 | if (tr->type.isValid()) |
818 | firstMetaObject = tr->type.metaObject(); |
819 | else if (const auto compilationUnit = tr->compilationUnit()) |
820 | firstMetaObject = compilationUnit->rootPropertyCache()->firstCppMetaObject(); |
821 | if (isUsableComponent(metaObject: firstMetaObject)) |
822 | continue; |
823 | // if here, not a QQmlComponent, so needs wrapping |
824 | |
825 | QQmlPropertyData *pd = nullptr; |
826 | if (binding->propertyNameIndex != quint32(0)) { |
827 | bool notInRevision = false; |
828 | pd = propertyResolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision); |
829 | } else { |
830 | pd = defaultProperty; |
831 | } |
832 | if (!pd || !pd->isQObject()) |
833 | continue; |
834 | |
835 | QQmlPropertyCache *pc = enginePrivate->rawPropertyCacheForType(pd->propType(), minorVersion: pd->typeMinorVersion()); |
836 | const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr; |
837 | while (mo) { |
838 | if (mo == &QQmlComponent::staticMetaObject) |
839 | break; |
840 | mo = mo->superClass(); |
841 | } |
842 | |
843 | if (!mo) |
844 | continue; |
845 | |
846 | // emulate "import Qml 2.0 as QmlInternals" and then wrap the component in "QmlInternals.Component {}" |
847 | QQmlType componentType = QQmlMetaType::qmlType(&QQmlComponent::staticMetaObject); |
848 | Q_ASSERT(componentType.isValid()); |
849 | const QString qualifier = QStringLiteral("QmlInternals" ); |
850 | |
851 | compiler->addImport(module: componentType.module(), qualifier, majorVersion: componentType.majorVersion(), minorVersion: componentType.minorVersion()); |
852 | |
853 | QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); |
854 | syntheticComponent->init(pool, typeNameIndex: compiler->registerString(str: qualifier + QLatin1Char('.') + componentType.elementName()), idIndex: compiler->registerString(str: QString())); |
855 | syntheticComponent->location = binding->valueLocation; |
856 | syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; |
857 | |
858 | if (!containsResolvedType(id: syntheticComponent->inheritedTypeNameIndex)) { |
859 | auto typeRef = new QV4::ResolvedTypeReference; |
860 | typeRef->type = componentType; |
861 | typeRef->majorVersion = componentType.majorVersion(); |
862 | typeRef->minorVersion = componentType.minorVersion(); |
863 | insertResolvedType(id: syntheticComponent->inheritedTypeNameIndex, value: typeRef); |
864 | } |
865 | |
866 | qmlObjects->append(t: syntheticComponent); |
867 | const int componentIndex = qmlObjects->count() - 1; |
868 | // Keep property caches symmetric |
869 | QQmlPropertyCache *componentCache = enginePrivate->cache(metaObject: &QQmlComponent::staticMetaObject); |
870 | propertyCaches.append(cache: componentCache); |
871 | |
872 | QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); |
873 | *syntheticBinding = *binding; |
874 | syntheticBinding->type = QV4::CompiledData::Binding::Type_Object; |
875 | QString error = syntheticComponent->appendBinding(b: syntheticBinding, /*isListBinding*/false); |
876 | Q_ASSERT(error.isEmpty()); |
877 | Q_UNUSED(error); |
878 | |
879 | binding->value.objectIndex = componentIndex; |
880 | |
881 | componentRoots.append(t: componentIndex); |
882 | } |
883 | } |
884 | |
885 | bool QQmlComponentAndAliasResolver::resolve() |
886 | { |
887 | // Detect real Component {} objects as well as implicitly defined components, such as |
888 | // someItemDelegate: Item {} |
889 | // In the implicit case Item is surrounded by a synthetic Component {} because the property |
890 | // on the left hand side is of QQmlComponent type. |
891 | const int objCountWithoutSynthesizedComponents = qmlObjects->count(); |
892 | for (int i = 0; i < objCountWithoutSynthesizedComponents; ++i) { |
893 | QmlIR::Object *obj = qmlObjects->at(i); |
894 | if (obj->isInlineComponent) { |
895 | componentRoots.append(t: i); |
896 | continue; |
897 | } |
898 | QQmlPropertyCache *cache = propertyCaches.at(index: i); |
899 | if (obj->inheritedTypeNameIndex == 0 && !cache) |
900 | continue; |
901 | |
902 | bool isExplicitComponent = false; |
903 | |
904 | if (obj->inheritedTypeNameIndex) { |
905 | auto *tref = resolvedType(id: obj->inheritedTypeNameIndex); |
906 | Q_ASSERT(tref); |
907 | if (tref->type.metaObject() == &QQmlComponent::staticMetaObject) |
908 | isExplicitComponent = true; |
909 | } |
910 | if (!isExplicitComponent) { |
911 | if (cache) |
912 | findAndRegisterImplicitComponents(obj, propertyCache: cache); |
913 | continue; |
914 | } |
915 | |
916 | obj->flags |= QV4::CompiledData::Object::IsComponent; |
917 | |
918 | if (obj->functionCount() > 0) |
919 | COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new functions." )); |
920 | if (obj->propertyCount() > 0 || obj->aliasCount() > 0) |
921 | COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new properties." )); |
922 | if (obj->signalCount() > 0) |
923 | COMPILE_EXCEPTION(obj, tr("Component objects cannot declare new signals." )); |
924 | |
925 | if (obj->bindingCount() == 0) |
926 | COMPILE_EXCEPTION(obj, tr("Cannot create empty component specification" )); |
927 | |
928 | const QmlIR::Binding *rootBinding = obj->firstBinding(); |
929 | |
930 | for (const QmlIR::Binding *b = rootBinding; b; b = b->next) { |
931 | if (b->propertyNameIndex != 0) |
932 | COMPILE_EXCEPTION(rootBinding, tr("Component elements may not contain properties other than id" )); |
933 | } |
934 | |
935 | if (rootBinding->next || rootBinding->type != QV4::CompiledData::Binding::Type_Object) |
936 | COMPILE_EXCEPTION(obj, tr("Invalid component body specification" )); |
937 | |
938 | // For the root object, we are going to collect ids/aliases and resolve them for as a separate |
939 | // last pass. |
940 | if (i != 0) |
941 | componentRoots.append(t: i); |
942 | |
943 | } |
944 | |
945 | for (int i = 0; i < componentRoots.count(); ++i) { |
946 | QmlIR::Object *component = qmlObjects->at(i: componentRoots.at(i)); |
947 | const QmlIR::Binding *rootBinding = component->firstBinding(); |
948 | |
949 | _idToObjectIndex.clear(); |
950 | |
951 | _objectsWithAliases.clear(); |
952 | |
953 | if (!collectIdsAndAliases(objectIndex: component->isInlineComponent ? componentRoots.at(i) : rootBinding->value.objectIndex)) |
954 | return false; |
955 | |
956 | component->namedObjectsInComponent.allocate(pool, container: _idToObjectIndex); |
957 | |
958 | if (!resolveAliases(componentIndex: componentRoots.at(i))) |
959 | return false; |
960 | } |
961 | |
962 | // Collect ids and aliases for root |
963 | _idToObjectIndex.clear(); |
964 | _objectsWithAliases.clear(); |
965 | |
966 | collectIdsAndAliases(/*root object*/objectIndex: 0); |
967 | |
968 | QmlIR::Object *rootComponent = qmlObjects->at(/*root object*/i: 0); |
969 | rootComponent->namedObjectsInComponent.allocate(pool, container: _idToObjectIndex); |
970 | |
971 | if (!resolveAliases(/*root object*/componentIndex: 0)) |
972 | return false; |
973 | |
974 | // Implicit component insertion may have added objects and thus we also need |
975 | // to extend the symmetric propertyCaches. |
976 | compiler->setPropertyCaches(std::move(propertyCaches)); |
977 | compiler->setComponentRoots(componentRoots); |
978 | |
979 | return true; |
980 | } |
981 | |
982 | bool QQmlComponentAndAliasResolver::collectIdsAndAliases(int objectIndex) |
983 | { |
984 | QmlIR::Object *obj = qmlObjects->at(i: objectIndex); |
985 | |
986 | if (obj->idNameIndex != 0) { |
987 | if (_idToObjectIndex.contains(akey: obj->idNameIndex)) { |
988 | recordError(location: obj->locationOfIdProperty, description: tr(sourceText: "id is not unique" )); |
989 | return false; |
990 | } |
991 | obj->id = _idToObjectIndex.count(); |
992 | _idToObjectIndex.insert(akey: obj->idNameIndex, avalue: objectIndex); |
993 | } |
994 | |
995 | if (obj->aliasCount() > 0) |
996 | _objectsWithAliases.append(t: objectIndex); |
997 | |
998 | // Stop at Component boundary |
999 | if (obj->flags & QV4::CompiledData::Object::IsComponent && objectIndex != /*root object*/0) |
1000 | return true; |
1001 | |
1002 | for (const QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
1003 | if (binding->type != QV4::CompiledData::Binding::Type_Object |
1004 | && binding->type != QV4::CompiledData::Binding::Type_AttachedProperty |
1005 | && binding->type != QV4::CompiledData::Binding::Type_GroupProperty) |
1006 | continue; |
1007 | |
1008 | if (!collectIdsAndAliases(objectIndex: binding->value.objectIndex)) |
1009 | return false; |
1010 | } |
1011 | |
1012 | return true; |
1013 | } |
1014 | |
1015 | bool QQmlComponentAndAliasResolver::resolveAliases(int componentIndex) |
1016 | { |
1017 | if (_objectsWithAliases.isEmpty()) |
1018 | return true; |
1019 | |
1020 | QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> aliasCacheCreator(&propertyCaches, compiler); |
1021 | |
1022 | bool atLeastOneAliasResolved; |
1023 | do { |
1024 | atLeastOneAliasResolved = false; |
1025 | QVector<int> pendingObjects; |
1026 | |
1027 | for (int objectIndex: qAsConst(t&: _objectsWithAliases)) { |
1028 | |
1029 | QQmlError error; |
1030 | const auto result = resolveAliasesInObject(objectIndex, error: &error); |
1031 | |
1032 | if (error.isValid()) { |
1033 | recordError(error); |
1034 | return false; |
1035 | } |
1036 | |
1037 | if (result == AllAliasesResolved) { |
1038 | QQmlError error = aliasCacheCreator.appendAliasesToPropertyCache(component: *qmlObjects->at(i: componentIndex), objectIndex, enginePriv: enginePrivate); |
1039 | if (error.isValid()) { |
1040 | recordError(error); |
1041 | return false; |
1042 | } |
1043 | atLeastOneAliasResolved = true; |
1044 | } else if (result == SomeAliasesResolved) { |
1045 | atLeastOneAliasResolved = true; |
1046 | pendingObjects.append(t: objectIndex); |
1047 | } else { |
1048 | pendingObjects.append(t: objectIndex); |
1049 | } |
1050 | } |
1051 | qSwap(value1&: _objectsWithAliases, value2&: pendingObjects); |
1052 | } while (!_objectsWithAliases.isEmpty() && atLeastOneAliasResolved); |
1053 | |
1054 | if (!atLeastOneAliasResolved && !_objectsWithAliases.isEmpty()) { |
1055 | const QmlIR::Object *obj = qmlObjects->at(i: _objectsWithAliases.first()); |
1056 | for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { |
1057 | if (!(alias->flags & QV4::CompiledData::Alias::Resolved)) { |
1058 | recordError(location: alias->location, description: tr(sourceText: "Circular alias reference detected" )); |
1059 | return false; |
1060 | } |
1061 | } |
1062 | } |
1063 | |
1064 | return true; |
1065 | } |
1066 | |
1067 | QQmlComponentAndAliasResolver::AliasResolutionResult |
1068 | QQmlComponentAndAliasResolver::resolveAliasesInObject(int objectIndex, |
1069 | QQmlError *error) |
1070 | { |
1071 | const QmlIR::Object * const obj = qmlObjects->at(i: objectIndex); |
1072 | if (!obj->aliasCount()) |
1073 | return AllAliasesResolved; |
1074 | |
1075 | int numResolvedAliases = 0; |
1076 | bool seenUnresolvedAlias = false; |
1077 | |
1078 | for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { |
1079 | if (alias->flags & QV4::CompiledData::Alias::Resolved) |
1080 | continue; |
1081 | |
1082 | seenUnresolvedAlias = true; |
1083 | |
1084 | const int idIndex = alias->idIndex; |
1085 | const int targetObjectIndex = _idToObjectIndex.value(akey: idIndex, adefaultValue: -1); |
1086 | if (targetObjectIndex == -1) { |
1087 | *error = qQmlCompileError( |
1088 | location: alias->referenceLocation, |
1089 | description: tr(sourceText: "Invalid alias reference. Unable to find id \"%1\"" ).arg(a: stringAt(idx: idIndex))); |
1090 | break; |
1091 | } |
1092 | |
1093 | const QmlIR::Object *targetObject = qmlObjects->at(i: targetObjectIndex); |
1094 | Q_ASSERT(targetObject->id >= 0); |
1095 | alias->targetObjectId = targetObject->id; |
1096 | alias->aliasToLocalAlias = false; |
1097 | |
1098 | const QString aliasPropertyValue = stringAt(idx: alias->propertyNameIndex); |
1099 | |
1100 | QStringRef property; |
1101 | QStringRef subProperty; |
1102 | |
1103 | const int propertySeparator = aliasPropertyValue.indexOf(c: QLatin1Char('.')); |
1104 | if (propertySeparator != -1) { |
1105 | property = aliasPropertyValue.leftRef(n: propertySeparator); |
1106 | subProperty = aliasPropertyValue.midRef(position: propertySeparator + 1); |
1107 | } else |
1108 | property = QStringRef(&aliasPropertyValue, 0, aliasPropertyValue.length()); |
1109 | |
1110 | QQmlPropertyIndex propIdx; |
1111 | |
1112 | if (property.isEmpty()) { |
1113 | alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; |
1114 | } else { |
1115 | QQmlPropertyCache *targetCache = propertyCaches.at(index: targetObjectIndex); |
1116 | if (!targetCache) { |
1117 | *error = qQmlCompileError( |
1118 | location: alias->referenceLocation, |
1119 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: property.toString())); |
1120 | break; |
1121 | } |
1122 | |
1123 | QQmlPropertyResolver resolver(targetCache); |
1124 | |
1125 | QQmlPropertyData *targetProperty = resolver.property(name: property.toString()); |
1126 | |
1127 | // If it's an alias that we haven't resolved yet, try again later. |
1128 | if (!targetProperty) { |
1129 | bool aliasPointsToOtherAlias = false; |
1130 | int localAliasIndex = 0; |
1131 | for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { |
1132 | if (stringAt(idx: targetAlias->nameIndex) == property) { |
1133 | aliasPointsToOtherAlias = true; |
1134 | break; |
1135 | } |
1136 | } |
1137 | if (aliasPointsToOtherAlias) { |
1138 | if (targetObjectIndex == objectIndex) { |
1139 | alias->localAliasIndex = localAliasIndex; |
1140 | alias->aliasToLocalAlias = true; |
1141 | alias->flags |= QV4::CompiledData::Alias::Resolved; |
1142 | ++numResolvedAliases; |
1143 | continue; |
1144 | } |
1145 | |
1146 | // restore |
1147 | alias->idIndex = idIndex; |
1148 | // Try again later and resolve the target alias first. |
1149 | break; |
1150 | } |
1151 | } |
1152 | |
1153 | if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { |
1154 | *error = qQmlCompileError( |
1155 | location: alias->referenceLocation, |
1156 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: property.toString())); |
1157 | break; |
1158 | } |
1159 | |
1160 | propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); |
1161 | |
1162 | if (!subProperty.isEmpty()) { |
1163 | const QMetaObject *valueTypeMetaObject = QQmlValueTypeFactory::metaObjectForMetaType(type: targetProperty->propType()); |
1164 | if (!valueTypeMetaObject) { |
1165 | // could be a deep alias |
1166 | bool isDeepAlias = subProperty.at(i: 0).isLower(); |
1167 | if (isDeepAlias) { |
1168 | isDeepAlias = false; |
1169 | for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { |
1170 | auto binding = *it; |
1171 | if (compiler->stringAt(idx: binding.propertyNameIndex) == property) { |
1172 | resolver = QQmlPropertyResolver(propertyCaches.at(index: binding.value.objectIndex)); |
1173 | QQmlPropertyData *actualProperty = resolver.property(name: subProperty.toString()); |
1174 | if (actualProperty) { |
1175 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); |
1176 | isDeepAlias = true; |
1177 | } |
1178 | } |
1179 | } |
1180 | } |
1181 | if (!isDeepAlias) { |
1182 | *error = qQmlCompileError( |
1183 | location: alias->referenceLocation, |
1184 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: subProperty.toString())); |
1185 | break; |
1186 | } |
1187 | } else { |
1188 | |
1189 | int valueTypeIndex = |
1190 | valueTypeMetaObject->indexOfProperty(name: subProperty.toString().toUtf8().constData()); |
1191 | if (valueTypeIndex == -1) { |
1192 | *error = qQmlCompileError( |
1193 | location: alias->referenceLocation, |
1194 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: subProperty.toString())); |
1195 | break; |
1196 | } |
1197 | Q_ASSERT(valueTypeIndex <= 0x0000FFFF); |
1198 | |
1199 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); |
1200 | } |
1201 | } else { |
1202 | if (targetProperty->isQObject()) |
1203 | alias->flags |= QV4::CompiledData::Alias::AliasPointsToPointerObject; |
1204 | } |
1205 | } |
1206 | |
1207 | alias->encodedMetaPropertyIndex = propIdx.toEncoded(); |
1208 | alias->flags |= QV4::CompiledData::Alias::Resolved; |
1209 | numResolvedAliases++; |
1210 | } |
1211 | |
1212 | if (numResolvedAliases == 0) |
1213 | return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; |
1214 | |
1215 | return SomeAliasesResolved; |
1216 | } |
1217 | |
1218 | QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) |
1219 | : QQmlCompilePass(typeCompiler) |
1220 | , qmlObjects(typeCompiler->qmlObjects()) |
1221 | , propertyCaches(typeCompiler->propertyCaches()) |
1222 | , customParsers(typeCompiler->customParserCache()) |
1223 | , _seenObjectWithId(false) |
1224 | { |
1225 | } |
1226 | |
1227 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject() |
1228 | { |
1229 | for (int i = 0; i < qmlObjects->size(); ++i) |
1230 | if (qmlObjects->at(i)->isInlineComponent) |
1231 | scanObject(objectIndex: i); |
1232 | return scanObject(/*root object*/objectIndex: 0); |
1233 | } |
1234 | |
1235 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject(int objectIndex) |
1236 | { |
1237 | QmlIR::Object *obj = qmlObjects->at(i: objectIndex); |
1238 | if (obj->idNameIndex != 0) |
1239 | _seenObjectWithId = true; |
1240 | |
1241 | if (obj->flags & QV4::CompiledData::Object::IsComponent && !obj->isInlineComponent) { |
1242 | Q_ASSERT(obj->bindingCount() == 1); |
1243 | const QV4::CompiledData::Binding *componentBinding = obj->firstBinding(); |
1244 | Q_ASSERT(componentBinding->type == QV4::CompiledData::Binding::Type_Object); |
1245 | return scanObject(objectIndex: componentBinding->value.objectIndex); |
1246 | } |
1247 | |
1248 | QQmlPropertyCache *propertyCache = propertyCaches->at(index: objectIndex); |
1249 | if (!propertyCache) |
1250 | return true; |
1251 | |
1252 | QString defaultPropertyName; |
1253 | QQmlPropertyData *defaultProperty = nullptr; |
1254 | if (obj->indexOfDefaultPropertyOrAlias != -1) { |
1255 | QQmlPropertyCache *cache = propertyCache->parent(); |
1256 | defaultPropertyName = cache->defaultPropertyName(); |
1257 | defaultProperty = cache->defaultProperty(); |
1258 | } else { |
1259 | defaultPropertyName = propertyCache->defaultPropertyName(); |
1260 | defaultProperty = propertyCache->defaultProperty(); |
1261 | } |
1262 | |
1263 | QQmlCustomParser *customParser = customParsers.value(akey: obj->inheritedTypeNameIndex); |
1264 | |
1265 | QQmlPropertyResolver propertyResolver(propertyCache); |
1266 | |
1267 | QStringList deferredPropertyNames; |
1268 | { |
1269 | const QMetaObject *mo = propertyCache->firstCppMetaObject(); |
1270 | const int namesIndex = mo->indexOfClassInfo(name: "DeferredPropertyNames" ); |
1271 | if (namesIndex != -1) { |
1272 | QMetaClassInfo classInfo = mo->classInfo(index: namesIndex); |
1273 | deferredPropertyNames = QString::fromUtf8(str: classInfo.value()).split(sep: QLatin1Char(',')); |
1274 | } |
1275 | } |
1276 | |
1277 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
1278 | QQmlPropertyData *pd = nullptr; |
1279 | QString name = stringAt(idx: binding->propertyNameIndex); |
1280 | |
1281 | if (customParser) { |
1282 | if (binding->type == QV4::CompiledData::Binding::Type_AttachedProperty) { |
1283 | if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { |
1284 | binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; |
1285 | obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; |
1286 | continue; |
1287 | } |
1288 | } else if (QmlIR::IRBuilder::isSignalPropertyName(name) |
1289 | && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { |
1290 | obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; |
1291 | binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; |
1292 | continue; |
1293 | } |
1294 | } |
1295 | |
1296 | if (name.isEmpty()) { |
1297 | pd = defaultProperty; |
1298 | name = defaultPropertyName; |
1299 | } else { |
1300 | if (name.constData()->isUpper()) |
1301 | continue; |
1302 | |
1303 | bool notInRevision = false; |
1304 | pd = propertyResolver.property(name, notInRevision: ¬InRevision, |
1305 | check: QQmlPropertyResolver::CheckRevision); |
1306 | } |
1307 | |
1308 | bool seenSubObjectWithId = false; |
1309 | |
1310 | if (binding->type >= QV4::CompiledData::Binding::Type_Object && (pd || binding->isAttachedProperty())) { |
1311 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1312 | const bool subObjectValid = scanObject(objectIndex: binding->value.objectIndex); |
1313 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1314 | if (!subObjectValid) |
1315 | return false; |
1316 | _seenObjectWithId |= seenSubObjectWithId; |
1317 | } |
1318 | |
1319 | if (!seenSubObjectWithId && binding->type != QV4::CompiledData::Binding::Type_GroupProperty |
1320 | && !deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(str: name)) { |
1321 | |
1322 | binding->flags |= QV4::CompiledData::Binding::IsDeferredBinding; |
1323 | obj->flags |= QV4::CompiledData::Object::HasDeferredBindings; |
1324 | } |
1325 | |
1326 | if (binding->flags & QV4::CompiledData::Binding::IsSignalHandlerExpression |
1327 | || binding->flags & QV4::CompiledData::Binding::IsSignalHandlerObject) |
1328 | continue; |
1329 | |
1330 | if (!pd) { |
1331 | if (customParser) { |
1332 | obj->flags |= QV4::CompiledData::Object::HasCustomParserBindings; |
1333 | binding->flags |= QV4::CompiledData::Binding::IsCustomParserBinding; |
1334 | } |
1335 | } |
1336 | } |
1337 | |
1338 | return true; |
1339 | } |
1340 | |
1341 | QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler) |
1342 | : QQmlCompilePass(typeCompiler) |
1343 | , qmlObjects(*typeCompiler->qmlObjects()) |
1344 | , propertyCaches(typeCompiler->propertyCaches()) |
1345 | { |
1346 | |
1347 | } |
1348 | |
1349 | void QQmlDefaultPropertyMerger::mergeDefaultProperties() |
1350 | { |
1351 | for (int i = 0; i < qmlObjects.count(); ++i) |
1352 | mergeDefaultProperties(objectIndex: i); |
1353 | } |
1354 | |
1355 | void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) |
1356 | { |
1357 | QQmlPropertyCache *propertyCache = propertyCaches->at(index: objectIndex); |
1358 | if (!propertyCache) |
1359 | return; |
1360 | |
1361 | QmlIR::Object *object = qmlObjects.at(i: objectIndex); |
1362 | |
1363 | QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); |
1364 | QmlIR::Binding *bindingsToReinsert = nullptr; |
1365 | QmlIR::Binding *tail = nullptr; |
1366 | |
1367 | QmlIR::Binding *previousBinding = nullptr; |
1368 | QmlIR::Binding *binding = object->firstBinding(); |
1369 | while (binding) { |
1370 | if (binding->propertyNameIndex == quint32(0) || stringAt(idx: binding->propertyNameIndex) != defaultProperty) { |
1371 | previousBinding = binding; |
1372 | binding = binding->next; |
1373 | continue; |
1374 | } |
1375 | |
1376 | QmlIR::Binding *toReinsert = binding; |
1377 | binding = object->unlinkBinding(before: previousBinding, binding); |
1378 | |
1379 | if (!tail) { |
1380 | bindingsToReinsert = toReinsert; |
1381 | tail = toReinsert; |
1382 | } else { |
1383 | tail->next = toReinsert; |
1384 | tail = tail->next; |
1385 | } |
1386 | tail->next = nullptr; |
1387 | } |
1388 | |
1389 | binding = bindingsToReinsert; |
1390 | while (binding) { |
1391 | QmlIR::Binding *toReinsert = binding; |
1392 | binding = binding->next; |
1393 | object->insertSorted(b: toReinsert); |
1394 | } |
1395 | } |
1396 | |
1397 | QT_END_NAMESPACE |
1398 | |