diff --git a/src/qmljsc/CMakeLists.txt b/src/qmljsc/CMakeLists.txt --- a/src/qmljsc/CMakeLists.txt +++ b/src/qmljsc/CMakeLists.txt @@ -12,6 +12,7 @@ ir/typesystem.cpp ir/builtintypes.cpp + compilerpasses/asttoirpass.cpp compilerpasses/parserpass.cpp compilerpasses/prettygeneratorpass.cpp diff --git a/src/qmljsc/compilerpasses/asttoirpass.h b/src/qmljsc/compilerpasses/asttoirpass.h new file mode 100644 --- /dev/null +++ b/src/qmljsc/compilerpasses/asttoirpass.h @@ -0,0 +1,71 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2016 Anton Kreuzkamp + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef ASTTOIR_H +#define ASTTOIR_H + +#include +#include + +#include "../ir/objecttree.h" +#include "../compilerpass.h" + +namespace QmlJSc { + +class AstToIRPass : public CompilerPass, public QQmlJS::AST::Visitor +{ + Q_OBJECT + +public: + AstToIRPass(); + ~AstToIRPass(); + +public slots: + void process(QQmlJS::AST::UiProgram *input) override; + + +private: + + bool visit(QQmlJS::AST::UiProgram *program) override; + bool visit(QQmlJS::AST::UiImport *import) override; + bool visit(QQmlJS::AST::UiObjectDefinition *object) override; + bool visit(QQmlJS::AST::UiScriptBinding *scriptBinding) override; + bool visit(QQmlJS::AST::UiObjectBinding *member) override; + bool visit(QQmlJS::AST::UiArrayBinding *array) override; + bool visit(QQmlJS::AST::UiPublicMember *member) override; + bool visit(QQmlJS::AST::FunctionDeclaration *function) override; + + void endVisit(QQmlJS::AST::UiArrayBinding *array) override; + void endVisit(QQmlJS::AST::UiObjectBinding *member) override; + void endVisit(QQmlJS::AST::UiObjectDefinition *object) override; + void endVisit(QQmlJS::AST::UiProgram *program) override; + + void assert(bool condition, const QQmlJS::AST::SourceLocation &token, QString errorMessage); + + IR::File *m_file; + QStack m_stack; + + friend class TestAstToIR; +}; + +} + +#endif // ASTTOIR_H + diff --git a/src/qmljsc/compilerpasses/asttoirpass.cpp b/src/qmljsc/compilerpasses/asttoirpass.cpp new file mode 100644 --- /dev/null +++ b/src/qmljsc/compilerpasses/asttoirpass.cpp @@ -0,0 +1,326 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2016 Anton Kreuzkamp + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "asttoirpass.h" +#include +#include +#include + +#include + +using namespace QmlJSc; + +AstToIRPass::AstToIRPass() +{ +} + +AstToIRPass::~AstToIRPass() +{ + +} + +void AstToIRPass::process(QQmlJS::AST::UiProgram *input) +{ + m_file = new IR::File; + + input->accept(this); + + emit finished(m_file); +} + +bool AstToIRPass::visit(QQmlJS::AST::UiProgram* program) +{ + m_stack << m_file; + return true; +} + + +bool AstToIRPass::visit(QQmlJS::AST::UiImport *import) +{ + Q_ASSERT(import); + IR::ImportDescription desc; + + if (!import->fileName.isEmpty()) { + // TODO: Decide between directory and file import + desc.kind = IR::ImportDescription::Kind_FileImport; + desc.name = import->fileName.toString(); + } else if (import->importUri && !import->importUri->name.isEmpty()) { + desc.kind = IR::ImportDescription::Kind_ModuleImport; + QQmlJS::AST::UiQualifiedId *id = import->importUri; + desc.name = id->name.toString(); + while ((id = id->next)){ + desc.name += "."; + desc.name += id->name; + } + QStringRef versionRef(import->importUri->name.string(), import->versionToken.offset, import->versionToken.length); + QmlIR::IRBuilder::extractVersion(versionRef, &desc.versionMajor, &desc.versionMinor); + + IR::Module *module = 0; + try { + module = ModuleLoading::loadModule(desc); + } catch (Error *error) { + QQmlJS::AST::SourceLocation token = import->fileNameToken; + error->setLine(token.startLine); + error->setColumn(token.startColumn); + throw error; + } +// assert(module->loadingState() == IR::Module::Successful, import->firstSourceLocation(), QStringLiteral("Can't load module %1.").arg(desc.name)); + m_file->addModule(module, import->importId.toString()); + } else { + throw new Error(Error::ModuleImportError, QStringLiteral("Invalid Module Description.")); + } + + return true; +} + +bool AstToIRPass::visit(QQmlJS::AST::UiObjectDefinition *astObject) +{ + Q_ASSERT(astObject); + Q_ASSERT(astObject->qualifiedTypeNameId); + IR::ObjectSpec *irObject = new IR::ObjectSpec; + QString typeName = astObject->qualifiedTypeNameId->name.toString(); + IR::Type *type = m_file->type(typeName); + assert(type, astObject->qualifiedTypeNameId->firstSourceLocation(), + QStringLiteral("Unknown type %1.").arg(typeName)); + assert(type->flags() & IR::Type::IsInstantiable, astObject->qualifiedTypeNameId->firstSourceLocation(), + QStringLiteral("Trying to instantiate uninstantiable type %1.").arg(typeName)); + irObject->setSuper(type); + m_stack.push(irObject); + return true; +} + +bool AstToIRPass::visit(QQmlJS::AST::UiScriptBinding* scriptBinding) +{ + IR::ObjectSpec *irObject = dynamic_cast(m_stack.last()); + Q_ASSERT(irObject); + QString name = scriptBinding->qualifiedId->name.toString(); + + // Check if it's a signal handler and if so, treat it as such + if (name.startsWith("on") && name[2].isUpper()) { + QString signalName = name.mid(2); + signalName[0] = signalName[0].toLower(); + IR::Signal *signal = irObject->signal(signalName); + assert(signal, scriptBinding->firstSourceLocation(), + QStringLiteral("Can't create handler, type %1 has no signal %2.").arg(irObject->super()->name()).arg(name)); + IR::SignalHandler *handler = irObject->addSignalHandler(signal); + handler->setHandlerCode(scriptBinding->statement); + return false; + } + + IR::Property *prop = irObject->property(name); + assert(prop, scriptBinding->firstSourceLocation(), + QStringLiteral("Type %1 has no property %2.").arg(irObject->super()->name()).arg(name)); + IR::ValueAssignment *assignment = irObject->addValueAssignment(prop); + assignment->assignJsValue(scriptBinding->statement); + return false; +} + +bool AstToIRPass::visit(QQmlJS::AST::UiObjectBinding* member) +{ + Q_ASSERT(member); + Q_ASSERT(member->qualifiedTypeNameId); + IR::ObjectSpec *childObject = new IR::ObjectSpec; + QString typeName = member->qualifiedTypeNameId->name.toString(); + IR::Type *type = m_file->type(typeName); + assert(type, member->qualifiedTypeNameId->firstSourceLocation(), + QStringLiteral("Unknown type %1.").arg(typeName)); + assert(type->flags() & IR::Type::IsInstantiable, member->qualifiedTypeNameId->firstSourceLocation(), + QStringLiteral("Trying to instantiate uninstantiable type %1.").arg(typeName)); + childObject->setSuper(type); + m_stack.push(childObject); + return true; +} + +bool AstToIRPass::visit(QQmlJS::AST::UiArrayBinding* array) +{ + Q_ASSERT(array); + Q_ASSERT(array->qualifiedId); + IR::ObjectSpec *irObject = dynamic_cast(m_stack.last()); + Q_ASSERT(irObject); + + QString name = array->qualifiedId->name.toString(); + IR::Property *prop = irObject->property(name); + assert(prop, array->firstSourceLocation(), + QStringLiteral("Type %1 has no property %2.").arg(irObject->super()->name()).arg(name)); + assert(prop->type->flags() & IR::Type::IsList, array->qualifiedId->firstSourceLocation(), + QStringLiteral("Trying to assign a list to property %1 with non-list type %2.").arg(prop->name, prop->type->name())); + IR::BuiltinTypes::List *listType = static_cast(prop->type); + assert(listType->flags() & IR::Type::IsInstantiable, array->qualifiedId->firstSourceLocation(), + QStringLiteral("Trying to assign to property %1, but list argument is uninstantiable type %2.").arg(prop->name, listType->contentType()->name())); + IR::ValueAssignment *assignment = irObject->addValueAssignment(prop); + assignment->setAssignmentType(IR::ValueAssignment::ObjectList); + m_stack << assignment; + return true; +} + +bool AstToIRPass::visit(QQmlJS::AST::UiPublicMember* member) +{ + IR::ObjectSpec *irObject = dynamic_cast(m_stack.last()); + Q_ASSERT(irObject); + QString name = member->name.toString(); + assert(!irObject->hasMember(name), member->identifierToken, QStringLiteral("Object already has a member named %1.").arg(name)); + + if (member->type == QQmlJS::AST::UiPublicMember::Property) { + IR::Property *property = irObject->addProperty(name); + property->readOnly = member->isReadonlyMember; + + + QString typeName = member->memberType.toString(); + IR::Type *type = m_file->type(typeName); + if (member->typeModifier == QStringLiteral("list")) { + assert(type, member->typeToken, + QStringLiteral("List argument is unknown type %1.").arg(typeName)); + property->type = new IR::BuiltinTypes::List(type); + } else { + assert(type, member->typeToken, + QStringLiteral("Unknown type %1.").arg(typeName)); + property->type = type; + } + + if (member->isDefaultMember) { + irObject->setDefaultProperty(property); + } + + // Default value + if (member->statement) { + IR::ValueAssignment *assignment = irObject->addValueAssignment(property); + assignment->assignJsValue(member->statement); + } else { + Q_ASSERT(member->binding); + // We don't need to put anything on the stack here, because the subtree + // consists of a UiObjectBinding or a UiArrayBinding. Both contain the + // property name again and thus their respective visitor treats them + // like a normal property assignment. + } + } else { + Q_ASSERT(member->type == QQmlJS::AST::UiPublicMember::Signal); + IR::Signal *signal = irObject->addSignal(name); + for (QQmlJS::AST::UiParameterList *param = member->parameters; param; param = param->next) { + QString typeName = param->type.toString(); + QString name = param->name.toString(); + IR::Type *type = m_file->type(typeName); + assert(type, param->propertyTypeToken, QStringLiteral("Parameter %1 has unknown type %2.").arg(name, typeName)); + signal->parameters.append({type, name}); + } + } + + return true; +} + +bool AstToIRPass::visit(QQmlJS::AST::FunctionDeclaration* function) +{ + IR::ObjectSpec *irObject = dynamic_cast(m_stack.last()); + Q_ASSERT(irObject); + QString name = function->name.toString(); + assert(!irObject->hasMember(name), function->identifierToken, QStringLiteral("Object already has a member named %1.").arg(name)); + + IR::Method *method = irObject->addMethod(name); + for (QQmlJS::AST::FormalParameterList *param = function->formals; param; param = param->next) { + method->parameters.append(param->name.toString()); + } + + method->body = function->body; + + return true; +} + +void AstToIRPass::endVisit(QQmlJS::AST::UiArrayBinding *array) +{ + IR::ValueAssignment *assignment = dynamic_cast(m_stack.pop()); + Q_ASSERT(assignment); +} + +void AstToIRPass::endVisit(QQmlJS::AST::UiObjectBinding *astChildObject) +{ + IR::ObjectSpec *irChildObject = dynamic_cast(m_stack.pop()); + Q_ASSERT(irChildObject); + IR::ObjectSpec *irParentObject = dynamic_cast(m_stack.last()); + Q_ASSERT(irParentObject); + + Q_ASSERT(astChildObject); + Q_ASSERT(astChildObject->qualifiedId); + QString propertyName = astChildObject->qualifiedId->name.toString(); + IR::Property *property = irParentObject->property(propertyName); + IR::ValueAssignment *assignment = irParentObject->addValueAssignment(property); + assert(property, astChildObject->qualifiedId->firstSourceLocation(), + QStringLiteral("Type %1 has no property %2.").arg(irParentObject->super()->name(), propertyName)); + assert(property->type->flags() & IR::Type::IsInstantiable, astChildObject->qualifiedId->firstSourceLocation(), + QStringLiteral("Trying to assign to property %1 of uninstantiable type %2.").arg(property->name, property->type->name())); + + if (property->type->flags() & IR::Type::IsList) { + assignment->addToObjectList(irChildObject); + } else { + assignment->assignObject(irChildObject); + } +} + +void AstToIRPass::endVisit(QQmlJS::AST::UiObjectDefinition *astObject) +{ + IR::ObjectSpec *irObject = dynamic_cast(m_stack.pop()); + Q_ASSERT(irObject); + + if (m_stack.last()->kind() == IR::Node::Kind_ValueAssignment) { + IR::ValueAssignment *assignment = dynamic_cast(m_stack.last()); + if (assignment->assignmentType() == IR::ValueAssignment::ObjectList) { + assignment->addToObjectList(irObject); + } else { + Q_ASSERT(assignment->assignmentType() == IR::ValueAssignment::ObjectAssignment); + assignment->assignObject(irObject); + } + + } else if (m_stack.last()->kind() == IR::Node::Kind_File) { + Q_ASSERT(m_stack.size() == 1); + dynamic_cast(m_stack.last())->setRootObject(irObject); + } else { + IR::ObjectSpec *parent = dynamic_cast(m_stack.last()); + Q_ASSERT(parent); + if (parent->defaultProperty()->type->flags() & IR::Type::IsList) { + IR::ValueAssignment *list = parent->valueAssignmentFor(parent->defaultProperty()); + if (list) { + list->addToObjectList(irObject); + } else { + parent->addValueAssignment(parent->defaultProperty())->addToObjectList(irObject); + } + } else { + parent->addValueAssignment(parent->defaultProperty())->assignObject(irObject); + } + } +} + +void AstToIRPass::endVisit(QQmlJS::AST::UiProgram* program) +{ + Q_ASSERT(m_stack.size() == 1); + m_stack.pop(); +} + +void AstToIRPass::assert(bool condition, const QQmlJS::AST::SourceLocation& token, QString errorMessage) +{ + if (condition) { + return; + } + // Normally you wouldn't throw by reference but by value. We need to create + // the error object on the heap and throw by pointer, because we'll need it + // to be available on another thread. + Error *error = new Error(Error::SymbolLookupError, errorMessage); + error->setLine(token.startLine); + error->setColumn(token.startColumn); + throw error; +} + diff --git a/src/qmljsc/ir/builtintypes.h b/src/qmljsc/ir/builtintypes.h --- a/src/qmljsc/ir/builtintypes.h +++ b/src/qmljsc/ir/builtintypes.h @@ -22,13 +22,12 @@ #define BUILTINTYPES_H #include +#include "typesystem.h" namespace QmlJSc { namespace IR { -class Type; - class BuiltinTypes { public: @@ -38,12 +37,21 @@ static Type *doubleType(); static Type *enumType(); static Type *intType(); - static Type *listType(); static Type *realType(); static Type *stringType(); static Type *urlType(); static Type *varType(); + class List : public Type { + public: + explicit List(Type *contentType); + + Type *contentType() const; + + private: + Type *m_contentType; + }; + private: static bool init(); diff --git a/src/qmljsc/ir/builtintypes.cpp b/src/qmljsc/ir/builtintypes.cpp --- a/src/qmljsc/ir/builtintypes.cpp +++ b/src/qmljsc/ir/builtintypes.cpp @@ -20,7 +20,6 @@ // Own #include "builtintypes.h" -#include "ir/typesystem.h" using namespace QmlJSc::IR; @@ -30,7 +29,6 @@ {"double", "QWDouble"}, {"enum", "QWEnum"}, {"int", "QWInt"}, - {"list", "QWList", Type::IsList}, {"real", "QWReal"}, {"string", "String"}, {"url", "QWUrl"}, @@ -77,11 +75,6 @@ return &s_builtinTypes[3]; } -Type *BuiltinTypes::listType() -{ - return &s_builtinTypes[4]; -} - Type *BuiltinTypes::realType() { return &s_builtinTypes[5]; @@ -102,3 +95,15 @@ return &s_builtinTypes[8]; } +BuiltinTypes::List::List(Type* contentType) + : Type("list", "QWList", Type::IsList) + , m_contentType(contentType) +{ + setFlags(flags() | m_contentType->flags()); +} + +Type *BuiltinTypes::List::contentType() const +{ + return m_contentType; +} + diff --git a/src/qmljsc/ir/file.h b/src/qmljsc/ir/file.h --- a/src/qmljsc/ir/file.h +++ b/src/qmljsc/ir/file.h @@ -35,37 +35,41 @@ namespace IR { class Module; -struct Type; +class Type; class File : public Node { +public: struct ModuleData { Module *module; QString localPrefix; bool operator==(const ModuleData &other) const; }; -public: explicit File(); virtual ~File(); - void addModule(Module *module); + virtual Node::Kind kind() const override; + + void addModule(Module *module, const QString &prefix = QString()); Type* type(const QString &typeName) const; QString fullyQualifiedName(const QString &typeName); - Object *rootObject() const; - void setRootObject(Object *root); + ObjectSpec *rootObject() const; + void setRootObject(ObjectSpec *root); virtual void accept(Visitor * visitor) override; + const QVector &importedModules() const; + private: const ModuleData *moduleForType(const QString &typeName) const; QVector m_importedModules; ShortSymbolName m_prefix; - Object *m_rootObject; + ObjectSpec *m_rootObject; }; } // namespace IR diff --git a/src/qmljsc/ir/file.cpp b/src/qmljsc/ir/file.cpp --- a/src/qmljsc/ir/file.cpp +++ b/src/qmljsc/ir/file.cpp @@ -19,6 +19,7 @@ */ #include "file.h" +#include "builtintypes.h" #include "module.h" #include "../utils/error.h" #include "../compiler.h" @@ -41,19 +42,31 @@ { } -void File::addModule(Module *module) +Node::Kind File::kind() const +{ + return Node::Kind_File; +} + +void File::addModule(Module *module, const QString &prefix) { if (m_importedModules.contains({ module, QString() })) return; - m_importedModules.append({ - module, - ++m_prefix - }); + ModuleData moduleData; + moduleData.module = module; + if (!prefix.isEmpty()) { + moduleData.localPrefix = prefix; + } else { + moduleData.localPrefix = ++m_prefix; + } + m_importedModules.append(moduleData); } Type *File::type(const QString &typeName) const { + if (Type *builtinType = IR::BuiltinTypes::type(typeName)) { + return builtinType; + } const ModuleData *data = moduleForType(typeName); if (data && data->module) return data->module->type(typeName); @@ -66,12 +79,12 @@ return QStringLiteral("%1.%2").arg(moduleForType(typeName)->localPrefix, typeName); } -Object *File::rootObject() const +ObjectSpec *File::rootObject() const { return m_rootObject; } -void File::setRootObject(Object *rootObject) +void File::setRootObject(ObjectSpec *rootObject) { m_rootObject = rootObject; } @@ -104,3 +117,8 @@ m_rootObject->accept(visitor); visitor->endVisit(this); } + +const QVector &File::importedModules() const +{ + return m_importedModules; +} diff --git a/src/qmljsc/ir/objecttree.h b/src/qmljsc/ir/objecttree.h --- a/src/qmljsc/ir/objecttree.h +++ b/src/qmljsc/ir/objecttree.h @@ -23,94 +23,168 @@ #define QMLWEB_IR_H #include -#include +#include #include #include "typesystem.h" namespace QQmlJS { - namespace AST { - class ExpressionNode; - } +namespace AST { +class Statement; +} } namespace QmlJSc { namespace IR { class Visitor; +/** + * Base class for nodes in object tree. + */ class Node { - public: enum Kind { Kind_Invalid, - Kind_Property, - Kind_Method, - Kind_Signal, - Kind_BasicType, - Kind_List, - Kind_Class, - Kind_Component, - Kind_Object + Kind_ValueAssignment, + Kind_SignalHandler, + Kind_ObjectSpec, + Kind_File }; + virtual void accept(Visitor *visitor) = 0; + virtual Kind kind() const = 0; +}; - template T *as() - { - if (T::kind == kind) { - return reinterpret_cast(this); - } - return 0; - } - virtual void accept(Visitor *visitor) = 0; +/////////////////////////////////////////////////////////////////// - Kind kind; -}; +class ObjectSpec; -class Object; +/** + * This class represents an assignment expression of one of these kinds: + * - simple js assignment (e.g. assigning 5 to a property) + * - object assignment (assigning e.g. `Item{}` to a property) + * - object list + * - binding + * - TODO: aliases + */ +class ValueAssignment : public Node { +public: + enum AssignmentType { + Invalid, + JsAssignment, + ObjectAssignment, + ObjectList, + Binding + }; -struct ValueAssignment : public Node { + ValueAssignment(Property *property = 0); + virtual ~ValueAssignment(); - ValueAssignment(); + virtual Node::Kind kind() const override; - ValueAssignment(Property *property, Object *objectValue, QQmlJS::AST::ExpressionNode *jsValue); + Property *property() const; - Property *property; - Object *objectValue; - QQmlJS::AST::ExpressionNode *jsValue; + /** + * If this assignment is to an attribute (e.g. `someprop.attrA: 15`), this + * function returns the name of that attribute (e.g. "attrA"). Else it + * returns an empty string. + */ + const QString &attributeName() const; virtual void accept(Visitor *visitor) override; + + void assignObject(ObjectSpec* arg); + void assignObjectList(QVector< QmlJSc::IR::ObjectSpec *> *arg); + void assignJsValue(QQmlJS::AST::Statement *arg); + void assignBinding(QQmlJS::AST::Statement *arg); + + void addToObjectList(ObjectSpec *arg); + + ObjectSpec *objectValue() const; + QVector *objectList() const; + QQmlJS::AST::Statement *jsValue() const; + QQmlJS::AST::Statement *bindingCode() const; + + AssignmentType assignmentType() const; + void setAssignmentType(AssignmentType newType); + + bool isUndefined() const; + +private: + Property *m_property; + QString m_attribute; + AssignmentType m_assignmentType; + + union { + ObjectSpec *m_objectValue; + QVector *m_objectList; + QQmlJS::AST::Statement *m_jsValue; + QQmlJS::AST::Statement *m_bindingCode; + }; }; -struct BindingAssignment : public Node { - BindingAssignment(); +class SignalHandler : public Node { +public: + SignalHandler(Signal *argSignal = 0, QQmlJS::AST::Statement *argHandlerCode=0 ); + + virtual Node::Kind kind() const override; - BindingAssignment(Property *property, QQmlJS::AST::ExpressionNode *binding); + Signal *signal() const; + QQmlJS::AST::Statement *handlerCode() const; - Property *property; - QQmlJS::AST::ExpressionNode *binding; + void setHandlerCode(QQmlJS::AST::Statement *argHandlerCode); virtual void accept(Visitor *visitor) override; + +private: + Signal *m_signal; + QQmlJS::AST::Statement *m_handlerCode; }; -class Object : public Node, public Type + +class ObjectSpec : public Node, public Type { public: + ObjectSpec( Type* objectType = 0); + + virtual Node::Kind kind() const override; + + const QString &id() const; + + ObjectSpec *parent() const; + void setParent(ObjectSpec *parent); + + const QVector &valueAssignments() const; + const QVector &signalHandlers() const; + ValueAssignment *valueAssignmentFor(Property *); - Object(); + ValueAssignment *addValueAssignment(Property *targetProperty); + SignalHandler *addSignalHandler(Signal *targetSignal); - QVector &valueAssignments(); - QVector &bindingAssignments(); + /** + * Root Object is a top object spec of Document tree, or a top object of Component + * so if document contains some of components, their inner objects are rootobjects. + */ + bool isRootObject() const; + void setIsRootObject(bool isRootObject); - ValueAssignment *addValueAssignment(); - BindingAssignment *addBindingAssignment(); + /** + * pointer to root object spec. Use it if current object is a component. + */ + ObjectSpec *rootObject() const; + void setRootObject(ObjectSpec *rootObject); virtual void accept(Visitor *visitor) override; -protected: - QVector m_valueAssignments; - QVector m_bindingAssignments; +private: + QVector m_valueAssignments; + QVector m_signalHandlers; + QString m_id; + bool m_isRootObject; + ObjectSpec *m_parent; + ObjectSpec *m_rootObject; virtual void visitChildren(Visitor *visitor); diff --git a/src/qmljsc/ir/objecttree.cpp b/src/qmljsc/ir/objecttree.cpp --- a/src/qmljsc/ir/objecttree.cpp +++ b/src/qmljsc/ir/objecttree.cpp @@ -26,85 +26,271 @@ using namespace QmlJSc; using namespace QmlJSc::IR; -Object::Object() - : Node() - , Type() +ValueAssignment::ValueAssignment(Property *property) + : m_property(property) + , m_assignmentType(Invalid) + , m_objectList(0) { } -QVector &Object::valueAssignments() +ValueAssignment::~ValueAssignment() { - return m_valueAssignments; -} - -QVector &Object::bindingAssignments() -{ - return m_bindingAssignments; + if (m_assignmentType == ObjectList) { + delete m_objectList; + } } -ValueAssignment::ValueAssignment() - : property(0) - , objectValue(0) - , jsValue(0) +Node::Kind ValueAssignment::kind() const { + return Kind_ValueAssignment; } -ValueAssignment::ValueAssignment(Property *property, Object *objectValue, QQmlJS::AST::ExpressionNode *jsValue) - : property(property) - , objectValue(objectValue) - , jsValue(jsValue) +IR::Property *ValueAssignment::property() const { + return m_property; } -ValueAssignment *Object::addValueAssignment() +const QString &ValueAssignment::attributeName() const { - m_valueAssignments.append(ValueAssignment()); - return &m_valueAssignments.last(); + return m_attribute; } void ValueAssignment::accept(Visitor *visitor) { visitor->visit(this); - if (objectValue) { - objectValue->accept(visitor); + + if (m_assignmentType == ObjectAssignment) { + m_objectValue->accept(visitor); + } else if (m_assignmentType == ObjectList) { + foreach (ObjectSpec* obj, *m_objectList) { + obj->accept(visitor); + } } + visitor->endVisit(this); } -BindingAssignment::BindingAssignment(Property *property, QQmlJS::AST::ExpressionNode *binding) - : property(property) - , binding(binding) +void ValueAssignment::assignObject(ObjectSpec *arg) { + setAssignmentType(ObjectAssignment); + m_objectValue = arg; +} + +void ValueAssignment::assignObjectList(QVector *arg) { + setAssignmentType(ObjectList); + m_objectList = arg; +} + +void ValueAssignment::assignJsValue(QQmlJS::AST::Statement *arg) { + setAssignmentType(JsAssignment); + m_jsValue = arg; +} + +void ValueAssignment::assignBinding(QQmlJS::AST::Statement *arg) { + setAssignmentType(Binding); + m_bindingCode = arg; +} + + +void ValueAssignment::addToObjectList(ObjectSpec *arg) +{ + setAssignmentType(ObjectList); // Won't do anything if already an ObjectList + *m_objectList << arg; +} + +ObjectSpec *ValueAssignment::objectValue() const +{ + if (m_assignmentType != ObjectAssignment) { + return 0; + } + return m_objectValue; +} + +QVector *ValueAssignment::objectList() const +{ + if (m_assignmentType != ObjectList) { + return 0; + } + return m_objectList; +} + +QQmlJS::AST::Statement *ValueAssignment::jsValue() const +{ + if (m_assignmentType != JsAssignment) { + return 0; + } + return m_jsValue; +} + +QQmlJS::AST::Statement *ValueAssignment::bindingCode() const +{ + if (m_assignmentType != Binding) { + return 0; + } + return m_bindingCode; +} + +ValueAssignment::AssignmentType ValueAssignment::assignmentType() const +{ + return m_assignmentType; +} + + +void ValueAssignment::setAssignmentType(AssignmentType newType) +{ + if (m_assignmentType == newType) { + return; + } + if (m_assignmentType == ObjectList) { + delete m_objectList; + } + + if (newType == ObjectList) { + m_objectList = new QVector; + } else { + m_objectValue = 0; // Will set jsValue and bindingCode to 0 as well. + } + m_assignmentType = newType; +} + +SignalHandler::SignalHandler(Signal *argSignal, QQmlJS::AST::Statement *handlerCode) +{ + m_signal = argSignal; + m_handlerCode = handlerCode ; +} + +Node::Kind SignalHandler::kind() const +{ + return Kind_SignalHandler; +} + +QQmlJS::AST::Statement * SignalHandler::handlerCode() const { + return m_handlerCode; } -BindingAssignment::BindingAssignment() - : property(0) - , binding(0) +Signal * SignalHandler::signal() const { + return m_signal; } -void BindingAssignment::accept(Visitor *visitor) { +void SignalHandler::setHandlerCode(QQmlJS::AST::Statement *handlerCode) +{ + m_handlerCode = handlerCode; +} + +void SignalHandler::accept(Visitor *visitor) +{ visitor->visit(this); visitor->endVisit(this); } -BindingAssignment *Object::addBindingAssignment() +ObjectSpec::ObjectSpec(Type* objectType) + : Node() + , Type() + , m_isRootObject(false) + , m_parent(0) + , m_rootObject(0) +{ + setSuper(objectType); +} + +Node::Kind ObjectSpec::kind() const +{ + return Kind_ObjectSpec; +} + +ObjectSpec *ObjectSpec::parent() const +{ + return m_parent; +} + +void ObjectSpec::setParent(ObjectSpec *parent) +{ + m_parent = parent; +} + +bool ObjectSpec::isRootObject() const +{ + return m_isRootObject; +} + +void ObjectSpec::setIsRootObject(bool isRootObject) { - m_bindingAssignments.append(BindingAssignment()); - return &m_bindingAssignments.last(); + m_isRootObject = isRootObject; } -void Object::accept(Visitor *visitor) +ObjectSpec *ObjectSpec::rootObject() const +{ + return m_rootObject; +} + +void ObjectSpec::setRootObject(ObjectSpec *rootObject) +{ + m_rootObject = rootObject; + m_rootObject->m_isRootObject = true; +} + +const QVector &ObjectSpec::valueAssignments() const +{ + return m_valueAssignments; +} + + +const QVector &ObjectSpec::signalHandlers() const +{ + return m_signalHandlers; +} + +ValueAssignment *ObjectSpec::valueAssignmentFor(Property *property) +{ + for (ValueAssignment &assignment: m_valueAssignments) { + if (assignment.property() == property) { + return &assignment; + } + } + return 0; +} + +ValueAssignment *ObjectSpec::addValueAssignment(Property *targetProperty) +{ + m_valueAssignments.append(ValueAssignment(targetProperty)); + return &m_valueAssignments.last(); +} + +SignalHandler *ObjectSpec::addSignalHandler(Signal *targetSignal) +{ + m_signalHandlers.append(SignalHandler(targetSignal)); + return &m_signalHandlers.last(); +} + +void ObjectSpec::accept(Visitor *visitor) { visitor->visit(this); - visitChildren(visitor); + + if (m_rootObject) { + // we have internal root object. wow! + // this means we are component and do not have own properties + // so just pass to that internal object + m_rootObject->accept( visitor ); + /* http://doc.qt.io/qt-5/qml-qtqml-component.html + * Component definition contains a single top level item (which in the above example + * is a Rectangle) and cannot define any data outside of this item, with the exception + * of an id (which in the above example is redSquare). + * */ + } else { + visitChildren(visitor); + } visitor->endVisit(this); } -void Object::visitChildren(Visitor *visitor) { - for (auto i = m_valueAssignments.begin(); i != m_valueAssignments.end(); i++) { +void ObjectSpec::visitChildren(Visitor *visitor) { + + for (auto i = m_signalHandlers.begin(); i != m_signalHandlers.end(); i++) { i->accept(visitor); } - for (auto i = m_bindingAssignments.begin(); i != m_bindingAssignments.end(); i++) { + + for (auto i = m_valueAssignments.begin(); i != m_valueAssignments.end(); i++) { i->accept(visitor); } -} \ No newline at end of file + +} + diff --git a/src/qmljsc/ir/typesystem.h b/src/qmljsc/ir/typesystem.h --- a/src/qmljsc/ir/typesystem.h +++ b/src/qmljsc/ir/typesystem.h @@ -29,6 +29,7 @@ namespace QQmlJS { namespace AST { class ExpressionNode; + class FunctionBody; } } @@ -70,6 +71,8 @@ Method *addMethod(const QString &name); Signal *addSignal(const QString &name); + bool hasMember(const QString &name); + void setName(const QString &name); void setJavaScriptName(const QString &jsName); @@ -82,14 +85,18 @@ Type *attachedType(); void setAttachedType(Type *); + Property *defaultProperty(); + void setDefaultProperty(Property *); + protected: QString m_name; QString m_javaScriptName; Flags m_flags; QHash m_properties; QHash m_methods; QHash m_signals; Type *m_attachedType; + Property *m_defaultProperty; /** * pointer to the super class or in case of objects the class of the object @@ -114,6 +121,7 @@ Type *returnType; QString name; QVector parameters; + QQmlJS::AST::FunctionBody *body; }; class Signal { diff --git a/src/qmljsc/ir/typesystem.cpp b/src/qmljsc/ir/typesystem.cpp --- a/src/qmljsc/ir/typesystem.cpp +++ b/src/qmljsc/ir/typesystem.cpp @@ -24,14 +24,19 @@ using namespace QmlJSc::IR; Type::Type() - : m_super(0) + : m_flags(0) + , m_attachedType(0) + , m_defaultProperty(0) + , m_super(0) { } Type::Type(const QString &name, const QString &jsName, Flags flags) : m_name(name) , m_javaScriptName(jsName) , m_flags(flags) + , m_attachedType(0) + , m_defaultProperty(0) , m_super(0) { } @@ -114,6 +119,18 @@ return 0; } + +bool Type::hasMember(const QString &name) +{ + if (m_properties.contains(name) || m_methods.contains(name) || m_signals.contains(name)) { + return true; + } + if (m_super) { + return m_super->hasMember(name); + } + return false; +} + Type *Type::super() { return m_super; } @@ -132,6 +149,22 @@ m_attachedType = attachedType; } +Property * Type::defaultProperty() +{ + if (m_defaultProperty) { + return m_defaultProperty; + } + if (m_super) { + return m_super->defaultProperty(); + } + return 0; +} + +void Type::setDefaultProperty(QmlJSc::IR::Property *property) +{ + m_defaultProperty = property; +} + Method::Method() : returnType(0) @@ -174,4 +207,4 @@ : type(type) , name(name) { -} \ No newline at end of file +} diff --git a/src/qmljsc/ir/visitor.h b/src/qmljsc/ir/visitor.h --- a/src/qmljsc/ir/visitor.h +++ b/src/qmljsc/ir/visitor.h @@ -27,22 +27,28 @@ class Node; class File; -class Object; + +class ObjectSpec; class ValueAssignment; class BindingAssignment; +class SignalHandler; + +class Method; +class Signal; +class Property; class Visitor { public: virtual void visit(File *file) {} - virtual void visit(Object *object) {} + virtual void visit(ObjectSpec *object) {} virtual void visit(ValueAssignment *valueAssignment) {} - virtual void visit(BindingAssignment *bindingAssignment) {} + virtual void visit(SignalHandler *signalHandler) {} virtual void endVisit(File *file) {} - virtual void endVisit(Object *object) {} + virtual void endVisit(ObjectSpec *object) {} virtual void endVisit(ValueAssignment *valueAssignment) {} - virtual void endVisit(BindingAssignment *bindingAssignment) {} + virtual void endVisit(SignalHandler *signalHandler) {} }; } // namespace IR diff --git a/src/qmljsc/moduleloading/javascriptmoduleloader.cpp b/src/qmljsc/moduleloading/javascriptmoduleloader.cpp --- a/src/qmljsc/moduleloading/javascriptmoduleloader.cpp +++ b/src/qmljsc/moduleloading/javascriptmoduleloader.cpp @@ -51,7 +51,7 @@ static void assertFailed(const AST::SourceLocation &token, QString errorMessage) { // Normally you wouldn't throw by reference but by value. We need to create - // the error object on the stack and throw by pointer, because we'll need it + // the error object on the heap and throw by pointer, because we'll need it // to be available on another thread. Error *error = new Error(Error::ModuleImportError, errorMessage); error->setLine(token.startLine); diff --git a/src/qmljsc/moduleloading/moduleloading.cpp b/src/qmljsc/moduleloading/moduleloading.cpp --- a/src/qmljsc/moduleloading/moduleloading.cpp +++ b/src/qmljsc/moduleloading/moduleloading.cpp @@ -71,7 +71,7 @@ if (!loader) { throw new Error(Error::ModuleImportError, QStringLiteral("Module %1 %2.%3 was not " - "found. Check if it is installed and include paths are correctly set")); + "found. Check version and include paths.").arg(import.name).arg(import.versionMajor).arg(import.versionMinor)); } QThreadPool::globalInstance()->start(loader); diff --git a/tests/auto/data/testasttoir/qtquick-import.qml b/tests/auto/data/testasttoir/qtquick-import.qml new file mode 100644 --- /dev/null +++ b/tests/auto/data/testasttoir/qtquick-import.qml @@ -0,0 +1,3 @@ +import QtQuick 2.1 + +Item{} diff --git a/tests/auto/qmljsc/CMakeLists.txt b/tests/auto/qmljsc/CMakeLists.txt --- a/tests/auto/qmljsc/CMakeLists.txt +++ b/tests/auto/qmljsc/CMakeLists.txt @@ -26,3 +26,4 @@ new_test(TEST testir) new_test(TEST testpurejavascriptgenerator) new_test(TEST testpurejavascriptgenerator_integration RESOURCES ../data/javascript/) +new_test(TEST testasttoir RESOURCES ../data/testasttoir/qtquick-import.qml) diff --git a/tests/auto/qmljsc/testasttoir.cpp b/tests/auto/qmljsc/testasttoir.cpp new file mode 100644 --- /dev/null +++ b/tests/auto/qmljsc/testasttoir.cpp @@ -0,0 +1,610 @@ +/* + * Qml.js Compiler - a QML to JS compiler bringing QML's power to the web. + * + * Copyright (C) 2016 Anton Kreuzkamp + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include "../../../src/qmljsc/moduleloading/moduleloading.h" +#include "../../../src/qmljsc/compilerpasses/parserpass.h" +#include "../../../src/qmljsc/compilerpipeline.h" +#include +#include + +#include + +#include "../../../src/qmljsc/compilerpasses/asttoirpass.h" +#include "../../../src/qmljsc/ir/objecttree.h" +#include "../../../src/qmljsc/ir/visitor.h" +#include "../../../src/qmljsc/ir/typesystem.h" +#include "../../../src/qmljsc/ir/builtintypes.h" +#include "../../../src/qmljsc/ir/module.h" + +#include "../../../src/qmljsc/moduleloading/abstractmoduleloader.h" + +// Qt private +#include + +using namespace QQmlJS; + +namespace QmlJSc { + +class MockModuleLoader : public AbstractModuleLoader +{ +public: + static MockModuleLoader *create(IR::Module *module) + { + return new MockModuleLoader(module); + } + + bool canLoad() override + { + if (module()->name() == "QtQuick" + && module()->importDescription().versionMajor == 2 + && module()->importDescription().versionMinor <= 1) { + return true; + } + if (module()->name() == "QtQuick.Controls" + && module()->importDescription().versionMajor == 1 + && module()->importDescription().versionMinor <= 3) { + return true; + } + if (module()->name() == "IExistInVersionTwoOne" + && module()->importDescription().versionMajor == 2 + && module()->importDescription().versionMinor <= 1) { + return true; + } + return false; + + } + void doLoad() override + { + module()->setLoadingState(IR::Module::Successful); + } + +private: + MockModuleLoader(IR::Module *module) + : AbstractModuleLoader(module) + {} +}; + + +class TestAstToIR : public QObject +{ + Q_OBJECT + +public: + TestAstToIR(); + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void testImports_data(); + void testImports(); + void testSimpleObject(); + void testSingleChild(); + void testMultipleChildren(); + void testProperties(); + void testSignalHandlers(); + void testObjectProperty(); + void testObjectList(); + void testPropertyDefinition(); + void testObjectPropertyDefinition(); + void testListPropertyDefinition(); + void testMethods(); + void testSignalDefinitions(); + void testAlias(); + void testMultipleMembers(); + void testErrorHandling_data(); + void testErrorHandling(); + + +private: + void prepareTest(QString qml); + + AstToIRPass m_pass; + QQmlJS::Engine m_engine; + QQmlJS::Lexer m_lexer; + QQmlJS::Parser m_parser; + IR::Module m_pizzaModule; + IR::Type *m_pizzaType; + IR::Type *m_toppingType; + IR::Type *m_salameType; + IR::Type *m_prosciuttoType; + IR::Type *m_pastaType; + IR::Type *m_sauceType; + IR::Type *m_tomatoSauceType; + IR::Type *m_spiceType; + IR::Type *m_oreganoType; + IR::Type *m_uninstantiableType; +}; + +TestAstToIR::TestAstToIR() + : QObject() + , m_lexer(&m_engine) + , m_parser(&m_engine) + , m_pizzaModule({IR::ImportDescription::Kind_ModuleImport, "Pizzeria", 1, 0}) +{ + ModuleLoading::registerModuleLoader(&MockModuleLoader::create); + + m_toppingType = new IR::Type("Topping", "QWTopping", IR::Type::Flags(IR::Type::IsInstantiable)); + m_pizzaModule.addType(m_toppingType); + + m_pizzaType = new IR::Type("Pizza", "QWPizza", IR::Type::Flags(IR::Type::IsInstantiable) ); + IR::Property *p = m_pizzaType->addProperty("topping"); + p->type = new IR::BuiltinTypes::List(m_toppingType); + m_pizzaType->setDefaultProperty(p); + p = m_pizzaType->addProperty("topping2"); + p->type = new IR::BuiltinTypes::List(m_toppingType); + p = m_pizzaType->addProperty("cmSize"); + p->type = IR::BuiltinTypes::intType(); + m_pizzaType->addSignal("auburn"); + m_pizzaModule.addType(m_pizzaType); + + m_salameType = new IR::Type("Salame", "QWSalame", IR::Type::Flags(IR::Type::IsInstantiable) ); + m_salameType->setSuper(m_toppingType); + m_pizzaModule.addType(m_salameType); + + m_prosciuttoType = new IR::Type("ProsciuttoCotto", "QWProsciuttoCotto", IR::Type::Flags(IR::Type::IsInstantiable) ); + m_prosciuttoType->setSuper(m_prosciuttoType); + m_pizzaModule.addType(m_prosciuttoType); + + m_spiceType = new IR::Type("Spice", "QWSpice", IR::Type::Flags(IR::Type::IsInstantiable)); + m_pizzaModule.addType(m_spiceType); + + m_oreganoType = new IR::Type("Oregano", "QWOregano", IR::Type::Flags(IR::Type::IsInstantiable) ); + m_oreganoType->setSuper(m_spiceType); + m_pizzaModule.addType(m_oreganoType); + + + m_sauceType = new IR::Type("Sauce", "QWSauce", IR::Type::Flags(IR::Type::IsInstantiable) ); + m_sauceType->setSuper(m_toppingType); + p = m_sauceType->addProperty("spices"); + p->type = new IR::BuiltinTypes::List(m_spiceType); + m_sauceType->setDefaultProperty(p); + m_pizzaModule.addType(m_sauceType); + + m_tomatoSauceType = new IR::Type("TomatoSauce", "QWTomatoSauce", IR::Type::Flags(IR::Type::IsInstantiable) ); + m_tomatoSauceType->setSuper(m_sauceType); + m_pizzaModule.addType(m_tomatoSauceType); + + m_pastaType = new IR::Type("Pasta", "QWPasta", IR::Type::Flags(IR::Type::IsInstantiable) ); + p = m_pastaType->addProperty("sauce"); + m_pastaType->setDefaultProperty(p); + p->type = m_sauceType; + m_pizzaModule.addType(m_pastaType); + + m_uninstantiableType = new IR::Type("ImUninstantiable", "QWUninstantiable" ); + m_pizzaModule.addType(m_uninstantiableType); + + m_pizzaModule.setLoadingState(IR::Module::Successful); +} + + +void TestAstToIR::initTestCase() +{ +} + +void TestAstToIR::testImports_data() +{ + QTest::addColumn("qml"); + QTest::addColumn("moduleName"); + QTest::addColumn("majorVersion"); + QTest::addColumn("minorVersion"); + QTest::addColumn("prefix"); + + QTest::newRow("qtquick-import") << "import QtQuick 2.1\nItem{}" << "QtQuick" << 2 << 1 << "A"; + QTest::newRow("qtquickcontrols-import") << "import QtQuick.Controls 1.0 as Widgets\nItem{}" << "QtQuick.Controls" << 1 << 0 << "Widgets"; +// QTest::newRow("file-import") << "import \"someFile.js\" as Something\nItem{}" << "someFile.js" << 0 << 0 << "Something"; +// QTest::newRow("directory-import") << "import \"someDir/\"\nItem{}" << "someFile.js" << 0 << 0 << "Something"; +} + +void TestAstToIR::testImports() +{ + QFETCH(QString, qml); + QFETCH(QString, moduleName); + QFETCH(int, majorVersion); + QFETCH(int, minorVersion); + QFETCH(QString, prefix); + + m_lexer.setCode(qml, 1, true); + m_parser.parse(); + AST::cast(m_parser.rootNode())->headers->headerItem->accept(&m_pass); + + const QVector &importedModules = m_pass.m_file->importedModules(); + QCOMPARE(importedModules.count(), 1); + QVERIFY(importedModules.first().module); + QCOMPARE(importedModules.first().module->name(), moduleName); + QCOMPARE(importedModules.first().module->importDescription().versionMajor, majorVersion); + QCOMPARE(importedModules.first().module->importDescription().versionMinor, minorVersion); + QCOMPARE(importedModules.first().localPrefix, prefix); +} + +void TestAstToIR::init() +{ + m_pass.m_file = new IR::File; + m_pass.m_stack << m_pass.m_file; +} + +void TestAstToIR::cleanup() +{ + delete m_pass.m_file; + m_pass.m_stack.clear(); +} + +void TestAstToIR::prepareTest(QString qml) +{ + m_pass.m_file->addModule(&m_pizzaModule); + + m_lexer.setCode(qml, 1, true); + m_parser.parse(); +} + +void TestAstToIR::testSimpleObject() +{ + prepareTest(QStringLiteral("Pizza {}")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pizza")); +} + +void TestAstToIR::testSingleChild() +{ + prepareTest(QStringLiteral("Pasta { TomatoSauce {} }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QVERIFY(rootObject->super()); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pasta")); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + QCOMPARE(rootObject->valueAssignments().first().property(), m_pastaType->property("sauce")); + IR::ObjectSpec *childObject = rootObject->valueAssignments().first().objectValue(); + QVERIFY(childObject); + QVERIFY(childObject->super()); + QCOMPARE(childObject->super()->name(), QStringLiteral("TomatoSauce")); +} + +void TestAstToIR::testMultipleChildren() +{ + prepareTest(QStringLiteral("Pizza { Salame {} ProsciuttoCotto {} }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QVERIFY(rootObject->super()); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pizza")); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + QCOMPARE(rootObject->valueAssignments().first().property(), m_pizzaType->property("topping")); + auto childList = rootObject->valueAssignments().first().objectList(); + QVERIFY(childList); + QCOMPARE(childList->count(), 2); + QCOMPARE(childList->first()->super()->name(), QStringLiteral("Salame")); + QCOMPARE(childList->last()->super()->name(), QStringLiteral("ProsciuttoCotto")); +} + +void TestAstToIR::testProperties() +{ + prepareTest(QStringLiteral("Pizza { cmSize: 28 }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QVERIFY(rootObject->super()); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pizza")); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + QCOMPARE(rootObject->valueAssignments().first().property(), m_pizzaType->property("cmSize")); + QQmlJS::AST::ExpressionStatement *jsValue = dynamic_cast(rootObject->valueAssignments().first().jsValue()); + QVERIFY(jsValue); + QCOMPARE(dynamic_cast(jsValue->expression)->value, 28.0); +} + +void TestAstToIR::testSignalHandlers() +{ + prepareTest(QStringLiteral("Pizza { onAuburn: {} }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QCOMPARE(rootObject->valueAssignments().size(), 0); + QCOMPARE(rootObject->signalHandlers().size(), 1); + QCOMPARE(rootObject->signalHandlers().first().signal(), m_pizzaType->signal("auburn")); + QQmlJS::AST::Block *code = dynamic_cast(rootObject->signalHandlers().first().handlerCode()); + QVERIFY(code); +} + +void TestAstToIR::testObjectProperty() +{ + prepareTest(QStringLiteral("Pasta { sauce: TomatoSauce {} }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QVERIFY(rootObject->super()); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pasta")); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + QCOMPARE(rootObject->valueAssignments().first().property(), m_pastaType->property("sauce")); + IR::ObjectSpec *childObject = rootObject->valueAssignments().first().objectValue(); + QVERIFY(childObject); + QVERIFY(childObject->super()); + QCOMPARE(childObject->super()->name(), QStringLiteral("TomatoSauce")); +} + +void TestAstToIR::testObjectList() +{ + prepareTest(QStringLiteral("Pizza { topping2: [Salame {}, ProsciuttoCotto {}] }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QVERIFY(rootObject->super()); + QCOMPARE(rootObject->super()->name(), QStringLiteral("Pizza")); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + QCOMPARE(rootObject->valueAssignments().first().property(), m_pizzaType->property("topping2")); + auto childList = rootObject->valueAssignments().first().objectList(); + QVERIFY(childList); + QCOMPARE(childList->count(), 2); + QCOMPARE(childList->first()->super()->name(), QStringLiteral("Salame")); + QCOMPARE(childList->last()->super()->name(), QStringLiteral("ProsciuttoCotto")); +} + +void TestAstToIR::testPropertyDefinition() +{ + prepareTest(QStringLiteral("Pizza { property bool eaten: false }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + IR::Property *property = rootObject->property("eaten"); + QVERIFY(property); + QCOMPARE(property->constant, false); + QCOMPARE(property->readOnly, false); + QCOMPARE(property->type, IR::BuiltinTypes::boolType()); + QCOMPARE(rootObject->valueAssignments().first().property(), rootObject->property("eaten")); + QQmlJS::AST::ExpressionStatement *jsValue = dynamic_cast(rootObject->valueAssignments().first().jsValue()); + QVERIFY(jsValue); + QVERIFY(dynamic_cast(jsValue->expression)); +} + +void TestAstToIR::testObjectPropertyDefinition() +{ + prepareTest(QStringLiteral("Pizza { readonly property Topping missing: TomatoSauce {} }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + IR::Property *property = rootObject->property("missing"); + QVERIFY(property); + QCOMPARE(property->constant, false); + QCOMPARE(property->readOnly, true); + QCOMPARE(property->type, m_toppingType); + QCOMPARE(rootObject->valueAssignments().first().property(), rootObject->property("missing")); + IR::ObjectSpec *childObject = rootObject->valueAssignments().first().objectValue(); + QVERIFY(childObject); + QCOMPARE(childObject->super(), m_tomatoSauceType); +} + +void TestAstToIR::testListPropertyDefinition() +{ + prepareTest(QStringLiteral("Pizza { property list missing: [TomatoSauce {}] }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->valueAssignments().size(), 1); + + IR::Property *property = rootObject->property("missing"); + QVERIFY(property); + QCOMPARE(property->constant, false); + QCOMPARE(property->readOnly, false); + QVERIFY(property->type->flags() & IR::Type::IsList); + QVERIFY(property->type->flags() & IR::Type::IsInstantiable); + QCOMPARE(static_cast(property->type)->contentType(), m_toppingType); + QCOMPARE(rootObject->valueAssignments().first().property(), rootObject->property("missing")); + auto childList = rootObject->valueAssignments().first().objectList(); + QVERIFY(childList); + QCOMPARE(childList->count(), 1); + QCOMPARE(childList->first()->super()->name(), QStringLiteral("TomatoSauce")); +} + +void TestAstToIR::testMethods() +{ + prepareTest(QStringLiteral("Pizza { function eat(gobble) { if (gobble) {console.log('rompf!')} else {console.log('mmh!');} } }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->valueAssignments().size(), 0); + + IR::Method *method = rootObject->method("eat"); + QVERIFY(method); + QCOMPARE(method->parameters.size(), 1); + QCOMPARE(method->parameters.first(), QStringLiteral("gobble")); + QVERIFY(method->body); + QVERIFY(method->body->elements); + QVERIFY(method->body->elements->element); + QCOMPARE(method->body->elements->element->kind, (int)AST::Node::Kind_StatementSourceElement); + AST::StatementSourceElement *sse = AST::cast(method->body->elements->element); + QVERIFY(sse); + QCOMPARE(sse->statement->kind, (int)AST::Node::Kind_IfStatement); +} + +void TestAstToIR::testSignalDefinitions() +{ + prepareTest(QStringLiteral("Pizza { signal eaten(bool wasDelicious) }")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + + IR::Signal *signal = rootObject->signal("eaten"); + QVERIFY(signal); + QCOMPARE(signal->name, QStringLiteral("eaten")); + QCOMPARE(signal->parameters.size(), 1); + QCOMPARE(signal->parameters.first().type, IR::BuiltinTypes::boolType()); + QCOMPARE(signal->parameters.first().name, QStringLiteral("wasDelicious")); +} + +void TestAstToIR::testAlias() +{ + QSKIP("Not implemented for now."); +} + +void TestAstToIR::testMultipleMembers() +{ + prepareTest(QStringLiteral("Pizza {" + "readonly property int foo: 5;" + "cmSize: 30;" + "signal eaten;" + "Salame {}" + "ProsciuttoCotto {}" + "TomatoSauce {" + "spices: Oregano {}" + "}" + "}")); + AST::cast(m_parser.rootNode())->members->member->accept(&m_pass); + + IR::ObjectSpec *rootObject = m_pass.m_file->rootObject(); + QVERIFY(rootObject); + QCOMPARE(rootObject->valueAssignments().size(), 3); + + IR::Property *property = rootObject->property("foo"); + IR::ValueAssignment *fooAssignment = rootObject->valueAssignmentFor(property); + QVERIFY(property); + QCOMPARE(property->readOnly, true); + QCOMPARE(fooAssignment->property(), property); + QQmlJS::AST::ExpressionStatement *jsValue = dynamic_cast(fooAssignment->jsValue()); + QVERIFY(jsValue); + QCOMPARE(dynamic_cast(jsValue->expression)->value, 5.0); + + IR::ValueAssignment *cmSizeAssignment = rootObject->valueAssignmentFor(m_pizzaType->property("cmSize")); + QCOMPARE(cmSizeAssignment->property(), m_pizzaType->property("cmSize")); + jsValue = dynamic_cast(cmSizeAssignment->jsValue()); + QVERIFY(jsValue); + QCOMPARE(dynamic_cast(jsValue->expression)->value, 30.0); + + IR::Signal *signal = rootObject->signal("eaten"); + QVERIFY(signal); + QCOMPARE(signal->name, QStringLiteral("eaten")); + QCOMPARE(signal->parameters.size(), 0); + + IR::ValueAssignment *toppingAssignment = rootObject->valueAssignmentFor(m_pizzaType->property("topping")); + QCOMPARE(toppingAssignment->property(), m_pizzaType->property("topping")); + auto childList = toppingAssignment->objectList(); + QVERIFY(childList); + QCOMPARE(childList->count(), 3); + auto j = childList->constBegin(); + QCOMPARE((*j)->super()->name(), QStringLiteral("Salame")); + j++; + QCOMPARE((*j)->super()->name(), QStringLiteral("ProsciuttoCotto")); + j++; + QCOMPARE((*j)->super()->name(), QStringLiteral("TomatoSauce")); + + IR::ObjectSpec *tomatoSauceObject = *j; + QVERIFY(tomatoSauceObject); + QCOMPARE(tomatoSauceObject->valueAssignments().size(), 1); + + QCOMPARE(tomatoSauceObject->valueAssignments().first().property(), m_tomatoSauceType->property("spices")); + auto tomatoSauceChildList = tomatoSauceObject->valueAssignments().first().objectList(); + QVERIFY(tomatoSauceChildList); + QCOMPARE(tomatoSauceChildList->count(), 1); + QCOMPARE(tomatoSauceChildList->first()->super()->name(), QStringLiteral("Oregano")); +} + +void TestAstToIR::testErrorHandling_data() +{ + QTest::addColumn("qml"); + QTest::addColumn("line"); + QTest::addColumn("column"); + QTest::addColumn("errorMsg"); + + QTest::newRow("unexistant-import") << "import IDontExist 2.1\nItem{}" << 1 << 8 << QStringLiteral("Module IDontExist 2.1 was not found. Check version and include paths."); + QTest::newRow("wrongversion-import") << "import IExistInVersionTwoOne 2.2\nItem{}" << 1 << 8 << QStringLiteral("Module IExistInVersionTwoOne 2.2 was not found. Check version and include paths."); + QTest::newRow("unexistant-type") << "IDontExist {}" << 1 << 1 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("uninstantiable-type") << "ImUninstantiable {}" << 1 << 1 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("unexistant-property") << "Pizza { idontexist: 5 }" << 1 << 9 << QStringLiteral("Type Pizza has no property idontexist."); + QTest::newRow("single-child-unexistant-type") << "Pasta { IDontExist {} }" << 1 << 9 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("single-child-uninstantiable-type") << "Pasta { ImUninstantiable {} }" << 1 << 9 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("multiple-children-unexistant-type") << "Pizza { Salame {} IDontExist {} }" << 1 << 19 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("multiple-children-uninstantiable-type") << "Pizza { Salame {} ImUninstantiable {} }" << 1 << 19 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("object-property-unexistant-property") << "Pasta { idontexist: TomatoSauce {} }" << 1 << 9 << QStringLiteral("Type Pasta has no property idontexist."); + QTest::newRow("object-property-unexistant-type") << "Pasta { sauce: IDontExist {} }" << 1 << 16 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("object-property-uninstantiable-type") << "Pasta { sauce: ImUninstantiable {} }" << 1 << 16 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("object-property-list-assignment") << "Pasta { sauce: [ TomatoSauce {}, TomatoSauce {} ] }" << 1 << 9 << QStringLiteral("Trying to assign a list to property sauce with non-list type Sauce."); + QTest::newRow("object-list-unexistant-property") << "Pizza { idontexist: [Salame {}, ProsciuttoCotto {}] }" << 1 << 9 << QStringLiteral("Type Pizza has no property idontexist."); + QTest::newRow("object-list-unexistant-type") << "Pizza { topping2: [Salame {}, IDontExist {}] }" << 1 << 31 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("object-list-uninstantiable-type") << "Pizza { topping2: [Salame {}, ImUninstantiable {}] }" << 1 << 31 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("method-definition-alreadyexisting-symbol") << "Pizza { function cmSize() {} }" << 1 << 18 << QStringLiteral("Object already has a member named cmSize."); + QTest::newRow("property-definition-alreadyexisting-property") << "Pizza { property bool cmSize: false }" << 1 << 23 << QStringLiteral("Object already has a member named cmSize."); + QTest::newRow("property-definition-unexistant-type") << "Pizza { property IDontExist foo: false }" << 1 << 18 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("object-property-definition-alreadyexisting-property") << "Pizza { readonly property Topping topping: TomatoSauce {} }" << 1 << 35 << QStringLiteral("Object already has a member named topping."); + QTest::newRow("object-property-definition-nonexisting-type") << "Pizza { readonly property IDontExist missing: TomatoSauce {} }" << 1 << 27 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("object-property-definition-noninstantiable-type") << "Pizza { readonly property ImUninstantiable missing: TomatoSauce {} }" << 1 << 44 << QStringLiteral("Trying to assign to property missing of uninstantiable type ImUninstantiable."); + QTest::newRow("object-property-definition-nonexisting-type-assignment") << "Pizza { readonly property Topping missing: IDontExist {} }" << 1 << 44 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("object-property-definition-noninstantiable-type-assign") << "Pizza { readonly property Topping missing: ImUninstantiable {} }" << 1 << 44 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("list-property-definition-alreadyexisting-property") << "Pizza { property list topping: [TomatoSauce {}] }" << 1 << 32 << QStringLiteral("Object already has a member named topping."); + QTest::newRow("list-property-definition-nonexisting-type") << "Pizza { property list missing: [TomatoSauce {}] }" << 1 << 23 << QStringLiteral("List argument is unknown type IDontExist."); + QTest::newRow("list-property-definition-noninstantiable-type") << "Pizza { property list missing: [TomatoSauce {}] }" << 1 << 41 << QStringLiteral("Trying to assign to property missing, but list argument is uninstantiable type ImUninstantiable."); + QTest::newRow("list-property-definition-nonexisting-type-assignment") << "Pizza { property list missing: [IDontExist {}] }" << 1 << 42 << QStringLiteral("Unknown type IDontExist."); + QTest::newRow("list-property-definition-noninstantiable-type-assign") << "Pizza { property list missing: [ImUninstantiable {}] }" << 1 << 42 << QStringLiteral("Trying to instantiate uninstantiable type ImUninstantiable."); + QTest::newRow("signal-definition-unexistant-type") << "Pizza { signal eaten(IDontExist wasDelicious) }" << 1 << 22 << QStringLiteral("Parameter wasDelicious has unknown type IDontExist."); + QTest::newRow("signal-handler-unexistant-signal") << "Pizza { onNonExistingSignal: print \"foo\"; }" << 1 << 9 << QStringLiteral("Can't create handler, type Pizza has no signal onNonExistingSignal."); +} + +void TestAstToIR::testErrorHandling() +{ + m_pass.m_stack.pop(); // Here we don't want the file to be on the stack already so we undo the init. + QFETCH(QString, qml); + QFETCH(int, line); + QFETCH(int, column); + QFETCH(QString, errorMsg); + + prepareTest(qml); + bool errorThrown = false; + + try { + AST::cast(m_parser.rootNode())->accept(&m_pass); + } catch(Error *error) { + errorThrown = true; + + QCOMPARE(error->line(), line); + QCOMPARE(error->column(), column); + QCOMPARE(error->description(), errorMsg); + + delete error; + } + + QVERIFY(errorThrown); +} + + +} // namespace QMLJSc + +QTEST_MAIN(QmlJSc::TestAstToIR) +#include "testasttoir.moc" + diff --git a/tests/auto/qmljsc/testir.cpp b/tests/auto/qmljsc/testir.cpp --- a/tests/auto/qmljsc/testir.cpp +++ b/tests/auto/qmljsc/testir.cpp @@ -28,7 +28,6 @@ #include "../../../src/qmljsc/ir/objecttree.h" #include "../../../src/qmljsc/ir/visitor.h" #include "../../../src/qmljsc/ir/typesystem.h" -#include "../../../src/qmljsc/ir/builtintypes.h" // Qt private #include @@ -48,16 +47,15 @@ void testBasics(); void testAdd(); void testVisitorAPI(); - void testBuiltinTypes(); private: Type city; - Object christiania; - Object copenhagen; + ObjectSpec christiania; + ObjectSpec copenhagen; Type state; Type democracy; - Object ottomanEmpire; - Object denmark; + ObjectSpec ottomanEmpire; + ObjectSpec denmark; QString christianiaName; QString copenhagenName; @@ -84,6 +82,15 @@ QQmlJS::AST::StringLiteral denmarkRPartyNode; QQmlJS::AST::NumericLiteral christianiaPostCodeNode; + QQmlJS::AST::ExpressionStatement christianiaNameStatement; + QQmlJS::AST::ExpressionStatement copenhagenNameStatement; + QQmlJS::AST::ExpressionStatement ottomanEmpireNameStatement; + QQmlJS::AST::ExpressionStatement ottomanEmpireLangStatement; + QQmlJS::AST::ExpressionStatement denmarkNameStatement; + QQmlJS::AST::ExpressionStatement denmarkLangStatement; + QQmlJS::AST::ExpressionStatement denmarkRPartyStatement; + QQmlJS::AST::ExpressionStatement christianiaPostCodeStatement; + }; TestIR::TestIR() @@ -112,6 +119,15 @@ , denmarkLangNode(denmarkLangRef) , denmarkRPartyNode(denmarkRPartyRef) , christianiaPostCodeNode(1050) + + , christianiaNameStatement(&christianiaNameNode) + , copenhagenNameStatement(&copenhagenNameNode) + , ottomanEmpireNameStatement(&ottomanEmpireNameNode) + , ottomanEmpireLangStatement(&ottomanEmpireLangNode) + , denmarkNameStatement(&denmarkNameNode) + , denmarkLangStatement(&denmarkLangNode) + , denmarkRPartyStatement(&denmarkRPartyNode) + , christianiaPostCodeStatement(&christianiaPostCodeNode) { } @@ -162,10 +178,13 @@ ottomanEmpire.m_methods = { {"capital", {0, "capital"}}, // Capital for year, overrides property capital. }; + ottomanEmpire.m_valueAssignments = { - {&state.m_properties["name"], 0, &ottomanEmpireNameNode}, - {&state.m_properties["language"], 0, &ottomanEmpireLangNode} + {&state.m_properties["name"]}, + {&state.m_properties["language"]} }; + ottomanEmpire.m_valueAssignments[0].assignJsValue(&ottomanEmpireNameStatement); + ottomanEmpire.m_valueAssignments[1].assignJsValue(&ottomanEmpireLangStatement); denmark.m_name = "Denmark"; denmark.m_super = &democracy; @@ -215,56 +234,59 @@ QVERIFY(!ottomanEmpire.property("reigningParty")); QVERIFY(!ottomanEmpire.property("postCode")); QCOMPARE(ottomanEmpire.valueAssignments().count(), 2); - QCOMPARE(ottomanEmpire.valueAssignments()[0].property, &state.m_properties["name"]); - QCOMPARE(reinterpret_cast( - ottomanEmpire.valueAssignments()[0].jsValue)->value.toString(), - QStringLiteral("Osmanlı İmparatorluğu")); - + QCOMPARE(ottomanEmpire.valueAssignments()[0].property(), &state.m_properties["name"]); + QQmlJS::AST::ExpressionStatement *nameStatement = QQmlJS::AST::cast(ottomanEmpire.valueAssignments()[0].jsValue()); + QVERIFY(nameStatement); + QQmlJS::AST::StringLiteral *nameLiteral = QQmlJS::AST::cast(nameStatement->expression); + QVERIFY(nameLiteral); + QCOMPARE(nameLiteral->value.toString(), QStringLiteral("Osmanlı İmparatorluğu")); } void TestIR::testAdd() { - ValueAssignment *denmarkNameAssignment = denmark.addValueAssignment(); - denmarkNameAssignment->property = &state.m_properties["name"]; - denmarkNameAssignment->jsValue = &denmarkNameNode; - ValueAssignment *denmarkLanguageAssignment = denmark.addValueAssignment(); - denmarkLanguageAssignment->property = &state.m_properties["language"]; - denmarkLanguageAssignment->jsValue = &denmarkLangNode; - ValueAssignment *denmarkCapitalAssignment = denmark.addValueAssignment(); - denmarkCapitalAssignment->property = &state.m_properties["capital"]; - denmarkCapitalAssignment->objectValue = &copenhagen; - ValueAssignment *denmarkReigningPartyAssignment = denmark.addValueAssignment(); - denmarkReigningPartyAssignment->property = &democracy.m_properties["reigningParty"]; - denmarkReigningPartyAssignment->jsValue = &denmarkRPartyNode; + ValueAssignment *denmarkNameAssignment = denmark.addValueAssignment( &state.m_properties["name"] ); + denmarkNameAssignment->assignJsValue(&denmarkNameStatement); + + ValueAssignment *denmarkLanguageAssignment = denmark.addValueAssignment( &state.m_properties["language"] );\ + denmarkLanguageAssignment->assignJsValue(&denmarkLangStatement); + + ValueAssignment *denmarkCapitalAssignment = denmark.addValueAssignment( &state.m_properties["capital"] ); + denmarkCapitalAssignment->assignObject( &copenhagen );\ + + ValueAssignment *denmarkReigningPartyAssignment = denmark.addValueAssignment( &democracy.m_properties["reigningParty"] ); + denmarkReigningPartyAssignment->assignJsValue(&denmarkRPartyStatement); Property *christianiaProperty = copenhagen.addProperty("christiania"); christianiaProperty->type = &city; - ValueAssignment *christianiaAssignment = copenhagen.addValueAssignment(); - christianiaAssignment->objectValue = &christiania; - ValueAssignment *copenhagenNameAssignment = copenhagen.addValueAssignment(); - copenhagenNameAssignment->property = &city.m_properties["name"]; - copenhagenNameAssignment->jsValue = &copenhagenNameNode; + ValueAssignment *christianiaPropertyAssignment = copenhagen.addValueAssignment(christianiaProperty); + christianiaPropertyAssignment->assignObject( &christiania ); + + ValueAssignment *copenhagenNameAssignment = copenhagen.addValueAssignment(&city.m_properties["name"]); + copenhagenNameAssignment->assignJsValue(&copenhagenNameStatement); Method *buyWeed = christiania.addMethod("buyWeed"); Signal *policeRaid = christiania.addSignal("policeRaid"); - ValueAssignment *christianiaNameAssignment = christiania.addValueAssignment(); - christianiaNameAssignment->property = &city.m_properties["name"]; - christianiaNameAssignment->jsValue = &christianiaNameNode; - ValueAssignment *christianiaPostCodeAssignment = christiania.addValueAssignment(); - christianiaPostCodeAssignment->property = &city.m_properties["postCode"]; - christianiaPostCodeAssignment->jsValue = &christianiaPostCodeNode; + ValueAssignment *christianiaNameAssignment = christiania.addValueAssignment(&city.m_properties["name"]); + christianiaNameAssignment->assignJsValue(&christianiaNameStatement); + + ValueAssignment *christianiaPostCodeAssignment = christiania.addValueAssignment( &city.m_properties["postCode"] ); + christianiaPostCodeAssignment->assignJsValue(&christianiaPostCodeStatement); QVERIFY(denmark.property("capital")); QVERIFY(denmark.property("reigningParty")); QVERIFY(!denmark.property("postCode")); QCOMPARE(denmark.valueAssignments().count(), 4); - QVERIFY(denmark.valueAssignments()[2].property); - QVERIFY(denmark.valueAssignments()[2].property == denmark.valueAssignments()[2].property); - QCOMPARE(denmark.valueAssignments()[3].property, &democracy.m_properties["reigningParty"]); - QCOMPARE(reinterpret_cast( - denmark.valueAssignments()[3].jsValue)->value.toString(), - QStringLiteral("S-RV")); + QVERIFY(denmark.valueAssignments()[2].property()); + QVERIFY(denmark.valueAssignments()[2].property() == denmark.valueAssignments()[2].property()); + QCOMPARE(denmark.valueAssignments()[3].property(), &democracy.m_properties["reigningParty"]); + QQmlJS::AST::ExpressionStatement *reigningPartyStatement = + QQmlJS::AST::cast(denmark.valueAssignments()[3].jsValue()); + QVERIFY(reigningPartyStatement); + QQmlJS::AST::StringLiteral *reigningPartyLiteral = + QQmlJS::AST::cast(reigningPartyStatement->expression); + QVERIFY(reigningPartyLiteral); + QCOMPARE(reigningPartyLiteral->value.toString(), QStringLiteral("S-RV")); QVERIFY(copenhagen.property("name")); QVERIFY(copenhagen.property("christiania")); @@ -290,36 +312,36 @@ , signalsVisited(0) , currentDepth(0) , valueAssignmentsVisited(0) - , bindingAssignmentsVisited(0) + , signalHandlersVisited(0) , lastValueAssigned(0) {} - virtual void visit(Object *object) + virtual void visit(ObjectSpec *object) { currentDepth++; objectsVisited++; } virtual void visit(ValueAssignment *valueAssignment) { currentDepth++; valueAssignmentsVisited++; - lastValueAssigned = valueAssignment->jsValue; + lastValueAssigned = valueAssignment->jsValue(); } - virtual void visit(BindingAssignment *bindingAssignment) + virtual void visit(SignalHandler *signalHandler) { currentDepth++; - bindingAssignmentsVisited++; + signalHandlersVisited++; } - virtual void endVisit(Object *object) + virtual void endVisit(ObjectSpec *object) { currentDepth--; } virtual void endVisit(ValueAssignment *valueAssignment) { currentDepth--; } - virtual void endVisit(BindingAssignment *bindingAssignment) + virtual void endVisit(SignalHandler *signalHandler) { currentDepth--; } @@ -329,10 +351,10 @@ int methodsVisited; int signalsVisited; int valueAssignmentsVisited; - int bindingAssignmentsVisited; + int signalHandlersVisited; int currentDepth; QString lastPropertyVisited; - QQmlJS::AST::ExpressionNode *lastValueAssigned; + QQmlJS::AST::Statement *lastValueAssigned; }; void TestIR::testVisitorAPI() @@ -347,52 +369,13 @@ QCOMPARE(visitor.methodsVisited, 1); QCOMPARE(visitor.signalsVisited, 1); QCOMPARE(visitor.valueAssignmentsVisited, 3); - QCOMPARE(visitor.bindingAssignmentsVisited, 0); + QCOMPARE(visitor.signalHandlersVisited, 0); QCOMPARE(visitor.lastPropertyVisited, QStringLiteral("christiania")); - QCOMPARE(visitor.lastValueAssigned->kind, (int)QQmlJS::AST::Node::Kind_StringLiteral); - QQmlJS::AST::StringLiteral* lastValueAssigned = QQmlJS::AST::cast(visitor.lastValueAssigned); - if (lastValueAssigned) - QCOMPARE(lastValueAssigned->value.toString(), QStringLiteral("København")); -} - -void TestIR::testBuiltinTypes() -{ - Type *boolType = BuiltinTypes::type("bool"); - QVERIFY(boolType); - QCOMPARE(BuiltinTypes::typeFromJSName("Boolean"), boolType); - - Type *doubleType = BuiltinTypes::type("double"); - QVERIFY(doubleType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWDouble"), doubleType); - - Type *enumType = BuiltinTypes::type("enum"); - QVERIFY(enumType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWEnum"), enumType); - - Type *intType = BuiltinTypes::type("int"); - QVERIFY(intType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWInt"), intType); - - Type *listType = BuiltinTypes::type("list"); - QVERIFY(listType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWList"), listType); - QCOMPARE(listType->flags(), Type::IsList); - - Type *realType = BuiltinTypes::type("real"); - QVERIFY(realType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWReal"), realType); - - Type *stringType = BuiltinTypes::type("string"); - QVERIFY(stringType); - QCOMPARE(BuiltinTypes::typeFromJSName("String"), stringType); - - Type *urlType = BuiltinTypes::type("url"); - QVERIFY(urlType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWUrl"), urlType); - - Type *varType = BuiltinTypes::type("var"); - QVERIFY(varType); - QCOMPARE(BuiltinTypes::typeFromJSName("QWVar"), varType); + QCOMPARE(visitor.lastValueAssigned->kind, (int)QQmlJS::AST::Node::Kind_ExpressionStatement); + QQmlJS::AST::ExpressionNode *lastValueExpression = QQmlJS::AST::cast(visitor.lastValueAssigned)->expression; + QCOMPARE(lastValueExpression->kind, (int)QQmlJS::AST::Node::Kind_StringLiteral); + QQmlJS::AST::StringLiteral* lastValueAssigned = QQmlJS::AST::cast(lastValueExpression); + QCOMPARE(lastValueAssigned->value.toString(), QStringLiteral("København")); } } // namespace IR diff --git a/tests/auto/qmljsc/testprettygeneratorpass.cpp b/tests/auto/qmljsc/testprettygeneratorpass.cpp --- a/tests/auto/qmljsc/testprettygeneratorpass.cpp +++ b/tests/auto/qmljsc/testprettygeneratorpass.cpp @@ -115,7 +115,7 @@ void TestPrettyGeneratorPass::emitsFinishedSignal() { // Setup QmlJSc::IR::File file; - file.setRootObject(new QmlJSc::IR::Object); + file.setRootObject(new QmlJSc::IR::ObjectSpec); file.rootObject()->setSuper(m_qtObjectType); TestNodeFile testNodeFile(file); @@ -134,7 +134,7 @@ QString minimalFileJs = readTestFileContent("minimal.qml.js"); QmlJSc::IR::File file; - file.setRootObject(new QmlJSc::IR::Object); + file.setRootObject(new QmlJSc::IR::ObjectSpec); file.rootObject()->setSuper(m_qtObjectType); TestNodeFile testNodeFile(file); @@ -147,4 +147,4 @@ } QTEST_MAIN(TestPrettyGeneratorPass) -#include "testprettygeneratorpass.moc" \ No newline at end of file +#include "testprettygeneratorpass.moc"