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 // 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 const auto msg = u"\"%1\" is ambiguous. Use a function instead: %2%3"_s.arg(
262 name, fixString, handler.isMultiline ? "{ ... }"_L1 : "..."_L1);
263 QQmlJSDocumentEdit documentEdit{ m_logger->filePath(), fixLocation, fixString };
264 suggestion = {{ msg, fixLocation, documentEdit }};
265 suggestion->setAutoApplicable();
266 }
267 break;
268 }
269 }
270
271 // Might be a delegate just missing a required property.
272 // This heuristic does not recognize all instances of this occurring but should be sufficient
273 // protection against wrongly suggesting to add an id to the view to access the model that way
274 // which is very misleading
275 const auto qmlScope = m_function->qmlScope.containedType();
276 if (name == u"model" || name == u"index") {
277 if (const QQmlJSScope::ConstPtr parent = qmlScope->parentScope(); !parent.isNull()) {
278 const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
279
280 for (auto it = bindings.first; it != bindings.second; it++) {
281 if (!it->hasObject())
282 continue;
283 if (it->objectType() == qmlScope) {
284 suggestion = QQmlJSFixSuggestion {
285 "'%1' is implicitly injected into this delegate. "
286 "Add a required property '%1' to the delegate instead."_L1
287 .arg(name),
288 qmlScope->sourceLocation()
289 };
290 };
291
292 break;
293 }
294 }
295 }
296
297 if (!suggestion.has_value()) {
298 for (QQmlJSScope::ConstPtr scope = qmlScope; !scope.isNull(); scope = scope->parentScope()) {
299 if (scope->hasProperty(name)) {
300 QQmlJSScopesById::MostLikelyCallback<QString> id;
301 m_function->addressableScopes.possibleIds(scope, qmlScope,
302 QQmlJSScopesByIdOption::Default, id);
303
304 QQmlJS::SourceLocation fixLocation = location;
305 fixLocation.length = 0;
306 QString m = "%1 is a member of a parent element.\n You can qualify the "
307 "access with its id to avoid this warning%2.\n"_L1.arg(name);
308 m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
309
310 suggestion = QQmlJSFixSuggestion{
311 m, fixLocation, { m_logger->filePath(), fixLocation,
312 (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.')) }
313 };
314
315 if (!id.result.isEmpty())
316 suggestion->setAutoApplicable();
317 }
318 }
319 }
320
321 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
322 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
323 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
324 QQmlJSFixSuggestion bindComponents {
325 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
326 .arg(replacement),
327 QQmlJS::s_documentOrigin,
328 QQmlJSDocumentEdit{ m_logger->filePath(), QQmlJS::s_documentOrigin, replacement + u'\n' }
329 };
330 bindComponents.setAutoApplicable();
331 suggestion = std::move(bindComponents);
332 }
333
334 if (!suggestion.has_value()) {
335 if (auto didYouMean = QQmlJSUtils::didYouMean(
336 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
337 m_logger->filePath(), 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
497bool QQmlJSLinterTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type)
498{
499 if (type->isFullyResolved() || type->isScript())
500 return true;
501
502 if (!m_knownUnresolvedTypes->hasSeen(type)) {
503
504 m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
505 .arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
506 qmlUnresolvedType, currentSourceLocation());
507 }
508
509 return false;
510}
511
512void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
513{
514 QQmlJSTypePropagator::handleLookupError(propertyName);
515
516 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
517 const QString typeName = accumulatorIn.containedTypeName();
518
519 if (typeName == u"QVariant")
520 return;
521 if (accumulatorIn.isList() && propertyName == u"length")
522 return;
523
524 auto baseType = accumulatorIn.containedType();
525 // Warn separately when a property is only not found because of a missing type
526
527 if (propertyResolution(baseType, propertyName) != PropertyMissing)
528 return;
529
530 if (baseType->isScript())
531 return;
532
533 std::optional<QQmlJSFixSuggestion> fixSuggestion;
534
535 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
536 m_logger->filePath(), currentSourceLocation());
537 suggestion.has_value()) {
538 fixSuggestion = std::move(suggestion);
539 }
540
541 if (!fixSuggestion.has_value()
542 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
543
544 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
545 const auto metaEnums = scopeType->enumerations();
546 const bool enforcesScoped = scopeType->enforcesScopedEnums();
547
548 QStringList enumKeys;
549 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
550 if (!enforcesScoped || !metaEnum.isScoped())
551 enumKeys << metaEnum.keys();
552 }
553
554 if (auto suggestion = QQmlJSUtils::didYouMean(
555 propertyName, enumKeys, m_logger->filePath(), currentSourceLocation());
556 suggestion.has_value()) {
557 fixSuggestion = std::move(suggestion);
558 }
559 }
560
561 if (checkTypeResolved(baseType)) {
562 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
563 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
564 }
565}
566
567bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
568 const QString &propertyName)
569{
570 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
571
572 if (base.isEnumeration()) {
573 const auto metaEnum = base.enumeration();
574 if (!metaEnum.hasKey(propertyName)) {
575 const auto fixSuggestion = QQmlJSUtils::didYouMean(
576 propertyName, metaEnum.keys(), m_logger->filePath(), currentSourceLocation());
577 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
578 .arg(propertyName, metaEnum.name());
579 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
580 fixSuggestion);
581 }
582 }
583
584 return res;
585}
586
588{
589 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
590
591 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
592 const QQmlJSRegisterContent in = m_state.accumulatorIn();
593 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
594
595 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
596 QQmlJSScope::createQQmlSAElement(
597 m_state.accumulatorIn().containedType()),
598 name,
599 QQmlJSScope::createQQmlSAElement(isAttached
600 ? in.attachee().containedType()
601 : m_function->qmlScope.containedType()),
602 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
603 currentNonEmptySourceLocation()));
604
605 const auto qmlScope = m_function->qmlScope.containedType();
606 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
607 QQmlJSScope::createQQmlSAElement(qmlScope), name,
608 QQmlJSScope::createQQmlSAElement(in.containedType()),
609 QQmlJSScope::createQQmlSAElement(qmlScope),
610 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
611 currentNonEmptySourceLocation()));
612}
613
614void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
615{
616 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
617
618 const QQmlJSRegisterContent in = m_state.accumulatorIn();
619 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
620
621 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
622 QQmlJSScope::createQQmlSAElement(
623 m_state.accumulatorIn().containedType()),
624 name,
625 QQmlJSScope::createQQmlSAElement(isAttached
626 ? in.attachee().containedType()
627 : m_function->qmlScope.containedType()),
628 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
629 currentNonEmptySourceLocation()));
630}
631
632void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
633 QQmlJSRegisterContent scope)
634{
635 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
636
637 QStringList errors;
638 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
639 if (!match.isValid())
640 return;
641
642 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
643 const QQmlSA::SourceLocation saLocation{
644 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
645 };
646 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
647 m_function->qmlScope.containedType()) };
648
649 QQmlSA::PassManagerPrivate::get(m_passManager)
650 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
651}
652
654{
655 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
656
657 QQmlSA::PassManagerPrivate::get(m_passManager)
658 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
659 methodName,
660 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
661 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
662 currentNonEmptySourceLocation()));
663}
664
665static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
666 const QQmlJSTypeResolver *resolver)
667{
668 return scope == resolver->varType() || scope == resolver->jsValueType()
669 || scope == resolver->jsPrimitiveType();
670}
671
672static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
673 const QQmlJSTypeResolver *resolver)
674{
675 return scope == resolver->boolType() || scope == resolver->stringType()
676 || resolver->isNumeric(scope);
677}
678
679static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
680 const QQmlJSTypeResolver *resolver)
681{
682 return scope == resolver->nullType() || scope == resolver->voidType();
683}
684
685static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
686 const QQmlJSScope::ConstPtr &rhs,
687 const QQmlJSTypeResolver *resolver)
688{
689 if (lhs == rhs)
690 return false;
691
692 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
693 return false;
694
695 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
696 return false;
697
698 if (isStringOrNumberOrBoolean(lhs, resolver)
699 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
700 return true;
701 }
702
703 if (isStringOrNumberOrBoolean(rhs, resolver)
704 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
705 return true;
706 }
707
708 return false;
709}
710
712{
713 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
714 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
715
716 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
717 return;
718
719 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
720 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
721}
722
723QT_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)