Fix extension lookup in QQmlJSScope

Do not go over extension's base types since this is not how the engine
logic work. Instead, only assess the direct extension type when
traversing the base type hierarchy. The special cases are value types
which still need base type extension traversal (e.g. due to Number
and NumberPrototype extensions on primitives) and QObject type itself
(we expect to see ObjectPrototype properties / methods of it)

Pick-to: 6.3 6.2
Change-Id: I92ba979202b33f16e1a7b948d4f1e79df37f2669
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Andrei Golubev 2022-05-03 17:08:37 +02:00
parent 11d2767f09
commit a287e862d4
5 changed files with 163 additions and 5 deletions

View File

@ -177,6 +177,9 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
template<typename QQmlJSScopePtr, typename Action>
static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check)
{
if (!type)
return false;
using namespace detail;
// NB: among other things, getQQmlJSScopeFromSmartPtr() also resolves const
@ -196,17 +199,26 @@ struct Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSUtils
}
};
const bool isValueType = (type->accessSemantics() == QQmlJSScope::AccessSemantics::Value);
QDuplicateTracker<T> seen;
for (T scope = type; scope && !seen.hasSeen(scope);
scope = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->baseType())) {
// Extensions override their base types
QDuplicateTracker<T> seenExtensions;
for (T extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->extensionType());
extension && !seenExtensions.hasSeen(extension);
extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType())) {
// Extensions override the types they extend. However, usually base
// types of extensions are ignored. The unusual cases are when we
// have a value type or when we have the QObject type, in which case
// we also study the extension's base type hierarchy.
const bool isQObject = scope->internalName() == QLatin1String("QObject");
T extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->extensionType());
do {
if (!extension || seenExtensions.hasSeen(extension))
break;
if (checkWrapper(extension, QQmlJSScope::ExtensionType))
return true;
}
extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType());
} while (isValueType || isQObject);
if (checkWrapper(scope, QQmlJSScope::NotExtension))
return true;

View File

@ -10,6 +10,7 @@ qt6_add_qml_module(qqmljsscope_test_module
URI QQmlJSScopeTests
SOURCES
singleton.h singleton.cpp
extensiontypes.h
)
qt_autogen_tools_initial_setup(qqmljsscope_test_moduleplugin)

View File

@ -0,0 +1,114 @@
/****************************************************************************
**
** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef EXTENSIONTYPES_H
#define EXTENSIONTYPES_H
#include <QtCore/qobject.h>
#include <qqml.h>
class Extension : public QObject
{
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(int count READ getCount WRITE setCount NOTIFY countChanged)
public:
Extension(QObject *parent = nullptr) : QObject(parent) { }
int getCount() const { return 42; }
void setCount(int) { }
Q_SIGNALS:
void countChanged();
};
class IndirectExtension : public Extension
{
Q_OBJECT
QML_ANONYMOUS
public:
IndirectExtension(QObject *parent = nullptr) : Extension(parent) { }
};
class Extended : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_EXTENDED(Extension)
Q_PROPERTY(double count READ getCount WRITE setCount NOTIFY countChanged)
public:
Extended(QObject *parent = nullptr) : QObject(parent) { }
double getCount() const { return 0.0; }
void setCount(double) { }
Q_SIGNALS:
void countChanged();
};
class ExtendedIndirect : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_EXTENDED(IndirectExtension)
Q_PROPERTY(double count READ getCount WRITE setCount NOTIFY countChanged)
public:
ExtendedIndirect(QObject *parent = nullptr) : QObject(parent) { }
double getCount() const { return 0; }
void setCount(double) { }
Q_SIGNALS:
void countChanged();
};
class Extension2 : public QObject
{
Q_OBJECT
QML_ANONYMOUS
Q_PROPERTY(QString str READ getStr WRITE setStr NOTIFY strChanged)
public:
Extension2(QObject *parent = nullptr) : QObject(parent) { }
QString getStr() const { return QStringLiteral("42"); }
void setStr(QString) { }
Q_SIGNALS:
void strChanged();
};
class ExtendedTwice : public Extended
{
Q_OBJECT
QML_ELEMENT
QML_EXTENDED(Extension2)
Q_PROPERTY(QByteArray str READ getStr WRITE setStr)
public:
ExtendedTwice(QObject *parent = nullptr) : Extended(parent) { }
QByteArray getStr() const { return QByteArray(); }
void setStr(QByteArray) { }
};
#endif // EXTENSIONTYPES_H

View File

@ -0,0 +1,8 @@
import QtQuick
import QQmlJSScopeTests 1.0
Item {
Extended { }
ExtendedIndirect { }
ExtendedTwice { }
}

View File

@ -128,6 +128,7 @@ private Q_SLOTS:
void groupedPropertySyntax();
void attachedProperties();
void scriptIndices();
void extensions();
public:
tst_qqmljsscope()
@ -568,5 +569,27 @@ void tst_qqmljsscope::scriptIndices()
QCOMPARE(orderedJSScopeExpressionsAbsolute, orderedQmlIrExpressionsAbsolute);
}
void tst_qqmljsscope::extensions()
{
QQmlJSScope::ConstPtr root = run(u"extensions.qml"_s);
QVERIFY(root);
QVERIFY(root->isFullyResolved());
const auto childScopes = root->childScopes();
QCOMPARE(childScopes.size(), 3);
QCOMPARE(childScopes[0]->baseTypeName(), u"Extended"_s);
QCOMPARE(childScopes[1]->baseTypeName(), u"ExtendedIndirect"_s);
QCOMPARE(childScopes[2]->baseTypeName(), u"ExtendedTwice"_s);
QVERIFY(childScopes[0]->isFullyResolved());
QVERIFY(childScopes[1]->isFullyResolved());
QVERIFY(childScopes[2]->isFullyResolved());
QCOMPARE(childScopes[0]->property(u"count"_s).typeName(), u"int"_s);
QCOMPARE(childScopes[1]->property(u"count"_s).typeName(), u"double"_s);
QCOMPARE(childScopes[2]->property(u"count"_s).typeName(), u"int"_s);
QCOMPARE(childScopes[2]->property(u"str"_s).typeName(), u"QString"_s);
}
QTEST_MAIN(tst_qqmljsscope)
#include "tst_qqmljsscope.moc"