diff --git a/duchain/builders/typebuilder.h b/duchain/builders/typebuilder.h --- a/duchain/builders/typebuilder.h +++ b/duchain/builders/typebuilder.h @@ -112,6 +112,7 @@ bool m_gotTypeFromDocComment; bool m_gotReturnTypeFromDocComment; + bool m_gotTypeFromTypeHint; KDevelop::AbstractType::Ptr injectParseType(QString type, AstNode* node); KDevelop::AbstractType::Ptr parseType(QString type, AstNode* node); diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -332,6 +332,20 @@ } else if (node->constsSequence) { //class constant TypeBuilderBase::visitClassStatement(node); + } else if (node->propertyType) { + m_gotTypeFromTypeHint = true; + AbstractType::Ptr phpDocTypehint = parseDocComment(node, QStringLiteral("var")); + AbstractType::Ptr type = propertyType(node, phpDocTypehint, editor(), currentContext()); + + injectType(type); + TypeBuilderBase::visitClassStatement(node); + clearLastType(); + m_gotTypeFromTypeHint = false; + + if (m_gotTypeFromDocComment) { + clearLastType(); + m_gotTypeFromDocComment = false; + } } else { //member-variable parseDocComment(node, QStringLiteral("var")); @@ -345,7 +359,7 @@ void TypeBuilder::visitClassVariable(ClassVariableAst *node) { - if (!m_gotTypeFromDocComment) { + if (!m_gotTypeFromDocComment && !m_gotTypeFromTypeHint) { if (node->value) { openAbstractType(getTypeForNode(node->value)); } else { diff --git a/duchain/builders/usebuilder.h b/duchain/builders/usebuilder.h --- a/duchain/builders/usebuilder.h +++ b/duchain/builders/usebuilder.h @@ -69,6 +69,7 @@ void visitUseStatement(UseStatementAst* node) override; void visitUseNamespace(UseNamespaceAst* node) override; void openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const KDevelop::RangeInRevision& range) override; + void visitPropertyType(PropertyTypeAst* node) override; void visitReturnType(ReturnTypeAst* node) override; private: diff --git a/duchain/builders/usebuilder.cpp b/duchain/builders/usebuilder.cpp --- a/duchain/builders/usebuilder.cpp +++ b/duchain/builders/usebuilder.cpp @@ -316,6 +316,12 @@ } } +void UseBuilder::visitPropertyType(PropertyTypeAst* node) { + if (node->typehint && isClassTypehint(node->typehint, m_editor)) { + buildNamespaceUses(node->typehint->genericType); + } +} + void UseBuilder::visitReturnType(ReturnTypeAst* node) { if (node->typehint && isClassTypehint(node->typehint, m_editor)) { buildNamespaceUses(node->typehint->genericType); diff --git a/duchain/helper.h b/duchain/helper.h --- a/duchain/helper.h +++ b/duchain/helper.h @@ -41,6 +41,8 @@ struct ParameterAst; struct GenericTypeHintAst; struct ReturnTypeAst; +struct ClassStatementAst; +struct PropertyTypeHintAst; class EditorIntegrator; enum DeclarationType { @@ -58,8 +60,12 @@ KDEVPHPDUCHAIN_EXPORT bool isMatch(KDevelop::Declaration* declaration, DeclarationType declarationType); +KDEVPHPDUCHAIN_EXPORT bool isGenericClassTypehint(NamespacedIdentifierAst* genericType, EditorIntegrator *editor); + KDEVPHPDUCHAIN_EXPORT bool isClassTypehint(GenericTypeHintAst* parameterType, EditorIntegrator *editor); +KDEVPHPDUCHAIN_EXPORT bool isClassTypehint(PropertyTypeHintAst* propertyType, EditorIntegrator *editor); + KDEVPHPDUCHAIN_EXPORT KDevelop::DeclarationPointer findDeclarationImportHelper(KDevelop::DUContext* currentContext, const KDevelop::QualifiedIdentifier& id, DeclarationType declarationType); @@ -97,6 +103,7 @@ KDEVPHPDUCHAIN_EXPORT KDevelop::QualifiedIdentifier identifierWithNamespace(const KDevelop::QualifiedIdentifier& base, KDevelop::DUContext* context); KDEVPHPDUCHAIN_EXPORT KDevelop::AbstractType::Ptr parameterType(const ParameterAst* node, KDevelop::AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, KDevelop::DUContext *currentContext); +KDEVPHPDUCHAIN_EXPORT KDevelop::AbstractType::Ptr propertyType(const ClassStatementAst* node, KDevelop::AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, KDevelop::DUContext *currentContext); KDEVPHPDUCHAIN_EXPORT KDevelop::AbstractType::Ptr returnType(const ReturnTypeAst* node, KDevelop::AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, KDevelop::DUContext *currentContext); } diff --git a/duchain/helper.cpp b/duchain/helper.cpp --- a/duchain/helper.cpp +++ b/duchain/helper.cpp @@ -86,6 +86,28 @@ return false; } +bool isGenericClassTypehint(NamespacedIdentifierAst* node, EditorIntegrator *editor) +{ + const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); + QString typehint = editor->parseSession()->symbol(it->element); + + if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { + return false; + } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { + return false; + } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { + return false; + } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { + return false; + } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { + return false; + } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { + return false; + } else { + return true; + } +} + bool isClassTypehint(GenericTypeHintAst* genericType, EditorIntegrator *editor) { Q_ASSERT(genericType); @@ -95,25 +117,20 @@ } else if (genericType->arrayType != -1) { return false; } else if (genericType->genericType) { - NamespacedIdentifierAst* node = genericType->genericType; - const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); - QString typehint = editor->parseSession()->symbol(it->element); - - if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { - return false; - } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { - return false; - } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { - return false; - } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { - return false; - } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { - return false; - } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { - return false; - } else { - return true; - } + return isGenericClassTypehint(genericType->genericType, editor); + } else { + return false; + } +} + +bool isClassTypehint(PropertyTypeHintAst* propertyType, EditorIntegrator *editor) +{ + Q_ASSERT(propertyType); + + if (propertyType->arrayType != -1) { + return false; + } else if (propertyType->genericType) { + return isGenericClassTypehint(propertyType->genericType, editor); } else { return false; } @@ -579,6 +596,25 @@ return type; } +AbstractType::Ptr propertyType(const ClassStatementAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) +{ + AbstractType::Ptr type; + if (node->propertyType) { + type = determineTypehint(node->propertyType, editor, currentContext); + } + + if (!type) { + if (phpDocTypehint) { + type = phpDocTypehint; + } else { + type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); + } + } + + Q_ASSERT(type); + return type; +} + AbstractType::Ptr returnType(const ReturnTypeAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) { AbstractType::Ptr type; if (node) { 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,7 @@ void declareClassWithSemiReservedMethod(); void declareClassWithBaseTypeMethod(); void classMemberVar(); + void classMemberVarTypehint(); void classMemberVarAfterUse(); void classMemberVarDocBlockType(); void declareTypehintFunction(); diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -478,6 +478,90 @@ QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } +void TestDUChain::classMemberVarTypehint() +{ + //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 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("parentContext()); + QCOMPARE(top->childContexts().count(), 2); + + DUContext* contextClassA = top->childContexts().at(1); + + QCOMPARE(top->localDeclarations().count(), 2); + Declaration* dec = top->localDeclarations().at(1); + QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); + QCOMPARE(dec->isDefinition(), true); + QCOMPARE(dec->logicalInternalContext(top), contextClassA); + + QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); + QCOMPARE(contextClassA->childContexts().count(), 0); + QCOMPARE(contextClassA->localDeclarations().count(), 4); + + TypePtr ut; + + //$foo + ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("foo")); + QCOMPARE(var->accessPolicy(), Declaration::Public); + QCOMPARE(var->isStatic(), false); + QVERIFY(var->type()); + + ut = var->type(); + QVERIFY(ut); + QCOMPARE(2u, ut->typesSize()); + + QVERIFY(ut->types()[0].type()); + QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeArray); + + QVERIFY(ut->types()[1].type()); + QVERIFY(ut->types()[1].type()->declaration(top)); + QCOMPARE(ut->types()[1].type()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("traversable")); + + //$bar + var = dynamic_cast(contextClassA->localDeclarations().at(1)); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("bar")); + QCOMPARE(var->accessPolicy(), Declaration::Protected); + QCOMPARE(var->isStatic(), false); + StructureType::Ptr type = var->type(); + QVERIFY(type); + QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); + + //$baz + var = dynamic_cast(contextClassA->localDeclarations().at(2)); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("baz")); + QCOMPARE(var->accessPolicy(), Declaration::Private); + QCOMPARE(var->isStatic(), true); + QVERIFY(var->type()); + ut = var->type(); + QVERIFY(ut); + QCOMPARE(2u, ut->typesSize()); + + QVERIFY(ut->types()[0].type()); + QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeString); + + QVERIFY(ut->types()[1].type()); + QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeNull); + + //$boo + var = dynamic_cast(contextClassA->localDeclarations().at(3)); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("boo")); + QCOMPARE(var->accessPolicy(), Declaration::Public); + QCOMPARE(var->isStatic(), false); + QVERIFY(var->type()); + QVERIFY(var->type()->dataType() == IntegralType::TypeInt); +} + void TestDUChain::classMemberVarAfterUse() { // 0 1 2 3 4 5 6 7 @@ -3675,7 +3759,7 @@ // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("() << RangeInRevision(0, 47, 0, 48)); } +void TestUses::propertyType() { + // 0 1 2 3 4 + // 01234567890123456789012345678901234567890123456 + QByteArray method("localDeclarations().at(0); + QCOMPARE(a->identifier().toString(), QString("a")); + compareUses(a, QList() << RangeInRevision(0, 20, 0, 21)); +} + } diff --git a/parser/php.g b/parser/php.g --- a/parser/php.g +++ b/parser/php.g @@ -1031,15 +1031,25 @@ VAR variable=classVariableDeclaration SEMICOLON | modifiers=optionalModifiers - ( variable=classVariableDeclaration SEMICOLON + ( (propertyType=propertyType | 0) variable=classVariableDeclaration SEMICOLON | FUNCTION (BIT_AND | 0) methodName=semiReservedIdentifier LPAREN parameters=parameterList RPAREN ( COLON returnType=returnType | 0) methodBody=methodBody | CONST #consts=classConstantDeclaration @ COMMA SEMICOLON ) | USE #traits=namespacedIdentifier @ COMMA (imports=traitAliasDeclaration|SEMICOLON) -> classStatement ;; + (isNullable=QUESTION | 0) typehint=propertyTypeHint +-> propertyType ;; + + ( genericType=namespacedIdentifier + | arrayType=ARRAY ) + [: (*yynode)->callableType = -1; :] +-> propertyTypeHint[ + member variable callableType: int; +] ;; + LBRACE #statements=traitAliasStatement @ (SEMICOLON [: if (yytoken == Token_RBRACE) { break; } :]) RBRACE -> traitAliasDeclaration ;;