File indexing completed on 2024-05-19 15:46:44
0001 /* 0002 SPDX-FileCopyrightText: 2012 Aleix Pol <aleixpol@kde.org> 0003 SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "declarationbuilder.h" 0009 #include "debug.h" 0010 0011 #include <language/duchain/types/integraltype.h> 0012 #include <language/duchain/types/enumerationtype.h> 0013 #include <language/duchain/types/enumeratortype.h> 0014 #include <language/duchain/types/arraytype.h> 0015 #include <language/duchain/types/typeutils.h> 0016 #include <language/duchain/declaration.h> 0017 #include <language/duchain/aliasdeclaration.h> 0018 #include <language/duchain/duchainlock.h> 0019 #include <language/duchain/classdeclaration.h> 0020 #include <language/duchain/namespacealiasdeclaration.h> 0021 0022 #include "expressionvisitor.h" 0023 #include "parsesession.h" 0024 #include "functiondeclaration.h" 0025 #include "functiontype.h" 0026 #include "helper.h" 0027 #include "cache.h" 0028 #include "frameworks/nodejs.h" 0029 0030 #include <QFileInfo> 0031 #include <QStandardPaths> 0032 #include <KLocalizedString> 0033 0034 using namespace KDevelop; 0035 0036 DeclarationBuilder::DeclarationBuilder(ParseSession* session) 0037 : m_prebuilding(false) 0038 { 0039 m_session = session; 0040 } 0041 0042 ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, 0043 QmlJS::AST::Node* node, 0044 const ReferencedTopDUContext& updateContext_) 0045 { 0046 Q_ASSERT(m_session->url() == url); 0047 0048 ReferencedTopDUContext updateContext(updateContext_); 0049 // The declaration builder needs to run twice, so it can resolve uses of e.g. functions 0050 // which are called before they are defined (which is easily possible, due to JS's dynamic nature). 0051 if (!m_prebuilding) { 0052 qCDebug(KDEV_QMLJS_DUCHAIN) << "building, but running pre-builder first"; 0053 auto prebuilder = new DeclarationBuilder(m_session); 0054 0055 prebuilder->m_prebuilding = true; 0056 updateContext = prebuilder->build(url, node, updateContext); 0057 0058 qCDebug(KDEV_QMLJS_DUCHAIN) << "pre-builder finished"; 0059 delete prebuilder; 0060 0061 if (!m_session->allDependenciesSatisfied()) { 0062 qCDebug(KDEV_QMLJS_DUCHAIN) << "dependencies were missing, don't perform the second parsing pass"; 0063 return updateContext; 0064 } 0065 } else { 0066 qCDebug(KDEV_QMLJS_DUCHAIN) << "prebuilding"; 0067 } 0068 0069 return DeclarationBuilderBase::build(url, node, updateContext); 0070 } 0071 0072 void DeclarationBuilder::startVisiting(QmlJS::AST::Node* node) 0073 { 0074 DUContext* builtinQmlContext = nullptr; 0075 0076 if (QmlJS::isQmlFile(currentContext()) && !currentContext()->url().str().contains(QLatin1String("__builtin_qml.qml"))) { 0077 builtinQmlContext = m_session->contextOfFile( 0078 QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevqmljssupport/nodejsmodules/__builtin_qml.qml")) 0079 ); 0080 } 0081 0082 { 0083 DUChainWriteLocker lock; 0084 0085 // Remove all the imported parent contexts: imports may have been edited 0086 // and there musn't be any leftover parent context 0087 currentContext()->topContext()->clearImportedParentContexts(); 0088 0089 // Initialize Node.js 0090 QmlJS::NodeJS::instance().initialize(this); 0091 0092 // Built-in QML types (color, rect, etc) 0093 if (builtinQmlContext) { 0094 topContext()->addImportedParentContext(builtinQmlContext); 0095 } 0096 } 0097 0098 DeclarationBuilderBase::startVisiting(node); 0099 } 0100 0101 /* 0102 * Functions 0103 */ 0104 template<typename Decl> 0105 void DeclarationBuilder::declareFunction(QmlJS::AST::Node* node, 0106 bool newPrototypeContext, 0107 const Identifier& name, 0108 const RangeInRevision& nameRange, 0109 QmlJS::AST::Node* parameters, 0110 const RangeInRevision& parametersRange, 0111 QmlJS::AST::Node* body, 0112 const RangeInRevision& bodyRange) 0113 { 0114 setComment(node); 0115 0116 // Declare the function 0117 QmlJS::FunctionType::Ptr func(new QmlJS::FunctionType); 0118 Decl* decl; 0119 0120 { 0121 DUChainWriteLocker lock; 0122 0123 decl = openDeclaration<Decl>(name, nameRange); 0124 decl->setKind(Declaration::Type); 0125 func->setDeclaration(decl); 0126 decl->setType(func); 0127 } 0128 openType(func); 0129 0130 // Parameters, if any (a function must always have an internal function context, 0131 // so always open a context here even if there are no parameters) 0132 DUContext* parametersContext = openContext( 0133 node + 1, // Don't call setContextOnNode on node, only the body context can be associated with node 0134 RangeInRevision(parametersRange.start, bodyRange.end), // Ensure that this context contains both the parameters and the body 0135 DUContext::Function, 0136 QualifiedIdentifier(name) 0137 ); 0138 0139 if (parameters) { 0140 QmlJS::AST::Node::accept(parameters, this); 0141 } 0142 0143 // The internal context of the function is its parameter context 0144 { 0145 DUChainWriteLocker lock; 0146 decl->setInternalContext(parametersContext); 0147 } 0148 0149 // Open the prototype context, if any. This has to be done before the body 0150 // because this context is needed for "this" to be properly resolved 0151 // in it. 0152 if (newPrototypeContext) { 0153 DUChainWriteLocker lock; 0154 auto* d = reinterpret_cast<QmlJS::FunctionDeclaration*>(decl); 0155 0156 d->setPrototypeContext(openContext( 0157 node + 2, // Don't call setContextOnNode on node, only the body context can be associated with node 0158 RangeInRevision(parametersRange.start, parametersRange.start), 0159 DUContext::Function, // This allows QmlJS::getOwnerOfContext to know that the parent of this context is the function declaration 0160 QualifiedIdentifier(name) 0161 )); 0162 0163 if (name != Identifier(QStringLiteral("Object"))) { 0164 // Every class inherit from Object 0165 QmlJS::importObjectContext(currentContext(), topContext()); 0166 } 0167 0168 closeContext(); 0169 } 0170 0171 // Body, if any (it is a child context of the parameters) 0172 openContext( 0173 node, 0174 bodyRange, 0175 DUContext::Other, 0176 QualifiedIdentifier(name) 0177 ); 0178 0179 if (body) { 0180 QmlJS::AST::Node::accept(body, this); 0181 } 0182 0183 // Close the body context and then the parameters context 0184 closeContext(); 0185 closeContext(); 0186 } 0187 0188 template<typename Node> 0189 void DeclarationBuilder::declareParameters(Node* node, QmlJS::AST::UiQualifiedId* Node::*typeFunc) 0190 { 0191 for (Node *plist = node; plist; plist = plist->next) { 0192 const Identifier name(plist->name.toString()); 0193 const RangeInRevision range = m_session->locationToRange(plist->identifierToken); 0194 0195 AbstractType::Ptr type = (typeFunc ? 0196 typeFromName((plist->*typeFunc)->name.toString()) : // The typeAttribute attribute of plist contains the type name of the argument 0197 AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)) // No type information, use mixed 0198 ); 0199 0200 { 0201 DUChainWriteLocker lock; 0202 openDeclaration<Declaration>(name, range); 0203 } 0204 openType(type); 0205 closeAndAssignType(); 0206 0207 if (QmlJS::FunctionType::Ptr funType = currentType<QmlJS::FunctionType>()) { 0208 funType->addArgument(type); 0209 } 0210 } 0211 } 0212 0213 bool DeclarationBuilder::visit(QmlJS::AST::FunctionDeclaration* node) 0214 { 0215 declareFunction<QmlJS::FunctionDeclaration>( 0216 node, 0217 true, // A function declaration always has its own prototype context 0218 Identifier(node->name.toString()), 0219 m_session->locationToRange(node->identifierToken), 0220 node->formals, 0221 m_session->locationsToRange(node->lparenToken, node->rparenToken), 0222 node->body, 0223 m_session->locationsToRange(node->lbraceToken, node->rbraceToken) 0224 ); 0225 0226 return false; 0227 } 0228 0229 bool DeclarationBuilder::visit(QmlJS::AST::FunctionExpression* node) 0230 { 0231 declareFunction<QmlJS::FunctionDeclaration>( 0232 node, 0233 false, 0234 Identifier(), 0235 QmlJS::emptyRangeOnLine(node->functionToken), 0236 node->formals, 0237 m_session->locationsToRange(node->lparenToken, node->rparenToken), 0238 node->body, 0239 m_session->locationsToRange(node->lbraceToken, node->rbraceToken) 0240 ); 0241 0242 return false; 0243 } 0244 0245 bool DeclarationBuilder::visit(QmlJS::AST::FormalParameterList* node) 0246 { 0247 declareParameters(node, (QmlJS::AST::UiQualifiedId* QmlJS::AST::FormalParameterList::*)nullptr); 0248 0249 return DeclarationBuilderBase::visit(node); 0250 } 0251 0252 bool DeclarationBuilder::visit(QmlJS::AST::UiParameterList* node) 0253 { 0254 declareParameters(node, &QmlJS::AST::UiParameterList::type); 0255 0256 return DeclarationBuilderBase::visit(node); 0257 } 0258 0259 bool DeclarationBuilder::visit(QmlJS::AST::ReturnStatement* node) 0260 { 0261 if (QmlJS::FunctionType::Ptr func = currentType<QmlJS::FunctionType>()) { 0262 AbstractType::Ptr returnType; 0263 0264 if (node->expression) { 0265 returnType = findType(node->expression).type; 0266 } else { 0267 returnType = new IntegralType(IntegralType::TypeVoid); 0268 } 0269 0270 DUChainWriteLocker lock; 0271 0272 func->setReturnType(QmlJS::mergeTypes(func->returnType(), returnType)); 0273 } 0274 0275 return false; // findType has already explored node 0276 } 0277 0278 void DeclarationBuilder::endVisitFunction() 0279 { 0280 QmlJS::FunctionType::Ptr func = currentType<QmlJS::FunctionType>(); 0281 0282 if (func && !func->returnType()) { 0283 // A function that returns nothing returns void 0284 DUChainWriteLocker lock; 0285 0286 func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); 0287 } 0288 0289 closeAndAssignType(); 0290 } 0291 0292 void DeclarationBuilder::endVisit(QmlJS::AST::FunctionDeclaration* node) 0293 { 0294 DeclarationBuilderBase::endVisit(node); 0295 0296 endVisitFunction(); 0297 } 0298 0299 void DeclarationBuilder::endVisit(QmlJS::AST::FunctionExpression* node) 0300 { 0301 DeclarationBuilderBase::endVisit(node); 0302 0303 endVisitFunction(); 0304 } 0305 0306 /* 0307 * Variables 0308 */ 0309 void DeclarationBuilder::inferArgumentsFromCall(QmlJS::AST::Node* base, QmlJS::AST::ArgumentList* arguments) 0310 { 0311 ContextBuilder::ExpressionType expr = findType(base); 0312 auto func_type = expr.type.dynamicCast<QmlJS::FunctionType>(); 0313 DUChainWriteLocker lock; 0314 0315 if (!func_type) { 0316 return; 0317 } 0318 0319 auto func_declaration = dynamic_cast<FunctionDeclaration*>(func_type->declaration(topContext())); 0320 0321 if (!func_declaration || !func_declaration->internalContext()) { 0322 return; 0323 } 0324 0325 // Put the argument nodes in a list that has a definite size 0326 QVector<Declaration *> argumentDecls = func_declaration->internalContext()->localDeclarations(); 0327 QVector<QmlJS::AST::ArgumentList *> args; 0328 0329 for (auto argument = arguments; argument; argument = argument->next) { 0330 args.append(argument); 0331 } 0332 0333 // Don't update a function when it is called with the wrong number 0334 // of arguments 0335 if (args.size() != argumentDecls.count()) { 0336 return; 0337 } 0338 0339 // Update the types of the function arguments 0340 QmlJS::FunctionType::Ptr new_func_type(new QmlJS::FunctionType); 0341 0342 for (int i=0; i<args.size(); ++i) { 0343 QmlJS::AST::ArgumentList *argument = args.at(i); 0344 AbstractType::Ptr current_type = argumentDecls.at(i)->abstractType(); 0345 0346 // Merge the current type of the argument with its type in the call expression 0347 AbstractType::Ptr call_type = findType(argument->expression).type; 0348 AbstractType::Ptr new_type = QmlJS::mergeTypes(current_type, call_type); 0349 0350 // Update the declaration of the argument and its type in the function type 0351 if (func_declaration->topContext() == topContext()) { 0352 new_func_type->addArgument(new_type); 0353 argumentDecls.at(i)->setAbstractType(new_type); 0354 } 0355 0356 // Add a warning if it is possible that the argument types don't match 0357 if (!m_prebuilding && !areTypesEqual(current_type, call_type)) { 0358 m_session->addProblem(argument, i18n( 0359 "Possible type mismatch between the argument type (%1) and the value passed as argument (%2)", 0360 current_type->toString(), 0361 call_type->toString() 0362 ), IProblem::Hint); 0363 } 0364 } 0365 0366 // Replace the function's type with the new type having updated arguments 0367 if (func_declaration->topContext() == topContext()) { 0368 new_func_type->setReturnType(func_type->returnType()); 0369 new_func_type->setDeclaration(func_declaration); 0370 func_declaration->setAbstractType(new_func_type); 0371 0372 if (expr.declaration) { 0373 // expr.declaration is the variable that contains the function, while 0374 // func_declaration is the declaration of the function. They can be 0375 // different and both need to be updated 0376 expr.declaration->setAbstractType(new_func_type); 0377 } 0378 } 0379 0380 return; 0381 } 0382 0383 bool DeclarationBuilder::visit(QmlJS::AST::VariableDeclaration* node) 0384 { 0385 setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); 0386 0387 const Identifier name(node->name.toString()); 0388 const RangeInRevision range = m_session->locationToRange(node->identifierToken); 0389 const AbstractType::Ptr type = findType(node->expression).type; 0390 0391 { 0392 DUChainWriteLocker lock; 0393 openDeclaration<Declaration>(name, range); 0394 } 0395 openType(type); 0396 0397 return false; // findType has already explored node 0398 } 0399 0400 void DeclarationBuilder::endVisit(QmlJS::AST::VariableDeclaration* node) 0401 { 0402 DeclarationBuilderBase::endVisit(node); 0403 0404 closeAndAssignType(); 0405 } 0406 0407 bool DeclarationBuilder::visit(QmlJS::AST::BinaryExpression* node) 0408 { 0409 if (node->op == QSOperator::Assign) { 0410 ExpressionType leftType = findType(node->left); 0411 ExpressionType rightType = findType(node->right); 0412 DUChainWriteLocker lock; 0413 0414 if (leftType.declaration) { 0415 DUContext* leftCtx = leftType.declaration->context(); 0416 DUContext* leftInternalCtx = QmlJS::getInternalContext(leftType.declaration); 0417 0418 // object.prototype.method = function(){} : when assigning a function 0419 // to a variable living in a Class context, set the prototype 0420 // context of the function to the context of the variable 0421 if (rightType.declaration && leftCtx->type() == DUContext::Class) { 0422 auto func = rightType.declaration.dynamicCast<QmlJS::FunctionDeclaration>(); 0423 0424 if (!QmlJS::getOwnerOfContext(leftCtx) && !leftCtx->importers().isEmpty()) { 0425 // MyClass.prototype.myfunc declares "myfunc" in a small context 0426 // that is imported by MyClass. The prototype of myfunc should 0427 // be the context of MyClass, not the small context in which 0428 // it has been declared 0429 leftCtx = leftCtx->importers().at(0); 0430 } 0431 0432 if (func && !func->prototypeContext()) { 0433 func->setPrototypeContext(leftCtx); 0434 } 0435 } 0436 0437 if (leftType.declaration->topContext() != topContext()) { 0438 // Do not modify a declaration of another file 0439 } else if (leftType.isPrototype && leftInternalCtx) { 0440 // Assigning something to a prototype is equivalent to making it 0441 // inherit from a class: "Class.prototype = ClassOrObject;" 0442 leftInternalCtx->clearImportedParentContexts(); 0443 0444 QmlJS::importDeclarationInContext( 0445 leftInternalCtx, 0446 rightType.declaration 0447 ); 0448 } else { 0449 // Merge the already-known type of the variable with the new one 0450 leftType.declaration->setAbstractType(QmlJS::mergeTypes(leftType.type, rightType.type)); 0451 } 0452 } 0453 0454 return false; // findType has already explored node 0455 } 0456 0457 return DeclarationBuilderBase::visit(node); 0458 } 0459 0460 bool DeclarationBuilder::visit(QmlJS::AST::CallExpression* node) 0461 { 0462 inferArgumentsFromCall(node->base, node->arguments); 0463 return false; 0464 } 0465 0466 bool DeclarationBuilder::visit(QmlJS::AST::NewMemberExpression* node) 0467 { 0468 inferArgumentsFromCall(node->base, node->arguments); 0469 return false; 0470 } 0471 0472 /* 0473 * Arrays 0474 */ 0475 void DeclarationBuilder::declareFieldMember(const KDevelop::DeclarationPointer& declaration, 0476 const QString& member, 0477 QmlJS::AST::Node* node, 0478 const QmlJS::AST::SourceLocation& location) 0479 { 0480 if (QmlJS::isPrototypeIdentifier(member)) { 0481 // Don't declare "prototype", this is a special member 0482 return; 0483 } 0484 0485 if (!m_session->allDependenciesSatisfied()) { 0486 // Don't declare anything automatically if dependencies are missing: the 0487 // checks hereafter may pass now but fail later, thus causing disappearing 0488 // declarations 0489 return; 0490 } 0491 0492 DUChainWriteLocker lock; 0493 Identifier identifier(member); 0494 0495 // Declaration must have an internal context so that the member can be added 0496 // into it. 0497 DUContext* ctx = QmlJS::getInternalContext(declaration); 0498 0499 if (!ctx || ctx->topContext() != topContext()) { 0500 return; 0501 } 0502 0503 // No need to re-declare a field if it already exists 0504 // TODO check if we can make getDeclaration receive an Identifier directly 0505 if (QmlJS::getDeclaration(QualifiedIdentifier(identifier), ctx, false)) { 0506 return; 0507 } 0508 0509 // The internal context of declaration is already closed and does not contain 0510 // location. This can be worked around by opening a new context, declaring the 0511 // new field in it, and then adding the context as a parent of 0512 // declaration->internalContext(). 0513 RangeInRevision range = m_session->locationToRange(location); 0514 IntegralType::Ptr type = IntegralType::Ptr(new IntegralType(IntegralType::TypeMixed)); 0515 DUContext* importedContext = openContext(node, range, DUContext::Class); 0516 auto* decl = openDeclaration<Declaration>(identifier, range); 0517 0518 decl->setInSymbolTable(false); // This declaration is in an anonymous context, and the symbol table acts as if the declaration was in the global context 0519 openType(type); 0520 closeAndAssignType(); 0521 closeContext(); 0522 0523 ctx->addImportedParentContext(importedContext); 0524 } 0525 0526 bool DeclarationBuilder::visit(QmlJS::AST::FieldMemberExpression* node) 0527 { 0528 setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); 0529 0530 ExpressionType type = findType(node->base); 0531 0532 if (type.declaration) { 0533 declareFieldMember( 0534 type.declaration, 0535 node->name.toString(), 0536 node, 0537 node->identifierToken 0538 ); 0539 } 0540 0541 return false; // findType has already visited node->base 0542 } 0543 0544 bool DeclarationBuilder::visit(QmlJS::AST::ArrayMemberExpression* node) 0545 { 0546 setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); 0547 0548 // When the user types array["new_key"], declare "new_key" as a new field of 0549 // array. 0550 auto stringLiteral = QmlJS::AST::cast<QmlJS::AST::StringLiteral*>(node->expression); 0551 0552 if (!stringLiteral) { 0553 return DeclarationBuilderBase::visit(node); 0554 } 0555 0556 ExpressionType type = findType(node->base); 0557 0558 if (type.declaration) { 0559 declareFieldMember( 0560 type.declaration, 0561 stringLiteral->value.toString(), 0562 node, 0563 stringLiteral->literalToken 0564 ); 0565 } 0566 0567 node->expression->accept(this); 0568 return false; // findType has already visited node->base, and we have just visited node->expression 0569 } 0570 0571 bool DeclarationBuilder::visit(QmlJS::AST::ObjectLiteral* node) 0572 { 0573 setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); 0574 0575 // Object literals can appear in the "values" property of enumerations. Their 0576 // keys must be declared in the enumeration, not in an anonymous class 0577 if (currentContext()->type() == DUContext::Enum) { 0578 return DeclarationBuilderBase::visit(node); 0579 } 0580 0581 // Open an anonymous class declaration, with its internal context 0582 StructureType::Ptr type(new StructureType); 0583 { 0584 DUChainWriteLocker lock; 0585 auto* decl = openDeclaration<ClassDeclaration>( 0586 Identifier(), 0587 QmlJS::emptyRangeOnLine(node->lbraceToken) 0588 ); 0589 0590 decl->setKind(Declaration::Type); 0591 decl->setInternalContext(openContext( 0592 node, 0593 m_session->locationsToRange(node->lbraceToken, node->rbraceToken), 0594 DUContext::Class 0595 )); 0596 0597 type->setDeclaration(decl); 0598 0599 // Every object literal inherits from Object 0600 QmlJS::importObjectContext(currentContext(), topContext()); 0601 } 0602 openType(type); 0603 0604 return DeclarationBuilderBase::visit(node); 0605 } 0606 0607 bool DeclarationBuilder::visit(QmlJS::AST::PropertyNameAndValue* node) 0608 { 0609 setComment(node); 0610 0611 if (!node->name || !node->value) { 0612 return DeclarationBuilderBase::visit(node); 0613 } 0614 0615 RangeInRevision range(m_session->locationToRange(node->name->propertyNameToken)); 0616 Identifier name(QmlJS::getNodeValue(node->name)); 0617 0618 // The type of the declaration can either be an enumeration value or the type 0619 // of its expression 0620 ExpressionType type; 0621 bool inSymbolTable = false; 0622 0623 if (currentContext()->type() == DUContext::Enum) { 0624 // This is an enumeration value 0625 auto value = QmlJS::AST::cast<QmlJS::AST::NumericLiteral*>(node->value); 0626 EnumeratorType::Ptr enumerator(new EnumeratorType); 0627 0628 enumerator->setDataType(IntegralType::TypeInt); 0629 0630 if (value) { 0631 enumerator->setValue((int)value->value); 0632 } 0633 0634 type.type = enumerator; 0635 type.declaration = nullptr; 0636 inSymbolTable = true; 0637 } else { 0638 // Normal value 0639 type = findType(node->value); 0640 } 0641 0642 // If a function is assigned to an object member, set the prototype context 0643 // of the function to the object containing the member 0644 if (type.declaration) { 0645 DUChainWriteLocker lock; 0646 auto func = type.declaration.dynamicCast<QmlJS::FunctionDeclaration>(); 0647 0648 if (func && !func->prototypeContext()) { 0649 func->setPrototypeContext(currentContext()); 0650 } 0651 } 0652 0653 // Open the declaration 0654 { 0655 DUChainWriteLocker lock; 0656 auto* decl = openDeclaration<ClassMemberDeclaration>(name, range); 0657 0658 decl->setInSymbolTable(inSymbolTable); 0659 } 0660 openType(type.type); 0661 0662 return false; // findType has already explored node->expression 0663 } 0664 0665 void DeclarationBuilder::endVisit(QmlJS::AST::PropertyNameAndValue* node) 0666 { 0667 DeclarationBuilderBase::endVisit(node); 0668 0669 closeAndAssignType(); 0670 } 0671 0672 void DeclarationBuilder::endVisit(QmlJS::AST::ObjectLiteral* node) 0673 { 0674 DeclarationBuilderBase::endVisit(node); 0675 0676 if (currentContext()->type() != DUContext::Enum) { 0677 // Enums are special-cased in visit(ObjectLiteral) 0678 closeContext(); 0679 closeAndAssignType(); 0680 } 0681 } 0682 0683 /* 0684 * plugins.qmltypes files 0685 */ 0686 void DeclarationBuilder::declareComponent(QmlJS::AST::UiObjectInitializer* node, 0687 const RangeInRevision &range, 0688 const Identifier &name) 0689 { 0690 QString baseClass = QmlJS::getQMLAttributeValue(node->members, QStringLiteral("prototype")).value.section(QLatin1Char('/'), -1, -1); 0691 0692 // Declare the component itself 0693 StructureType::Ptr type(new StructureType); 0694 0695 ClassDeclaration* decl; 0696 { 0697 DUChainWriteLocker lock; 0698 decl = openDeclaration<ClassDeclaration>(name, range); 0699 0700 decl->setKind(Declaration::Type); 0701 decl->setClassType(ClassDeclarationData::Interface); 0702 decl->clearBaseClasses(); 0703 0704 if (!baseClass.isEmpty()) { 0705 addBaseClass(decl, baseClass); 0706 } 0707 0708 type->setDeclaration(decl); 0709 decl->setType(type); // declareExports needs to know the type of decl 0710 } 0711 openType(type); 0712 } 0713 0714 void DeclarationBuilder::declareMethod(QmlJS::AST::UiObjectInitializer* node, 0715 const RangeInRevision &range, 0716 const Identifier &name, 0717 bool isSlot, 0718 bool isSignal) 0719 { 0720 QString type_name = QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value; 0721 QmlJS::FunctionType::Ptr type(new QmlJS::FunctionType); 0722 0723 if (type_name.isEmpty()) { 0724 type->setReturnType(typeFromName(QStringLiteral("void"))); 0725 } else { 0726 type->setReturnType(typeFromName(type_name)); 0727 } 0728 0729 { 0730 DUChainWriteLocker lock; 0731 auto* decl = openDeclaration<ClassFunctionDeclaration>(name, range); 0732 0733 decl->setIsSlot(isSlot); 0734 decl->setIsSignal(isSignal); 0735 type->setDeclaration(decl); 0736 } 0737 openType(type); 0738 } 0739 0740 void DeclarationBuilder::declareProperty(QmlJS::AST::UiObjectInitializer* node, 0741 const RangeInRevision &range, 0742 const Identifier &name) 0743 { 0744 AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value); 0745 0746 { 0747 DUChainWriteLocker lock; 0748 auto* decl = openDeclaration<ClassMemberDeclaration>(name, range); 0749 0750 decl->setAbstractType(type); 0751 } 0752 openType(type); 0753 } 0754 0755 void DeclarationBuilder::declareParameter(QmlJS::AST::UiObjectInitializer* node, 0756 const RangeInRevision &range, 0757 const Identifier &name) 0758 { 0759 QmlJS::FunctionType::Ptr function = currentType<QmlJS::FunctionType>(); 0760 AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, QStringLiteral("type")).value); 0761 0762 Q_ASSERT(function); 0763 function->addArgument(type); 0764 0765 { 0766 DUChainWriteLocker lock; 0767 openDeclaration<Declaration>(name, range); 0768 } 0769 openType(type); 0770 } 0771 0772 void DeclarationBuilder::declareEnum(const RangeInRevision &range, 0773 const Identifier &name) 0774 { 0775 EnumerationType::Ptr type(new EnumerationType); 0776 0777 { 0778 DUChainWriteLocker lock; 0779 auto* decl = openDeclaration<ClassMemberDeclaration>(name, range); 0780 0781 decl->setKind(Declaration::Type); 0782 decl->setType(type); // The type needs to be set here because closeContext is called before closeAndAssignType and needs to know the type of decl 0783 0784 type->setDataType(IntegralType::TypeEnumeration); 0785 type->setDeclaration(decl); 0786 } 0787 openType(type); 0788 } 0789 0790 void DeclarationBuilder::declareComponentSubclass(QmlJS::AST::UiObjectInitializer* node, 0791 const KDevelop::RangeInRevision& range, 0792 const QString& baseclass, 0793 QmlJS::AST::UiQualifiedId* qualifiedId) 0794 { 0795 Identifier name( 0796 QmlJS::getQMLAttributeValue(node->members, QStringLiteral("name")).value.section(QLatin1Char('/'), -1, -1) 0797 ); 0798 DUContext::ContextType contextType = DUContext::Class; 0799 0800 if (baseclass == QLatin1String("Component")) { 0801 // QML component, equivalent to a QML class 0802 declareComponent(node, range, name); 0803 } else if (baseclass == QLatin1String("Method") || 0804 baseclass == QLatin1String("Signal") || 0805 baseclass == QLatin1String("Slot")) { 0806 // Method (that can also be a signal or a slot) 0807 declareMethod(node, range, name, baseclass == QLatin1String("Slot"), baseclass == QLatin1String("Signal")); 0808 contextType = DUContext::Function; 0809 } else if (baseclass == QLatin1String("Property")) { 0810 // A property 0811 declareProperty(node, range, name); 0812 } else if (baseclass == QLatin1String("Parameter") && currentType<QmlJS::FunctionType>()) { 0813 // One parameter of a signal/slot/method 0814 declareParameter(node, range, name); 0815 } else if (baseclass == QLatin1String("Enum")) { 0816 // Enumeration. The "values" key contains a dictionary of name -> number entries. 0817 declareEnum(range, name); 0818 contextType = DUContext::Enum; 0819 name = Identifier(); // Enum contexts should have no name so that their members have the correct scope 0820 } else { 0821 // Define an anonymous subclass of the baseclass. This subclass will 0822 // be instantiated when "id:" is encountered 0823 name = Identifier(); 0824 0825 // Use ExpressionVisitor to find the declaration of the base class 0826 DeclarationPointer baseClass = findType(qualifiedId).declaration; 0827 StructureType::Ptr type(new StructureType); 0828 0829 { 0830 DUChainWriteLocker lock; 0831 auto* decl = openDeclaration<ClassDeclaration>( 0832 currentContext()->type() == DUContext::Global ? 0833 Identifier(m_session->moduleName()) : 0834 name, 0835 QmlJS::emptyRangeOnLine(node->lbraceToken) 0836 ); 0837 0838 decl->clearBaseClasses(); 0839 decl->setKind(Declaration::Type); 0840 decl->setType(type); // The class needs to know its type early because it contains definitions that depend on that type 0841 type->setDeclaration(decl); 0842 0843 if (baseClass) { 0844 addBaseClass(decl, baseClass->indexedType()); 0845 } 0846 } 0847 openType(type); 0848 } 0849 0850 // Open a context of the proper type and identifier 0851 openContext( 0852 node, 0853 m_session->locationsToInnerRange(node->lbraceToken, node->rbraceToken), 0854 contextType, 0855 QualifiedIdentifier(name) 0856 ); 0857 0858 DUContext* ctx = currentContext(); 0859 Declaration* decl = currentDeclaration(); 0860 0861 { 0862 // Set the inner context of the current declaration, because nested classes 0863 // need to know the inner context of their parents 0864 DUChainWriteLocker lock; 0865 0866 decl->setInternalContext(ctx); 0867 0868 if (contextType == DUContext::Enum) { 0869 ctx->setPropagateDeclarations(true); 0870 } 0871 } 0872 0873 // If we have have declared a class, import the context of its base classes 0874 registerBaseClasses(); 0875 } 0876 0877 void DeclarationBuilder::declareComponentInstance(QmlJS::AST::ExpressionStatement* expression) 0878 { 0879 if (!expression) { 0880 return; 0881 } 0882 0883 auto identifier = QmlJS::AST::cast<QmlJS::AST::IdentifierExpression *>(expression->expression); 0884 0885 if (!identifier) { 0886 return; 0887 } 0888 0889 { 0890 DUChainWriteLocker lock; 0891 0892 injectContext(topContext()); 0893 auto* decl = openDeclaration<Declaration>( 0894 Identifier(identifier->name.toString()), 0895 m_session->locationToRange(identifier->identifierToken) 0896 ); 0897 closeInjectedContext(); 0898 0899 // Put the declaration in the global scope 0900 decl->setKind(Declaration::Instance); 0901 decl->setType(currentAbstractType()); 0902 } 0903 closeDeclaration(); 0904 } 0905 0906 DeclarationBuilder::ExportLiteralsAndNames DeclarationBuilder::exportedNames(QmlJS::AST::ExpressionStatement* exports) 0907 { 0908 ExportLiteralsAndNames res; 0909 0910 if (!exports) { 0911 return res; 0912 } 0913 0914 auto exportslist = QmlJS::AST::cast<QmlJS::AST::ArrayLiteral*>(exports->expression); 0915 0916 if (!exportslist) { 0917 return res; 0918 } 0919 0920 // Explore all the exported symbols for this component and keep only those 0921 // having a version compatible with the one of this module 0922 QSet<QString> knownNames; 0923 0924 for (auto it = exportslist->elements; it && it->expression; it = it->next) { 0925 auto stringliteral = QmlJS::AST::cast<QmlJS::AST::StringLiteral *>(it->expression); 0926 0927 if (!stringliteral) { 0928 continue; 0929 } 0930 0931 // String literal like "Namespace/Class version". 0932 QStringList nameAndVersion = stringliteral->value.toString().section(QLatin1Char('/'), -1, -1).split(QLatin1Char(' ')); 0933 QString name = nameAndVersion.at(0); 0934 0935 if (!knownNames.contains(name)) { 0936 knownNames.insert(name); 0937 res.append(qMakePair(stringliteral, name)); 0938 } 0939 } 0940 0941 return res; 0942 } 0943 0944 0945 void DeclarationBuilder::declareExports(const ExportLiteralsAndNames& exports, 0946 ClassDeclaration* classdecl) 0947 { 0948 DUChainWriteLocker lock; 0949 0950 // Create the exported versions of the component 0951 for (auto& exp : exports) { 0952 QmlJS::AST::StringLiteral* literal = exp.first; 0953 QString name = exp.second; 0954 StructureType::Ptr type(new StructureType); 0955 0956 injectContext(currentContext()->parentContext()); // Don't declare the export in its C++-ish component, but in the scope above 0957 auto* decl = openDeclaration<ClassDeclaration>( 0958 Identifier(name), 0959 m_session->locationToRange(literal->literalToken) 0960 ); 0961 closeInjectedContext(); 0962 0963 // The exported version inherits from the C++ component 0964 decl->setKind(Declaration::Type); 0965 decl->setClassType(ClassDeclarationData::Class); 0966 decl->clearBaseClasses(); 0967 type->setDeclaration(decl); 0968 0969 addBaseClass(decl, classdecl->indexedType()); 0970 0971 // Open a context for the exported class, and register its base class in it 0972 decl->setInternalContext(openContext( 0973 literal, 0974 DUContext::Class, 0975 QualifiedIdentifier(name) 0976 )); 0977 registerBaseClasses(); 0978 closeContext(); 0979 0980 openType(type); 0981 closeAndAssignType(); 0982 } 0983 } 0984 0985 /* 0986 * UI 0987 */ 0988 void DeclarationBuilder::importDirectory(const QString& directory, QmlJS::AST::UiImport* node) 0989 { 0990 DUChainWriteLocker lock; 0991 QString currentFilePath = currentContext()->topContext()->url().str(); 0992 QFileInfo dir(directory); 0993 QFileInfoList entries; 0994 0995 if (dir.isDir()) { 0996 // Import all the files in the given directory 0997 entries = QDir(directory).entryInfoList( 0998 QStringList{ 0999 (QLatin1String("*.") + currentFilePath.section(QLatin1Char('.'), -1, -1)), 1000 QStringLiteral("*.qmltypes"), 1001 QStringLiteral("*.so")}, 1002 QDir::Files 1003 ); 1004 } else if (dir.isFile()) { 1005 // Import the specific file given in the import statement 1006 entries.append(dir); 1007 } else if (!m_prebuilding) { 1008 m_session->addProblem(node, i18n("Module not found, some types or properties may not be recognized")); 1009 return; 1010 } 1011 1012 // Translate the QFileInfos into QStrings (and replace .so files with 1013 // qmlplugindump dumps) 1014 lock.unlock(); 1015 const QStringList filePaths = QmlJS::Cache::instance().getFileNames(entries); 1016 lock.lock(); 1017 1018 if (node && !node->importId.isEmpty()) { 1019 // Open a namespace that will contain the declarations 1020 Identifier identifier(node->importId.toString()); 1021 RangeInRevision range = m_session->locationToRange(node->importIdToken); 1022 1023 auto* decl = openDeclaration<Declaration>(identifier, range); 1024 decl->setKind(Declaration::Namespace); 1025 decl->setInternalContext(openContext(node, range, DUContext::Class, QualifiedIdentifier(identifier))); 1026 } 1027 1028 for (const QString& filePath : filePaths) { 1029 if (filePath == currentFilePath) { 1030 continue; 1031 } 1032 1033 ReferencedTopDUContext context = m_session->contextOfFile(filePath); 1034 1035 if (context) { 1036 currentContext()->addImportedParentContext(context.data()); 1037 } 1038 } 1039 1040 if (node && !node->importId.isEmpty()) { 1041 // Close the namespace containing the declarations 1042 closeContext(); 1043 closeDeclaration(); 1044 } 1045 } 1046 1047 void DeclarationBuilder::importModule(QmlJS::AST::UiImport* node) 1048 { 1049 QmlJS::AST::UiQualifiedId *part = node->importUri; 1050 QString uri; 1051 1052 while (part) { 1053 if (!uri.isEmpty()) { 1054 uri.append(QLatin1Char('.')); 1055 } 1056 1057 uri.append(part->name.toString()); 1058 part = part->next; 1059 } 1060 1061 // Version of the import 1062 QString version = m_session->symbolAt(node->versionToken); 1063 1064 // Import the directory containing the module 1065 QString modulePath = QmlJS::Cache::instance().modulePath(m_session->url(), uri, version); 1066 importDirectory(modulePath, node); 1067 } 1068 1069 bool DeclarationBuilder::visit(QmlJS::AST::UiImport* node) 1070 { 1071 if (node->importUri) { 1072 importModule(node); 1073 } else if (!node->fileName.isEmpty() && node->fileName != QLatin1String(".")) { 1074 QUrl currentFileUrl = currentContext()->topContext()->url().toUrl(); 1075 QUrl importUrl = QUrl(node->fileName.toString()); 1076 1077 importDirectory(currentFileUrl.resolved(importUrl).toLocalFile(), node); 1078 } 1079 1080 return DeclarationBuilderBase::visit(node); 1081 } 1082 1083 bool DeclarationBuilder::visit(QmlJS::AST::UiObjectDefinition* node) 1084 { 1085 setComment(node); 1086 1087 // Do not crash if the user has typed an empty object definition 1088 if (!node->initializer || !node->initializer->members) { 1089 m_skipEndVisit.push(true); 1090 return DeclarationBuilderBase::visit(node); 1091 } 1092 1093 RangeInRevision range(m_session->locationToRange(node->qualifiedTypeNameId->identifierToken)); 1094 QString baseclass = node->qualifiedTypeNameId->name.toString(); 1095 1096 // "Component" needs special care: a component that appears only in a future 1097 // version of this module, or that already appeared in a former version, must 1098 // be skipped because it is useless 1099 ExportLiteralsAndNames exports; 1100 1101 if (baseclass == QLatin1String("Component")) { 1102 QmlJS::AST::Statement* statement = QmlJS::getQMLAttribute(node->initializer->members, QStringLiteral("exports")); 1103 1104 exports = exportedNames(QmlJS::AST::cast<QmlJS::AST::ExpressionStatement *>(statement)); 1105 1106 if (statement && exports.count() == 0) { 1107 // This component has an "exports:" member but no export matched 1108 // the version of this module. Skip the component 1109 m_skipEndVisit.push(true); 1110 return false; 1111 } 1112 } else if (baseclass == QLatin1String("Module")) { 1113 // "Module" is disabled. This allows the declarations of a module 1114 // dump to appear in the same namespace as the .qml files in the same 1115 // directory. 1116 m_skipEndVisit.push(true); 1117 return true; 1118 } 1119 1120 // Declare the component subclass 1121 declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); 1122 1123 // If we had a component with exported names, declare these exports 1124 if (baseclass == QLatin1String("Component")) { 1125 auto* classDecl = currentDeclaration<ClassDeclaration>(); 1126 1127 if (classDecl) { 1128 declareExports(exports, classDecl); 1129 } 1130 } 1131 1132 m_skipEndVisit.push(false); 1133 return DeclarationBuilderBase::visit(node); 1134 } 1135 1136 void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectDefinition* node) 1137 { 1138 DeclarationBuilderBase::endVisit(node); 1139 1140 // Do not crash if the user has typed an empty object definition 1141 if (!m_skipEndVisit.pop()) { 1142 closeContext(); 1143 closeAndAssignType(); 1144 } 1145 } 1146 1147 bool DeclarationBuilder::visit(QmlJS::AST::UiScriptBinding* node) 1148 { 1149 setComment(node); 1150 1151 if (!node->qualifiedId) { 1152 return DeclarationBuilderBase::visit(node); 1153 } 1154 1155 // Special-case some binding names 1156 QString bindingName = node->qualifiedId->name.toString(); 1157 1158 if (bindingName == QLatin1String("id")) { 1159 // Instantiate a QML component: its type is the current type (the anonymous 1160 // QML class that surrounds the declaration) 1161 declareComponentInstance(QmlJS::AST::cast<QmlJS::AST::ExpressionStatement *>(node->statement)); 1162 } 1163 1164 // Use ExpressionVisitor to find the signal/property bound 1165 DeclarationPointer bindingDecl = findType(node->qualifiedId).declaration; 1166 DUChainPointer<ClassFunctionDeclaration> signal; 1167 1168 // If a Javascript block is used as expression or if the script binding is a 1169 // slot, open a subcontext so that variables declared in the binding are kept 1170 // local, and the signal parameters can be visible to the slot 1171 if (( 1172 bindingDecl && 1173 (signal = bindingDecl.dynamicCast<ClassFunctionDeclaration>()) && 1174 signal->isSignal() 1175 ) || 1176 node->statement->kind == QmlJS::AST::Node::Kind_Block) { 1177 1178 openContext( 1179 node->statement, 1180 m_session->locationsToInnerRange( 1181 node->statement->firstSourceLocation(), 1182 node->statement->lastSourceLocation() 1183 ), 1184 DUContext::Other 1185 ); 1186 1187 // If this script binding is a slot, import the parameters of its signal 1188 if (signal && signal->isSignal() && signal->internalContext()) { 1189 DUChainWriteLocker lock; 1190 1191 currentContext()->addIndirectImport(DUContext::Import( 1192 signal->internalContext(), 1193 nullptr 1194 )); 1195 } 1196 } else { 1197 // Check that the type of the value matches the type of the property 1198 AbstractType::Ptr expressionType = findType(node->statement).type; 1199 DUChainReadLocker lock; 1200 1201 if (!m_prebuilding && bindingDecl && !areTypesEqual(bindingDecl->abstractType(), expressionType)) { 1202 m_session->addProblem(node->qualifiedId, i18n( 1203 "Mismatch between the value type (%1) and the property type (%2)", 1204 expressionType->toString(), 1205 bindingDecl->abstractType()->toString() 1206 ), IProblem::Error); 1207 } 1208 } 1209 1210 return DeclarationBuilderBase::visit(node); 1211 } 1212 1213 void DeclarationBuilder::endVisit(QmlJS::AST::UiScriptBinding* node) 1214 { 1215 QmlJS::AST::Visitor::endVisit(node); 1216 1217 // If visit(UiScriptBinding) has opened a context, close it 1218 if (currentContext()->type() == DUContext::Other) { 1219 closeContext(); 1220 } 1221 } 1222 1223 bool DeclarationBuilder::visit(QmlJS::AST::UiObjectBinding* node) 1224 { 1225 setComment(node); 1226 1227 if (!node->qualifiedId || !node->qualifiedTypeNameId || !node->initializer) { 1228 return DeclarationBuilderBase::visit(node); 1229 } 1230 1231 // Declare the component subclass. "Behavior on ... {}" is treated exactly 1232 // like "Behavior {}". 1233 RangeInRevision range = m_session->locationToRange(node->qualifiedTypeNameId->identifierToken); 1234 QString baseclass = node->qualifiedTypeNameId->name.toString(); 1235 1236 declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); 1237 1238 return DeclarationBuilderBase::visit(node); 1239 } 1240 1241 void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectBinding* node) 1242 { 1243 DeclarationBuilderBase::endVisit(node); 1244 1245 if (node->qualifiedId && node->qualifiedTypeNameId && node->initializer) { 1246 closeContext(); 1247 closeAndAssignType(); 1248 } 1249 } 1250 1251 bool DeclarationBuilder::visit(QmlJS::AST::UiPublicMember* node) 1252 { 1253 setComment(node); 1254 1255 RangeInRevision range = m_session->locationToRange(node->identifierToken); 1256 Identifier id(node->name.toString()); 1257 QString typeName = node->memberTypeName().toString(); 1258 bool res = DeclarationBuilderBase::visit(node); 1259 1260 // Build the type of the public member 1261 if (node->type == QmlJS::AST::UiPublicMember::Signal) { 1262 // Open a function declaration corresponding to this signal 1263 declareFunction<ClassFunctionDeclaration>( 1264 node, 1265 false, 1266 Identifier(node->name.toString()), 1267 m_session->locationToRange(node->identifierToken), 1268 node->parameters, 1269 m_session->locationToRange(node->identifierToken), // The AST does not provide the location of the parens 1270 nullptr, 1271 m_session->locationToRange(node->identifierToken) // A body range must be provided 1272 ); 1273 1274 // This declaration is a signal and its return type is void 1275 { 1276 DUChainWriteLocker lock; 1277 1278 currentDeclaration<ClassFunctionDeclaration>()->setIsSignal(true); 1279 currentType<QmlJS::FunctionType>()->setReturnType(typeFromName(QStringLiteral("void"))); 1280 } 1281 } else { 1282 AbstractType::Ptr type; 1283 1284 if (typeName == QLatin1String("alias")) { 1285 // Property aliases take the type of their aliased property 1286 type = findType(node->statement).type; 1287 res = false; // findType has already explored node->statement 1288 } else { 1289 type = typeFromName(typeName); 1290 1291 if (node->typeModifier == QLatin1String("list")) { 1292 // QML list, noted "list<type>" in the source file 1293 ArrayType::Ptr array(new ArrayType); 1294 array->setElementType(type); 1295 type = array; 1296 } 1297 } 1298 1299 { 1300 DUChainWriteLocker lock; 1301 Declaration* decl = openDeclaration<ClassMemberDeclaration>(id, range); 1302 1303 decl->setInSymbolTable(false); 1304 } 1305 openType(type); 1306 } 1307 1308 return res; 1309 } 1310 1311 void DeclarationBuilder::endVisit(QmlJS::AST::UiPublicMember* node) 1312 { 1313 DeclarationBuilderBase::endVisit(node); 1314 1315 closeAndAssignType(); 1316 } 1317 1318 /* 1319 * Utils 1320 */ 1321 void DeclarationBuilder::setComment(QmlJS::AST::Node* node) 1322 { 1323 setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); 1324 } 1325 1326 void DeclarationBuilder::closeAndAssignType() 1327 { 1328 closeType(); 1329 Declaration* dec = currentDeclaration(); 1330 Q_ASSERT(dec); 1331 1332 if (auto type = lastType()) { 1333 DUChainWriteLocker lock; 1334 dec->setType(type); 1335 } 1336 closeDeclaration(); 1337 } 1338 1339 AbstractType::Ptr DeclarationBuilder::typeFromName(const QString& name) 1340 { 1341 auto type = IntegralType::TypeNone; 1342 QString realName = name; 1343 1344 // Built-in types 1345 if (name == QLatin1String("string")) { 1346 type = IntegralType::TypeString; 1347 } else if (name == QLatin1String("bool")) { 1348 type = IntegralType::TypeBoolean; 1349 } else if (name == QLatin1String("int")) { 1350 type = IntegralType::TypeInt; 1351 } else if (name == QLatin1String("half")) { 1352 type = IntegralType::TypeHalf; 1353 } else if (name == QLatin1String("float")) { 1354 type = IntegralType::TypeFloat; 1355 } else if (name == QLatin1String("double") || name == QLatin1String("real")) { 1356 type = IntegralType::TypeDouble; 1357 } else if (name == QLatin1String("void")) { 1358 type = IntegralType::TypeVoid; 1359 } else if (name == QLatin1String("var") || name == QLatin1String("variant")) { 1360 type = IntegralType::TypeMixed; 1361 } else if (m_session->language() == QmlJS::Dialect::Qml) { 1362 // In QML files, some Qt type names need to be renamed to the QML equivalent 1363 if (name == QLatin1String("QFont")) { 1364 realName = QStringLiteral("Font"); 1365 } else if (name == QLatin1String("QColor")) { 1366 realName = QStringLiteral("color"); 1367 } else if (name == QLatin1String("QDateTime")) { 1368 realName = QStringLiteral("date"); 1369 } else if (name == QLatin1String("QDate")) { 1370 realName = QStringLiteral("date"); 1371 } else if (name == QLatin1String("QTime")) { 1372 realName = QStringLiteral("time"); 1373 } else if (name == QLatin1String("QRect") || name == QLatin1String("QRectF")) { 1374 realName = QStringLiteral("rect"); 1375 } else if (name == QLatin1String("QPoint") || name == QLatin1String("QPointF")) { 1376 realName = QStringLiteral("point"); 1377 } else if (name == QLatin1String("QSize") || name == QLatin1String("QSizeF")) { 1378 realName = QStringLiteral("size"); 1379 } else if (name == QLatin1String("QUrl")) { 1380 realName = QStringLiteral("url"); 1381 } else if (name == QLatin1String("QVector3D")) { 1382 realName = QStringLiteral("vector3d"); 1383 } else if (name.endsWith(QLatin1String("ScriptString"))) { 1384 // Q{Declarative,Qml}ScriptString represents a JS snippet 1385 auto func = new QmlJS::FunctionType; 1386 func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); 1387 return AbstractType::Ptr(func); 1388 } 1389 } 1390 1391 if (type == IntegralType::TypeNone) { 1392 // Not a built-in type, but a class 1393 return typeFromClassName(realName); 1394 } else { 1395 return AbstractType::Ptr(new IntegralType(type)); 1396 } 1397 } 1398 1399 AbstractType::Ptr DeclarationBuilder::typeFromClassName(const QString& name) 1400 { 1401 DeclarationPointer decl = QmlJS::getDeclaration(QualifiedIdentifier(name), currentContext()); 1402 1403 if (!decl) { 1404 if (name == QLatin1String("QRegExp")) { 1405 decl = QmlJS::NodeJS::instance().moduleMember(QStringLiteral("__builtin_ecmascript"), QStringLiteral("RegExp"), currentContext()->url()); 1406 } 1407 } 1408 1409 if (decl) { 1410 return decl->abstractType(); 1411 } else { 1412 DelayedType::Ptr type(new DelayedType); 1413 type->setKind(DelayedType::Unresolved); 1414 type->setIdentifier(IndexedTypeIdentifier(name)); 1415 return type; 1416 } 1417 } 1418 1419 void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const QString& name) 1420 { 1421 addBaseClass(classDecl, IndexedType(typeFromClassName(name))); 1422 } 1423 1424 void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const IndexedType& type) 1425 { 1426 BaseClassInstance baseClass; 1427 1428 baseClass.access = Declaration::Public; 1429 baseClass.virtualInheritance = false; 1430 baseClass.baseClass = type; 1431 1432 classDecl->addBaseClass(baseClass); 1433 } 1434 1435 void DeclarationBuilder::registerBaseClasses() 1436 { 1437 auto* classdecl = currentDeclaration<ClassDeclaration>(); 1438 DUContext *ctx = currentContext(); 1439 1440 if (classdecl) { 1441 DUChainWriteLocker lock; 1442 1443 for (uint i=0; i<classdecl->baseClassesSize(); ++i) 1444 { 1445 const BaseClassInstance &baseClass = classdecl->baseClasses()[i]; 1446 auto baseType = baseClass.baseClass.abstractType().dynamicCast<StructureType>(); 1447 TopDUContext* topctx = topContext(); 1448 1449 if (baseType && baseType->declaration(topctx)) { 1450 QmlJS::importDeclarationInContext(ctx, DeclarationPointer(baseType->declaration(topctx))); 1451 } 1452 } 1453 } 1454 } 1455 1456 static bool enumContainsEnumerator(const AbstractType::Ptr& a, const AbstractType::Ptr& b) 1457 { 1458 Q_ASSERT(a->whichType() == AbstractType::TypeEnumeration); 1459 auto aEnum = a.staticCast<EnumerationType>(); 1460 Q_ASSERT(b->whichType() == AbstractType::TypeEnumerator); 1461 auto bEnumerator = b.staticCast<EnumeratorType>(); 1462 return bEnumerator->qualifiedIdentifier().beginsWith(aEnum->qualifiedIdentifier()); 1463 } 1464 1465 static bool isNumeric(const IntegralType::Ptr& type) 1466 { 1467 return type->dataType() == IntegralType::TypeInt 1468 || type->dataType() == IntegralType::TypeIntegral 1469 || type->dataType() == IntegralType::TypeHalf 1470 || type->dataType() == IntegralType::TypeFloat 1471 || type->dataType() == IntegralType::TypeDouble; 1472 } 1473 1474 bool DeclarationBuilder::areTypesEqual(const AbstractType::Ptr& a, const AbstractType::Ptr& b) 1475 { 1476 if (!a || !b) { 1477 return true; 1478 } 1479 1480 if (a->whichType() == AbstractType::TypeUnsure || b->whichType() == AbstractType::TypeUnsure) { 1481 // Don't try to guess something if one of the types is unsure 1482 return true; 1483 } 1484 1485 const auto bIntegral = b.dynamicCast<IntegralType>(); 1486 if (bIntegral && (bIntegral->dataType() == IntegralType::TypeString || bIntegral->dataType() == IntegralType::TypeMixed)) { 1487 // In QML/JS, a string can be converted to nearly everything else, similarly ignore mixed types 1488 return true; 1489 } 1490 1491 const auto aIntegral = a.dynamicCast<IntegralType>(); 1492 if (aIntegral && (aIntegral->dataType() == IntegralType::TypeString || aIntegral->dataType() == IntegralType::TypeMixed)) { 1493 // In QML/JS, nearly everything can be to a string, similarly ignore mixed types 1494 return true; 1495 } 1496 if (aIntegral && bIntegral) { 1497 if (isNumeric(aIntegral) && isNumeric(bIntegral)) { 1498 // Casts between integral types is possible 1499 return true; 1500 } 1501 } 1502 1503 if (a->whichType() == AbstractType::TypeEnumeration && b->whichType() == AbstractType::TypeEnumerator) { 1504 return enumContainsEnumerator(a, b); 1505 } else if (a->whichType() == AbstractType::TypeEnumerator && b->whichType() == AbstractType::TypeEnumeration) { 1506 return enumContainsEnumerator(b, a); 1507 } 1508 1509 { 1510 auto aId = dynamic_cast<const IdentifiedType*>(a.constData()); 1511 auto bId = dynamic_cast<const IdentifiedType*>(b.constData()); 1512 if (aId && bId && aId->qualifiedIdentifier() == bId->qualifiedIdentifier()) 1513 return true; 1514 } 1515 1516 { 1517 auto aStruct = a.dynamicCast<StructureType>(); 1518 auto bStruct = b.dynamicCast<StructureType>(); 1519 if (aStruct && bStruct) { 1520 auto top = currentContext()->topContext(); 1521 auto aDecl = dynamic_cast<ClassDeclaration*>(aStruct->declaration(top)); 1522 auto bDecl = dynamic_cast<ClassDeclaration*>(bStruct->declaration(top)); 1523 if (aDecl && bDecl) { 1524 if (aDecl->isPublicBaseClass(bDecl, top) || bDecl->isPublicBaseClass(aDecl, top)) { 1525 return true; 1526 } 1527 } 1528 } 1529 } 1530 1531 return a->equals(b.constData()); 1532 }