Commit 54964af0 authored by Heinz Wiesinger's avatar Heinz Wiesinger

Add support for PHP 7.4's typed properties

Summary:
https://wiki.php.net/rfc/typed_properties_v2

Subscribers: kdevelop-devel

Tags: #kdevelop

Differential Revision: https://phabricator.kde.org/D26254
parent 2cc3eb7f
......@@ -332,6 +332,20 @@ void TypeBuilder::visitClassStatement(ClassStatementAst *node)
} 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::visitClassStatement(ClassStatementAst *node)
void TypeBuilder::visitClassVariable(ClassVariableAst *node)
{
if (!m_gotTypeFromDocComment) {
if (!m_gotTypeFromDocComment && !m_gotTypeFromTypeHint) {
if (node->value) {
openAbstractType(getTypeForNode(node->value));
} else {
......
......@@ -112,6 +112,7 @@ private:
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);
......
......@@ -316,6 +316,12 @@ void UseBuilder::visitNodeWithExprVisitor(AstNode* node)
}
}
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);
......
......@@ -69,6 +69,7 @@ protected:
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:
......
......@@ -86,6 +86,28 @@ bool isMatch(Declaration* declaration, DeclarationType declarationType)
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 @@ bool isClassTypehint(GenericTypeHintAst* genericType, EditorIntegrator *editor)
} 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 @@ AbstractType::Ptr parameterType(const ParameterAst* node, AbstractType::Ptr phpD
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) {
......
......@@ -41,6 +41,8 @@ struct NamespacedIdentifierAst;
struct ParameterAst;
struct GenericTypeHintAst;
struct ReturnTypeAst;
struct ClassStatementAst;
struct PropertyTypeHintAst;
class EditorIntegrator;
enum DeclarationType {
......@@ -58,8 +60,12 @@ enum DeclarationScope {
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 identifierForNamespace(Names
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);
}
......
......@@ -478,6 +478,90 @@ void TestDUChain::classMemberVar()
QVERIFY(var->type<IntegralType>()->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("<? interface Traversable { } class A { public iterable $foo; protected A $bar; private static ?string $baz; public int $boo = 1; }");
TopDUContext* top = parse(method, DumpNone);
DUChainReleaser releaseTop(top);
DUChainWriteLocker lock(DUChain::lock());
QVERIFY(!top->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<UnsureType> ut;
//$foo
ClassMemberDeclaration* var = dynamic_cast<ClassMemberDeclaration*>(contextClassA->localDeclarations().first());
QVERIFY(var);
QCOMPARE(var->identifier(), Identifier("foo"));
QCOMPARE(var->accessPolicy(), Declaration::Public);
QCOMPARE(var->isStatic(), false);
QVERIFY(var->type<UnsureType>());
ut = var->type<UnsureType>();
QVERIFY(ut);
QCOMPARE(2u, ut->typesSize());
QVERIFY(ut->types()[0].type<IntegralType>());
QVERIFY(ut->types()[0].type<IntegralType>()->dataType() == IntegralType::TypeArray);
QVERIFY(ut->types()[1].type<StructureType>());
QVERIFY(ut->types()[1].type<StructureType>()->declaration(top));
QCOMPARE(ut->types()[1].type<StructureType>()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("traversable"));
//$bar
var = dynamic_cast<ClassMemberDeclaration*>(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<StructureType>();
QVERIFY(type);
QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a"));
//$baz
var = dynamic_cast<ClassMemberDeclaration*>(contextClassA->localDeclarations().at(2));
QVERIFY(var);
QCOMPARE(var->identifier(), Identifier("baz"));
QCOMPARE(var->accessPolicy(), Declaration::Private);
QCOMPARE(var->isStatic(), true);
QVERIFY(var->type<UnsureType>());
ut = var->type<UnsureType>();
QVERIFY(ut);
QCOMPARE(2u, ut->typesSize());
QVERIFY(ut->types()[0].type<IntegralType>());
QVERIFY(ut->types()[0].type<IntegralType>()->dataType() == IntegralType::TypeString);
QVERIFY(ut->types()[1].type<IntegralType>());
QVERIFY(ut->types()[1].type<IntegralType>()->dataType() == IntegralType::TypeNull);
//$boo
var = dynamic_cast<ClassMemberDeclaration*>(contextClassA->localDeclarations().at(3));
QVERIFY(var);
QCOMPARE(var->identifier(), Identifier("boo"));
QCOMPARE(var->accessPolicy(), Declaration::Public);
QCOMPARE(var->isStatic(), false);
QVERIFY(var->type<IntegralType>());
QVERIFY(var->type<IntegralType>()->dataType() == IntegralType::TypeInt);
}
void TestDUChain::classMemberVarAfterUse()
{
// 0 1 2 3 4 5 6 7
......@@ -3675,7 +3759,7 @@ void TestDUChain::varStatic()
// 0 1 2 3 4 5 6 7
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789
TopDUContext* top = parse("<?php\n"
"class c { static a = 1; static function foo() {} }\n"
"class c { const a = 1; static function foo() {} }\n"
"$o = 'c';\n"
"$o::a;\n"
"$o::foo();\n"
......
......@@ -41,6 +41,7 @@ private slots:
void declareClassWithSemiReservedMethod();
void declareClassWithBaseTypeMethod();
void classMemberVar();
void classMemberVarTypehint();
void classMemberVarAfterUse();
void classMemberVarDocBlockType();
void declareTypehintFunction();
......
......@@ -1787,5 +1787,18 @@ void TestUses::defaultValue() {
compareUses(c, QList<RangeInRevision>() << RangeInRevision(0, 47, 0, 48));
}
void TestUses::propertyType() {
// 0 1 2 3 4
// 01234567890123456789012345678901234567890123456
QByteArray method("<? class A { public A $foo; }");
TopDUContext *top = parse(method, DumpNone);
DUChainReleaser releaseTop(top);
DUChainWriteLocker lock(DUChain::lock());
Declaration *a = top->localDeclarations().at(0);
QCOMPARE(a->identifier().toString(), QString("a"));
compareUses(a, QList<RangeInRevision>() << RangeInRevision(0, 20, 0, 21));
}
}
......@@ -101,6 +101,7 @@ private slots:
void returnTypeClassFunction();
void returnTypeFunction();
void defaultValue();
void propertyType();
};
}
......
......@@ -1031,7 +1031,7 @@ try/recover(#classStatements=classStatement)*
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
......@@ -1040,6 +1040,16 @@ try/recover(#classStatements=classStatement)*
| 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 ;;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment