200 changes: 196 additions & 4 deletions clang/lib/AST/ODRDiagsEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ bool ODRDiagsEmitter::diagnoseSubMismatchField(
this](ODRFieldDifference DiffType) {
return Diag(SecondField->getLocation(),
diag::note_module_odr_violation_field)
<< SecondModule << SecondField->getSourceRange() << DiffType;
<< SecondModule.empty() << SecondModule << SecondField->getSourceRange() << DiffType;
};

IdentifierInfo *FirstII = FirstField->getIdentifier();
Expand All @@ -175,9 +175,6 @@ bool ODRDiagsEmitter::diagnoseSubMismatchField(
return true;
}

assert(Context.hasSameType(FirstField->getType(), SecondField->getType()));
(void)Context;

QualType FirstType = FirstField->getType();
QualType SecondType = SecondField->getType();
if (computeODRHash(FirstType) != computeODRHash(SecondType)) {
Expand All @@ -186,6 +183,9 @@ bool ODRDiagsEmitter::diagnoseSubMismatchField(
return true;
}

assert(Context.hasSameType(FirstField->getType(), SecondField->getType()));
(void)Context;

const bool IsFirstBitField = FirstField->isBitField();
const bool IsSecondBitField = SecondField->isBitField();
if (IsFirstBitField != IsSecondBitField) {
Expand Down Expand Up @@ -615,6 +615,8 @@ ODRDiagsEmitter::FindTypeDiffs(DeclHashes &FirstHashes,
return FunctionTemplate;
case Decl::ObjCMethod:
return ObjCMethod;
case Decl::ObjCIvar:
return ObjCIvar;
case Decl::ObjCProperty:
return ObjCProperty;
}
Expand Down Expand Up @@ -675,6 +677,8 @@ void ODRDiagsEmitter::diagnoseSubMismatchDifferentDeclKinds(
if (DiffType == EndOfClass) {
if (auto *Tag = dyn_cast<TagDecl>(Container))
Loc = Tag->getBraceRange().getEnd();
else if (auto *IF = dyn_cast<ObjCInterfaceDecl>(Container))
Loc = IF->getAtEndRange().getBegin();
else
Loc = Container->getEndLoc();
} else {
Expand Down Expand Up @@ -970,6 +974,7 @@ bool ODRDiagsEmitter::diagnoseMismatch(
case PrivateSpecifer:
case ProtectedSpecifer:
case ObjCMethod:
case ObjCIvar:
case ObjCProperty:
llvm_unreachable("Invalid diff type");

Expand Down Expand Up @@ -1604,6 +1609,7 @@ bool ODRDiagsEmitter::diagnoseMismatch(const RecordDecl *FirstRecord,
case FunctionTemplate:
// Cannot be contained by RecordDecl, invalid in this context.
case ObjCMethod:
case ObjCIvar:
case ObjCProperty:
llvm_unreachable("Invalid diff type");

Expand Down Expand Up @@ -1908,6 +1914,191 @@ bool ODRDiagsEmitter::diagnoseMismatch(const EnumDecl *FirstEnum,
return false;
}

bool ODRDiagsEmitter::diagnoseMismatch(
const ObjCInterfaceDecl *FirstID, const ObjCInterfaceDecl *SecondID,
const struct ObjCInterfaceDecl::DefinitionData *SecondDD) const {
// Multiple different declarations got merged together; tell the user
// where they came from.
if (FirstID == SecondID)
return false;

std::string FirstModule = getOwningModuleNameForDiagnostic(FirstID);
std::string SecondModule = getOwningModuleNameForDiagnostic(SecondID);

// Keep in sync with err_module_odr_violation_objc_interface.
enum ODRInterfaceDifference {
SuperClassType,
IVarAccess,
};

auto DiagError = [FirstID, &FirstModule,
this](SourceLocation Loc, SourceRange Range,
ODRInterfaceDifference DiffType) {
return Diag(Loc, diag::err_module_odr_violation_objc_interface)
<< FirstID << FirstModule.empty() << FirstModule << Range
<< DiffType;
};
auto DiagNote = [&SecondModule, this](SourceLocation Loc, SourceRange Range,
ODRInterfaceDifference DiffType) {
return Diag(Loc, diag::note_module_odr_violation_objc_interface)
<< SecondModule.empty() << SecondModule << Range << DiffType;
};

const struct ObjCInterfaceDecl::DefinitionData *FirstDD = &FirstID->data();
assert(FirstDD && SecondDD && "Definitions without DefinitionData");
if (FirstDD != SecondDD) {
// Check for matching super class.
auto GetSuperClassSourceRange = [](const TypeSourceInfo *SuperInfo,
const ObjCInterfaceDecl *ID) {
if (!SuperInfo)
return ID->getSourceRange();
TypeLoc Loc = SuperInfo->getTypeLoc();
return SourceRange(Loc.getBeginLoc(), Loc.getEndLoc());
};

ObjCInterfaceDecl *FirstSuperClass = FirstID->getSuperClass();
ObjCInterfaceDecl *SecondSuperClass = nullptr;
const TypeSourceInfo *FirstSuperInfo = FirstID->getSuperClassTInfo();
const TypeSourceInfo *SecondSuperInfo = SecondDD->SuperClassTInfo;
if (SecondSuperInfo)
SecondSuperClass =
SecondSuperInfo->getType()->castAs<ObjCObjectType>()->getInterface();

if ((FirstSuperClass && SecondSuperClass &&
FirstSuperClass->getODRHash() != SecondSuperClass->getODRHash()) ||
(FirstSuperClass && !SecondSuperClass) ||
(!FirstSuperClass && SecondSuperClass)) {
QualType FirstType;
if (FirstSuperInfo)
FirstType = FirstSuperInfo->getType();

DiagError(FirstID->getLocation(),
GetSuperClassSourceRange(FirstSuperInfo, FirstID),
SuperClassType)
<< (bool)FirstSuperInfo << FirstType;

QualType SecondType;
if (SecondSuperInfo)
SecondType = SecondSuperInfo->getType();

DiagNote(SecondID->getLocation(),
GetSuperClassSourceRange(SecondSuperInfo, SecondID),
SuperClassType)
<< (bool)SecondSuperInfo << SecondType;
return true;
}

// Check both interfaces reference the same protocols.
auto &FirstProtos = FirstID->getReferencedProtocols();
auto &SecondProtos = SecondDD->ReferencedProtocols;
if (diagnoseSubMismatchProtocols(FirstProtos, FirstID, FirstModule,
SecondProtos, SecondID, SecondModule))
return true;
}

auto PopulateHashes = [](DeclHashes &Hashes, const ObjCInterfaceDecl *ID,
const DeclContext *DC) {
for (auto *D : ID->decls()) {
if (!ODRHash::isSubDeclToBeProcessed(D, DC))
continue;
Hashes.emplace_back(D, computeODRHash(D));
}
};

DeclHashes FirstHashes;
DeclHashes SecondHashes;
// Use definition as DeclContext because definitions are merged when
// DeclContexts are merged and separate when DeclContexts are separate.
PopulateHashes(FirstHashes, FirstID, FirstID->getDefinition());
PopulateHashes(SecondHashes, SecondID, SecondID->getDefinition());

DiffResult DR = FindTypeDiffs(FirstHashes, SecondHashes);
ODRMismatchDecl FirstDiffType = DR.FirstDiffType;
ODRMismatchDecl SecondDiffType = DR.SecondDiffType;
const Decl *FirstDecl = DR.FirstDecl;
const Decl *SecondDecl = DR.SecondDecl;

if (FirstDiffType == Other || SecondDiffType == Other) {
diagnoseSubMismatchUnexpected(DR, FirstID, FirstModule, SecondID,
SecondModule);
return true;
}

if (FirstDiffType != SecondDiffType) {
diagnoseSubMismatchDifferentDeclKinds(DR, FirstID, FirstModule, SecondID,
SecondModule);
return true;
}

assert(FirstDiffType == SecondDiffType);
switch (FirstDiffType) {
// Already handled.
case EndOfClass:
case Other:
// Cannot be contained by ObjCInterfaceDecl, invalid in this context.
case Field:
case TypeDef:
case Var:
// C++ only, invalid in this context.
case PublicSpecifer:
case PrivateSpecifer:
case ProtectedSpecifer:
case StaticAssert:
case CXXMethod:
case TypeAlias:
case Friend:
case FunctionTemplate:
llvm_unreachable("Invalid diff type");

case ObjCMethod: {
if (diagnoseSubMismatchObjCMethod(FirstID, FirstModule, SecondModule,
cast<ObjCMethodDecl>(FirstDecl),
cast<ObjCMethodDecl>(SecondDecl)))
return true;
break;
}
case ObjCIvar: {
if (diagnoseSubMismatchField(FirstID, FirstModule, SecondModule,
cast<FieldDecl>(FirstDecl),
cast<FieldDecl>(SecondDecl)))
return true;

// Check if the access match.
const ObjCIvarDecl *FirstIvar = cast<ObjCIvarDecl>(FirstDecl);
const ObjCIvarDecl *SecondIvar = cast<ObjCIvarDecl>(SecondDecl);
if (FirstIvar->getCanonicalAccessControl() !=
SecondIvar->getCanonicalAccessControl()) {
DiagError(FirstIvar->getLocation(), FirstIvar->getSourceRange(),
IVarAccess)
<< FirstIvar->getName()
<< (int)FirstIvar->getCanonicalAccessControl();
DiagNote(SecondIvar->getLocation(), SecondIvar->getSourceRange(),
IVarAccess)
<< SecondIvar->getName()
<< (int)SecondIvar->getCanonicalAccessControl();
return true;
}
break;
}
case ObjCProperty: {
if (diagnoseSubMismatchObjCProperty(FirstID, FirstModule, SecondModule,
cast<ObjCPropertyDecl>(FirstDecl),
cast<ObjCPropertyDecl>(SecondDecl)))
return true;
break;
}
}

Diag(FirstDecl->getLocation(),
diag::err_module_odr_violation_mismatch_decl_unknown)
<< FirstID << FirstModule.empty() << FirstModule << FirstDiffType
<< FirstDecl->getSourceRange();
Diag(SecondDecl->getLocation(),
diag::note_module_odr_violation_mismatch_decl_unknown)
<< SecondModule << FirstDiffType << SecondDecl->getSourceRange();
return true;
}

bool ODRDiagsEmitter::diagnoseMismatch(
const ObjCProtocolDecl *FirstProtocol,
const ObjCProtocolDecl *SecondProtocol,
Expand Down Expand Up @@ -1975,6 +2166,7 @@ bool ODRDiagsEmitter::diagnoseMismatch(
case Field:
case TypeDef:
case Var:
case ObjCIvar:
// C++ only, invalid in this context.
case PublicSpecifer:
case PrivateSpecifer:
Expand Down
33 changes: 33 additions & 0 deletions clang/lib/AST/ODRHash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
Inherited::VisitFieldDecl(D);
}

void VisitObjCIvarDecl(const ObjCIvarDecl *D) {
ID.AddInteger(D->getCanonicalAccessControl());
Inherited::VisitObjCIvarDecl(D);
}

void VisitObjCPropertyDecl(const ObjCPropertyDecl *D) {
ID.AddInteger(D->getPropertyAttributes());
ID.AddInteger(D->getPropertyImplementation());
Expand Down Expand Up @@ -537,6 +542,7 @@ bool ODRHash::isSubDeclToBeProcessed(const Decl *D, const DeclContext *Parent) {
case Decl::Typedef:
case Decl::Var:
case Decl::ObjCMethod:
case Decl::ObjCIvar:
case Decl::ObjCProperty:
return true;
}
Expand Down Expand Up @@ -613,6 +619,33 @@ void ODRHash::AddRecordDecl(const RecordDecl *Record) {
AddSubDecl(SubDecl);
}

void ODRHash::AddObjCInterfaceDecl(const ObjCInterfaceDecl *IF) {
AddDecl(IF);

auto *SuperClass = IF->getSuperClass();
AddBoolean(SuperClass);
if (SuperClass)
ID.AddInteger(SuperClass->getODRHash());

// Hash referenced protocols.
ID.AddInteger(IF->getReferencedProtocols().size());
for (const ObjCProtocolDecl *RefP : IF->protocols()) {
// Hash the name only as a referenced protocol can be a forward declaration.
AddDeclarationName(RefP->getDeclName());
}

// Filter out sub-Decls which will not be processed in order to get an
// accurate count of Decl's.
llvm::SmallVector<const Decl *, 16> Decls;
for (Decl *SubDecl : IF->decls())
if (isSubDeclToBeProcessed(SubDecl, IF))
Decls.push_back(SubDecl);

ID.AddInteger(Decls.size());
for (auto *SubDecl : Decls)
AddSubDecl(SubDecl);
}

void ODRHash::AddFunctionDecl(const FunctionDecl *Function,
bool SkipBody) {
assert(Function && "Expecting non-null pointer.");
Expand Down
15 changes: 14 additions & 1 deletion clang/lib/Parse/ParseObjc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,17 +370,30 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
Actions.ActOnTypedefedProtocols(protocols, protocolLocs,
superClassId, superClassLoc);

Sema::SkipBodyInfo SkipBody;
ObjCInterfaceDecl *ClsType = Actions.ActOnStartClassInterface(
getCurScope(), AtLoc, nameId, nameLoc, typeParameterList, superClassId,
superClassLoc, typeArgs,
SourceRange(typeArgsLAngleLoc, typeArgsRAngleLoc), protocols.data(),
protocols.size(), protocolLocs.data(), EndProtoLoc, attrs);
protocols.size(), protocolLocs.data(), EndProtoLoc, attrs, &SkipBody);

if (Tok.is(tok::l_brace))
ParseObjCClassInstanceVariables(ClsType, tok::objc_protected, AtLoc);

ParseObjCInterfaceDeclList(tok::objc_interface, ClsType);

if (SkipBody.CheckSameAsPrevious) {
auto *PreviousDef = cast<ObjCInterfaceDecl>(SkipBody.Previous);
if (Actions.ActOnDuplicateODRHashDefinition(ClsType, PreviousDef)) {
ClsType->mergeDuplicateDefinitionWithCommon(PreviousDef->getDefinition());
} else {
ODRDiagsEmitter DiagsEmitter(Diags, Actions.getASTContext(),
getPreprocessor().getLangOpts());
DiagsEmitter.diagnoseMismatch(PreviousDef, ClsType);
ClsType->setInvalidDecl();
}
}

return ClsType;
}

Expand Down
20 changes: 14 additions & 6 deletions clang/lib/Sema/SemaDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ ObjCInterfaceDecl *Sema::ActOnStartClassInterface(
ArrayRef<ParsedType> SuperTypeArgs, SourceRange SuperTypeArgsRange,
Decl *const *ProtoRefs, unsigned NumProtoRefs,
const SourceLocation *ProtoLocs, SourceLocation EndProtoLoc,
const ParsedAttributesView &AttrList) {
const ParsedAttributesView &AttrList, SkipBodyInfo *SkipBody) {
assert(ClassName && "Missing class identifier");

// Check for another declaration kind with the same name.
Expand Down Expand Up @@ -1057,10 +1057,16 @@ ObjCInterfaceDecl *Sema::ActOnStartClassInterface(
if (PrevIDecl) {
// Class already seen. Was it a definition?
if (ObjCInterfaceDecl *Def = PrevIDecl->getDefinition()) {
Diag(AtInterfaceLoc, diag::err_duplicate_class_def)
<< PrevIDecl->getDeclName();
Diag(Def->getLocation(), diag::note_previous_definition);
IDecl->setInvalidDecl();
if (SkipBody && !hasVisibleDefinition(Def)) {
SkipBody->CheckSameAsPrevious = true;
SkipBody->New = IDecl;
SkipBody->Previous = Def;
} else {
Diag(AtInterfaceLoc, diag::err_duplicate_class_def)
<< PrevIDecl->getDeclName();
Diag(Def->getLocation(), diag::note_previous_definition);
IDecl->setInvalidDecl();
}
}
}

Expand All @@ -1075,7 +1081,9 @@ ObjCInterfaceDecl *Sema::ActOnStartClassInterface(

// Start the definition of this class. If we're in a redefinition case, there
// may already be a definition, so we'll end up adding to it.
if (!IDecl->hasDefinition())
if (SkipBody && SkipBody->CheckSameAsPrevious)
IDecl->startDuplicateDefinitionForComparison();
else if (!IDecl->hasDefinition())
IDecl->startDefinition();

if (SuperName) {
Expand Down
32 changes: 32 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6857,6 +6857,7 @@ void TypeLocReader::VisitPackExpansionTypeLoc(PackExpansionTypeLoc TL) {

void TypeLocReader::VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc TL) {
TL.setNameLoc(readSourceLocation());
TL.setNameEndLoc(readSourceLocation());
}

void TypeLocReader::VisitObjCTypeParamTypeLoc(ObjCTypeParamTypeLoc TL) {
Expand Down Expand Up @@ -9507,6 +9508,7 @@ void ASTReader::diagnoseOdrViolations() {
PendingRecordOdrMergeFailures.empty() &&
PendingFunctionOdrMergeFailures.empty() &&
PendingEnumOdrMergeFailures.empty() &&
PendingObjCInterfaceOdrMergeFailures.empty() &&
PendingObjCProtocolOdrMergeFailures.empty())
return;

Expand Down Expand Up @@ -9538,6 +9540,16 @@ void ASTReader::diagnoseOdrViolations() {
D->decls_begin();
}

// Trigger the import of the full interface definition.
auto ObjCInterfaceOdrMergeFailures =
std::move(PendingObjCInterfaceOdrMergeFailures);
PendingObjCInterfaceOdrMergeFailures.clear();
for (auto &Merge : ObjCInterfaceOdrMergeFailures) {
Merge.first->decls_begin();
for (auto &InterfacePair : Merge.second)
InterfacePair.first->decls_begin();
}

// Trigger the import of functions.
auto FunctionOdrMergeFailures = std::move(PendingFunctionOdrMergeFailures);
PendingFunctionOdrMergeFailures.clear();
Expand Down Expand Up @@ -9657,6 +9669,7 @@ void ASTReader::diagnoseOdrViolations() {

if (OdrMergeFailures.empty() && RecordOdrMergeFailures.empty() &&
FunctionOdrMergeFailures.empty() && EnumOdrMergeFailures.empty() &&
ObjCInterfaceOdrMergeFailures.empty() &&
ObjCProtocolOdrMergeFailures.empty())
return;

Expand Down Expand Up @@ -9749,6 +9762,25 @@ void ASTReader::diagnoseOdrViolations() {
assert(Diagnosed && "Unable to emit ODR diagnostic.");
}

for (auto &Merge : ObjCInterfaceOdrMergeFailures) {
// If we've already pointed out a specific problem with this interface,
// don't bother issuing a general "something's different" diagnostic.
if (!DiagnosedOdrMergeFailures.insert(Merge.first).second)
continue;

bool Diagnosed = false;
ObjCInterfaceDecl *FirstID = Merge.first;
for (auto &InterfacePair : Merge.second) {
if (DiagsEmitter.diagnoseMismatch(FirstID, InterfacePair.first,
InterfacePair.second)) {
Diagnosed = true;
break;
}
}
(void)Diagnosed;
assert(Diagnosed && "Unable to emit ODR diagnostic.");
}

for (auto &Merge : ObjCProtocolOdrMergeFailures) {
// If we've already pointed out a specific problem with this protocol,
// don't bother issuing a general "something's different" diagnostic.
Expand Down
17 changes: 11 additions & 6 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,8 @@ void ASTDeclReader::ReadObjCDefinitionData(

Data.EndLoc = readSourceLocation();
Data.HasDesignatedInitializers = Record.readInt();
Data.ODRHash = Record.readInt();
Data.HasODRHash = true;

// Read the directly referenced protocols and their SourceLocations.
unsigned NumProtocols = Record.readInt();
Expand Down Expand Up @@ -1234,13 +1236,16 @@ void ASTDeclReader::ReadObjCDefinitionData(
void ASTDeclReader::MergeDefinitionData(ObjCInterfaceDecl *D,
struct ObjCInterfaceDecl::DefinitionData &&NewDD) {
struct ObjCInterfaceDecl::DefinitionData &DD = D->data();
if (DD.Definition != NewDD.Definition) {
Reader.MergedDeclContexts.insert(
std::make_pair(NewDD.Definition, DD.Definition));
Reader.mergeDefinitionVisibility(DD.Definition, NewDD.Definition);
}
if (DD.Definition == NewDD.Definition)
return;

// FIXME: odr checking?
Reader.MergedDeclContexts.insert(
std::make_pair(NewDD.Definition, DD.Definition));
Reader.mergeDefinitionVisibility(DD.Definition, NewDD.Definition);

if (D->getODRHash() != NewDD.ODRHash)
Reader.PendingObjCInterfaceOdrMergeFailures[DD.Definition].push_back(
{NewDD.Definition, &NewDD});
}

void ASTDeclReader::VisitObjCInterfaceDecl(ObjCInterfaceDecl *ID) {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ void TypeLocWriter::VisitPackExpansionTypeLoc(PackExpansionTypeLoc TL) {

void TypeLocWriter::VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc TL) {
addSourceLocation(TL.getNameLoc());
addSourceLocation(TL.getNameEndLoc());
}

void TypeLocWriter::VisitObjCObjectTypeLoc(ObjCObjectTypeLoc TL) {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ void ASTDeclWriter::VisitObjCInterfaceDecl(ObjCInterfaceDecl *D) {
Record.AddTypeSourceInfo(D->getSuperClassTInfo());
Record.AddSourceLocation(D->getEndOfDefinitionLoc());
Record.push_back(Data.HasDesignatedInitializers);
Record.push_back(D->getODRHash());

// Write out the protocols that are directly referenced by the @interface.
Record.push_back(Data.ReferencedProtocols.size());
Expand Down
446 changes: 446 additions & 0 deletions clang/test/Modules/compare-objc-interface.m

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions clang/test/Modules/hidden-duplicates.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ @protocol ForwardDeclaredProtocolWithoutDefinition;
id<ForwardDeclaredProtocolWithoutDefinition> forwardDeclaredProtocol(
id<ForwardDeclaredProtocolWithoutDefinition> t);

@interface NSObject @end
@class ForwardDeclaredInterfaceWithoutDefinition;

NSObject *interfaceDefinition(NSObject *o);
NSObject *forwardDeclaredInterface(NSObject *o);

#endif

//--- include/empty.h
Expand Down
3 changes: 2 additions & 1 deletion clang/test/Modules/interface-diagnose-missing-import.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// RUN: rm -rf %t
// RUN: %clang_cc1 %s -fsyntax-only -fmodules -fimplicit-module-maps -fmodules-cache-path=%t -F%S/Inputs/interface-diagnose-missing-import -verify
// expected-no-diagnostics
@interface Buggy
@interface NSObject @end
@interface Buggy : NSObject
@end

@import Foo.Bar;
Expand Down
2 changes: 2 additions & 0 deletions clang/test/Modules/method_pool.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ void testMethod5(id object, D* d) {
}

@import MethodPoolB;
// expected-error@MethodPoolB.h:* {{'B' has different definitions in different modules; first difference is definition in module 'MethodPoolB' found no super class}}
// expected-note@MethodPoolA.h:* {{but in 'MethodPoolA' found super class with type 'A'}}

void testMethod1Again(id object) {
[object method1];
Expand Down