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
15QQmlJSLinterTypePropagator::QQmlJSLinterTypePropagator(
16 const QV4::Compiler::JSUnitGenerator *unitGenerator,
17 const QQmlJSTypeResolver *typeResolver, QQmlJSLogger *logger,
18 const BasicBlocks &basicBlocks, const InstructionAnnotations &annotations,
19 QQmlSA::PassManager *passManager, const ContextPropertyInfo &contextPropertyInfo)
20 : QQmlJSTypePropagator(unitGenerator, typeResolver, logger, basicBlocks, annotations),
21 m_passManager(passManager), m_contextPropertyInfo(contextPropertyInfo)
22{
23
24}
25
27{
28 QQmlJSTypePropagator::generate_Ret();
29
30 if (m_function->isSignalHandler) {
31 // Signal handlers cannot return anything.
32 } else if (m_state.accumulatorIn().contains(m_typeResolver->voidType())) {
33 // You can always return undefined.
34 } else if (!m_returnType.isValid() && m_state.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(m_state.accumulatorIn(), m_returnType)) {
42 m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
43 m_state.accumulatorIn().containedTypeName(),
44 m_returnType.containedTypeName()),
45 qmlIncompatibleType, currentFunctionSourceLocation());
46 }
47
48 const QQmlJS::SourceLocation location = m_function->isProperty
49 ? currentFunctionSourceLocation()
50 : currentNonEmptySourceLocation();
51 QQmlSA::PassManagerPrivate::get(m_passManager)
52 ->analyzeBinding(
53 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
54 QQmlJSScope::createQQmlSAElement(m_state.accumulatorIn().containedType()),
55 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(location));
56}
57
59{
60 QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(index);
61
62 Q_ASSERT(m_idMemberShadows);
63
64 const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
65 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
66
67 const auto qmlScope = m_function->qmlScope.containedType();
68 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
69 QQmlJSScope::createQQmlSAElement(qmlScope), name,
70 QQmlJSScope::createQQmlSAElement(qmlScope),
71 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
72 currentNonEmptySourceLocation()));
73
74 const auto &accumulatorOut = m_state.accumulatorOut();
75 if (!accumulatorOut.isValid())
76 return;
77
78 // complain about renamed types in enums like MyType.Enum.EnumValue
79 if (accumulatorOut.variant() == QQmlJSRegisterContent::Attachment
80 || accumulatorOut.variant() == QQmlJSRegisterContent::MetaType) {
81 if (m_renamedComponents) {
82 m_renamedComponents->handleRenamedType(accumulatorOut.scopeType(), name,
83 currentNonEmptySourceLocation(), m_logger);
84 }
85 }
86
87 const QQmlJSScope::ConstPtr scope = accumulatorOut.scopeType();
88 const QQmlJSScope::ConstPtr idScope = m_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
138{
139 QQmlJSTypePropagator::generate_StoreProperty(nameIndex, base);
140
141 auto callBase = m_state.registers[base].content;
142 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
143 const bool isAttached = callBase.variant() == QQmlJSRegisterContent::Attachment;
144
145 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
146 QQmlJSScope::createQQmlSAElement(callBase.containedType()),
147 propertyName,
148 QQmlJSScope::createQQmlSAElement(
149 m_state.accumulatorIn().containedType()),
150 QQmlJSScope::createQQmlSAElement(isAttached
151 ? callBase.attachee().containedType()
152 : m_function->qmlScope.containedType()),
153 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
154 currentNonEmptySourceLocation()));
155}
156
157void QQmlJSLinterTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
158{
159 QQmlJSTypePropagator::generate_CallProperty(nameIndex, base, argc, argv);
160
161 const auto saCheck = [&](const QString &propertyName, const QQmlJSScope::ConstPtr &baseType) {
162 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(baseType) };
163 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
164 m_function->qmlScope.containedType()) };
165 const QQmlSA::SourceLocation saLocation{
166 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
167 };
168
169 QQmlSA::PassManagerPrivate::get(m_passManager)
170 ->analyzeRead(saBaseType, propertyName, saContainedType, saLocation);
171 QQmlSA::PassManagerPrivate::get(m_passManager)
172 ->analyzeCall(saBaseType, propertyName, saContainedType, saLocation);
173 };
174
175 const auto callBase = m_state.registers[base].content;
176 const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
177 const auto member = m_typeResolver->memberType(callBase, propertyName);
178
179 const bool isLoggingMethod = QQmlJSTypePropagator::isLoggingMethod(propertyName);
180 if (callBase.contains(m_typeResolver->mathObject()))
181 saCheck(propertyName, callBase.containedType());
182 else if (callBase.contains(m_typeResolver->consoleObject()) && isLoggingMethod)
183 saCheck(propertyName, callBase.containedType());
184 else if (!member.isMethod()) {
185 if (callBase.contains(m_typeResolver->jsValueType())
186 || callBase.contains(m_typeResolver->varType())) {
187 saCheck(propertyName, callBase.containedType());
188 }
189 }
190}
191
193{
194 QQmlJSTypePropagator::generate_CallPossiblyDirectEval(argc, argv);
195
196 const QQmlSA::SourceLocation saLocation{
197 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
198 };
199 const QQmlSA::Element saBaseType{ QQmlJSScope::createQQmlSAElement(
200 m_typeResolver->jsGlobalObject()) };
201 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
202 m_function->qmlScope.containedType()) };
203
204 QQmlSA::PassManagerPrivate::get(m_passManager)
205 ->analyzeCall(saBaseType, "eval"_L1, saContainedType, saLocation);
206}
207
208void QQmlJSLinterTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
209{
210 QQmlJSTypePropagator::handleUnqualifiedAccess(name, isMethod);
211
212 auto location = currentSourceLocation();
213
214 const auto qmlScopeContained = m_function->qmlScope.containedType();
215 if (qmlScopeContained->isInCustomParserParent()) {
216 // Only ignore custom parser based elements if it's not Connections.
217 if (qmlScopeContained->baseType().isNull()
218 || qmlScopeContained->baseType()->internalName() != u"QQmlConnections"_s)
219 return;
220 }
221
222 if (isMethod) {
223 if (isCallingProperty(qmlScopeContained, name))
224 return;
225 } else if (propertyResolution(qmlScopeContained, name) != PropertyMissing) {
226 return;
227 }
228
229 std::optional<QQmlJSFixSuggestion> suggestion;
230
231 const auto childScopes = m_function->qmlScope.containedType()->childScopes();
232 for (qsizetype i = 0, end = childScopes.size(); i < end; i++) {
233 auto &scope = childScopes[i];
234 if (location.offset > scope->sourceLocation().offset) {
235 if (i + 1 < end
236 && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
237 continue;
238 if (scope->childScopes().size() == 0)
239 continue;
240
241 const auto jsId = scope->childScopes().first()->jsIdentifier(name);
242
243 if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
244 const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
245
246 QQmlJS::SourceLocation fixLocation = id.location;
247 Q_UNUSED(fixLocation)
248 fixLocation.length = 0;
249
250 const auto handler = m_typeResolver->signalHandlers()[id.location];
251
252 QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
253 const auto parameters = handler.signalParameters;
254 for (int numParams = parameters.size(); numParams > 0; --numParams) {
255 fixString += parameters.at(parameters.size() - numParams);
256 if (numParams > 1)
257 fixString += u", "_s;
258 }
259
260 fixString += handler.isMultiline ? u") "_s : u") => "_s;
261
262 suggestion =
263 QQmlJSFixSuggestion{ u"\"%1\" is ambiguous. "
264 "Use a function instead: %2%3"_s.arg(
265 name, fixString,
266 handler.isMultiline ? "{ ... }"_L1 : "..."_L1),
267 fixLocation, fixString };
268 suggestion->setAutoApplicable();
269 }
270 break;
271 }
272 }
273
274 // Might be a delegate just missing a required property.
275 // This heuristic does not recognize all instances of this occurring but should be sufficient
276 // protection against wrongly suggesting to add an id to the view to access the model that way
277 // which is very misleading
278 const auto qmlScope = m_function->qmlScope.containedType();
279 if (name == u"model" || name == u"index") {
280 if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
281 const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
282
283 for (auto it = bindings.first; it != bindings.second; it++) {
284 if (!it->hasObject())
285 continue;
286 if (it->objectType() == qmlScope) {
287 suggestion = QQmlJSFixSuggestion {
288 "'%1' is implicitly injected into this delegate. "
289 "Add a required property '%1' to the delegate instead."_L1
290 .arg(name),
291 qmlScope->sourceLocation()
292 };
293 };
294
295 break;
296 }
297 }
298 }
299
300 if (!suggestion.has_value()) {
301 for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
302 if (scope->hasProperty(name)) {
303 QQmlJSScopesById::MostLikelyCallback<QString> id;
304 m_function->addressableScopes.possibleIds(scope, qmlScope, Default, id);
305
306 QQmlJS::SourceLocation fixLocation = location;
307 fixLocation.length = 0;
308 QString m = "%1 is a member of a parent element.\n You can qualify the "
309 "access with its id to avoid this warning%2.\n"_L1.arg(name);
310 m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
311
312 suggestion = QQmlJSFixSuggestion{
313 m, fixLocation, (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.'))
314 };
315
316 if (!id.result.isEmpty())
317 suggestion->setAutoApplicable();
318 }
319 }
320 }
321
322 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
323 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
324 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
325 QQmlJSFixSuggestion bindComponents {
326 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
327 .arg(replacement),
328 QQmlJS::s_documentOrigin,
329 replacement + '\n'_L1
330 };
331 bindComponents.setAutoApplicable();
332 suggestion = std::move(bindComponents);
333 }
334
335 if (!suggestion.has_value()) {
336 if (auto didYouMean = QQmlJSUtils::didYouMean(
337 name, qmlScope->properties().keys() + qmlScope->methods().keys(), location);
338 didYouMean.has_value()) {
339 suggestion = std::move(didYouMean);
340 }
341 }
342
343 m_logger->log(QLatin1String("Unqualified access"), qmlUnqualified, location, true, true,
344 suggestion);
345}
346
347static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
348{
349 if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
350 && !qmlScope->isInlineComponent()) {
351 return false;
352 }
353
354 const auto properties = qmlScope->properties();
355 return std::none_of(properties.constBegin(), properties.constEnd(),
356 [&qmlScope](const QQmlJSMetaProperty &property) {
357 return qmlScope->isPropertyRequired(property.propertyName());
358 });
359}
360
362 const QString &name, bool isMethod) const
363{
364 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
365
366 if (m_contextPropertyInfo.userContextProperties.isUnqualifiedAccessDisabled(name))
367 return;
368
369 const auto warningMessage = [&name, this]() {
370 QString result =
371 "Potential context property access detected."
372 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
373
374 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
375 result.append(
376 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
377 .arg(name));
378 }
379 return result;
380 };
381
382 if (m_contextPropertyInfo.userContextProperties.isOnUsageWarned(name)) {
383 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
384 return;
385 }
386
387 // name is not the name of a user context property, so emit the unqualified warning.
388 handleUnqualifiedAccess(name, isMethod);
389
390 const QList<QQmlJS::HeuristicContextProperty> definitions =
391 m_contextPropertyInfo.heuristicContextProperties.definitionsForName(name);
392 if (definitions.isEmpty())
393 return;
394 QString warning = warningMessage();
395 for (const auto &candidate : definitions) {
396 warning.append("\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
397 name, QDir::cleanPath(candidate.filename),
398 QString::number(candidate.location.startLine),
399 QString::number(candidate.location.startColumn)));
400 }
401 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
402}
403
404void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
405 bool isMethod) const
406{
407 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
408
409 Q_ASSERT(!scope.isNull());
410 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
411 if (qmlScope.isNull())
412 return;
413
414 QList<QQmlJSAnnotation> annotations;
415
416 QQmlJSMetaMethod method;
417
418 if (isMethod) {
419 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
420 if (methods.isEmpty())
421 return;
422 method = methods.constFirst();
423 annotations = method.annotations();
424 } else {
425 QQmlJSMetaProperty property = qmlScope->property(name);
426 if (!property.isValid())
427 return;
428 annotations = property.annotations();
429 }
430
431 auto deprecationAnn = std::find_if(
432 annotations.constBegin(), annotations.constEnd(),
433 [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
434
435 if (deprecationAnn == annotations.constEnd())
436 return;
437
438 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
439
440 QString descriptor = name;
441 if (isMethod)
442 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
443
444 QString message = "%1 \"%2\" is deprecated"_L1
445 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
446
447 if (!deprecation.reason.isEmpty())
448 message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
449
450 m_logger->log(message, qmlDeprecated, currentSourceLocation());
451}
452
453bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
454 const QString &name) const
455{
456 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
457
458 if (const auto property = scope->property(name); property.isValid()) {
459 QString errorType;
460 if (property.type() == m_typeResolver->varType()) {
461 errorType = u"a var property. It may or may not be a method. "_s
462 u"Use a regular function instead."_s;
463 } else if (property.type() == m_typeResolver->jsValueType()) {
464 errorType = u"a QJSValue property. It may or may not be a method. "_s
465 u"Use a regular Q_INVOKABLE instead."_s;
466 } else {
467 errorType = u"not a method"_s;
468 }
469
470 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
471 qmlUseProperFunction, currentSourceLocation(), true, true, {});
472 }
473
474 return res;
475}
476
478{
479 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
480
481 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
482 if (m_typeResolver->isPrefix(propertyName)) {
483 if (!accumulatorIn.containedType()->isReferenceType()) {
484 m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
485 accumulatorIn.containedType()->internalName()),
486 qmlPrefixedImportType,
487 currentSourceLocation());
488 }
489 } else if (accumulatorIn.isImportNamespace()) {
490 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
491 currentSourceLocation());
492 }
493
494 return res;
495}
496
497void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
498{
499 QQmlJSTypePropagator::handleLookupError(propertyName);
500
501 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
502 const QString typeName = accumulatorIn.containedTypeName();
503
504 if (typeName == u"QVariant")
505 return;
506 if (accumulatorIn.isList() && propertyName == u"length")
507 return;
508
509 auto baseType = accumulatorIn.containedType();
510 // Warn separately when a property is only not found because of a missing type
511
512 if (propertyResolution(baseType, propertyName) != PropertyMissing)
513 return;
514
515 if (baseType->isScript())
516 return;
517
518 std::optional<QQmlJSFixSuggestion> fixSuggestion;
519
520 if (auto suggestion = QQmlJSUtils::didYouMean(
521 propertyName, baseType->properties().keys(), currentSourceLocation());
522 suggestion.has_value()) {
523 fixSuggestion = std::move(suggestion);
524 }
525
526 if (!fixSuggestion.has_value()
527 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
528
529 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
530 const auto metaEnums = scopeType->enumerations();
531 const bool enforcesScoped = scopeType->enforcesScopedEnums();
532
533 QStringList enumKeys;
534 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
535 if (!enforcesScoped || !metaEnum.isScoped())
536 enumKeys << metaEnum.keys();
537 }
538
539 if (auto suggestion = QQmlJSUtils::didYouMean(
540 propertyName, enumKeys, currentSourceLocation());
541 suggestion.has_value()) {
542 fixSuggestion = std::move(suggestion);
543 }
544 }
545
546 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
547 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
548}
549
550bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
551 const QString &propertyName)
552{
553 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
554
555 if (base.isEnumeration()) {
556 const auto metaEnum = base.enumeration();
557 if (!metaEnum.hasKey(propertyName)) {
558 const auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEnum.keys(),
559 currentSourceLocation());
560 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
561 .arg(propertyName, metaEnum.name());
562 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
563 fixSuggestion);
564 }
565 }
566
567 return res;
568}
569
571{
572 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
573
574 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
575 const QQmlJSRegisterContent in = m_state.accumulatorIn();
576 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
577
578 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
579 QQmlJSScope::createQQmlSAElement(
580 m_state.accumulatorIn().containedType()),
581 name,
582 QQmlJSScope::createQQmlSAElement(isAttached
583 ? in.attachee().containedType()
584 : m_function->qmlScope.containedType()),
585 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
586 currentNonEmptySourceLocation()));
587
588 const auto qmlScope = m_function->qmlScope.containedType();
589 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
590 QQmlJSScope::createQQmlSAElement(qmlScope), name,
591 QQmlJSScope::createQQmlSAElement(in.containedType()),
592 QQmlJSScope::createQQmlSAElement(qmlScope),
593 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
594 currentNonEmptySourceLocation()));
595}
596
597void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
598{
599 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
600
601 const QQmlJSRegisterContent in = m_state.accumulatorIn();
602 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
603
604 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
605 QQmlJSScope::createQQmlSAElement(
606 m_state.accumulatorIn().containedType()),
607 name,
608 QQmlJSScope::createQQmlSAElement(isAttached
609 ? in.attachee().containedType()
610 : m_function->qmlScope.containedType()),
611 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
612 currentNonEmptySourceLocation()));
613}
614
615void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
616 QQmlJSRegisterContent scope)
617{
618 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
619
620 QStringList errors;
621 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
622 if (!match.isValid())
623 return;
624
625 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
626 const QQmlSA::SourceLocation saLocation{
627 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
628 };
629 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
630 m_function->qmlScope.containedType()) };
631
632 QQmlSA::PassManagerPrivate::get(m_passManager)
633 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
634}
635
637{
638 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
639
640 QQmlSA::PassManagerPrivate::get(m_passManager)
641 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
642 methodName,
643 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
644 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
645 currentNonEmptySourceLocation()));
646}
647
648static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
649 const QQmlJSTypeResolver *resolver)
650{
651 return scope == resolver->varType() || scope == resolver->jsValueType()
652 || scope == resolver->jsPrimitiveType();
653}
654
655static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
656 const QQmlJSTypeResolver *resolver)
657{
658 return scope == resolver->boolType() || scope == resolver->stringType()
659 || resolver->isNumeric(scope);
660}
661
662static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
663 const QQmlJSTypeResolver *resolver)
664{
665 return scope == resolver->nullType() || scope == resolver->voidType();
666}
667
668static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
669 const QQmlJSScope::ConstPtr &rhs,
670 const QQmlJSTypeResolver *resolver)
671{
672 if (lhs == rhs)
673 return false;
674
675 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
676 return false;
677
678 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
679 return false;
680
681 if (isStringOrNumberOrBoolean(lhs, resolver)
682 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
683 return true;
684 }
685
686 if (isStringOrNumberOrBoolean(rhs, resolver)
687 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
688 return true;
689 }
690
691 return false;
692}
693
695{
696 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
697 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
698
699 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
700 return;
701
702 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
703 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
704}
705
706QT_END_NAMESPACE
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)