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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | using namespace Qt::StringLiterals; |
16 | |
17 | // This makes no sense, but we want to warn about things QQmlPropertyResolver complains about. |
18 | static 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 (resolver->equals(a: from, b: to)) |
26 | return true; |
27 | |
28 | if (!resolver->canConvertFromTo(from, to)) |
29 | return false; |
30 | |
31 | const bool fromIsString = resolver->equals(a: from, b: resolver->stringType()); |
32 | |
33 | if (resolver->equals(a: to, b: resolver->stringType()) |
34 | || resolver->equals(a: to, b: resolver->stringListType()) |
35 | || resolver->equals(a: to, b: resolver->byteArrayType()) |
36 | || resolver->equals(a: to, b: resolver->urlType())) { |
37 | return fromIsString; |
38 | } |
39 | |
40 | if (resolver->isNumeric(type: to)) |
41 | return resolver->isNumeric(type: from); |
42 | |
43 | if (resolver->equals(a: to, b: resolver->boolType())) |
44 | return resolver->equals(a: from, b: resolver->boolType()); |
45 | |
46 | return true; |
47 | } |
48 | |
49 | QQmlJSLiteralBindingCheck::QQmlJSLiteralBindingCheck(QQmlSA::PassManager *passManager) |
50 | : LiteralBindingCheckBase(passManager), |
51 | m_resolver(QQmlSA::PassManagerPrivate::resolver(*passManager)) |
52 | { |
53 | } |
54 | |
55 | QQmlJSStructuredTypeError QQmlJSLiteralBindingCheck::check(const QString &typeName, |
56 | const QString &value) const |
57 | { |
58 | return QQmlJSValueTypeFromStringCheck::hasError(typeName, value); |
59 | } |
60 | |
61 | static 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 | |
80 | QQmlSA::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 | |
96 | |
97 | void LiteralBindingCheckBase::onBinding(const QQmlSA::Element &element, const QString &propertyName, |
98 | const QQmlSA::Binding &binding, |
99 | const QQmlSA::Element &bindingScope, |
100 | const QQmlSA::Element &value) |
101 | { |
102 | Q_UNUSED(value); |
103 | |
104 | const auto property = getProperty(propertyName, binding, bindingScope); |
105 | if (!property.isValid()) |
106 | return; |
107 | |
108 | // If the property is defined in the same scope where it is set, |
109 | // we are in fact allowed to set it, even if it's not writable. |
110 | if (property.isReadonly() && !element.hasOwnProperty(propertyName)) { |
111 | emitWarning(diagnostic: u"Cannot assign to read-only property %1"_s .arg(a: propertyName), |
112 | id: qmlReadOnlyProperty, srcLocation: binding.sourceLocation()); |
113 | return; |
114 | } |
115 | if (auto propertyType = property.type(); propertyType) { |
116 | auto construction = check(typeName: propertyType.internalId(), value: binding.stringValue()); |
117 | if (construction.isValid()) { |
118 | const QString warningMessage = |
119 | u"Construction from string is deprecated. Use structured value type " |
120 | u"construction instead for type \"%1\""_s .arg(a: propertyType.internalId()); |
121 | |
122 | if (!construction.code.isNull()) { |
123 | QQmlSA::FixSuggestion suggestion( |
124 | u"Replace string by structured value construction"_s , |
125 | binding.sourceLocation(), construction.code); |
126 | emitWarning(diagnostic: warningMessage, id: qmlIncompatibleType, srcLocation: binding.sourceLocation(), fix: suggestion); |
127 | return; |
128 | } |
129 | emitWarning(diagnostic: warningMessage, id: qmlIncompatibleType, srcLocation: binding.sourceLocation()); |
130 | return; |
131 | } |
132 | } |
133 | |
134 | } |
135 | |
136 | void QQmlJSLiteralBindingCheck::onBinding(const QQmlSA::Element &element, |
137 | const QString &propertyName, |
138 | const QQmlSA::Binding &binding, |
139 | const QQmlSA::Element &bindingScope, |
140 | const QQmlSA::Element &value) |
141 | { |
142 | LiteralBindingCheckBase::onBinding(element, propertyName, binding, bindingScope, value); |
143 | |
144 | const auto property = getProperty(propertyName, binding, bindingScope); |
145 | if (!property.isValid()) |
146 | return; |
147 | |
148 | if (!canConvertForLiteralBinding(resolver: m_resolver, fromElement: resolveLiteralType(binding), toElement: property.type())) { |
149 | emitWarning(diagnostic: u"Cannot assign literal of type %1 to %2"_s .arg( |
150 | args: literalPrettyTypeName(type: binding.bindingType()), |
151 | args: QQmlJSScope::prettyName(name: property.typeName())), |
152 | id: qmlIncompatibleType, srcLocation: binding.sourceLocation()); |
153 | return; |
154 | } |
155 | } |
156 | |
157 | QT_END_NAMESPACE |
158 | |