diff --git a/duchain/helpers.h b/duchain/helpers.h --- a/duchain/helpers.h +++ b/duchain/helpers.h @@ -143,10 +143,11 @@ * checks for a class declaration; then returns the constructor * * @param called the declaration to check + * @param isAlias whether the called declaration aliases a class or function definition. * @return the function pointer which was found, or an invalid pointer, and a bool * which is true when it is a constructor **/ - static FuncInfo functionForCalled(Declaration* called); + static FuncInfo functionForCalled(Declaration* called, bool isAlias=true); template static const Decorator* findDecoratorByName(T* inDeclaration, const QString& name) { const int count = inDeclaration->decoratorsSize(); diff --git a/duchain/helpers.cpp b/duchain/helpers.cpp --- a/duchain/helpers.cpp +++ b/duchain/helpers.cpp @@ -157,7 +157,7 @@ })); } -Helper::FuncInfo Helper::functionForCalled(Declaration* called ) +Helper::FuncInfo Helper::functionForCalled(Declaration* called, bool isAlias) { if ( ! called ) { return { nullptr, false }; @@ -165,12 +165,14 @@ else if ( called->isFunctionDeclaration() ) { return { static_cast( called ), false }; } - else { - // not a function -- try looking for a constructor - static const IndexedIdentifier initIdentifier(KDevelop::Identifier("__init__")); - auto attr = accessAttribute( called->abstractType(), initIdentifier, called->topContext()); - return { dynamic_cast(attr), true }; - } +// If we're calling a type object (isAlias == true), look for a constructor. + static const IndexedIdentifier initId(KDevelop::Identifier("__init__")); + +// Otherwise look for a `__call__()` method. + static const IndexedIdentifier callId(KDevelop::Identifier("__call__")); + + auto attr = accessAttribute(called->abstractType(), (isAlias ? initId : callId), called->topContext()); + return { dynamic_cast(attr), isAlias }; } Declaration* Helper::declarationForName(const QualifiedIdentifier& identifier, const RangeInRevision& nodeRange, diff --git a/duchain/usebuilder.h b/duchain/usebuilder.h --- a/duchain/usebuilder.h +++ b/duchain/usebuilder.h @@ -43,7 +43,9 @@ protected: virtual void visitName(NameAst* node); + virtual void visitCall(CallAst* node); virtual void visitAttribute(AttributeAst* node); + virtual void visitSubscript(SubscriptAst* node); private: ParseSession* m_session; inline int& nextUseIndex() @@ -59,6 +61,7 @@ m_errorReportingEnabled = true; }; DUContext* contextAtOrCurrent(const CursorInRevision& pos); + void useHiddenMethod(ExpressionAst* value, IndexedIdentifier method); QVector m_ignoreVariables; }; diff --git a/duchain/usebuilder.cpp b/duchain/usebuilder.cpp --- a/duchain/usebuilder.cpp +++ b/duchain/usebuilder.cpp @@ -60,23 +60,36 @@ return context; } +void UseBuilder::useHiddenMethod(ExpressionAst* value, IndexedIdentifier method) { + DUContext* context = contextAtOrCurrent(editorFindPositionSafe(value)); + ExpressionVisitor v(context); + v.visitNode(value); + RangeInRevision useRange; + // TODO fixme! this does not necessarily use the opening bracket as it should + useRange.start = CursorInRevision(value->endLine, value->endCol + 1); + useRange.end = CursorInRevision(value->endLine, value->endCol + 2); + DUChainReadLocker lock; + auto function = Helper::accessAttribute(v.lastType(), method, context->topContext()); + lock.unlock(); + if ( function && function->isFunctionDeclaration() ) { + UseBuilderBase::newUse(value, useRange, DeclarationPointer(function)); + } +} + void UseBuilder::visitName(NameAst* node) { DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node)); Declaration* declaration = Helper::declarationForName(identifierForNode(node->identifier), editorFindRange(node, node), DUChainPointer(context)); - - static QStringList keywords; - if ( keywords.isEmpty() ) { - keywords << "None" << "True" << "False" << "print"; - } - + + static const QStringList keywords = {"None", "True", "False"}; + Q_ASSERT(node->identifier); RangeInRevision useRange = rangeForNode(node->identifier, true); - + if ( declaration && declaration->range() == useRange ) return; - + if ( ! declaration && ! keywords.contains(node->identifier->value) && m_errorReportingEnabled ) { if ( ! m_ignoreVariables.contains(IndexedString(node->identifier->value)) ) { KDevelop::Problem *p = new KDevelop::Problem(); @@ -91,25 +104,30 @@ } } } - - if ( declaration && declaration->abstractType() && declaration->abstractType()->whichType() == AbstractType::TypeStructure ) { - if ( node->belongsToCall ) { - DUChainReadLocker lock; - auto constructor = Helper::functionForCalled(declaration); - lock.unlock(); - if ( constructor.isConstructor ) { - RangeInRevision constructorRange; - // TODO fixme! this does not necessarily use the opening bracket as it should - constructorRange.start = CursorInRevision(node->endLine, node->endCol + 1); - constructorRange.end = CursorInRevision(node->endLine, node->endCol + 2); - UseBuilderBase::newUse(node, constructorRange, DeclarationPointer(constructor.declaration)); - } - } - } - UseBuilderBase::newUse(node, useRange, DeclarationPointer(declaration)); } +void UseBuilder::visitCall(CallAst* node) +{ + UseBuilderBase::visitCall(node); + DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node)); + ExpressionVisitor v(context); + v.visitNode(node->function); + auto declaration = v.lastDeclaration().data(); + if ( declaration && declaration->abstractType() && + declaration->abstractType()->whichType() == AbstractType::TypeStructure ) { + DUChainReadLocker lock; + // This is either __init__() or __call__(): `a = Foo()` or `b = a()`. + auto function = Helper::functionForCalled(declaration, v.isAlias()); + lock.unlock(); + RangeInRevision openingParenRange; + // TODO fixme! this does not necessarily use the opening bracket as it should + openingParenRange.start = CursorInRevision(node->endLine, node->endCol + 1); + openingParenRange.end = CursorInRevision(node->endLine, node->endCol + 2); + UseBuilderBase::newUse(node, openingParenRange, DeclarationPointer(function.declaration)); + } +} + void UseBuilder::visitAttribute(AttributeAst* node) { qCDebug(KDEV_PYTHON_DUCHAIN) << "VisitAttribute start"; @@ -140,6 +158,20 @@ UseBuilderBase::newUse(node, useRange, declaration); } +void UseBuilder::visitSubscript(SubscriptAst* node) { + UseBuilderBase::visitSubscript(node); + static const IndexedIdentifier getitemIdentifier(KDevelop::Identifier("__getitem__")); + static const IndexedIdentifier setitemIdentifier(KDevelop::Identifier("__setitem__")); + bool isAugTarget = (node->parent->astType == Ast::AugmentedAssignmentAstType && + static_cast(node->parent)->target == node); + // e.g `a[0] += 2` uses both __getitem__ and __setitem__. + if (isAugTarget || node->context == ExpressionAst::Context::Load) { + useHiddenMethod(node->value, getitemIdentifier); + } + if ( node->context == ExpressionAst::Context::Store ) { + useHiddenMethod(node->value, setitemIdentifier); + } +} ParseSession *UseBuilder::parseSession() const { diff --git a/parser/ast.h b/parser/ast.h --- a/parser/ast.h +++ b/parser/ast.h @@ -443,7 +443,6 @@ Invalid = -1 }; ExpressionAst* value; // WARNING this is not set in most cases! - CallAst* belongsToCall; }; class KDEVPYTHONPARSER_EXPORT AwaitAst : public ExpressionAst { diff --git a/parser/ast.cpp b/parser/ast.cpp --- a/parser/ast.cpp +++ b/parser/ast.cpp @@ -153,7 +153,7 @@ } -ExpressionAst::ExpressionAst(Ast* parent, AstType type): Ast(parent, type), value(0), belongsToCall(0) +ExpressionAst::ExpressionAst(Ast* parent, AstType type): Ast(parent, type), value(0) { } diff --git a/parser/generated.h b/parser/generated.h --- a/parser/generated.h +++ b/parser/generated.h @@ -268,7 +268,6 @@ nodeStack.push(v); v->function = static_cast(visitNode(node->v.Call.func)); nodeStack.pop(); nodeStack.push(v); v->arguments = visitNodeList<_expr, ExpressionAst>(node->v.Call.args); nodeStack.pop(); nodeStack.push(v); v->keywords = visitNodeList<_keyword, KeywordAst>(node->v.Call.keywords); nodeStack.pop(); - v->function->belongsToCall = v; result = v; break; } diff --git a/parser/python35.sdef b/parser/python35.sdef --- a/parser/python35.sdef +++ b/parser/python35.sdef @@ -50,7 +50,7 @@ RULE_FOR _expr;KIND GeneratorExp_kind;ACTIONS create|GeneratorExpressionAst set|element->ExpressionAst,elt set|generators=>ComprehensionAst,generators;; RULE_FOR _expr;KIND Yield_kind;ACTIONS create|YieldAst set|value->ExpressionAst,value;; RULE_FOR _expr;KIND Compare_kind;ACTIONS create|CompareAst set|leftmostElement->ExpressionAst,left set|operators#>ComparisonOperatorTypes,ops set|comparands=>ExpressionAst,comparators;; -RULE_FOR _expr;KIND Call_kind;ACTIONS create|CallAst set|function->ExpressionAst,func set|arguments=>ExpressionAst,args set|keywords=>KeywordAst,keywords;CODE v->function->belongsToCall = v;; +RULE_FOR _expr;KIND Call_kind;ACTIONS create|CallAst set|function->ExpressionAst,func set|arguments=>ExpressionAst,args set|keywords=>KeywordAst,keywords;; RULE_FOR _expr;KIND Num_kind;ACTIONS create|NumberAst;CODE v->isInt = PyLong_Check(node->v.Num.n); v->value = PyLong_AsLong(node->v.Num.n);; RULE_FOR _expr;KIND Str_kind;ACTIONS create|StringAst set|value$>s;; RULE_FOR _expr;KIND Bytes_kind;ACTIONS create|BytesAst set|value$>s;;