diff options
Diffstat (limited to 'src')
44 files changed, 592 insertions, 566 deletions
diff --git a/src/qml/Qt6QmlMacros.cmake b/src/qml/Qt6QmlMacros.cmake index c9aebff06d..b0eb863ddf 100644 --- a/src/qml/Qt6QmlMacros.cmake +++ b/src/qml/Qt6QmlMacros.cmake @@ -914,6 +914,13 @@ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details." endif() endif() + # Ensure that a top-level target-tooling that accumulates the target sources + # Before 3.19 interface targets could not have sources, so a different approach is used + # for that. + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.19) + _qt_internal_ensure_tooling_target("${target}_tooling" DEPENDENT_TARGET "${target}") + endif() + set(cache_target) qt6_target_qml_sources(${target} __QT_INTERNAL_FORCE_DEFER_QMLDIR @@ -924,6 +931,20 @@ Check https://doc.qt.io/qt-6/qt-cmake-policy-qtp0001.html for policy details." ) list(APPEND output_targets ${cache_target}) + # Setup the qml/resource files copy instructions + _qt_internal_qml_copy_files_to_build_dir("${target}" + CUSTOM_TARGET_SUFFIX qml + PROP_WITH_ENTRIES _qt_qml_files_to_copy + PROP_WITH_SRCS _qt_qml_files_absolute_src_paths + FILE_TYPE "sources" + ) + _qt_internal_qml_copy_files_to_build_dir("${target}" + CUSTOM_TARGET_SUFFIX res + PROP_WITH_ENTRIES _qt_qml_resources_to_copy + PROP_WITH_SRCS _qt_qml_resources_absolute_src_paths + FILE_TYPE "resources" + ) + # Build an init object library for static plugins and propagate it along with the plugin # target. # TODO: Figure out if we can move this code block into qt_add_qml_plugin. Need to consider @@ -2798,7 +2819,10 @@ function(_qt_internal_qml_copy_files_to_build_dir target) # Protect against multiple calls of qt6_add_qml_sources. It's enough to setup once, # because we use generator expressions to get all the files. if(copy_files_setup_done) - return() + message(FATAL_ERROR + "_qt_internal_qml_copy_files_to_build_dir was called more than once for " + "target ${target} CUSTOM_TARGET_SUFFIX ${arg_CUSTOM_TARGET_SUFFIX}." + ) endif() set_property(TARGET "${target}" PROPERTY "${setup_done_prop}" TRUE) @@ -3585,7 +3609,6 @@ function(qt6_target_qml_sources target) # system target from CMake 3.19 onward, and we can add the sources # progressively over multiple calls. set(tooling_target "${target}_tooling") - _qt_internal_ensure_tooling_target("${tooling_target}" DEPENDENT_TARGET "${target}") # If the tooling target was created in another directory scope, we must create another # interface library in this directory scope to drive the custom target. A dependency @@ -3615,19 +3638,6 @@ function(qt6_target_qml_sources target) endif() endif() - _qt_internal_qml_copy_files_to_build_dir("${target}" - CUSTOM_TARGET_SUFFIX qml - PROP_WITH_ENTRIES _qt_qml_files_to_copy - PROP_WITH_SRCS _qt_qml_files_absolute_src_paths - FILE_TYPE "sources" - ) - _qt_internal_qml_copy_files_to_build_dir("${target}" - CUSTOM_TARGET_SUFFIX res - PROP_WITH_ENTRIES _qt_qml_resources_to_copy - PROP_WITH_SRCS _qt_qml_resources_absolute_src_paths - FILE_TYPE "resources" - ) - # Batch all the non-compiled qml sources into a single resource for this # call. Subsequent calls for the same target will be in their own separate # resource file. diff --git a/src/qml/common/qv4compileddata.cpp b/src/qml/common/qv4compileddata.cpp index 9ce5f5058f..4fbd05d060 100644 --- a/src/qml/common/qv4compileddata.cpp +++ b/src/qml/common/qv4compileddata.cpp @@ -50,29 +50,6 @@ bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) return true; } -/*! - \internal - This function creates a temporary key vector and sorts it to guarantuee a stable - hash. This is used to calculate a check-sum on dependent meta-objects. - */ -bool ResolvedTypeReferenceMap::addToHash( - QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const -{ - std::vector<int> keys (size()); - int i = 0; - for (auto it = constBegin(), end = constEnd(); it != end; ++it) { - keys[i] = it.key(); - ++i; - } - std::sort(keys.begin(), keys.end()); - for (int key: keys) { - if (!this->operator[](key)->addToHash(hash, checksums)) - return false; - } - - return true; -} - CompilationUnit::CompilationUnit( const Unit *unitData, const QString &fileName, const QString &finalUrlString) { @@ -354,21 +331,6 @@ int CompilationUnit::totalParserStatusCount(const QString &inlineComponentRootNa return inlineComponentData[inlineComponentRootName].totalParserStatusCount; } -bool CompilationUnit::verifyChecksum(const DependentTypesHasher &dependencyHasher) const -{ - if (!dependencyHasher) { - for (size_t i = 0; i < sizeof(data->dependencyMD5Checksum); ++i) { - if (data->dependencyMD5Checksum[i] != 0) - return false; - } - return true; - } - const QByteArray checksum = dependencyHasher(); - return checksum.size() == sizeof(data->dependencyMD5Checksum) - && memcmp(data->dependencyMD5Checksum, checksum.constData(), - sizeof(data->dependencyMD5Checksum)) == 0; -} - QQmlType CompilationUnit::qmlTypeForComponent(const QString &inlineComponentName) const { if (inlineComponentName.isEmpty()) diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h index 889df101d0..3e29ec7aef 100644 --- a/src/qml/common/qv4compileddata_p.h +++ b/src/qml/common/qv4compileddata_p.h @@ -51,7 +51,7 @@ QT_BEGIN_NAMESPACE // Also change the comment behind the number to describe the latest change. This has the added // benefit that if another patch changes the version too, it will result in a merge conflict, and // not get removed silently. -#define QV4_DATA_STRUCTURE_VERSION 0x45 // Removed Qt version and compile hash checks +#define QV4_DATA_STRUCTURE_VERSION 0x46 // Removed property indices from aliases class QIODevice; class QQmlTypeNameCache; @@ -85,10 +85,7 @@ namespace CompiledData { using BindingPropertyData = QVector<const QQmlPropertyData *>; // map from name index -struct ResolvedTypeReferenceMap: public QHash<int, ResolvedTypeReference*> -{ - bool addToHash(QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) const; -}; +using ResolvedTypeReferenceMap = QHash<int, ResolvedTypeReference*>; struct String; struct Function; @@ -871,8 +868,8 @@ public: enum Flag : unsigned int { IsReadOnly = 0x1, - Resolved = 0x2, - AliasPointsToPointerObject = 0x4 + AliasPointsToPointerObject = 0x2, + // One bit vacant }; Q_DECLARE_FLAGS(Flags, Flag) @@ -882,7 +879,6 @@ public: union { quint32_le propertyNameIndex; // string index - qint32_le encodedMetaPropertyIndex; quint32_le localAliasIndex; // index in list of aliases local to the object (if targetObjectId == objectId) }; Location location; @@ -913,12 +909,6 @@ public: nameIndexAndFlags.set<NameIndexField>(nameIndex); } - bool isObjectAlias() const - { - Q_ASSERT(hasFlag(Resolved)); - return encodedMetaPropertyIndex == -1; - } - quint32 idIndex() const { return idIndexAndTargetObjectIdAndAliasToLocalAlias.get<IdIndexField>(); @@ -1217,7 +1207,7 @@ struct Unit quint32_le unitSize; // Size of the Unit and any depending data. char md5Checksum[16]; // checksum of all bytes following this field. - char dependencyMD5Checksum[16]; + char reserved2[16]; // Was dependency checksum. 0 means "No checksum needed" to old versions. enum : unsigned int { IsJavascript = 0x1, @@ -1449,8 +1439,6 @@ struct TypeReferenceMap : QHash<int, TypeReference> } }; -using DependentTypesHasher = std::function<QByteArray()>; - struct InlineComponentData { InlineComponentData() = default; @@ -1681,8 +1669,6 @@ public: void finalizeCompositeType(const QQmlType &type); - bool verifyChecksum(const CompiledData::DependentTypesHasher &dependencyHasher) const; - enum class ListPropertyAssignBehavior { Append, Replace, ReplaceIfNotDefault }; ListPropertyAssignBehavior listPropertyAssignBehavior() const { diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp index cdfa698ff6..094df48a2a 100644 --- a/src/qml/compiler/qqmlirbuilder.cpp +++ b/src/qml/compiler/qqmlirbuilder.cpp @@ -1645,7 +1645,7 @@ bool IRBuilder::isRedundantNullInitializerForPropertyDeclaration(Property *prope return QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr); } -void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher) +void QmlUnitGenerator::generate(Document &output) { using namespace QV4::CompiledData; @@ -1740,14 +1740,6 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen } } - if (dependencyHasher) { - const QByteArray checksum = dependencyHasher(); - if (checksum.size() == sizeof(createdUnit->dependencyMD5Checksum)) { - memcpy(createdUnit->dependencyMD5Checksum, checksum.constData(), - sizeof(createdUnit->dependencyMD5Checksum)); - } - } - createdUnit->sourceFileIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.fileName); createdUnit->finalUrlIndex = output.jsGenerator.stringTable.getStringId(output.jsModule.finalUrl); } diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h index d4cdcf62f0..36c9c33561 100644 --- a/src/qml/compiler/qqmlirbuilder_p.h +++ b/src/qml/compiler/qqmlirbuilder_p.h @@ -622,7 +622,7 @@ public: struct Q_QML_COMPILER_EXPORT QmlUnitGenerator { - void generate(Document &output, const QV4::CompiledData::DependentTypesHasher &dependencyHasher = QV4::CompiledData::DependentTypesHasher()); + void generate(Document &output); private: typedef bool (Binding::*BindingFilter)() const; diff --git a/src/qml/compiler/qv4compiler.cpp b/src/qml/compiler/qv4compiler.cpp index c2c7426bd2..2ef93cf7d2 100644 --- a/src/qml/compiler/qv4compiler.cpp +++ b/src/qml/compiler/qv4compiler.cpp @@ -632,8 +632,9 @@ QV4::CompiledData::Unit QV4::Compiler::JSUnitGenerator::generateHeader(QV4::Comp unit.flags = QV4::CompiledData::Unit::IsJavascript; unit.flags |= module->unitFlags; unit.version = QV4_DATA_STRUCTURE_VERSION; + unit.reserved = 0; memset(unit.md5Checksum, 0, sizeof(unit.md5Checksum)); - memset(unit.dependencyMD5Checksum, 0, sizeof(unit.dependencyMD5Checksum)); + memset(unit.reserved2, 0, sizeof(unit.reserved2)); quint32 nextOffset = sizeof(CompiledData::Unit); diff --git a/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc b/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc index fbf7306a3d..681385a08a 100644 --- a/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc +++ b/src/qml/doc/src/tools/qtqml-tooling-qmlformat.qdoc @@ -101,7 +101,10 @@ The following options are available: \li --functions-spacing \li \li Ensure spaces between functions (only works with normalize option). - +\row + \li --semicolon-rule + \li always + \li Customize the addition of semicolons at the end of JS statements. \endtable \section2 Arguments @@ -155,4 +158,12 @@ the valid file entries in place. \warning If you provide -F option, qmlformat will ignore the positional arguments. +\section3 Semicolon Rule +The \c{--semicolon-rule} option allows you to customize the addition of semicolons at the end of JS statements. +The following values are accepted: +\list + \li \c{always} - Always add semicolons (default). + \li \c{essential} - Remove semicolons unless omitting them would cause issues. +\endlist + */ diff --git a/src/qml/jsruntime/qv4resolvedtypereference.cpp b/src/qml/jsruntime/qv4resolvedtypereference.cpp index 7dcf2cd0b8..0f55660048 100644 --- a/src/qml/jsruntime/qv4resolvedtypereference.cpp +++ b/src/qml/jsruntime/qv4resolvedtypereference.cpp @@ -64,35 +64,6 @@ QQmlPropertyCache::ConstPtr ResolvedTypeReference::createPropertyCache() } } -bool ResolvedTypeReference::addToHash( - QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums) -{ - if (m_type.isInlineComponentType()) { - - // A reference to an inline component in the same file will have - // - no compilation unit since we cannot resolve the compilation unit before it's built. - // - a property cache since we've assigned one in buildMetaObjectsIncrementally(). - // - a QQmlType that says it's an inline component. - // We don't have to add such a thing to the hash since if it changes, the QML document - // itself changes, leading to a new timestamp, which is checked before the checksum. - if (!m_compilationUnit) - return !m_typePropertyCache.isNull(); - - } else if (m_type.isValid()) { - bool ok = false; - if (QQmlPropertyCache::ConstPtr propertyCache = createPropertyCache()) - hash->addData(propertyCache->checksum(checksums, &ok)); - else - Q_ASSERT(m_type.module() == QLatin1String("QML")); // a builtin without metaobject - return ok; - } - if (!m_compilationUnit) - return false; - hash->addData({m_compilationUnit->unitData()->md5Checksum, - sizeof(m_compilationUnit->unitData()->md5Checksum)}); - return true; -} - } // namespace QV4 QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4resolvedtypereference_p.h b/src/qml/jsruntime/qv4resolvedtypereference_p.h index 4aaafdd71c..fce71ea0ad 100644 --- a/src/qml/jsruntime/qv4resolvedtypereference_p.h +++ b/src/qml/jsruntime/qv4resolvedtypereference_p.h @@ -39,7 +39,6 @@ public: QQmlPropertyCache::ConstPtr propertyCache() const; QQmlPropertyCache::ConstPtr createPropertyCache(); - bool addToHash(QCryptographicHash *hash, QHash<quintptr, QByteArray> *checksums); void doDynamicTypeCheck(); diff --git a/src/qml/qml/qqmlcomponentandaliasresolver_p.h b/src/qml/qml/qqmlcomponentandaliasresolver_p.h index 50d65ddb37..528a12b1c2 100644 --- a/src/qml/qml/qqmlcomponentandaliasresolver_p.h +++ b/src/qml/qml/qqmlcomponentandaliasresolver_p.h @@ -63,7 +63,20 @@ private: void setObjectId(int index) const; [[nodiscard]] bool markAsComponent(int index) const; [[nodiscard]] AliasResolutionResult resolveAliasesInObject( - const CompiledObject &component, int objectIndex, QQmlError *error); + const CompiledObject &component, int objectIndex, + QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error); + [[nodiscard]] bool appendAliasToPropertyCache( + const CompiledObject *component, const QV4::CompiledData::Alias *alias, int objectIndex, + int aliasIndex, int encodedPropertyIndex, + QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error) + { + *error = aliasCacheCreator->appendAliasToPropertyCache( + *component, *alias, objectIndex, aliasIndex, encodedPropertyIndex); + resolvedAliases.insert(alias); + return !error->isValid(); + } + + void resolveGeneralizedGroupProperty(const CompiledObject &component, CompiledBinding *binding); [[nodiscard]] bool wrapImplicitComponent(CompiledBinding *binding); @@ -122,6 +135,7 @@ private: QVector<quint32> m_componentRoots; QVector<int> m_objectsWithAliases; QVector<CompiledBinding *> m_generalizedGroupProperties; + QSet<const QV4::CompiledData::Alias *> resolvedAliases; typename ObjectContainer::IdToObjectMap m_idToObjectIndex; }; @@ -435,15 +449,12 @@ QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int com QQmlError error; const auto &component = *m_compiler->objectAt(componentIndex); - const auto result = resolveAliasesInObject(component, objectIndex, &error); + const auto result + = resolveAliasesInObject(component, objectIndex, &aliasCacheCreator, &error); if (error.isValid()) return error; if (result == AllAliasesResolved) { - QQmlError error = aliasCacheCreator.appendAliasesToPropertyCache( - component, objectIndex); - if (error.isValid()) - return error; atLeastOneAliasResolved = true; } else if (result == SomeAliasesResolved) { atLeastOneAliasResolved = true; @@ -458,7 +469,7 @@ QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int com if (!atLeastOneAliasResolved && !m_objectsWithAliases.isEmpty()) { const CompiledObject *obj = m_compiler->objectAt(m_objectsWithAliases.first()); for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { - if (!alias->hasFlag(QV4::CompiledData::Alias::Resolved)) + if (!resolvedAliases.contains(alias)) return error(alias->location, QQmlComponentAndAliasResolverBase::tr("Circular alias reference detected")); } } diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index 1e8a5f855a..90ee916fe1 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -1842,7 +1842,6 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * const auto originalAlias = alias; while (alias->isAliasToLocalAlias()) alias = _compiledObject->aliasesBegin() + alias->localAliasIndex; - Q_ASSERT(alias->hasFlag(QV4::CompiledData::Alias::Resolved)); if (!context->isIdValueSet(0)) // TODO: Do we really want 0 here? continue; QObject *target = context->idValue(alias->targetObjectId()); @@ -1851,7 +1850,14 @@ bool QQmlObjectCreator::populateInstance(int index, QObject *instance, QObject * QQmlData *targetDData = QQmlData::get(target, /*create*/false); if (targetDData == nullptr || targetDData->propertyCache.isNull()) continue; - int coreIndex = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + + const QQmlPropertyData *aliasProperty = + _propertyCache->property(_vmeMetaObject->aliasOffset() + aliasIndex); + if (!aliasProperty) + continue; + const int targetPropertyIndex = aliasProperty->aliasTarget(); + int coreIndex = QQmlPropertyIndex::fromEncoded(targetPropertyIndex).coreIndex(); + const QQmlPropertyData *const targetProperty = targetDData->propertyCache->property(coreIndex); if (!targetProperty) continue; diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index e56ead94f9..59a26bb630 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1153,10 +1153,22 @@ QVariant QQmlPropertyPrivate::readValueProperty() } return QVariant(); } else if (core.isQList()) { + auto coreMetaType = core.propType(); - QQmlListProperty<QObject> prop; - core.readProperty(object, &prop); - return QVariant::fromValue(QQmlListReferencePrivate::init(prop, core.propType())); + // IsQmlList is set for QQmlListPropery and list<ObjectType> + if (coreMetaType.flags() & QMetaType::IsQmlList) { + QQmlListProperty<QObject> prop; + core.readProperty(object, &prop); + return QVariant::fromValue(QQmlListReferencePrivate::init(prop, coreMetaType)); + } else { + // but not for lists of value types + QVariant result(coreMetaType); + // TODO: ideally, we would not default construct and copy assign, + // but do a single copy-construct; we don't have API for that, though + coreMetaType.construct(result.data()); + core.readProperty(object, result.data()); + return result; + } } else if (core.isQObject()) { diff --git a/src/qml/qml/qqmlpropertycache.cpp b/src/qml/qml/qqmlpropertycache.cpp index e33381c84c..31f9af3375 100644 --- a/src/qml/qml/qqmlpropertycache.cpp +++ b/src/qml/qml/qqmlpropertycache.cpp @@ -221,18 +221,23 @@ void QQmlPropertyCache::appendProperty(const QString &name, QQmlPropertyData::Fl data.setFlags(flags); data.setTypeVersion(version); - QQmlPropertyData *old = findNamedProperty(name); - const OverrideResult overrideResult = handleOverride(name, &data, old); - if (overrideResult == InvalidOverride) { - // Insert the overridden member once more, to keep the counts in sync - propertyIndexCache.append(*old); - return; - } + doAppendPropertyData(name, std::move(data)); +} - int index = propertyIndexCache.size(); - propertyIndexCache.append(data); +void QQmlPropertyCache::appendAlias( + const QString &name, QQmlPropertyData::Flags flags, int coreIndex, QMetaType propType, + QTypeRevision version, int notifyIndex, int encodedTargetIndex) +{ + QQmlPropertyData data; + data.setPropType(propType); + data.setCoreIndex(coreIndex); + data.setNotifyIndex(notifyIndex); + flags.setIsAlias(true); + data.setFlags(flags); + data.setAliasTarget(encodedTargetIndex); + data.setTypeVersion(version); - setNamedProperty(name, index + propertyOffset(), propertyIndexCache.data() + index); + doAppendPropertyData(name, std::move(data)); } void QQmlPropertyCache::appendSignal(const QString &name, QQmlPropertyData::Flags flags, @@ -569,7 +574,7 @@ void QQmlPropertyCache::append(const QMetaObject *metaObject, } // otherwise always dispatch over a 'normal' meta-call so the QQmlValueType can intercept - if (!isGadget) + if (!isGadget && !data->isAlias()) data->trySetStaticMetaCallFunction(metaObject->d.static_metacall, ii - propOffset); } } @@ -922,15 +927,6 @@ const QQmlPropertyData *QQmlPropertyCache::property( return qQmlPropertyCacheProperty<const QLatin1String &>(obj, name, context, local); } -// this function is copied from qmetaobject.cpp -static inline const QByteArray stringData(const QMetaObject *mo, int index) -{ - uint offset = mo->d.stringdata[2*index]; - uint length = mo->d.stringdata[2*index + 1]; - const char *string = reinterpret_cast<const char *>(mo->d.stringdata) + offset; - return QByteArray::fromRawData(string, length); -} - const char *QQmlPropertyCache::className() const { if (const QMetaObject *mo = _metaObject.metaObject()) @@ -1073,209 +1069,7 @@ void QQmlPropertyCache::toMetaObjectBuilder(QMetaObjectBuilder &builder) const builder.addClassInfo("QML.ListPropertyAssignBehavior", _listPropertyAssignBehavior); } -namespace { -template <typename StringVisitor, typename TypeInfoVisitor> -int visitMethods(const QMetaObject &mo, int methodOffset, int methodCount, - StringVisitor visitString, TypeInfoVisitor visitTypeInfo) -{ - int fieldsForParameterData = 0; - - bool hasOldStyleRevisionedMethods = false; - - for (int i = 0; i < methodCount; ++i) { - const int handle = methodOffset + i * QMetaObjectPrivate::IntsPerMethod; - - const uint flags = mo.d.data[handle + 4]; - if (flags & MethodRevisioned) { - if (mo.d.data[0] < 13) - hasOldStyleRevisionedMethods = true; - else - fieldsForParameterData += 1; // revision - } - - visitString(mo.d.data[handle + 0]); // name - visitString(mo.d.data[handle + 3]); // tag - - const int argc = mo.d.data[handle + 1]; - const int paramIndex = mo.d.data[handle + 2]; - - fieldsForParameterData += argc * 2; // type and name - fieldsForParameterData += 1; // + return type - - // return type + args - for (int i = 0; i < 1 + argc; ++i) { - // type name (maybe) - visitTypeInfo(mo.d.data[paramIndex + i]); - - // parameter name - if (i > 0) - visitString(mo.d.data[paramIndex + argc + i]); - } - } - - int fieldsForRevisions = 0; - if (hasOldStyleRevisionedMethods) - fieldsForRevisions = methodCount; - - return methodCount * QMetaObjectPrivate::IntsPerMethod - + fieldsForRevisions + fieldsForParameterData; -} - -template <typename StringVisitor, typename TypeInfoVisitor> -int visitProperties(const QMetaObject &mo, StringVisitor visitString, TypeInfoVisitor visitTypeInfo) -{ - const QMetaObjectPrivate *const priv = reinterpret_cast<const QMetaObjectPrivate*>(mo.d.data); - - for (int i = 0; i < priv->propertyCount; ++i) { - const int handle = priv->propertyData + i * QMetaObjectPrivate::IntsPerProperty; - - visitString(mo.d.data[handle]); // name - visitTypeInfo(mo.d.data[handle + 1]); - } - - return priv->propertyCount * QMetaObjectPrivate::IntsPerProperty; -} - -template <typename StringVisitor> -int visitClassInfo(const QMetaObject &mo, StringVisitor visitString) -{ - const QMetaObjectPrivate *const priv = reinterpret_cast<const QMetaObjectPrivate*>(mo.d.data); - const int intsPerClassInfo = 2; - - for (int i = 0; i < priv->classInfoCount; ++i) { - const int handle = priv->classInfoData + i * intsPerClassInfo; - - visitString(mo.d.data[handle]); // key - visitString(mo.d.data[handle + 1]); // value - } - - return priv->classInfoCount * intsPerClassInfo; -} - -template <typename StringVisitor> -int visitEnumerations(const QMetaObject &mo, StringVisitor visitString) -{ - const QMetaObjectPrivate *const priv = reinterpret_cast<const QMetaObjectPrivate*>(mo.d.data); - - int fieldCount = priv->enumeratorCount * QMetaObjectPrivate::IntsPerEnum; - - for (int i = 0; i < priv->enumeratorCount; ++i) { - const uint *enumeratorData = mo.d.data + priv->enumeratorData + i * QMetaObjectPrivate::IntsPerEnum; - - const uint keyCount = enumeratorData[3]; - fieldCount += keyCount * 2; - - visitString(enumeratorData[0]); // name - visitString(enumeratorData[1]); // enum name - - const uint keyOffset = enumeratorData[4]; - - for (uint j = 0; j < keyCount; ++j) { - visitString(mo.d.data[keyOffset + 2 * j]); - } - } - - return fieldCount; -} - -template <typename StringVisitor> -int countMetaObjectFields(const QMetaObject &mo, StringVisitor stringVisitor) -{ - const QMetaObjectPrivate *const priv = reinterpret_cast<const QMetaObjectPrivate*>(mo.d.data); - - const auto typeInfoVisitor = [&stringVisitor](uint typeInfo) { - if (typeInfo & IsUnresolvedType) - stringVisitor(typeInfo & TypeNameIndexMask); - }; - - int fieldCount = MetaObjectPrivateFieldCount; - - fieldCount += visitMethods(mo, priv->methodData, priv->methodCount, stringVisitor, - typeInfoVisitor); - fieldCount += visitMethods(mo, priv->constructorData, priv->constructorCount, stringVisitor, - typeInfoVisitor); - - fieldCount += visitProperties(mo, stringVisitor, typeInfoVisitor); - fieldCount += visitClassInfo(mo, stringVisitor); - fieldCount += visitEnumerations(mo, stringVisitor); - - return fieldCount; -} - -} // anonymous namespace - -static_assert(QMetaObjectPrivate::OutputRevision == 13, "Check and adjust determineMetaObjectSizes"); - -bool QQmlPropertyCache::determineMetaObjectSizes(const QMetaObject &mo, int *fieldCount, - int *stringCount) -{ - const QMetaObjectPrivate *priv = reinterpret_cast<const QMetaObjectPrivate*>(mo.d.data); - if (priv->revision != QMetaObjectPrivate::OutputRevision) - return false; - - uint highestStringIndex = 0; - const auto stringIndexVisitor = [&highestStringIndex](uint index) { - highestStringIndex = qMax(highestStringIndex, index); - }; - - *fieldCount = countMetaObjectFields(mo, stringIndexVisitor); - *stringCount = highestStringIndex + 1; - - return true; -} - -bool QQmlPropertyCache::addToHash(QCryptographicHash &hash, const QMetaObject &mo) -{ - int fieldCount = 0; - int stringCount = 0; - if (!determineMetaObjectSizes(mo, &fieldCount, &stringCount)) { - return false; - } - hash.addData({reinterpret_cast<const char *>(mo.d.data), qsizetype(fieldCount * sizeof(uint))}); - for (int i = 0; i < stringCount; ++i) { - hash.addData(stringData(&mo, i)); - } - - return true; -} - -QByteArray QQmlPropertyCache::checksum(QHash<quintptr, QByteArray> *checksums, bool *ok) const -{ - auto it = checksums->constFind(quintptr(this)); - if (it != checksums->constEnd()) { - *ok = true; - return *it; - } - - // Generate a checksum on the meta-object data only on C++ types. - if (_metaObject.isShared()) { - *ok = false; - return QByteArray(); - } - - QCryptographicHash hash(QCryptographicHash::Md5); - - if (_parent) { - hash.addData(_parent->checksum(checksums, ok)); - if (!*ok) - return QByteArray(); - } - - if (!addToHash(hash, *_metaObject.metaObject())) { - *ok = false; - return QByteArray(); - } - - const QByteArray result = hash.result(); - if (result.isEmpty()) { - *ok = false; - } else { - *ok = true; - checksums->insert(quintptr(this), result); - } - return result; -} /*! \internal \a index MUST be in the signal index range (see QObjectPrivate::signalIndex()). diff --git a/src/qml/qml/qqmlpropertycache_p.h b/src/qml/qml/qqmlpropertycache_p.h index 24d00c030f..5b75bf48f3 100644 --- a/src/qml/qml/qqmlpropertycache_p.h +++ b/src/qml/qml/qqmlpropertycache_p.h @@ -153,6 +153,9 @@ public: int propertyCount, int methodCount, int signalCount, int enumCount) const; void appendProperty(const QString &, QQmlPropertyData::Flags flags, int coreIndex, QMetaType propType, QTypeRevision revision, int notifyIndex); + void appendAlias(const QString &, QQmlPropertyData::Flags flags, int coreIndex, + QMetaType propType, QTypeRevision version, int notifyIndex, + int encodedTargetIndex); void appendSignal(const QString &, QQmlPropertyData::Flags, int coreIndex, const QMetaType *types = nullptr, const QList<QByteArray> &names = QList<QByteArray>()); @@ -224,11 +227,6 @@ public: inline bool callJSFactoryMethod(QObject *object, void **args) const; - static bool determineMetaObjectSizes(const QMetaObject &mo, int *fieldCount, int *stringCount); - static bool addToHash(QCryptographicHash &hash, const QMetaObject &mo); - - QByteArray checksum(QHash<quintptr, QByteArray> *checksums, bool *ok) const; - QTypeRevision allowedRevision(int index) const { return allowedRevisionCache[index]; } void setAllowedRevision(int index, QTypeRevision allowed) { allowedRevisionCache[index] = allowed; } @@ -296,6 +294,22 @@ private: return handleOverride(name, data, findNamedProperty(name)); } + void doAppendPropertyData(const QString &name, QQmlPropertyData &&data) + { + QQmlPropertyData *old = findNamedProperty(name); + const OverrideResult overrideResult = handleOverride(name, &data, old); + if (overrideResult == InvalidOverride) { + // Insert the overridden member once more, to keep the counts in sync + propertyIndexCache.append(*old); + return; + } + + const int index = propertyIndexCache.size(); + propertyIndexCache.append(std::move(data)); + + setNamedProperty(name, index + propertyOffset(), propertyIndexCache.data() + index); + } + int propertyIndexCacheStart = 0; // placed here to avoid gap between QQmlRefCount and _parent QQmlPropertyCache::ConstPtr _parent; diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index d240cc179b..2d60504d8d 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -873,12 +873,15 @@ public: QQmlPropertyCacheAliasCreator( QQmlPropertyCacheVector *propertyCaches, const ObjectContainer *objectContainer); - QQmlError appendAliasesToPropertyCache(const CompiledObject &component, int objectIndex); + QQmlError appendAliasToPropertyCache( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, int objectIndex, + int aliasIndex, int encodedMetaPropertyIndex); private: QQmlError propertyDataForAlias( const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type, - QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags); + QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, + int targetPropertyIndex); QQmlPropertyCacheVector *propertyCaches; const ObjectContainer *objectContainer; @@ -895,7 +898,7 @@ inline QQmlPropertyCacheAliasCreator<ObjectContainer>::QQmlPropertyCacheAliasCre template <typename ObjectContainer> inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataForAlias( const CompiledObject &component, const QV4::CompiledData::Alias &alias, QMetaType *type, - QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags) + QTypeRevision *version, QQmlPropertyData::Flags *propertyFlags, int targetPropertyIndex) { *type = QMetaType(); bool writable = false; @@ -929,14 +932,15 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor lastAlias = targetAlias; } while (lastAlias->isAliasToLocalAlias()); - return propertyDataForAlias(component, *lastAlias, type, version, propertyFlags); + return propertyDataForAlias( + component, *lastAlias, type, version, propertyFlags, targetPropertyIndex); } const int targetObjectIndex = objectForId(objectContainer, component, alias.targetObjectId()); Q_ASSERT(targetObjectIndex >= 0); const CompiledObject &targetObject = *objectContainer->objectAt(targetObjectIndex); - if (alias.encodedMetaPropertyIndex == -1) { + if (targetPropertyIndex == -1) { Q_ASSERT(alias.hasFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject)); auto *typeRef = objectContainer->resolvedType(targetObject.inheritedTypeNameIndex); if (!typeRef) { @@ -962,9 +966,9 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor propertyFlags->setType(QQmlPropertyData::Flags::QObjectDerivedType); } else { - int coreIndex = QQmlPropertyIndex::fromEncoded(alias.encodedMetaPropertyIndex).coreIndex(); - int valueTypeIndex = QQmlPropertyIndex::fromEncoded( - alias.encodedMetaPropertyIndex).valueTypeIndex(); + int coreIndex = QQmlPropertyIndex::fromEncoded(targetPropertyIndex).coreIndex(); + int valueTypeIndex + = QQmlPropertyIndex::fromEncoded(targetPropertyIndex).valueTypeIndex(); QQmlPropertyCache::ConstPtr targetCache = propertyCaches->at(targetObjectIndex); Q_ASSERT(targetCache); @@ -1040,41 +1044,36 @@ inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::propertyDataFor } template <typename ObjectContainer> -inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasesToPropertyCache( - const CompiledObject &component, int objectIndex) +inline QQmlError QQmlPropertyCacheAliasCreator<ObjectContainer>::appendAliasToPropertyCache( + const CompiledObject &component, const QV4::CompiledData::Alias &alias, int objectIndex, + int aliasIndex, int encodedMetaPropertyIndex) { const CompiledObject &object = *objectContainer->objectAt(objectIndex); - if (!object.aliasCount()) - return QQmlError(); - QQmlPropertyCache::Ptr propertyCache = propertyCaches->ownAt(objectIndex); - Q_ASSERT(propertyCache); - - int effectiveSignalIndex = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.size(); - int effectivePropertyIndex = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.size(); + Q_ASSERT(object.aliasCount() > aliasIndex); + QMetaType type; + QTypeRevision version = QTypeRevision::zero(); + QQmlPropertyData::Flags propertyFlags; + QQmlError error = propertyDataForAlias( + component, alias, &type, &version, &propertyFlags, encodedMetaPropertyIndex); + if (error.isValid()) + return error; - int aliasIndex = 0; - auto alias = object.aliasesBegin(); - auto end = object.aliasesEnd(); - for ( ; alias != end; ++alias, ++aliasIndex) { - Q_ASSERT(alias->hasFlag(QV4::CompiledData::Alias::Resolved)); - - QMetaType type; - QTypeRevision version = QTypeRevision::zero(); - QQmlPropertyData::Flags propertyFlags; - QQmlError error = propertyDataForAlias(component, *alias, &type, &version, &propertyFlags); - if (error.isValid()) - return error; + const QString propertyName = objectContainer->stringAt(alias.nameIndex()); - const QString propertyName = objectContainer->stringAt(alias->nameIndex()); + const QQmlPropertyCache::Ptr propertyCache = propertyCaches->ownAt(objectIndex); + Q_ASSERT(propertyCache); - if (object.hasAliasAsDefaultProperty() && aliasIndex == object.indexOfDefaultPropertyOrAlias) - propertyCache->_defaultPropertyName = propertyName; + const int effectiveSignalIndex + = propertyCache->signalHandlerIndexCacheStart + propertyCache->propertyIndexCache.size(); + const int effectivePropertyIndex + = propertyCache->propertyIndexCacheStart + propertyCache->propertyIndexCache.size(); - propertyCache->appendProperty(propertyName, propertyFlags, effectivePropertyIndex++, - type, version, effectiveSignalIndex++); - } + if (object.hasAliasAsDefaultProperty() && aliasIndex == object.indexOfDefaultPropertyOrAlias) + propertyCache->_defaultPropertyName = propertyName; + propertyCache->appendAlias(propertyName, propertyFlags, effectivePropertyIndex, + type, version, effectiveSignalIndex, encodedMetaPropertyIndex); return QQmlError(); } diff --git a/src/qml/qml/qqmlpropertydata_p.h b/src/qml/qml/qqmlpropertydata_p.h index 4093617364..d19937d672 100644 --- a/src/qml/qml/qqmlpropertydata_p.h +++ b/src/qml/qml/qqmlpropertydata_p.h @@ -209,7 +209,6 @@ public: bool isFinal() const { return !isFunction() && m_flags.isFinalORisV4Function; } bool isOverridden() const { return m_flags.isOverridden; } bool isRequired() const { return !isFunction() && m_flags.isRequiredORisCloned; } - bool hasStaticMetaCallFunction() const { return staticMetaCallFunction() != nullptr; } bool isFunction() const { return m_flags.type == Flags::FunctionType; } bool isQObject() const { return m_flags.type == Flags::QObjectDerivedType; } bool isEnum() const { return m_flags.type == Flags::EnumType; } @@ -224,6 +223,11 @@ public: bool isSignalHandler() const { return m_flags.isSignalHandler; } bool hasMetaObject() const { return m_flags.hasMetaObject; } + bool hasStaticMetaCallFunction() const + { + return !isAlias() && staticMetaCallFunction() != nullptr; + } + // TODO: Remove this once we can. Signals should not be overridable. bool isOverridableSignal() const { return m_flags.isOverridableSignal; } @@ -269,6 +273,17 @@ public: m_coreIndex = qint16(idx); } + int aliasTarget() const + { + Q_ASSERT(isAlias()); + return m_encodedAliasTargetIndex; + } + void setAliasTarget(int target) + { + Q_ASSERT(isAlias()); + m_encodedAliasTargetIndex = target; + } + QTypeRevision revision() const { return m_revision; } void setRevision(QTypeRevision revision) { m_revision = revision; } @@ -332,10 +347,15 @@ public: m_metaObjectOffset = qint16(off); } - StaticMetaCallFunction staticMetaCallFunction() const { Q_ASSERT(!isFunction()); return m_staticMetaCallFunction; } + StaticMetaCallFunction staticMetaCallFunction() const + { + Q_ASSERT(!isFunction() && !isAlias()); + return m_staticMetaCallFunction; + } + void trySetStaticMetaCallFunction(StaticMetaCallFunction f, unsigned relativePropertyIndex) { - Q_ASSERT(!isFunction()); + Q_ASSERT(!isFunction() && !isAlias()); if (relativePropertyIndex < (1 << Flags::BitsLeftInFlags) - 1) { m_flags.otherBits = relativePropertyIndex; m_staticMetaCallFunction = f; @@ -452,9 +472,17 @@ private: QMetaType m_propType = {}; union { + // only for methods (if stored in property cache) QQmlPropertyCacheMethodArguments *m_arguments = nullptr; + + // only for C++-declared properties StaticMetaCallFunction m_staticMetaCallFunction; + + // only for methods (if stored in lookups) const QMetaObject *m_metaObject; + + // only for aliases + int m_encodedAliasTargetIndex; }; }; diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index 842b63b12d..4f091eafe8 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -26,11 +26,9 @@ Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler"); QQmlTypeCompiler::QQmlTypeCompiler( QQmlTypeLoader *typeLoader, QQmlTypeData *typeData, QmlIR::Document *parsedQML, - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, - const QV4::CompiledData::DependentTypesHasher &dependencyHasher) + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache) : resolvedTypes(resolvedTypeCache) , loader(typeLoader) - , dependencyHasher(dependencyHasher) , document(parsedQML) , typeData(typeData) { @@ -138,7 +136,7 @@ QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeCompiler::compile() // Generate QML compiled type data structures QmlIR::QmlUnitGenerator qmlGenerator; - qmlGenerator.generate(*document, dependencyHasher); + qmlGenerator.generate(*document); if (!errors.isEmpty()) return nullptr; @@ -831,22 +829,30 @@ void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupPro template<> typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( - const CompiledObject &component, int objectIndex, QQmlError *error) + const CompiledObject &component, int objectIndex, + QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> *aliasCacheCreator, QQmlError *error) { + // TODO: This method should not modify the aliases themselves. Rather, all information + // needed for handling them later should be stored in the property cache. + // Some of the information calculated here could be calculated already at compile time. + // See QTBUG-136572. + Q_UNUSED(component); const QmlIR::Object * const obj = m_compiler->objectAt(objectIndex); if (!obj->aliasCount()) return AllAliasesResolved; - int numResolvedAliases = 0; - bool seenUnresolvedAlias = false; + int aliasIndex = 0; + int numSkippedAliases = 0; for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next) { - if (alias->hasFlag(QV4::CompiledData::Alias::Resolved)) + if (resolvedAliases.contains(alias)) { + ++aliasIndex; + ++numSkippedAliases; continue; + } - seenUnresolvedAlias = true; const int idIndex = alias->idIndex(); const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1); @@ -906,14 +912,19 @@ QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( if (targetObjectIndex == objectIndex) { alias->localAliasIndex = localAliasIndex; alias->setIsAliasToLocalAlias(true); - alias->setFlag(QV4::CompiledData::Alias::Resolved); - ++numResolvedAliases; + if (!appendAliasToPropertyCache( + &component, alias, objectIndex, aliasIndex++, -1, + aliasCacheCreator, error)) { + break; + } continue; } // restore alias->setIdIndex(idIndex); // Try again later and resolve the target alias first. + ++numSkippedAliases; + ++aliasIndex; break; } } @@ -972,13 +983,18 @@ QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject( } } - alias->encodedMetaPropertyIndex = propIdx.toEncoded(); - alias->setFlag(QV4::CompiledData::Alias::Resolved); - numResolvedAliases++; + if (!appendAliasToPropertyCache( + &component, alias, objectIndex, aliasIndex++, propIdx.toEncoded(), + aliasCacheCreator, error)) { + break; + } } - if (numResolvedAliases == 0) - return seenUnresolvedAlias ? NoAliasResolved : AllAliasesResolved; + if (numSkippedAliases == aliasIndex) + return NoAliasResolved; + + if (aliasIndex == obj->aliasCount()) + return AllAliasesResolved; return SomeAliasesResolved; } diff --git a/src/qml/qml/qqmltypecompiler_p.h b/src/qml/qml/qqmltypecompiler_p.h index ecf449f549..1c14980988 100644 --- a/src/qml/qml/qqmltypecompiler_p.h +++ b/src/qml/qml/qqmltypecompiler_p.h @@ -46,8 +46,7 @@ public: QQmlTypeCompiler(QQmlTypeLoader *typeLoader, QQmlTypeData *typeData, QmlIR::Document *document, - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, - const QV4::CompiledData::DependentTypesHasher &dependencyHasher); + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache); // --- interface used by QQmlPropertyCacheCreator typedef QmlIR::Object CompiledObject; @@ -121,7 +120,6 @@ public: private: QList<QQmlError> errors; QQmlTypeLoader *loader; - const QV4::CompiledData::DependentTypesHasher &dependencyHasher; QmlIR::Document *document; // index is string index of type name (use obj->inheritedTypeNameIndex) QHash<int, QQmlCustomParser*> customParsers; diff --git a/src/qml/qml/qqmltypedata.cpp b/src/qml/qml/qqmltypedata.cpp index 86b6de63b3..b07ed3b753 100644 --- a/src/qml/qml/qqmltypedata.cpp +++ b/src/qml/qml/qqmltypedata.cpp @@ -219,28 +219,104 @@ void QQmlComponentAndAliasResolver<QV4::CompiledData::CompilationUnit>::resolveG template<> typename QQmlComponentAndAliasResolver<QV4::CompiledData::CompilationUnit>::AliasResolutionResult QQmlComponentAndAliasResolver<QV4::CompiledData::CompilationUnit>::resolveAliasesInObject( - const CompiledObject &component, int objectIndex, QQmlError *error) + const CompiledObject &component, int objectIndex, + QQmlPropertyCacheAliasCreator<QV4::CompiledData::CompilationUnit> *aliasCacheCreator, + QQmlError *error) { const CompiledObject *obj = m_compiler->objectAt(objectIndex); + int aliasIndex = 0; + const auto doAppendAlias = [&](const QV4::CompiledData::Alias *alias, int encodedIndex) { + return appendAliasToPropertyCache( + &component, alias, objectIndex, aliasIndex++, encodedIndex, aliasCacheCreator, + error); + }; + for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) { - if (!alias->hasFlag(QV4::CompiledData::Alias::Resolved)) { - *error = qQmlCompileError(alias->referenceLocation, - QQmlComponentAndAliasResolverBase::tr("Unresolved alias found")); - return NoAliasResolved; + if (resolvedAliases.contains(alias)) { + ++aliasIndex; + continue; } - if (alias->isAliasToLocalAlias() || alias->encodedMetaPropertyIndex == -1) - continue; + if (alias->isAliasToLocalAlias()) { + if (doAppendAlias(alias, -1)) + continue; + return SomeAliasesResolved; + } const int targetObjectIndex = objectForId(m_compiler, component, alias->targetObjectId()); - const int coreIndex - = QQmlPropertyIndex::fromEncoded(alias->encodedMetaPropertyIndex).coreIndex(); + const QV4::CompiledData::Object *targetObject = m_compiler->objectAt(targetObjectIndex); + + QStringView property; + QStringView subProperty; + + const QString aliasPropertyValue = stringAt(alias->propertyNameIndex); + const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.')); + if (propertySeparator != -1) { + property = QStringView{aliasPropertyValue}.left(propertySeparator); + subProperty = QStringView{aliasPropertyValue}.mid(propertySeparator + 1); + } else { + property = QStringView(aliasPropertyValue); + } + if (property.isEmpty()) { + if (doAppendAlias(alias, -1)) + continue; + return SomeAliasesResolved; + } + + Q_ASSERT(!property.isEmpty()); QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex); Q_ASSERT(targetCache); - if (!targetCache->property(coreIndex)) + const QQmlPropertyResolver resolver(targetCache); + const QQmlPropertyData *targetProperty = resolver.property(property.toString()); + if (!targetProperty) + return SomeAliasesResolved; + + const int coreIndex = targetProperty->coreIndex(); + if (subProperty.isEmpty()) { + if (doAppendAlias(alias, QQmlPropertyIndex(coreIndex).toEncoded())) + continue; + return SomeAliasesResolved; + } + + if (const QMetaObject *valueTypeMetaObject + = QQmlMetaType::metaObjectForValueType(targetProperty->propType())) { + const int valueTypeIndex = valueTypeMetaObject->indexOfProperty( + subProperty.toString().toUtf8().constData()); + if (valueTypeIndex == -1) + return SomeAliasesResolved; + + if (doAppendAlias(alias, QQmlPropertyIndex(coreIndex, valueTypeIndex).toEncoded())) + continue; + + return SomeAliasesResolved; + } + + Q_ASSERT(subProperty.at(0).isLower()); + + bool isDeepAlias = false; + for (auto it = targetObject->bindingsBegin(); it != targetObject->bindingsEnd(); ++it) { + if (m_compiler->stringAt(it->propertyNameIndex) != property) + continue; + + const QQmlPropertyResolver resolver + = QQmlPropertyResolver(m_propertyCaches->at(it->value.objectIndex)); + const QQmlPropertyData *actualProperty = resolver.property(subProperty.toString()); + if (!actualProperty) + continue; + + if (doAppendAlias( + alias, QQmlPropertyIndex(coreIndex, actualProperty->coreIndex()).toEncoded())) { + isDeepAlias = true; + break; + } + + return SomeAliasesResolved; + } + + if (!isDeepAlias) return SomeAliasesResolved; } @@ -465,27 +541,16 @@ void QQmlTypeData::done() } } - const auto dependencyHasher = [&resolvedTypeCache, this]() { - return typeLoader()->hashDependencies(&resolvedTypeCache, m_compositeSingletons); - }; - // verify if any dependencies changed if we're using a cache if (m_document.isNull() && verifyCaches) { const QQmlError error = createTypeAndPropertyCaches(typeNameCache, resolvedTypeCache); - if (!error.isValid() && m_compiledData->verifyChecksum(dependencyHasher)) { + if (!error.isValid()) { setCompileUnit(m_compiledData); } else { - - if (error.isValid()) { - qCDebug(DBG_DISK_CACHE) - << "Failed to create property caches for" - << m_compiledData->fileName() - << "because" << error.description(); - } else { - qCDebug(DBG_DISK_CACHE) - << "Checksum mismatch for cached version of" - << m_compiledData->fileName(); - } + qCDebug(DBG_DISK_CACHE) + << "Failed to create property caches for" + << m_compiledData->fileName() + << "because" << error.description(); if (!loadFromSource()) return; @@ -515,7 +580,7 @@ void QQmlTypeData::done() if (!m_document.isNull()) { Q_ASSERT(verifyCaches); // Compile component - compile(typeNameCache, &resolvedTypeCache, dependencyHasher); + compile(typeNameCache, &resolvedTypeCache); if (isError()) return; else @@ -846,8 +911,7 @@ QString QQmlTypeData::stringAt(int index) const } void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, - const QV4::CompiledData::DependentTypesHasher &dependencyHasher) + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache) { assertTypeLoaderThread(); @@ -859,8 +923,7 @@ void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCach && (m_document->javaScriptCompilationUnit->unitData()->flags & QV4::CompiledData::Unit::PendingTypeCompilation); - QQmlTypeCompiler compiler( - typeLoader(), this, m_document.data(), resolvedTypeCache, dependencyHasher); + QQmlTypeCompiler compiler(typeLoader(), this, m_document.data(), resolvedTypeCache); auto compilationUnit = compiler.compile(); if (!compilationUnit) { qDeleteAll(*resolvedTypeCache); diff --git a/src/qml/qml/qqmltypedata_p.h b/src/qml/qml/qqmltypedata_p.h index ba04ee1ad2..4cd9ff64ed 100644 --- a/src/qml/qml/qqmltypedata_p.h +++ b/src/qml/qml/qqmltypedata_p.h @@ -97,8 +97,7 @@ private: QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache ) const; void compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, - const QV4::CompiledData::DependentTypesHasher &dependencyHasher); + QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache); QQmlError createTypeAndPropertyCaches( const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache); diff --git a/src/qml/qml/qqmltypeloader.cpp b/src/qml/qml/qqmltypeloader.cpp index 7420a1654c..3296b1ec1a 100644 --- a/src/qml/qml/qqmltypeloader.cpp +++ b/src/qml/qml/qqmltypeloader.cpp @@ -1732,7 +1732,6 @@ void QQmlTypeLoader::clearCache() QQmlTypeLoaderThreadDataPtr threadData(&m_data); qDeleteAll(threadData->importQmlDirCache); - threadData->checksumCache.clear(); threadData->importQmlDirCache.clear(); QQmlTypeLoaderSharedDataPtr data(&m_data); diff --git a/src/qml/qml/qqmltypeloader_p.h b/src/qml/qml/qqmltypeloader_p.h index 47df026a07..fd8f026b59 100644 --- a/src/qml/qml/qqmltypeloader_p.h +++ b/src/qml/qml/qqmltypeloader_p.h @@ -44,7 +44,6 @@ class Q_QML_EXPORT QQmlTypeLoader { Q_DECLARE_TR_FUNCTIONS(QQmlTypeLoader) public: - using ChecksumCache = QQmlTypeLoaderThreadData::ChecksumCache; enum Mode { PreferSynchronous, Asynchronous, Synchronous }; class Q_QML_EXPORT Blob : public QQmlDataBlob @@ -161,23 +160,6 @@ public: } } - // We can't include QQmlTypeData here. - // Use a template specialized only for QQmlTypeData::TypeReference instead. - template<typename TypeReference> - QByteArray hashDependencies( - QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache, - const QList<TypeReference> &compositeSingletons) - { - QQmlTypeLoaderThreadDataPtr data(&m_data); - - QCryptographicHash hash(QCryptographicHash::Md5); - return (resolvedTypeCache->addToHash(&hash, &data->checksumCache) - && addTypeReferenceChecksumsToHash( - compositeSingletons, &data->checksumCache, &hash)) - ? hash.result() - : QByteArray(); - } - static QUrl normalize(const QUrl &unNormalizedUrl); QQmlRefPointer<QQmlTypeData> getType( @@ -342,26 +324,6 @@ private: void doLoad(const Loader &loader, const QQmlDataBlob::Ptr &blob, Mode mode); void updateTypeCacheTrimThreshold(const QQmlTypeLoaderSharedDataPtr &data); - template<typename TypeReference> - static bool addTypeReferenceChecksumsToHash( - const QList<TypeReference> &typeRefs, - QHash<quintptr, QByteArray> *checksums, QCryptographicHash *hash) - { - for (const auto &typeRef: typeRefs) { - if (typeRef.typeData) { - const auto unit = typeRef.typeData->compilationUnit()->unitData(); - hash->addData({unit->md5Checksum, sizeof(unit->md5Checksum)}); - } else if (const QMetaObject *mo = typeRef.type.metaObject()) { - const auto propertyCache = QQmlMetaType::propertyCache(mo); - bool ok = false; - hash->addData(propertyCache->checksum(checksums, &ok)); - if (!ok) - return false; - } - } - return true; - } - QQmlMetaType::CacheMode aotCacheMode(); QQmlTypeLoaderLockedData m_data; diff --git a/src/qml/qml/qqmltypeloaderdata_p.h b/src/qml/qml/qqmltypeloaderdata_p.h index f9ab346cbb..f9cc20137f 100644 --- a/src/qml/qml/qqmltypeloaderdata_p.h +++ b/src/qml/qml/qqmltypeloaderdata_p.h @@ -58,7 +58,6 @@ class QQmlTypeLoaderThreadData { Q_DISABLE_COPY_MOVE(QQmlTypeLoaderThreadData) public: - using ChecksumCache = QHash<quintptr, QByteArray>; using ImportQmlDirCache = QStringHash<QQmlTypeLoaderQmldirContent *>; struct QmldirInfo { @@ -71,7 +70,6 @@ public: QQmlTypeLoaderThreadData() = default; ImportQmlDirCache importQmlDirCache; - ChecksumCache checksumCache; // Maps from an import to a linked list of qmldir info. // Used in locateLocalQmldir() diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index e4988a8152..1ed98a4f74 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -221,14 +221,19 @@ void QQmlVMEMetaObjectEndpoint::tryConnect() int sigIdx = aliasId + metaObject->propCount(); metaObject->activate(metaObject->object, sigIdx, nullptr); } else if (const QV4::CompiledData::Object *compiledObject = metaObject->findCompiledObject()) { - const QV4::CompiledData::Alias *aliasData = &compiledObject->aliasTable()[aliasId]; - if (!aliasData->isObjectAlias()) { + const QQmlPropertyData *aliasProperty + = metaObject->cache->property(metaObject->aliasOffset() + aliasId); + const int targetPropertyIndex = aliasProperty ? aliasProperty->aliasTarget() : -1; + + if (targetPropertyIndex != -1) { + const QV4::CompiledData::Alias *aliasData = &compiledObject->aliasTable()[aliasId]; + QQmlRefPointer<QQmlContextData> ctxt = metaObject->ctxt; QObject *target = ctxt->idValue(aliasData->targetObjectId()); if (!target) return; - QQmlPropertyIndex encodedIndex = QQmlPropertyIndex::fromEncoded(aliasData->encodedMetaPropertyIndex); + QQmlPropertyIndex encodedIndex = QQmlPropertyIndex::fromEncoded(targetPropertyIndex); int coreIndex = encodedIndex.coreIndex(); int valueTypeIndex = encodedIndex.valueTypeIndex(); const QQmlPropertyData *pd = QQmlData::ensurePropertyCache(target)->property(coreIndex); @@ -1076,7 +1081,10 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * connectAlias(compiledObject, id); - if (aliasData->isObjectAlias()) { + const QQmlPropertyData *aliasProperty = cache->property(aliasOffset() + id); + const int targetPropertyIndex = aliasProperty ? aliasProperty->aliasTarget() : -1; + + if (targetPropertyIndex == -1) { *reinterpret_cast<QObject **>(a[0]) = target; return -1; } @@ -1085,7 +1093,8 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * if (!targetDData) return -1; - QQmlPropertyIndex encodedIndex = QQmlPropertyIndex::fromEncoded(aliasData->encodedMetaPropertyIndex); + QQmlPropertyIndex encodedIndex + = QQmlPropertyIndex::fromEncoded(targetPropertyIndex); int coreIndex = encodedIndex.coreIndex(); const int valueTypePropertyIndex = encodedIndex.valueTypeIndex(); @@ -1433,8 +1442,11 @@ bool QQmlVMEMetaObject::aliasTarget(int index, QObject **target, int *coreIndex, if (!*target) return false; - if (!aliasData->isObjectAlias()) { - QQmlPropertyIndex encodedIndex = QQmlPropertyIndex::fromEncoded(aliasData->encodedMetaPropertyIndex); + const QQmlPropertyData *aliasProperty = cache->property(aliasOffset() + aliasId); + const int targetPropertyIndex = aliasProperty ? aliasProperty->aliasTarget() : -1; + + if (targetPropertyIndex != -1) { + QQmlPropertyIndex encodedIndex = QQmlPropertyIndex::fromEncoded(targetPropertyIndex); *coreIndex = encodedIndex.coreIndex(); *valueTypeIndex = encodedIndex.valueTypeIndex(); } diff --git a/src/qmldom/qqmldomelements.cpp b/src/qmldom/qqmldomelements.cpp index 4b18bce50f..e6bdd47799 100644 --- a/src/qmldom/qqmldomelements.cpp +++ b/src/qmldom/qqmldomelements.cpp @@ -1825,13 +1825,13 @@ QString ScriptExpression::astRelocatableDump() const void ScriptExpression::writeOut(const DomItem &, OutWriter &lw) const { - reformatAst( - lw, m_astComments, - [this](SourceLocation astL) { - SourceLocation l = this->locationToLocal(astL); // use engine->code() instead? - return this->code().mid(l.offset, l.length); - }, - ast()); + reformatAst(lw, this); +} + +QStringView ScriptExpression::loc2Str(SourceLocation astL) const +{ + SourceLocation l = this->locationToLocal(astL); // use engine->code() instead? + return this->code().mid(l.offset, l.length); } SourceLocation ScriptExpression::globalLocation(const DomItem &self) const diff --git a/src/qmldom/qqmldomelements_p.h b/src/qmldom/qqmldomelements_p.h index c667da93a5..c6781c6ab8 100644 --- a/src/qmldom/qqmldomelements_p.h +++ b/src/qmldom/qqmldomelements_p.h @@ -467,6 +467,7 @@ public: QMutexLocker l(mutex()); return m_engine; } + QStringView loc2Str(SourceLocation) const; std::shared_ptr<AstComments> astComments() const { return m_astComments; } void writeOut(const DomItem &self, OutWriter &lw) const override; SourceLocation globalLocation(const DomItem &self) const; diff --git a/src/qmldom/qqmldomfieldfilter.cpp b/src/qmldom/qqmldomfieldfilter.cpp index 50a819414e..36bf2212e0 100644 --- a/src/qmldom/qqmldomfieldfilter.cpp +++ b/src/qmldom/qqmldomfieldfilter.cpp @@ -190,6 +190,7 @@ FieldFilter FieldFilter::compareNoCommentsFilter() { QLatin1String("QmlObject"), QLatin1String("prototypes") }, { QLatin1String(), QLatin1String("code") }, { QLatin1String("ScriptExpression"), QLatin1String("localOffset") }, + { QLatin1String("ScriptExpression"), QLatin1String("astRelocatableDump") }, { QLatin1String("FileLocationsNode"), QLatin1String("parent") }, { QString(), QLatin1String("fileLocationsTree") }, { QString(), QLatin1String("preCode") }, diff --git a/src/qmldom/qqmldomlinewriter.cpp b/src/qmldom/qqmldomlinewriter.cpp index 32488fe6cc..8de38dff55 100644 --- a/src/qmldom/qqmldomlinewriter.cpp +++ b/src/qmldom/qqmldomlinewriter.cpp @@ -46,6 +46,13 @@ LineWriter &LineWriter::ensureSpace(TextAddType t) return *this; } +LineWriter &LineWriter::ensureSemicolon(TextAddType t) +{ + if (!m_currentLine.isEmpty() && m_currentLine.back() != u';') + write(u";", t); + return *this; +} + LineWriter &LineWriter::ensureSpace(QStringView space, TextAddType t) { int tabSize = m_options.formatOptions.tabSize; diff --git a/src/qmldom/qqmldomlinewriter_p.h b/src/qmldom/qqmldomlinewriter_p.h index a18ebc7319..f2b3d36918 100644 --- a/src/qmldom/qqmldomlinewriter_p.h +++ b/src/qmldom/qqmldomlinewriter_p.h @@ -81,6 +81,10 @@ public: Q_ENUM(TrailingSpace) enum class AttributesSequence { Normalize, Preserve }; Q_ENUM(AttributesSequence) + // Always: always add a semicolon at the end of statement + // Essential: adds semicolon only when ASI would not insert for us + enum class SemicolonRule { Always, Essential }; + Q_ENUM(SemicolonRule) int maxLineLength = -1; // -1 means no limit int minContentLength = 10; @@ -97,6 +101,7 @@ public: bool objectsSpacing = false; bool functionsSpacing = false; bool sortImports = false; + SemicolonRule semicolonRule = SemicolonRule::Always; }; class LineWriter; @@ -130,6 +135,7 @@ public: LineWriter &ensureNewline(int nNewlines = 1, TextAddType t = TextAddType::Extra); LineWriter &ensureSpace(TextAddType t = TextAddType::Extra); LineWriter &ensureSpace(QStringView space, TextAddType t = TextAddType::Extra); + LineWriter &ensureSemicolon(TextAddType t = TextAddType::Extra); LineWriter &newline() { diff --git a/src/qmldom/qqmldomreformatter.cpp b/src/qmldom/qqmldomreformatter.cpp index 9576e3c8cb..d58ab39ce5 100644 --- a/src/qmldom/qqmldomreformatter.cpp +++ b/src/qmldom/qqmldomreformatter.cpp @@ -10,9 +10,7 @@ #include <QtQml/private/qqmljslexer_p.h> #include <QString> - #include <algorithm> -#include <limits> QT_BEGIN_NAMESPACE namespace QQmlJS { @@ -46,6 +44,10 @@ void ScriptFormatter::lnAcceptIndented(Node *node) bool ScriptFormatter::acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline) { + if (auto *es = cast<EmptyStatement *>(ast)) { + writeOutSemicolon(es); + return false; + } if (cast<Block *>(ast)) { lw.lineWriter.ensureSpace(); accept(ast); @@ -92,7 +94,7 @@ bool ScriptFormatter::visit(StringLiteral *ast) // correctly handle multiline literals if (ast->literalToken.length == 0) return true; - QStringView str = loc2Str(ast->literalToken); + QStringView str = m_script->loc2Str(ast->literalToken); if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { out(str.mid(0, 1)); lw.indentNextlines = false; @@ -288,7 +290,7 @@ bool ScriptFormatter::visit(TemplateLiteral *ast) { // correctly handle multiline literals if (ast->literalToken.length != 0) { - QStringView str = loc2Str(ast->literalToken); + QStringView str = m_script->loc2Str(ast->literalToken); if (lw.indentNextlines && str.contains(QLatin1Char('\n'))) { out(str.mid(0, 1)); lw.indentNextlines = false; @@ -474,7 +476,7 @@ bool ScriptFormatter::visit(VariableStatement *ast) lw.lineWriter.ensureSpace(); accept(ast->declarations); if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -512,9 +514,9 @@ bool ScriptFormatter::visit(PatternElement *ast) return false; } -bool ScriptFormatter::visit(EmptyStatement *ast) +bool ScriptFormatter::visit(EmptyStatement *) { - out(ast->semicolonToken); + lw.lineWriter.ensureSemicolon(); return false; } @@ -575,14 +577,21 @@ bool ScriptFormatter::visit(ForStatement *ast) out(pe->declarationKindToken); lw.lineWriter.ensureSpace(); } + bool first = true; for (VariableDeclarationList *it = ast->declarations; it; it = it->next) { + if (!std::exchange(first, false)) { + out(","); + lw.lineWriter.ensureSpace(); + } accept(it->declaration); } } - out(";"); // ast->firstSemicolonToken + // We don't use writeOutSemicolon() here because we need a semicolon unconditionally. + // Repeats for the second semicolon token below. + out(u";"); // ast->firstSemicolonToken lw.lineWriter.ensureSpace(); accept(ast->condition); - out(";"); // ast->secondSemicolonToken + out(u";"); // ast->secondSemicolonToken lw.lineWriter.ensureSpace(); accept(ast->expression); out(ast->rparenToken); @@ -617,7 +626,7 @@ bool ScriptFormatter::visit(ContinueStatement *ast) out(ast->identifierToken); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -629,7 +638,7 @@ bool ScriptFormatter::visit(BreakStatement *ast) out(ast->identifierToken); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -642,7 +651,7 @@ bool ScriptFormatter::visit(ReturnStatement *ast) accept(ast->expression); } if (ast->returnToken.length > 0 && addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -667,7 +676,7 @@ bool ScriptFormatter::visit(ThrowStatement *ast) accept(ast->expression); } if (addSemicolons()) - out(";"); + writeOutSemicolon(ast); return false; } @@ -860,7 +869,7 @@ bool ScriptFormatter::visit(StatementList *ast) for (StatementList *it = ast; it; it = it->next) { // ### work around parser bug: skip empty statements with wrong tokens if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) { - if (loc2Str(emptyStatement->semicolonToken) != QLatin1String(";")) + if (m_script->loc2Str(emptyStatement->semicolonToken) != QLatin1String(";")) continue; } @@ -946,7 +955,7 @@ bool ScriptFormatter::visit(Expression *el) bool ScriptFormatter::visit(ExpressionStatement *el) { if (addSemicolons()) - postOps[el->expression].append([this]() { out(";"); }); + postOps[el->expression].append([this, el]() { writeOutSemicolon(el); }); return true; } @@ -1107,16 +1116,16 @@ void ScriptFormatter::endVisit(ComputedPropertyName *) void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) { // add a semicolon at the end of the following expressions - // export * FromClause ; + // export * FromClause // export ExportClause FromClause ; if (ast->fromClause) { - out(";"); + writeOutSemicolon(ast); } // add a semicolon at the end of the following expressions // export ExportClause ; if (ast->exportClause && !ast->fromClause) { - out(";"); + writeOutSemicolon(ast); } // add a semicolon at the end of the following expressions @@ -1125,7 +1134,7 @@ void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) // lookahead ∉ { function, class } if (!(ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration || ast->variableStatementOrDeclaration->kind == Node::Kind_ClassDeclaration)) { - out(";"); + writeOutSemicolon(ast); } // ArrowFunction in QQmlJS::AST is handled with the help of FunctionDeclaration // and not as part of AssignmentExpression (as per ECMA @@ -1133,7 +1142,7 @@ void ScriptFormatter::endVisit(AST::ExportDeclaration *ast) if (ast->variableStatementOrDeclaration->kind == Node::Kind_FunctionDeclaration && static_cast<AST::FunctionDeclaration *>(ast->variableStatementOrDeclaration) ->isArrowFunction) { - out(";"); + writeOutSemicolon(ast); } } } @@ -1154,9 +1163,9 @@ void ScriptFormatter::endVisit(AST::NamedImports *ast) out(ast->rightBraceToken); } -void ScriptFormatter::endVisit(AST::ImportDeclaration *) +void ScriptFormatter::endVisit(AST::ImportDeclaration *id) { - out(";"); + writeOutSemicolon(id); } void ScriptFormatter::throwRecursionDepthError() @@ -1164,14 +1173,84 @@ void ScriptFormatter::throwRecursionDepthError() out("/* ERROR: Hit recursion limit ScriptFormatter::visiting AST, rewrite failed */"); } -void reformatAst(OutWriter &lw, const std::shared_ptr<AstComments> &comments, - const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n) -{ - if (n) { - ScriptFormatter formatter(lw, comments, loc2Str, n); +// This is a set of characters that are not allowed to be at the beginning of a line +// after a semicolon for ASI. +using namespace Qt::StringLiterals; +static constexpr QLatin1StringView restrictedChars = "([/+-"_L1; + +// Given an existing semicolon, can we safely remove it without changing behavior +bool ScriptFormatter::canRemoveSemicolon(AST::Node *node) +{ + const auto canRelyOnASI = [this](Node *node) { + auto nodeLoc = node->lastSourceLocation().offset + 1; + auto code = m_script->engine()->code(); + // Bounds check for nodeLoc + if (qsizetype(nodeLoc) >= code.size()) + return false; + auto startIt = code.begin() + nodeLoc; + auto endIt = std::find_first_of(startIt, code.end(), restrictedChars.begin(), + restrictedChars.end()); + // No restricted character found, then it is safe to remove the semicolon + if (endIt == code.end()) + return true; + + // Check if there is at least one character between nodeLoc and the found character + // that are neither space chars nor semicolons. + bool hasOtherChars = + std::any_of(startIt, endIt, [](QChar ch) { return !(ch.isSpace() || ch == u';'); }); + + if (hasOtherChars) + return true; + + // Check if there is no linebreak between nodeLoc and the found character + return std::none_of(startIt, endIt, [](QChar c) { return c == u'\n'; }); + }; + + // Check if the node is a statement that requires a semicolon to avoid ASI issues + switch (node->kind) { + case AST::Node::Kind_ExpressionStatement: + return canRelyOnASI(cast<ExpressionStatement *>(node)); + case AST::Node::Kind_VariableStatement: + return canRelyOnASI(cast<VariableStatement *>(node)); + case AST::Node::Kind_EmptyStatement: + return false; + case AST::Node::Kind_ContinueStatement: + case AST::Node::Kind_BreakStatement: + case AST::Node::Kind_ReturnStatement: + case AST::Node::Kind_ThrowStatement: + case AST::Node::Kind_ExportDeclaration: + case AST::Node::Kind_ImportDeclaration: + case AST::Node::Kind_FromClause: + case AST::Node::Kind_ExportClause: + default: + return true; } } +OutWriter &ScriptFormatter::writeOutSemicolon(AST::Node *node) +{ + if (!node) + return lw; + switch (lw.lineWriter.options().semicolonRule) { + case LineWriterOptions::SemicolonRule::Essential: + if (!canRemoveSemicolon(node)) + out(u";"); + lw.lineWriter.ensureNewline(); + return lw; + case LineWriterOptions::SemicolonRule::Always: + out(u";"); + return lw; + default: + Q_UNREACHABLE_RETURN(lw); + } +} + +void reformatAst(OutWriter &lw, const QQmlJS::Dom::ScriptExpression *const script) +{ + if (script) + ScriptFormatter formatter(lw, script); +} + } // namespace Dom } // namespace QQmlJS QT_END_NAMESPACE diff --git a/src/qmldom/qqmldomreformatter_p.h b/src/qmldom/qqmldomreformatter_p.h index ae6b27a43e..a13425dec7 100644 --- a/src/qmldom/qqmldomreformatter_p.h +++ b/src/qmldom/qqmldomreformatter_p.h @@ -20,6 +20,7 @@ #include "qqmldomoutwriter_p.h" #include "qqmldom_fwd_p.h" #include "qqmldomcomments_p.h" +#include "qqmldomelements_p.h" #include <QtQml/private/qqmljsast_p.h> @@ -31,11 +32,12 @@ class ScriptFormatter final : protected AST::JSVisitor { public: // TODO QTBUG-121988 - ScriptFormatter(OutWriter &lw, const std::shared_ptr<AstComments> &comments, - const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *node) - : lw(lw), comments(comments), loc2Str(loc2Str) + ScriptFormatter(OutWriter &lw, const ScriptExpression *const script) : lw(lw), m_script(script) { - accept(node); + if (m_script) { + comments = m_script->astComments(); + accept(m_script->ast()); + } } protected: @@ -44,7 +46,7 @@ protected: inline void out(const SourceLocation &loc) { if (loc.length != 0) - out(loc2Str(loc)); + out(m_script->loc2Str(loc)); } enum CommentOption { NoSpace, SpaceBeforePostComment, OnlyComments }; void outWithComments(const SourceLocation &loc, AST::Node *node, CommentOption option = NoSpace) @@ -218,17 +220,16 @@ protected: private: bool addSemicolons() const { return expressionDepth > 0; } - + bool canRemoveSemicolon(AST::Node *node); + OutWriter &writeOutSemicolon(AST::Node *); OutWriter &lw; std::shared_ptr<AstComments> comments; - std::function<QStringView(SourceLocation)> loc2Str; + const ScriptExpression *const m_script = nullptr; // outlives this QHash<AST::Node *, QList<std::function<void()>>> postOps; int expressionDepth = 0; }; -QMLDOM_EXPORT void reformatAst( - OutWriter &lw, const std::shared_ptr<AstComments> &comments, - const std::function<QStringView(SourceLocation)> &loc2Str, AST::Node *n); +QMLDOM_EXPORT void reformatAst(OutWriter &lw, const QQmlJS::Dom::ScriptExpression *const script); } // namespace Dom } // namespace QQmlJS diff --git a/src/qmlformat/qqmlformatoptions.cpp b/src/qmlformat/qqmlformatoptions.cpp index e76fffcb0d..d3b1686061 100644 --- a/src/qmlformat/qqmlformatoptions.cpp +++ b/src/qmlformat/qqmlformatoptions.cpp @@ -180,6 +180,13 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & QStringLiteral("Sort imports alphabetically " "(Warning: this might change semantics if a given " "name identifies types in multiple modules!)."))); + QCommandLineOption semicolonRuleOption( + QStringList() << "semicolon-rule"_L1, + QStringLiteral("Specify the semicolon rule to use (always, essential).\n" + "always: always adds semicolon [default].\n" + "essential: adds only when ASI wouldn't be relied on."), + "rule"_L1, "always"_L1); + parser.addOption(semicolonRuleOption); parser.addPositionalArgument("filenames"_L1, "files to be processed by qmlformat"_L1); @@ -277,6 +284,19 @@ QQmlFormatOptions QQmlFormatOptions::buildCommandLineOptions(const QStringList & options.mark(Settings::NewlineType); options.setNewline(QQmlFormatOptions::parseEndings(parser.value("newline"_L1))); } + + if (parser.isSet(semicolonRuleOption)) { + options.mark(Settings::SemicolonRule); + const auto value = parser.value(semicolonRuleOption); + if (value == "always"_L1) { + options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Always); + } else if (value == "essential"_L1) { + options.setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule::Essential); + } else { + options.addError("Error: Invalid value passed to --semicolon-rule."_L1); + return options; + } + } options.setFiles(files); options.setArguments(parser.positionalArguments()); diff --git a/src/qmlformat/qqmlformatoptions_p.h b/src/qmlformat/qqmlformatoptions_p.h index 63ba509b88..011ad4f9fa 100644 --- a/src/qmlformat/qqmlformatoptions_p.h +++ b/src/qmlformat/qqmlformatoptions_p.h @@ -83,6 +83,16 @@ public: void setMaxColumnWidth(int width) { m_options.maxLineLength = width; } bool isMaxColumnWidthSet() const { return m_options.maxLineLength > 0; } + void setSemicolonRule(QQmlJS::Dom::LineWriterOptions::SemicolonRule rule) + { + m_options.semicolonRule = rule; + } + + QQmlJS::Dom::LineWriterOptions::SemicolonRule semicolonRule() const + { + return m_options.semicolonRule; + } + QQmlJS::Dom::LineWriterOptions optionsForCode(const QString &code) const { QQmlJS::Dom::LineWriterOptions result = m_options; @@ -134,6 +144,7 @@ public: ObjectsSpacing, FunctionsSpacing, SortImports, + SemicolonRule, SettingsCount }; diff --git a/src/qmlformat/qqmlformatsettings.cpp b/src/qmlformat/qqmlformatsettings.cpp index 8ee0f44f77..181a3d9514 100644 --- a/src/qmlformat/qqmlformatsettings.cpp +++ b/src/qmlformat/qqmlformatsettings.cpp @@ -13,4 +13,5 @@ QQmlFormatSettings::QQmlFormatSettings(const QString &toolName) : QQmlToolingSet addOption(s_objectsSpacingSetting, false); addOption(s_functionsSpacingSetting, false); addOption(s_sortImportsSetting, false); + addOption(s_semiColonRuleSetting, QStringLiteral("always")); } diff --git a/src/qmlformat/qqmlformatsettings_p.h b/src/qmlformat/qqmlformatsettings_p.h index d62b733e35..9630439858 100644 --- a/src/qmlformat/qqmlformatsettings_p.h +++ b/src/qmlformat/qqmlformatsettings_p.h @@ -31,6 +31,7 @@ public: static const inline QLatin1StringView s_objectsSpacingSetting = QLatin1String("ObjectsSpacing"); static const inline QLatin1StringView s_functionsSpacingSetting = QLatin1String("FunctionsSpacing"); static const inline QLatin1StringView s_sortImportsSetting = QLatin1String("SortImports"); + static const inline QLatin1StringView s_semiColonRuleSetting = QLatin1String("SemicolonRule"); }; QT_END_NAMESPACE diff --git a/src/qmlls/qqmllsutils.cpp b/src/qmlls/qqmllsutils.cpp index d0c9ac9201..898bfd0334 100644 --- a/src/qmlls/qqmllsutils.cpp +++ b/src/qmlls/qqmllsutils.cpp @@ -1362,15 +1362,16 @@ static std::optional<ExpressionType> resolveFieldMemberExpressionType(const DomI // Enumerations should live under the root element scope of the file that defines the enum, // therefore use the DomItem to find the root element of the qml file instead of directly // using owner->semanticScope. - const auto scope = item.goToFile(owner->semanticScope->filePath()) - .rootQmlObject(GoTo::MostLikely) - .semanticScope(); - if (scope->hasEnumerationKey(name)) { - return ExpressionType{ name, scope, EnumeratorValueIdentifier }; - } - // Or it is a enum name <TypeName>.<EnumName>.<EnumValue> - else if (scope->hasEnumeration(name)) { - return ExpressionType{ name, scope, EnumeratorIdentifier }; + if (const auto scope = item.goToFile(owner->semanticScope->filePath()) + .rootQmlObject(GoTo::MostLikely) + .semanticScope()) { + if (scope->hasEnumerationKey(name)) { + return ExpressionType{ name, scope, EnumeratorValueIdentifier }; + } + // Or it is a enum name <TypeName>.<EnumName>.<EnumValue> + else if (scope->hasEnumeration(name)) { + return ExpressionType{ name, scope, EnumeratorIdentifier }; + } } // check inline components <TypeName>.<InlineComponentName> diff --git a/src/quick/items/qquickitemview_p_p.h b/src/quick/items/qquickitemview_p_p.h index 9aca664ec6..459d348b9b 100644 --- a/src/quick/items/qquickitemview_p_p.h +++ b/src/quick/items/qquickitemview_p_p.h @@ -43,7 +43,7 @@ public: }; -class Q_AUTOTEST_EXPORT QQuickItemViewChangeSet +class Q_QUICK_EXPORT QQuickItemViewChangeSet { public: QQuickItemViewChangeSet(); diff --git a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp index 25397216bb..3753832a98 100644 --- a/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp +++ b/src/quick/scenegraph/adaptations/software/qsgsoftwarecontext.cpp @@ -71,6 +71,8 @@ QSurfaceFormat QSGSoftwareContext::defaultSurfaceFormat() const format.setRenderableType(QSurfaceFormat::DefaultRenderableType); format.setMajorVersion(0); format.setMinorVersion(0); + if (QQuickWindow::hasDefaultAlphaBuffer()) + format.setAlphaBufferSize(8); return format; } diff --git a/src/quick/scenegraph/qsgrenderloop.cpp b/src/quick/scenegraph/qsgrenderloop.cpp index 3c8da0fc6a..319c98ef93 100644 --- a/src/quick/scenegraph/qsgrenderloop.cpp +++ b/src/quick/scenegraph/qsgrenderloop.cpp @@ -580,14 +580,6 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) if (!ensureRhi(window, data)) return; - bool lastDirtyWindow = true; - for (auto it = m_windows.cbegin(), end = m_windows.cend(); it != end; ++it) { - if (it->updatePending) { - lastDirtyWindow = false; - break; - } - } - cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(window); // Event delivery/processing triggered the window to be deleted or stop rendering. if (!m_windows.contains(window)) @@ -689,8 +681,7 @@ void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window) data.rhi->makeThreadLocalNativeContextCurrent(); cd->syncSceneGraph(); - if (lastDirtyWindow) - data.rc->endSync(); + data.rc->endSync(); if (profileFrames) syncTime = renderTimer.nsecsElapsed(); @@ -824,16 +815,12 @@ void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window) if (winDataIt == m_windows.end()) return; - // Even if the window is not renderable, - // renderWindow() called on different window - // should not delete QSGTexture's - // from this unrenderable window. - winDataIt->updatePending = true; - QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window); if (!cd->isRenderable()) return; + winDataIt->updatePending = true; + // An updatePolish() implementation may call update() to get the QQuickItem // dirtied. That's fine but it also leads to calling this function. // Requesting another update is a waste then since the updatePolish() call diff --git a/src/quick/scenegraph/qsgrenderloop_p.h b/src/quick/scenegraph/qsgrenderloop_p.h index 9157e2edd2..bd7414492c 100644 --- a/src/quick/scenegraph/qsgrenderloop_p.h +++ b/src/quick/scenegraph/qsgrenderloop_p.h @@ -105,6 +105,9 @@ WM_Obscure = QEvent::User + 1, // (updatePaintNode()) WM_RequestSync = QEvent::User + 2, +// Passed from the RL to the RT when a window is exposed +WM_Exposed = QEvent::User + 3, + // Passed by the RL to the RT to free up maybe release SG and GL contexts // if no windows are rendering. WM_TryRelease = QEvent::User + 4, diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index e45ef8768b..6a0286dbce 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -338,6 +338,17 @@ bool QSGRenderThread::event(QEvent *e) return true; } + + case WM_Exposed: { + qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Exposed"); + + mutex.lock(); + window = static_cast<WMWindowEvent *>(e)->window; + waitCondition.wakeOne(); + mutex.unlock(); + + return true; } + case WM_RequestSync: { qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestSync"); WMSyncEvent *se = static_cast<WMSyncEvent *>(e); @@ -641,8 +652,14 @@ void QSGRenderThread::syncAndRender() // An update request could still be delivered right before we get an // unexpose. With Vulkan on Windows for example attempting to render // leads to failures at this stage since the surface size is already 0. - if (effectiveOutputSize.isEmpty()) + if (effectiveOutputSize.isEmpty()) { + if (syncRequested) { + mutex.lock(); + waitCondition.wakeOne(); + mutex.unlock(); + } return; + } const QSize previousOutputSize = cd->swapchain->currentPixelSize(); if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) { @@ -1286,10 +1303,6 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) } } - // set this early as we'll be rendering shortly anyway and this avoids - // specialcasing exposure in polishAndSync. - w->thread->window = window; - #ifndef QT_NO_DEBUG if (w->window->width() <= 0 || w->window->height() <= 0 || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) { @@ -1308,6 +1321,10 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) if (!w->thread->isRunning()) { qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread"); + // set this early as we'll be rendering shortly anyway and this avoids + // specialcasing exposure in polishAndSync. + w->thread->window = window; + if (!w->thread->rhi) { QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); if (!w->thread->offscreenSurface) @@ -1332,6 +1349,12 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) } else { qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running"); + + // set w->thread->window here too, but using an event so it's thread-safe + w->thread->mutex.lock(); + w->thread->postEvent(new WMWindowEvent(w->window, QEvent::Type(WM_Exposed))); + w->thread->waitCondition.wait(&w->thread->mutex); + w->thread->mutex.unlock(); } polishAndSync(w, true); diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index 7b49fb7ef3..3e90e2f041 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -36,6 +36,7 @@ #include <private/qqmlobjectmodel_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtQuick/private/qquickitemview_p_p.h> #include <QtQuick/private/qquickevents_p_p.h> #include <QtQuick/private/qquicklistview_p.h> #include <QtQuick/private/qquickrendercontrol_p.h> @@ -979,6 +980,22 @@ bool QQuickMenuPrivate::prepareEnterTransition() // the right, it flips on the other side of the parent menu. allowHorizontalFlip = cascade && parentMenu; + // Enter transitions may want to animate the Menu's height based on its implicitHeight. + // The Menu's implicitHeight is typically based on the ListView's contentHeight, + // among other things. The docs for ListView's forceLayout function say: + // "Responding to changes in the model is usually batched to happen only once per frame." + // As e.g. NumberAnimation's from and to values are set before any polishes happen, + // any re-evaluation of their bindings happen too late, and the starting height can be + // out-dated when menu items are added after component completion + // (QQuickItemView::componentComplete does a layout, so items declared as children aren't + // affected by this). To account for this, we force a layout before the transition starts. + // We try to avoid unnecessary re-layouting if we can avoid it. + auto *contentItemAsListView = qobject_cast<QQuickListView *>(contentItem); + if (contentItemAsListView) { + if (QQuickItemViewPrivate::get(contentItemAsListView)->currentChanges.hasPendingChanges()) + contentItemAsListView->forceLayout(); + } + if (!QQuickPopupPrivate::prepareEnterTransition()) return false; diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h index 44226d6a78..fea49db107 100644 --- a/src/quickvectorimage/generator/qquicknodeinfo_p.h +++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h @@ -70,6 +70,14 @@ struct StrokeStyle } }; +struct PathTrimInfo +{ + bool enabled = false; + QQuickAnimatedProperty start = QQuickAnimatedProperty(QVariant::fromValue(0.0)); + QQuickAnimatedProperty end = QQuickAnimatedProperty(QVariant::fromValue(1.0)); + QQuickAnimatedProperty offset = QQuickAnimatedProperty(QVariant::fromValue(0.0)); +}; + struct PathNodeInfo : NodeInfo { QPainterPath painterPath; @@ -79,6 +87,7 @@ struct PathNodeInfo : NodeInfo StrokeStyle strokeStyle; QGradient grad; QTransform fillTransform; + PathTrimInfo trim; }; struct TextNodeInfo : NodeInfo diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp index 72b6b51035..2e71b415bd 100644 --- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp +++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp @@ -467,6 +467,13 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte generateTransform(xf); } + if (info.trim.enabled) { + stream() << "trim.start: " << info.trim.start.defaultValue().toReal(); + stream() << "trim.end: " << info.trim.end.defaultValue().toReal(); + stream() << "trim.offset: " << info.trim.offset.defaultValue().toReal(); + + } + if (fillRule == QQuickShapePath::WindingFill) stream() << "fillRule: ShapePath.WindingFill"; else @@ -484,6 +491,12 @@ void QQuickQmlGenerator::outputShapePath(const PathNodeInfo &info, const QPainte m_indentLevel--; stream() << "}"; + if (info.trim.enabled) { + generatePropertyAnimation(info.trim.start, shapePathId + QStringLiteral(".trim"), QStringLiteral("start")); + generatePropertyAnimation(info.trim.end, shapePathId + QStringLiteral(".trim"), QStringLiteral("end")); + generatePropertyAnimation(info.trim.offset, shapePathId + QStringLiteral(".trim"), QStringLiteral("offset")); + } + generatePropertyAnimation(info.strokeStyle.color, shapePathId, QStringLiteral("strokeColor")); generatePropertyAnimation(info.strokeStyle.opacity, shapePathId, QStringLiteral("strokeColor"), AnimationType::ColorOpacity); generatePropertyAnimation(info.fillColor, shapePathId, QStringLiteral("fillColor")); |