Commit 72d4493f authored by Erik Verbruggen's avatar Erik Verbruggen
Browse files

Added scope calculation for Objective-C classes.

parent 7938f9de
......@@ -40,6 +40,7 @@ BackwardsScanner::BackwardsScanner(const QTextCursor &cursor, const QString &suf
{
_tokenize.setQtMocRunEnabled(true);
_tokenize.setSkipComments(true);
_tokenize.setObjCEnabled(true);
_text = _block.text().left(cursor.position() - cursor.block().position());
if (! suffix.isEmpty())
......
......@@ -106,6 +106,8 @@ int ExpressionUnderCursor::startOfExpression_helper(BackwardsScanner &tk, int in
return startOfExpression(tk, index - 2);
} else if (tk[index - 2].is(T_DOT_STAR) || tk[index - 2].is(T_ARROW_STAR)) {
return startOfExpression(tk, index - 2);
// } else if (tk[index - 2].is(T_IDENTIFIER) && tk[index - 3].is(T_LBRACKET)) {
// return index - 3;
}
return index - 1;
} else if (tk[index - 1].is(T_RPAREN)) {
......
......@@ -89,8 +89,10 @@ bool LookupContext::maybeValidSymbol(Symbol *symbol,
ResolveMode mode,
const QList<Symbol *> &candidates)
{
if (((mode & ResolveNamespace) && symbol->isNamespace()) ||
((mode & ResolveClass) && symbol->isClass()) ||
if (((mode & ResolveNamespace) && symbol->isNamespace()) ||
((mode & ResolveClass) && symbol->isClass()) ||
((mode & ResolveObjCClass) && symbol->isObjCClass()) ||
((mode & ResolveObjCProtocol) && symbol->isObjCProtocol()) ||
(mode & ResolveSymbol)) {
return ! candidates.contains(symbol);
}
......@@ -527,6 +529,69 @@ void LookupContext::expandObjCMethod(ObjCMethod *method,
expandedScopes->append(method->arguments());
}
void LookupContext::expandObjCClass(ObjCClass *klass,
const QList<Scope *> &visibleScopes,
QList<Scope *> *expandedScopes) const
{
{// expand other @interfaces, @implementations and categories for this class:
const QList<Symbol *> classList = resolveObjCClass(klass->name(), visibleScopes);
foreach (Symbol *otherClass, classList) {
if (otherClass == klass)
continue;
expand(otherClass->asObjCClass()->members(), visibleScopes, expandedScopes);
}
}
// expand definitions in the currect class:
for (unsigned i = 0; i < klass->memberCount(); ++i) {
Symbol *symbol = klass->memberAt(i);
if (Class *nestedClass = symbol->asClass()) {
if (! nestedClass->name()) {
expand(nestedClass->members(), visibleScopes, expandedScopes);
}
} else if (Enum *e = symbol->asEnum()) {
expand(e->members(), visibleScopes, expandedScopes);
}
}
// expand the base class:
if (ObjCBaseClass *baseClass = klass->baseClass()) {
Name *baseClassName = baseClass->name();
const QList<Symbol *> baseClassCandidates = resolveObjCClass(baseClassName,
visibleScopes);
for (int j = 0; j < baseClassCandidates.size(); ++j) {
if (ObjCClass *baseClassSymbol = baseClassCandidates.at(j)->asObjCClass())
expand(baseClassSymbol->members(), visibleScopes, expandedScopes);
}
}
// expand the protocols:
for (unsigned i = 0; i < klass->protocolCount(); ++i) {
Name *protocolName = klass->protocolAt(i)->name();
const QList<Symbol *> protocolCandidates = resolveObjCProtocol(protocolName, visibleScopes);
for (int j = 0; j < protocolCandidates.size(); ++j) {
if (ObjCProtocol *protocolSymbol = protocolCandidates.at(j)->asObjCProtocol())
expandObjCProtocol(protocolSymbol, visibleScopes, expandedScopes);
}
}
}
void LookupContext::expandObjCProtocol(ObjCProtocol *protocol, const QList<Scope *> &visibleScopes, QList<Scope *> *expandedScopes) const
{
// First expand the protocol itself
expand(protocol->members(), visibleScopes, expandedScopes);
// Then do the same for any incorporated protocol
for (unsigned i = 0; i < protocol->protocolCount(); ++i) {
ObjCBaseProtocol *baseProtocol = protocol->protocolAt(i);
const QList<Symbol *> protocolList = resolveObjCProtocol(baseProtocol->name(), visibleScopes);
foreach (Symbol *symbol, protocolList)
if (ObjCProtocol *protocolSymbol = symbol->asObjCProtocol())
expandObjCProtocol(protocolSymbol, visibleScopes, expandedScopes);
}
}
void LookupContext::expand(Scope *scope,
const QList<Scope *> &visibleScopes,
QList<Scope *> *expandedScopes) const
......@@ -546,6 +611,8 @@ void LookupContext::expand(Scope *scope,
expandFunction(fun, visibleScopes, expandedScopes);
} else if (ObjCMethod *meth = scope->owner()->asObjCMethod()) {
expandObjCMethod(meth, visibleScopes, expandedScopes);
} else if (ObjCClass *objcKlass = scope->owner()->asObjCClass()) {
expandObjCClass(objcKlass, visibleScopes, expandedScopes);
}
}
......
......@@ -75,12 +75,20 @@ public:
QList<Symbol *> resolveClassOrNamespace(Name *name) const
{ return resolveClassOrNamespace(name, visibleScopes()); }
QList<Symbol *> resolveObjCClass(Name *name) const
{ return resolveObjCClass(name, visibleScopes()); }
QList<Symbol *> resolveObjCProtocol(Name *name) const
{ return resolveObjCProtocol(name, visibleScopes()); }
enum ResolveMode {
ResolveSymbol = 0x01,
ResolveClass = 0x02,
ResolveNamespace = 0x04,
ResolveClassOrNamespace = ResolveClass | ResolveNamespace,
ResolveAll = ResolveSymbol | ResolveClassOrNamespace
ResolveObjCClass = 0x08,
ResolveObjCProtocol = 0x10,
ResolveAll = ResolveSymbol | ResolveClassOrNamespace | ResolveObjCClass | ResolveObjCProtocol
};
QList<Symbol *> resolve(Name *name, const QList<Scope *> &visibleScopes,
......@@ -95,6 +103,12 @@ public:
QList<Symbol *> resolveClassOrNamespace(Name *name, const QList<Scope *> &visibleScopes) const
{ return resolve(name, visibleScopes, ResolveClassOrNamespace); }
QList<Symbol *> resolveObjCClass(Name *name, const QList<Scope *> &visibleScopes) const
{ return resolve(name, visibleScopes, ResolveObjCClass); }
QList<Symbol *> resolveObjCProtocol(Name *name, const QList<Scope *> &visibleScopes) const
{ return resolve(name, visibleScopes, ResolveObjCProtocol); }
QList<Scope *> visibleScopes() const
{ return _visibleScopes; }
......@@ -128,6 +142,14 @@ public:
const QList<Scope *> &visibleScopes,
QList<Scope *> *expandedScopes) const;
void expandObjCClass(ObjCClass *klass,
const QList<Scope *> &visibleScopes,
QList<Scope *> *expandedScopes) const;
void expandObjCProtocol(ObjCProtocol *protocol,
const QList<Scope *> &visibleScopes,
QList<Scope *> *expandedScopes) const;
void expandEnumOrAnonymousSymbol(ScopedSymbol *scopedSymbol,
QList<Scope *> *expandedScopes) const;
......
......@@ -738,11 +738,67 @@ ResolveExpression::resolveMember(Name *memberName, Class *klass,
}
QList<ResolveExpression::Result>
ResolveExpression::resolveMember(Name *memberName, ObjCClass *klass) const
{
QList<Result> results;
if (!memberName || !klass)
return results;
QList<Scope *> scopes;
_context.expand(klass->members(), _context.visibleScopes(), &scopes);
QList<Symbol *> candidates = _context.resolve(memberName, scopes);
foreach (Symbol *candidate, candidates) {
FullySpecifiedType ty = candidate->type();
results.append(Result(ty, candidate));
}
return removeDuplicates(results);
}
bool ResolveExpression::visit(PostIncrDecrAST *)
{
return false;
}
bool ResolveExpression::visit(ObjCMessageExpressionAST *ast)
{
QList<Result> receiverResults = operator()(ast->receiver_expression);
if (!receiverResults.isEmpty()) {
Result result = receiverResults.first();
FullySpecifiedType ty = result.first.simplified();
Name *klassName = 0;
if (const ObjCClass *classTy = ty->asObjCClassType()) {
// static access, e.g.:
// [NSObject description];
klassName = classTy->name();
} else if (const PointerType *ptrTy = ty->asPointerType()) {
const FullySpecifiedType pointeeTy = ptrTy->elementType();
if (pointeeTy && pointeeTy->isNamedType()) {
// dynamic access, e.g.:
// NSObject *obj = ...; [obj release];
klassName = pointeeTy->asNamedType()->name();
}
}
if (klassName&&ast->selector && ast->selector->selector_name) {
ResolveObjCClass resolveObjCClass;
QList<Symbol *> resolvedSymbols = resolveObjCClass(klassName, result, _context);
foreach (Symbol *resolvedSymbol, resolvedSymbols)
if (ObjCClass *klass = resolvedSymbol->asObjCClass())
_results.append(resolveMember(ast->selector->selector_name, klass));
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
ResolveClass::ResolveClass()
{ }
......@@ -811,3 +867,35 @@ QList<Symbol *> ResolveClass::resolveClass(Name *name,
return resolvedSymbols;
}
ResolveObjCClass::ResolveObjCClass()
{}
QList<Symbol *> ResolveObjCClass::operator ()(Name *name,
const ResolveExpression::Result &p,
const LookupContext &context)
{
QList<Symbol *> resolvedSymbols;
const QList<Symbol *> candidates =
context.resolve(name, context.visibleScopes(p));
foreach (Symbol *candidate, candidates) {
if (ObjCClass *klass = candidate->asObjCClass()) {
if (resolvedSymbols.contains(klass))
continue; // we already know about `klass'
resolvedSymbols.append(klass);
} else if (candidate->isTypedef()) {
if (Declaration *decl = candidate->asDeclaration()) {
if (decl->type()->isObjCClassType()) {
ObjCClass *klass = decl->type()->asObjCClassType();
if (resolvedSymbols.contains(klass))
continue;
resolvedSymbols.append(klass);
}
}
}
}
return resolvedSymbols;
}
......@@ -61,6 +61,8 @@ public:
QList<Result> resolveMember(Name *memberName, Class *klass,
Name *className = 0) const;
QList<Result> resolveMember(Name *memberName, ObjCClass *klass) const;
protected:
QList<Result> switchResults(const QList<Result> &symbols);
......@@ -109,6 +111,9 @@ protected:
virtual bool visit(PostIncrDecrAST *ast);
virtual bool visit(MemberAccessAST *ast);
// Objective-C expressions
virtual bool visit(ObjCMessageExpressionAST *ast);
QList<Scope *> visibleScopes(const Result &result) const;
private:
......@@ -136,6 +141,16 @@ private:
QList<ResolveExpression::Result> _blackList;
};
class CPLUSPLUS_EXPORT ResolveObjCClass
{
public:
ResolveObjCClass();
QList<Symbol *> operator()(Name *name,
const ResolveExpression::Result &p,
const LookupContext &context);
};
} // end of namespace CPlusPlus
......
......@@ -662,15 +662,13 @@ bool CheckDeclaration::visit(ObjCMethodDeclarationAST *ast)
Declaration *decl = control()->newDeclaration(ast->firstToken(), methodType->name());
decl->setType(methodType);
symbol = decl;
symbol->setStorage(methodType->storage());
}
symbol->setStartOffset(tokenAt(ast->firstToken()).offset);
symbol->setEndOffset(tokenAt(ast->lastToken()).offset);
symbol->setVisibility(semantic()->currentVisibility());
if (semantic()->isObjCClassMethod(ast->method_prototype->method_type_token))
symbol->setStorage(Symbol::Static);
_scope->enterSymbol(symbol);
return false;
......
......@@ -246,21 +246,32 @@ bool CheckDeclarator::visit(ReferenceAST *)
bool CheckDeclarator::visit(ObjCMethodPrototypeAST *ast)
{
if (!ast)
return false;
if (!ast->selector) {
// TODO: (EV) this currently happens when parsing:
// + (id<NSSomeProtocol>) zoo;
// where the parser will start doing template magic. We'll need to disambiguate this case.
return false;
}
FullySpecifiedType returnType = semantic()->check(ast->type_name, _scope);
unsigned location = ast->firstToken();
Name *name = semantic()->check(ast->selector, _scope);
semantic()->check(ast->selector, _scope);
ObjCMethod *method = control()->newObjCMethod(location, name);
ObjCMethod *method = control()->newObjCMethod(location, ast->selector->selector_name);
ast->symbol = method;
method->setSourceLocation(location);
method->setScope(_scope);
method->setVisibility(semantic()->currentVisibility());
method->setReturnType(returnType);
if (semantic()->isObjCClassMethod(tokenKind(ast->method_type_token)))
method->setStorage(Symbol::Static);
if (ast->selector && ast->selector->asObjCSelectorWithArguments()) {
// TODO: add arguments (EV)
for (ObjCMessageArgumentDeclarationListAST *it = ast->argument_list; it; it = it->next) {
ObjCMessageArgumentDeclarationAST *argDecl = it->value;
......@@ -273,8 +284,6 @@ bool CheckDeclarator::visit(ObjCMethodPrototypeAST *ast)
_fullySpecifiedType = FullySpecifiedType(method);
// TODO: check which specifiers are allowed here (EV)
return false;
}
......
......@@ -368,7 +368,7 @@ bool CheckExpression::visit(MemberAccessAST *ast)
bool CheckExpression::visit(ObjCMessageExpressionAST *ast)
{
semantic()->check(ast->receiver_expression, _scope);
(void) semantic()->check(ast->receiver_expression, _scope);
(void) semantic()->check(ast->selector, _scope);
accept(ast->argument_list); // ### not necessary.
......
......@@ -379,8 +379,9 @@ bool CheckName::visit(TemplateIdAST *ast)
bool CheckName::visit(ObjCSelectorWithoutArgumentsAST *ast)
{
std::vector<Name *> names;
Identifier *id = identifier(ast->name_token);
names.push_back(control()->nameId(id));
Identifier *id = control()->findOrInsertIdentifier(spell(ast->name_token));
NameId *nameId = control()->nameId(id);
names.push_back(nameId);
_name = control()->selectorNameId(&names[0], names.size(), false);
ast->selector_name = _name;
......@@ -391,10 +392,9 @@ bool CheckName::visit(ObjCSelectorWithArgumentsAST *ast)
{
std::vector<Name *> names;
for (ObjCSelectorArgumentListAST *it = ast->selector_argument_list; it; it = it->next) {
Identifier *id = identifier(it->value->name_token);
Name *name = control()->nameId(id);
names.push_back(name);
Identifier *id = control()->findOrInsertIdentifier(spell(it->value->name_token));
NameId *nameId = control()->nameId(id);
names.push_back(nameId);
}
if (!names.empty()) {
......
......@@ -289,7 +289,9 @@ void SelectorNameId::accept0(NameVisitor *visitor)
Identifier *SelectorNameId::identifier() const
{
// FIXME: (EV)
if (! _nameCount)
return 0;
return nameAt(0)->identifier();
}
......
......@@ -245,6 +245,9 @@ Symbol *Scope::lookat(Identifier *id) const
break;
} else if (identity->isQualifiedNameId()) {
assert(0);
} else if (SelectorNameId *selectorNameId = identity->asSelectorNameId()) {
if (selectorNameId->identifier()->isEqualTo(id))
break;
}
}
return symbol;
......
......@@ -232,9 +232,7 @@ bool Semantic::isObjCClassMethod(int tokenKind) const
case T_PLUS:
return true;
case T_MINUS:
return false;
default:
// TODO EV: assert here?
return false;
}
}
......
......@@ -104,6 +104,9 @@ protected:
virtual void visit(QualifiedNameId *name)
{ _value = operator()(name->unqualifiedNameId()); }
virtual void visit(SelectorNameId *name)
{ _value = name->identifier()->hashCode(); }
private:
unsigned _value;
};
......@@ -151,6 +154,9 @@ protected:
virtual void visit(QualifiedNameId *name)
{ _identity = name->unqualifiedNameId(); }
virtual void visit(SelectorNameId *name)
{ _identity = name; }
private:
Name *_identity;
};
......@@ -461,6 +467,12 @@ bool Symbol::isArgument() const
bool Symbol::isBaseClass() const
{ return asBaseClass() != 0; }
bool Symbol::isObjCBaseClass() const
{ return asObjCBaseClass() != 0; }
bool Symbol::isObjCBaseProtocol() const
{ return asObjCBaseProtocol() != 0; }
bool Symbol::isObjCClass() const
{ return asObjCClass() != 0; }
......
......@@ -70,6 +70,7 @@ private slots:
void assignment_2();
// objc++
void objc_simple_class();
void objc_attributes_followed_by_at_keyword();
void objc_protocol_forward_declaration_1();
void objc_protocol_definition_1();
......@@ -512,6 +513,20 @@ void tst_AST::cpp_initializer_or_function_declaration()
QCOMPARE(param->type_specifier_list->value->asNamedTypeSpecifier()->name->asSimpleName()->identifier_token, 4U);
}
void tst_AST::objc_simple_class()
{
QSharedPointer<TranslationUnit> unit(parseDeclaration("\n"
"@interface Zoo {} +(id)alloc;-(id)init;@end\n"
"@implementation Zoo\n"
"+(id)alloc{}\n"
"-(id)init{}\n"
"@end\n"
));
AST *ast = unit->ast();
QVERIFY(ast);
}
void tst_AST::objc_attributes_followed_by_at_keyword()
{
QSharedPointer<TranslationUnit> unit(parseDeclaration("\n"
......
......@@ -6,7 +6,10 @@
#include <ASTVisitor.h>
#include <TranslationUnit.h>
#include <CppDocument.h>
#include <Literals.h>
#include <LookupContext.h>
#include <Name.h>
#include <ResolveExpression.h>
#include <Symbols.h>
#include <Overview.h>
......@@ -55,6 +58,11 @@ class tst_Lookup: public QObject
private Q_SLOTS:
void base_class_defined_1();
// Objective-C
void simple_class_1();
void class_with_baseclass();
void class_with_protocol_with_protocol();
};
void tst_Lookup::base_class_defined_1()
......@@ -110,5 +118,204 @@ void tst_Lookup::base_class_defined_1()
QVERIFY(classToAST.value(derivedClass) != 0);
}
void tst_Lookup::simple_class_1()
{
const QByteArray source = "\n"
"@interface Zoo {} +(id)alloc; -(id)init; @end\n"
"@implementation Zoo +(id)alloc{} -(id)init{} -(void)dealloc{} @end\n";
Document::Ptr doc = Document::create("simple_class_1");
doc->setSource(source);
doc->parse();
doc->check();
QVERIFY(doc->diagnosticMessages().isEmpty());
QCOMPARE(doc->globalSymbolCount(), 2U);
Snapshot snapshot;
snapshot.insert(doc->fileName(), doc);
Document::Ptr emptyDoc = Document::create("<empty>");
ObjCClass *iface = doc->globalSymbolAt(0)->asObjCClass();
QVERIFY(iface);
QVERIFY(iface->isInterface());
QCOMPARE(iface->memberCount(), 2U);
ObjCClass *impl = doc->globalSymbolAt(1)->asObjCClass();
QVERIFY(impl);
QVERIFY(!impl->isInterface());
QCOMPARE(impl->memberCount(), 3U);
ObjCMethod *allocMethod = impl->memberAt(0)->asObjCMethod();
QVERIFY(allocMethod);
QVERIFY(allocMethod->name() && allocMethod->name()->identifier());
QCOMPARE(QLatin1String(allocMethod->name()->identifier()->chars()), QLatin1String("alloc"));
ObjCMethod *deallocMethod = impl->memberAt(2)->asObjCMethod();
QVERIFY(deallocMethod);
QVERIFY(deallocMethod->name() && deallocMethod->name()->identifier());
QCOMPARE(QLatin1String(deallocMethod->name()->identifier()->chars()), QLatin1String("dealloc"));
const LookupContext ctxt(impl, emptyDoc, doc, snapshot);
// check class resolving:
const QList<Symbol *> candidates = ctxt.resolveObjCClass(impl->name());
QCOMPARE(candidates.size(), 2);
QVERIFY(candidates.contains(iface));
QVERIFY(candidates.contains(impl));
// check scope expansion:
QList<Scope *> expandedScopes;
ctxt.expand(impl->members(), ctxt.visibleScopes(), &expandedScopes);
QCOMPARE(expandedScopes.size(), 2);
const ResolveExpression resolver(ctxt);
// check method resolving:
QList<ResolveExpression::Result> results = resolver.resolveMember(allocMethod->name(), impl);
QCOMPARE(results.size(), 2);
QVERIFY(results.at(0).second == allocMethod || results.at(1).second == allocMethod);
QVERIFY(results.at(0).second->asDeclaration() || results.at(1).second->asDeclaration());
results = resolver.resolveMember(deallocMethod->name(), impl);
QCOMPARE(results.size(), 1);
QCOMPARE(results.at(0).second, deallocMethod);
}
void tst_Lookup::class_with_baseclass()
{
const QByteArray source = "\n"