diff options
Diffstat (limited to 'src/qmlcompiler/qqmljsimportvisitor.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 278 |
1 files changed, 187 insertions, 91 deletions
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 158ff03e20..733cb3b2c0 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -69,19 +69,21 @@ inline QString getScopeName(const QQmlJSScope::ConstPtr &scope, QQmlJSScope::Sco return scope->baseTypeName(); } -QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, - const QString &implicitImportDirectory, - const QStringList &qmltypesFiles, const QString &fileName, - const QString &code, bool silent) +QQmlJSImportVisitor::QQmlJSImportVisitor( + const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, + const QString &implicitImportDirectory, const QStringList &qmltypesFiles, + const QString &fileName, const QString &code, bool silent) : m_implicitImportDirectory(implicitImportDirectory), m_code(code), m_filePath(fileName), m_rootId(u"<id>"_qs), m_qmltypesFiles(qmltypesFiles), - m_currentScope(QQmlJSScope::create(QQmlJSScope::JSFunctionScope)), + m_currentScope(QQmlJSScope::create()), + m_exportedRootScope(target), m_importer(importer), m_logger(fileName, code, silent) { + m_currentScope->setScopeType(QQmlJSScope::JSFunctionScope); m_globalScope = m_currentScope; m_currentScope->setIsComposite(true); @@ -111,13 +113,30 @@ QQmlJSImportVisitor::QQmlJSImportVisitor(QQmlJSImporter *importer, m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript); } -void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name, - const QQmlJS::SourceLocation &location) +void QQmlJSImportVisitor::populateCurrentScope( + QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location) { - m_currentScope = QQmlJSScope::create(type, m_currentScope); + m_currentScope->setScopeType(type); setScopeName(m_currentScope, type, name); m_currentScope->setIsComposite(true); m_currentScope->setSourceLocation(location); + m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, m_currentScope); +} + +void QQmlJSImportVisitor::enterRootScope(QQmlJSScope::ScopeType type, const QString &name, const QQmlJS::SourceLocation &location) +{ + QQmlJSScope::reparent(m_currentScope, m_exportedRootScope); + m_currentScope = m_exportedRootScope; + populateCurrentScope(type, name, location); +} + +void QQmlJSImportVisitor::enterEnvironment(QQmlJSScope::ScopeType type, const QString &name, + const QQmlJS::SourceLocation &location) +{ + QQmlJSScope::Ptr newScope = QQmlJSScope::create(); + QQmlJSScope::reparent(m_currentScope, newScope); + m_currentScope = std::move(newScope); + populateCurrentScope(type, name, location); } bool QQmlJSImportVisitor::enterEnvironmentNonUnique(QQmlJSScope::ScopeType type, @@ -142,6 +161,7 @@ bool QQmlJSImportVisitor::enterEnvironmentNonUnique(QQmlJSScope::ScopeType type, return false; } // enter found scope + m_scopesByIrLocation.insert({ location.startLine, location.startColumn }, *it); m_currentScope = *it; return true; } @@ -168,14 +188,14 @@ void QQmlJSImportVisitor::resolveAliases() if (!property.isAlias() || !property.type().isNull()) continue; - QStringList components = property.typeName().split(u'.'); - QQmlJSScope::ConstPtr type; + QStringList components = property.typeName().split(u'.', Qt::SkipEmptyParts); QQmlJSMetaProperty targetProperty; // The first component has to be an ID. Find the object it refers to. - const auto it = m_scopesById.find(components.takeFirst()); - if (it != m_scopesById.end()) { - type = *it; + QQmlJSScope::ConstPtr type = components.isEmpty() + ? QQmlJSScope::ConstPtr() + : m_scopesById.scope(components.takeFirst(), object); + if (!type.isNull()) { // Any further components are nested properties of that object. // Technically we can only resolve a limited depth in the engine, but the rules @@ -238,11 +258,6 @@ void QQmlJSImportVisitor::resolveAliases() } } -QQmlJSScope::Ptr QQmlJSImportVisitor::result() const -{ - return m_exportedRootScope; -} - QString QQmlJSImportVisitor::implicitImportDirectory( const QString &localFile, QQmlJSResourceFileMapper *mapper) { @@ -260,10 +275,10 @@ QString QQmlJSImportVisitor::implicitImportDirectory( return QFileInfo(localFile).canonicalPath() + u'/'; } -void QQmlJSImportVisitor::processImportWarnings(const QString &what, const QQmlJS::SourceLocation &srcLocation) +void QQmlJSImportVisitor::processImportWarnings( + const QString &what, const QQmlJS::SourceLocation &srcLocation) { const auto warnings = m_importer->takeWarnings(); - if (warnings.isEmpty()) return; @@ -308,12 +323,15 @@ void QQmlJSImportVisitor::endVisit(UiProgram *) processPropertyBindingObjects(); checkRequiredProperties(); - for (const auto &scope : m_objectBindingScopes) - checkInheritanceCycle(scope); + for (const auto &scope : m_objectBindingScopes) { + breakInheritanceCycles(scope); + checkDeprecation(scope); + } for (const auto &scope : m_objectDefinitionScopes) { checkGroupedAndAttachedScopes(scope); - checkInheritanceCycle(scope); + breakInheritanceCycles(scope); + checkDeprecation(scope); } auto unusedImports = m_importLocations; @@ -489,7 +507,7 @@ void QQmlJSImportVisitor::processPropertyTypes() auto property = type.scope->ownProperty(type.name); - if (const auto propertyType = m_rootScopeImports.value(property.typeName())) { + if (const auto propertyType = m_rootScopeImports.value(property.typeName()).scope) { property.setType(propertyType); type.scope->addOwnProperty(property); } else { @@ -799,11 +817,47 @@ void QQmlJSImportVisitor::addDefaultProperties() m_pendingDefaultProperties[m_currentScope->parentScope()] << m_currentScope; } -void QQmlJSImportVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) +void QQmlJSImportVisitor::breakInheritanceCycles(const QQmlJSScope::Ptr &originalScope) { - QQmlJSScope::ConstPtr originalScope = scope; QList<QQmlJSScope::ConstPtr> scopes; - while (!scope.isNull()) { + for (QQmlJSScope::ConstPtr scope = originalScope; scope;) { + if (scopes.contains(scope)) { + QString inheritenceCycle; + for (const auto &seen : qAsConst(scopes)) { + inheritenceCycle.append(seen->baseTypeName()); + inheritenceCycle.append(QLatin1String(" -> ")); + } + inheritenceCycle.append(scopes.first()->baseTypeName()); + + const QString message = QStringLiteral("%1 is part of an inheritance cycle: %2") + .arg(scope->internalName(), inheritenceCycle); + m_logger.log(message, Log_InheritanceCycle); + originalScope->clearBaseType(); + originalScope->setBaseTypeError(message); + break; + } + + scopes.append(scope); + + const auto newScope = scope->baseType(); + if (newScope.isNull()) { + const QString error = scope->baseTypeError(); + const QString name = scope->baseTypeName(); + if (!error.isEmpty()) { + m_logger.log(error, Log_Import); + } else if (!name.isEmpty()) { + m_logger.log(name + QStringLiteral(" was not found. Did you add all import paths?"), + Log_Import); + } + } + + scope = newScope; + } +} + +void QQmlJSImportVisitor::checkDeprecation(const QQmlJSScope::ConstPtr &originalScope) +{ + for (QQmlJSScope::ConstPtr scope = originalScope; scope; scope = scope->baseType()) { for (const QQmlJSAnnotation &annotation : scope->annotations()) { if (annotation.isDeprecation()) { QQQmlJSDeprecation deprecation = annotation.deprecation(); @@ -817,34 +871,6 @@ void QQmlJSImportVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) m_logger.log(message, Log_Deprecation, originalScope->sourceLocation()); } } - - if (scopes.contains(scope)) { - QString inheritenceCycle; - for (const auto &seen : qAsConst(scopes)) { - if (!inheritenceCycle.isEmpty()) - inheritenceCycle.append(QLatin1String(" -> ")); - inheritenceCycle.append(seen->baseTypeName()); - } - - m_logger.log(QStringLiteral("%1 is part of an inheritance cycle: %2") - .arg(scope->internalName()) - .arg(inheritenceCycle), - Log_InheritanceCycle); - break; - } - - scopes.append(scope); - - if (scope->baseTypeName().isEmpty()) { - break; - } else if (auto newScope = scope->baseType()) { - scope = newScope; - } else { - m_logger.log(scope->baseTypeName() - + QStringLiteral(" was not found. Did you add all import paths?"), - Log_Import); - break; - } } } @@ -931,24 +957,27 @@ bool QQmlJSImportVisitor::visit(UiObjectDefinition *definition) Q_ASSERT(!superType.isEmpty()); if (superType.front().isUpper()) { - enterEnvironment(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation()); - if (!m_exportedRootScope) - m_exportedRootScope = m_currentScope; + if (rootScopeIsValid()) + enterEnvironment(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation()); + else + enterRootScope(QQmlJSScope::QMLScope, superType, definition->firstSourceLocation()); + const QTypeRevision revision = QQmlJSScope::resolveTypes( + m_currentScope, m_rootScopeImports, &m_usedTypes); if (m_nextIsInlineComponent) { m_currentScope->setIsInlineComponent(true); - m_rootScopeImports.insert(m_inlineComponentName.toString(), m_currentScope); + m_rootScopeImports.insert( + m_inlineComponentName.toString(), { m_currentScope, revision }); m_nextIsInlineComponent = false; } } else { enterEnvironmentNonUnique(QQmlJSScope::GroupedPropertyScope, superType, definition->firstSourceLocation()); - Q_ASSERT(m_exportedRootScope); + Q_ASSERT(rootScopeIsValid()); + QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes); } m_currentScope->setAnnotations(parseAnnotations(definition->annotations)); - - QQmlJSScope::resolveTypes(m_currentScope, m_rootScopeImports, &m_usedTypes); return true; } @@ -1018,7 +1047,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember) } } else { const auto name = publicMember->memberType->name.toString(); - if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].isNull()) { + if (m_rootScopeImports.contains(name) && !m_rootScopeImports[name].scope.isNull()) { if (m_importTypeLocationMap.contains(name)) m_usedTypes.insert(name); } @@ -1028,7 +1057,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember) prop.setIsList(publicMember->typeModifier == QLatin1String("list")); prop.setIsWritable(!publicMember->isReadonlyMember); prop.setIsAlias(isAlias); - if (const auto type = m_rootScopeImports.value(typeName)) { + if (const auto type = m_rootScopeImports.value(typeName).scope) { prop.setType(type); const QString internalName = type->internalName(); prop.setTypeName(internalName.isEmpty() ? typeName : internalName); @@ -1077,17 +1106,31 @@ void QQmlJSImportVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExp m_pendingMethodAnnotations.clear(); } + bool anyFormalTyped = false; if (const auto *formals = fexpr->formals) { const auto parameters = formals->formals(); for (const auto ¶meter : parameters) { const QString type = parameter.typeName(); - method.addParameter(parameter.id, - type.isEmpty() ? QStringLiteral("var") : type); + if (type.isEmpty()) { + method.addParameter(parameter.id, QStringLiteral("var")); + } else { + anyFormalTyped = true; + method.addParameter(parameter.id, type); + } } } - method.setReturnTypeName(fexpr->typeAnnotation - ? fexpr->typeAnnotation->type->toString() - : QStringLiteral("var")); + + // Methods with explicit return type return that. + // Methods with only untyped arguments return an untyped value. + // Methods with at least one typed argument but no explicit return type return void. + // In order to make a function without arguments return void, you have to specify that. + if (fexpr->typeAnnotation) + method.setReturnTypeName(fexpr->typeAnnotation->type->toString()); + else if (anyFormalTyped) + method.setReturnTypeName(QStringLiteral("void")); + else + method.setReturnTypeName(QStringLiteral("var")); + m_currentScope->addOwnMethod(method); if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) { @@ -1147,15 +1190,39 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::ClassExpression *) leaveEnvironment(); } +bool QQmlJSImportVisitor::isImportPrefix(QString prefix) const +{ + if (prefix.isEmpty() || !prefix.front().isUpper()) + return false; + + auto it = m_rootScopeImports.find(prefix); + + if (it == m_rootScopeImports.end()) + return false; + + return it->scope.isNull(); +} + bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding) { m_savedBindingOuterScope = m_currentScope; const auto id = scriptBinding->qualifiedId; const auto *statement = cast<ExpressionStatement *>(scriptBinding->statement); if (!id->next && id->name == QLatin1String("id")) { - const auto *idExpression = cast<IdentifierExpression *>(statement->expression); - const QString &name = idExpression->name.toString(); - m_scopesById.insert(name, m_currentScope); + const QString name = [&]() { + if (const auto *idExpression = cast<IdentifierExpression *>(statement->expression)) { + return idExpression->name.toString(); + } else if (const auto *idString = cast<StringLiteral *>(statement->expression)) { + m_logger.log(u"ids do not need quotation marks"_qs, Log_Syntax, + idString->firstSourceLocation()); + return idString->value.toString(); + } + m_logger.log(u"Failed to parse id"_qs, Log_Syntax, + statement->expression->firstSourceLocation()); + return QString(); + }(); + if (!name.isEmpty()) + m_scopesById.insert(name, m_currentScope); // TODO: Discard this once we properly store binding values and can just use // QQmlJSScope::property() to obtain this @@ -1167,14 +1234,22 @@ bool QQmlJSImportVisitor::visit(UiScriptBinding *scriptBinding) auto group = id; + + QString prefix; for (; group->next; group = group->next) { const QString name = group->name.toString(); if (name.isEmpty()) break; + if (group == id && isImportPrefix(name)) { + prefix = name + u'.'; + continue; + } + enterEnvironmentNonUnique(name.front().isUpper() ? QQmlJSScope::AttachedPropertyScope : QQmlJSScope::GroupedPropertyScope, - name, group->firstSourceLocation()); + prefix + name, group->firstSourceLocation()); + prefix.clear(); } auto name = group->name; @@ -1310,7 +1385,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import) const QString actualPrefix = prefix.isEmpty() ? QFileInfo(entry.resourcePath).baseName() : prefix; - m_rootScopeImports.insert(actualPrefix, scope); + m_rootScopeImports.insert(actualPrefix, { scope, QTypeRevision() }); addImportLocation(actualPrefix); } else { @@ -1334,7 +1409,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import) } else if (path.isFile()) { const auto scope = m_importer->importFile(path.canonicalFilePath()); const QString actualPrefix = prefix.isEmpty() ? scope->internalName() : prefix; - m_rootScopeImports.insert(actualPrefix, scope); + m_rootScopeImports.insert(actualPrefix, { scope, QTypeRevision() }); addImportLocation(actualPrefix); } @@ -1507,20 +1582,30 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) name.chop(1); bool needsResolution = false; + int scopesEnteredCounter = 0; + + QString prefix; for (auto group = uiob->qualifiedId; group->next; group = group->next) { const QString idName = group->name.toString(); if (idName.isEmpty()) break; + if (group == uiob->qualifiedId && isImportPrefix(idName)) { + prefix = idName + u'.'; + continue; + } + const auto scopeKind = idName.front().isUpper() ? QQmlJSScope::AttachedPropertyScope : QQmlJSScope::GroupedPropertyScope; - bool exists = enterEnvironmentNonUnique(scopeKind, idName, group->firstSourceLocation()); + bool exists = enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation()); + ++scopesEnteredCounter; needsResolution = needsResolution || !exists; + + prefix.clear(); } - while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope - || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) { + for (int i=0; i < scopesEnteredCounter; ++i) { // leave the scopes we entered again leaveEnvironment(); } @@ -1544,16 +1629,29 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) leaveEnvironment(); auto group = uiob->qualifiedId; + int scopesEnteredCounter = 0; + + QString prefix; for (; group->next; group = group->next) { const QString idName = group->name.toString(); if (idName.isEmpty()) break; + if (group == uiob->qualifiedId && isImportPrefix(idName)) { + prefix = idName + u'.'; + continue; + } + const auto scopeKind = idName.front().isUpper() ? QQmlJSScope::AttachedPropertyScope : QQmlJSScope::GroupedPropertyScope; // definitely exists - enterEnvironmentNonUnique(scopeKind, idName, group->firstSourceLocation()); + [[maybe_unused]] bool exists = + enterEnvironmentNonUnique(scopeKind, prefix + idName, group->firstSourceLocation()); + Q_ASSERT(exists); + scopesEnteredCounter++; + + prefix.clear(); } // on ending the visit to UiObjectBinding, set the property type to the @@ -1570,15 +1668,13 @@ void QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) uiob->firstSourceLocation(), uiob->hasOnToken }; } - while (m_currentScope->scopeType() == QQmlJSScope::GroupedPropertyScope - || m_currentScope->scopeType() == QQmlJSScope::AttachedPropertyScope) { + for (int i = 0; i < scopesEnteredCounter; ++i) leaveEnvironment(); - } } bool QQmlJSImportVisitor::visit(ExportDeclaration *) { - Q_ASSERT(!m_exportedRootScope.isNull()); + Q_ASSERT(rootScopeIsValid()); Q_ASSERT(m_exportedRootScope != m_globalScope); Q_ASSERT(m_currentScope == m_globalScope); m_currentScope = m_exportedRootScope; @@ -1587,18 +1683,17 @@ bool QQmlJSImportVisitor::visit(ExportDeclaration *) void QQmlJSImportVisitor::endVisit(ExportDeclaration *) { - Q_ASSERT(!m_exportedRootScope.isNull()); + Q_ASSERT(rootScopeIsValid()); m_currentScope = m_exportedRootScope->parentScope(); Q_ASSERT(m_currentScope == m_globalScope); } bool QQmlJSImportVisitor::visit(ESModule *module) { - enterEnvironment(QQmlJSScope::JSLexicalScope, QStringLiteral("module"), - module->firstSourceLocation()); - Q_ASSERT(m_exportedRootScope.isNull()); - m_exportedRootScope = m_currentScope; - m_exportedRootScope->setIsScript(true); + Q_ASSERT(!rootScopeIsValid()); + enterRootScope( + QQmlJSScope::JSLexicalScope, QStringLiteral("module"), module->firstSourceLocation()); + m_currentScope->setIsScript(true); importBaseModules(); leaveEnvironment(); return true; @@ -1612,9 +1707,10 @@ void QQmlJSImportVisitor::endVisit(ESModule *) bool QQmlJSImportVisitor::visit(Program *) { Q_ASSERT(m_globalScope == m_currentScope); - Q_ASSERT(m_exportedRootScope.isNull()); - m_exportedRootScope = m_currentScope; + Q_ASSERT(!rootScopeIsValid()); + *m_exportedRootScope = std::move(*QQmlJSScope::clone(m_currentScope)); m_exportedRootScope->setIsScript(true); + m_currentScope = m_exportedRootScope; importBaseModules(); return true; } |