File indexing completed on 2024-05-19 04:41:59

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 }