diff options
author | Ulf Hermann <[email protected]> | 2025-01-16 18:32:24 +0100 |
---|---|---|
committer | Ulf Hermann <[email protected]> | 2025-01-17 14:13:55 +0100 |
commit | 746f38bf56027c29a72bd8a3c3a5f9aad73b9bd3 (patch) | |
tree | c1f779a136b03784e17a7518c74b22fa511fcbdf | |
parent | c159f4e837d99c8cb4b260e7bbc390cdb83d5e19 (diff) |
QmlCompiler: Reduce duplication of warnings about missing properties
If a property cannot be found, we don't need to check its type or even
try to compile a binding for it. We also shouldn't claim a property
doesn't exist if we cannot find it. We may have failed to resolve the
type after all.
Pick-to: 6.9 6.8
Task-number: QTBUG-124913
Change-Id: I93ef12e888762ae03f8fa6b1bef2e8d04ba3d4b2
Reviewed-by: Sami Shalayel <[email protected]>
-rw-r--r-- | src/qmlcompiler/qqmljscompiler.cpp | 9 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsfunctioninitializer.cpp | 103 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 15 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor_p.h | 3 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljslintercodegen.cpp | 12 | ||||
-rw-r--r-- | tests/auto/qml/qmllint/tst_qmllint.cpp | 40 |
6 files changed, 104 insertions, 78 deletions
diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index 8143f588ae..30c869c439 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -674,8 +674,13 @@ std::variant<QQmlJSAotFunction, QList<QQmlJS::DiagnosticMessage>> QQmlJSAotCompi const QString name = m_document->stringAt(irBinding.propertyNameIndex); QQmlJSCompilePass::Function function = initializer.run( context, name, astNode, irBinding, &errors); - const QQmlJSAotFunction aotFunction = doCompileAndRecordAotStats( - context, &function, &errors, name, astNode->firstSourceLocation()); + + QQmlJSAotFunction aotFunction; + if (errors.isEmpty()) { + aotFunction = doCompileAndRecordAotStats( + context, &function, &errors, name, astNode->firstSourceLocation()); + } + if (!errors.isEmpty()) { for (auto &error : errors) { diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp index ffd6bf7308..95bd243f0b 100644 --- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp +++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp @@ -168,71 +168,64 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( } function.isProperty = m_objectType->hasProperty(propertyName); - if (!function.isProperty) { - if (QQmlSignalNames::isHandlerName(propertyName)) { - if (auto actualPropertyName = - QQmlSignalNames::changedHandlerNameToPropertyName(propertyName); - actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) { - function.isSignalHandler = true; - } else { - auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); - const auto methods = m_objectType->methods(*signalName); - for (const auto &method : methods) { - if (method.isCloned()) - continue; - if (method.methodType() == QQmlJSMetaMethodType::Signal) { - function.isSignalHandler = true; - const auto arguments = method.parameters(); - for (qsizetype i = 0, end = arguments.size(); i < end; ++i) { - const auto &type = arguments[i].type(); - if (type.isNull()) { - diagnose(u"Cannot resolve the argument type %1."_s.arg( - arguments[i].typeName()), - QtDebugMsg, bindingLocation, errors); - function.argumentTypes.append( - m_typeResolver->namedType(m_typeResolver->varType())); - } else { - function.argumentTypes.append(m_typeResolver->namedType(type)); - } - } - break; - } - } - if (!function.isSignalHandler) { - diagnose(u"Could not compile signal handler for %1: The signal does not exist"_s.arg( - *signalName), - QtWarningMsg, bindingLocation, errors); - } - } - } - } - - if (!function.isSignalHandler) { - if (!function.isProperty) { - diagnose(u"Could not compile binding for %1: The property does not exist"_s.arg( - propertyName), - QtWarningMsg, bindingLocation, errors); - } - + if (function.isProperty) { const auto property = m_objectType->property(propertyName); if (const QQmlJSScope::ConstPtr propertyType = property.type()) { function.returnType = m_typeResolver->namedType(propertyType->isListProperty() ? m_typeResolver->qObjectListType() : QQmlJSScope::ConstPtr(property.type())); } else { - QString message = u"Cannot resolve property type %1 for binding on %2."_s - .arg(property.typeName(), propertyName); - if (m_objectType->isNameDeferred(propertyName)) { - // If the property doesn't exist but the name is deferred, then - // it's deferred via the presence of immediate names. Those are - // most commonly used to enable generalized grouped properties. - message += u" You may want use ID-based grouped properties here."; - } - diagnose(message, QtWarningMsg, bindingLocation, errors); + diagnose(u"Cannot resolve property type %1 for binding on %2."_s + .arg(property.typeName(), propertyName), + QtWarningMsg, bindingLocation, errors); } if (!property.bindable().isEmpty() && !property.isPrivate()) function.isQPropertyBinding = true; + } else if (QQmlSignalNames::isHandlerName(propertyName)) { + if (auto actualPropertyName = + QQmlSignalNames::changedHandlerNameToPropertyName(propertyName); + actualPropertyName && m_objectType->hasProperty(*actualPropertyName)) { + function.isSignalHandler = true; + } else { + auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); + const auto methods = m_objectType->methods(*signalName); + for (const auto &method : methods) { + if (method.isCloned()) + continue; + if (method.methodType() == QQmlJSMetaMethodType::Signal) { + function.isSignalHandler = true; + const auto arguments = method.parameters(); + for (qsizetype i = 0, end = arguments.size(); i < end; ++i) { + const auto &type = arguments[i].type(); + if (type.isNull()) { + diagnose(u"Cannot resolve the argument type %1."_s.arg( + arguments[i].typeName()), + QtDebugMsg, bindingLocation, errors); + function.argumentTypes.append( + m_typeResolver->namedType(m_typeResolver->varType())); + } else { + function.argumentTypes.append(m_typeResolver->namedType(type)); + } + } + break; + } + } + if (!function.isSignalHandler) { + diagnose(u"Could not find signal \"%1\"."_s.arg(*signalName), + QtWarningMsg, bindingLocation, errors); + } + } + } else { + QString message = u"Could not find property \"%1\"."_s.arg(propertyName); + if (m_objectType->isNameDeferred(propertyName)) { + // If the property doesn't exist but the name is deferred, then + // it's deferred via the presence of immediate names. Those are + // most commonly used to enable generalized grouped properties. + message += u" You may want use ID-based grouped properties here."; + } + + diagnose(message, QtWarningMsg, bindingLocation, errors); } QQmlJS::MemoryPool pool; diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 62c08d2bfb..9db0e9af69 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -222,6 +222,14 @@ void QQmlJSImportVisitor::warnUnresolvedType(const QQmlJSScope::ConstPtr &type) qmlUnresolvedType, type->sourceLocation()); } +void QQmlJSImportVisitor::warnMissingPropertyForBinding( + const QString &property, const QQmlJS::SourceLocation &location, + const std::optional<QQmlJSFixSuggestion> &fixSuggestion) +{ + m_logger->log(QStringLiteral("Could not find property \"%1\".").arg(property), + qmlMissingProperty, location, true, true, fixSuggestion); +} + static bool mayBeUnresolvedGroupedProperty(const QQmlJSScope::ConstPtr &scope) { return scope->scopeType() == QQmlSA::ScopeType::GroupedPropertyScope && !scope->baseType(); @@ -753,8 +761,7 @@ void QQmlJSImportVisitor::processPropertyBindingObjects() QQmlJSMetaProperty property = objectBinding.scope->property(propertyName); if (!property.isValid()) { - m_logger->log(QStringLiteral("Property \"%1\" does not exist").arg(propertyName), - qmlMissingProperty, objectBinding.location); + warnMissingPropertyForBinding(propertyName, objectBinding.location); continue; } const auto handleUnresolvedProperty = [&](const QQmlJSScope::ConstPtr &) { @@ -976,9 +983,7 @@ void QQmlJSImportVisitor::processPropertyBindings() } } - m_logger->log(QStringLiteral("Property \"%1\" does not exist.") - .arg(name), - qmlMissingProperty, location, true, true, fixSuggestion); + warnMissingPropertyForBinding(name, location, fixSuggestion); continue; } diff --git a/src/qmlcompiler/qqmljsimportvisitor_p.h b/src/qmlcompiler/qqmljsimportvisitor_p.h index 02c8109d75..b9abf6ce7a 100644 --- a/src/qmlcompiler/qqmljsimportvisitor_p.h +++ b/src/qmlcompiler/qqmljsimportvisitor_p.h @@ -259,6 +259,9 @@ protected: } void warnUnresolvedType(const QQmlJSScope::ConstPtr &type) const; + void warnMissingPropertyForBinding( + const QString &property, const QQmlJS::SourceLocation &location, + const std::optional<QQmlJSFixSuggestion> &fixSuggestion = {}); QVector<QQmlJSAnnotation> parseAnnotations(QQmlJS::AST::UiAnnotationList *list); void setAllBindings(); diff --git a/src/qmlcompiler/qqmljslintercodegen.cpp b/src/qmlcompiler/qqmljslintercodegen.cpp index e33fe7139b..0717769a91 100644 --- a/src/qmlcompiler/qqmljslintercodegen.cpp +++ b/src/qmlcompiler/qqmljslintercodegen.cpp @@ -41,8 +41,10 @@ QQmlJSLinterCodegen::compileBinding(const QV4::Compiler::Context *context, const QString name = m_document->stringAt(irBinding.propertyNameIndex); QQmlJSCompilePass::Function function = initializer.run(context, name, astNode, irBinding, &initializationErrors); - for (const auto &error : initializationErrors) - diagnose(error.message, error.type, error.loc); + for (const auto &error : initializationErrors) { + diagnose(u"Could not determine signature of binding for %1: %2"_s.arg(name, error.message), + error.type, error.loc); + } QList<QQmlJS::DiagnosticMessage> analyzeErrors; if (!analyzeFunction(context, &function, &analyzeErrors)) { @@ -70,8 +72,10 @@ QQmlJSLinterCodegen::compileFunction(const QV4::Compiler::Context *context, &m_typeResolver, m_currentObject->location, m_currentScope->location); QQmlJSCompilePass::Function function = initializer.run(context, name, astNode, &initializationErrors); - for (const auto &error : initializationErrors) - diagnose(error.message, error.type, error.loc); + for (const auto &error : initializationErrors) { + diagnose(u"Could not determine signature of function %1: %2"_s.arg(name, error.message), + error.type, error.loc); + } QList<QQmlJS::DiagnosticMessage> analyzeErrors; if (!analyzeFunction(context, &function, &analyzeErrors)) { diff --git a/tests/auto/qml/qmllint/tst_qmllint.cpp b/tests/auto/qml/qmllint/tst_qmllint.cpp index 5c64d293e8..3256d7b423 100644 --- a/tests/auto/qml/qmllint/tst_qmllint.cpp +++ b/tests/auto/qml/qmllint/tst_qmllint.cpp @@ -621,7 +621,7 @@ void TestQmllint::dirtyQmlCode_data() "unknown attached property scope WrongAttached.") } } }; QTest::newRow("BadBinding") << QStringLiteral("badBinding.qml") << Result{ { Message{ QStringLiteral( - "Property \"doesNotExist\" does not exist.") } } }; + "Could not find property \"doesNotExist\".") } } }; QTest::newRow("bad template literal (simple)") << QStringLiteral("badTemplateStringSimple.qml") << Result { { Message { @@ -641,11 +641,11 @@ void TestQmllint::dirtyQmlCode_data() QStringLiteral("Cannot assign literal of type string to int") } } }; QTest::newRow("BadScriptBindingOnGroup") << QStringLiteral("badScriptBinding.group.qml") - << Result{ { Message{ QStringLiteral("Property \"bogusProperty\" does not exist."), 3, + << Result{ { Message{ QStringLiteral("Could not find property \"bogusProperty\"."), 3, 10 } } }; QTest::newRow("BadScriptBindingOnAttachedType") << QStringLiteral("badScriptBinding.attached.qml") - << Result{ { Message{ QStringLiteral("Property \"bogusProperty\" does not exist."), 5, + << Result{ { Message{ QStringLiteral("Could not find property \"bogusProperty\"."), 5, 12 } } }; QTest::newRow("BadScriptBindingOnAttachedSignalHandler") << QStringLiteral("badScriptBinding.attachedSignalHandler.qml") @@ -844,7 +844,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", "Cannot combine value source and binding on property \"objs\"") } } }; QTest::newRow("NonExistentListProperty") << QStringLiteral("nonExistentListProperty.qml") - << Result { { Message { QStringLiteral("Property \"objs\" does not exist") } } }; + << Result { { Message { QStringLiteral("Could not find property \"objs\".") } } }; QTest::newRow("QtQuick.Window 2.0") << QStringLiteral("qtquickWindow20.qml") << Result { { Message { QStringLiteral( @@ -853,7 +853,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", << QStringLiteral("unresolvedAttachedType.qml") << Result { { Message { QStringLiteral( "unknown attached property scope UnresolvedAttachedType.") } }, - { Message { QStringLiteral("Property \"property\" does not exist.") } } }; + { Message { QStringLiteral("Could not find property \"property\".") } } }; QTest::newRow("nestedInlineComponents") << QStringLiteral("nestedInlineComponents.qml") << Result { { Message { @@ -906,7 +906,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", "MenuItem is part of an inheritance cycle: MenuItem -> MenuItem") } } }; QTest::newRow("badGeneralizedGroup1") << QStringLiteral("badGeneralizedGroup1.qml") - << Result{ { Message{ QStringLiteral("Property \"aaaa\" does not exist.") } } }; + << Result{ { Message{ QStringLiteral("Could not find property \"aaaa\".") } } }; QTest::newRow("badGeneralizedGroup2") << QStringLiteral("badGeneralizedGroup2.qml") << Result { { Message { QStringLiteral("unknown grouped property scope aself") } } }; @@ -971,7 +971,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)", "no matching signal found for handler \"onWannabeSignal\"") } } }; QTest::newRow("didYouMean(binding)") << QStringLiteral("didYouMeanBinding.qml") - << Result{ { Message{ QStringLiteral("Property \"witdh\" does not exist.") } }, + << Result{ { Message{ QStringLiteral("Could not find property \"witdh\".") } }, {}, { Message{ QStringLiteral("width") } } }; QTest::newRow("didYouMean(unqualified)") @@ -1465,32 +1465,38 @@ void TestQmllint::compilerWarnings_data() << true; QTest::newRow("tooFewParameters") << QStringLiteral("tooFewParams.qml") - << Result { { Message { QStringLiteral("No matching override found") } } } << true; + << Result { { Message { QStringLiteral("Could not compile binding for a: " + "No matching override found") } } } << true; QTest::newRow("javascriptVariableArgs") << QStringLiteral("javascriptVariableArgs.qml") << Result { { Message { - QStringLiteral("Function expects 0 arguments, but 2 were provided") } } } + QStringLiteral("Could not compile binding for onCompleted: " + "Function expects 0 arguments, but 2 were provided") } } } << true; QTest::newRow("unknownTypeInRegister") << QStringLiteral("unknownTypeInRegister.qml") << Result { { Message { - QStringLiteral("Functions without type annotations won't be compiled") } } } + QStringLiteral("Could not determine signature of function foo: " + "Functions without type annotations won't be compiled") } } } << true; QTest::newRow("pragmaStrict") << QStringLiteral("pragmaStrict.qml") << Result { { { QStringLiteral( + "Could not determine signature of function add: " "Functions without type annotations won't be compiled") } } } << true; QTest::newRow("generalizedGroupHint") << QStringLiteral("generalizedGroupHint.qml") << Result { { { QStringLiteral( - "Cannot resolve property type for binding on myColor. " + "Could not determine signature of binding for myColor: " + "Could not find property \"myColor\". " "You may want use ID-based grouped properties here.") } } } << true; QTest::newRow("invalidIdLookup") << QStringLiteral("invalidIdLookup.qml") << Result { { { - QStringLiteral("Cannot retrieve a non-object type by ID: stateMachine") + QStringLiteral("Could not compile binding for objectName: " + "Cannot retrieve a non-object type by ID: stateMachine") } } } << true; QTest::newRow("returnTypeAnnotation-component") @@ -1531,6 +1537,16 @@ void TestQmllint::compilerWarnings_data() << QStringLiteral("functionAssign1.qml") << Result::clean() << true; QTest::newRow("functionAssign2") << QStringLiteral("functionAssign2.qml") << Result::clean() << true; + + // We want to see the warning about the missing property only once. + QTest::newRow("unresolvedType2") + << QStringLiteral("unresolvedType2.qml") + << Result { { Message { QStringLiteral( + "Could not determine signature of binding for text: " + "Could not find property \"text\".") } }, + { Message { QStringLiteral( + "Cannot resolve property type for binding on text.") }, }, + } << true; } void TestQmllint::compilerWarnings() |