diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -598,31 +598,6 @@ DUChainWriteLocker lock(DUChain::lock()); - // check for redeclaration of private or protected stuff - { - // only interesting context might be the class context when we are inside a method - DUContext *ctx = currentContext()->parentContext(); - foreach ( Declaration* dec, parentCtx->findDeclarations(identifier) ) { - if ( ClassMemberDeclaration* cdec = dynamic_cast(dec) ) { - if ( cdec->accessPolicy() == Declaration::Private && cdec->context() != ctx ) { - reportError(i18n("Cannot redeclare private property %1 from this context.", - cdec->toString()), node); - return; - } else if ( cdec->accessPolicy() == Declaration::Protected - && cdec->context() != ctx - && ( !ctx || !ctx->imports(cdec->context()) ) ) { - reportError(i18n("Cannot redeclare protected property %1 from this context.", - cdec->toString()), node); - return; - } - if ( cdec->abstractType()->indexed() == type->indexed() ) { - encounter(dec); - return; - } - } - } - } - // this member should be public and non-static m_currentModifers = ModifierPublic; injectContext(parentCtx); @@ -1208,7 +1183,100 @@ { DeclarationBuilderBase::visitAssignmentExpressionEqual(node); - if ( !m_findVariable.identifier.isEmpty() && currentAbstractType()) { + bool alreadyDeclared = false; + + // check for already declared class members + if (!m_findVariable.identifier.isEmpty() && !m_findVariable.parentIdentifier.isEmpty()) { + DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); + if (ctx) { + DUChainReadLocker lock(DUChain::lock()); + ifDebug(qCDebug(DUCHAIN) << "checking if already declared: " << m_findVariable.identifier.toString();) + QList decs; + foreach ( Declaration* dec, currentContext()->findDeclarations(m_findVariable.identifier) ) { + if ( dec->kind() == Declaration::Kind::Instance) { + ClassMemberDeclaration *classDec = dynamic_cast(dec); + + if (dec->range() == editorFindRange(m_findVariable.node)) { + // Don't reuse previous declarations as the type might have changed. + continue; + } + + if (classDec && classDec->accessPolicy() == Declaration::Private) { + if (currentContext()->parentContext() == dec->context()) { + decs << dec; + ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) + } + } else { + decs << dec; + ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) + } + } + } + lock.unlock(); + + if (!decs.isEmpty()) { + Declaration * dec = decs.last(); + + if (dec) { + encounter(dec); + alreadyDeclared = true; + IntegralType::Ptr type = dec->type(); + + if (type && type->dataType() == IntegralType::TypeNull) { + DUChainWriteLocker wlock(DUChain::lock()); + dec->setAbstractType(currentAbstractType()); + wlock.unlock(); + } + } + } + + lock.lock(); + if (decs.isEmpty() && (!currentContext()->parentContext() || !currentContext()->parentContext()->imports(ctx))) { + ifDebug(qCDebug(DUCHAIN) << "nothing found in current context, look in class context of parent";) + QList decs; + foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { + if ( dec->kind() == Declaration::Kind::Instance) { + if (dec->range() == editorFindRange(m_findVariable.node)) { + // Don't reuse previous declarations as the type might have changed. + continue; + } + + ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) + decs << dec; + } + } + lock.unlock(); + + if (!decs.isEmpty()) { + Declaration * dec = decs.last(); + + if (dec) { + alreadyDeclared = true; + IntegralType::Ptr type = dec->type(); + + // check for redeclaration of private or protected stuff + DUContext *parentCtx = currentContext()->parentContext(); + ClassMemberDeclaration *classDec = dynamic_cast(dec); + + if (classDec && classDec->accessPolicy() == Declaration::Private && classDec->context() != parentCtx) { + reportError(i18n("Cannot access private property %1", + classDec->toString()), m_findVariable.node); + } else if (classDec && classDec->accessPolicy() == Declaration::Protected && classDec->context() != parentCtx) { + reportError(i18n("Cannot access protected property %1", + classDec->toString()), m_findVariable.node); + } else if (type && type->dataType() == IntegralType::TypeNull) { + DUChainWriteLocker wlock(DUChain::lock()); + dec->setAbstractType(currentAbstractType()); + wlock.unlock(); + } + } + } + } + } + } + + if ( !alreadyDeclared && !m_findVariable.identifier.isEmpty() && currentAbstractType()) { + ifDebug(qCDebug(DUCHAIN) << "not yet declared: " << m_findVariable.identifier.toString();) //create new declaration assignments to not-yet declared variables and class members AbstractType::Ptr type; diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -88,9 +88,16 @@ if (!type.compare(QLatin1String("object"), Qt::CaseInsensitive)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeObject)); } + + QualifiedIdentifier typehint = QualifiedIdentifier(type.toLower().replace(QLatin1Literal("\\"), QLatin1Literal("::"))); + + if (typehint.toString().startsWith(QLatin1Literal("::"))) { + typehint.setExplicitlyGlobal(true); + } + //don't use openTypeFromName as it uses cursor for findDeclarations - DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, - QualifiedIdentifier(type.toLower())); + DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, typehint); + if (decl && decl->abstractType()) { return decl->abstractType(); } @@ -326,7 +333,11 @@ void TypeBuilder::visitClassVariable(ClassVariableAst *node) { if (!m_gotTypeFromDocComment) { - openAbstractType(getTypeForNode(node->value)); + if (node->value) { + openAbstractType(getTypeForNode(node->value)); + } else { + openAbstractType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); + } TypeBuilderBase::visitClassVariable(node); diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -648,8 +648,16 @@ } } else { if ( !dec->isFunctionDeclaration() ) { - decs << dec; - ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) + ClassMemberDeclaration *classDec = dynamic_cast(dec); + if (classDec && classDec->accessPolicy() == Declaration::Private) { + if (declaration == dec->context()->owner()) { + decs << dec; + ifDebug(qCDebug(DUCHAIN) << "found private:" << dec->toString();) + } + } else { + decs << dec; + ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) + } } } } 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,8 @@ void declareClassWithSemiReservedMethod(); void declareClassWithBaseTypeMethod(); void classMemberVar(); + void classMemberVarAfterUse(); + void classMemberVarDocBlockType(); void declareTypehintFunction(); void declareVariadicFunction(); void declareTypehintVariadicFunction(); diff --git a/duchain/tests/duchain.cpp b/duchain/tests/duchain.cpp --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -447,7 +447,7 @@ QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); - QVERIFY(var->type()->dataType() == IntegralType::TypeMixed); + QVERIFY(var->type()->dataType() == IntegralType::TypeNull); //$bar var = dynamic_cast(contextClassA->localDeclarations().at(1)); @@ -478,6 +478,85 @@ QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } +void TestDUChain::classMemberVarAfterUse() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("a = 1; } public $a = 1; }"); + + TopDUContext* top = parse(method, DumpNone); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock(DUChain::lock()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 1); + QVERIFY(top->problems().isEmpty()); + + DUContext* contextClassB = top->childContexts().first(); + + QCOMPARE(top->localDeclarations().count(), 1); + Declaration* dec = top->localDeclarations().first(); + QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("b")); + QCOMPARE(dec->isDefinition(), true); + QCOMPARE(dec->logicalInternalContext(top), contextClassB); + + QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); + QCOMPARE(contextClassB->childContexts().count(), 2); + QCOMPARE(contextClassB->localDeclarations().count(), 2); + + //$foo + ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().at(1)); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("a")); + QCOMPARE(var->accessPolicy(), Declaration::Public); + QCOMPARE(var->isStatic(), false); + QVERIFY(var->type()); + QVERIFY(var->type()->dataType() == IntegralType::TypeInt); + QVERIFY(var->range() == RangeInRevision(0, 54, 0, 56)); +} + +void TestDUChain::classMemberVarDocBlockType() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("parentContext()); + QCOMPARE(top->childContexts().count(), 2); + + DUContext* contextClassA = top->childContexts().at(0)->childContexts().first(); + DUContext* contextClassB = top->childContexts().at(1)->childContexts().first(); + + QCOMPARE(top->localDeclarations().count(), 2); + Declaration* dec = top->childContexts().first()->localDeclarations().first(); + QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("test::a")); + QCOMPARE(dec->isDefinition(), true); + + QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); + QCOMPARE(contextClassA->childContexts().count(), 0); + QCOMPARE(contextClassA->localDeclarations().count(), 0); + + QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); + QCOMPARE(contextClassB->childContexts().count(), 0); + QCOMPARE(contextClassB->localDeclarations().count(), 1); + + //$foo + ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().first()); + QVERIFY(var); + QCOMPARE(var->identifier(), Identifier("foo")); + QCOMPARE(var->accessPolicy(), Declaration::Public); + QCOMPARE(var->isStatic(), false); + StructureType::Ptr type = var->type(); + QVERIFY(type); + QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("test::a")); +} + void TestDUChain::returnTypeGenerator_data() { QTest::addColumn("code"); @@ -2427,7 +2506,7 @@ QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); - QVERIFY(var->type()->dataType() == IntegralType::TypeMixed); + QVERIFY(var->type()->dataType() == IntegralType::TypeNull); } } @@ -2801,7 +2880,7 @@ { // check if x got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("x"))); - // the type of both assignments to $bar->asdf are the same, hence it should only be declared once + // the type of both assignments to $a->x are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); @@ -2819,8 +2898,7 @@ " function test() { $this->asdf = true; $this->asdf = false; }\n" // should only declare bar once as private " private $xyz = 0; function test2() { $this->xyz = 42; }\n" - // should not create any local declarations, and issue an error for trying to - // assign something to a private member variable of a parent class + // should create a local declaration for the private attribute " function test3() { $this->prot = 42;\n$this->priv = 42; }\n" " }"); TopDUContext* top = parse(code, DumpAST); @@ -2849,14 +2927,22 @@ QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } - { // prot and priv + { // priv + QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("priv"))); + QCOMPARE(decs.size(), 1); + ClassMemberDeclaration *dec = dynamic_cast(decs.first()); + QVERIFY(dec); + QVERIFY(dec->accessPolicy() == Declaration::Public); + QVERIFY(!dec->isStatic()); + QVERIFY(dec->type()); + QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); + } + + { // prot QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); - QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("priv")).isEmpty()); } - // only one problem: error trying to assign to a private member of a parent class - QCOMPARE(top->problems().count(), 1); - QCOMPARE(top->problems().first()->finalLocation().start().line(), 4); + QCOMPARE(top->problems().count(), 0); } void TestDUChain::thisRedeclaration() @@ -2980,7 +3066,7 @@ QVERIFY(cmdec->type()); qDebug() << cmdec->type()->dataType() << cmdec->toString(); - QVERIFY(cmdec->type()->dataType() == IntegralType::TypeMixed); + QVERIFY(cmdec->type()->dataType() == IntegralType::TypeNull); } } diff --git a/duchain/tests/uses.h b/duchain/tests/uses.h --- a/duchain/tests/uses.h +++ b/duchain/tests/uses.h @@ -36,6 +36,7 @@ void memberFunctionCall(); void unsureMemberFunctionCall(); void memberVariable(); + void implicitMemberVariable(); void variable(); void varInString(); void variableInNamespace(); diff --git a/duchain/tests/uses.cpp b/duchain/tests/uses.cpp --- a/duchain/tests/uses.cpp +++ b/duchain/tests/uses.cpp @@ -144,6 +144,22 @@ QCOMPARE(var->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberVariable.php"))); } +void TestUses::implicitMemberVariable() +{ + + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("y = 1; $x->y = 2; class A {}"); + TopDUContext* top = parse(method, DumpNone); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock(DUChain::lock()); + Declaration* var = top->childContexts().first()->localDeclarations().first(); + QList ranges; + ranges << RangeInRevision(0, 21, 0, 22) << RangeInRevision(0, 32, 0, 33); + compareUses(var, ranges); + QVERIFY(var->range() == RangeInRevision(0, 21, 0, 22)); +} + void TestUses::variable() { // 0 1 2 3 4 5 6 7