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