aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-05-20 14:09:08 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2021-05-26 14:46:39 +0200
commit0472a7a4326cf9ac1abdcbc0910c3b13f5c4fb5a (patch)
treea6d6ca7ecebcd733d11997f23a7ef2114b078457 /src/qml
parente2f247fafcd90c02e5db686c791ff4818be9cc50 (diff)
Evaluate type assertions in QML
Type assertions actually check whether the expression matches the type, and return null if it doesn't. [ChangeLog][QtQml] You can use TypeScript-like type assertions using "as" now. In contrast to TypeScript, QML's type assertions are enforced at runtime. If the type doesn't match, null is returned for object types. Also, type assertions can only cast to object types. There is no way to create a value type or primitive type reference. As value types and primitives cannot be polymorphic, this doesn't matter, though. There are other ways of converting those. Task-number: QTBUG-93662 Change-Id: I00fce3d4ea7a8c6b4631c580eaf6c113ac485813 Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io> Reviewed-by: Paul Wicking <paul.wicking@qt.io>
Diffstat (limited to 'src/qml')
-rw-r--r--src/qml/common/qv4compileddata_p.h2
-rw-r--r--src/qml/compiler/qv4codegen.cpp12
-rw-r--r--src/qml/compiler/qv4instr_moth.cpp4
-rw-r--r--src/qml/compiler/qv4instr_moth_p.h2
-rw-r--r--src/qml/doc/src/javascript/hostenvironment.qdoc31
-rw-r--r--src/qml/jit/qv4baselinejit.cpp10
-rw-r--r--src/qml/jit/qv4baselinejit_p.h1
-rw-r--r--src/qml/jsruntime/qv4runtime.cpp14
-rw-r--r--src/qml/jsruntime/qv4runtimeapi_p.h4
-rw-r--r--src/qml/jsruntime/qv4vme_moth.cpp6
-rw-r--r--src/qml/parser/qqmljs.g24
11 files changed, 86 insertions, 24 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h
index dfa8101d4a..00a1890bf6 100644
--- a/src/qml/common/qv4compileddata_p.h
+++ b/src/qml/common/qv4compileddata_p.h
@@ -79,7 +79,7 @@ QT_BEGIN_NAMESPACE
// Also change the comment behind the number to describe the latest change. This has the added
// benefit that if another patch changes the version too, it will result in a merge conflict, and
// not get removed silently.
-#define QV4_DATA_STRUCTURE_VERSION 0x32// added new bytecode instructions for optional chaining
+#define QV4_DATA_STRUCTURE_VERSION 0x33 // added new bytecode instruction for type assertions
class QIODevice;
class QQmlTypeNameCache;
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp
index d13ef1bc86..f7c4e2c98f 100644
--- a/src/qml/compiler/qv4codegen.cpp
+++ b/src/qml/compiler/qv4codegen.cpp
@@ -1536,6 +1536,7 @@ bool Codegen::visit(BinaryExpression *ast)
// intentional fall-through!
case QSOperator::In:
case QSOperator::InstanceOf:
+ case QSOperator::As:
case QSOperator::Equal:
case QSOperator::NotEqual:
case QSOperator::Ge:
@@ -1568,9 +1569,6 @@ bool Codegen::visit(BinaryExpression *ast)
break;
}
- case QSOperator::As:
- setExprResult(left);
- break;
} // switch
return false;
@@ -1734,6 +1732,14 @@ Codegen::Reference Codegen::binopHelper(QSOperator::Op oper, Reference &left, Re
bytecodeGenerator->addInstruction(binop);
break;
}
+ case QSOperator::As: {
+ Instruction::As as;
+ left = left.storeOnStack();
+ right.loadInAccumulator();
+ as.lhs = left.stackSlot();
+ bytecodeGenerator->addInstruction(as);
+ break;
+ }
case QSOperator::In: {
Instruction::CmpIn binop;
left = left.storeOnStack();
diff --git a/src/qml/compiler/qv4instr_moth.cpp b/src/qml/compiler/qv4instr_moth.cpp
index c791790cba..542e33870e 100644
--- a/src/qml/compiler/qv4instr_moth.cpp
+++ b/src/qml/compiler/qv4instr_moth.cpp
@@ -657,6 +657,10 @@ void dumpBytecode(const char *code, int len, int nLocals, int nFormals, int /*st
d << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(Sub)
+ MOTH_BEGIN_INSTR(As)
+ d << dumpRegister(lhs, nFormals) << ", acc";
+ MOTH_END_INSTR(Sub)
+
MOTH_BEGIN_INSTR(CmpIn)
d << dumpRegister(lhs, nFormals) << ", acc";
MOTH_END_INSTR(CmpIn)
diff --git a/src/qml/compiler/qv4instr_moth_p.h b/src/qml/compiler/qv4instr_moth_p.h
index 69e4eb26f3..7662f8461f 100644
--- a/src/qml/compiler/qv4instr_moth_p.h
+++ b/src/qml/compiler/qv4instr_moth_p.h
@@ -192,6 +192,7 @@ QT_BEGIN_NAMESPACE
#define INSTR_Div(op) INSTRUCTION(op, Div, 1, lhs)
#define INSTR_Mod(op) INSTRUCTION(op, Mod, 1, lhs)
#define INSTR_Sub(op) INSTRUCTION(op, Sub, 1, lhs)
+#define INSTR_As(op) INSTRUCTION(op, As, 1, lhs)
#define INSTR_LoadQmlImportedScripts(op) INSTRUCTION(op, LoadQmlImportedScripts, 1, result)
#define INSTR_InitializeBlockDeadTemporalZone(op) INSTRUCTION(op, InitializeBlockDeadTemporalZone, 2, firstReg, count)
#define INSTR_ThrowOnNullOrUndefined(op) INSTRUCTION(op, ThrowOnNullOrUndefined, 0)
@@ -284,6 +285,7 @@ QT_BEGIN_NAMESPACE
F(Div) \
F(Mod) \
F(Sub) \
+ F(As) \
F(CallValue) \
F(CallWithReceiver) \
F(CallProperty) \
diff --git a/src/qml/doc/src/javascript/hostenvironment.qdoc b/src/qml/doc/src/javascript/hostenvironment.qdoc
index 0c495fdaa9..6f1134a3e2 100644
--- a/src/qml/doc/src/javascript/hostenvironment.qdoc
+++ b/src/qml/doc/src/javascript/hostenvironment.qdoc
@@ -53,6 +53,37 @@ specific to the browser environment. In the case of the W3Schools link above, th
Reference} section generally covers the standard, while the \c{Browser Objects Reference} and \c{HTML DOM
Objects Reference} sections are browser specific (and thus not applicable to QML).
+\section1 Type annotations and assertions
+
+Function declarations can, and should, contain type annotations. Type
+annotations are appended to the declaration of arguments and to the function
+itself, for annotating the return type. The following function takes an \c int
+and a \c string parameter, and returns a \c QtObject:
+
+\qml
+function doThings(a: int, b: string) : QtObject { ... }
+\endqml
+
+Type annotations help tools like \l{Qt Creator} and \l{qmllint} to make sense
+of the code and provide better diagnostics. Moreover, they make functions easier
+to use from C++. See
+\l {qtqml-cppintegration-interactqmlfromcpp.html}{Interacting with QML Objects from C++}
+for more information.
+
+Type assertions can also be used in order to cast an object to a different
+object type. If the object is actually of the given type, then the type
+assertion returns the same object. If not, it returns \c null. In the following
+snippet we assert that the \c parent object is a \c Rectangle before accessing
+a specific member of it.
+
+\qml
+Item {
+ property color parentColor: (parent as Rectangle)?.color || "red"
+}
+\endqml
+
+The optional chaining (\c{?.}) avoids throwing an exception if the parent is
+actually not a rectangle. In that case "red" is chosen as \c parentColor.
\section1 QML Global Object
diff --git a/src/qml/jit/qv4baselinejit.cpp b/src/qml/jit/qv4baselinejit.cpp
index f660e363ea..b21acf93c7 100644
--- a/src/qml/jit/qv4baselinejit.cpp
+++ b/src/qml/jit/qv4baselinejit.cpp
@@ -864,6 +864,16 @@ void BaselineJIT::generate_CmpInstanceOf(int lhs)
BASELINEJIT_GENERATE_RUNTIME_CALL(Instanceof, CallResultDestination::InAccumulator);
}
+void BaselineJIT::generate_As(int lhs)
+{
+ STORE_ACC();
+ as->prepareCallWithArgCount(3);
+ as->passAccumulatorAsArg(2);
+ as->passJSSlotAsArg(lhs, 1);
+ as->passEngineAsArg(0);
+ BASELINEJIT_GENERATE_RUNTIME_CALL(As, CallResultDestination::InAccumulator);
+}
+
void BaselineJIT::generate_UNot() { as->unot(); }
void BaselineJIT::generate_UPlus() { as->toNumber(); }
void BaselineJIT::generate_UMinus() { as->uminus(); }
diff --git a/src/qml/jit/qv4baselinejit_p.h b/src/qml/jit/qv4baselinejit_p.h
index 1fa20d5902..35b1f18344 100644
--- a/src/qml/jit/qv4baselinejit_p.h
+++ b/src/qml/jit/qv4baselinejit_p.h
@@ -180,6 +180,7 @@ public:
void generate_CmpStrictNotEqual(int lhs) override;
void generate_CmpIn(int lhs) override;
void generate_CmpInstanceOf(int lhs) override;
+ void generate_As(int lhs) override;
void generate_UNot() override;
void generate_UPlus() override;
void generate_UMinus() override;
diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp
index a3b1a52535..01a7878dae 100644
--- a/src/qml/jsruntime/qv4runtime.cpp
+++ b/src/qml/jsruntime/qv4runtime.cpp
@@ -60,6 +60,7 @@
#include <private/qqmlengine_p.h>
#include <private/qqmljavascriptexpression_p.h>
#include <private/qqmljsast_p.h>
+#include <private/qqmlvaluetypewrapper_p.h>
#include "qv4qobjectwrapper_p.h"
#include "qv4symbol_p.h"
#include "qv4generatorobject_p.h"
@@ -393,6 +394,19 @@ QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Valu
return scope.hasException() ? Encode::undefined() : Encode(result->toBoolean());
}
+QV4::ReturnedValue Runtime::As::call(ExecutionEngine *engine, const Value &lval, const Value &rval)
+{
+ Scope scope(engine);
+ ScopedValue result(scope, Runtime::Instanceof::call(engine, lval, rval));
+
+ if (scope.hasException())
+ engine->catchException();
+ else if (result->toBoolean())
+ return lval.asReturnedValue();
+
+ return Encode::null();
+}
+
QV4::ReturnedValue Runtime::In::call(ExecutionEngine *engine, const Value &left, const Value &right)
{
Object *ro = right.objectValue();
diff --git a/src/qml/jsruntime/qv4runtimeapi_p.h b/src/qml/jsruntime/qv4runtimeapi_p.h
index 012a43251a..4a83669979 100644
--- a/src/qml/jsruntime/qv4runtimeapi_p.h
+++ b/src/qml/jsruntime/qv4runtimeapi_p.h
@@ -358,6 +358,10 @@ struct Q_QML_PRIVATE_EXPORT Runtime {
{
static ReturnedValue call(ExecutionEngine *, const Value &, const Value &);
};
+ struct Q_QML_PRIVATE_EXPORT As : Method<Throws::No>
+ {
+ static ReturnedValue call(ExecutionEngine *, const Value &, const Value &);
+ };
struct Q_QML_PRIVATE_EXPORT In : Method<Throws::Yes>
{
static ReturnedValue call(ExecutionEngine *, const Value &, const Value &);
diff --git a/src/qml/jsruntime/qv4vme_moth.cpp b/src/qml/jsruntime/qv4vme_moth.cpp
index ff1442db3b..3a75979377 100644
--- a/src/qml/jsruntime/qv4vme_moth.cpp
+++ b/src/qml/jsruntime/qv4vme_moth.cpp
@@ -1373,6 +1373,12 @@ QV4::ReturnedValue VME::interpret(JSTypesStackFrame *frame, ExecutionEngine *eng
}
MOTH_END_INSTR(Sub)
+ MOTH_BEGIN_INSTR(As)
+ const Value left = STACK_VALUE(lhs);
+ STORE_ACC();
+ acc = Runtime::As::call(engine, left, accumulator);
+ MOTH_END_INSTR(As)
+
MOTH_BEGIN_INSTR(Exp)
const Value left = STACK_VALUE(lhs);
double base = left.toNumber();
diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g
index 2716983f42..eb082b77c2 100644
--- a/src/qml/parser/qqmljs.g
+++ b/src/qml/parser/qqmljs.g
@@ -1730,14 +1730,6 @@ TypeAnnotation: T_COLON Type;
} break;
./
-
-TypeExpression: Type;
-/.
- case $rule_number: {
- sym(1).Expression = new (pool) AST::TypeExpression(sym(1).Type);
- } break;
-./
-
TypeAnnotationOpt: TypeAnnotation;
TypeAnnotationOpt: ;
/.
@@ -2763,30 +2755,22 @@ RelationalOperator: T_INSTANCEOF;
sym(1).ival = QSOperator::InstanceOf;
} break;
./
-
-RelationalExpression_In: RelationalExpression_In T_IN ShiftExpression;
+RelationalOperator: T_AS;
/.
case $rule_number: {
- AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, QSOperator::In, sym(3).Expression);
- node->operatorToken = loc(2);
- sym(1).Node = node;
+ sym(1).ival = QSOperator::As;
} break;
./
-TypeAssertExpression_In: RelationalExpression_In T_AS TypeExpression;
-/. case $rule_number: Q_FALLTHROUGH(); ./
-TypeAssertExpression: RelationalExpression T_AS TypeExpression;
+RelationalExpression_In: RelationalExpression_In T_IN ShiftExpression;
/.
case $rule_number: {
- AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, QSOperator::As, sym(3).Expression);
+ AST::BinaryExpression *node = new (pool) AST::BinaryExpression(sym(1).Expression, QSOperator::In, sym(3).Expression);
node->operatorToken = loc(2);
sym(1).Node = node;
} break;
./
-RelationalExpression_In: TypeAssertExpression_In;
-RelationalExpression: TypeAssertExpression;
-
EqualityExpression_In: RelationalExpression_In;
EqualityExpression: RelationalExpression;