Delay loading implicit import
authorAlan Alpert <aalpert@rim.com>
Sat, 8 Dec 2012 21:57:12 +0000 (13:57 -0800)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 27 Mar 2013 19:40:28 +0000 (20:40 +0100)
As a performance improvement to avoid extra filesystem access, only
import "." if it is needed for type resolution.

Change-Id: If9be25deb3205f8c81f9f418404d9fb41bebb84f
Reviewed-by: Christopher Adams <chris.adams@jollamobile.com>
src/qml/qml/qqmlimport.cpp
src/qml/qml/qqmltypeloader.cpp
src/qml/qml/qqmltypeloader_p.h
tests/auto/qml/qqmllanguage/data/LocalLast2.qml [new file with mode: 0644]
tests/auto/qml/qqmllanguage/data/localOrderTest.qml [new file with mode: 0644]
tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
tests/auto/qml/qqmlmoduleplugin/data/implicit2/temptest2.qml

index d3ec9e6..f793ca9 100644 (file)
@@ -260,7 +260,7 @@ public:
     QQmlImportNamespace::Import *addImportToNamespace(QQmlImportNamespace *nameSpace,
                                                       const QString &uri, const QString &url,
                                                       int vmaj, int vmin, QQmlScript::Import::Type type,
-                                                      QList<QQmlError> *errors);
+                                                      QList<QQmlError> *errors, bool lowPrecedence = false);
 };
 
 /*!
@@ -1003,7 +1003,7 @@ QQmlImportNamespace *QQmlImportsPrivate::importNamespace(const QString &prefix)
 QQmlImportNamespace::Import *QQmlImportsPrivate::addImportToNamespace(QQmlImportNamespace *nameSpace,
                                                                       const QString &uri, const QString &url, int vmaj, int vmin,
                                                                       QQmlScript::Import::Type type,
-                                                                      QList<QQmlError> *errors)
+                                                                      QList<QQmlError> *errors, bool lowPrecedence)
 {
     Q_ASSERT(nameSpace);
     Q_ASSERT(errors);
@@ -1017,7 +1017,11 @@ QQmlImportNamespace::Import *QQmlImportsPrivate::addImportToNamespace(QQmlImport
     import->minversion = vmin;
     import->isLibrary = (type == QQmlScript::Import::Library);
 
-    nameSpace->imports.prepend(import);
+    if (lowPrecedence)
+        nameSpace->imports.append(import);
+    else
+        nameSpace->imports.prepend(import);
+
     return import;
 }
 
@@ -1162,7 +1166,7 @@ bool QQmlImportsPrivate::addFileImport(const QString& uri, const QString &prefix
     if (!url.endsWith(Slash) && !url.endsWith(Backslash))
         url += Slash;
 
-    QQmlImportNamespace::Import *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QQmlScript::Import::File, errors);
+    QQmlImportNamespace::Import *inserted = addImportToNamespace(nameSpace, importUri, url, vmaj, vmin, QQmlScript::Import::File, errors, isImplicitImport);
     Q_ASSERT(inserted);
 
     if (!incomplete && !qmldirIdentifier.isEmpty()) {
@@ -1238,6 +1242,8 @@ bool QQmlImportsPrivate::updateQmldirContent(const QString &uri, const QString &
 
   Adds an implicit "." file import.  This is equivalent to calling addFileImport(), but error
   messages related to the path or qmldir file not existing are suppressed.
+
+  Additionally, this will add the import with lowest instead of highest precedence.
 */
 bool QQmlImports::addImplicitImport(QQmlImportDatabase *importDb, QList<QQmlError> *errors)
 {
index 9547204..e800eb8 100644 (file)
@@ -1902,7 +1902,7 @@ QQmlTypeData::TypeDataCallback::~TypeDataCallback()
 QQmlTypeData::QQmlTypeData(const QUrl &url, QQmlTypeLoader::Options options, 
                                            QQmlTypeLoader *manager)
 : QQmlTypeLoader::Blob(url, QmlFile, manager), m_options(options),
-   m_typesResolved(false), m_compiledData(0), m_implicitImport(0)
+   m_typesResolved(false), m_compiledData(0), m_implicitImport(0), m_implicitImportLoaded(false)
 {
 }
 
@@ -2008,23 +2008,14 @@ void QQmlTypeData::completed()
     }
 }
 
-void QQmlTypeData::dataReceived(const Data &data)
+bool QQmlTypeData::loadImplicitImport()
 {
-    QString code = QString::fromUtf8(data.data(), data.size());
-    QByteArray preparseData;
-
-    if (data.isFile()) preparseData = data.asFile()->metaData(QLatin1String("qml:preparse"));
-
-    if (!scriptParser.parse(code, preparseData, finalUrl(), finalUrlString())) {
-        setError(scriptParser.errors());
-        return;
-    }
+    m_implicitImportLoaded = true; // Even if we hit an error, count as loaded (we'd just keep hitting the error)
 
     m_imports.setBaseUrl(finalUrl(), finalUrlString());
 
     QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
-
-    // For local urls, add an implicit import "." as first (most overridden) lookup.
+    // For local urls, add an implicit import "." as most overridden lookup.
     // This will also trigger the loading of the qmldir and the import of any native
     // types from available plugins.
     QList<QQmlError> implicitImportErrors;
@@ -2032,20 +2023,41 @@ void QQmlTypeData::dataReceived(const Data &data)
 
     if (!implicitImportErrors.isEmpty()) {
         setError(implicitImportErrors);
+        return false;
+    }
+
+    return true;
+}
+
+void QQmlTypeData::dataReceived(const Data &data)
+{
+    QString code = QString::fromUtf8(data.data(), data.size());
+    QByteArray preparseData;
+
+    if (data.isFile()) preparseData = data.asFile()->metaData(QLatin1String("qml:preparse"));
+
+    if (!scriptParser.parse(code, preparseData, finalUrl(), finalUrlString())) {
+        setError(scriptParser.errors());
         return;
     }
 
-    QList<QQmlError> errors;
+    m_imports.setBaseUrl(finalUrl(), finalUrlString());
 
+    // For remote URLs, we don't delay the loading of the implicit import
+    // because the loading probably requires an asynchronous fetch of the
+    // qmldir (so we can't load it just in time).
     if (!finalUrl().scheme().isEmpty()) {
         QUrl qmldirUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
         if (!QQmlImports::isLocal(qmldirUrl)) {
+            if (!loadImplicitImport())
+                return;
             // This qmldir is for the implicit import
             m_implicitImport = new QQmlScript::Import;
             m_implicitImport->uri = QLatin1String(".");
             m_implicitImport->qualifier = QString();
             m_implicitImport->majorVersion = -1;
             m_implicitImport->minorVersion = -1;
+            QList<QQmlError> errors;
 
             if (!fetchQmldir(qmldirUrl, m_implicitImport, 1, &errors)) {
                 setError(errors);
@@ -2054,6 +2066,8 @@ void QQmlTypeData::dataReceived(const Data &data)
         }
     }
 
+    QList<QQmlError> errors;
+
     foreach (const QQmlScript::Import &import, scriptParser.imports()) {
         if (!addImport(import, &errors)) {
             Q_ASSERT(errors.size());
@@ -2155,8 +2169,21 @@ void QQmlTypeData::resolveTypes()
         QQmlImportNamespace *typeNamespace = 0;
         QList<QQmlError> errors;
 
-        if (!m_imports.resolveType(parserRef->name, &ref.type, &majorVersion, &minorVersion,
-                                   &typeNamespace, &errors) || typeNamespace) {
+        bool typeFound = m_imports.resolveType(parserRef->name, &ref.type,
+                &majorVersion, &minorVersion, &typeNamespace, &errors);
+        if (!typeNamespace && !typeFound && !m_implicitImportLoaded) {
+            // Lazy loading of implicit import
+            if (loadImplicitImport()) {
+                // Try again to find the type
+                errors.clear();
+                typeFound = m_imports.resolveType(parserRef->name, &ref.type,
+                    &majorVersion, &minorVersion, &typeNamespace, &errors);
+            } else {
+                return; //loadImplicitImport() hit an error, and called setError already
+            }
+        }
+
+        if (!typeFound || typeNamespace) {
             // Known to not be a type:
             //  - known to be a namespace (Namespace {})
             //  - type with unknown namespace (UnknownNamespace.SomeType {})
index b4ecfb7..68b8f33 100644 (file)
@@ -465,6 +465,8 @@ private:
     QList<TypeDataCallback *> m_callbacks;
 
     QQmlScript::Import *m_implicitImport;
+    bool m_implicitImportLoaded;
+    bool loadImplicitImport();
 };
 
 // QQmlScriptData instances are created, uninitialized, by the loader in the 
diff --git a/tests/auto/qml/qqmllanguage/data/LocalLast2.qml b/tests/auto/qml/qqmllanguage/data/LocalLast2.qml
new file mode 100644 (file)
index 0000000..a6acfcd
--- /dev/null
@@ -0,0 +1,2 @@
+import QtQuick 2.0
+MouseArea {}
diff --git a/tests/auto/qml/qqmllanguage/data/localOrderTest.qml b/tests/auto/qml/qqmllanguage/data/localOrderTest.qml
new file mode 100644 (file)
index 0000000..a6a9a4d
--- /dev/null
@@ -0,0 +1,7 @@
+import QtQuick 2.0
+import org.qtproject.installedtest 1.0
+
+LocalLast2 {
+    property QtObject item: LocalLast {}
+}
+
index 3121d10..5a924a9 100644 (file)
@@ -140,6 +140,7 @@ private slots:
     void readonly();
     void receivers();
     void registeredCompositeType();
+    void implicitImportsLast();
 
     void basicRemote_data();
     void basicRemote();
@@ -2470,6 +2471,12 @@ void tst_qqmllanguage::importsOrder_data()
            << (!qmlCheckTypes()?"QQuickRectangle":"")// i.e. from org.qtproject.installedtest, not data/LocalLast.qml
            << (!qmlCheckTypes()?"":"LocalLast is ambiguous. Found in lib/org/qtproject/installedtest/ and in ")
            << false;
+    QTest::newRow("local last 3") << //Forces it to load the local qmldir to resolve types, but they shouldn't override anything
+           "import org.qtproject.installedtest 1.0\n"
+           "LocalLast {LocalLast2{}}"
+           << (!qmlCheckTypes()?"QQuickRectangle":"")// i.e. from org.qtproject.installedtest, not data/LocalLast.qml
+           << (!qmlCheckTypes()?"":"LocalLast is ambiguous. Found in lib/org/qtproject/installedtest/ and in ")
+           << false;
 }
 
 void tst_qqmllanguage::importsOrder()
@@ -3142,6 +3149,30 @@ void tst_qqmllanguage::scopedProperties()
     QVERIFY(o->property("success").toBool());
 }
 
+// Tests that the implicit import has lowest precedence, in the case where
+// there are conflicting types and types only found in the local import.
+// Tests that just check one (or the root) type are in ::importsOrder
+void tst_qqmllanguage::implicitImportsLast()
+{
+    if (qmlCheckTypes())
+        QSKIP("This test is about maintaining the same choice when type is ambiguous.");
+
+    if (engine.importPathList() == defaultImportPathList)
+        engine.addImportPath(testFile("lib"));
+
+    QQmlComponent component(&engine, testFile("localOrderTest.qml"));
+    VERIFY_ERRORS(0);
+    QObject *object = qobject_cast<QObject *>(component.create());
+    QVERIFY(object != 0);
+    QVERIFY(QString(object->metaObject()->className()).startsWith(QLatin1String("QQuickMouseArea")));
+    QObject* object2 = object->property("item").value<QObject*>();
+    QVERIFY(object2 != 0);
+    QCOMPARE(QString(object2->metaObject()->className()), QLatin1String("QQuickRectangle"));
+
+    engine.setImportPathList(defaultImportPathList);
+}
+
+
 QTEST_MAIN(tst_qqmllanguage)
 
 #include "tst_qqmllanguage.moc"
index 0fa9f6e..051c6f8 100644 (file)
@@ -1,8 +1,10 @@
 import QtQuick 2.0
 
 // the type loader will implicitly search "." for a qmldir
+// to try and find the missing type of AItem
 // and the qmldir has various syntax errors in it.
 
 Item {
     id: root
+    AItem{}
 }