diff options
27 files changed, 545 insertions, 98 deletions
diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index 453963c1dd..6248348d8e 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -610,6 +610,8 @@ void Codegen::initializeAndDestructureBindingElement(AST::PatternElement *e, con if (hasError()) return; + accept(e->typeAnnotation); + if (e->initializer) { if (!baseRef.isValid()) { // assignment @@ -885,6 +887,12 @@ bool Codegen::visit(ExportDeclaration *ast) return false; } +bool Codegen::visit(TypeAnnotation *ast) +{ + throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Type annotations are not supported (yet).")); + return false; +} + bool Codegen::visit(StatementList *) { Q_UNREACHABLE(); diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 51b821aafe..ef710b5648 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -593,6 +593,8 @@ protected: bool visit(AST::ExportDeclaration *ast) override; + bool visit(AST::TypeAnnotation *ast) override; + // expressions bool visit(AST::Expression *ast) override; bool visit(AST::ArrayPattern *ast) override; diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 416a0edee0..11da6605c3 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -679,6 +679,11 @@ bool ScanFunctions::enterFunction(Node *ast, const QString &name, FormalParamete _context->isArrowFunction = true; else if (expr->isGenerator) _context->isGenerator = true; + + if (expr->typeAnnotation) { + _cg->throwSyntaxError(ast->firstSourceLocation(), QLatin1String("Type annotations are not supported (yet).")); + return false; + } } diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index daaa402ef1..28566e21ea 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -299,6 +299,9 @@ public: AST::ExportsList *ExportsList; AST::ExportClause *ExportClause; AST::ExportDeclaration *ExportDeclaration; + AST::TypeAnnotation *TypeAnnotation; + AST::TypeArgumentList *TypeArgumentList; + AST::Type *Type; AST::UiProgram *UiProgram; AST::UiHeaderItemList *UiHeaderItemList; @@ -424,6 +427,8 @@ protected: diagnostic_messages.append(compileError(location, message)); } + bool ensureNoFunctionTypeAnnotations(AST::TypeAnnotation *returnTypeAnnotation, AST::FormalParameterList *formals); + protected: Engine *driver; MemoryPool *pool; @@ -594,6 +599,21 @@ int Parser::lookaheadToken(Lexer *lexer) return yytoken; } +bool Parser::ensureNoFunctionTypeAnnotations(AST::TypeAnnotation *returnValueAnnotation, AST::FormalParameterList *formals) +{ + for (auto formal = formals; formal; formal = formal->next) { + if (formal->element && formal->element->typeAnnotation) { + syntaxError(formal->element->typeAnnotation->firstSourceLocation(), "Type annotations are not permitted in function parameters in JavaScript functions"); + return false; + } + } + if (returnValueAnnotation) { + syntaxError(returnValueAnnotation->firstSourceLocation(), "Type annotations are not permitted for the return value of JavaScript functions"); + return false; + } + return true; +} + //#define PARSER_DEBUG bool Parser::parse(int startToken) @@ -1355,7 +1375,7 @@ UiObjectMember: T_READONLY T_PROPERTY UiPropertyType QmlIdentifier T_COLON Expre } break; ./ -UiObjectMember: FunctionDeclaration; +UiObjectMember: FunctionDeclarationWithTypes; /. case $rule_number: { sym(1).Node = new (pool) AST::UiSourceElement(sym(1).Node); @@ -1471,6 +1491,54 @@ IdentifierReference: JsIdentifier; BindingIdentifier: IdentifierReference; -------------------------------------------------------------------------------------------------------- +-- Types +-------------------------------------------------------------------------------------------------------- + +TypeArguments: Type; +/. + case $rule_number: { + sym(1).TypeArgumentList = new (pool) AST::TypeArgumentList(sym(1).Type); + } break; +./ + +TypeArguments: TypeArguments T_COMMA Type; +/. + case $rule_number: { + sym(1).TypeArgumentList = new (pool) AST::TypeArgumentList(sym(1).TypeArgumentList, sym(3).Type); + } break; +./ + +Type: UiQualifiedId T_LT TypeArguments T_GT; +/. + case $rule_number: { + sym(1).Type = new (pool) AST::Type(sym(1).UiQualifiedId, sym(3).TypeArgumentList->finish()); + } break; +./ + +Type: UiQualifiedId; +/. + case $rule_number: { + sym(1).Type = new (pool) AST::Type(sym(1).UiQualifiedId); + } break; +./ + +TypeAnnotation: T_COLON Type; +/. + case $rule_number: { + sym(1).TypeAnnotation = new (pool) AST::TypeAnnotation(sym(2).Type); + sym(1).TypeAnnotation->colonToken = loc(1); + } break; +./ + +TypeAnnotationOpt: TypeAnnotation; +TypeAnnotationOpt: ; +/. + case $rule_number: { + sym(1).TypeAnnotation = nullptr; + } break; +./ + +-------------------------------------------------------------------------------------------------------- -- Expressions -------------------------------------------------------------------------------------------------------- @@ -2851,7 +2919,14 @@ VarDeclaration: Var VariableDeclarationList; VarDeclaration_In: Var VariableDeclarationList_In; /. case $rule_number: { - AST::VariableStatement *node = new (pool) AST::VariableStatement(sym(2).VariableDeclarationList->finish(sym(1).scope)); + AST::VariableDeclarationList *declarations = sym(2).VariableDeclarationList->finish(sym(1).scope); + for (auto it = declarations; it; it = it->next) { + if (it->declaration && it->declaration->typeAnnotation) { + syntaxError(it->declaration->typeAnnotation->firstSourceLocation(), "Type annotations are not permitted in variable declarations"); + return false; + } + } + AST::VariableStatement *node = new (pool) AST::VariableStatement(declarations); node->declarationKindToken = loc(1); sym(1).Node = node; } break; @@ -2888,22 +2963,22 @@ VariableDeclarationList_In: VariableDeclarationList_In T_COMMA VariableDeclarati } break; ./ -LexicalBinding: BindingIdentifier InitializerOpt; +LexicalBinding: BindingIdentifier TypeAnnotationOpt InitializerOpt; /. case $rule_number: Q_FALLTHROUGH(); ./ -LexicalBinding_In: BindingIdentifier InitializerOpt_In; +LexicalBinding_In: BindingIdentifier TypeAnnotationOpt InitializerOpt_In; /. case $rule_number: Q_FALLTHROUGH(); ./ -VariableDeclaration: BindingIdentifier InitializerOpt; +VariableDeclaration: BindingIdentifier TypeAnnotationOpt InitializerOpt; /. case $rule_number: Q_FALLTHROUGH(); ./ -VariableDeclaration_In: BindingIdentifier InitializerOpt_In; +VariableDeclaration_In: BindingIdentifier TypeAnnotationOpt InitializerOpt_In; /. case $rule_number: { - auto *node = new (pool) AST::PatternElement(stringRef(1), sym(2).Expression); + auto *node = new (pool) AST::PatternElement(stringRef(1), sym(2).TypeAnnotation, sym(3).Expression); node->identifierToken = loc(1); sym(1).Node = node; // if initializer is an anonymous function expression, we need to assign identifierref as it's name - if (auto *f = asAnonymousFunctionDefinition(sym(2).Expression)) + if (auto *f = asAnonymousFunctionDefinition(sym(3).Expression)) f->name = stringRef(1); - if (auto *c = asAnonymousClassDefinition(sym(2).Expression)) + if (auto *c = asAnonymousClassDefinition(sym(3).Expression)) c->name = stringRef(1); } break; ./ @@ -3053,15 +3128,15 @@ BindingProperty: PropertyName T_COLON BindingPattern InitializerOpt_In; } break; ./ -BindingElement: BindingIdentifier InitializerOpt_In; +BindingElement: BindingIdentifier TypeAnnotationOpt InitializerOpt_In; /. case $rule_number: { - AST::PatternElement *node = new (pool) AST::PatternElement(stringRef(1), sym(2).Expression); + AST::PatternElement *node = new (pool) AST::PatternElement(stringRef(1), sym(2).TypeAnnotation, sym(3).Expression); node->identifierToken = loc(1); // if initializer is an anonymous function expression, we need to assign identifierref as it's name - if (auto *f = asAnonymousFunctionDefinition(sym(2).Expression)) + if (auto *f = asAnonymousFunctionDefinition(sym(3).Expression)) f->name = stringRef(1); - if (auto *c = asAnonymousClassDefinition(sym(2).Expression)) + if (auto *c = asAnonymousClassDefinition(sym(3).Expression)) c->name = stringRef(1); sym(1).Node = node; } break; @@ -3078,7 +3153,7 @@ BindingElement: BindingPattern InitializerOpt_In; BindingRestElement: T_ELLIPSIS BindingIdentifier; /. case $rule_number: { - AST::PatternElement *node = new (pool) AST::PatternElement(stringRef(2), nullptr, AST::PatternElement::RestElement); + AST::PatternElement *node = new (pool) AST::PatternElement(stringRef(2), /*type annotation*/nullptr, nullptr, AST::PatternElement::RestElement); node->identifierToken = loc(2); sym(1).Node = node; } break; @@ -3268,12 +3343,16 @@ IterationStatement: T_FOR T_LPAREN ForDeclaration InOrOf Expression_In T_RPAREN } break; ./ -ForDeclaration: LetOrConst BindingIdentifier; +ForDeclaration: LetOrConst BindingIdentifier TypeAnnotationOpt; /. case $rule_number: Q_FALLTHROUGH(); ./ -ForDeclaration: Var BindingIdentifier; +ForDeclaration: Var BindingIdentifier TypeAnnotationOpt; /. case $rule_number: { - auto *node = new (pool) AST::PatternElement(stringRef(2), nullptr); + if (auto typeAnnotation = sym(3).TypeAnnotation) { + syntaxError(typeAnnotation->firstSourceLocation(), "Type annotations are not permitted in variable declarations"); + return false; + } + auto *node = new (pool) AST::PatternElement(stringRef(2), sym(3).TypeAnnotation, nullptr); node->identifierToken = loc(2); node->scope = sym(1).scope; node->isForDeclaration = true; @@ -3560,59 +3639,85 @@ DebuggerStatement: T_DEBUGGER T_SEMICOLON; -- otherwise conflict. Function: T_FUNCTION %prec REDUCE_HERE; -FunctionDeclaration: Function BindingIdentifier T_LPAREN FormalParameters T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +FunctionDeclaration: Function BindingIdentifier T_LPAREN FormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(6).TypeAnnotation, sym(4).FormalParameterList)) + return false; + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(8).StatementList, + /*type annotation*/nullptr); node->functionToken = loc(1); node->identifierToken = loc(2); node->lparenToken = loc(3); node->rparenToken = loc(5); - node->lbraceToken = loc(6); - node->rbraceToken = loc(8); + node->lbraceToken = loc(7); + node->rbraceToken = loc(9); sym(1).Node = node; } break; ./ +FunctionDeclarationWithTypes: Function BindingIdentifier T_LPAREN FormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; +/. + case $rule_number: { + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(stringRef(2), sym(4).FormalParameterList, sym(8).StatementList, + sym(6).TypeAnnotation); + node->functionToken = loc(1); + node->identifierToken = loc(2); + node->lparenToken = loc(3); + node->rparenToken = loc(5); + node->lbraceToken = loc(7); + node->rbraceToken = loc(9); + sym(1).Node = node; + } break; +./ FunctionDeclaration_Default: FunctionDeclaration; -FunctionDeclaration_Default: Function T_LPAREN FormalParameters T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +FunctionDeclaration_Default: Function T_LPAREN FormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(QStringRef(), sym(3).FormalParameterList, sym(6).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(5).TypeAnnotation, sym(3).FormalParameterList)) + return false; + AST::FunctionDeclaration *node = new (pool) AST::FunctionDeclaration(QStringRef(), sym(3).FormalParameterList, sym(7).StatementList, + /*type annotation*/nullptr); node->functionToken = loc(1); node->lparenToken = loc(2); node->rparenToken = loc(4); - node->lbraceToken = loc(5); - node->rbraceToken = loc(7); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); sym(1).Node = node; } break; ./ -FunctionExpression: T_FUNCTION BindingIdentifier T_LPAREN FormalParameters T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +FunctionExpression: T_FUNCTION BindingIdentifier T_LPAREN FormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(6).TypeAnnotation, sym(4).FormalParameterList)) + return false; + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(8).StatementList, + /*type annotation*/nullptr); node->functionToken = loc(1); if (! stringRef(2).isNull()) node->identifierToken = loc(2); node->lparenToken = loc(3); node->rparenToken = loc(5); - node->lbraceToken = loc(6); - node->rbraceToken = loc(8); + node->lbraceToken = loc(7); + node->rbraceToken = loc(9); sym(1).Node = node; } break; ./ -FunctionExpression: T_FUNCTION T_LPAREN FormalParameters T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +FunctionExpression: T_FUNCTION T_LPAREN FormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionExpression *node = new (pool) AST::FunctionExpression(QStringRef(), sym(3).FormalParameterList, sym(6).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(5).TypeAnnotation, sym(3).FormalParameterList)) + return false; + AST::FunctionExpression *node = new (pool) AST::FunctionExpression(QStringRef(), sym(3).FormalParameterList, sym(7).StatementList, + /*type annotation*/nullptr); node->functionToken = loc(1); node->lparenToken = loc(2); node->rparenToken = loc(4); - node->lbraceToken = loc(5); - node->rbraceToken = loc(7); + node->lbraceToken = loc(6); + node->rbraceToken = loc(8); sym(1).Node = node; } break; ./ @@ -3722,7 +3827,7 @@ ArrowFunction_In: ArrowParameters T_ARROW ConciseBodyLookahead T_FORCE_BLOCK Fun ArrowParameters: BindingIdentifier; /. case $rule_number: { - AST::PatternElement *e = new (pool) AST::PatternElement(stringRef(1), nullptr, AST::PatternElement::Binding); + AST::PatternElement *e = new (pool) AST::PatternElement(stringRef(1), /*type annotation*/nullptr, nullptr, AST::PatternElement::Binding); e->identifierToken = loc(1); sym(1).FormalParameterList = (new (pool) AST::FormalParameterList(nullptr, e))->finish(pool); } break; @@ -3756,30 +3861,34 @@ ConciseBodyLookahead: ; } break; ./ -MethodDefinition: PropertyName T_LPAREN StrictFormalParameters T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +MethodDefinition: PropertyName T_LPAREN StrictFormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(1), sym(3).FormalParameterList, sym(6).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(5).TypeAnnotation, sym(3).FormalParameterList)) + return false; + AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(1), sym(3).FormalParameterList, sym(7).StatementList); f->functionToken = sym(1).PropertyName->firstSourceLocation(); f->lparenToken = loc(2); f->rparenToken = loc(4); - f->lbraceToken = loc(5); - f->rbraceToken = loc(7); + f->lbraceToken = loc(6); + f->rbraceToken = loc(8); AST::PatternProperty *node = new (pool) AST::PatternProperty(sym(1).PropertyName, f, AST::PatternProperty::Method); node->colonToken = loc(2); sym(1).Node = node; } break; ./ -MethodDefinition: T_STAR PropertyName GeneratorLParen StrictFormalParameters T_RPAREN FunctionLBrace GeneratorBody GeneratorRBrace; +MethodDefinition: T_STAR PropertyName GeneratorLParen StrictFormalParameters T_RPAREN TypeAnnotationOpt FunctionLBrace GeneratorBody GeneratorRBrace; /. case $rule_number: { - AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(6).TypeAnnotation, sym(4).FormalParameterList)) + return false; + AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(8).StatementList); f->functionToken = sym(2).PropertyName->firstSourceLocation(); f->lparenToken = loc(3); f->rparenToken = loc(5); - f->lbraceToken = loc(6); - f->rbraceToken = loc(8); + f->lbraceToken = loc(7); + f->rbraceToken = loc(9); f->isGenerator = true; AST::PatternProperty *node = new (pool) AST::PatternProperty(sym(2).PropertyName, f, AST::PatternProperty::Method); node->colonToken = loc(2); @@ -3788,30 +3897,34 @@ MethodDefinition: T_STAR PropertyName GeneratorLParen StrictFormalParameters T_R ./ -MethodDefinition: T_GET PropertyName T_LPAREN T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +MethodDefinition: T_GET PropertyName T_LPAREN T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), nullptr, sym(6).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(5).TypeAnnotation, /*formals*/nullptr)) + return false; + AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), nullptr, sym(7).StatementList); f->functionToken = sym(2).PropertyName->firstSourceLocation(); f->lparenToken = loc(3); f->rparenToken = loc(4); - f->lbraceToken = loc(5); - f->rbraceToken = loc(7); + f->lbraceToken = loc(6); + f->rbraceToken = loc(8); AST::PatternProperty *node = new (pool) AST::PatternProperty(sym(2).PropertyName, f, AST::PatternProperty::Getter); node->colonToken = loc(2); sym(1).Node = node; } break; ./ -MethodDefinition: T_SET PropertyName T_LPAREN PropertySetParameterList T_RPAREN FunctionLBrace FunctionBody FunctionRBrace; +MethodDefinition: T_SET PropertyName T_LPAREN PropertySetParameterList T_RPAREN TypeAnnotationOpt FunctionLBrace FunctionBody FunctionRBrace; /. case $rule_number: { - AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(7).StatementList); + if (!ensureNoFunctionTypeAnnotations(sym(6).TypeAnnotation, sym(4).FormalParameterList)) + return false; + AST::FunctionExpression *f = new (pool) AST::FunctionExpression(stringRef(2), sym(4).FormalParameterList, sym(8).StatementList); f->functionToken = sym(2).PropertyName->firstSourceLocation(); f->lparenToken = loc(3); f->rparenToken = loc(5); - f->lbraceToken = loc(6); - f->rbraceToken = loc(8); + f->lbraceToken = loc(7); + f->rbraceToken = loc(9); AST::PatternProperty *node = new (pool) AST::PatternProperty(sym(2).PropertyName, f, AST::PatternProperty::Setter); node->colonToken = loc(2); sym(1).Node = node; diff --git a/src/qml/parser/qqmljsast.cpp b/src/qml/parser/qqmljsast.cpp index 1bc0e6e364..de5db73d3f 100644 --- a/src/qml/parser/qqmljsast.cpp +++ b/src/qml/parser/qqmljsast.cpp @@ -131,7 +131,7 @@ FormalParameterList *ExpressionNode::reparseAsFormalParameterList(MemoryPool *po } AST::PatternElement *binding = nullptr; if (AST::IdentifierExpression *idExpr = AST::cast<AST::IdentifierExpression *>(expr)) { - binding = new (pool) AST::PatternElement(idExpr->name, rhs); + binding = new (pool) AST::PatternElement(idExpr->name, /*type annotation*/nullptr, rhs); binding->identifierToken = idExpr->identifierToken; } else if (AST::Pattern *p = expr->patternCast()) { SourceLocation loc; @@ -961,6 +961,7 @@ void FunctionDeclaration::accept0(Visitor *visitor) { if (visitor->visit(this)) { accept(formals, visitor); + accept(typeAnnotation, visitor); accept(body, visitor); } @@ -971,6 +972,7 @@ void FunctionExpression::accept0(Visitor *visitor) { if (visitor->visit(this)) { accept(formals, visitor); + accept(typeAnnotation, visitor); accept(body, visitor); } @@ -1271,6 +1273,35 @@ void UiQualifiedId::accept0(Visitor *visitor) visitor->endVisit(this); } +void Type::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(typeId, visitor); + accept(typeArguments, visitor); + } + + visitor->endVisit(this); +} + +void TypeArgumentList::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + for (TypeArgumentList *it = this; it; it = it->next) + accept(it->typeId, visitor); + } + + visitor->endVisit(this); +} + +void TypeAnnotation::accept0(Visitor *visitor) +{ + if (visitor->visit(this)) { + accept(type, visitor); + } + + visitor->endVisit(this); +} + void UiImport::accept0(Visitor *visitor) { if (visitor->visit(this)) { @@ -1339,6 +1370,7 @@ void PatternElement::accept0(Visitor *visitor) { if (visitor->visit(this)) { accept(bindingTarget, visitor); + accept(typeAnnotation, visitor); accept(initializer, visitor); } @@ -1382,6 +1414,7 @@ void PatternProperty::accept0(Visitor *visitor) if (visitor->visit(this)) { accept(name, visitor); accept(bindingTarget, visitor); + accept(typeAnnotation, visitor); accept(initializer, visitor); } diff --git a/src/qml/parser/qqmljsast_p.h b/src/qml/parser/qqmljsast_p.h index c62c11885f..21d143edf1 100644 --- a/src/qml/parser/qqmljsast_p.h +++ b/src/qml/parser/qqmljsast_p.h @@ -234,6 +234,9 @@ public: Kind_PatternElementList, Kind_PatternProperty, Kind_PatternPropertyList, + Kind_Type, + Kind_TypeArgumentList, + Kind_TypeAnnotation, Kind_UiArrayBinding, Kind_UiImport, @@ -304,6 +307,128 @@ public: int kind = Kind_Undefined; }; + +class QML_PARSER_EXPORT UiQualifiedId: public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(UiQualifiedId) + + UiQualifiedId(const QStringRef &name) + : next(this), name(name) + { kind = K; } + + UiQualifiedId(UiQualifiedId *previous, const QStringRef &name) + : name(name) + { + kind = K; + next = previous->next; + previous->next = this; + } + + UiQualifiedId *finish() + { + UiQualifiedId *head = next; + next = nullptr; + return head; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return identifierToken; } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : identifierToken; } + +// attributes + UiQualifiedId *next; + QStringRef name; + SourceLocation identifierToken; +}; + +class QML_PARSER_EXPORT Type: public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(Type) + + Type(UiQualifiedId *typeId, Node *typeArguments = nullptr) + : typeId(typeId) + , typeArguments(typeArguments) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return typeId->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return typeArguments ? typeArguments->lastSourceLocation() : typeId->lastSourceLocation(); } + +// attributes + UiQualifiedId *typeId; + Node *typeArguments; // TypeArgumentList +}; + + +class QML_PARSER_EXPORT TypeArgumentList: public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(TypeArgumentList) + + TypeArgumentList(Type *typeId) + : typeId(typeId) + , next(nullptr) + { kind = K; } + + TypeArgumentList(TypeArgumentList *previous, Type *typeId) + : typeId(typeId) + { + kind = K; + next = previous->next; + previous->next = this; + } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return typeId->firstSourceLocation(); } + + SourceLocation lastSourceLocation() const override + { return next ? next->lastSourceLocation() : typeId->lastSourceLocation(); } + + inline TypeArgumentList *finish() + { + TypeArgumentList *front = next; + next = nullptr; + return front; + } + +// attributes + Type *typeId; + TypeArgumentList *next; +}; + +class QML_PARSER_EXPORT TypeAnnotation: public Node +{ +public: + QQMLJS_DECLARE_AST_NODE(TypeAnnotation) + + TypeAnnotation(Type *type) + : type(type) + { kind = K; } + + void accept0(Visitor *visitor) override; + + SourceLocation firstSourceLocation() const override + { return colonToken; } + + SourceLocation lastSourceLocation() const override + { return type->lastSourceLocation(); } + +// attributes + Type *type; + SourceLocation colonToken; +}; class QML_PARSER_EXPORT ExpressionNode: public Node { public: @@ -729,8 +854,9 @@ public: : initializer(i), type(t) { kind = K; } - PatternElement(const QStringRef &n, ExpressionNode *i = nullptr, Type t = Binding) + PatternElement(const QStringRef &n, TypeAnnotation *typeAnnotation = nullptr, ExpressionNode *i = nullptr, Type t = Binding) : bindingIdentifier(n), initializer(i), type(t) + , typeAnnotation(typeAnnotation) { Q_ASSERT(t >= RestElement); kind = K; @@ -750,7 +876,7 @@ public: { return identifierToken.isValid() ? identifierToken : (bindingTarget ? bindingTarget->firstSourceLocation() : initializer->firstSourceLocation()); } SourceLocation lastSourceLocation() const override - { return initializer ? initializer->lastSourceLocation() : (bindingTarget ? bindingTarget->lastSourceLocation() : identifierToken); } + { return initializer ? initializer->lastSourceLocation() : (bindingTarget ? bindingTarget->lastSourceLocation() : (typeAnnotation ? typeAnnotation->lastSourceLocation() : identifierToken)); } ExpressionNode *destructuringTarget() const { return bindingTarget; } Pattern *destructuringPattern() const { return bindingTarget ? bindingTarget->patternCast() : nullptr; } @@ -768,6 +894,7 @@ public: ExpressionNode *bindingTarget = nullptr; ExpressionNode *initializer = nullptr; Type type = Literal; + TypeAnnotation *typeAnnotation = nullptr; // when used in a VariableDeclarationList VariableScope scope = VariableScope::NoScope; bool isForDeclaration = false; @@ -820,7 +947,7 @@ public: { kind = K; } PatternProperty(PropertyName *name, const QStringRef &n, ExpressionNode *i = nullptr) - : PatternElement(n, i), name(name) + : PatternElement(n, /*type annotation*/nullptr, i), name(name) { kind = K; } PatternProperty(PropertyName *name, Pattern *pattern, ExpressionNode *i = nullptr) @@ -2155,8 +2282,9 @@ class QML_PARSER_EXPORT FunctionExpression: public ExpressionNode public: QQMLJS_DECLARE_AST_NODE(FunctionExpression) - FunctionExpression(const QStringRef &n, FormalParameterList *f, StatementList *b): - name (n), formals (f), body (b) + FunctionExpression(const QStringRef &n, FormalParameterList *f, StatementList *b, TypeAnnotation *typeAnnotation = nullptr): + name (n), formals (f), body (b), + typeAnnotation(typeAnnotation) { kind = K; } void accept0(Visitor *visitor) override; @@ -2175,6 +2303,7 @@ public: bool isGenerator = false; FormalParameterList *formals; StatementList *body; + TypeAnnotation *typeAnnotation; SourceLocation functionToken; SourceLocation identifierToken; SourceLocation lparenToken; @@ -2188,8 +2317,8 @@ class QML_PARSER_EXPORT FunctionDeclaration: public FunctionExpression public: QQMLJS_DECLARE_AST_NODE(FunctionDeclaration) - FunctionDeclaration(const QStringRef &n, FormalParameterList *f, StatementList *b): - FunctionExpression(n, f, b) + FunctionDeclaration(const QStringRef &n, FormalParameterList *f, StatementList *b, TypeAnnotation *typeAnnotation = nullptr): + FunctionExpression(n, f, b, typeAnnotation) { kind = K; } void accept0(Visitor *visitor) override; @@ -2810,44 +2939,6 @@ public: SourceLocation semicolonToken; }; -class QML_PARSER_EXPORT UiQualifiedId: public Node -{ -public: - QQMLJS_DECLARE_AST_NODE(UiQualifiedId) - - UiQualifiedId(const QStringRef &name) - : next(this), name(name) - { kind = K; } - - UiQualifiedId(UiQualifiedId *previous, const QStringRef &name) - : name(name) - { - kind = K; - next = previous->next; - previous->next = this; - } - - UiQualifiedId *finish() - { - UiQualifiedId *head = next; - next = nullptr; - return head; - } - - void accept0(Visitor *visitor) override; - - SourceLocation firstSourceLocation() const override - { return identifierToken; } - - SourceLocation lastSourceLocation() const override - { return next ? next->lastSourceLocation() : identifierToken; } - -// attributes - UiQualifiedId *next; - QStringRef name; - SourceLocation identifierToken; -}; - class QML_PARSER_EXPORT UiImport: public Node { public: diff --git a/src/qml/parser/qqmljsastfwd_p.h b/src/qml/parser/qqmljsastfwd_p.h index 6fe108e425..05226fd043 100644 --- a/src/qml/parser/qqmljsastfwd_p.h +++ b/src/qml/parser/qqmljsastfwd_p.h @@ -158,6 +158,9 @@ class NestedExpression; class ClassExpression; class ClassDeclaration; class ClassElementList; +class TypeArgumentList; +class Type; +class TypeAnnotation; // ui elements class UiProgram; diff --git a/src/qml/parser/qqmljsastvisitor_p.h b/src/qml/parser/qqmljsastvisitor_p.h index f3732cbba8..7146cd00ac 100644 --- a/src/qml/parser/qqmljsastvisitor_p.h +++ b/src/qml/parser/qqmljsastvisitor_p.h @@ -403,6 +403,15 @@ public: virtual bool visit(DebuggerStatement *) { return true; } virtual void endVisit(DebuggerStatement *) {} + virtual bool visit(Type *) { return true; } + virtual void endVisit(Type *) {} + + virtual bool visit(TypeArgumentList *) { return true; } + virtual void endVisit(TypeArgumentList *) {} + + virtual bool visit(TypeAnnotation *) { return true; } + virtual void endVisit(TypeAnnotation *) {} + virtual void throwRecursionDepthError() = 0; quint16 recursionDepth() const { return m_recursionDepth; } diff --git a/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.errors.txt b/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.errors.txt new file mode 100644 index 0000000000..b316acae30 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.errors.txt @@ -0,0 +1 @@ +5:14:Type annotations are not permitted in variable declarations diff --git a/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.qml b/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.qml new file mode 100644 index 0000000000..655fe4c226 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typeAnnotations.2.qml @@ -0,0 +1,8 @@ +import QtQml 2.0 + +QtObject { + function notYet() { + var x: string = "ko" + return x + } +} diff --git a/tests/auto/qml/qqmllanguage/data/typeAnnotations.errors.txt b/tests/auto/qml/qqmllanguage/data/typeAnnotations.errors.txt new file mode 100644 index 0000000000..3db89a47d5 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typeAnnotations.errors.txt @@ -0,0 +1 @@ +4:5:Type annotations are not supported (yet). diff --git a/tests/auto/qml/qqmllanguage/data/typeAnnotations.qml b/tests/auto/qml/qqmllanguage/data/typeAnnotations.qml new file mode 100644 index 0000000000..d8cc4535eb --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typeAnnotations.qml @@ -0,0 +1,7 @@ +import QtQml 2.0 + +QtObject { + function notYet() : string { + return "ko" + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 8cbb39974e..cc7935f383 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -622,6 +622,9 @@ void tst_qqmllanguage::errors_data() QTest::newRow("fuzzed.2") << "fuzzed.2.qml" << "fuzzed.2.errors.txt" << false; QTest::newRow("bareQmlImport") << "bareQmlImport.qml" << "bareQmlImport.errors.txt" << false; + + QTest::newRow("typeAnnotations") << "typeAnnotations.qml" << "typeAnnotations.errors.txt" << false; + QTest::newRow("typeAnnotations.2") << "typeAnnotations.2.qml" << "typeAnnotations.2.errors.txt" << false; } diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/anonfunctionexpr.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/anonfunctionexpr.js new file mode 100644 index 0000000000..f660edb69e --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/anonfunctionexpr.js @@ -0,0 +1,3 @@ +(function () : string { + return "ko" +}) diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classmemberparam.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classmemberparam.js new file mode 100644 index 0000000000..ab85d90880 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classmemberparam.js @@ -0,0 +1,6 @@ + +class Foo { + member(param: string) { + return "ko" + } +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classreturnvalue.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classreturnvalue.js new file mode 100644 index 0000000000..a7da9e0ca7 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/classreturnvalue.js @@ -0,0 +1,6 @@ + +class Foo { + member(): string { + return "ko" + } +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/function.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/function.js new file mode 100644 index 0000000000..4d6021e835 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/function.js @@ -0,0 +1,4 @@ + +function x() : string { + return "ok" +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionexpr.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionexpr.js new file mode 100644 index 0000000000..33f2abbb61 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionexpr.js @@ -0,0 +1,3 @@ +(function x() : string { + return "ko" +}) diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionparams.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionparams.js new file mode 100644 index 0000000000..2c23628e3f --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/functionparams.js @@ -0,0 +1,3 @@ + +function test(x: string, y: string) { +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration.js new file mode 100644 index 0000000000..a6cc00e38a --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration.js @@ -0,0 +1,3 @@ + +for (var i: int = 0; i < 100; ++i) { +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration2.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration2.js new file mode 100644 index 0000000000..24d5acce98 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/iteration2.js @@ -0,0 +1,5 @@ + +let y = [1, 2, 3]; + +for (let x: int of y) { +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/qmlnestedfunction.qml b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/qmlnestedfunction.qml new file mode 100644 index 0000000000..f435bf1b25 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/qmlnestedfunction.qml @@ -0,0 +1,9 @@ +import QtQuick 2.12 as MyQuick +MyQuick.Item { + function factory(param: string) : MyQuick.Item { + function nested(foo: string) { + return this + } + return nested() + } +} diff --git a/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/variables.js b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/variables.js new file mode 100644 index 0000000000..bf332ac7a8 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/disallowedtypeannotations/variables.js @@ -0,0 +1,2 @@ + +var x: string = "ko" diff --git a/tests/auto/qml/qqmlparser/data/typeannotations/parametrized.qml b/tests/auto/qml/qqmlparser/data/typeannotations/parametrized.qml new file mode 100644 index 0000000000..d80b5e3f87 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/typeannotations/parametrized.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 as MyQuick +MyQuick.Item { + function factory() : list<MyQuick.Item> { + } +} diff --git a/tests/auto/qml/qqmlparser/data/typeannotations/qmlfunction.qml b/tests/auto/qml/qqmlparser/data/typeannotations/qmlfunction.qml new file mode 100644 index 0000000000..cd5c1f51e5 --- /dev/null +++ b/tests/auto/qml/qqmlparser/data/typeannotations/qmlfunction.qml @@ -0,0 +1,6 @@ +import QtQuick 2.12 as MyQuick +MyQuick.Item { + function factory(param: string) : MyQuick.Item { + return this + } +} diff --git a/tests/auto/qml/qqmlparser/qqmlparser.pro b/tests/auto/qml/qqmlparser/qqmlparser.pro index 74cb620f06..d8e4b0dd06 100644 --- a/tests/auto/qml/qqmlparser/qqmlparser.pro +++ b/tests/auto/qml/qqmlparser/qqmlparser.pro @@ -7,3 +7,7 @@ SOURCES += tst_qqmlparser.cpp DEFINES += SRCDIR=\\\"$$PWD\\\" cross_compile: DEFINES += QTEST_CROSS_COMPILED + +TESTDATA = data/* + +include (../../shared/util.pri) diff --git a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp index 5f58df75d4..9d8818d01e 100644 --- a/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp +++ b/tests/auto/qml/qqmlparser/tst_qqmlparser.cpp @@ -32,12 +32,14 @@ #include <private/qqmljsastvisitor_p.h> #include <private/qqmljsast_p.h> +#include "../../shared/util.h" + #include <qtest.h> #include <QDir> #include <QDebug> #include <cstdlib> -class tst_qqmlparser : public QObject +class tst_qqmlparser : public QQmlDataTest { Q_OBJECT public: @@ -56,6 +58,10 @@ private slots: void leadingSemicolonInClass(); void templatedReadonlyProperty(); void qmlImportInJSRequiresFullVersion(); + void typeAnnotations_data(); + void typeAnnotations(); + void disallowedTypeAnnotations_data(); + void disallowedTypeAnnotations(); private: QStringList excludedDirs; @@ -88,7 +94,7 @@ public: qDebug() << "first source loc failed: node:" << node->kind << "at" << node->firstSourceLocation().startLine << "/" << node->firstSourceLocation().startColumn << "parent" << parent->kind << "at" << parent->firstSourceLocation().startLine << "/" << parent->firstSourceLocation().startColumn; if (node->lastSourceLocation().end() > parentEnd) - qDebug() << "first source loc failed: node:" << node->kind << "at" << node->lastSourceLocation().startLine << "/" << node->lastSourceLocation().startColumn + qDebug() << "last source loc failed: node:" << node->kind << "at" << node->lastSourceLocation().startLine << "/" << node->lastSourceLocation().startColumn << "parent" << parent->kind << "at" << parent->lastSourceLocation().startLine << "/" << parent->lastSourceLocation().startColumn; QVERIFY(node->firstSourceLocation().begin() >= parentBegin); @@ -114,6 +120,27 @@ public: } }; +struct TypeAnnotationObserver: public AST::Visitor +{ + bool typeAnnotationSeen = false; + + void operator()(AST::Node *node) + { + AST::Node::accept(node, this); + } + + virtual bool visit(AST::TypeAnnotation *) + { + typeAnnotationSeen = true; + return true; + } + + void throwRecursionDepthError() final + { + QFAIL("Maximum statement or expression depth exceeded"); + } +}; + } tst_qqmlparser::tst_qqmlparser() @@ -122,6 +149,7 @@ tst_qqmlparser::tst_qqmlparser() void tst_qqmlparser::initTestCase() { + QQmlDataTest::initTestCase(); // Add directories you want excluded here // These snippets are not expected to run on their own. @@ -334,6 +362,82 @@ void tst_qqmlparser::qmlImportInJSRequiresFullVersion() } } +void tst_qqmlparser::typeAnnotations_data() +{ + QTest::addColumn<QString>("file"); + + QString tests = dataDirectory() + "/typeannotations/"; + + QStringList files; + files << findFiles(QDir(tests)); + + for (const QString &file: qAsConst(files)) + QTest::newRow(qPrintable(file)) << file; +} + +void tst_qqmlparser::typeAnnotations() +{ + using namespace QQmlJS; + + QFETCH(QString, file); + + QString code; + + QFile f(file); + if (f.open(QFile::ReadOnly)) + code = QString::fromUtf8(f.readAll()); + + const bool qmlMode = file.endsWith(QLatin1String(".qml")); + + Engine engine; + Lexer lexer(&engine); + lexer.setCode(code, 1, qmlMode); + Parser parser(&engine); + bool ok = qmlMode ? parser.parse() : parser.parseProgram(); + QVERIFY(ok); + + check::TypeAnnotationObserver observer; + observer(parser.rootNode()); + + QVERIFY(observer.typeAnnotationSeen); +} + +void tst_qqmlparser::disallowedTypeAnnotations_data() +{ + QTest::addColumn<QString>("file"); + + QString tests = dataDirectory() + "/disallowedtypeannotations/"; + + QStringList files; + files << findFiles(QDir(tests)); + + for (const QString &file: qAsConst(files)) + QTest::newRow(qPrintable(file)) << file; +} + +void tst_qqmlparser::disallowedTypeAnnotations() +{ + using namespace QQmlJS; + + QFETCH(QString, file); + + QString code; + + QFile f(file); + if (f.open(QFile::ReadOnly)) + code = QString::fromUtf8(f.readAll()); + + const bool qmlMode = file.endsWith(QLatin1String(".qml")); + + Engine engine; + Lexer lexer(&engine); + lexer.setCode(code, 1, qmlMode); + Parser parser(&engine); + bool ok = qmlMode ? parser.parse() : parser.parseProgram(); + QVERIFY(!ok); + QVERIFY2(parser.errorMessage().startsWith("Type annotations are not permitted "), qPrintable(parser.errorMessage())); +} + QTEST_MAIN(tst_qqmlparser) #include "tst_qqmlparser.moc" |