1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qqmljsliteralbindingcheck_p.h"
5
6#include <private/qqmljsimportvisitor_p.h>
7#include <private/qqmljstyperesolver_p.h>
8#include <private/qqmljsmetatypes_p.h>
9#include <private/qqmlsa_p.h>
10#include <private/qqmlsasourcelocation_p.h>
11#include <private/qqmlstringconverters_p.h>
12
13QT_BEGIN_NAMESPACE
14
15using namespace Qt::StringLiterals;
16
17// This makes no sense, but we want to warn about things QQmlPropertyResolver complains about.
18static bool canConvertForLiteralBinding(QQmlJSTypeResolver *resolver,
19 const QQmlSA::Element &fromElement,
20 const QQmlSA::Element &toElement)
21{
22 Q_ASSERT(resolver);
23 auto from = QQmlJSScope::scope(fromElement);
24 auto to = QQmlJSScope::scope(toElement);
25 if (from == to)
26 return true;
27
28 if (!resolver->canConvertFromTo(from, to))
29 return false;
30
31 const bool fromIsString = from == resolver->stringType();
32
33 if (to == resolver->stringType()
34 || to == resolver->stringListType()
35 || to == resolver->byteArrayType()
36 || to == resolver->urlType()) {
37 return fromIsString;
38 }
39
40 if (resolver->isNumeric(type: to))
41 return resolver->isNumeric(type: from);
42
43 if (to == resolver->boolType())
44 return from == resolver->boolType();
45
46 return true;
47}
48
49QQmlJSLiteralBindingCheck::QQmlJSLiteralBindingCheck(QQmlSA::PassManager *passManager)
50 : LiteralBindingCheckBase(passManager),
51 m_resolver(QQmlSA::PassManagerPrivate::resolver(*passManager))
52{
53}
54
55QQmlJSStructuredTypeError QQmlJSLiteralBindingCheck::check(const QString &typeName,
56 const QString &value) const
57{
58 return QQmlJSValueTypeFromStringCheck::hasError(typeName, value);
59}
60
61static QString literalPrettyTypeName(QQmlSA::BindingType type)
62{
63 switch (type) {
64 case QQmlSA::BindingType::BoolLiteral:
65 return u"bool"_s;
66 case QQmlSA::BindingType::NumberLiteral:
67 return u"double"_s;
68 case QQmlSA::BindingType::StringLiteral:
69 return u"string"_s;
70 case QQmlSA::BindingType::RegExpLiteral:
71 return u"regexp"_s;
72 case QQmlSA::BindingType::Null:
73 return u"null"_s;
74 default:
75 return QString();
76 }
77 Q_UNREACHABLE_RETURN(QString());
78}
79
80QQmlSA::Property LiteralBindingCheckBase::getProperty(const QString &propertyName,
81 const QQmlSA::Binding &binding,
82 const QQmlSA::Element &bindingScope) const
83{
84 if (!QQmlSA::Binding::isLiteralBinding(binding.bindingType()))
85 return {};
86
87 const QString unqualifiedPropertyName = [&propertyName]() -> QString {
88 if (auto idx = propertyName.lastIndexOf(c: u'.'); idx != -1 && idx != propertyName.size() - 1)
89 return propertyName.sliced(pos: idx + 1);
90 return propertyName;
91 }();
92
93 return bindingScope.property(propertyName: unqualifiedPropertyName);
94}
95
96void LiteralBindingCheckBase::warnOnCheckedBinding(
97 const QQmlSA::Binding &binding, const QQmlSA::Element &propertyType)
98{
99 auto construction = check(typeName: propertyType.internalId(), value: binding.stringValue());
100 if (!construction.isValid())
101 return;
102
103 const QString warningMessage =
104 u"Construction from string is deprecated. Use structured value type "
105 u"construction instead for type \"%1\""_s.arg(a: propertyType.internalId());
106
107 if (construction.code.isEmpty()) {
108 emitWarning(diagnostic: warningMessage, id: qmlIncompatibleType, srcLocation: binding.sourceLocation());
109 return;
110 }
111
112 const QQmlSA::FixSuggestion suggestion(
113 u"Replace string by structured value construction"_s,
114 binding.sourceLocation(), construction.code);
115 emitWarning(diagnostic: warningMessage, id: qmlIncompatibleType, srcLocation: binding.sourceLocation(), fix: suggestion);
116}
117
118void QQmlJSLiteralBindingCheck::onBinding(const QQmlSA::Element &element,
119 const QString &propertyName,
120 const QQmlSA::Binding &binding,
121 const QQmlSA::Element &bindingScope,
122 const QQmlSA::Element &value)
123{
124 Q_UNUSED(value);
125
126 const auto property = getProperty(propertyName, binding, bindingScope);
127 if (!property.isValid())
128 return;
129
130 // If the property is defined in the same scope where it is set,
131 // we are in fact allowed to set it, even if it's not writable.
132 if (property.isReadonly() && !element.hasOwnProperty(propertyName)) {
133 emitWarning(diagnostic: u"Cannot assign to read-only property %1"_s.arg(a: propertyName),
134 id: qmlReadOnlyProperty, srcLocation: binding.sourceLocation());
135 return;
136 }
137
138 if (const auto propertyType = property.type())
139 warnOnCheckedBinding(binding, propertyType);
140
141 if (!canConvertForLiteralBinding(resolver: m_resolver, fromElement: resolveLiteralType(binding), toElement: property.type())) {
142 emitWarning(diagnostic: u"Cannot assign literal of type %1 to %2"_s.arg(
143 args: literalPrettyTypeName(type: binding.bindingType()),
144 args: QQmlJSScope::prettyName(name: property.typeName())),
145 id: qmlIncompatibleType, srcLocation: binding.sourceLocation());
146 return;
147 }
148}
149
150QT_END_NAMESPACE
151

source code of qtdeclarative/src/qmlcompiler/qqmljsliteralbindingcheck.cpp