aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljsliteralbindingcheck.cpp')
-rw-r--r--src/qmlcompiler/qqmljsliteralbindingcheck.cpp158
1 files changed, 158 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljsliteralbindingcheck.cpp b/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
new file mode 100644
index 0000000000..af1fb9104f
--- /dev/null
+++ b/src/qmlcompiler/qqmljsliteralbindingcheck.cpp
@@ -0,0 +1,158 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "qqmljsliteralbindingcheck_p.h"
+
+#include <private/qqmljsimportvisitor_p.h>
+#include <private/qqmljstyperesolver_p.h>
+#include <private/qqmljsmetatypes_p.h>
+#include <private/qqmlsa_p.h>
+#include <private/qqmlsasourcelocation_p.h>
+#include <private/qqmlstringconverters_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+// This makes no sense, but we want to warn about things QQmlPropertyResolver complains about.
+static bool canConvertForLiteralBinding(QQmlJSTypeResolver *resolver,
+ const QQmlSA::Element &fromElement,
+ const QQmlSA::Element &toElement)
+{
+ Q_ASSERT(resolver);
+ auto from = QQmlJSScope::scope(fromElement);
+ auto to = QQmlJSScope::scope(toElement);
+ if (resolver->equals(from, to))
+ return true;
+
+ if (!resolver->canConvertFromTo(from, to))
+ return false;
+
+ const bool fromIsString = resolver->equals(from, resolver->stringType());
+
+ if (resolver->equals(to, resolver->stringType())
+ || resolver->equals(to, resolver->stringListType())
+ || resolver->equals(to, resolver->byteArrayType())
+ || resolver->equals(to, resolver->urlType())) {
+ return fromIsString;
+ }
+
+ if (resolver->isNumeric(to))
+ return resolver->isNumeric(from);
+
+ if (resolver->equals(to, resolver->boolType()))
+ return resolver->equals(from, resolver->boolType());
+
+ return true;
+}
+
+QQmlJSLiteralBindingCheck::QQmlJSLiteralBindingCheck(QQmlSA::PassManager *passManager)
+ : LiteralBindingCheckBase(passManager),
+ m_resolver(QQmlSA::PassManagerPrivate::resolver(*passManager))
+{
+}
+
+QQmlJSStructuredTypeError QQmlJSLiteralBindingCheck::check(const QString &typeName,
+ const QString &value) const
+{
+ return QQmlJSValueTypeFromStringCheck::hasError(typeName, value);
+}
+
+static QString literalPrettyTypeName(QQmlSA::BindingType type)
+{
+ switch (type) {
+ case QQmlSA::BindingType::BoolLiteral:
+ return u"bool"_s;
+ case QQmlSA::BindingType::NumberLiteral:
+ return u"double"_s;
+ case QQmlSA::BindingType::StringLiteral:
+ return u"string"_s;
+ case QQmlSA::BindingType::RegExpLiteral:
+ return u"regexp"_s;
+ case QQmlSA::BindingType::Null:
+ return u"null"_s;
+ default:
+ return QString();
+ }
+ Q_UNREACHABLE_RETURN(QString());
+}
+
+QQmlSA::Property LiteralBindingCheckBase::getProperty(const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope) const
+{
+ if (!QQmlSA::Binding::isLiteralBinding(binding.bindingType()))
+ return {};
+
+ const QString unqualifiedPropertyName = [&propertyName]() -> QString {
+ if (auto idx = propertyName.lastIndexOf(u'.'); idx != -1 && idx != propertyName.size() - 1)
+ return propertyName.sliced(idx + 1);
+ return propertyName;
+ }();
+
+ return bindingScope.property(unqualifiedPropertyName);
+}
+
+
+void LiteralBindingCheckBase::onBinding(const QQmlSA::Element &element, const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value)
+{
+ Q_UNUSED(value);
+
+ const auto property = getProperty(propertyName, binding, bindingScope);
+ if (!property.isValid())
+ return;
+
+ // If the property is defined in the same scope where it is set,
+ // we are in fact allowed to set it, even if it's not writable.
+ if (property.isReadonly() && !element.hasOwnProperty(propertyName)) {
+ emitWarning(u"Cannot assign to read-only property %1"_s.arg(propertyName),
+ qmlReadOnlyProperty, binding.sourceLocation());
+ return;
+ }
+ if (auto propertyType = property.type(); propertyType) {
+ auto construction = check(propertyType.internalId(), binding.stringValue());
+ if (construction.isValid()) {
+ const QString warningMessage =
+ u"Binding is not supported: Type %1 should be constructed using"
+ u" QML_STRUCTURED_VALUE's construction mechanism, instead of a "
+ u"string."_s.arg(propertyType.internalId());
+
+ if (!construction.code.isNull()) {
+ QQmlSA::FixSuggestion suggestion(
+ u"Replace string by structured value construction"_s,
+ binding.sourceLocation(), construction.code);
+ emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation(), suggestion);
+ return;
+ }
+ emitWarning(warningMessage, qmlIncompatibleType, binding.sourceLocation());
+ return;
+ }
+ }
+
+}
+
+void QQmlJSLiteralBindingCheck::onBinding(const QQmlSA::Element &element,
+ const QString &propertyName,
+ const QQmlSA::Binding &binding,
+ const QQmlSA::Element &bindingScope,
+ const QQmlSA::Element &value)
+{
+ LiteralBindingCheckBase::onBinding(element, propertyName, binding, bindingScope, value);
+
+ const auto property = getProperty(propertyName, binding, bindingScope);
+ if (!property.isValid())
+ return;
+
+ if (!canConvertForLiteralBinding(m_resolver, resolveLiteralType(binding), property.type())) {
+ emitWarning(u"Cannot assign literal of type %1 to %2"_s.arg(
+ literalPrettyTypeName(binding.bindingType()),
+ QQmlJSScope::prettyName(property.typeName())),
+ qmlIncompatibleType, binding.sourceLocation());
+ return;
+ }
+}
+
+QT_END_NAMESPACE