diff --git a/completion/context.cpp b/completion/context.cpp --- a/completion/context.cpp +++ b/completion/context.cpp @@ -504,6 +504,7 @@ case Parser::Token_DOUBLE_CAST: case Parser::Token_DOUBLE_QUOTE: case Parser::Token_ECHO: + case Parser::Token_ELLIPSIS: case Parser::Token_ENCAPSED_AND_WHITESPACE: case Parser::Token_EXIT: case Parser::Token_INC: diff --git a/duchain/builders/declarationbuilder.h b/duchain/builders/declarationbuilder.h --- a/duchain/builders/declarationbuilder.h +++ b/duchain/builders/declarationbuilder.h @@ -70,6 +70,7 @@ virtual void importTraitMethods(ClassStatementAst *node); void visitClassExtends(ClassExtendsAst *node) override; void visitClassImplements(ClassImplementsAst *node) override; + void visitParameterList(ParameterListAst *node) override; void visitParameter(ParameterAst *node) override; void visitFunctionDeclarationStatement(FunctionDeclarationStatementAst *node) override; void visitClassVariable(ClassVariableAst *node) override; @@ -142,6 +143,8 @@ int m_functionCallParameterPos; /// Type of the current function, will only be set inside function calls. KDevelop::FunctionType::Ptr m_currentFunctionType; + /// The AstNode of the previous function declaration argument + ParameterAst *m_functionDeclarationPreviousArgument; unsigned int m_currentModifers; QString m_lastTopStatementComment; diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -762,14 +762,24 @@ } } +void DeclarationBuilder::visitParameterList(ParameterListAst* node) +{ + PushValue push(m_functionDeclarationPreviousArgument, 0); + + DeclarationBuilderBase::visitParameterList(node); +} + void DeclarationBuilder::visitParameter(ParameterAst *node) { AbstractFunctionDeclaration* funDec = dynamic_cast(currentDeclaration()); Q_ASSERT(funDec); + if (node->defaultValue) { QString symbol = m_editor->parseSession()->symbol(node->defaultValue); funDec->addDefaultParameter(IndexedString(symbol)); - if ( node->parameterType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0 ) { + if (node->isVariadic != -1) { + reportError(i18n("Variadic parameter cannot have a default value"), node->defaultValue); + } else if ( node->parameterType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0 ) { reportError(i18n("Default value for parameters with a class type hint can only be NULL."), node->defaultValue); } } else if ( !node->defaultValue && funDec->defaultParametersSize() ) { @@ -784,7 +794,14 @@ } DeclarationBuilderBase::visitParameter(node); + + if (m_functionDeclarationPreviousArgument && m_functionDeclarationPreviousArgument->isVariadic != -1) { + reportError(i18n("Only the last parameter can be variadic."), m_functionDeclarationPreviousArgument); + } + closeDeclaration(); + + m_functionDeclarationPreviousArgument = node; } void DeclarationBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -357,7 +357,9 @@ void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr type; - if (node->parameterType) { + if (node->isVariadic != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); + } else if (node->parameterType) { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->parameterType, editor())); diff --git a/duchain/tests/duchain.h b/duchain/tests/duchain.h --- a/duchain/tests/duchain.h +++ b/duchain/tests/duchain.h @@ -37,6 +37,7 @@ void declareClass(); void classMemberVar(); void declareTypehintFunction(); + void declareVariadicFunction(); void declareTypehintArrayFunction(); void declareTypehintCallableFunction(); void returnTypeClass(); diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -608,6 +608,28 @@ QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); } +void TestDUChain::declareVariadicFunction() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("localDeclarations().first()->type(); + QVERIFY(fun); + QCOMPARE(fun->arguments().count(), 1); + QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); + QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeArray); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeArray); +} + void TestDUChain::declareTypehintArrayFunction() { // 0 1 2 3 4 5 6 7 diff --git a/duchain/tests/expressionparser.h b/duchain/tests/expressionparser.h --- a/duchain/tests/expressionparser.h +++ b/duchain/tests/expressionparser.h @@ -62,6 +62,8 @@ void classMemberOnInstantiation(); void classNameConstant_data(); void classNameConstant(); + void invalidVariadicFunction_data(); + void invalidVariadicFunction(); }; } diff --git a/duchain/tests/expressionparser.cpp b/duchain/tests/expressionparser.cpp --- a/duchain/tests/expressionparser.cpp +++ b/duchain/tests/expressionparser.cpp @@ -661,5 +661,25 @@ QCOMPARE(IntegralType::Ptr::staticCast(res.type())->dataType(), static_cast(IntegralType::TypeString)); } +void TestExpressionParser::invalidVariadicFunction_data() +{ + QTest::addColumn("code"); + + QTest::newRow("defaultValue") << "problems().isEmpty()); +} + } diff --git a/parser/php.g b/parser/php.g --- a/parser/php.g +++ b/parser/php.g @@ -266,7 +266,7 @@ INC ("++"), DEC ("--"), BANG ("!"), QUESTION ("?"), COLON (":"), BIT_AND ("&"), BIT_OR("|"), BIT_XOR ("^"), SL ("<<"), SR (">>"), MUL("*"), DIV("/"), MOD ("%"), - TILDE ("~"), DOLLAR ("$"), EXP ("**"), + TILDE ("~"), DOLLAR ("$"), EXP ("**"), ELLIPSIS ("..."), LOGICAL_OR ("logical or"), LOGICAL_AND ("logical and"), LOGICAL_XOR ("logical xor") ;; -- literals and identifiers: @@ -880,7 +880,7 @@ -> parameterList ;; (parameterType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE | 0) (isRef=BIT_AND | 0) - variable=variableIdentifier (ASSIGN defaultValue=staticScalar | 0) + (isVariadic=ELLIPSIS | 0) variable=variableIdentifier (ASSIGN defaultValue=staticScalar | 0) -> parameter ;; value=commonScalar diff --git a/parser/phplexer.cpp b/parser/phplexer.cpp --- a/parser/phplexer.cpp +++ b/parser/phplexer.cpp @@ -544,6 +544,9 @@ if ((it + 1)->unicode() == '=') { m_curpos++; token = Parser::Token_CONCAT_ASSIGN; + } else if ((it + 1)->unicode() == '.' && (it + 2)->unicode() == '.') { + m_curpos = m_curpos + 2; + token = Parser::Token_ELLIPSIS; } else { token = Parser::Token_CONCAT; } diff --git a/parser/test/lexertest.h b/parser/test/lexertest.h --- a/parser/test/lexertest.h +++ b/parser/test/lexertest.h @@ -68,6 +68,7 @@ void testTypeHintsOnFunction(); void testExponentiation(); void testExceptionFinally(); + void testEllipsis(); protected: TokenStream* tokenize(const QString& unit, bool debug = false, int initialState = Lexer::HtmlState); diff --git a/parser/test/lexertest.cpp b/parser/test/lexertest.cpp --- a/parser/test/lexertest.cpp +++ b/parser/test/lexertest.cpp @@ -510,6 +510,24 @@ COMPARE_TOKEN(ts, 18, Parser::Token_RBRACE, 1, 26, 1, 26); } +void LexerTest::testEllipsis() +{ + QScopedPointer ts(tokenize(QStringLiteral("size(), 11); + + COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5); + COMPARE_TOKEN(ts, 1, Parser::Token_FUNCTION, 1, 0, 1, 7); + COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 8, 1, 8); + COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 9, 1, 11); + COMPARE_TOKEN(ts, 4, Parser::Token_LPAREN, 1, 12, 1, 12); + COMPARE_TOKEN(ts, 5, Parser::Token_ELLIPSIS, 1, 13, 1, 15); + COMPARE_TOKEN(ts, 6, Parser::Token_VARIABLE, 1, 16, 1, 20); + COMPARE_TOKEN(ts, 7, Parser::Token_RPAREN, 1, 21, 1, 21); + COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 22, 1, 22); + COMPARE_TOKEN(ts, 9, Parser::Token_LBRACE, 1, 23, 1, 23); + COMPARE_TOKEN(ts, 10, Parser::Token_RBRACE, 1, 24, 1, 24); +} + TokenStream* LexerTest::tokenize(const QString& unit, bool debug, int initialState) { TokenStream* tokenStream = new TokenStream;