From 89ff3cebe6d9ca8385d8d9300cfaa47cf07adb42 Mon Sep 17 00:00:00 2001
From: Christian Kamm <christian.d.kamm@nokia.com>
Date: Mon, 10 Oct 2011 12:53:28 +0200
Subject: [PATCH] QmlJS: Introduce UnknownValue.

To distinguish known-to-be-undefined from a genuinely unknown value.

Change-Id: I606b4ea4d726f94553400b8950d3c0a4e76564a8
Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com>
---
 src/libs/qmljs/qmljsbind.cpp                  |  2 +-
 src/libs/qmljs/qmljscheck.cpp                 | 14 ++--
 src/libs/qmljs/qmljsevaluate.cpp              |  3 +-
 src/libs/qmljs/qmljsinterpreter.cpp           | 66 ++++++++-----------
 src/libs/qmljs/qmljsinterpreter.h             | 19 +++++-
 src/libs/qmljs/qmljslink.cpp                  |  2 +-
 src/libs/qmljs/qmljsscopechain.cpp            |  2 +
 src/libs/qmljs/qmljsvalueowner.cpp            | 27 +++++---
 src/libs/qmljs/qmljsvalueowner.h              |  3 +
 .../qml/codemodel/check/equality-checks.qml   | 43 +++++++-----
 10 files changed, 105 insertions(+), 76 deletions(-)

diff --git a/src/libs/qmljs/qmljsbind.cpp b/src/libs/qmljs/qmljsbind.cpp
index 99431b593bd..abc219e791a 100644
--- a/src/libs/qmljs/qmljsbind.cpp
+++ b/src/libs/qmljs/qmljsbind.cpp
@@ -325,7 +325,7 @@ bool Bind::visit(FunctionExpression *ast)
     // 1. Function formal arguments
     for (FormalParameterList *it = ast->formals; it; it = it->next) {
         if (!it->name.isEmpty())
-            functionScope->setMember(it->name.toString(), _valueOwner.undefinedValue());
+            functionScope->setMember(it->name.toString(), _valueOwner.unknownValue());
     }
 
     // 2. Functions defined inside the function body
diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp
index 88f6ffe55eb..676bb5e11c8 100644
--- a/src/libs/qmljs/qmljscheck.cpp
+++ b/src/libs/qmljs/qmljscheck.cpp
@@ -89,7 +89,7 @@ public:
                     setMessage(ErrInvalidEnumValue);
                 }
             } else if (! _rhsValue->asStringValue() && ! _rhsValue->asNumberValue()
-                       && ! _rhsValue->asUndefinedValue()) {
+                       && ! _rhsValue->asUnknownValue()) {
                 setMessage(ErrEnumValueMustBeStringOrNumber);
             }
         } else {
@@ -155,7 +155,7 @@ public:
 
     virtual void visit(const AnchorLineValue *)
     {
-        if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUndefinedValue()))
+        if (! (_rhsValue->asAnchorLineValue() || _rhsValue->asUnknownValue()))
             setMessage(ErrAnchorLineExpected);
     }
 
@@ -842,9 +842,8 @@ bool Check::visit(FunctionExpression *ast)
 
 static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs)
 {
-    // we currently use undefined as a "we don't know" value
-    if (lhs->asUndefinedValue() || rhs->asUndefinedValue())
-        return true;
+    if (lhs->asUnknownValue() || rhs->asUnknownValue())
+        return true; // may coerce or not
 
     if (lhs->asStringValue() && rhs->asNumberValue())
         return true; // coerces string to number
@@ -855,7 +854,8 @@ static bool shouldAvoidNonStrictEqualityCheck(const Value *lhs, const Value *rhs
     if (lhs->asObjectValue() && rhs->asStringValue())
         return true; // coerces object to primitive
 
-    if (lhs->asBooleanValue() && !rhs->asBooleanValue())
+    if (lhs->asBooleanValue() && (!rhs->asBooleanValue()
+                                  && !rhs->asUndefinedValue()))
         return true; // coerces bool to number
 
     return false;
@@ -1156,7 +1156,7 @@ bool Check::visit(NewMemberExpression *ast)
             if (ast->arguments && ast->arguments->expression && !ast->arguments->next) {
                 Evaluate evaluate(&_scopeChain);
                 const Value *arg = evaluate(ast->arguments->expression);
-                if (arg->asNumberValue() || arg->asUndefinedValue())
+                if (arg->asNumberValue() || arg->asUnknownValue())
                     ok = true;
             }
             if (!ok)
diff --git a/src/libs/qmljs/qmljsevaluate.cpp b/src/libs/qmljs/qmljsevaluate.cpp
index 501636e7e91..75e301344cc 100644
--- a/src/libs/qmljs/qmljsevaluate.cpp
+++ b/src/libs/qmljs/qmljsevaluate.cpp
@@ -69,8 +69,9 @@ const Value *Evaluate::value(AST::Node *ast)
             result = _context->lookupReference(ref);
     }
 
+    // if evaluation fails, return an unknown value
     if (! result)
-        result = _valueOwner->undefinedValue();
+        result = _valueOwner->unknownValue();
 
     return result;
 }
diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp
index f82c8366456..b448212e3d9 100644
--- a/src/libs/qmljs/qmljsinterpreter.cpp
+++ b/src/libs/qmljs/qmljsinterpreter.cpp
@@ -123,21 +123,11 @@ public:
     {
     }
 
-    virtual const Value *returnValue() const
-    {
-        return valueOwner()->undefinedValue();
-    }
-
     virtual int argumentCount() const
     {
         return _method.parameterNames().size();
     }
 
-    virtual const Value *argument(int) const
-    {
-        return valueOwner()->undefinedValue();
-    }
-
     virtual QString argumentName(int index) const
     {
         if (index < _method.parameterNames().size())
@@ -153,7 +143,7 @@ public:
 
     virtual const Value *invoke(const Activation *) const
     {
-        return valueOwner()->undefinedValue();
+        return valueOwner()->unknownValue();
     }
 };
 
@@ -264,7 +254,7 @@ void CppComponentValue::processMembers(MemberProcessor *processor) const
         if (!explicitSignals.contains(signalName)) {
             // process the generated slot
             const QString &slotName = generatedSlotName(signalName);
-            processor->processGeneratedSlot(slotName, valueOwner()->undefinedValue());
+            processor->processGeneratedSlot(slotName, valueOwner()->unknownValue());
         }
     }
 
@@ -337,7 +327,8 @@ const Value *CppComponentValue::valueForCppName(const QString &typeName) const
             return value;
     }
 
-    return valueOwner()->undefinedValue();
+    // may still be a cpp based value
+    return valueOwner()->unknownValue();
 }
 
 const CppComponentValue *CppComponentValue::prototype() const
@@ -566,6 +557,10 @@ void ValueVisitor::visit(const UndefinedValue *)
 {
 }
 
+void ValueVisitor::visit(const UnknownValue *)
+{
+}
+
 void ValueVisitor::visit(const NumberValue *)
 {
 }
@@ -624,6 +619,11 @@ const UndefinedValue *Value::asUndefinedValue() const
     return 0;
 }
 
+const UnknownValue *Value::asUnknownValue() const
+{
+    return 0;
+}
+
 const NumberValue *Value::asNumberValue() const
 {
     return 0;
@@ -727,11 +727,20 @@ const UndefinedValue *UndefinedValue::asUndefinedValue() const
     return this;
 }
 
-void UndefinedValue::accept(ValueVisitor *visitor) const
+void UnknownValue::accept(ValueVisitor *visitor) const
 {
     visitor->visit(this);
 }
 
+const UnknownValue *UnknownValue::asUnknownValue() const
+{
+    return this;
+}
+
+void UndefinedValue::accept(ValueVisitor *visitor) const
+{
+    visitor->visit(this);
+}
 const NumberValue *NumberValue::asNumberValue() const
 {
     return this;
@@ -1168,7 +1177,7 @@ const Value *FunctionValue::call(const ObjectValue *thisObject, const ValueList
 
 const Value *FunctionValue::returnValue() const
 {
-    return valueOwner()->undefinedValue();
+    return valueOwner()->unknownValue();
 }
 
 int FunctionValue::argumentCount() const
@@ -1178,7 +1187,7 @@ int FunctionValue::argumentCount() const
 
 const Value *FunctionValue::argument(int) const
 {
-    return valueOwner()->undefinedValue();
+    return valueOwner()->unknownValue();
 }
 
 QString FunctionValue::argumentName(int index) const
@@ -1812,8 +1821,9 @@ ASTVariableReference::~ASTVariableReference()
 
 const Value *ASTVariableReference::value(ReferenceContext *referenceContext) const
 {
+    // may be assigned to later
     if (!_ast->expression)
-        return valueOwner()->undefinedValue();
+        return valueOwner()->unknownValue();
 
     Document::Ptr doc = _doc->ptr();
     ScopeChain scopeChain(doc, referenceContext->context());
@@ -1850,21 +1860,11 @@ FunctionExpression *ASTFunctionValue::ast() const
     return _ast;
 }
 
-const Value *ASTFunctionValue::returnValue() const
-{
-    return valueOwner()->undefinedValue();
-}
-
 int ASTFunctionValue::argumentCount() const
 {
     return _argumentNames.size();
 }
 
-const Value *ASTFunctionValue::argument(int) const
-{
-    return valueOwner()->undefinedValue();
-}
-
 QString ASTFunctionValue::argumentName(int index) const
 {
     if (index < _argumentNames.size()) {
@@ -1876,11 +1876,6 @@ QString ASTFunctionValue::argumentName(int index) const
     return FunctionValue::argumentName(index);
 }
 
-bool ASTFunctionValue::isVariadic() const
-{
-    return true;
-}
-
 bool ASTFunctionValue::getSourceLocation(QString *fileName, int *line, int *column) const
 {
     *fileName = _doc->fileName();
@@ -1963,10 +1958,7 @@ const Value *ASTPropertyReference::value(ReferenceContext *referenceContext) con
         return evaluator(_ast->statement);
     }
 
-    if (!_ast->memberType.isEmpty())
-        return valueOwner()->defaultValueForBuiltinType(_ast->memberType.toString());
-
-    return valueOwner()->undefinedValue();
+    return valueOwner()->defaultValueForBuiltinType(_ast->memberType.toString());
 }
 
 ASTSignal::ASTSignal(UiPublicMember *ast, const Document *doc, ValueOwner *valueOwner)
@@ -2006,7 +1998,7 @@ const Value *ASTSignal::argument(int index) const
     for (int i = 0; param && i < index; ++i)
         param = param->next;
     if (!param || param->type.isEmpty())
-        return valueOwner()->undefinedValue();
+        return valueOwner()->unknownValue();
     return valueOwner()->defaultValueForBuiltinType(param->type.toString());
 }
 
diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h
index c550637ba15..c914d28d0f8 100644
--- a/src/libs/qmljs/qmljsinterpreter.h
+++ b/src/libs/qmljs/qmljsinterpreter.h
@@ -58,6 +58,7 @@ class ValueOwner;
 class Value;
 class NullValue;
 class UndefinedValue;
+class UnknownValue;
 class NumberValue;
 class IntValue;
 class RealValue;
@@ -95,6 +96,7 @@ public:
 
     virtual void visit(const NullValue *);
     virtual void visit(const UndefinedValue *);
+    virtual void visit(const UnknownValue *);
     virtual void visit(const NumberValue *);
     virtual void visit(const BooleanValue *);
     virtual void visit(const StringValue *);
@@ -119,6 +121,7 @@ public:
 
     virtual const NullValue *asNullValue() const;
     virtual const UndefinedValue *asUndefinedValue() const;
+    virtual const UnknownValue *asUnknownValue() const;
     virtual const NumberValue *asNumberValue() const;
     virtual const IntValue *asIntValue() const;
     virtual const RealValue *asRealValue() const;
@@ -161,6 +164,12 @@ template <> Q_INLINE_TEMPLATE const UndefinedValue *value_cast(const Value *v)
     else   return 0;
 }
 
+template <> Q_INLINE_TEMPLATE const UnknownValue *value_cast(const Value *v)
+{
+    if (v) return v->asUnknownValue();
+    else   return 0;
+}
+
 template <> Q_INLINE_TEMPLATE const NumberValue *value_cast(const Value *v)
 {
     if (v) return v->asNumberValue();
@@ -280,6 +289,13 @@ public:
     virtual void accept(ValueVisitor *visitor) const;
 };
 
+class QMLJS_EXPORT UnknownValue: public Value
+{
+public:
+    virtual const UnknownValue *asUnknownValue() const;
+    virtual void accept(ValueVisitor *) const;
+};
+
 class QMLJS_EXPORT NumberValue: public Value
 {
 public:
@@ -793,11 +809,8 @@ public:
 
     AST::FunctionExpression *ast() const;
 
-    virtual const Value *returnValue() const;
     virtual int argumentCount() const;
-    virtual const Value *argument(int) const;
     virtual QString argumentName(int index) const;
-    virtual bool isVariadic() const;
 
     virtual bool getSourceLocation(QString *fileName, int *line, int *column) const;
 };
diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp
index 7d693ec0c7e..3e8cc72f147 100644
--- a/src/libs/qmljs/qmljslink.cpp
+++ b/src/libs/qmljs/qmljslink.cpp
@@ -165,7 +165,7 @@ Link::Link(const Snapshot &snapshot, const QStringList &importPaths, const Libra
                 if (!cppTypeName.isEmpty())
                     value = d->valueOwner->cppQmlTypes().objectByCppName(cppTypeName);
                 if (!value)
-                    value = d->valueOwner->undefinedValue();
+                    value = d->valueOwner->unknownValue();
                 global->setMember(it.key(), value);
             }
         }
diff --git a/src/libs/qmljs/qmljsscopechain.cpp b/src/libs/qmljs/qmljsscopechain.cpp
index 17901321eaf..378e2abbfa1 100644
--- a/src/libs/qmljs/qmljsscopechain.cpp
+++ b/src/libs/qmljs/qmljsscopechain.cpp
@@ -112,6 +112,8 @@ const Value * ScopeChain::lookup(const QString &name, const ObjectValue **foundI
 
     if (foundInScope)
         *foundInScope = 0;
+
+    // we're confident to implement global lookup correctly, so return 'undefined'
     return m_context->valueOwner()->undefinedValue();
 }
 
diff --git a/src/libs/qmljs/qmljsvalueowner.cpp b/src/libs/qmljs/qmljsvalueowner.cpp
index a4b96bd40d2..9182f037404 100644
--- a/src/libs/qmljs/qmljsvalueowner.cpp
+++ b/src/libs/qmljs/qmljsvalueowner.cpp
@@ -292,6 +292,11 @@ const UndefinedValue *ValueOwner::undefinedValue() const
     return &_undefinedValue;
 }
 
+const UnknownValue *ValueOwner::unknownValue() const
+{
+    return &_unknownValue;
+}
+
 const NumberValue *ValueOwner::numberValue() const
 {
     return &_numberValue;
@@ -488,7 +493,7 @@ Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, cons
     Function *function = newFunction();
     function->setReturnValue(result);
     for (int i = 0; i < argumentCount; ++i)
-        function->addArgument(undefinedValue()); // ### introduce unknownValue
+        function->addArgument(unknownValue());
     object->setMember(name, function);
     return function;
 }
@@ -497,7 +502,7 @@ Function *ValueOwner::addFunction(ObjectValue *object, const QString &name, int
 {
     Function *function = newFunction();
     for (int i = 0; i < argumentCount; ++i)
-        function->addArgument(undefinedValue()); // ### introduce unknownValue
+        function->addArgument(unknownValue());
     object->setMember(name, function);
     return function;
 }
@@ -775,7 +780,7 @@ void ValueOwner::initializePrototypes()
     f->addArgument(stringValue(), "header");
     f->addArgument(stringValue(), "value");
     f = addFunction(xmlHttpRequest, "send");
-    f->addArgument(undefinedValue(), "data");
+    f->addArgument(unknownValue(), "data");
     f = addFunction(xmlHttpRequest, "abort");
     xmlHttpRequest->setMember("status", numberValue());
     xmlHttpRequest->setMember("statusText", stringValue());
@@ -783,7 +788,7 @@ void ValueOwner::initializePrototypes()
     f->addArgument(stringValue(), "header");
     f = addFunction(xmlHttpRequest, "getAllResponseHeaders");
     xmlHttpRequest->setMember("responseText", stringValue());
-    xmlHttpRequest->setMember("responseXML", undefinedValue());
+    xmlHttpRequest->setMember("responseXML", unknownValue());
 
     f = addFunction(_globalObject, "XMLHttpRequest", xmlHttpRequest);
     f->setMember("prototype", xmlHttpRequest);
@@ -814,9 +819,9 @@ void ValueOwner::initializePrototypes()
     f->addArgument(stringValue(), "text");
     f->addArgument(functionPrototype(), "reviver");
     f = addFunction(json, "stringify", stringValue());
-    f->addArgument(undefinedValue(), "value");
-    f->addArgument(undefinedValue(), "replacer");
-    f->addArgument(undefinedValue(), "space");
+    f->addArgument(unknownValue(), "value");
+    f->addArgument(unknownValue(), "replacer");
+    f->addArgument(unknownValue(), "space");
     _globalObject->setMember("JSON", json);
 
     // global Qt object, in alphabetic order
@@ -864,8 +869,8 @@ void ValueOwner::initializePrototypes()
     _qmlFontObject = newObject(/*prototype =*/ 0);
     _qmlFontObject->setClassName(QLatin1String("Font"));
     _qmlFontObject->setMember("family", stringValue());
-    _qmlFontObject->setMember("weight", undefinedValue()); // ### make me an object
-    _qmlFontObject->setMember("capitalization", undefinedValue()); // ### make me an object
+    _qmlFontObject->setMember("weight", unknownValue()); // ### make me an object
+    _qmlFontObject->setMember("capitalization", unknownValue()); // ### make me an object
     _qmlFontObject->setMember("bold", booleanValue());
     _qmlFontObject->setMember("italic", booleanValue());
     _qmlFontObject->setMember("underline", booleanValue());
@@ -948,7 +953,9 @@ const Value *ValueOwner::defaultValueForBuiltinType(const QString &name) const
         return colorValue();
     } else if (name == QLatin1String("date")) {
         return datePrototype();
+    } else if (name == QLatin1String("var")
+               || name == QLatin1String("variant")) {
+        return unknownValue();
     }
-    // ### variant or var
     return undefinedValue();
 }
diff --git a/src/libs/qmljs/qmljsvalueowner.h b/src/libs/qmljs/qmljsvalueowner.h
index 7e690578da0..4b903f25129 100644
--- a/src/libs/qmljs/qmljsvalueowner.h
+++ b/src/libs/qmljs/qmljsvalueowner.h
@@ -46,6 +46,7 @@ namespace QmlJS {
 class Value;
 class NullValue;
 class UndefinedValue;
+class UnknownValue;
 class NumberValue;
 class IntValue;
 class RealValue;
@@ -72,6 +73,7 @@ public:
 
     const NullValue *nullValue() const;
     const UndefinedValue *undefinedValue() const;
+    const UnknownValue *unknownValue() const;
     const NumberValue *numberValue() const;
     const RealValue *realValue() const;
     const IntValue *intValue() const;
@@ -174,6 +176,7 @@ private:
 
     NullValue _nullValue;
     UndefinedValue _undefinedValue;
+    UnknownValue _unknownValue;
     NumberValue _numberValue;
     RealValue _realValue;
     IntValue _intValue;
diff --git a/tests/auto/qml/codemodel/check/equality-checks.qml b/tests/auto/qml/codemodel/check/equality-checks.qml
index 700331780f0..e92b1c4cc19 100644
--- a/tests/auto/qml/codemodel/check/equality-checks.qml
+++ b/tests/auto/qml/codemodel/check/equality-checks.qml
@@ -1,11 +1,8 @@
 import Qt 4.7
 
 Rectangle {
-    onXChanged: {
-        if (0 == undefined) {} // 126 15 16
-    }
-
-    function foo() {
+    function foo(k) {
+        // k is a unknown value
         var s = ""
         var n = 0
         var N = null
@@ -16,45 +13,59 @@ Rectangle {
         if (s == s) {}
         if (s == n) {} // 126 15 16
         if (s == N) {} // ### should warn: always false
-        if (s == u) {} // 126 15 16
+        if (s == u) {} // ### should warn: always false
         if (s == b) {} // 126 15 16
         if (s == o) {} // 126 15 16
+        if (s == k) {} // 126 15 16
 
         if (n == s) {} // 126 15 16
         if (n == n) {}
         if (n == N) {} // ### should warn: always false
-        if (n == u) {} // 126 15 16
+        if (n == u) {} // ### should warn: always false
         if (n == b) {} // 126 15 16
         if (n == o) {} // 126 15 16
+        if (n == k) {} // 126 15 16
 
         if (N == s) {} // ### should warn: always false
         if (N == n) {} // ### should warn: always false
         if (N == N) {}
-        if (N == u) {} // 126 15 16
+        if (N == u) {} // ### should warn: always true
         // ### should warn: always false
         if (N == b) {} // 126 15 16
         if (N == o) {} // ### should warn: always false
+        if (N == k) {} // 126 15 16
 
-        if (u == s) {} // 126 15 16
-        if (u == n) {} // 126 15 16
-        if (u == N) {} // 126 15 16
-        if (u == u) {} // 126 15 16
-        if (u == b) {} // 126 15 16
-        if (u == o) {} // 126 15 16
+        if (u == s) {} // ### should warn: always false
+        if (u == n) {} // ### should warn: always false
+        if (u == N) {} // ### should warn: always true
+        if (u == u) {} // ### should warn: always true
+        if (u == b) {} // ### should warn: always false
+        if (u == o) {} // ### should warn: always false
+        if (u == k) {} // 126 15 16
 
         if (b == s) {} // 126 15 16
         if (b == n) {} // 126 15 16
         // ### should warn: always false
         if (b == N) {} // 126 15 16
-        if (b == u) {} // 126 15 16
+        if (b == u) {} // ### should warn: always false
         if (b == b) {}
         if (b == o) {} // 126 15 16
+        if (b == k) {} // 126 15 16
 
         if (o == s) {} // 126 15 16
         if (o == n) {} // 126 15 16
         if (o == N) {} // ### should warn: always false
-        if (o == u) {} // 126 15 16
+        if (o == u) {} // ### should warn: always false
         if (o == b) {} // 126 15 16
         if (o == o) {}
+        if (o == k) {} // 126 15 16
+
+        if (k == s) {} // 126 15 16
+        if (k == n) {} // 126 15 16
+        if (k == N) {} // 126 15 16
+        if (k == u) {} // 126 15 16
+        if (k == b) {} // 126 15 16
+        if (k == o) {} // 126 15 16
+        if (k == k) {} // 126 15 16
     }
 }
-- 
GitLab