1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmltypecompiler_p.h" |
5 | |
6 | #include <private/qqmlobjectcreator_p.h> |
7 | #include <private/qqmlcustomparser_p.h> |
8 | #include <private/qqmlvmemetaobject_p.h> |
9 | #include <private/qqmlcomponent_p.h> |
10 | #include <private/qqmlpropertyresolver_p.h> |
11 | #include <private/qqmlcomponentandaliasresolver_p.h> |
12 | |
13 | #define COMPILE_EXCEPTION(token, desc) \ |
14 | { \ |
15 | recordError((token)->location, desc); \ |
16 | return false; \ |
17 | } |
18 | |
19 | QT_BEGIN_NAMESPACE |
20 | |
21 | DEFINE_BOOL_CONFIG_OPTION( |
22 | disableInternalDeferredProperties, QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES); |
23 | |
24 | Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler" ); |
25 | |
26 | QQmlTypeCompiler::QQmlTypeCompiler(QQmlEnginePrivate *engine, QQmlTypeData *typeData, |
27 | QmlIR::Document *parsedQML, const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, |
28 | QV4::ResolvedTypeReferenceMap *resolvedTypeCache, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) |
29 | : resolvedTypes(resolvedTypeCache) |
30 | , engine(engine) |
31 | , dependencyHasher(dependencyHasher) |
32 | , document(parsedQML) |
33 | , typeNameCache(typeNameCache) |
34 | , typeData(typeData) |
35 | { |
36 | } |
37 | |
38 | QQmlRefPointer<QV4::ExecutableCompilationUnit> QQmlTypeCompiler::compile() |
39 | { |
40 | // Build property caches and VME meta object data |
41 | |
42 | for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd(); |
43 | it != end; ++it) { |
44 | QQmlCustomParser *customParser = (*it)->type().customParser(); |
45 | if (customParser) |
46 | customParsers.insert(key: it.key(), value: customParser); |
47 | } |
48 | |
49 | QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; |
50 | |
51 | |
52 | { |
53 | QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, &pendingGroupPropertyBindings, |
54 | engine, this, imports(), typeData->typeClassName()); |
55 | QQmlError cycleError = propertyCacheBuilder.verifyNoICCycle(); |
56 | if (cycleError.isValid()) { |
57 | recordError(e: cycleError); |
58 | return nullptr; |
59 | } |
60 | QQmlPropertyCacheCreatorBase::IncrementalResult result; |
61 | do { |
62 | result = propertyCacheBuilder.buildMetaObjectsIncrementally(); |
63 | const QQmlError &error = result.error; |
64 | if (error.isValid()) { |
65 | recordError(e: error); |
66 | return nullptr; |
67 | } else { |
68 | // Resolve component boundaries and aliases |
69 | |
70 | QQmlComponentAndAliasResolver resolver(this, enginePrivate(), &m_propertyCaches); |
71 | if (QQmlError error = resolver.resolve(root: result.processedRoot); error.isValid()) { |
72 | recordError(e: error); |
73 | return nullptr; |
74 | } |
75 | pendingGroupPropertyBindings.resolveMissingPropertyCaches(propertyCaches: &m_propertyCaches); |
76 | pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed |
77 | } |
78 | } while (result.canResume); |
79 | } |
80 | |
81 | { |
82 | QQmlDefaultPropertyMerger merger(this); |
83 | merger.mergeDefaultProperties(); |
84 | } |
85 | |
86 | { |
87 | SignalHandlerResolver converter(this); |
88 | if (!converter.resolveSignalHandlerExpressions()) |
89 | return nullptr; |
90 | } |
91 | |
92 | { |
93 | QQmlEnumTypeResolver enumResolver(this); |
94 | if (!enumResolver.resolveEnumBindings()) |
95 | return nullptr; |
96 | } |
97 | |
98 | { |
99 | QQmlCustomParserScriptIndexer cpi(this); |
100 | cpi.annotateBindingsWithScriptStrings(); |
101 | } |
102 | |
103 | { |
104 | QQmlAliasAnnotator annotator(this); |
105 | annotator.annotateBindingsToAliases(); |
106 | } |
107 | |
108 | { |
109 | QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); |
110 | if (!deferredAndCustomParserBindingScanner.scanObject()) |
111 | return nullptr; |
112 | } |
113 | |
114 | if (!document->javaScriptCompilationUnit.unitData()) { |
115 | // Compile JS binding expressions and signal handlers if necessary |
116 | { |
117 | // We can compile script strings ahead of time, but they must be compiled |
118 | // without type optimizations as their scope is always entirely dynamic. |
119 | QQmlScriptStringScanner sss(this); |
120 | sss.scan(); |
121 | } |
122 | |
123 | document->jsModule.fileName = typeData->urlString(); |
124 | document->jsModule.finalUrl = typeData->finalUrlString(); |
125 | QmlIR::JSCodeGen v4CodeGenerator(document, engine->v4engine()->illegalNames()); |
126 | for (QmlIR::Object *object : std::as_const(t&: document->objects)) { |
127 | if (!v4CodeGenerator.generateRuntimeFunctions(object)) { |
128 | Q_ASSERT(v4CodeGenerator.hasError()); |
129 | recordError(message: v4CodeGenerator.error()); |
130 | return nullptr; |
131 | } |
132 | } |
133 | document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/generateUnitData: false); |
134 | } |
135 | |
136 | // Generate QML compiled type data structures |
137 | |
138 | QmlIR::QmlUnitGenerator qmlGenerator; |
139 | qmlGenerator.generate(output&: *document, dependencyHasher); |
140 | |
141 | if (!errors.isEmpty()) |
142 | return nullptr; |
143 | |
144 | QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit |
145 | = QV4::ExecutableCompilationUnit::create(compilationUnit: std::move( |
146 | document->javaScriptCompilationUnit)); |
147 | compilationUnit->typeNameCache = typeNameCache; |
148 | compilationUnit->resolvedTypes = *resolvedTypes; |
149 | compilationUnit->propertyCaches = std::move(m_propertyCaches); |
150 | Q_ASSERT(compilationUnit->propertyCaches.count() == static_cast<int>(compilationUnit->objectCount())); |
151 | return compilationUnit; |
152 | } |
153 | |
154 | void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) |
155 | { |
156 | QQmlError error; |
157 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: location.line())); |
158 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: location.column())); |
159 | error.setDescription(description); |
160 | error.setUrl(url()); |
161 | errors << error; |
162 | } |
163 | |
164 | void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message) |
165 | { |
166 | QQmlError error; |
167 | error.setDescription(message.message); |
168 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startLine)); |
169 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startColumn)); |
170 | error.setUrl(url()); |
171 | errors << error; |
172 | } |
173 | |
174 | void QQmlTypeCompiler::recordError(const QQmlError &e) |
175 | { |
176 | QQmlError error = e; |
177 | error.setUrl(url()); |
178 | errors << error; |
179 | } |
180 | |
181 | QString QQmlTypeCompiler::stringAt(int idx) const |
182 | { |
183 | return document->stringAt(index: idx); |
184 | } |
185 | |
186 | int QQmlTypeCompiler::registerString(const QString &str) |
187 | { |
188 | return document->jsGenerator.registerString(str); |
189 | } |
190 | |
191 | int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v) |
192 | { |
193 | return document->jsGenerator.registerConstant(v); |
194 | } |
195 | |
196 | const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const |
197 | { |
198 | return document->javaScriptCompilationUnit.unitData(); |
199 | } |
200 | |
201 | const QQmlImports *QQmlTypeCompiler::imports() const |
202 | { |
203 | return typeData->imports(); |
204 | } |
205 | |
206 | QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const |
207 | { |
208 | return &document->objects; |
209 | } |
210 | |
211 | QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() |
212 | { |
213 | return &m_propertyCaches; |
214 | } |
215 | |
216 | const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const |
217 | { |
218 | return &m_propertyCaches; |
219 | } |
220 | |
221 | QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() |
222 | { |
223 | return document->jsParserEngine.pool(); |
224 | } |
225 | |
226 | QStringView QQmlTypeCompiler::newStringRef(const QString &string) |
227 | { |
228 | return document->jsParserEngine.newStringRef(text: string); |
229 | } |
230 | |
231 | const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const |
232 | { |
233 | return &document->jsGenerator.stringTable; |
234 | } |
235 | |
236 | QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const |
237 | { |
238 | return object->bindingAsString(doc: document, scriptIndex); |
239 | } |
240 | |
241 | void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, QTypeRevision version) |
242 | { |
243 | const quint32 moduleIdx = registerString(str: module); |
244 | const quint32 qualifierIdx = registerString(str: qualifier); |
245 | |
246 | for (int i = 0, count = document->imports.size(); i < count; ++i) { |
247 | const QV4::CompiledData::Import *existingImport = document->imports.at(i); |
248 | if (existingImport->type == QV4::CompiledData::Import::ImportLibrary |
249 | && existingImport->uriIndex == moduleIdx |
250 | && existingImport->qualifierIndex == qualifierIdx) |
251 | return; |
252 | } |
253 | auto pool = memoryPool(); |
254 | QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>(); |
255 | import->type = QV4::CompiledData::Import::ImportLibrary; |
256 | import->version = version; |
257 | import->uriIndex = moduleIdx; |
258 | import->qualifierIndex = qualifierIdx; |
259 | document->imports.append(t: import); |
260 | } |
261 | |
262 | CompositeMetaTypeIds QQmlTypeCompiler::typeIdsForComponent(const QString &inlineComponentName) const |
263 | { |
264 | return typeData->typeIds(inlineComponentName); |
265 | } |
266 | |
267 | QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) |
268 | : compiler(typeCompiler) |
269 | { |
270 | } |
271 | |
272 | SignalHandlerResolver::SignalHandlerResolver(QQmlTypeCompiler *typeCompiler) |
273 | : QQmlCompilePass(typeCompiler) |
274 | , enginePrivate(typeCompiler->enginePrivate()) |
275 | , qmlObjects(*typeCompiler->qmlObjects()) |
276 | , imports(typeCompiler->imports()) |
277 | , customParsers(typeCompiler->customParserCache()) |
278 | , illegalNames(typeCompiler->enginePrivate()->v4engine()->illegalNames()) |
279 | , propertyCaches(typeCompiler->propertyCaches()) |
280 | { |
281 | } |
282 | |
283 | bool SignalHandlerResolver::resolveSignalHandlerExpressions() |
284 | { |
285 | for (int objectIndex = 0; objectIndex < qmlObjects.size(); ++objectIndex) { |
286 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
287 | QQmlPropertyCache::ConstPtr cache = propertyCaches->at(index: objectIndex); |
288 | if (!cache) |
289 | continue; |
290 | if (QQmlCustomParser *customParser = customParsers.value(key: obj->inheritedTypeNameIndex)) { |
291 | if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) |
292 | continue; |
293 | } |
294 | const QString elementName = stringAt(idx: obj->inheritedTypeNameIndex); |
295 | if (!resolveSignalHandlerExpressions(obj, typeName: elementName, propertyCache: cache)) |
296 | return false; |
297 | } |
298 | return true; |
299 | } |
300 | |
301 | bool SignalHandlerResolver::resolveSignalHandlerExpressions( |
302 | const QmlIR::Object *obj, const QString &typeName, |
303 | const QQmlPropertyCache::ConstPtr &propertyCache) |
304 | { |
305 | // map from signal name defined in qml itself to list of parameters |
306 | QHash<QString, QStringList> customSignals; |
307 | |
308 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
309 | const QString bindingPropertyName = stringAt(idx: binding->propertyNameIndex); |
310 | // Attached property? |
311 | const QV4::CompiledData::Binding::Type bindingType = binding->type(); |
312 | if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) { |
313 | const QmlIR::Object *attachedObj = qmlObjects.at(i: binding->value.objectIndex); |
314 | auto *typeRef = resolvedType(id: binding->propertyNameIndex); |
315 | QQmlType type = typeRef ? typeRef->type() : QQmlType(); |
316 | if (!type.isValid()) |
317 | imports->resolveType(type: bindingPropertyName, type_return: &type, version_return: nullptr, ns_return: nullptr, errors: nullptr); |
318 | |
319 | const QMetaObject *attachedType = type.attachedPropertiesType(engine: enginePrivate); |
320 | if (!attachedType) |
321 | COMPILE_EXCEPTION(binding, tr("Non-existent attached object" )); |
322 | QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(metaObject: attachedType); |
323 | if (!resolveSignalHandlerExpressions(obj: attachedObj, typeName: bindingPropertyName, propertyCache: cache)) |
324 | return false; |
325 | continue; |
326 | } |
327 | |
328 | if (!QmlIR::IRBuilder::isSignalPropertyName(name: bindingPropertyName)) |
329 | continue; |
330 | |
331 | QQmlPropertyResolver resolver(propertyCache); |
332 | |
333 | const QString signalName = QmlIR::IRBuilder::signalNameFromSignalPropertyName( |
334 | signalPropertyName: bindingPropertyName); |
335 | |
336 | QString qPropertyName; |
337 | if (signalName.endsWith(s: QLatin1String("Changed" ))) |
338 | qPropertyName = signalName.mid(position: 0, n: signalName.size() - static_cast<int>(strlen(s: "Changed" ))); |
339 | |
340 | bool notInRevision = false; |
341 | const QQmlPropertyData * const signal = resolver.signal(name: signalName, notInRevision: ¬InRevision); |
342 | const QQmlPropertyData * const signalPropertyData = resolver.property(name: signalName, /*notInRevision ptr*/notInRevision: nullptr); |
343 | const QQmlPropertyData * const qPropertyData = !qPropertyName.isEmpty() ? resolver.property(name: qPropertyName) : nullptr; |
344 | QString finalSignalHandlerPropertyName = signalName; |
345 | QV4::CompiledData::Binding::Flag flag |
346 | = QV4::CompiledData::Binding::IsSignalHandlerExpression; |
347 | |
348 | const bool isPropertyObserver = !signalPropertyData && qPropertyData && qPropertyData->isBindable(); |
349 | if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) { |
350 | int sigIndex = propertyCache->methodIndexToSignalIndex(index: signal->coreIndex()); |
351 | sigIndex = propertyCache->originalClone(index: sigIndex); |
352 | |
353 | bool unnamedParameter = false; |
354 | |
355 | QList<QByteArray> parameterNames = propertyCache->signalParameterNames(index: sigIndex); |
356 | for (int i = 0; i < parameterNames.size(); ++i) { |
357 | const QString param = QString::fromUtf8(ba: parameterNames.at(i)); |
358 | if (param.isEmpty()) |
359 | unnamedParameter = true; |
360 | else if (unnamedParameter) { |
361 | COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter." )); |
362 | } else if (illegalNames.contains(value: param)) { |
363 | COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable." ).arg(param)); |
364 | } |
365 | } |
366 | } else if (isPropertyObserver) { |
367 | finalSignalHandlerPropertyName = qPropertyName; |
368 | flag = QV4::CompiledData::Binding::IsPropertyObserver; |
369 | } else { |
370 | if (notInRevision) { |
371 | // Try assinging it as a property later |
372 | if (signalPropertyData) |
373 | continue; |
374 | |
375 | const QString &originalPropertyName = stringAt(idx: binding->propertyNameIndex); |
376 | |
377 | auto *typeRef = resolvedType(id: obj->inheritedTypeNameIndex); |
378 | const QQmlType type = typeRef ? typeRef->type() : QQmlType(); |
379 | if (type.isValid()) { |
380 | COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5." ) |
381 | .arg(typeName).arg(originalPropertyName).arg(type.module()) |
382 | .arg(type.version().majorVersion()) |
383 | .arg(type.version().minorVersion())); |
384 | } else { |
385 | COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning." ).arg(typeName).arg(originalPropertyName)); |
386 | } |
387 | } |
388 | |
389 | // Try to look up the signal parameter names in the object itself |
390 | |
391 | // build cache if necessary |
392 | if (customSignals.isEmpty()) { |
393 | for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) { |
394 | const QString &signalName = stringAt(idx: signal->nameIndex); |
395 | customSignals.insert(key: signalName, value: signal->parameterStringList(stringPool: compiler->stringPool())); |
396 | } |
397 | |
398 | for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) { |
399 | const QString propName = stringAt(idx: property->nameIndex); |
400 | customSignals.insert(key: propName, value: QStringList()); |
401 | } |
402 | } |
403 | |
404 | QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(key: signalName); |
405 | if (entry == customSignals.constEnd() && !qPropertyName.isEmpty()) |
406 | entry = customSignals.constFind(key: qPropertyName); |
407 | |
408 | if (entry == customSignals.constEnd()) { |
409 | // Can't find even a custom signal, then just don't do anything and try |
410 | // keeping the binding as a regular property assignment. |
411 | continue; |
412 | } |
413 | } |
414 | |
415 | // Binding object to signal means connect the signal to the object's default method. |
416 | if (bindingType == QV4::CompiledData::Binding::Type_Object) { |
417 | binding->setFlag(QV4::CompiledData::Binding::IsSignalHandlerObject); |
418 | continue; |
419 | } |
420 | |
421 | if (bindingType != QV4::CompiledData::Binding::Type_Script) { |
422 | if (bindingType < QV4::CompiledData::Binding::Type_Script) { |
423 | COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)" )); |
424 | } else { |
425 | COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment" )); |
426 | } |
427 | } |
428 | |
429 | binding->propertyNameIndex = compiler->registerString(str: finalSignalHandlerPropertyName); |
430 | binding->setFlag(flag); |
431 | } |
432 | return true; |
433 | } |
434 | |
435 | QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler) |
436 | : QQmlCompilePass(typeCompiler) |
437 | , qmlObjects(*typeCompiler->qmlObjects()) |
438 | , propertyCaches(typeCompiler->propertyCaches()) |
439 | , imports(typeCompiler->imports()) |
440 | { |
441 | } |
442 | |
443 | bool QQmlEnumTypeResolver::resolveEnumBindings() |
444 | { |
445 | for (int i = 0; i < qmlObjects.size(); ++i) { |
446 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: i); |
447 | if (!propertyCache) |
448 | continue; |
449 | const QmlIR::Object *obj = qmlObjects.at(i); |
450 | |
451 | QQmlPropertyResolver resolver(propertyCache); |
452 | |
453 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
454 | const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags(); |
455 | if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression |
456 | || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject |
457 | || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver) |
458 | continue; |
459 | |
460 | if (binding->type() != QV4::CompiledData::Binding::Type_Script) |
461 | continue; |
462 | |
463 | const QString propertyName = stringAt(idx: binding->propertyNameIndex); |
464 | bool notInRevision = false; |
465 | const QQmlPropertyData *pd = resolver.property(name: propertyName, notInRevision: ¬InRevision); |
466 | if (!pd || pd->isQList()) |
467 | continue; |
468 | |
469 | if (!pd->isEnum() && pd->propType().id() != QMetaType::Int) |
470 | continue; |
471 | |
472 | if (!tryQualifiedEnumAssignment(obj, propertyCache, prop: pd, binding)) |
473 | return false; |
474 | } |
475 | } |
476 | |
477 | return true; |
478 | } |
479 | |
480 | bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, QStringView, int enumValue, bool) |
481 | { |
482 | binding->setType(QV4::CompiledData::Binding::Type_Number); |
483 | binding->value.constantValueIndex = compiler->registerConstant(v: QV4::Encode((double)enumValue)); |
484 | // binding->setNumberValueInternal((double)enumValue); |
485 | binding->setFlag(QV4::CompiledData::Binding::IsResolvedEnum); |
486 | return true; |
487 | } |
488 | |
489 | bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment( |
490 | const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache, |
491 | const QQmlPropertyData *prop, QmlIR::Binding *binding) |
492 | { |
493 | bool isIntProp = (prop->propType().id() == QMetaType::Int) && !prop->isEnum(); |
494 | if (!prop->isEnum() && !isIntProp) |
495 | return true; |
496 | |
497 | if (!prop->isWritable() |
498 | && !(binding->hasFlag(flag: QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration))) { |
499 | COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property" ) |
500 | .arg(stringAt(binding->propertyNameIndex))); |
501 | } |
502 | |
503 | Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script); |
504 | const QString string = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
505 | if (!string.constData()->isUpper()) |
506 | return true; |
507 | |
508 | // reject any "complex" expression (even simple arithmetic) |
509 | // we do this by excluding everything that is not part of a |
510 | // valid identifier or a dot |
511 | for (const QChar &c : string) |
512 | if (!(c.isLetterOrNumber() || c == u'.' || c == u'_' || c.isSpace())) |
513 | return true; |
514 | |
515 | // we support one or two '.' in the enum phrase: |
516 | // * <TypeName>.<EnumValue> |
517 | // * <TypeName>.<ScopedEnumName>.<EnumValue> |
518 | |
519 | int dot = string.indexOf(c: QLatin1Char('.')); |
520 | if (dot == -1 || dot == string.size()-1) |
521 | return true; |
522 | |
523 | int dot2 = string.indexOf(c: QLatin1Char('.'), from: dot+1); |
524 | if (dot2 != -1 && dot2 != string.size()-1) { |
525 | if (!string.at(i: dot+1).isUpper()) |
526 | return true; |
527 | if (string.indexOf(c: QLatin1Char('.'), from: dot2+1) != -1) |
528 | return true; |
529 | } |
530 | |
531 | QHashedStringRef typeName(string.constData(), dot); |
532 | const bool isQtObject = (typeName == QLatin1String("Qt" )); |
533 | const QStringView scopedEnumName = (dot2 != -1 ? QStringView{string}.mid(pos: dot + 1, n: dot2 - dot - 1) : QStringView()); |
534 | // ### consider supporting scoped enums in Qt namespace |
535 | const QStringView enumValue = QStringView{string}.mid(pos: !isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1); |
536 | |
537 | if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here? |
538 | // Allow enum assignment to ints. |
539 | bool ok; |
540 | int enumval = evaluateEnum(scope: typeName.toString(), enumName: scopedEnumName, enumValue, ok: &ok); |
541 | if (ok) { |
542 | if (!assignEnumToBinding(binding, enumValue, enumValue: enumval, isQtObject)) |
543 | return false; |
544 | } |
545 | return true; |
546 | } |
547 | QQmlType type; |
548 | imports->resolveType(type: typeName, type_return: &type, version_return: nullptr, ns_return: nullptr, errors: nullptr); |
549 | |
550 | if (!type.isValid() && !isQtObject) |
551 | return true; |
552 | |
553 | int value = 0; |
554 | bool ok = false; |
555 | |
556 | auto *tr = resolvedType(id: obj->inheritedTypeNameIndex); |
557 | |
558 | // When these two match, we can short cut the search, unless... |
559 | bool useFastPath = type.isValid() && tr && tr->type() == type; |
560 | QMetaProperty mprop; |
561 | QMetaEnum ; |
562 | if (useFastPath) { |
563 | mprop = propertyCache->firstCppMetaObject()->property(index: prop->coreIndex()); |
564 | menum = mprop.enumerator(); |
565 | // ...the enumerator merely comes from a related metaobject, but the enum scope does not match |
566 | // the typename we resolved |
567 | if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(utf8: menum.scope())) |
568 | useFastPath = false;; |
569 | } |
570 | if (useFastPath) { |
571 | QByteArray enumName = enumValue.toUtf8(); |
572 | if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8()) |
573 | return true; |
574 | |
575 | if (mprop.isFlagType()) { |
576 | value = menum.keysToValue(keys: enumName.constData(), ok: &ok); |
577 | } else { |
578 | value = menum.keyToValue(key: enumName.constData(), ok: &ok); |
579 | } |
580 | } else { |
581 | // Otherwise we have to search the whole type |
582 | if (type.isValid()) { |
583 | if (!scopedEnumName.isEmpty()) |
584 | value = type.scopedEnumValue(engine: compiler->enginePrivate(), scopedEnumName, enumValue, ok: &ok); |
585 | else |
586 | value = type.enumValue(engine: compiler->enginePrivate(), QHashedStringRef(enumValue), ok: &ok); |
587 | } else { |
588 | QByteArray enumName = enumValue.toUtf8(); |
589 | const QMetaObject *metaObject = &Qt::staticMetaObject; |
590 | for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) { |
591 | QMetaEnum e = metaObject->enumerator(index: ii); |
592 | value = e.keyToValue(key: enumName.constData(), ok: &ok); |
593 | } |
594 | } |
595 | } |
596 | |
597 | if (!ok) |
598 | return true; |
599 | |
600 | return assignEnumToBinding(binding, enumValue, enumValue: value, isQtObject); |
601 | } |
602 | |
603 | int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, QStringView enumName, QStringView enumValue, bool *ok) const |
604 | { |
605 | Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum" , "ok must not be a null pointer" ); |
606 | *ok = false; |
607 | |
608 | if (scope != QLatin1String("Qt" )) { |
609 | QQmlType type; |
610 | imports->resolveType(type: scope, type_return: &type, version_return: nullptr, ns_return: nullptr, errors: nullptr); |
611 | if (!type.isValid()) |
612 | return -1; |
613 | if (!enumName.isEmpty()) |
614 | return type.scopedEnumValue(engine: compiler->enginePrivate(), enumName, enumValue, ok); |
615 | return type.enumValue(engine: compiler->enginePrivate(), QHashedStringRef(enumValue.constData(), enumValue.size()), ok); |
616 | } |
617 | |
618 | const QMetaObject *mo = &Qt::staticMetaObject; |
619 | int i = mo->enumeratorCount(); |
620 | const QByteArray ba = enumValue.toUtf8(); |
621 | while (i--) { |
622 | int v = mo->enumerator(index: i).keyToValue(key: ba.constData(), ok); |
623 | if (*ok) |
624 | return v; |
625 | } |
626 | return -1; |
627 | } |
628 | |
629 | QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler) |
630 | : QQmlCompilePass(typeCompiler) |
631 | , qmlObjects(*typeCompiler->qmlObjects()) |
632 | , customParsers(typeCompiler->customParserCache()) |
633 | { |
634 | } |
635 | |
636 | void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings() |
637 | { |
638 | scanObjectRecursively(/*root object*/objectIndex: 0); |
639 | for (int i = 0; i < qmlObjects.size(); ++i) |
640 | if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) |
641 | scanObjectRecursively(objectIndex: i); |
642 | } |
643 | |
644 | void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings) |
645 | { |
646 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
647 | if (!annotateScriptBindings) |
648 | annotateScriptBindings = customParsers.contains(key: obj->inheritedTypeNameIndex); |
649 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
650 | switch (binding->type()) { |
651 | case QV4::CompiledData::Binding::Type_Script: |
652 | if (annotateScriptBindings) { |
653 | binding->stringIndex = compiler->registerString( |
654 | str: compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex)); |
655 | } |
656 | break; |
657 | case QV4::CompiledData::Binding::Type_Object: |
658 | case QV4::CompiledData::Binding::Type_AttachedProperty: |
659 | case QV4::CompiledData::Binding::Type_GroupProperty: |
660 | scanObjectRecursively(objectIndex: binding->value.objectIndex, annotateScriptBindings); |
661 | break; |
662 | default: |
663 | break; |
664 | } |
665 | } |
666 | } |
667 | |
668 | QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) |
669 | : QQmlCompilePass(typeCompiler) |
670 | , qmlObjects(*typeCompiler->qmlObjects()) |
671 | , propertyCaches(typeCompiler->propertyCaches()) |
672 | { |
673 | } |
674 | |
675 | void QQmlAliasAnnotator::annotateBindingsToAliases() |
676 | { |
677 | for (int i = 0; i < qmlObjects.size(); ++i) { |
678 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: i); |
679 | if (!propertyCache) |
680 | continue; |
681 | |
682 | const QmlIR::Object *obj = qmlObjects.at(i); |
683 | |
684 | QQmlPropertyResolver resolver(propertyCache); |
685 | const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
686 | |
687 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
688 | if (!binding->isValueBinding()) |
689 | continue; |
690 | bool notInRevision = false; |
691 | const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
692 | if (pd && pd->isAlias()) |
693 | binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias); |
694 | } |
695 | } |
696 | } |
697 | |
698 | QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler) |
699 | : QQmlCompilePass(typeCompiler) |
700 | , qmlObjects(*typeCompiler->qmlObjects()) |
701 | , propertyCaches(typeCompiler->propertyCaches()) |
702 | { |
703 | |
704 | } |
705 | |
706 | void QQmlScriptStringScanner::scan() |
707 | { |
708 | const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>(); |
709 | for (int i = 0; i < qmlObjects.size(); ++i) { |
710 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: i); |
711 | if (!propertyCache) |
712 | continue; |
713 | |
714 | const QmlIR::Object *obj = qmlObjects.at(i); |
715 | |
716 | QQmlPropertyResolver resolver(propertyCache); |
717 | const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
718 | |
719 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
720 | if (binding->type() != QV4::CompiledData::Binding::Type_Script) |
721 | continue; |
722 | bool notInRevision = false; |
723 | const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
724 | if (!pd || pd->propType() != scriptStringMetaType) |
725 | continue; |
726 | |
727 | QString script = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
728 | binding->stringIndex = compiler->registerString(str: script); |
729 | } |
730 | } |
731 | } |
732 | |
733 | template<> |
734 | void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects( |
735 | QmlIR::Object *object) const |
736 | { |
737 | object->namedObjectsInComponent.allocate(pool: m_compiler->memoryPool(), container: m_idToObjectIndex); |
738 | } |
739 | |
740 | template<> |
741 | bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const |
742 | { |
743 | m_compiler->qmlObjects()->at(i: index)->flags |= QV4::CompiledData::Object::IsComponent; |
744 | return true; |
745 | } |
746 | |
747 | template<> |
748 | void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const |
749 | { |
750 | m_compiler->qmlObjects()->at(i: index)->id = m_idToObjectIndex.size(); |
751 | } |
752 | |
753 | template<> |
754 | bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::wrapImplicitComponent(QmlIR::Binding *binding) |
755 | { |
756 | QQmlJS::MemoryPool *pool = m_compiler->memoryPool(); |
757 | QVector<QmlIR::Object *> *qmlObjects = m_compiler->qmlObjects(); |
758 | |
759 | // emulate "import QML 1.0" and then wrap the component in "QML.Component {}" |
760 | QQmlType componentType = QQmlMetaType::qmlType( |
761 | metaObject: &QQmlComponent::staticMetaObject, QStringLiteral("QML" ), |
762 | version: QTypeRevision::fromVersion(majorVersion: 1, minorVersion: 0)); |
763 | Q_ASSERT(componentType.isValid()); |
764 | const QString qualifier = QStringLiteral("QML" ); |
765 | |
766 | m_compiler->addImport(module: componentType.module(), qualifier, version: componentType.version()); |
767 | |
768 | QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); |
769 | syntheticComponent->init( |
770 | pool, |
771 | typeNameIndex: m_compiler->registerString( |
772 | str: qualifier + QLatin1Char('.') + componentType.elementName()), |
773 | idIndex: m_compiler->registerString(str: QString()), location: binding->valueLocation); |
774 | syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; |
775 | |
776 | if (!m_compiler->resolvedTypes->contains(key: syntheticComponent->inheritedTypeNameIndex)) { |
777 | auto typeRef = new QV4::ResolvedTypeReference; |
778 | typeRef->setType(componentType); |
779 | typeRef->setVersion(componentType.version()); |
780 | m_compiler->resolvedTypes->insert(key: syntheticComponent->inheritedTypeNameIndex, value: typeRef); |
781 | } |
782 | |
783 | qmlObjects->append(t: syntheticComponent); |
784 | const int componentIndex = qmlObjects->size() - 1; |
785 | // Keep property caches symmetric |
786 | QQmlPropertyCache::ConstPtr componentCache |
787 | = QQmlMetaType::propertyCache(metaObject: &QQmlComponent::staticMetaObject); |
788 | m_propertyCaches->append(cache: componentCache); |
789 | |
790 | QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); |
791 | *syntheticBinding = *binding; |
792 | |
793 | // The synthetic binding inside Component has no name. It's just "Component { Foo {} }". |
794 | syntheticBinding->propertyNameIndex = 0; |
795 | |
796 | syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object); |
797 | QString error = syntheticComponent->appendBinding(b: syntheticBinding, /*isListBinding*/false); |
798 | Q_ASSERT(error.isEmpty()); |
799 | Q_UNUSED(error); |
800 | |
801 | binding->value.objectIndex = componentIndex; |
802 | |
803 | m_componentRoots.append(t: componentIndex); |
804 | return true; |
805 | } |
806 | |
807 | template<> |
808 | typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult |
809 | QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( |
810 | const CompiledObject &component, int objectIndex, QQmlError *error) |
811 | { |
812 | Q_UNUSED(component); |
813 | |
814 | const QmlIR::Object * const obj = m_compiler->objectAt(index: objectIndex); |
815 | if (!obj->aliasCount()) |
816 | return AllAliasesResolved; |
817 | |
818 | int numResolvedAliases = 0; |
819 | bool seenUnresolvedAlias = false; |
820 | |
821 | for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { |
822 | if (alias->hasFlag(flag: QV4::CompiledData::Alias::Resolved)) |
823 | continue; |
824 | |
825 | seenUnresolvedAlias = true; |
826 | |
827 | const int idIndex = alias->idIndex(); |
828 | const int targetObjectIndex = m_idToObjectIndex.value(key: idIndex, defaultValue: -1); |
829 | if (targetObjectIndex == -1) { |
830 | *error = qQmlCompileError( |
831 | location: alias->referenceLocation, |
832 | description: tr(sourceText: "Invalid alias reference. Unable to find id \"%1\"" ).arg(a: stringAt(idx: idIndex))); |
833 | break; |
834 | } |
835 | |
836 | const QmlIR::Object *targetObject = m_compiler->objectAt(index: targetObjectIndex); |
837 | Q_ASSERT(targetObject->id >= 0); |
838 | alias->setTargetObjectId(targetObject->id); |
839 | alias->setIsAliasToLocalAlias(false); |
840 | |
841 | const QString aliasPropertyValue = stringAt(idx: alias->propertyNameIndex); |
842 | |
843 | QStringView property; |
844 | QStringView subProperty; |
845 | |
846 | const int propertySeparator = aliasPropertyValue.indexOf(c: QLatin1Char('.')); |
847 | if (propertySeparator != -1) { |
848 | property = QStringView{aliasPropertyValue}.left(n: propertySeparator); |
849 | subProperty = QStringView{aliasPropertyValue}.mid(pos: propertySeparator + 1); |
850 | } else |
851 | property = QStringView(aliasPropertyValue); |
852 | |
853 | QQmlPropertyIndex propIdx; |
854 | |
855 | if (property.isEmpty()) { |
856 | alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); |
857 | } else { |
858 | QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(index: targetObjectIndex); |
859 | if (!targetCache) { |
860 | *error = qQmlCompileError( |
861 | location: alias->referenceLocation, |
862 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: property.toString())); |
863 | break; |
864 | } |
865 | |
866 | QQmlPropertyResolver resolver(targetCache); |
867 | |
868 | const QQmlPropertyData *targetProperty = resolver.property(name: property.toString()); |
869 | |
870 | // If it's an alias that we haven't resolved yet, try again later. |
871 | if (!targetProperty) { |
872 | bool aliasPointsToOtherAlias = false; |
873 | int localAliasIndex = 0; |
874 | for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { |
875 | if (stringAt(idx: targetAlias->nameIndex()) == property) { |
876 | aliasPointsToOtherAlias = true; |
877 | break; |
878 | } |
879 | } |
880 | if (aliasPointsToOtherAlias) { |
881 | if (targetObjectIndex == objectIndex) { |
882 | alias->localAliasIndex = localAliasIndex; |
883 | alias->setIsAliasToLocalAlias(true); |
884 | alias->setFlag(QV4::CompiledData::Alias::Resolved); |
885 | ++numResolvedAliases; |
886 | continue; |
887 | } |
888 | |
889 | // restore |
890 | alias->setIdIndex(idIndex); |
891 | // Try again later and resolve the target alias first. |
892 | break; |
893 | } |
894 | } |
895 | |
896 | if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { |
897 | *error = qQmlCompileError( |
898 | location: alias->referenceLocation, |
899 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: property.toString())); |
900 | break; |
901 | } |
902 | |
903 | propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); |
904 | |
905 | if (!subProperty.isEmpty()) { |
906 | const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(type: targetProperty->propType()); |
907 | if (!valueTypeMetaObject) { |
908 | // could be a deep alias |
909 | bool isDeepAlias = subProperty.at(n: 0).isLower(); |
910 | if (isDeepAlias) { |
911 | isDeepAlias = false; |
912 | for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { |
913 | auto binding = *it; |
914 | if (m_compiler->stringAt(idx: binding.propertyNameIndex) == property) { |
915 | resolver = QQmlPropertyResolver(m_propertyCaches->at(index: binding.value.objectIndex)); |
916 | const QQmlPropertyData *actualProperty = resolver.property(name: subProperty.toString()); |
917 | if (actualProperty) { |
918 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); |
919 | isDeepAlias = true; |
920 | } |
921 | } |
922 | } |
923 | } |
924 | if (!isDeepAlias) { |
925 | *error = qQmlCompileError( |
926 | location: alias->referenceLocation, |
927 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: subProperty.toString())); |
928 | break; |
929 | } |
930 | } else { |
931 | |
932 | int valueTypeIndex = |
933 | valueTypeMetaObject->indexOfProperty(name: subProperty.toString().toUtf8().constData()); |
934 | if (valueTypeIndex == -1) { |
935 | *error = qQmlCompileError( |
936 | location: alias->referenceLocation, |
937 | description: tr(sourceText: "Invalid alias target location: %1" ).arg(a: subProperty.toString())); |
938 | break; |
939 | } |
940 | Q_ASSERT(valueTypeIndex <= 0x0000FFFF); |
941 | |
942 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); |
943 | } |
944 | } else { |
945 | if (targetProperty->isQObject()) |
946 | alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); |
947 | } |
948 | } |
949 | |
950 | alias->encodedMetaPropertyIndex = propIdx.toEncoded(); |
951 | alias->setFlag(QV4::CompiledData::Alias::Resolved); |
952 | numResolvedAliases++; |
953 | } |
954 | |
955 | if (numResolvedAliases == 0) |
956 | return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; |
957 | |
958 | return SomeAliasesResolved; |
959 | } |
960 | |
961 | QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) |
962 | : QQmlCompilePass(typeCompiler) |
963 | , qmlObjects(typeCompiler->qmlObjects()) |
964 | , propertyCaches(typeCompiler->propertyCaches()) |
965 | , customParsers(typeCompiler->customParserCache()) |
966 | , _seenObjectWithId(false) |
967 | { |
968 | } |
969 | |
970 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject() |
971 | { |
972 | for (int i = 0; i < qmlObjects->size(); ++i) { |
973 | if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) |
974 | && !scanObject(objectIndex: i, scopeDeferred: ScopeDeferred::False)) { |
975 | return false; |
976 | } |
977 | } |
978 | return scanObject(/*root object*/objectIndex: 0, scopeDeferred: ScopeDeferred::False); |
979 | } |
980 | |
981 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject( |
982 | int objectIndex, ScopeDeferred scopeDeferred) |
983 | { |
984 | using namespace QV4::CompiledData; |
985 | |
986 | QmlIR::Object *obj = qmlObjects->at(i: objectIndex); |
987 | if (obj->idNameIndex != 0) |
988 | _seenObjectWithId = true; |
989 | |
990 | if (obj->flags & Object::IsComponent) { |
991 | Q_ASSERT(obj->bindingCount() == 1); |
992 | const Binding *componentBinding = obj->firstBinding(); |
993 | Q_ASSERT(componentBinding->type() == Binding::Type_Object); |
994 | // Components are separate from their surrounding scope. They cannot be deferred. |
995 | return scanObject(objectIndex: componentBinding->value.objectIndex, scopeDeferred: ScopeDeferred::False); |
996 | } |
997 | |
998 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: objectIndex); |
999 | if (!propertyCache) |
1000 | return true; |
1001 | |
1002 | QString defaultPropertyName; |
1003 | const QQmlPropertyData *defaultProperty = nullptr; |
1004 | if (obj->indexOfDefaultPropertyOrAlias != -1) { |
1005 | const QQmlPropertyCache *cache = propertyCache->parent().data(); |
1006 | defaultPropertyName = cache->defaultPropertyName(); |
1007 | defaultProperty = cache->defaultProperty(); |
1008 | } else { |
1009 | defaultPropertyName = propertyCache->defaultPropertyName(); |
1010 | defaultProperty = propertyCache->defaultProperty(); |
1011 | } |
1012 | |
1013 | QQmlCustomParser *customParser = customParsers.value(key: obj->inheritedTypeNameIndex); |
1014 | |
1015 | QQmlPropertyResolver propertyResolver(propertyCache); |
1016 | |
1017 | QStringList deferredPropertyNames; |
1018 | QStringList immediatePropertyNames; |
1019 | { |
1020 | const QMetaObject *mo = propertyCache->firstCppMetaObject(); |
1021 | const int deferredNamesIndex = mo->indexOfClassInfo(name: "DeferredPropertyNames" ); |
1022 | const int immediateNamesIndex = mo->indexOfClassInfo(name: "ImmediatePropertyNames" ); |
1023 | if (deferredNamesIndex != -1) { |
1024 | if (immediateNamesIndex != -1) { |
1025 | COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and " |
1026 | "ImmediatePropertyNames on the same type." )); |
1027 | } |
1028 | const QMetaClassInfo classInfo = mo->classInfo(index: deferredNamesIndex); |
1029 | deferredPropertyNames = QString::fromUtf8(utf8: classInfo.value()).split(sep: u','); |
1030 | } else if (immediateNamesIndex != -1) { |
1031 | const QMetaClassInfo classInfo = mo->classInfo(index: immediateNamesIndex); |
1032 | immediatePropertyNames = QString::fromUtf8(utf8: classInfo.value()).split(sep: u','); |
1033 | |
1034 | // If the property contains an empty string, all properties shall be deferred. |
1035 | if (immediatePropertyNames.isEmpty()) |
1036 | immediatePropertyNames.append(t: QString()); |
1037 | } |
1038 | } |
1039 | |
1040 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
1041 | QString name = stringAt(idx: binding->propertyNameIndex); |
1042 | |
1043 | if (customParser) { |
1044 | if (binding->type() == Binding::Type_AttachedProperty) { |
1045 | if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { |
1046 | binding->setFlag(Binding::IsCustomParserBinding); |
1047 | obj->flags |= Object::HasCustomParserBindings; |
1048 | continue; |
1049 | } |
1050 | } else if (QmlIR::IRBuilder::isSignalPropertyName(name) |
1051 | && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { |
1052 | obj->flags |= Object::HasCustomParserBindings; |
1053 | binding->setFlag(Binding::IsCustomParserBinding); |
1054 | continue; |
1055 | } |
1056 | } |
1057 | |
1058 | const bool hasPropertyData = [&]() { |
1059 | if (name.isEmpty()) { |
1060 | name = defaultPropertyName; |
1061 | if (defaultProperty) |
1062 | return true; |
1063 | } else if (name.constData()->isUpper()) { |
1064 | // Upper case names cannot be custom-parsed unless they are attached properties |
1065 | // and the custom parser explicitly accepts them. See above for that case. |
1066 | return false; |
1067 | } else { |
1068 | bool notInRevision = false; |
1069 | if (propertyResolver.property( |
1070 | name, notInRevision: ¬InRevision, check: QQmlPropertyResolver::CheckRevision)) { |
1071 | return true; |
1072 | } |
1073 | } |
1074 | |
1075 | if (!customParser) |
1076 | return false; |
1077 | |
1078 | const Binding::Flags bindingFlags = binding->flags(); |
1079 | if (bindingFlags & Binding::IsSignalHandlerExpression |
1080 | || bindingFlags & Binding::IsSignalHandlerObject |
1081 | || bindingFlags & Binding::IsPropertyObserver) { |
1082 | // These signal handlers cannot be custom-parsed. We have already established |
1083 | // that the signal exists. |
1084 | return false; |
1085 | } |
1086 | |
1087 | // If the property isn't found, we may want to custom-parse the binding. |
1088 | obj->flags |= Object::HasCustomParserBindings; |
1089 | binding->setFlag(Binding::IsCustomParserBinding); |
1090 | return false; |
1091 | }(); |
1092 | |
1093 | bool seenSubObjectWithId = false; |
1094 | bool isExternal = false; |
1095 | if (binding->type() >= Binding::Type_Object) { |
1096 | const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty(); |
1097 | isExternal = !isOwnProperty && binding->isGroupProperty(); |
1098 | if (isOwnProperty || isExternal) { |
1099 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1100 | const bool subObjectValid = scanObject( |
1101 | objectIndex: binding->value.objectIndex, |
1102 | scopeDeferred: (isExternal || scopeDeferred == ScopeDeferred::True) |
1103 | ? ScopeDeferred::True |
1104 | : ScopeDeferred::False); |
1105 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1106 | if (!subObjectValid) |
1107 | return false; |
1108 | _seenObjectWithId |= seenSubObjectWithId; |
1109 | } |
1110 | } |
1111 | |
1112 | bool isDeferred = false; |
1113 | if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(str: name)) { |
1114 | if (seenSubObjectWithId) { |
1115 | COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned " |
1116 | "to a deferred property." )); |
1117 | } |
1118 | if (isExternal || !disableInternalDeferredProperties()) |
1119 | isDeferred = true; |
1120 | } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(str: name)) { |
1121 | if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupProperty) { |
1122 | if (isExternal || !disableInternalDeferredProperties()) |
1123 | isDeferred = true; |
1124 | } |
1125 | } |
1126 | |
1127 | if (binding->type() >= Binding::Type_Object) { |
1128 | if (isExternal && !isDeferred && !customParser) { |
1129 | COMPILE_EXCEPTION( |
1130 | binding, tr("Cannot assign to non-existent property \"%1\"" ).arg(name)); |
1131 | } |
1132 | } |
1133 | |
1134 | if (isDeferred) { |
1135 | binding->setFlag(Binding::IsDeferredBinding); |
1136 | obj->flags |= Object::HasDeferredBindings; |
1137 | } |
1138 | } |
1139 | |
1140 | return true; |
1141 | } |
1142 | |
1143 | QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler) |
1144 | : QQmlCompilePass(typeCompiler) |
1145 | , qmlObjects(*typeCompiler->qmlObjects()) |
1146 | , propertyCaches(typeCompiler->propertyCaches()) |
1147 | { |
1148 | |
1149 | } |
1150 | |
1151 | void QQmlDefaultPropertyMerger::mergeDefaultProperties() |
1152 | { |
1153 | for (int i = 0; i < qmlObjects.size(); ++i) |
1154 | mergeDefaultProperties(objectIndex: i); |
1155 | } |
1156 | |
1157 | void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) |
1158 | { |
1159 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: objectIndex); |
1160 | if (!propertyCache) |
1161 | return; |
1162 | |
1163 | QmlIR::Object *object = qmlObjects.at(i: objectIndex); |
1164 | |
1165 | QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); |
1166 | QmlIR::Binding *bindingsToReinsert = nullptr; |
1167 | QmlIR::Binding *tail = nullptr; |
1168 | |
1169 | QmlIR::Binding *previousBinding = nullptr; |
1170 | QmlIR::Binding *binding = object->firstBinding(); |
1171 | while (binding) { |
1172 | if (binding->propertyNameIndex == quint32(0) || stringAt(idx: binding->propertyNameIndex) != defaultProperty) { |
1173 | previousBinding = binding; |
1174 | binding = binding->next; |
1175 | continue; |
1176 | } |
1177 | |
1178 | QmlIR::Binding *toReinsert = binding; |
1179 | binding = object->unlinkBinding(before: previousBinding, binding); |
1180 | |
1181 | if (!tail) { |
1182 | bindingsToReinsert = toReinsert; |
1183 | tail = toReinsert; |
1184 | } else { |
1185 | tail->next = toReinsert; |
1186 | tail = tail->next; |
1187 | } |
1188 | tail->next = nullptr; |
1189 | } |
1190 | |
1191 | binding = bindingsToReinsert; |
1192 | while (binding) { |
1193 | QmlIR::Binding *toReinsert = binding; |
1194 | binding = binding->next; |
1195 | object->insertSorted(b: toReinsert); |
1196 | } |
1197 | } |
1198 | |
1199 | QT_END_NAMESPACE |
1200 | |