Keep track of implicit and explicit Component

This allows us to ignore them when we e.g. check for required
properties.

Fixes: QTBUG-95373
Change-Id: I2b02e3c24b4891773ac6619a9e051edd9d87aa1f
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Fabian Kosmale 2021-08-10 11:02:18 +02:00
parent f0e5ff83a1
commit 3dccc84336
6 changed files with 86 additions and 12 deletions

View File

@ -489,8 +489,13 @@ void QQmlJSImportVisitor::processDefaultProperties()
// Assigning any element to a QQmlComponent property implicitly wraps it into a Component
// Check whether the property can be assigned the scope
if (propType->canAssign(scope))
if (propType->canAssign(scope)) {
if (propType->causesImplicitComponentWrapping()) {
// mark the scope as implicitly wrapped, unless it is a Component
scope->setIsWrappedInImplicitComponent(!scope->causesImplicitComponentWrapping());
}
continue;
}
m_logger->logWarning(
QStringLiteral("Cannot assign to default property of incompatible type"),
@ -529,6 +534,10 @@ void QQmlJSImportVisitor::processPropertyBindingObjects()
if (property.isValid() && !property.type().isNull()
&& (objectBinding.onToken || property.type()->canAssign(objectBinding.childScope))) {
if (property.type()->causesImplicitComponentWrapping())
objectBinding.childScope->setIsWrappedInImplicitComponent(!objectBinding.childScope->causesImplicitComponentWrapping());
QQmlJSMetaPropertyBinding binding =
objectBinding.scope->hasOwnPropertyBinding(propertyName)
? objectBinding.scope->ownPropertyBinding(propertyName)
@ -629,7 +638,7 @@ void QQmlJSImportVisitor::checkRequiredProperties()
}
for (const auto &defScope : m_objectDefinitionScopes) {
if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent())
if (defScope->parentScope() == m_globalScope || defScope->isInlineComponent() || defScope->isComponentRootElement())
continue;
QVector<QQmlJSScope::ConstPtr> scopesToSearch;
@ -1611,7 +1620,8 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
{
QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes);
const QQmlJSScope::ConstPtr childScope = m_currentScope;
// must be mutable, as we might mark it as implicitly wrapped in a component
const QQmlJSScope::Ptr childScope = m_currentScope;
leaveEnvironment();
auto group = uiob->qualifiedId;

View File

@ -198,7 +198,7 @@ protected:
struct PendingPropertyObjectBinding
{
QQmlJSScope::Ptr scope;
QQmlJSScope::ConstPtr childScope;
QQmlJSScope::Ptr childScope;
QString name;
QQmlJS::SourceLocation location;
bool onToken;

View File

@ -179,6 +179,45 @@ QQmlJSMetaEnum QQmlJSScope::enumeration(const QString &name) const
return result;
}
/*!
Returns if assigning to a property of this type would cause
implicit component wrapping for non-Component types.
\note This method can also be used to check whether a type needs
to be implicitly wrapped: A type for which this function returns true
doesn't need to be actually wrapped.
*/
bool QQmlJSScope::causesImplicitComponentWrapping() const {
if (internalName() == u"QQmlComponent")
return true;
else if (isComposite()) // composite types are never treated as Component
return false;
// A class which is derived from component is not treated as a Component
// However isUsableComponent considers also QQmlAbstractDelegateComponent
// See isUsableComponent in qqmltypecompiler.cpp
for (auto cppBase = nonCompositeBaseType(baseType()); cppBase; cppBase = cppBase->baseType())
if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
return true;
return false;
}
/*!
\internal
Returns true if the scope is the outermost element of a separate Component
Either because it has been implicitly wrapped, e.g. due to an assignment to
a Component property, or because it is the first (and only) child of a
Component.
*/
bool QQmlJSScope::isComponentRootElement() const {
if (m_flags.testFlag(WrappedInImplicitComponent))
return true;
auto base = nonCompositeBaseType(parentScope()); // handles null parentScope()
if (!base)
return false;
return base->internalName() == u"QQmlComponent";
}
bool QQmlJSScope::isIdInCurrentQmlScopes(const QString &id) const
{
if (m_scopeType == QQmlJSScope::QMLScope)
@ -602,13 +641,7 @@ bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const
if (!derived)
return false;
bool isBaseComponent = false;
for (auto scope = this; scope; scope = scope->baseType().get()) {
if (internalName() == u"QQmlComponent"_qs) {
isBaseComponent = true;
break;
}
}
bool isBaseComponent = causesImplicitComponentWrapping();
for (auto scope = derived; !scope.isNull(); scope = scope->baseType()) {
if (isSameType(scope))

View File

@ -115,7 +115,8 @@ public:
Script = 0x8,
CustomParser = 0x10,
Array = 0x20,
InlineComponent = 0x40
InlineComponent = 0x40,
WrappedInImplicitComponent = 0x80
};
Q_DECLARE_FLAGS(Flags, Flag)
Q_FLAGS(Flags);
@ -201,6 +202,9 @@ public:
QString internalName() const { return m_internalName; }
void setInternalName(const QString &internalName) { m_internalName = internalName; }
bool causesImplicitComponentWrapping() const;
bool isComponentRootElement() const;
void addExport(const QString &name, const QString &package, const QTypeRevision &version);
QList<Export> exports() const { return m_exports; }
@ -295,6 +299,7 @@ public:
}
void setIsArrayScope(bool v) { m_flags.setFlag(Array, v); }
void setIsInlineComponent(bool v) { m_flags.setFlag(InlineComponent, v); }
void setIsWrappedInImplicitComponent(bool v) { m_flags.setFlag(WrappedInImplicitComponent, v); }
void setAccessSemantics(AccessSemantics semantics) { m_semantics = semantics; }
AccessSemantics accessSemantics() const { return m_semantics; }

View File

@ -0,0 +1,25 @@
import QtQuick 2.0
Row {
// explicit component
Component {
id: foo
Item {
required property int i
}
}
// implicit component, plain property
property Component com: Item {}
Repeater {
model: 3
// implicit component, default property
Text {
required property int index
height: 40
color: "black"
text: "I'm item " + index
}
}
}

View File

@ -820,6 +820,7 @@ void TestQmllint::cleanQmlCode_data()
QTest::newRow("QQmlEasingEnums::Type") << QStringLiteral("animationEasing.qml");
QTest::newRow("ValidLiterals") << QStringLiteral("validLiterals.qml");
QTest::newRow("GoodModulePrefix") << QStringLiteral("goodModulePrefix.qml");
QTest::newRow("required property in Component") << QStringLiteral("requiredPropertyInComponent.qml");
}
void TestQmllint::cleanQmlCode()