]> granicus.if.org Git - clang/commitdiff
Add a clarifying note when a return statement is rejected because
authorJohn McCall <rjmccall@apple.com>
Tue, 19 Mar 2013 07:04:25 +0000 (07:04 +0000)
committerJohn McCall <rjmccall@apple.com>
Tue, 19 Mar 2013 07:04:25 +0000 (07:04 +0000)
we expect a related result type.

rdar://12493140

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@177378 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Sema/Sema.h
lib/Sema/SemaDeclObjC.cpp
lib/Sema/SemaExpr.cpp
lib/Sema/SemaExprObjC.cpp
lib/Sema/SemaInit.cpp
lib/Sema/SemaStmt.cpp
test/CodeGenObjC/arc.m
test/SemaObjC/instancetype.m
test/SemaObjC/related-result-type-inference.m
test/SemaObjCXX/instancetype.mm [new file with mode: 0644]

index e08c5fc985a4d0e2d705b2027814cbed1554cfac..93f0697a56e0295ebb3b331351a2069495442c62 100644 (file)
@@ -6198,15 +6198,19 @@ def warn_related_result_type_compatibility_class : Warning<
 def warn_related_result_type_compatibility_protocol : Warning<
   "protocol method is expected to return an instance of the implementing "
   "class, but is declared to return %0">;
-def note_related_result_type_overridden_family : Note<
-  "overridden method is part of the '%select{|alloc|copy|init|mutableCopy|"
-  "new|autorelease|dealloc|finalize|release|retain|retainCount|self}0' method "
-  "family">;
+def note_related_result_type_family : Note<
+  "%select{overridden|current}0 method is part of the '%select{|alloc|copy|init|"
+  "mutableCopy|new|autorelease|dealloc|finalize|release|retain|retainCount|"
+  "self}1' method family%select{| and is expected to return an instance of its "
+  "class type}0">;
 def note_related_result_type_overridden : Note<
   "overridden method returns an instance of its class type">;
 def note_related_result_type_inferred : Note<
   "%select{class|instance}0 method %1 is assumed to return an instance of "
   "its receiver type (%2)">;
+def note_related_result_type_explicit : Note<
+  "%select{overridden|current}0 method is explicitly declared 'instancetype'"
+  "%select{| and is expected to return an instance of its class type}0">;
 
 }
 
index 9205e2a4a83b3ae885712711f4bd91bf4d7bcf75..3f3fcb57a1add5305a14e277e643ec9e11b1b48d 100644 (file)
@@ -7026,6 +7026,11 @@ public:
   /// with a related result type, emit a note describing what happened.
   void EmitRelatedResultTypeNote(const Expr *E);
 
+  /// \brief Given that we had incompatible pointer types in a return
+  /// statement, check whether we're in a method with a related result
+  /// type, and if so, emit a note describing what happened.
+  void EmitRelatedResultTypeNoteForReturn(QualType destType);
+
   /// CheckBooleanCondition - Diagnose problems involving the use of
   /// the given expression as a boolean condition (e.g. in an if
   /// statement).  Also performs the standard function and array
index 759f6c9e8bcfc3fbd5f790fea0611ccb2d90daa9..4bb5d72b28d0b0a0a39ce3682d3c74b6cf607568 100644 (file)
@@ -151,7 +151,8 @@ void Sema::CheckObjCMethodOverride(ObjCMethodDecl *NewMethod,
     
     if (ObjCMethodFamily Family = Overridden->getMethodFamily())
       Diag(Overridden->getLocation(), 
-           diag::note_related_result_type_overridden_family)
+           diag::note_related_result_type_family)
+        << /*overridden method*/ 0
         << Family;
     else
       Diag(Overridden->getLocation(), 
index bf4632c7bf6fc1197e47d6d47a9c1d5b1cafdbed..e4323c35a6762008c7669139293fa0f8002dd574 100644 (file)
@@ -10039,6 +10039,9 @@ bool Sema::DiagnoseAssignmentResult(AssignConvertType ConvTy,
 
   if (CheckInferredResultType)
     EmitRelatedResultTypeNote(SrcExpr);
+
+  if (Action == AA_Returning && ConvTy == IncompatiblePointer)
+    EmitRelatedResultTypeNoteForReturn(DstType);
   
   if (Complained)
     *Complained = true;
index b0d54538081017c37d28f9680bafdd548f09954d..af8380df18e1a82b3ba37d3d3875ad1c33aa54c1 100644 (file)
@@ -1094,6 +1094,73 @@ QualType Sema::getMessageSendResultType(QualType ReceiverType,
   return ReceiverType;
 }
 
+/// Look for an ObjC method whose result type exactly matches the given type.
+static const ObjCMethodDecl *
+findExplicitInstancetypeDeclarer(const ObjCMethodDecl *MD,
+                                 QualType instancetype) {
+  if (MD->getResultType() == instancetype) return MD;
+
+  // For these purposes, a method in an @implementation overrides a
+  // declaration in the @interface.
+  if (const ObjCImplDecl *impl =
+        dyn_cast<ObjCImplDecl>(MD->getDeclContext())) {
+    const ObjCContainerDecl *iface;
+    if (const ObjCCategoryImplDecl *catImpl = 
+          dyn_cast<ObjCCategoryImplDecl>(impl)) {
+      iface = catImpl->getCategoryDecl();
+    } else {
+      iface = impl->getClassInterface();
+    }
+
+    const ObjCMethodDecl *ifaceMD = 
+      iface->getMethod(MD->getSelector(), MD->isInstanceMethod());
+    if (ifaceMD) return findExplicitInstancetypeDeclarer(ifaceMD, instancetype);
+  }
+
+  SmallVector<const ObjCMethodDecl *, 4> overrides;
+  MD->getOverriddenMethods(overrides);
+  for (unsigned i = 0, e = overrides.size(); i != e; ++i) {
+    if (const ObjCMethodDecl *result =
+          findExplicitInstancetypeDeclarer(overrides[i], instancetype))
+      return result;
+  }
+
+  return 0;
+}
+
+void Sema::EmitRelatedResultTypeNoteForReturn(QualType destType) {
+  // Only complain if we're in an ObjC method and the required return
+  // type doesn't match the method's declared return type.
+  ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CurContext);
+  if (!MD || !MD->hasRelatedResultType() ||
+      Context.hasSameUnqualifiedType(destType, MD->getResultType()))
+    return;
+
+  // Look for a method overridden by this method which explicitly uses
+  // 'instancetype'.
+  if (const ObjCMethodDecl *overridden =
+        findExplicitInstancetypeDeclarer(MD, Context.getObjCInstanceType())) {
+    SourceLocation loc;
+    SourceRange range;
+    if (TypeSourceInfo *TSI = overridden->getResultTypeSourceInfo()) {
+      range = TSI->getTypeLoc().getSourceRange();
+      loc = range.getBegin();
+    }
+    if (loc.isInvalid())
+      loc = overridden->getLocation();
+    Diag(loc, diag::note_related_result_type_explicit)
+      << /*current method*/ 1 << range;
+    return;
+  }
+
+  // Otherwise, if we have an interesting method family, note that.
+  // This should always trigger if the above didn't.
+  if (ObjCMethodFamily family = MD->getMethodFamily())
+    Diag(MD->getLocation(), diag::note_related_result_type_family)
+      << /*current method*/ 1
+      << family;
+}
+
 void Sema::EmitRelatedResultTypeNote(const Expr *E) {
   E = E->IgnoreParenImpCasts();
   const ObjCMessageExpr *MsgSend = dyn_cast<ObjCMessageExpr>(E);
index 2ddabbdb26e653723c713ee75dc162c7f9d46f71..75aa17bf076d6ba03512626c2798b25fc40e52b9 100644 (file)
@@ -5590,6 +5590,26 @@ static bool DiagnoseUninitializedReference(Sema &S, SourceLocation Loc,
 //===----------------------------------------------------------------------===//
 // Diagnose initialization failures
 //===----------------------------------------------------------------------===//
+
+/// Emit notes associated with an initialization that failed due to a
+/// "simple" conversion failure.
+static void emitBadConversionNotes(Sema &S, const InitializedEntity &entity,
+                                   Expr *op) {
+  QualType destType = entity.getType();
+  if (destType.getNonReferenceType()->isObjCObjectPointerType() &&
+      op->getType()->isObjCObjectPointerType()) {
+
+    // Emit a possible note about the conversion failing because the
+    // operand is a message send with a related result type.
+    S.EmitRelatedResultTypeNote(op);
+
+    // Emit a possible note about a return failing because we're
+    // expecting a related result type.
+    if (entity.getKind() == InitializedEntity::EK_Result)
+      S.EmitRelatedResultTypeNoteForReturn(destType);
+  }
+}
+
 bool InitializationSequence::Diagnose(Sema &S,
                                       const InitializedEntity &Entity,
                                       const InitializationKind &Kind,
@@ -5734,9 +5754,7 @@ bool InitializationSequence::Diagnose(Sema &S,
       << Args[0]->isLValue()
       << Args[0]->getType()
       << Args[0]->getSourceRange();
-    if (DestType.getNonReferenceType()->isObjCObjectPointerType() &&
-        Args[0]->getType()->isObjCObjectPointerType())
-      S.EmitRelatedResultTypeNote(Args[0]);
+    emitBadConversionNotes(S, Entity, Args[0]);
     break;
 
   case FK_ConversionFailed: {
@@ -5749,9 +5767,7 @@ bool InitializationSequence::Diagnose(Sema &S,
       << Args[0]->getSourceRange();
     S.HandleFunctionTypeMismatch(PDiag, FromType, DestType);
     S.Diag(Kind.getLocation(), PDiag);
-    if (DestType.getNonReferenceType()->isObjCObjectPointerType() &&
-        Args[0]->getType()->isObjCObjectPointerType())
-      S.EmitRelatedResultTypeNote(Args[0]);
+    emitBadConversionNotes(S, Entity, Args[0]);
     break;
   }
 
index b5b35fc48b765d9c0e08afe22edb53a92c50683d..0c51e4437173642f81694056f85cfc70aac41b08 100644 (file)
@@ -2536,20 +2536,7 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) {
     if (!FnRetType->isDependentType() && !RetValExp->isTypeDependent()) {
       // we have a non-void function with an expression, continue checking
 
-      if (!RelatedRetType.isNull()) {
-        // If we have a related result type, perform an extra conversion here.
-        // FIXME: The diagnostics here don't really describe what is happening.
-        InitializedEntity Entity =
-            InitializedEntity::InitializeTemporary(RelatedRetType);
-
-        ExprResult Res = PerformCopyInitialization(Entity, SourceLocation(),
-                                                   RetValExp);
-        if (Res.isInvalid()) {
-          // FIXME: Cleanup temporaries here, anyway?
-          return StmtError();
-        }
-        RetValExp = Res.takeAs<Expr>();
-      }
+      QualType RetType = (RelatedRetType.isNull() ? FnRetType : RelatedRetType);
 
       // C99 6.8.6.4p3(136): The return statement is not an assignment. The
       // overlap restriction of subclause 6.5.16.1 does not apply to the case of
@@ -2559,18 +2546,33 @@ Sema::ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) {
       // the C version of which boils down to CheckSingleAssignmentConstraints.
       NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, false);
       InitializedEntity Entity = InitializedEntity::InitializeResult(ReturnLoc,
-                                                                     FnRetType,
+                                                                     RetType,
                                                             NRVOCandidate != 0);
       ExprResult Res = PerformMoveOrCopyInitialization(Entity, NRVOCandidate,
-                                                       FnRetType, RetValExp);
+                                                       RetType, RetValExp);
       if (Res.isInvalid()) {
-        // FIXME: Cleanup temporaries here, anyway?
+        // FIXME: Clean up temporaries here anyway?
         return StmtError();
       }
-
       RetValExp = Res.takeAs<Expr>();
-      if (RetValExp)
-        CheckReturnStackAddr(RetValExp, FnRetType, ReturnLoc);
+
+      // If we have a related result type, we need to implicitly
+      // convert back to the formal result type.  We can't pretend to
+      // initialize the result again --- we might end double-retaining
+      // --- so instead we initialize a notional temporary; this can
+      // lead to less-than-great diagnostics, but this stage is much
+      // less likely to fail than the previous stage.
+      if (!RelatedRetType.isNull()) {
+        Entity = InitializedEntity::InitializeTemporary(FnRetType);
+        Res = PerformCopyInitialization(Entity, ReturnLoc, RetValExp);
+        if (Res.isInvalid()) {
+          // FIXME: Clean up temporaries here anyway?
+          return StmtError();
+        }
+        RetValExp = Res.takeAs<Expr>();
+      }
+
+      CheckReturnStackAddr(RetValExp, FnRetType, ReturnLoc);
     }
 
     if (RetValExp) {
index 0ef3d89510514eee7e2b6f5391aacfec69492009..3778625aa973411732ce82b46fa2fc9093d04508 100644 (file)
@@ -621,7 +621,9 @@ void test21(unsigned n) {
 // CHECK-NEXT: store i8* {{%.*}}, i8** [[CMD]]
 // CHECK-NEXT: [[T0:%.*]] = load [[TEST27]]** [[SELF]]
 // CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST27]]* [[T0]] to i8*
-// CHECK-NEXT: [[RET:%.*]] = call i8* @objc_retain(i8* [[T1]])
+// CHECK-NEXT: [[T2:%.*]] = call i8* @objc_retain(i8* [[T1]])
+// CHECK-NEXT: [[T3:%.*]] = bitcast i8* [[T2]] to [[TEST27]]*
+// CHECK-NEXT: [[RET:%.*]] = bitcast [[TEST27]]* [[T3]] to i8*
 // CHECK-NEXT: store i32 {{[0-9]+}}, i32* [[DEST]]
 // CHECK-NEXT: [[T0:%.*]] = load [[TEST27]]** [[SELF]]
 // CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST27]]* [[T0]] to i8*
@@ -685,7 +687,9 @@ static id _test29_allocator = 0;
 // Return statement.
 // CHECK-NEXT: [[T2:%.*]] = bitcast i8* [[CALL]]
 // CHECK-NEXT: [[CALL:%.*]] = bitcast
-// CHECK-NEXT: [[RET:%.*]] = call i8* @objc_retain(i8* [[CALL]]) [[NUW]]
+// CHECK-NEXT: [[T0:%.*]] = call i8* @objc_retain(i8* [[CALL]]) [[NUW]]
+// CHECK-NEXT: [[T1:%.*]] = bitcast i8* [[T0]] to [[TEST29]]*
+// CHECK-NEXT: [[RET:%.*]] = bitcast [[TEST29]]* [[T1]] to i8*
 // CHECK-NEXT: store i32 1, i32* [[CLEANUP]]
 
 // Cleanup.
@@ -739,7 +743,9 @@ static id _test29_allocator = 0;
 // Return statement.
 // CHECK-NEXT: [[T0:%.*]] = load [[TEST29]]** [[SELF]]
 // CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST29]]* [[T0]] to i8*
-// CHECK-NEXT: [[RET:%.*]] = call i8* @objc_retain(i8* [[T1]]) [[NUW]]
+// CHECK-NEXT: [[T0:%.*]] = call i8* @objc_retain(i8* [[T1]]) [[NUW]]
+// CHECK-NEXT: [[T1:%.*]] = bitcast i8* [[T0]] to [[TEST29]]*
+// CHECK-NEXT: [[RET:%.*]] = bitcast [[TEST29]]* [[T1]] to i8*
 // CHECK-NEXT: store i32 1, i32* [[CLEANUP]]
 
 // Cleanup.
@@ -794,7 +800,9 @@ char *helper;
 // Return.
 // CHECK-NEXT: [[T0:%.*]] = load [[TEST30]]** [[SELF]]
 // CHECK-NEXT: [[T1:%.*]] = bitcast [[TEST30]]* [[T0]] to i8*
-// CHECK-NEXT: [[RET:%.*]] = call i8* @objc_retain(i8* [[T1]])
+// CHECK-NEXT: [[T0:%.*]] = call i8* @objc_retain(i8* [[T1]])
+// CHECK-NEXT: [[T1:%.*]] = bitcast i8* [[T0]] to [[TEST30]]*
+// CHECK-NEXT: [[RET:%.*]] = bitcast [[TEST30]]* [[T1]] to i8*
 // CHECK-NEXT: store i32 1
 
 // Cleanup.
index 40f35d93b2bd41d27a519d81547a2f8a2635cd3d..8137964737e465af04742c044d63449bf0ab686b 100644 (file)
@@ -5,9 +5,9 @@
 #endif
 
 @interface Root
-+ (instancetype)alloc;
++ (instancetype)alloc; // expected-note {{explicitly declared 'instancetype'}}
 - (instancetype)init; // expected-note{{overridden method is part of the 'init' method family}}
-- (instancetype)self;
+- (instancetype)self; // expected-note {{explicitly declared 'instancetype'}}
 - (Class)class;
 
 @property (assign) Root *selfProp;
@@ -143,7 +143,7 @@ void test_instancetype_narrow_method_search() {
 
 @implementation Subclass4
 + (id)alloc {
-  return self; // expected-warning{{incompatible pointer types casting 'Class' to type 'Subclass4 *'}}
+  return self; // expected-warning{{incompatible pointer types returning 'Class' from a function with result type 'Subclass4 *'}}
 }
 
 - (Subclass3 *)init { return 0; } // don't complain: we lost the related return type
@@ -164,14 +164,14 @@ void test_instancetype_inherited() {
 // Check that related return types tighten up the semantics of
 // Objective-C method implementations.
 @implementation Subclass2
-- (instancetype)initSubclass2 {
+- (instancetype)initSubclass2 { // expected-note {{explicitly declared 'instancetype'}}
   Subclass1 *sc1 = [[Subclass1 alloc] init];
-  return sc1; // expected-warning{{incompatible pointer types casting 'Subclass1 *' to type 'Subclass2 *'}}
+  return sc1; // expected-warning{{incompatible pointer types returning 'Subclass1 *' from a function with result type 'Subclass2 *'}}
 }
 - (void)methodOnSubclass2 {}
 - (id)self {
   Subclass1 *sc1 = [[Subclass1 alloc] init];
-  return sc1; // expected-warning{{incompatible pointer types casting 'Subclass1 *' to type 'Subclass2 *'}}
+  return sc1; // expected-warning{{incompatible pointer types returning 'Subclass1 *' from a function with result type 'Subclass2 *'}}
 }
 @end
 
@@ -188,3 +188,29 @@ void test_instancetype_inherited() {
 
 @end
 
+// rdar://12493140
+@protocol P4
+- (instancetype) foo; // expected-note {{current method is explicitly declared 'instancetype' and is expected to return an instance of its class type}}
+@end
+@interface A4 : Root <P4>
+- (instancetype) bar; // expected-note {{current method is explicitly declared 'instancetype' and is expected to return an instance of its class type}}
+- (instancetype) baz; // expected-note {{overridden method returns an instance of its class type}} expected-note {{previous definition is here}}
+@end
+@interface B4 : Root @end
+
+@implementation A4 {
+  B4 *_b;
+}
+- (id) foo {
+  return _b; // expected-warning {{incompatible pointer types returning 'B4 *' from a function with result type 'A4 *'}}
+}
+- (id) bar {
+  return _b; // expected-warning {{incompatible pointer types returning 'B4 *' from a function with result type 'A4 *'}}
+}
+
+// This is really just to ensure that we don't crash.
+// FIXME: only one diagnostic, please
+- (float) baz { // expected-warning {{method is expected to return an instance of its class type 'A4', but is declared to return 'float'}} expected-warning {{conflicting return type in implementation}}
+  return 0;
+}
+@end
index b1d77dc1727747899f317d823b62539a0177ba39..50aaf2da4d9e025576782d79ebfdaffe8336e9fb 100644 (file)
@@ -175,7 +175,7 @@ void test_inference() {
 @implementation Fail
 - (id<X>) initWithX
 {
-  return (id)self; // expected-warning {{returning 'Fail *' from a function with incompatible result type 'id<X>'}}
+  return (id)self; // expected-warning {{casting 'Fail *' to incompatible type 'id<X>'}}
 }
 @end
 
diff --git a/test/SemaObjCXX/instancetype.mm b/test/SemaObjCXX/instancetype.mm
new file mode 100644 (file)
index 0000000..bbf100e
--- /dev/null
@@ -0,0 +1,216 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#if !__has_feature(objc_instancetype)
+# error Missing 'instancetype' feature macro.
+#endif
+
+@interface Root
++ (instancetype)alloc;
+- (instancetype)init; // expected-note{{overridden method is part of the 'init' method family}}
+- (instancetype)self; // expected-note {{explicitly declared 'instancetype'}}
+- (Class)class;
+
+@property (assign) Root *selfProp;
+- (instancetype)selfProp;
+@end
+
+@protocol Proto1
+@optional
+- (instancetype)methodInProto1;
+@end
+
+@protocol Proto2
+@optional
+- (instancetype)methodInProto2; // expected-note{{overridden method returns an instance of its class type}}
+- (instancetype)otherMethodInProto2; // expected-note{{overridden method returns an instance of its class type}}
+@end
+
+@interface Subclass1 : Root
+- (instancetype)initSubclass1;
+- (void)methodOnSubclass1;
++ (instancetype)allocSubclass1;
+@end
+
+@interface Subclass2 : Root
+- (instancetype)initSubclass2;
+- (void)methodOnSubclass2;
+@end
+
+// Sanity check: the basic initialization pattern.
+void test_instancetype_alloc_init_simple() {
+  Root *r1 = [[Root alloc] init];
+  Subclass1 *sc1 = [[Subclass1 alloc] init];
+}
+
+// Test that message sends to instancetype methods have the right type.
+void test_instancetype_narrow_method_search() {
+  // instancetype on class methods
+  Subclass1 *sc1 = [[Subclass1 alloc] initSubclass2]; // expected-warning{{'Subclass1' may not respond to 'initSubclass2'}}
+  Subclass2 *sc2 = [[Subclass2 alloc] initSubclass2]; // okay
+
+  // instancetype on instance methods
+  [[[Subclass1 alloc] init] methodOnSubclass2]; // expected-warning{{'Subclass1' may not respond to 'methodOnSubclass2'}}
+  [[[Subclass2 alloc] init] methodOnSubclass2];
+  
+  // instancetype on class methods using protocols
+  typedef Subclass1<Proto1> SC1Proto1;
+  typedef Subclass1<Proto2> SC1Proto2;
+  [[SC1Proto1 alloc] methodInProto2]; // expected-warning{{method '-methodInProto2' not found (return type defaults to 'id')}}
+  [[SC1Proto2 alloc] methodInProto2];
+
+  // instancetype on instance methods
+  Subclass1<Proto1> *sc1proto1 = 0;
+  [[sc1proto1 self] methodInProto2]; // expected-warning{{method '-methodInProto2' not found (return type defaults to 'id')}}
+  Subclass1<Proto2> *sc1proto2 = 0;
+  [[sc1proto2 self] methodInProto2];
+
+  // Exact type checks
+  typeof([[Subclass1 alloc] init]) *ptr1 = (Subclass1 **)0;
+  typeof([[Subclass2 alloc] init]) *ptr2 = (Subclass2 **)0;
+
+  // Message sends to Class.
+  Subclass1<Proto1> *sc1proto1_2 = [[[sc1proto1 class] alloc] init];
+
+  // Property access
+  [sc1proto1.self methodInProto2]; // expected-warning{{method '-methodInProto2' not found (return type defaults to 'id')}}
+  [sc1proto2.self methodInProto2];
+  [Subclass1.alloc initSubclass2]; // expected-warning{{'Subclass1' may not respond to 'initSubclass2'}}
+  [Subclass2.alloc initSubclass2];
+
+  [sc1proto1.selfProp methodInProto2]; // expected-warning{{method '-methodInProto2' not found (return type defaults to 'id')}}
+  [sc1proto2.selfProp methodInProto2];
+}
+
+// Test that message sends to super methods have the right type.
+@interface Subsubclass1 : Subclass1
+- (instancetype)initSubclass1;
++ (instancetype)allocSubclass1;
+
+- (void)onlyInSubsubclass1;
+@end
+
+@implementation Subsubclass1
+- (instancetype)initSubclass1 {
+  // Check based on method search.
+  [[super initSubclass1] methodOnSubclass2]; // expected-warning{{'Subsubclass1' may not respond to 'methodOnSubclass2'}}
+  [super.initSubclass1 methodOnSubclass2]; // expected-warning{{'Subsubclass1' may not respond to 'methodOnSubclass2'}}
+
+  self = [super init]; // common pattern
+
+  // Exact type check.
+  typeof([super initSubclass1]) *ptr1 = (Subsubclass1**)0;
+
+  return self;
+}
+
++ (instancetype)allocSubclass1 {
+  // Check based on method search.
+  [[super allocSubclass1] methodOnSubclass2]; // expected-warning{{'Subsubclass1' may not respond to 'methodOnSubclass2'}}
+
+  // The ASTs don't model super property accesses well enough to get this right
+  [super.allocSubclass1 methodOnSubclass2]; // expected-warning{{'Subsubclass1' may not respond to 'methodOnSubclass2'}}
+
+  // Exact type check.
+  typeof([super allocSubclass1]) *ptr1 = (Subsubclass1**)0;
+  
+  return [super allocSubclass1];
+}
+
+- (void)onlyInSubsubclass1 {}
+@end
+
+// Check compatibility rules for inheritance of related return types.
+@class Subclass4;
+
+@interface Subclass3 <Proto1, Proto2>
+- (Subclass3 *)methodInProto1;
+- (Subclass4 *)methodInProto2; // expected-warning{{method is expected to return an instance of its class type 'Subclass3', but is declared to return 'Subclass4 *'}}
+@end
+
+@interface Subclass4 : Root
++ (Subclass4 *)alloc; // okay
+- (Subclass3 *)init; // expected-warning{{method is expected to return an instance of its class type 'Subclass4', but is declared to return 'Subclass3 *'}}
+- (id)self; // expected-note{{overridden method is part of the 'self' method family}}
+- (instancetype)initOther;
+@end
+
+@protocol Proto3 <Proto1, Proto2>
+@optional
+- (id)methodInProto1;
+- (Subclass1 *)methodInProto2;
+- (int)otherMethodInProto2; // expected-warning{{protocol method is expected to return an instance of the implementing class, but is declared to return 'int'}}
+@end
+
+@implementation Subclass4
++ (id)alloc {
+  return self; // FIXME: we accept this in ObjC++ but not ObjC?
+}
+
+- (Subclass3 *)init { return 0; } // don't complain: we lost the related return type
+
+- (Subclass3 *)self { return 0; } // expected-warning{{method is expected to return an instance of its class type 'Subclass4', but is declared to return 'Subclass3 *'}}
+
+- (Subclass4 *)initOther { return 0; }
+
+@end
+
+// Check that inherited related return types influence the types of
+// message sends.
+void test_instancetype_inherited() {
+  [[Subclass4 alloc] initSubclass1]; // expected-warning{{'Subclass4' may not respond to 'initSubclass1'}}
+  [[Subclass4 alloc] initOther];
+}
+
+// Check that related return types tighten up the semantics of
+// Objective-C method implementations.
+@implementation Subclass2
+- (instancetype)initSubclass2 { // expected-note {{explicitly declared 'instancetype'}}
+  Subclass1 *sc1 = [[Subclass1 alloc] init];
+  return sc1; // expected-error{{cannot initialize return object of type 'Subclass2 *' with an lvalue of type 'Subclass1 *'}}
+}
+- (void)methodOnSubclass2 {}
+- (id)self {
+  Subclass1 *sc1 = [[Subclass1 alloc] init];
+  return sc1; // expected-error{{cannot initialize return object of type 'Subclass2 *' with an lvalue of type 'Subclass1 *'}}
+}
+@end
+
+@interface MyClass : Root
++ (int)myClassMethod;
+@end
+
+@implementation MyClass
++ (int)myClassMethod { return 0; }
+
+- (void)blah {
+  int i = [[MyClass self] myClassMethod];
+}
+
+@end
+
+// rdar://12493140
+@protocol P4
+- (instancetype) foo; // expected-note {{current method is explicitly declared 'instancetype' and is expected to return an instance of its class type}}
+@end
+@interface A4 : Root <P4>
+- (instancetype) bar; // expected-note {{current method is explicitly declared 'instancetype' and is expected to return an instance of its class type}}
+- (instancetype) baz; // expected-note {{overridden method returns an instance of its class type}} expected-note {{previous definition is here}}
+@end
+@interface B4 : Root @end
+
+@implementation A4 {
+  B4 *_b;
+}
+- (id) foo {
+  return _b; // expected-error {{cannot initialize return object of type 'A4 *' with an lvalue of type 'B4 *'}}
+}
+- (id) bar {
+  return _b; // expected-error {{cannot initialize return object of type 'A4 *' with an lvalue of type 'B4 *'}}
+}
+
+// This is really just to ensure that we don't crash.
+// FIXME: only one diagnostic, please
+- (float) baz { // expected-warning {{method is expected to return an instance of its class type 'A4', but is declared to return 'float'}} expected-warning {{conflicting return type in implementation}}
+  return 0;
+}
+@end