Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmljslintertypepropagator.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3// Qt-Security score:significant
4
6
7#include <private/qqmljsutils_p.h>
8
9#include <private/qqmljslintercodegen_p.h>
10
12
13using namespace Qt::StringLiterals;
14
16 const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver,
17 QQmlJSLogger *logger, const QQmlJS::LinterContext &context, const BasicBlocks &basicBlocks,
18 const InstructionAnnotations &annotations, QQmlSA::PassManager *passManager)
22{
23}
24
26{
27 QQmlJSTypePropagator::generate_Ret();
28
29 const auto accumulatorIn = m_state.accumulatorIn();
30 if (m_function->isSignalHandler) {
31 // Signal handlers cannot return anything.
32 } else if (accumulatorIn.contains(m_typeResolver->voidType())) {
33 // You can always return undefined.
34 } else if (!m_returnType.isValid() && accumulatorIn.isValid()) {
35 if (m_function->isFullyTyped) {
36 // Do not complain if the function didn't have a valid annotation in the first place.
37 m_logger->log(u"Function without return type annotation returns %1"_s.arg(
38 m_state.accumulatorIn().containedTypeName()),
39 qmlIncompatibleType, currentFunctionSourceLocation());
40 }
41 } else if (!canConvertFromTo(accumulatorIn, m_returnType)) {
42 if (checkTypeResolved(accumulatorIn.containedType())) {
43 m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
44 m_state.accumulatorIn().containedTypeName(),
45 m_returnType.containedTypeName()),
46 qmlIncompatibleType, currentFunctionSourceLocation());
47 }
48 }
49
50 const QQmlJS::SourceLocation location = m_function->isProperty
51 ? currentFunctionSourceLocation()
52 : currentNonEmptySourceLocation();
53 QQmlSA::PassManagerPrivate::get(m_passManager)
54 ->analyzeBinding(
55 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
56 QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
57 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
58}
59
61{
62 QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
63
64 Q_ASSERT(m_idMemberShadows);
65
66 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
67 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
68
69 const auto qmlScope = m_function->qmlScope.containedType();
70 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
71 QQmlJSScope::createQQmlSAElement(qmlScope), name,
72 QQmlJSScope::createQQmlSAElement(qmlScope),
73 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
74 currentNonEmptySourceLocation()));
75
76 const auto &accumulatorOut = m_state.accumulatorOut();
77 if (!accumulatorOut.isValid())
78 return;
79
80 // complain about renamed types in enums like MyType.Enum.EnumValue
81 if (accumulatorOut.variant() == QQmlJSRegisterContent::Attachment
82 || accumulatorOut.variant() == QQmlJSRegisterContent::MetaType) {
83 m_context.renamedComponents.handleRenamedType(accumulatorOut.scopeType(), name,
84 currentNonEmptySourceLocation(), m_logger);
85 }
86
87 const QQmlJSScope::ConstPtr scope = accumulatorOut.scopeType();
88 const QQmlJSScope::ConstPtr idScope = m_context.scopesById.scope(name, scope);
89 if (!idScope.isNull()) {
90 const auto log = [&](const auto &memberType, const auto &memberOwnerScope) {
91 IdMemberShadow idMemberShadow{ name, idScope, memberOwnerScope };
92
93 // Only warn once per shadowing instance, even for multiple usages.
94 if (m_idMemberShadows->contains(idMemberShadow))
95 return;
96
97 m_idMemberShadows->insert(std::move(idMemberShadow));
98 const auto useLoc = currentSourceLocation();
99 m_logger->log("Id for object %1 shadows %2 \"%3\". Rename one or the other."_L1
100 .arg(idScope->baseTypeName(), memberType, name),
101 qmlIdShadowsMember, useLoc);
102 m_logger->log("Note: Id defined here"_L1, qmlIdShadowsMember,
103 idScope->idSourceLocation(), true, true, {}, useLoc.startLine);
104 };
105
106 if (scope->hasProperty(name)) {
107 log("property"_L1, scope->ownerOfProperty(scope, name).scope);
108 } else if (scope->hasMethod(name)) {
109 const auto methods = scope->methods(name);
110 const auto &method = methods[0];
111 if (method.methodType() == QQmlSA::MethodType::Method)
112 log("method"_L1, scope->ownerOfMethod(scope, name).scope);
113 else if (method.methodType() == QQmlSA::MethodType::Signal)
114 log("signal"_L1, scope->ownerOfMethod(scope, name).scope);
115 }
116 }
117}
118
120{
121 QQmlJSTypePropagator::generate_GetOptionalLookup(index, offset);
122
123 auto suggMsg = "Consider using non-optional chaining instead: '?.' -> '.'"_L1;
124 auto suggestion = std::make_optional(QQmlJSFixSuggestion(suggMsg, currentSourceLocation()));
125 if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Enum) {
126 m_logger->log("Redundant optional chaining for enum lookup"_L1, qmlRedundantOptionalChaining,
127 currentSourceLocation(), true, true, suggestion);
128 } else if (!m_state.accumulatorIn().containedType()->isReferenceType()
129 && !m_typeResolver->canHoldUndefined(m_state.accumulatorIn())) {
130 auto baseType = m_state.accumulatorIn().containedTypeName();
131 m_logger->log("Redundant optional chaining for lookup on non-voidable and non-nullable "_L1
132 "type %1"_L1.arg(baseType), qmlRedundantOptionalChaining,
133 currentSourceLocation(), true, true, suggestion);
134 }
135}
136
137void QQmlJSLinterTypePropagator::checkWrite(const QQmlJSRegisterContent &callBase,
138 const QString &propertyName)
139{
140 if (m_typeResolver->memberType(callBase, propertyName).isProperty())
141 return;
142 auto containedType = callBase.containedType();
143 if (!containedType || !checkTypeResolved(containedType))
144 return;
145 const QString internalName = callBase.containedType()->internalName();
146 if (internalName == "QJSValue"_L1 || internalName == "QVariant"_L1)
147 return;
148
149 QString typeName = callBase.descriptiveName();
150 if (!typeName.isEmpty())
151 typeName.prepend(u' ');
152 m_logger->log(u"Member \"%1\" not found on type%2"_s.arg(propertyName, typeName),
153 qmlMissingProperty, currentSourceLocation());
154}
155
157{
158 QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
159
160 auto callBase = m_state.registers[base].content;
161 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
162 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
163
164 checkWrite(callBase, propertyName);
165
166 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
167 QQmlJSScope::createQQmlSAElement(callBase.containedType()),
168 propertyName,
169 QQmlJSScope::createQQmlSAElement(
170 m_state.accumulatorIn().containedType()),
171 QQmlJSScope::createQQmlSAElement(isAttached
172 ? callBase.attachee().containedType()
173 : m_function->qmlScope.containedType()),
174 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
175 currentNonEmptySourceLocation()));
176}
177
178void QQmlJSLinterTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
179{
180 QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
181
182 const auto saCheck = [&](const QString &propertyName, const QQmlJSScope::ConstPtr &baseType) {
183 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(baseType) };
184 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
185 m_function->qmlScope.containedType()) };
186 const QQmlSA::SourceLocation saLocation{
187 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
188 };
189
190 QQmlSA::PassManagerPrivate::get(m_passManager)
191 ->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
192 QQmlSA::PassManagerPrivate::get(m_passManager)
193 ->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
194 };
195
196 const auto callBase = m_state.registers[base].content;
197 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
198 const auto member = m_typeResolver->memberType(callBase, propertyName);
199
200 const bool isLoggingMethod = QQmlJSTypePropagator::isLoggingMethod(propertyName);
201 if (callBase.contains(m_typeResolver->mathObject()))
202 saCheck(propertyName, callBase.containedType());
203 else if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod)
204 saCheck(propertyName, callBase.containedType());
205 else if (!member.isMethod()) {
206 if (callBase.contains(m_typeResolver->jsValueType())
207 || callBase.contains(m_typeResolver->varType())) {
208 saCheck(propertyName, callBase.containedType());
209 }
210 }
211}
212
214{
215 QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
216
217 const QQmlSA::SourceLocation saLocation{
218 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
219 };
220 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
221 m_typeResolver->jsGlobalObject()) };
222 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
223 m_function->qmlScope.containedType()) };
224
225 QQmlSA::PassManagerPrivate::get(m_passManager)
226 ->analyzeCall(saBaseType, "eval"_L1, saContainedType, saLocation);
227}
228
229// Only to be called once a lookup has already failed
230QQmlJSLinterTypePropagator::PropertyResolution
231QQmlJSLinterTypePropagator::propertyResolution(QQmlJSScope::ConstPtr scope,
232 const QString &propertyName) const
233{
234 auto property = scope->property(propertyName);
235 if (!property.isValid())
236 return PropertyMissing;
237
238 QString errorType;
239 if (property.type().isNull())
240 errorType = u"found"_s;
241 else if (!property.type()->isFullyResolved())
242 errorType = u"fully resolved"_s;
243 else
244 return PropertyFullyResolved;
245
246 Q_ASSERT(!errorType.isEmpty());
247
248 if (QQmlJSScope::ownerOfProperty(scope, propertyName).scope->filePath()
249 != m_logger->filePath()) {
250 m_logger->log(
251 u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_s
252 .arg(property.typeName(), propertyName, errorType),
253 qmlUnresolvedType, currentSourceLocation());
254 }
255
256 return PropertyTypeUnresolved;
257}
258
259void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
260{
261 QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
262
263 auto location = currentSourceLocation();
264
265 const auto qmlScopeContained = m_function->qmlScope.containedType();
266 if (qmlScopeContained->isInCustomParserParent()) {
267 // Only ignore custom parser based elements if it's not Connections.
268 if (qmlScopeContained->baseType().isNull()
269 || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
270 return;
271 }
272
273 if (isMethod) {
274 if (isCallingProperty(qmlScopeContained, name))
275 return;
276 } else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
277 return;
278 }
279
280 std::optional<QQmlJSFixSuggestion> suggestion;
281
282 const auto childScopes = m_function->qmlScope.containedType()->childScopes();
283 for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
284 auto &scope = childScopes[i];
285 if (location.offset > scope->sourceLocation().offset) {
286 if (i + 1 < end
287 && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
288 continue;
289 if (scope->childScopes().size() == 0)
290 continue;
291
292 const auto jsId = scope->childScopes().first()->jsIdentifier(name);
293
294 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
295 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
296
297 QQmlJS::SourceLocation fixLocation = id.location;
298 Q_UNUSED(fixLocation)
299 fixLocation.length = 0;
300
301 const auto handler = m_typeResolver->signalHandlers()[id.location];
302
303 QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
304 const auto parameters = handler.signalParameters;
305 for (int numParams = parameters.size(); numParams > 0; --numParams) {
306 fixString += parameters.at(parameters.size() - numParams);
307 if (numParams > 1)
308 fixString += u", "_s;
309 }
310
311 fixString += handler.isMultiline ? u") "_s : u") => "_s;
312 const auto msg = u"\"%1\" is ambiguous. Use a function instead: %2%3"_s.arg(
313 name, fixString, handler.isMultiline ? "{ ... }"_L1 : "..."_L1);
314 QQmlJSDocumentEdit documentEdit{ m_logger->filePath(), fixLocation, fixString };
315 suggestion = {{ msg, fixLocation, documentEdit }};
316 suggestion->setAutoApplicable();
317 }
318 break;
319 }
320 }
321
322 // Might be a delegate just missing a required property.
323 // This heuristic does not recognize all instances of this occurring but should be sufficient
324 // protection against wrongly suggesting to add an id to the view to access the model that way
325 // which is very misleading
326 const auto qmlScope = m_function->qmlScope.containedType();
327 if (name == u"model" || name == u"index") {
328 if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
329 const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
330
331 for (auto it = bindings.first; it != bindings.second; it++) {
332 if (!it->hasObject())
333 continue;
334 if (it->objectType() == qmlScope) {
335 suggestion = QQmlJSFixSuggestion {
336 "'%1' is implicitly injected into this delegate. "
337 "Add a required property '%1' to the delegate instead."_L1
338 .arg(name),
339 qmlScope->sourceLocation()
340 };
341 };
342
343 break;
344 }
345 }
346 }
347
348 if (!suggestion.has_value()) {
349 for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
350 if (scope->hasProperty(name)) {
351 QQmlJSScopesById::MostLikelyCallback<QString> id;
352 m_function->addressableScopes.possibleIds(scope, qmlScope,
353 QQmlJSScopesByIdOption::Default, id);
354
355 QQmlJS::SourceLocation fixLocation = location;
356 fixLocation.length = 0;
357 QString m = "%1 is a member of a parent element.\n You can qualify the "
358 "access with its id to avoid this warning%2.\n"_L1.arg(name);
359 m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
360
361 suggestion = QQmlJSFixSuggestion{
362 m, fixLocation, { m_logger->filePath(), fixLocation,
363 (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.')) }
364 };
365
366 if (!id.result.isEmpty())
367 suggestion->setAutoApplicable();
368 }
369 }
370 }
371
372 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
373 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
374 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
375 QQmlJSFixSuggestion bindComponents {
376 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
377 .arg(replacement),
378 QQmlJS::s_documentOrigin,
379 QQmlJSDocumentEdit{ m_logger->filePath(), QQmlJS::s_documentOrigin, replacement + u'\n' }
380 };
381 bindComponents.setAutoApplicable();
382 suggestion = std::move(bindComponents);
383 }
384
385 if (!suggestion.has_value()) {
386 if (auto didYouMean = QQmlJSUtils::didYouMean(
387 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
388 m_logger->filePath(), location);
389 didYouMean.has_value()) {
390 suggestion = std::move(didYouMean);
391 }
392 }
393
394 if (name.front().isUpper()) {
395 Q_ASSERT(name.front() != u'_'); // QML runtime forbids accessing types starting with an '_'
396 // \a{name} starts with an uppercase iff:
397 // * a context property name
398 // * a (missing) module qualifier (from `import QtQuick as <qualifier>`)
399 // * an attached, singleton, or normal type without registration or module import
400 // * a namespace containing enums without registration or module import
401 m_logger->log("%1 was not found."_L1.arg(name), qmlUnqualified, location, true, true,
402 suggestion);
403 } else {
404 m_logger->log("Unqualified access"_L1, qmlUnqualified, location, true, true, suggestion);
405 }
406}
407
408static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
409{
410 if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
411 && !qmlScope->isInlineComponent()) {
412 return false;
413 }
414
415 const auto properties = qmlScope->properties();
416 return std::none_of(properties.constBegin(), properties.constEnd(),
417 [&qmlScope](const QQmlJSMetaProperty &property) {
418 return qmlScope->isPropertyRequired(property.propertyName());
419 });
420}
421
423 const QString &name, bool isMethod) const
424{
425 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
426
427 if (m_context.userContextProperties.isUnqualifiedAccessDisabled(name))
428 return;
429
430 const auto warningMessage = [&name, this]() {
431 QString result =
432 "Potential context property access detected."
433 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
434
435 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
436 result.append(
437 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
438 .arg(name));
439 }
440 return result;
441 };
442
443 if (m_context.userContextProperties.isOnUsageWarned(name)) {
444 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
445 return;
446 }
447
448 // name is not the name of a user context property, so emit the unqualified warning.
449 handleUnqualifiedAccess(name, isMethod);
450
451 const QList<QQmlJS::HeuristicContextProperty> definitions =
452 m_context.heuristicContextProperties.definitionsForName(name);
453 if (definitions.isEmpty())
454 return;
455 QString warning = warningMessage();
456 for (const auto &candidate : definitions) {
457 warning.append("\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
458 name, QDir::cleanPath(candidate.filename),
459 QString::number(candidate.location.startLine),
460 QString::number(candidate.location.startColumn)));
461 }
462 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
463}
464
465void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
466 bool isMethod) const
467{
468 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
469
470 Q_ASSERT(!scope.isNull());
471 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
472 if (qmlScope.isNull())
473 return;
474
475 QList<QQmlJSAnnotation> annotations;
476
477 QQmlJSMetaMethod method;
478
479 if (isMethod) {
480 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
481 if (methods.isEmpty())
482 return;
483 method = methods.constFirst();
484 annotations = method.annotations();
485 } else {
486 QQmlJSMetaProperty property = qmlScope->property(name);
487 if (!property.isValid())
488 return;
489 annotations = property.annotations();
490 }
491
492 auto deprecationAnn = std::find_if(
493 annotations.constBegin(), annotations.constEnd(),
494 [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
495
496 if (deprecationAnn == annotations.constEnd())
497 return;
498
499 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
500
501 QString descriptor = name;
502 if (isMethod)
503 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
504
505 QString message = "%1 \"%2\" is deprecated"_L1
506 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
507
508 if (!deprecation.reason.isEmpty())
509 message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
510
511 m_logger->log(message, qmlDeprecated, currentSourceLocation());
512}
513
514bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
515 const QString &name) const
516{
517 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
518
519 if (const auto property = scope->property(name); property.isValid()) {
520 QString errorType;
521 if (property.type() == m_typeResolver->varType()) {
522 errorType = u"a var property. It may or may not be a method. "_s
523 u"Use a regular function instead."_s;
524 } else if (property.type() == m_typeResolver->jsValueType()) {
525 errorType = u"a QJSValue property. It may or may not be a method. "_s
526 u"Use a regular Q_INVOKABLE instead."_s;
527 } else {
528 errorType = u"not a method"_s;
529 }
530
531 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
532 qmlUseProperFunction, currentSourceLocation(), true, true, {});
533 }
534
535 return res;
536}
537
539{
540 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
541
542 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
543 if (m_typeResolver->isPrefix(propertyName)) {
544 if (!accumulatorIn.containedType()->isReferenceType()) {
545 m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
546 accumulatorIn.containedType()->internalName()),
547 qmlPrefixedImportType,
548 currentSourceLocation());
549 }
550 } else if (accumulatorIn.isImportNamespace()) {
551 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
552 currentSourceLocation());
553 }
554
555 return res;
556}
557
558bool QQmlJSLinterTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type)
559{
560 if (type->isFullyResolved() || type->isScript())
561 return true;
562
563 if (!m_context.knownUnresolvedTypes.hasSeen(type)) {
564
565 m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
566 .arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
567 qmlUnresolvedType, currentSourceLocation());
568 }
569
570 return false;
571}
572
573void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
574{
575 QQmlJSTypePropagator::handleLookupError(propertyName);
576
577 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
578 const QString typeName = accumulatorIn.containedTypeName();
579
580 if (typeName == u"QVariant")
581 return;
582 if (accumulatorIn.isList() && propertyName == u"length")
583 return;
584
585 auto baseType = accumulatorIn.containedType();
586 // Warn separately when a property is only not found because of a missing type
587
588 if (propertyResolution(baseType, propertyName) != PropertyMissing)
589 return;
590
591 if (baseType->isScript())
592 return;
593
594 std::optional<QQmlJSFixSuggestion> fixSuggestion;
595
596 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
597 m_logger->filePath(), currentSourceLocation());
598 suggestion.has_value()) {
599 fixSuggestion = std::move(suggestion);
600 }
601
602 if (!fixSuggestion.has_value()
603 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
604
605 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
606 const auto metaEnums = scopeType->enumerations();
607 const bool enforcesScoped = scopeType->enforcesScopedEnums();
608
609 QStringList enumKeys;
610 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
611 if (!enforcesScoped || !metaEnum.isScoped())
612 enumKeys << metaEnum.keys();
613 }
614
615 if (auto suggestion = QQmlJSUtils::didYouMean(
616 propertyName, enumKeys, m_logger->filePath(), currentSourceLocation());
617 suggestion.has_value()) {
618 fixSuggestion = std::move(suggestion);
619 }
620 }
621
622 if (checkTypeResolved(baseType)) {
623 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
624 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
625 }
626}
627
628bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
629 const QString &propertyName)
630{
631 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
632
633 if (base.isEnumeration()) {
634 const auto metaEnum = base.enumeration();
635 if (!metaEnum.hasKey(propertyName)) {
636 const auto fixSuggestion = QQmlJSUtils::didYouMean(
637 propertyName, metaEnum.keys(), m_logger->filePath(), currentSourceLocation());
638 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
639 .arg(propertyName, metaEnum.name());
640 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
641 fixSuggestion);
642 }
643 }
644
645 return res;
646}
647
649{
650 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
651
652 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
653 const QQmlJSRegisterContent in = m_state.accumulatorIn();
654 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
655
656 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
657 QQmlJSScope::createQQmlSAElement(
658 m_state.accumulatorIn().containedType()),
659 name,
660 QQmlJSScope::createQQmlSAElement(isAttached
661 ? in.attachee().containedType()
662 : m_function->qmlScope.containedType()),
663 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
664 currentNonEmptySourceLocation()));
665
666 const auto qmlScope = m_function->qmlScope.containedType();
667 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
668 QQmlJSScope::createQQmlSAElement(qmlScope), name,
669 QQmlJSScope::createQQmlSAElement(in.containedType()),
670 QQmlJSScope::createQQmlSAElement(qmlScope),
671 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
672 currentNonEmptySourceLocation()));
673}
674
675void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
676{
677 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
678
679 const QQmlJSRegisterContent in = m_state.accumulatorIn();
680 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
681
682 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
683 QQmlJSScope::createQQmlSAElement(
684 m_state.accumulatorIn().containedType()),
685 name,
686 QQmlJSScope::createQQmlSAElement(isAttached
687 ? in.attachee().containedType()
688 : m_function->qmlScope.containedType()),
689 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
690 currentNonEmptySourceLocation()));
691}
692
693void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
694 QQmlJSRegisterContent scope)
695{
696 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
697
698 QStringList errors;
699 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
700 if (!match.isValid())
701 return;
702
703 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
704 const QQmlSA::SourceLocation saLocation{
705 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
706 };
707 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
708 m_function->qmlScope.containedType()) };
709
710 QQmlSA::PassManagerPrivate::get(m_passManager)
711 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
712}
713
715{
716 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
717
718 QQmlSA::PassManagerPrivate::get(m_passManager)
719 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
720 methodName,
721 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
722 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
723 currentNonEmptySourceLocation()));
724}
725
726static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
727 const QQmlJSTypeResolver *resolver)
728{
729 return scope == resolver->varType() || scope == resolver->jsValueType()
730 || scope == resolver->jsPrimitiveType();
731}
732
733static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
734 const QQmlJSTypeResolver *resolver)
735{
736 return scope == resolver->boolType() || scope == resolver->stringType()
737 || resolver->isNumeric(scope);
738}
739
740static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
741 const QQmlJSTypeResolver *resolver)
742{
743 return scope == resolver->nullType() || scope == resolver->voidType();
744}
745
746static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
747 const QQmlJSScope::ConstPtr &rhs,
748 const QQmlJSTypeResolver *resolver)
749{
750 if (lhs == rhs)
751 return false;
752
753 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
754 return false;
755
756 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
757 return false;
758
759 if (isStringOrNumberOrBoolean(lhs, resolver)
760 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
761 return true;
762 }
763
764 if (isStringOrNumberOrBoolean(rhs, resolver)
765 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
766 return true;
767 }
768
769 return false;
770}
771
773{
774 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
775 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
776
777 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
778 return;
779
780 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
781 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
782}
783
784QT_END_NAMESPACE
QQmlJSLinterTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger, const QQmlJS::LinterContext &linterContext, const BasicBlocks &basicBlocks={ }, const InstructionAnnotations &annotations={ }, QQmlSA::PassManager *passManager=nullptr)
void propagateCall(const QList< QQmlJSMetaMethod > &methods, int argc, int argv, QQmlJSRegisterContent scope) override
void handleUnqualifiedAccess(const QString &name, bool isMethod) const override
void generate_CallProperty(int nameIndex, int base, int argc, int argv) override
bool isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const override
void generate_CallPossiblyDirectEval(int argc, int argv) override
void generate_LoadQmlContextPropertyLookup(int index) override
void generate_StoreNameCommon(int nameIndex) override
void handleUnqualifiedAccessAndContextProperties(const QString &name, bool isMethod) const override
void generate_GetOptionalLookup(int index, int offset) override
bool checkForEnumProblems(QQmlJSRegisterContent base, const QString &propertyName) override
void generate_StoreProperty(int nameIndex, int base) override
void handleLookupError(const QString &propertyName) override
void propagateTranslationMethod_SAcheck(const QString &methodName) override
bool handleImportNamespaceLookup(const QString &propertyName) override
void propagatePropertyLookup(const QString &name, int lookupIndex=QQmlJSRegisterContent::InvalidLookupIndex) override
void checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name, bool isMethod) const override
Combined button and popup list for selecting options.
static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope, const QQmlJSTypeResolver *resolver)
static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs, const QQmlJSScope::ConstPtr &rhs, const QQmlJSTypeResolver *resolver)