Commit bb6490b1 authored by Denis Steckelmacher's avatar Denis Steckelmacher

Use DeclarationBuilder to build declarations before ExpressionVisitor tries to find their type

When Declaration wants to find the type of an expression, it starts by recursing
into it (as JS expressions can contain declarations) and then uses ExpressionVisitor
to find its type.

This commit rewrites ExpressionVisitor so that it stops recursing when it first
encounters a type. This way, launching ExpressionVisitor multiple times on
nested expressions does not takes too much time.

REVIEW: 117509
parent 244f8114
......@@ -6,7 +6,6 @@ kde4_add_library(kdevqmljsduchain SHARED
declarationbuilder.cpp
expressionvisitor.cpp
usebuilder.cpp
debugvisitor.cpp
)
target_link_libraries(kdevqmljsduchain LINK_PRIVATE
......
......@@ -43,8 +43,16 @@ QualifiedIdentifier ContextBuilder::identifierForNode(QmlJS::AST::IdentifierProp
AbstractType::Ptr ContextBuilder::findType(QmlJS::AST::Node* node)
{
if (!node) {
return AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed));
}
ExpressionVisitor visitor(currentContext());
QmlJS::AST::Node::accept(node, &visitor);
// Build every needed declaration in node, and then try to guess its type
node->accept(this);
node->accept(&visitor);
return visitor.lastType();
}
......
......@@ -75,6 +75,9 @@ ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url,
return DeclarationBuilderBase::build(url, node, updateContext);
}
/*
* Functions
*/
bool DeclarationBuilder::visit(QmlJS::AST::FunctionDeclaration* node)
{
setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8());
......@@ -94,23 +97,18 @@ bool DeclarationBuilder::visit(QmlJS::AST::FunctionDeclaration* node)
return DeclarationBuilderBase::visit(node);
}
void DeclarationBuilder::endVisit(QmlJS::AST::FunctionDeclaration* node)
{
DeclarationBuilderBase::endVisit(node);
closeAndAssignType();
}
bool DeclarationBuilder::visit(QmlJS::AST::FormalParameterList* node)
{
for (QmlJS::AST::FormalParameterList *plist = node; plist; plist = plist->next) {
const QualifiedIdentifier name(plist->name.toString());
const RangeInRevision range = m_session->locationToRange(plist->identifierToken);
DUChainWriteLocker lock;
Declaration* dec = openDeclaration<Declaration>(name, range);
IntegralType::Ptr type(new IntegralType(IntegralType::TypeMixed));
dec->setType(type);
closeDeclaration();
if (FunctionType::Ptr funType = currentType<FunctionType>()) {
funType->addArgument(type.cast<AbstractType>());
}
......@@ -119,16 +117,28 @@ bool DeclarationBuilder::visit(QmlJS::AST::FormalParameterList* node)
return DeclarationBuilderBase::visit(node);
}
void DeclarationBuilder::endVisit(QmlJS::AST::ReturnStatement* node)
bool DeclarationBuilder::visit(QmlJS::AST::ReturnStatement* node)
{
DeclarationBuilderBase::endVisit(node);
DUChainWriteLocker lock;
if (FunctionType::Ptr type = currentType<FunctionType>()) {
type->setReturnType(findType(node->expression));
AbstractType::Ptr expressionType = findType(node->expression);
DUChainWriteLocker lock;
type->setReturnType(expressionType);
}
return false; // findType has already explored node
}
void DeclarationBuilder::endVisit(QmlJS::AST::FunctionDeclaration* node)
{
DeclarationBuilderBase::endVisit(node);
closeAndAssignType();
}
/*
* Variables
*/
bool DeclarationBuilder::visit(QmlJS::AST::VariableDeclaration* node)
{
setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8());
......@@ -136,13 +146,14 @@ bool DeclarationBuilder::visit(QmlJS::AST::VariableDeclaration* node)
const QualifiedIdentifier name(node->name.toString());
const RangeInRevision range = m_session->locationToRange(node->identifierToken);
const AbstractType::Ptr type = findType(node->expression);
{
DUChainWriteLocker lock;
openDeclaration<Declaration>(name, range);
}
openType(type);
return DeclarationBuilderBase::visit(node);
return false; // findType has already explored node
}
void DeclarationBuilder::endVisit(QmlJS::AST::VariableDeclaration* node)
......@@ -152,24 +163,9 @@ void DeclarationBuilder::endVisit(QmlJS::AST::VariableDeclaration* node)
closeAndAssignType();
}
void DeclarationBuilder::closeContext()
{
{
DUChainWriteLocker lock;
FunctionDeclaration* function = dynamic_cast<FunctionDeclaration*>(currentDeclaration());
DUContext* ctx = currentContext();
if (function && ctx) {
if (ctx->type() == DUContext::Function) {
function->setInternalFunctionContext(ctx);
} else {
Q_ASSERT(ctx->type() == DUContext::Other);
function->setInternalContext(ctx);
}
}
}
DeclarationBuilderBase::closeContext();
}
/*
* UI
*/
bool DeclarationBuilder::visit(QmlJS::AST::UiObjectDefinition* node)
{
setComment(node);
......@@ -217,14 +213,17 @@ bool DeclarationBuilder::visit(QmlJS::AST::UiScriptBinding* node)
const RangeInRevision& range = m_session->locationToRange(node->qualifiedId->identifierToken);
const QualifiedIdentifier id(node->qualifiedId->name.toString());
const AbstractType::Ptr type(new IntegralType(IntegralType::TypeMixed));
const AbstractType::Ptr type(findType(node->statement));
{
DUChainWriteLocker lock;
openDeclaration<ClassMemberDeclaration>(id, range);
}
openType(type);
return false; // findType has already explored node->statement
}
return DeclarationBuilderBase::visit(node);
}
......@@ -261,6 +260,27 @@ void DeclarationBuilder::endVisit(QmlJS::AST::UiPublicMember* node)
closeAndAssignType();
}
/*
* Utils
*/
void DeclarationBuilder::closeContext()
{
{
DUChainWriteLocker lock;
FunctionDeclaration* function = dynamic_cast<FunctionDeclaration*>(currentDeclaration());
DUContext* ctx = currentContext();
if (function && ctx) {
if (ctx->type() == DUContext::Function) {
function->setInternalFunctionContext(ctx);
} else {
Q_ASSERT(ctx->type() == DUContext::Other);
function->setInternalContext(ctx);
}
}
}
DeclarationBuilderBase::closeContext();
}
void DeclarationBuilder::setComment(QmlJS::AST::Node* node)
{
setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8());
......
......@@ -41,16 +41,17 @@ protected:
using Visitor::visit;
using Visitor::endVisit;
// Functions
virtual bool visit(QmlJS::AST::FunctionDeclaration* node);
virtual bool visit(QmlJS::AST::FormalParameterList* node);
virtual bool visit(QmlJS::AST::ReturnStatement* node);
virtual void endVisit(QmlJS::AST::FunctionDeclaration* node);
// Variables
virtual bool visit(QmlJS::AST::VariableDeclaration* node);
virtual void endVisit(QmlJS::AST::VariableDeclaration* node);
virtual bool visit(QmlJS::AST::FormalParameterList* node);
virtual void endVisit(QmlJS::AST::ReturnStatement* node);
// UI
virtual bool visit(QmlJS::AST::UiObjectDefinition* node);
virtual void endVisit(QmlJS::AST::UiObjectDefinition* node);
......
......@@ -19,10 +19,12 @@
#include "expressionvisitor.h"
#include <language/duchain/declaration.h>
#include <language/duchain/duchainlock.h>
#include <language/duchain/types/structuretype.h>
#include <language/duchain/types/functiontype.h>
#include "helper.h"
#include "parsesession.h"
using namespace KDevelop;
......@@ -31,61 +33,105 @@ ExpressionVisitor::ExpressionVisitor(DUContext* context) :
{
}
void ExpressionVisitor::endVisit(QmlJS::AST::ArrayLiteral* /*node*/)
/*
* Literals
*/
bool ExpressionVisitor::visit(QmlJS::AST::NumericLiteral* node)
{
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)));
int num_int_digits = (int)std::log10(node->value) + 1;
setType(
num_int_digits == (int)node->literalToken.length ?
IntegralType::TypeInt :
IntegralType::TypeDouble
);
return false;
}
void ExpressionVisitor::endVisit(QmlJS::AST::FalseLiteral* /*node*/)
bool ExpressionVisitor::visit(QmlJS::AST::StringLiteral*)
{
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)));
setType(IntegralType::TypeString);
return false;
}
void ExpressionVisitor::endVisit(QmlJS::AST::IdentifierExpression* node)
bool ExpressionVisitor::visit(QmlJS::AST::TrueLiteral*)
{
const QualifiedIdentifier name(node->name.toString());
DeclarationPointer dec = QmlJS::getDeclaration(name, DUContextPointer(m_context));
// TODO: the declaration won't have a type yet, if it is used recursively, we'd need to delay it then
// or add proper reference/pointer semantics to Declaration::abstractType
if (dec && dec->abstractType()) {
m_lastType.push(dec->abstractType());
}
setType(IntegralType::TypeBoolean);
return false;
}
void ExpressionVisitor::endVisit(QmlJS::AST::NumericLiteral* node)
bool ExpressionVisitor::visit(QmlJS::AST::FalseLiteral*)
{
if (QString::number(node->value).contains('.')) {
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeDouble)));
} else {
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)));
}
setType(IntegralType::TypeBoolean);
return false;
}
/*
* Object and arrays
*/
bool ExpressionVisitor::visit(QmlJS::AST::ArrayLiteral*)
{
setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)));
return false;
}
bool ExpressionVisitor::visit(QmlJS::AST::ObjectLiteral*)
{
setType(AbstractType::Ptr(new StructureType));
return false;
}
void ExpressionVisitor::endVisit(QmlJS::AST::ObjectLiteral* /*node*/)
/*
* Identifiers and common expressions
*/
bool ExpressionVisitor::visit(QmlJS::AST::IdentifierExpression* node)
{
m_lastType.push(AbstractType::Ptr(new StructureType));
setType(node->name.toString());
return false;
}
void ExpressionVisitor::endVisit(QmlJS::AST::StringLiteral* /*node*/)
/*
* Functions
*/
bool ExpressionVisitor::visit(QmlJS::AST::CallExpression* node)
{
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeString)));
// Find the type of the function called
node->base->accept(this);
FunctionType::Ptr func = FunctionType::Ptr::dynamicCast(m_lastType);
if (func && func->returnType()) {
setType(func->returnType());
}
return false;
}
void ExpressionVisitor::setType(AbstractType::Ptr type)
{
m_lastType = type;
}
void ExpressionVisitor::endVisit(QmlJS::AST::TrueLiteral* /*node*/)
void ExpressionVisitor::setType(IntegralType::CommonIntegralTypes type)
{
m_lastType.push(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)));
m_lastType = AbstractType::Ptr(new IntegralType(type));
}
void ExpressionVisitor::endVisit(QmlJS::AST::FunctionExpression* /*node*/)
void ExpressionVisitor::setType(const QString& declaration)
{
FunctionType::Ptr type(new FunctionType);
type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)));
m_lastType.push(type.cast<AbstractType>());
const QualifiedIdentifier name(declaration);
DeclarationPointer dec = QmlJS::getDeclaration(name, DUContextPointer(m_context));
if (dec && dec->abstractType()) {
setType(dec->abstractType());
} else {
m_lastType = NULL;
}
}
AbstractType::Ptr ExpressionVisitor::lastType()
AbstractType::Ptr ExpressionVisitor::lastType() const
{
return ( m_lastType.isEmpty() ?
AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)) :
m_lastType.last() );
return m_lastType ?
m_lastType :
AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed));
}
......@@ -33,24 +33,31 @@ class KDEVQMLJSDUCHAIN_EXPORT ExpressionVisitor : public QmlJS::AST::Visitor
public:
explicit ExpressionVisitor(KDevelop::DUContext* context);
KDevelop::AbstractType::Ptr lastType() const;
using Visitor::visit;
using Visitor::endVisit;
KDevelop::AbstractType::Ptr lastType();
protected:
virtual void endVisit(QmlJS::AST::ArrayLiteral* node);
virtual void endVisit(QmlJS::AST::FalseLiteral* node);
virtual void endVisit(QmlJS::AST::IdentifierExpression* node);
virtual void endVisit(QmlJS::AST::NumericLiteral* node);
virtual void endVisit(QmlJS::AST::ObjectLiteral* node);
virtual void endVisit(QmlJS::AST::StringLiteral* node);
virtual void endVisit(QmlJS::AST::TrueLiteral* node);
virtual void endVisit(QmlJS::AST::FunctionExpression* node);
virtual bool visit(QmlJS::AST::NumericLiteral* node);
virtual bool visit(QmlJS::AST::StringLiteral* node);
virtual bool visit(QmlJS::AST::TrueLiteral* node);
virtual bool visit(QmlJS::AST::FalseLiteral* node);
virtual bool visit(QmlJS::AST::ArrayLiteral* node);
virtual bool visit(QmlJS::AST::ObjectLiteral* node);
virtual bool visit(QmlJS::AST::IdentifierExpression* node);
virtual bool visit(QmlJS::AST::CallExpression* node);
private:
void setType(KDevelop::AbstractType::Ptr type);
void setType(KDevelop::IntegralType::CommonIntegralTypes type);
void setType(const QString &declaration);
private:
QStack<KDevelop::AbstractType::Ptr> m_lastType;
KDevelop::DUContext* m_context;
KDevelop::AbstractType::Ptr m_lastType;
};
......
......@@ -72,6 +72,7 @@ IndexedString ParseSession::url() const
QList<ProblemPointer> ParseSession::problems() const
{
QList<ProblemPointer> problems;
foreach (const QmlJS::DiagnosticMessage& msg, m_doc->diagnosticMessages()) {
ProblemPointer p(new Problem);
p->setDescription(msg.message);
......@@ -80,6 +81,7 @@ QList<ProblemPointer> ParseSession::problems() const
p->setFinalLocation(DocumentRange(m_url, locationToRange(msg.loc).castToSimpleRange()));
problems << p;
}
return problems;
}
......
......@@ -125,6 +125,7 @@ public:
private:
KDevelop::IndexedString m_url;
QmlJS::Document::MutablePtr m_doc;
typedef QHash<QmlJS::AST::Node*, KDevelop::DUContext*> NodeToContextHash;
NodeToContextHash m_astToContext;
};
......
......@@ -202,7 +202,6 @@ void TestDeclarations::testProperty()
QVERIFY(foo);
QCOMPARE(foo->identifier().toString(), QString("foo"));
QVERIFY(foo->abstractType());
QEXPECT_FAIL("", "type deduction not yet implemented", Continue);
QCOMPARE(foo->abstractType()->toString(), QString("int"));
QCOMPARE(QString::fromUtf8(foo->comment()), QString("some comment"));
}
......
......@@ -10,7 +10,7 @@ function helloWorld()
/**
* "type" : { "toString" : "int" }
*/
var a = 5;
var a = 50;
/**
* "type" : { "toString" : "int" }
......@@ -20,7 +20,7 @@ var b = a;
/**
* "type" : { "toString" : "double" }
*/
var d = 1.2;
var d = 1.0;
/**
* "type" : { "toString" : "array" }
......@@ -28,6 +28,7 @@ var d = 1.2;
var array = [1, 2, 3];
/**
* "EXPECT_FAIL" : { "type" : "function expressions not yet handled" },
* "type" : { "toString" : "function mixed (mixed)" }
*/
var c = function(a) {
......@@ -62,6 +63,11 @@ function testVariables()
*/
var arg = "arg";
/**
* "type" : { "toString" : "string" }
*/
var s = testVariables();
/**
* "type": { "toString" : "function mixed (mixed)" },
* "returnType" : { "toString" : "mixed" }
......@@ -76,6 +82,7 @@ function testReturnMixedArg(arg)
/**
* "EXPECT_FAIL": { "type" : "function expressions not yet handled", "returnType" : "function expressions not yet handled" },
* "type": { "toString" : "function void ()" },
* "returnType" : { "toString" : "void" }
*/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment