diff --git a/completion/context.cpp b/completion/context.cpp --- a/completion/context.cpp +++ b/completion/context.cpp @@ -598,6 +598,7 @@ case Parser::Token_ARRAY: case Parser::Token_AS: case Parser::Token_BACKTICK: + case Parser::Token_BOOL: case Parser::Token_BREAK: case Parser::Token_CALLABLE: case Parser::Token_CASE: @@ -627,6 +628,7 @@ case Parser::Token_EVAL: case Parser::Token_FILE: case Parser::Token_FINALLY: + case Parser::Token_FLOAT: case Parser::Token_FOR: case Parser::Token_FOREACH: case Parser::Token_FUNCTION: @@ -638,9 +640,11 @@ case Parser::Token_INCLUDE_ONCE: case Parser::Token_INLINE_HTML: case Parser::Token_INSTEADOF: + case Parser::Token_INT: case Parser::Token_INTERFACE: case Parser::Token_INVALID: case Parser::Token_ISSET: + case Parser::Token_ITERABLE: case Parser::Token_LINE: case Parser::Token_LIST: case Parser::Token_LNUMBER: @@ -651,6 +655,7 @@ case Parser::Token_REQUIRE_ONCE: case Parser::Token_RBRACKET: case Parser::Token_RPAREN: + case Parser::Token_STRING_TYPE: case Parser::Token_STRING_VARNAME: case Parser::Token_SWITCH: case Parser::Token_TRAIT: diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -358,30 +358,50 @@ void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr type; - if (node->isVariadic != -1) { - if (node->parameterType) { + if (node->parameterType) { + if (node->parameterType->objectType) { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, - identifierForNamespace(node->parameterType, editor())); + identifierForNamespace(node->parameterType->objectType, editor())); if (decl) { - IndexedContainer *container = new IndexedContainer(); - const IndexedString *containerType = new IndexedString("array"); - container->addEntry(decl->abstractType()); - container->setPrettyName(*containerType); - type = AbstractType::Ptr(container); + type = decl->abstractType(); } - } else { + } else if (node->parameterType->arrayType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); + } else if (node->parameterType->boolType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)); + } else if (node->parameterType->floatType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeFloat)); + } else if (node->parameterType->intType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)); + } else if (node->parameterType->stringType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeString)); + } else if (node->parameterType->iterableType != -1) { + DeclarationPointer traversableDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("traversable")); + + if (traversableDecl) { + UnsureType::Ptr unsure(new UnsureType()); + AbstractType::Ptr arrayType = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); + unsure->addType(arrayType->indexed()); + unsure->addType(traversableDecl->abstractType()->indexed()); + + type = AbstractType::Ptr(unsure); + } } - } else if (node->parameterType) { - //don't use openTypeFromName as it uses cursor for findDeclarations - DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, - identifierForNamespace(node->parameterType, editor())); - if (decl) { - type = decl->abstractType(); + + if (type && node->parameterType->isNullable != -1) { + AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); + if (type.cast()) { + UnsureType::Ptr unsure = type.cast(); + unsure->addType(nullType->indexed()); + } else { + UnsureType::Ptr unsure(new UnsureType()); + unsure->addType(type->indexed()); + unsure->addType(nullType->indexed()); + + type = AbstractType::Ptr(unsure); + } } - } else if (node->arrayType != -1) { - type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else if (node->defaultValue) { ExpressionVisitor v(editor()); node->defaultValue->ducontext = currentContext(); @@ -403,6 +423,14 @@ type = p.cast(); } + if (node->isVariadic != -1) { + IndexedContainer *container = new IndexedContainer(); + const IndexedString *containerType = new IndexedString("array"); + container->addEntry(type); + container->setPrettyName(*containerType); + type = AbstractType::Ptr(container); + } + openAbstractType(type); TypeBuilderBase::visitParameter(node); closeType(); diff --git a/duchain/builders/usebuilder.cpp b/duchain/builders/usebuilder.cpp --- a/duchain/builders/usebuilder.cpp +++ b/duchain/builders/usebuilder.cpp @@ -64,8 +64,8 @@ void UseBuilder::visitParameter(ParameterAst *node) { - if (node->parameterType) { - buildNamespaceUses(node->parameterType); + if (node->parameterType && node->parameterType->objectType) { + buildNamespaceUses(node->parameterType->objectType); } } diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "duchaindebug.h" @@ -226,16 +227,50 @@ forever { AbstractType::Ptr type; if (it->element->parameterType) { - //don't use openTypeFromName as it uses cursor for findDeclarations - DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, - identifierForNamespace(it->element->parameterType, m_editor)); - if (decl) { - type = decl->abstractType(); + auto parameterType = it->element->parameterType; + if (parameterType->objectType) { + //don't use openTypeFromName as it uses cursor for findDeclarations + DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, + identifierForNamespace(parameterType->objectType, m_editor)); + if (decl) { + type = decl->abstractType(); + } + } else if (parameterType->arrayType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); + } else if (parameterType->boolType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)); + } else if (parameterType->floatType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeFloat)); + } else if (parameterType->intType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)); + } else if (parameterType->stringType != -1) { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeString)); + } else if (parameterType->iterableType != -1) { + DeclarationPointer traversableDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("traversable")); + + if (traversableDecl) { + UnsureType::Ptr unsure(new UnsureType()); + AbstractType::Ptr arrayType = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); + unsure->addType(arrayType->indexed()); + unsure->addType(traversableDecl->abstractType()->indexed()); + + type = AbstractType::Ptr(unsure); + } + } + + if (type && parameterType->isNullable != -1) { + AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); + if (type.cast()) { + UnsureType::Ptr unsure = type.cast(); + unsure->addType(nullType->indexed()); + } else { + UnsureType::Ptr unsure(new UnsureType()); + unsure->addType(type->indexed()); + unsure->addType(nullType->indexed()); + + type = AbstractType::Ptr(unsure); + } } - } else if (it->element->arrayType != -1) { - type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); - } else if (it->element->callableType != -1) { - type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } else if (it->element->defaultValue) { ExpressionVisitor v(m_editor); it->element->defaultValue->ducontext = m_currentContext; diff --git a/duchain/tests/duchain.h b/duchain/tests/duchain.h --- a/duchain/tests/duchain.h +++ b/duchain/tests/duchain.h @@ -41,6 +41,15 @@ void declareTypehintVariadicFunction(); void declareTypehintArrayFunction(); void declareTypehintCallableFunction(); + void declareTypehintIterableFunction(); + void declareTypehintBoolFunction(); + void declareTypehintFloatFunction(); + void declareTypehintIntFunction(); + void declareTypehintStringFunction(); + void declareNullableTypehintArrayFunction(); + void declareNullableTypehintCallableFunction(); + void declareTypehintNullableIterableFunction(); + void declareTypehintWithPhpdocFunction(); void returnTypeClass(); void declarationReturnType(); void declarationReturnTypeInRecursingFunction(); diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -623,12 +623,17 @@ FunctionType::Ptr fun = top->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); + AbstractType::Ptr arg = fun->arguments().first(); + QVERIFY(arg); + QVERIFY(arg.cast()); + QCOMPARE(arg.cast()->typesCount(), 1); + QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); + + AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); + QVERIFY(typehint); + QVERIFY(IntegralType::Ptr::dynamicCast(typehint)); + QVERIFY(IntegralType::Ptr::dynamicCast(typehint)->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintVariadicFunction() @@ -701,6 +706,224 @@ QVERIFY(type->dataType() == IntegralType::TypeMixed); } +void TestDUChain::declareTypehintIterableFunction() +{ + //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves + // 0 1 2 3 + // 0123456789012345678901234567890123 + QByteArray method("localDeclarations().count(), 2); + FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); + QVERIFY(fun); + QCOMPARE(fun->arguments().count(), 1); + + UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); + QVERIFY(argType); + QCOMPARE(argType->typesSize(), 2u); + QVERIFY(argType->types()[0].abstractType().cast()); + QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); + QVERIFY(argType->types()[1].abstractType().cast()); + QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); +} + +void TestDUChain::declareTypehintBoolFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeBoolean); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeBoolean); +} + +void TestDUChain::declareTypehintFloatFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeFloat); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeFloat); +} + +void TestDUChain::declareTypehintIntFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeInt); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeInt); +} + +void TestDUChain::declareTypehintStringFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeString); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeString); +} + +void TestDUChain::declareNullableTypehintArrayFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + QByteArray method("localDeclarations().first()->type(); + QVERIFY(fun); + QCOMPARE(fun->arguments().count(), 1); + + UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); + QVERIFY(argType); + QCOMPARE(argType->typesSize(), 2u); + QVERIFY(argType->types()[0].abstractType().cast()); + QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); + QVERIFY(argType->types()[1].abstractType().cast()); + QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); + + UnsureType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QCOMPARE(type->typesSize(), 2u); + QVERIFY(type->types()[0].abstractType().cast()); + QVERIFY(type->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); + QVERIFY(type->types()[1].abstractType().cast()); + QVERIFY(type->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); +} + +void TestDUChain::declareTypehintWithPhpdocFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeInt); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeInt); +} + +void TestDUChain::declareNullableTypehintCallableFunction() +{ + // 0 1 2 3 + // 0123456789012345678901234567890123 + 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::TypeMixed); + + IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); + QVERIFY(type); + QVERIFY(type->dataType() == IntegralType::TypeMixed); +} + +void TestDUChain::declareTypehintNullableIterableFunction() +{ + //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves + // 0 1 2 3 + // 0123456789012345678901234567890123 + QByteArray method("localDeclarations().count(), 2); + FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); + QVERIFY(fun); + QCOMPARE(fun->arguments().count(), 1); + + UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); + QVERIFY(argType); + QCOMPARE(argType->typesSize(), 3u); + QVERIFY(argType->types()[0].abstractType().cast()); + QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); + QVERIFY(argType->types()[1].abstractType().cast()); + QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); + QVERIFY(argType->types()[2].abstractType().cast()); + QVERIFY(argType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeNull); +} + void TestDUChain::classImplementsInterface() { // 0 1 2 3 4 5 6 7 diff --git a/parser/php.g b/parser/php.g --- a/parser/php.g +++ b/parser/php.g @@ -236,7 +236,8 @@ FILE ("__FILE__"), COMMENT ("comment"), DOC_COMMENT ("doc comment"), PAAMAYIM_NEKUDOTAYIM ("::"), INCLUDE ("include"), INCLUDE_ONCE ("include_once"), EVAL ("eval"), REQUIRE ("require"), REQUIRE_ONCE ("require_once"), NAMESPACE ("namespace"), NAMESPACE_C("__NAMESPACE__"), USE("use"), - GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable") ;; + GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable"), + ITERABLE ("iterable"), BOOL ("bool"), FLOAT ("float"), INT ("int"), STRING_TYPE ("string") ;; -- casts: %token INT_CAST ("int cast"), DOUBLE_CAST ("double cast"), STRING_CAST ("string cast"), @@ -879,10 +880,22 @@ (#parameters=parameter @ COMMA) | 0 -> parameterList ;; -(parameterType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE | 0) (isRef=BIT_AND | 0) +(parameterType=parameterType | 0) (isRef=BIT_AND | 0) (isVariadic=ELLIPSIS | 0) variable=variableIdentifier (ASSIGN defaultValue=staticScalar | 0) -> parameter ;; + (isNullable=QUESTION | 0) ( + objectType=namespacedIdentifier + | arrayType=ARRAY + | callableType=CALLABLE + | iterableType=ITERABLE + | boolType=BOOL + | floatType=FLOAT + | intType=INT + | stringType=STRING_TYPE + ) +-> parameterType ;; + value=commonScalar | constantOrClassConst=constantOrClassConst | PLUS plusValue=staticScalar diff --git a/parser/phplexer.cpp b/parser/phplexer.cpp --- a/parser/phplexer.cpp +++ b/parser/phplexer.cpp @@ -786,6 +786,16 @@ token = Parser::Token_NAMESPACE_C; } else if (name.compare(QLatin1String("callable"), Qt::CaseInsensitive) == 0) { token = Parser::Token_CALLABLE; + } else if (name.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { + token = Parser::Token_ITERABLE; + } else if (name.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { + token = Parser::Token_BOOL; + } else if (name.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { + token = Parser::Token_FLOAT; + } else if (name.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { + token = Parser::Token_INT; + } else if (name.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { + token = Parser::Token_STRING_TYPE; } else { token = Parser::Token_STRING; }