aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsimportvisitor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljsimportvisitor.cpp')
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp278
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 &parameter : 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;
}