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 | #include <private/qqmlsignalnames_p.h> |
13 | |
14 | #define COMPILE_EXCEPTION(token, desc) \ |
15 | { \ |
16 | recordError((token)->location, desc); \ |
17 | return false; \ |
18 | } |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | DEFINE_BOOL_CONFIG_OPTION( |
23 | disableInternalDeferredProperties, QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES); |
24 | |
25 | Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler"); |
26 | |
27 | QQmlTypeCompiler::QQmlTypeCompiler( |
28 | QQmlEnginePrivate *engine, QQmlTypeData *typeData, QmlIR::Document *parsedQML, |
29 | QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, |
30 | const QV4::CompiledData::DependentTypesHasher &dependencyHasher) |
31 | : resolvedTypes(resolvedTypeCache) |
32 | , engine(engine) |
33 | , dependencyHasher(dependencyHasher) |
34 | , document(parsedQML) |
35 | , typeData(typeData) |
36 | { |
37 | } |
38 | |
39 | QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeCompiler::compile() |
40 | { |
41 | // Build property caches and VME meta object data |
42 | |
43 | for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd(); |
44 | it != end; ++it) { |
45 | QQmlCustomParser *customParser = (*it)->type().customParser(); |
46 | if (customParser) |
47 | customParsers.insert(key: it.key(), value: customParser); |
48 | } |
49 | |
50 | QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings; |
51 | |
52 | |
53 | { |
54 | QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(&m_propertyCaches, &pendingGroupPropertyBindings, |
55 | engine, this, imports(), typeData->typeClassName()); |
56 | QQmlError cycleError = propertyCacheBuilder.verifyNoICCycle(); |
57 | if (cycleError.isValid()) { |
58 | recordError(e: cycleError); |
59 | return nullptr; |
60 | } |
61 | QQmlPropertyCacheCreatorBase::IncrementalResult result; |
62 | do { |
63 | result = propertyCacheBuilder.buildMetaObjectsIncrementally(); |
64 | const QQmlError &error = result.error; |
65 | if (error.isValid()) { |
66 | recordError(e: error); |
67 | return nullptr; |
68 | } else { |
69 | // Resolve component boundaries and aliases |
70 | |
71 | QQmlComponentAndAliasResolver resolver(this, enginePrivate(), &m_propertyCaches); |
72 | if (QQmlError error = resolver.resolve(root: result.processedRoot); error.isValid()) { |
73 | recordError(e: error); |
74 | return nullptr; |
75 | } |
76 | pendingGroupPropertyBindings.resolveMissingPropertyCaches(propertyCaches: &m_propertyCaches); |
77 | pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed |
78 | } |
79 | } while (result.canResume); |
80 | } |
81 | |
82 | { |
83 | QQmlDefaultPropertyMerger merger(this); |
84 | merger.mergeDefaultProperties(); |
85 | } |
86 | |
87 | { |
88 | SignalHandlerResolver converter(this); |
89 | if (!converter.resolveSignalHandlerExpressions()) |
90 | return nullptr; |
91 | } |
92 | |
93 | { |
94 | QQmlEnumTypeResolver enumResolver(this); |
95 | if (!enumResolver.resolveEnumBindings()) |
96 | return nullptr; |
97 | } |
98 | |
99 | { |
100 | QQmlCustomParserScriptIndexer cpi(this); |
101 | cpi.annotateBindingsWithScriptStrings(); |
102 | } |
103 | |
104 | { |
105 | QQmlAliasAnnotator annotator(this); |
106 | annotator.annotateBindingsToAliases(); |
107 | } |
108 | |
109 | { |
110 | QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this); |
111 | if (!deferredAndCustomParserBindingScanner.scanObject()) |
112 | return nullptr; |
113 | } |
114 | |
115 | if (!document->javaScriptCompilationUnit || !document->javaScriptCompilationUnit->unitData()) { |
116 | // Compile JS binding expressions and signal handlers if necessary |
117 | { |
118 | // We can compile script strings ahead of time, but they must be compiled |
119 | // without type optimizations as their scope is always entirely dynamic. |
120 | QQmlScriptStringScanner sss(this); |
121 | sss.scan(); |
122 | } |
123 | |
124 | Q_ASSERT(document->jsModule.fileName == typeData->urlString()); |
125 | Q_ASSERT(document->jsModule.finalUrl == typeData->finalUrlString()); |
126 | QmlIR::JSCodeGen v4CodeGenerator(document, engine->v4engine()->illegalNames()); |
127 | for (QmlIR::Object *object : std::as_const(t&: document->objects)) { |
128 | if (!v4CodeGenerator.generateRuntimeFunctions(object)) { |
129 | Q_ASSERT(v4CodeGenerator.hasError()); |
130 | recordError(message: v4CodeGenerator.error()); |
131 | return nullptr; |
132 | } |
133 | } |
134 | document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/generateUnitData: false); |
135 | } |
136 | |
137 | // Generate QML compiled type data structures |
138 | |
139 | QmlIR::QmlUnitGenerator qmlGenerator; |
140 | qmlGenerator.generate(output&: *document, dependencyHasher); |
141 | |
142 | if (!errors.isEmpty()) |
143 | return nullptr; |
144 | |
145 | return std::move(document->javaScriptCompilationUnit); |
146 | } |
147 | |
148 | void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description) |
149 | { |
150 | QQmlError error; |
151 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: location.line())); |
152 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: location.column())); |
153 | error.setDescription(description); |
154 | error.setUrl(url()); |
155 | errors << error; |
156 | } |
157 | |
158 | void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message) |
159 | { |
160 | QQmlError error; |
161 | error.setDescription(message.message); |
162 | error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startLine)); |
163 | error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: message.loc.startColumn)); |
164 | error.setUrl(url()); |
165 | errors << error; |
166 | } |
167 | |
168 | void QQmlTypeCompiler::recordError(const QQmlError &e) |
169 | { |
170 | QQmlError error = e; |
171 | error.setUrl(url()); |
172 | errors << error; |
173 | } |
174 | |
175 | QString QQmlTypeCompiler::stringAt(int idx) const |
176 | { |
177 | return document->stringAt(index: idx); |
178 | } |
179 | |
180 | int QQmlTypeCompiler::registerString(const QString &str) |
181 | { |
182 | return document->jsGenerator.registerString(str); |
183 | } |
184 | |
185 | int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v) |
186 | { |
187 | return document->jsGenerator.registerConstant(v); |
188 | } |
189 | |
190 | const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const |
191 | { |
192 | return document->javaScriptCompilationUnit->unitData(); |
193 | } |
194 | |
195 | const QQmlImports *QQmlTypeCompiler::imports() const |
196 | { |
197 | return typeData->imports(); |
198 | } |
199 | |
200 | QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const |
201 | { |
202 | return &document->objects; |
203 | } |
204 | |
205 | QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() |
206 | { |
207 | return &m_propertyCaches; |
208 | } |
209 | |
210 | const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const |
211 | { |
212 | return &m_propertyCaches; |
213 | } |
214 | |
215 | QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool() |
216 | { |
217 | return document->jsParserEngine.pool(); |
218 | } |
219 | |
220 | QStringView QQmlTypeCompiler::newStringRef(const QString &string) |
221 | { |
222 | return document->jsParserEngine.newStringRef(text: string); |
223 | } |
224 | |
225 | const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const |
226 | { |
227 | return &document->jsGenerator.stringTable; |
228 | } |
229 | |
230 | QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const |
231 | { |
232 | return object->bindingAsString(doc: document, scriptIndex); |
233 | } |
234 | |
235 | void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, QTypeRevision version) |
236 | { |
237 | const quint32 moduleIdx = registerString(str: module); |
238 | const quint32 qualifierIdx = registerString(str: qualifier); |
239 | |
240 | for (int i = 0, count = document->imports.size(); i < count; ++i) { |
241 | const QV4::CompiledData::Import *existingImport = document->imports.at(i); |
242 | if (existingImport->type == QV4::CompiledData::Import::ImportLibrary |
243 | && existingImport->uriIndex == moduleIdx |
244 | && existingImport->qualifierIndex == qualifierIdx) |
245 | return; |
246 | } |
247 | auto pool = memoryPool(); |
248 | QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>(); |
249 | import->type = QV4::CompiledData::Import::ImportLibrary; |
250 | import->version = version; |
251 | import->uriIndex = moduleIdx; |
252 | import->qualifierIndex = qualifierIdx; |
253 | document->imports.append(t: import); |
254 | } |
255 | |
256 | QQmlType QQmlTypeCompiler::qmlTypeForComponent(const QString &inlineComponentName) const |
257 | { |
258 | return typeData->qmlType(inlineComponentName); |
259 | } |
260 | |
261 | QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler) |
262 | : compiler(typeCompiler) |
263 | { |
264 | } |
265 | |
266 | SignalHandlerResolver::SignalHandlerResolver(QQmlTypeCompiler *typeCompiler) |
267 | : QQmlCompilePass(typeCompiler) |
268 | , enginePrivate(typeCompiler->enginePrivate()) |
269 | , qmlObjects(*typeCompiler->qmlObjects()) |
270 | , imports(typeCompiler->imports()) |
271 | , customParsers(typeCompiler->customParserCache()) |
272 | , illegalNames(typeCompiler->enginePrivate()->v4engine()->illegalNames()) |
273 | , propertyCaches(typeCompiler->propertyCaches()) |
274 | { |
275 | } |
276 | |
277 | bool SignalHandlerResolver::resolveSignalHandlerExpressions() |
278 | { |
279 | for (int objectIndex = 0; objectIndex < qmlObjects.size(); ++objectIndex) { |
280 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
281 | QQmlPropertyCache::ConstPtr cache = propertyCaches->at(index: objectIndex); |
282 | if (!cache) |
283 | continue; |
284 | if (QQmlCustomParser *customParser = customParsers.value(key: obj->inheritedTypeNameIndex)) { |
285 | if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) |
286 | continue; |
287 | } |
288 | const QString elementName = stringAt(idx: obj->inheritedTypeNameIndex); |
289 | if (!resolveSignalHandlerExpressions(obj, typeName: elementName, propertyCache: cache)) |
290 | return false; |
291 | } |
292 | return true; |
293 | } |
294 | |
295 | bool SignalHandlerResolver::resolveSignalHandlerExpressions( |
296 | const QmlIR::Object *obj, const QString &typeName, |
297 | const QQmlPropertyCache::ConstPtr &propertyCache) |
298 | { |
299 | // map from signal name defined in qml itself to list of parameters |
300 | QHash<QString, QStringList> customSignals; |
301 | |
302 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
303 | const QString bindingPropertyName = stringAt(idx: binding->propertyNameIndex); |
304 | // Attached property? |
305 | const QV4::CompiledData::Binding::Type bindingType = binding->type(); |
306 | if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) { |
307 | const QmlIR::Object *attachedObj = qmlObjects.at(i: binding->value.objectIndex); |
308 | auto *typeRef = resolvedType(id: binding->propertyNameIndex); |
309 | QQmlType type = typeRef ? typeRef->type() : QQmlType(); |
310 | if (!type.isValid()) { |
311 | imports->resolveType( |
312 | typeLoader: QQmlTypeLoader::get(engine: enginePrivate), type: bindingPropertyName, type_return: &type, version_return: nullptr, |
313 | ns_return: nullptr); |
314 | } |
315 | |
316 | const QMetaObject *attachedType = type.attachedPropertiesType(engine: enginePrivate); |
317 | if (!attachedType) |
318 | COMPILE_EXCEPTION(binding, tr("Non-existent attached object")); |
319 | QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(metaObject: attachedType); |
320 | if (!resolveSignalHandlerExpressions(obj: attachedObj, typeName: bindingPropertyName, propertyCache: cache)) |
321 | return false; |
322 | continue; |
323 | } |
324 | |
325 | QString qPropertyName; |
326 | QString signalName; |
327 | if (auto propertyName = |
328 | QQmlSignalNames::changedHandlerNameToPropertyName(handler: bindingPropertyName)) { |
329 | qPropertyName = *propertyName; |
330 | signalName = *QQmlSignalNames::changedHandlerNameToSignalName(changedHandler: bindingPropertyName); |
331 | } else { |
332 | signalName = QQmlSignalNames::handlerNameToSignalName(handler: bindingPropertyName) |
333 | .value_or(u: QString()); |
334 | } |
335 | if (signalName.isEmpty()) |
336 | continue; |
337 | |
338 | QQmlPropertyResolver resolver(propertyCache); |
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(ch: QLatin1Char('.')); |
520 | if (dot == -1 || dot == string.size()-1) |
521 | return true; |
522 | |
523 | int dot2 = string.indexOf(ch: 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(ch: 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( |
549 | typeLoader: QQmlTypeLoader::get(engine: compiler->enginePrivate()), type: typeName, type_return: &type, version_return: nullptr, ns_return: nullptr); |
550 | |
551 | if (!type.isValid() && !isQtObject) |
552 | return true; |
553 | |
554 | int value = 0; |
555 | bool ok = false; |
556 | |
557 | auto *tr = resolvedType(id: obj->inheritedTypeNameIndex); |
558 | |
559 | // When these two match, we can short cut the search, unless... |
560 | bool useFastPath = type.isValid() && tr && tr->type() == type; |
561 | QMetaProperty mprop; |
562 | QMetaEnum menum; |
563 | if (useFastPath) { |
564 | mprop = propertyCache->firstCppMetaObject()->property(index: prop->coreIndex()); |
565 | menum = mprop.enumerator(); |
566 | // ...the enumerator merely comes from a related metaobject, but the enum scope does not match |
567 | // the typename we resolved |
568 | if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(utf8: menum.scope())) |
569 | useFastPath = false;; |
570 | } |
571 | if (useFastPath) { |
572 | QByteArray enumName = enumValue.toUtf8(); |
573 | if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8()) |
574 | return true; |
575 | |
576 | if (mprop.isFlagType()) { |
577 | value = menum.keysToValue(keys: enumName.constData(), ok: &ok); |
578 | } else { |
579 | value = menum.keyToValue(key: enumName.constData(), ok: &ok); |
580 | } |
581 | } else { |
582 | // Otherwise we have to search the whole type |
583 | if (type.isValid()) { |
584 | if (!scopedEnumName.isEmpty()) { |
585 | value = type.scopedEnumValue( |
586 | typeLoader: compiler->typeLoader(), scopedEnumName, enumValue, ok: &ok); |
587 | } else { |
588 | value = type.enumValue(typeLoader: compiler->typeLoader(), QHashedStringRef(enumValue), ok: &ok); |
589 | } |
590 | } else { |
591 | QByteArray enumName = enumValue.toUtf8(); |
592 | const QMetaObject *metaObject = &Qt::staticMetaObject; |
593 | for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) { |
594 | QMetaEnum e = metaObject->enumerator(index: ii); |
595 | value = e.keyToValue(key: enumName.constData(), ok: &ok); |
596 | } |
597 | } |
598 | } |
599 | |
600 | if (!ok) |
601 | return true; |
602 | |
603 | return assignEnumToBinding(binding, enumValue, enumValue: value, isQtObject); |
604 | } |
605 | |
606 | int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, QStringView enumName, QStringView enumValue, bool *ok) const |
607 | { |
608 | Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer"); |
609 | *ok = false; |
610 | |
611 | if (scope != QLatin1String("Qt")) { |
612 | QQmlType type; |
613 | imports->resolveType(typeLoader: compiler->typeLoader(), type: scope, type_return: &type, version_return: nullptr, ns_return: nullptr); |
614 | if (!type.isValid()) |
615 | return -1; |
616 | if (!enumName.isEmpty()) |
617 | return type.scopedEnumValue(typeLoader: compiler->typeLoader(), enumName, enumValue, ok); |
618 | return type.enumValue( |
619 | typeLoader: compiler->typeLoader(), |
620 | QHashedStringRef(enumValue.constData(), enumValue.size()), ok); |
621 | } |
622 | |
623 | const QMetaObject *mo = &Qt::staticMetaObject; |
624 | int i = mo->enumeratorCount(); |
625 | const QByteArray ba = enumValue.toUtf8(); |
626 | while (i--) { |
627 | int v = mo->enumerator(index: i).keyToValue(key: ba.constData(), ok); |
628 | if (*ok) |
629 | return v; |
630 | } |
631 | return -1; |
632 | } |
633 | |
634 | QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler) |
635 | : QQmlCompilePass(typeCompiler) |
636 | , qmlObjects(*typeCompiler->qmlObjects()) |
637 | , customParsers(typeCompiler->customParserCache()) |
638 | { |
639 | } |
640 | |
641 | void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings() |
642 | { |
643 | scanObjectRecursively(/*root object*/objectIndex: 0); |
644 | for (int i = 0; i < qmlObjects.size(); ++i) |
645 | if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) |
646 | scanObjectRecursively(objectIndex: i); |
647 | } |
648 | |
649 | void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings) |
650 | { |
651 | const QmlIR::Object * const obj = qmlObjects.at(i: objectIndex); |
652 | if (!annotateScriptBindings) |
653 | annotateScriptBindings = customParsers.contains(key: obj->inheritedTypeNameIndex); |
654 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
655 | switch (binding->type()) { |
656 | case QV4::CompiledData::Binding::Type_Script: |
657 | if (annotateScriptBindings) { |
658 | binding->stringIndex = compiler->registerString( |
659 | str: compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex)); |
660 | } |
661 | break; |
662 | case QV4::CompiledData::Binding::Type_Object: |
663 | case QV4::CompiledData::Binding::Type_AttachedProperty: |
664 | case QV4::CompiledData::Binding::Type_GroupProperty: |
665 | scanObjectRecursively(objectIndex: binding->value.objectIndex, annotateScriptBindings); |
666 | break; |
667 | default: |
668 | break; |
669 | } |
670 | } |
671 | } |
672 | |
673 | QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler) |
674 | : QQmlCompilePass(typeCompiler) |
675 | , qmlObjects(*typeCompiler->qmlObjects()) |
676 | , propertyCaches(typeCompiler->propertyCaches()) |
677 | { |
678 | } |
679 | |
680 | void QQmlAliasAnnotator::annotateBindingsToAliases() |
681 | { |
682 | for (int i = 0; i < qmlObjects.size(); ++i) { |
683 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: i); |
684 | if (!propertyCache) |
685 | continue; |
686 | |
687 | const QmlIR::Object *obj = qmlObjects.at(i); |
688 | |
689 | QQmlPropertyResolver resolver(propertyCache); |
690 | const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
691 | |
692 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
693 | if (!binding->isValueBinding()) |
694 | continue; |
695 | bool notInRevision = false; |
696 | const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
697 | if (pd && pd->isAlias()) |
698 | binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias); |
699 | } |
700 | } |
701 | } |
702 | |
703 | QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler) |
704 | : QQmlCompilePass(typeCompiler) |
705 | , qmlObjects(*typeCompiler->qmlObjects()) |
706 | , propertyCaches(typeCompiler->propertyCaches()) |
707 | { |
708 | |
709 | } |
710 | |
711 | void QQmlScriptStringScanner::scan() |
712 | { |
713 | const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>(); |
714 | for (int i = 0; i < qmlObjects.size(); ++i) { |
715 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: i); |
716 | if (!propertyCache) |
717 | continue; |
718 | |
719 | const QmlIR::Object *obj = qmlObjects.at(i); |
720 | |
721 | QQmlPropertyResolver resolver(propertyCache); |
722 | const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty(); |
723 | |
724 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
725 | if (binding->type() != QV4::CompiledData::Binding::Type_Script) |
726 | continue; |
727 | bool notInRevision = false; |
728 | const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(name: stringAt(idx: binding->propertyNameIndex), notInRevision: ¬InRevision) : defaultProperty; |
729 | if (!pd || pd->propType() != scriptStringMetaType) |
730 | continue; |
731 | |
732 | QString script = compiler->bindingAsString(object: obj, scriptIndex: binding->value.compiledScriptIndex); |
733 | binding->stringIndex = compiler->registerString(str: script); |
734 | } |
735 | } |
736 | } |
737 | |
738 | template<> |
739 | void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects( |
740 | QmlIR::Object *object) const |
741 | { |
742 | object->namedObjectsInComponent.allocate(pool: m_compiler->memoryPool(), container: m_idToObjectIndex); |
743 | } |
744 | |
745 | template<> |
746 | bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const |
747 | { |
748 | m_compiler->qmlObjects()->at(i: index)->flags |= QV4::CompiledData::Object::IsComponent; |
749 | return true; |
750 | } |
751 | |
752 | template<> |
753 | void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const |
754 | { |
755 | m_compiler->qmlObjects()->at(i: index)->id = m_idToObjectIndex.size(); |
756 | } |
757 | |
758 | template<> |
759 | bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::wrapImplicitComponent(QmlIR::Binding *binding) |
760 | { |
761 | QQmlJS::MemoryPool *pool = m_compiler->memoryPool(); |
762 | QVector<QmlIR::Object *> *qmlObjects = m_compiler->qmlObjects(); |
763 | |
764 | // emulate "import QML 1.0" and then wrap the component in "QML.Component {}" |
765 | QQmlType componentType = QQmlMetaType::qmlType( |
766 | metaObject: &QQmlComponent::staticMetaObject, QStringLiteral("QML"), |
767 | version: QTypeRevision::fromVersion(majorVersion: 1, minorVersion: 0)); |
768 | Q_ASSERT(componentType.isValid()); |
769 | const QString qualifier = QStringLiteral("QML"); |
770 | |
771 | m_compiler->addImport(module: componentType.module(), qualifier, version: componentType.version()); |
772 | |
773 | QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>(); |
774 | syntheticComponent->init( |
775 | pool, |
776 | typeNameIndex: m_compiler->registerString( |
777 | str: qualifier + QLatin1Char('.') + componentType.elementName()), |
778 | idIndex: m_compiler->registerString(str: QString()), location: binding->valueLocation); |
779 | syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent; |
780 | |
781 | if (!m_compiler->resolvedTypes->contains(key: syntheticComponent->inheritedTypeNameIndex)) { |
782 | auto typeRef = new QV4::ResolvedTypeReference; |
783 | typeRef->setType(componentType); |
784 | typeRef->setVersion(componentType.version()); |
785 | m_compiler->resolvedTypes->insert(key: syntheticComponent->inheritedTypeNameIndex, value: typeRef); |
786 | } |
787 | |
788 | qmlObjects->append(t: syntheticComponent); |
789 | const int componentIndex = qmlObjects->size() - 1; |
790 | // Keep property caches symmetric |
791 | QQmlPropertyCache::ConstPtr componentCache |
792 | = QQmlMetaType::propertyCache(metaObject: &QQmlComponent::staticMetaObject); |
793 | m_propertyCaches->append(cache: componentCache); |
794 | |
795 | QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>(); |
796 | *syntheticBinding = *binding; |
797 | |
798 | // The synthetic binding inside Component has no name. It's just "Component { Foo {} }". |
799 | syntheticBinding->propertyNameIndex = 0; |
800 | |
801 | syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object); |
802 | QString error = syntheticComponent->appendBinding(b: syntheticBinding, /*isListBinding*/false); |
803 | Q_ASSERT(error.isEmpty()); |
804 | Q_UNUSED(error); |
805 | |
806 | binding->value.objectIndex = componentIndex; |
807 | |
808 | m_componentRoots.append(t: componentIndex); |
809 | return true; |
810 | } |
811 | |
812 | template<> |
813 | void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupProperty( |
814 | const CompiledObject &component, CompiledBinding *binding) |
815 | { |
816 | Q_UNUSED(component); |
817 | // We cannot make it fail here. It might be a custom-parsed property |
818 | const int targetObjectIndex = m_idToObjectIndex.value(key: binding->propertyNameIndex, defaultValue: -1); |
819 | if (targetObjectIndex != -1) |
820 | m_propertyCaches->set(index: binding->value.objectIndex, replacement: m_propertyCaches->at(index: targetObjectIndex)); |
821 | } |
822 | |
823 | template<> |
824 | typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult |
825 | QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( |
826 | const CompiledObject &component, int objectIndex, QQmlError *error) |
827 | { |
828 | Q_UNUSED(component); |
829 | |
830 | const QmlIR::Object * const obj = m_compiler->objectAt(index: objectIndex); |
831 | if (!obj->aliasCount()) |
832 | return AllAliasesResolved; |
833 | |
834 | int numResolvedAliases = 0; |
835 | bool seenUnresolvedAlias = false; |
836 | |
837 | for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { |
838 | if (alias->hasFlag(flag: QV4::CompiledData::Alias::Resolved)) |
839 | continue; |
840 | |
841 | seenUnresolvedAlias = true; |
842 | |
843 | const int idIndex = alias->idIndex(); |
844 | const int targetObjectIndex = m_idToObjectIndex.value(key: idIndex, defaultValue: -1); |
845 | if (targetObjectIndex == -1) { |
846 | *error = qQmlCompileError( |
847 | location: alias->referenceLocation, |
848 | description: QQmlComponentAndAliasResolverBase::tr(sourceText: "Invalid alias reference. Unable to find id \"%1\"").arg(a: stringAt(idx: idIndex))); |
849 | break; |
850 | } |
851 | |
852 | const QmlIR::Object *targetObject = m_compiler->objectAt(index: targetObjectIndex); |
853 | Q_ASSERT(targetObject->id >= 0); |
854 | alias->setTargetObjectId(targetObject->id); |
855 | alias->setIsAliasToLocalAlias(false); |
856 | |
857 | const QString aliasPropertyValue = stringAt(idx: alias->propertyNameIndex); |
858 | |
859 | QStringView property; |
860 | QStringView subProperty; |
861 | |
862 | const int propertySeparator = aliasPropertyValue.indexOf(ch: QLatin1Char('.')); |
863 | if (propertySeparator != -1) { |
864 | property = QStringView{aliasPropertyValue}.left(n: propertySeparator); |
865 | subProperty = QStringView{aliasPropertyValue}.mid(pos: propertySeparator + 1); |
866 | } else |
867 | property = QStringView(aliasPropertyValue); |
868 | |
869 | QQmlPropertyIndex propIdx; |
870 | |
871 | if (property.isEmpty()) { |
872 | alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); |
873 | } else { |
874 | QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(index: targetObjectIndex); |
875 | if (!targetCache) { |
876 | *error = qQmlCompileError( |
877 | location: alias->referenceLocation, |
878 | description: QQmlComponentAndAliasResolverBase::tr(sourceText: "Invalid alias target location: %1").arg(a: property.toString())); |
879 | break; |
880 | } |
881 | |
882 | QQmlPropertyResolver resolver(targetCache); |
883 | |
884 | const QQmlPropertyData *targetProperty = resolver.property(name: property.toString()); |
885 | |
886 | // If it's an alias that we haven't resolved yet, try again later. |
887 | if (!targetProperty) { |
888 | bool aliasPointsToOtherAlias = false; |
889 | int localAliasIndex = 0; |
890 | for (auto targetAlias = targetObject->aliasesBegin(), end = targetObject->aliasesEnd(); targetAlias != end; ++targetAlias, ++localAliasIndex) { |
891 | if (stringAt(idx: targetAlias->nameIndex()) == property) { |
892 | aliasPointsToOtherAlias = true; |
893 | break; |
894 | } |
895 | } |
896 | if (aliasPointsToOtherAlias) { |
897 | if (targetObjectIndex == objectIndex) { |
898 | alias->localAliasIndex = localAliasIndex; |
899 | alias->setIsAliasToLocalAlias(true); |
900 | alias->setFlag(QV4::CompiledData::Alias::Resolved); |
901 | ++numResolvedAliases; |
902 | continue; |
903 | } |
904 | |
905 | // restore |
906 | alias->setIdIndex(idIndex); |
907 | // Try again later and resolve the target alias first. |
908 | break; |
909 | } |
910 | } |
911 | |
912 | if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) { |
913 | *error = qQmlCompileError( |
914 | location: alias->referenceLocation, |
915 | description: QQmlComponentAndAliasResolverBase::tr(sourceText: "Invalid alias target location: %1").arg(a: property.toString())); |
916 | break; |
917 | } |
918 | |
919 | propIdx = QQmlPropertyIndex(targetProperty->coreIndex()); |
920 | |
921 | if (!subProperty.isEmpty()) { |
922 | const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(type: targetProperty->propType()); |
923 | if (!valueTypeMetaObject) { |
924 | // could be a deep alias |
925 | bool isDeepAlias = subProperty.at(n: 0).isLower(); |
926 | if (isDeepAlias) { |
927 | isDeepAlias = false; |
928 | for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { |
929 | auto binding = *it; |
930 | if (m_compiler->stringAt(idx: binding.propertyNameIndex) == property) { |
931 | resolver = QQmlPropertyResolver(m_propertyCaches->at(index: binding.value.objectIndex)); |
932 | const QQmlPropertyData *actualProperty = resolver.property(name: subProperty.toString()); |
933 | if (actualProperty) { |
934 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), actualProperty->coreIndex()); |
935 | isDeepAlias = true; |
936 | } |
937 | } |
938 | } |
939 | } |
940 | if (!isDeepAlias) { |
941 | *error = qQmlCompileError( |
942 | location: alias->referenceLocation, |
943 | description: QQmlComponentAndAliasResolverBase::tr(sourceText: "Invalid alias target location: %1").arg(a: subProperty.toString())); |
944 | break; |
945 | } |
946 | } else { |
947 | |
948 | int valueTypeIndex = |
949 | valueTypeMetaObject->indexOfProperty(name: subProperty.toString().toUtf8().constData()); |
950 | if (valueTypeIndex == -1) { |
951 | *error = qQmlCompileError( |
952 | location: alias->referenceLocation, |
953 | description: QQmlComponentAndAliasResolverBase::tr(sourceText: "Invalid alias target location: %1").arg(a: subProperty.toString())); |
954 | break; |
955 | } |
956 | Q_ASSERT(valueTypeIndex <= 0x0000FFFF); |
957 | |
958 | propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex); |
959 | } |
960 | } else { |
961 | if (targetProperty->isQObject()) |
962 | alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject); |
963 | } |
964 | } |
965 | |
966 | alias->encodedMetaPropertyIndex = propIdx.toEncoded(); |
967 | alias->setFlag(QV4::CompiledData::Alias::Resolved); |
968 | numResolvedAliases++; |
969 | } |
970 | |
971 | if (numResolvedAliases == 0) |
972 | return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; |
973 | |
974 | return SomeAliasesResolved; |
975 | } |
976 | |
977 | QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler) |
978 | : QQmlCompilePass(typeCompiler) |
979 | , qmlObjects(typeCompiler->qmlObjects()) |
980 | , propertyCaches(typeCompiler->propertyCaches()) |
981 | , customParsers(typeCompiler->customParserCache()) |
982 | , _seenObjectWithId(false) |
983 | { |
984 | } |
985 | |
986 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject() |
987 | { |
988 | for (int i = 0; i < qmlObjects->size(); ++i) { |
989 | if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot) |
990 | && !scanObject(objectIndex: i, scopeDeferred: ScopeDeferred::False)) { |
991 | return false; |
992 | } |
993 | } |
994 | return scanObject(/*root object*/objectIndex: 0, scopeDeferred: ScopeDeferred::False); |
995 | } |
996 | |
997 | bool QQmlDeferredAndCustomParserBindingScanner::scanObject( |
998 | int objectIndex, ScopeDeferred scopeDeferred) |
999 | { |
1000 | using namespace QV4::CompiledData; |
1001 | |
1002 | QmlIR::Object *obj = qmlObjects->at(i: objectIndex); |
1003 | if (obj->idNameIndex != 0) |
1004 | _seenObjectWithId = true; |
1005 | |
1006 | if (obj->flags & Object::IsComponent) { |
1007 | Q_ASSERT(obj->bindingCount() == 1); |
1008 | const Binding *componentBinding = obj->firstBinding(); |
1009 | Q_ASSERT(componentBinding->type() == Binding::Type_Object); |
1010 | // Components are separate from their surrounding scope. They cannot be deferred. |
1011 | return scanObject(objectIndex: componentBinding->value.objectIndex, scopeDeferred: ScopeDeferred::False); |
1012 | } |
1013 | |
1014 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: objectIndex); |
1015 | if (!propertyCache) |
1016 | return true; |
1017 | |
1018 | QString defaultPropertyName; |
1019 | const QQmlPropertyData *defaultProperty = nullptr; |
1020 | if (obj->indexOfDefaultPropertyOrAlias != -1) { |
1021 | const QQmlPropertyCache *cache = propertyCache->parent().data(); |
1022 | defaultPropertyName = cache->defaultPropertyName(); |
1023 | defaultProperty = cache->defaultProperty(); |
1024 | } else { |
1025 | defaultPropertyName = propertyCache->defaultPropertyName(); |
1026 | defaultProperty = propertyCache->defaultProperty(); |
1027 | } |
1028 | |
1029 | QQmlCustomParser *customParser = customParsers.value(key: obj->inheritedTypeNameIndex); |
1030 | |
1031 | QQmlPropertyResolver propertyResolver(propertyCache); |
1032 | |
1033 | QStringList deferredPropertyNames; |
1034 | QStringList immediatePropertyNames; |
1035 | { |
1036 | const QMetaObject *mo = propertyCache->firstCppMetaObject(); |
1037 | const int deferredNamesIndex = mo->indexOfClassInfo(name: "DeferredPropertyNames"); |
1038 | const int immediateNamesIndex = mo->indexOfClassInfo(name: "ImmediatePropertyNames"); |
1039 | if (deferredNamesIndex != -1) { |
1040 | if (immediateNamesIndex != -1) { |
1041 | COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and " |
1042 | "ImmediatePropertyNames on the same type.")); |
1043 | } |
1044 | const QMetaClassInfo classInfo = mo->classInfo(index: deferredNamesIndex); |
1045 | deferredPropertyNames = QString::fromUtf8(utf8: classInfo.value()).split(sep: u','); |
1046 | } else if (immediateNamesIndex != -1) { |
1047 | const QMetaClassInfo classInfo = mo->classInfo(index: immediateNamesIndex); |
1048 | immediatePropertyNames = QString::fromUtf8(utf8: classInfo.value()).split(sep: u','); |
1049 | |
1050 | // If the property contains an empty string, all properties shall be deferred. |
1051 | if (immediatePropertyNames.isEmpty()) |
1052 | immediatePropertyNames.append(t: QString()); |
1053 | } |
1054 | } |
1055 | |
1056 | for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) { |
1057 | QString name = stringAt(idx: binding->propertyNameIndex); |
1058 | |
1059 | if (customParser) { |
1060 | if (binding->type() == Binding::Type_AttachedProperty) { |
1061 | if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) { |
1062 | binding->setFlag(Binding::IsCustomParserBinding); |
1063 | obj->flags |= Object::HasCustomParserBindings; |
1064 | continue; |
1065 | } |
1066 | } else if (QQmlSignalNames::isHandlerName(signalName: name) |
1067 | && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) { |
1068 | obj->flags |= Object::HasCustomParserBindings; |
1069 | binding->setFlag(Binding::IsCustomParserBinding); |
1070 | continue; |
1071 | } |
1072 | } |
1073 | |
1074 | const bool hasPropertyData = [&]() { |
1075 | if (name.isEmpty()) { |
1076 | name = defaultPropertyName; |
1077 | if (defaultProperty) |
1078 | return true; |
1079 | } else if (name.constData()->isUpper()) { |
1080 | // Upper case names cannot be custom-parsed unless they are attached properties |
1081 | // and the custom parser explicitly accepts them. See above for that case. |
1082 | return false; |
1083 | } else { |
1084 | bool notInRevision = false; |
1085 | if (propertyResolver.property( |
1086 | name, notInRevision: ¬InRevision, check: QQmlPropertyResolver::CheckRevision)) { |
1087 | return true; |
1088 | } |
1089 | } |
1090 | |
1091 | if (!customParser) |
1092 | return false; |
1093 | |
1094 | const Binding::Flags bindingFlags = binding->flags(); |
1095 | if (bindingFlags & Binding::IsSignalHandlerExpression |
1096 | || bindingFlags & Binding::IsSignalHandlerObject |
1097 | || bindingFlags & Binding::IsPropertyObserver) { |
1098 | // These signal handlers cannot be custom-parsed. We have already established |
1099 | // that the signal exists. |
1100 | return false; |
1101 | } |
1102 | |
1103 | // If the property isn't found, we may want to custom-parse the binding. |
1104 | obj->flags |= Object::HasCustomParserBindings; |
1105 | binding->setFlag(Binding::IsCustomParserBinding); |
1106 | return false; |
1107 | }(); |
1108 | |
1109 | bool seenSubObjectWithId = false; |
1110 | bool isExternal = false; |
1111 | if (binding->type() >= Binding::Type_Object) { |
1112 | const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty(); |
1113 | isExternal = !isOwnProperty && binding->isGroupProperty(); |
1114 | if (isOwnProperty || isExternal) { |
1115 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1116 | const bool subObjectValid = scanObject( |
1117 | objectIndex: binding->value.objectIndex, |
1118 | scopeDeferred: (isExternal || scopeDeferred == ScopeDeferred::True) |
1119 | ? ScopeDeferred::True |
1120 | : ScopeDeferred::False); |
1121 | qSwap(value1&: _seenObjectWithId, value2&: seenSubObjectWithId); |
1122 | if (!subObjectValid) |
1123 | return false; |
1124 | _seenObjectWithId |= seenSubObjectWithId; |
1125 | } |
1126 | } |
1127 | |
1128 | bool isDeferred = false; |
1129 | if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(str: name)) { |
1130 | if (seenSubObjectWithId) { |
1131 | COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned " |
1132 | "to a deferred property.")); |
1133 | } |
1134 | if (isExternal || !disableInternalDeferredProperties()) |
1135 | isDeferred = true; |
1136 | } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(str: name)) { |
1137 | if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupProperty) { |
1138 | if (isExternal || !disableInternalDeferredProperties()) |
1139 | isDeferred = true; |
1140 | } |
1141 | } |
1142 | |
1143 | if (binding->type() >= Binding::Type_Object) { |
1144 | if (isExternal && !isDeferred && !customParser) { |
1145 | COMPILE_EXCEPTION( |
1146 | binding, tr("Cannot assign to non-existent property \"%1\"").arg(name)); |
1147 | } |
1148 | } |
1149 | |
1150 | if (isDeferred) { |
1151 | binding->setFlag(Binding::IsDeferredBinding); |
1152 | obj->flags |= Object::HasDeferredBindings; |
1153 | } |
1154 | } |
1155 | |
1156 | return true; |
1157 | } |
1158 | |
1159 | QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler) |
1160 | : QQmlCompilePass(typeCompiler) |
1161 | , qmlObjects(*typeCompiler->qmlObjects()) |
1162 | , propertyCaches(typeCompiler->propertyCaches()) |
1163 | { |
1164 | |
1165 | } |
1166 | |
1167 | void QQmlDefaultPropertyMerger::mergeDefaultProperties() |
1168 | { |
1169 | for (int i = 0; i < qmlObjects.size(); ++i) |
1170 | mergeDefaultProperties(objectIndex: i); |
1171 | } |
1172 | |
1173 | void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex) |
1174 | { |
1175 | QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(index: objectIndex); |
1176 | if (!propertyCache) |
1177 | return; |
1178 | |
1179 | QmlIR::Object *object = qmlObjects.at(i: objectIndex); |
1180 | |
1181 | QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName(); |
1182 | QmlIR::Binding *bindingsToReinsert = nullptr; |
1183 | QmlIR::Binding *tail = nullptr; |
1184 | |
1185 | QmlIR::Binding *previousBinding = nullptr; |
1186 | QmlIR::Binding *binding = object->firstBinding(); |
1187 | while (binding) { |
1188 | if (binding->propertyNameIndex == quint32(0) || stringAt(idx: binding->propertyNameIndex) != defaultProperty) { |
1189 | previousBinding = binding; |
1190 | binding = binding->next; |
1191 | continue; |
1192 | } |
1193 | |
1194 | QmlIR::Binding *toReinsert = binding; |
1195 | binding = object->unlinkBinding(before: previousBinding, binding); |
1196 | |
1197 | if (!tail) { |
1198 | bindingsToReinsert = toReinsert; |
1199 | tail = toReinsert; |
1200 | } else { |
1201 | tail->next = toReinsert; |
1202 | tail = tail->next; |
1203 | } |
1204 | tail->next = nullptr; |
1205 | } |
1206 | |
1207 | binding = bindingsToReinsert; |
1208 | while (binding) { |
1209 | QmlIR::Binding *toReinsert = binding; |
1210 | binding = binding->next; |
1211 | object->insertSorted(b: toReinsert); |
1212 | } |
1213 | } |
1214 | |
1215 | QT_END_NAMESPACE |
1216 |
Definitions
- disableInternalDeferredProperties
- lcQmlTypeCompiler
- QQmlTypeCompiler
- compile
- recordError
- recordError
- recordError
- stringAt
- registerString
- registerConstant
- qmlUnit
- imports
- qmlObjects
- propertyCaches
- propertyCaches
- memoryPool
- newStringRef
- stringPool
- bindingAsString
- addImport
- qmlTypeForComponent
- QQmlCompilePass
- SignalHandlerResolver
- resolveSignalHandlerExpressions
- resolveSignalHandlerExpressions
- QQmlEnumTypeResolver
- resolveEnumBindings
- assignEnumToBinding
- tryQualifiedEnumAssignment
- evaluateEnum
- QQmlCustomParserScriptIndexer
- annotateBindingsWithScriptStrings
- scanObjectRecursively
- QQmlAliasAnnotator
- annotateBindingsToAliases
- QQmlScriptStringScanner
- scan
- allocateNamedObjects
- markAsComponent
- setObjectId
- wrapImplicitComponent
- resolveGeneralizedGroupProperty
- resolveAliasesInObject
- QQmlDeferredAndCustomParserBindingScanner
- scanObject
- scanObject
- QQmlDefaultPropertyMerger
- mergeDefaultProperties
Learn to use CMake with our Intro Training
Find out more