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, Default, id);
302
303 QQmlJS::SourceLocation fixLocation = location;
304 fixLocation.length = 0;
305 QString m = "%1 is a member of a parent element.\n You can qualify the "
306 "access with its id to avoid this warning%2.\n"_L1.arg(name);
307 m = m.arg(id.result.isEmpty() ? " (You first have to give the element an id)"_L1 : ""_L1);
308
309 suggestion = QQmlJSFixSuggestion{
310 m, fixLocation, { m_logger->filePath(), fixLocation,
311 (id.result.isEmpty() ? u"<id>."_s : (id.result + u'.')) }
312 };
313
314 if (!id.result.isEmpty())
315 suggestion->setAutoApplicable();
316 }
317 }
318 }
319
320 if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
321 && m_function->addressableScopes.existsAnywhereInDocument(name)) {
322 const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
323 QQmlJSFixSuggestion bindComponents {
324 "Set \"%1\" in order to use IDs from outer components in nested components."_L1
325 .arg(replacement),
326 QQmlJS::s_documentOrigin,
327 QQmlJSDocumentEdit{ m_logger->filePath(), QQmlJS::s_documentOrigin, replacement + u'\n' }
328 };
329 bindComponents.setAutoApplicable();
330 suggestion = std::move(bindComponents);
331 }
332
333 if (!suggestion.has_value()) {
334 if (auto didYouMean = QQmlJSUtils::didYouMean(
335 name, qmlScope->properties().keys() + qmlScope->methods().keys(),
336 m_logger->filePath(), location);
337 didYouMean.has_value()) {
338 suggestion = std::move(didYouMean);
339 }
340 }
341
342 m_logger->log(QLatin1String("Unqualified access"), qmlUnqualified, location, true, true,
343 suggestion);
344}
345
346static bool shouldMentionRequiredProperties(const QQmlJSScope::ConstPtr &qmlScope)
347{
348 if (!qmlScope->isWrappedInImplicitComponent() && !qmlScope->isFileRootComponent()
349 && !qmlScope->isInlineComponent()) {
350 return false;
351 }
352
353 const auto properties = qmlScope->properties();
354 return std::none_of(properties.constBegin(), properties.constEnd(),
355 [&qmlScope](const QQmlJSMetaProperty &property) {
356 return qmlScope->isPropertyRequired(property.propertyName());
357 });
358}
359
361 const QString &name, bool isMethod) const
362{
363 QQmlJSTypePropagator::handleUnqualifiedAccessAndContextProperties(name, isMethod);
364
365 if (m_contextPropertyInfo.userContextProperties.isUnqualifiedAccessDisabled(name))
366 return;
367
368 const auto warningMessage = [&name, this]() {
369 QString result =
370 "Potential context property access detected."
371 " Context properties are discouraged in QML: use normal, required, or singleton properties instead."_L1;
372
373 if (shouldMentionRequiredProperties(m_function->qmlScope.containedType())) {
374 result.append(
375 "\nNote: '%1' assumed to be a potential context property because it is not declared as required property."_L1
376 .arg(name));
377 }
378 return result;
379 };
380
381 if (m_contextPropertyInfo.userContextProperties.isOnUsageWarned(name)) {
382 m_logger->log(warningMessage(), qmlContextProperties, currentSourceLocation());
383 return;
384 }
385
386 // name is not the name of a user context property, so emit the unqualified warning.
387 handleUnqualifiedAccess(name, isMethod);
388
389 const QList<QQmlJS::HeuristicContextProperty> definitions =
390 m_contextPropertyInfo.heuristicContextProperties.definitionsForName(name);
391 if (definitions.isEmpty())
392 return;
393 QString warning = warningMessage();
394 for (const auto &candidate : definitions) {
395 warning.append("\nNote: candidate context property declaration '%1' at %2:%3:%4"_L1.arg(
396 name, QDir::cleanPath(candidate.filename),
397 QString::number(candidate.location.startLine),
398 QString::number(candidate.location.startColumn)));
399 }
400 m_logger->log(warning, qmlContextProperties, currentSourceLocation());
401}
402
403void QQmlJSLinterTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
404 bool isMethod) const
405{
406 QQmlJSTypePropagator::checkDeprecated(scope, name, isMethod);
407
408 Q_ASSERT(!scope.isNull());
409 auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
410 if (qmlScope.isNull())
411 return;
412
413 QList<QQmlJSAnnotation> annotations;
414
415 QQmlJSMetaMethod method;
416
417 if (isMethod) {
418 const QList<QQmlJSMetaMethod> methods = qmlScope->methods(name);
419 if (methods.isEmpty())
420 return;
421 method = methods.constFirst();
422 annotations = method.annotations();
423 } else {
424 QQmlJSMetaProperty property = qmlScope->property(name);
425 if (!property.isValid())
426 return;
427 annotations = property.annotations();
428 }
429
430 auto deprecationAnn = std::find_if(
431 annotations.constBegin(), annotations.constEnd(),
432 [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
433
434 if (deprecationAnn == annotations.constEnd())
435 return;
436
437 QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
438
439 QString descriptor = name;
440 if (isMethod)
441 descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
442
443 QString message = "%1 \"%2\" is deprecated"_L1
444 .arg(isMethod ? u"Method"_s : u"Property"_s, descriptor);
445
446 if (!deprecation.reason.isEmpty())
447 message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
448
449 m_logger->log(message, qmlDeprecated, currentSourceLocation());
450}
451
452bool QQmlJSLinterTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope,
453 const QString &name) const
454{
455 const bool res = QQmlJSTypePropagator::isCallingProperty(scope, name);
456
457 if (const auto property = scope->property(name); property.isValid()) {
458 QString errorType;
459 if (property.type() == m_typeResolver->varType()) {
460 errorType = u"a var property. It may or may not be a method. "_s
461 u"Use a regular function instead."_s;
462 } else if (property.type() == m_typeResolver->jsValueType()) {
463 errorType = u"a QJSValue property. It may or may not be a method. "_s
464 u"Use a regular Q_INVOKABLE instead."_s;
465 } else {
466 errorType = u"not a method"_s;
467 }
468
469 m_logger->log(u"Property \"%1\" is %2"_s.arg(name, errorType),
470 qmlUseProperFunction, currentSourceLocation(), true, true, {});
471 }
472
473 return res;
474}
475
477{
478 const auto res = QQmlJSTypePropagator::handleImportNamespaceLookup(propertyName);
479
480 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
481 if (m_typeResolver->isPrefix(propertyName)) {
482 if (!accumulatorIn.containedType()->isReferenceType()) {
483 m_logger->log(u"Cannot use non-QObject type %1 to access prefixed import"_s.arg(
484 accumulatorIn.containedType()->internalName()),
485 qmlPrefixedImportType,
486 currentSourceLocation());
487 }
488 } else if (accumulatorIn.isImportNamespace()) {
489 m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
490 currentSourceLocation());
491 }
492
493 return res;
494}
495
496bool QQmlJSLinterTypePropagator::checkTypeResolved(const QQmlJSScope::ConstPtr &type)
497{
498 if (type->isFullyResolved() || type->isScript())
499 return true;
500
501 if (!m_knownUnresolvedTypes->hasSeen(type)) {
502
503 m_logger->log(QStringLiteral("Type %1 is used but it is not resolved")
504 .arg(QQmlJSUtils::getScopeName(type, type->scopeType())),
505 qmlUnresolvedType, currentSourceLocation());
506 }
507
508 return false;
509}
510
511void QQmlJSLinterTypePropagator::handleLookupError(const QString &propertyName)
512{
513 QQmlJSTypePropagator::handleLookupError(propertyName);
514
515 const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
516 const QString typeName = accumulatorIn.containedTypeName();
517
518 if (typeName == u"QVariant")
519 return;
520 if (accumulatorIn.isList() && propertyName == u"length")
521 return;
522
523 auto baseType = accumulatorIn.containedType();
524 // Warn separately when a property is only not found because of a missing type
525
526 if (propertyResolution(baseType, propertyName) != PropertyMissing)
527 return;
528
529 if (baseType->isScript())
530 return;
531
532 std::optional<QQmlJSFixSuggestion> fixSuggestion;
533
534 if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
535 m_logger->filePath(), currentSourceLocation());
536 suggestion.has_value()) {
537 fixSuggestion = std::move(suggestion);
538 }
539
540 if (!fixSuggestion.has_value()
541 && accumulatorIn.variant() == QQmlJSRegisterContent::MetaType) {
542
543 const QQmlJSScope::ConstPtr scopeType = accumulatorIn.scopeType();
544 const auto metaEnums = scopeType->enumerations();
545 const bool enforcesScoped = scopeType->enforcesScopedEnums();
546
547 QStringList enumKeys;
548 for (const QQmlJSMetaEnum &metaEnum : metaEnums) {
549 if (!enforcesScoped || !metaEnum.isScoped())
550 enumKeys << metaEnum.keys();
551 }
552
553 if (auto suggestion = QQmlJSUtils::didYouMean(
554 propertyName, enumKeys, m_logger->filePath(), currentSourceLocation());
555 suggestion.has_value()) {
556 fixSuggestion = std::move(suggestion);
557 }
558 }
559
560 if (checkTypeResolved(baseType)) {
561 m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName, typeName),
562 qmlMissingProperty, currentSourceLocation(), true, true, fixSuggestion);
563 }
564}
565
566bool QQmlJSLinterTypePropagator::checkForEnumProblems(QQmlJSRegisterContent base,
567 const QString &propertyName)
568{
569 const bool res = QQmlJSTypePropagator::checkForEnumProblems(base, propertyName);
570
571 if (base.isEnumeration()) {
572 const auto metaEnum = base.enumeration();
573 if (!metaEnum.hasKey(propertyName)) {
574 const auto fixSuggestion = QQmlJSUtils::didYouMean(
575 propertyName, metaEnum.keys(), m_logger->filePath(), currentSourceLocation());
576 const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
577 .arg(propertyName, metaEnum.name());
578 m_logger->log(error, qmlMissingEnumEntry, currentSourceLocation(), true, true,
579 fixSuggestion);
580 }
581 }
582
583 return res;
584}
585
587{
588 QQmlJSTypePropagator::generate_StoreNameCommon(nameIndex);
589
590 const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
591 const QQmlJSRegisterContent in = m_state.accumulatorIn();
592 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
593
594 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
595 QQmlJSScope::createQQmlSAElement(
596 m_state.accumulatorIn().containedType()),
597 name,
598 QQmlJSScope::createQQmlSAElement(isAttached
599 ? in.attachee().containedType()
600 : m_function->qmlScope.containedType()),
601 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
602 currentNonEmptySourceLocation()));
603
604 const auto qmlScope = m_function->qmlScope.containedType();
605 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
606 QQmlJSScope::createQQmlSAElement(qmlScope), name,
607 QQmlJSScope::createQQmlSAElement(in.containedType()),
608 QQmlJSScope::createQQmlSAElement(qmlScope),
609 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
610 currentNonEmptySourceLocation()));
611}
612
613void QQmlJSLinterTypePropagator::propagatePropertyLookup(const QString &name, int lookupIndex)
614{
615 QQmlJSTypePropagator::propagatePropertyLookup(name, lookupIndex);
616
617 const QQmlJSRegisterContent in = m_state.accumulatorIn();
618 const bool isAttached = in.variant() == QQmlJSRegisterContent::Attachment;
619
620 QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
621 QQmlJSScope::createQQmlSAElement(
622 m_state.accumulatorIn().containedType()),
623 name,
624 QQmlJSScope::createQQmlSAElement(isAttached
625 ? in.attachee().containedType()
626 : m_function->qmlScope.containedType()),
627 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
628 currentNonEmptySourceLocation()));
629}
630
631void QQmlJSLinterTypePropagator::propagateCall(const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
632 QQmlJSRegisterContent scope)
633{
634 QQmlJSTypePropagator::propagateCall(methods, argc, argv, scope);
635
636 QStringList errors;
637 const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
638 if (!match.isValid())
639 return;
640
641 const QQmlSA::Element saBaseType = QQmlJSScope::createQQmlSAElement(scope.containedType());
642 const QQmlSA::SourceLocation saLocation{
643 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(currentSourceLocation())
644 };
645 const QQmlSA::Element saContainedType{ QQmlJSScope::createQQmlSAElement(
646 m_function->qmlScope.containedType()) };
647
648 QQmlSA::PassManagerPrivate::get(m_passManager)
649 ->analyzeCall(saBaseType, match.methodName(), saContainedType, saLocation);
650}
651
653{
654 QQmlJSTypePropagator::propagateTranslationMethod_SAcheck(methodName);
655
656 QQmlSA::PassManagerPrivate::get(m_passManager)
657 ->analyzeCall(QQmlJSScope::createQQmlSAElement(m_typeResolver->jsGlobalObject()),
658 methodName,
659 QQmlJSScope::createQQmlSAElement(m_function->qmlScope.containedType()),
660 QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
661 currentNonEmptySourceLocation()));
662}
663
664static bool mightContainStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
665 const QQmlJSTypeResolver *resolver)
666{
667 return scope == resolver->varType() || scope == resolver->jsValueType()
668 || scope == resolver->jsPrimitiveType();
669}
670
671static bool isStringOrNumberOrBoolean(const QQmlJSScope::ConstPtr &scope,
672 const QQmlJSTypeResolver *resolver)
673{
674 return scope == resolver->boolType() || scope == resolver->stringType()
675 || resolver->isNumeric(scope);
676}
677
678static bool isVoidOrUndefined(const QQmlJSScope::ConstPtr &scope,
679 const QQmlJSTypeResolver *resolver)
680{
681 return scope == resolver->nullType() || scope == resolver->voidType();
682}
683
684static bool requiresStrictEquality(const QQmlJSScope::ConstPtr &lhs,
685 const QQmlJSScope::ConstPtr &rhs,
686 const QQmlJSTypeResolver *resolver)
687{
688 if (lhs == rhs)
689 return false;
690
691 if (resolver->isNumeric(lhs) && resolver->isNumeric(rhs))
692 return false;
693
694 if (isVoidOrUndefined(lhs, resolver) || isVoidOrUndefined(rhs, resolver))
695 return false;
696
697 if (isStringOrNumberOrBoolean(lhs, resolver)
698 && !mightContainStringOrNumberOrBoolean(rhs, resolver)) {
699 return true;
700 }
701
702 if (isStringOrNumberOrBoolean(rhs, resolver)
703 && !mightContainStringOrNumberOrBoolean(lhs, resolver)) {
704 return true;
705 }
706
707 return false;
708}
709
711{
712 const QQmlJSScope::ConstPtr lhsType = checkedInputRegister(lhs).containedType();
713 const QQmlJSScope::ConstPtr rhsType = m_state.accumulatorIn().containedType();
714
715 if (!requiresStrictEquality(lhsType, rhsType, m_typeResolver))
716 return;
717
718 m_logger->log("== and != may perform type coercion, use === or !== to avoid it."_L1,
719 qmlEqualityTypeCoercion, currentNonEmptySourceLocation());
720}
721
722QT_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)