Skip to content

Commit

Permalink
Sema: Diagnose unsupported '@objc' on classes and members of extensio…
Browse files Browse the repository at this point in the history
…ns of classes with resilient ancestry

Unless -enable-resilient-objc-class-stubs is passed in, these cases
are not supported, so now we diagnose them instead of asserting or
failing to link.

Note the behavior change here; classes with resilient ancestry were
previously isObjC(). However this is wrong since isObjC() means
"statically visible to Objective-C via the generated header".

After this patch, isObjC() only returns true for a class with resilient
ancestry if -enable-resilient-objc-class-stubs is passed in.
  • Loading branch information
slavapestov committed Mar 26, 2019
1 parent 68c0762 commit 8ecb83e
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 9 deletions.
12 changes: 9 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3592,9 +3592,11 @@ ERROR(invalid_nonobjc_extension,none,
ERROR(objc_in_extension_context,none,
"members of constrained extensions cannot be declared @objc", ())
ERROR(objc_in_generic_extension,none,
"members of extensions of "
"%select{classes from generic context|generic classes}0 "
"cannot be declared @objc", (bool))
"extensions of %select{classes from generic context|generic classes}0 "
"cannot contain '@objc' members", (bool))
ERROR(objc_in_resilient_extension,none,
"extensions of classes built with library evolution support "
"cannot contain '@objc' members", ())
ERROR(objc_operator, none,
"operator methods cannot be declared @objc", ())
ERROR(objc_operator_proto, none,
Expand All @@ -3615,6 +3617,10 @@ NOTE(objc_inference_swift3_addnonobjc,none,
ERROR(objc_for_generic_class,none,
"generic subclasses of '@objc' classes cannot have an explicit '@objc' "
"because they are not directly visible from Objective-C", ())
ERROR(objc_for_resilient_class,none,
"classes built with library evolution support cannot have explicit "
"'@objc' subclasses because they are not directly "
"visible from Objective-C", ())
ERROR(objc_getter_for_nonobjc_property,none,
"'@objc' getter for non-'@objc' property", ())
ERROR(objc_getter_for_nonobjc_subscript,none,
Expand Down
41 changes: 39 additions & 2 deletions lib/Sema/TypeCheckDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,21 @@ static bool checkObjCInExtensionContext(const ValueDecl *value,
}

if (auto classDecl = ED->getSelfClassDecl()) {
auto *mod = value->getModuleContext();
auto &ctx = mod->getASTContext();

if (!ctx.LangOpts.EnableObjCResilientClassStubs) {
if (classDecl->checkAncestry().contains(
AncestryFlags::ResilientOther) ||
classDecl->hasResilientMetadata(mod,
ResilienceExpansion::Maximal)) {
if (diagnose) {
value->diagnose(diag::objc_in_resilient_extension);
}
return true;
}
}

if (classDecl->isGenericContext()) {
if (!classDecl->usesObjCGenericsModel()) {
if (diagnose) {
Expand Down Expand Up @@ -1016,6 +1031,20 @@ static Optional<ObjCReason> shouldMarkClassAsObjC(const ClassDecl *CD) {
.fixItRemove(attr->getRangeWithAt());
}

// If the class has resilient ancestry, @objc just controls the runtime
// name unless -enable-resilient-objc-class-stubs is enabled.
if (ancestry.contains(AncestryFlags::ResilientOther) &&
!ctx.LangOpts.EnableObjCResilientClassStubs) {
if (attr->hasName()) {
const_cast<ClassDecl *>(CD)->getAttrs().add(
new (ctx) ObjCRuntimeNameAttr(*attr));
return None;
}

ctx.Diags.diagnose(attr->getLocation(), diag::objc_for_resilient_class)
.fixItRemove(attr->getRangeWithAt());
}

// Only allow ObjC-rooted classes to be @objc.
// (Leave a hole for test cases.)
if (ancestry.contains(AncestryFlags::ObjC) &&
Expand All @@ -1032,8 +1061,16 @@ static Optional<ObjCReason> shouldMarkClassAsObjC(const ClassDecl *CD) {
return ObjCReason(ObjCReason::ExplicitlyObjC);
}

if (ancestry.contains(AncestryFlags::ObjC) &&
!ancestry.contains(AncestryFlags::Generic)) {
if (ancestry.contains(AncestryFlags::ObjC)) {
if (ancestry.contains(AncestryFlags::Generic)) {
return None;
}

if (ancestry.contains(AncestryFlags::ResilientOther) &&
!ctx.LangOpts.EnableObjCResilientClassStubs) {
return None;
}

return ObjCReason(ObjCReason::ImplicitlyObjC);
}

Expand Down
4 changes: 2 additions & 2 deletions test/attr/attr_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,9 @@ extension subject_genericClass where T : Hashable {
}

extension subject_genericClass {
@objc var extProp: Int { return 0 } // expected-error{{members of extensions of generic classes cannot be declared @objc}}
@objc var extProp: Int { return 0 } // expected-error{{extensions of generic classes cannot contain '@objc' members}}

@objc func extFoo() {} // expected-error{{members of extensions of generic classes cannot be declared @objc}}
@objc func extFoo() {} // expected-error{{extensions of generic classes cannot contain '@objc' members}}
}

@objc
Expand Down
44 changes: 44 additions & 0 deletions test/attr/attr_objc_resilience.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module-path %t/resilient_struct.swiftmodule %S/../Inputs/resilient_struct.swift -enable-library-evolution
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module-path %t/resilient_objc_class.swiftmodule %S/../Inputs/resilient_objc_class.swift -I %t -enable-library-evolution
// RUN: %target-swift-frontend -typecheck -verify %s -I %t

// REQUIRES: objc_interop

import Foundation
import resilient_objc_class

@objc public class ResilientNSObjectSubclass : ResilientNSObjectOutsideParent {}
// expected-error@-1 {{classes built with library evolution support cannot have explicit '@objc' subclasses because they are not directly visible from Objective-C}}

public class AnotherResilientNSObjectSubclass : ResilientNSObjectOutsideParent {}

extension ResilientNSObjectOutsideParent {
@objc public func categoryOneMethod() {}
// expected-error@-1 {{extensions of classes built with library evolution support cannot contain '@objc' members}}
}

extension AnotherResilientNSObjectSubclass {
@objc public func categoryTwoMethod() {}
// expected-error@-1 {{extensions of classes built with library evolution support cannot contain '@objc' members}}
}

// Note: @_fixed_layout on a class only applies to the storage layout and
// not metadata, which remains resilient.

@_fixed_layout
@objc public class FixedLayoutNSObjectSubclass : FixedLayoutNSObjectOutsideParent {}
// expected-error@-1 {{classes built with library evolution support cannot have explicit '@objc' subclasses because they are not directly visible from Objective-C}}

@_fixed_layout
public class AnotherFixedLayoutNSObjectSubclass : FixedLayoutNSObjectOutsideParent {}

extension FixedLayoutNSObjectOutsideParent {
@objc public func categoryOneMethod() {}
// expected-error@-1 {{extensions of classes built with library evolution support cannot contain '@objc' members}}
}

extension AnotherFixedLayoutNSObjectSubclass {
@objc public func categoryTwoMethod() {}
// expected-error@-1 {{extensions of classes built with library evolution support cannot contain '@objc' members}}
}
41 changes: 41 additions & 0 deletions test/attr/attr_objc_resilient_stubs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module-path %t/resilient_struct.swiftmodule %S/../Inputs/resilient_struct.swift -enable-library-evolution
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module-path %t/resilient_objc_class.swiftmodule %S/../Inputs/resilient_objc_class.swift -I %t -enable-library-evolution -enable-resilient-objc-class-stubs
// RUN: %target-swift-frontend -typecheck -verify %s -I %t -enable-resilient-objc-class-stubs

// REQUIRES: objc_interop

import Foundation
import resilient_objc_class

// When built with -enable-resilient-objc-class-stubs, all of these cases are
// allowed.

@objc public class ResilientNSObjectSubclass : ResilientNSObjectOutsideParent {}

public class AnotherResilientNSObjectSubclass : ResilientNSObjectOutsideParent {}

extension ResilientNSObjectOutsideParent {
@objc public func categoryOneMethod() {}
}

extension AnotherResilientNSObjectSubclass {
@objc public func categoryTwoMethod() {}
}

// Note: @_fixed_layout on a class only applies to the storage layout and
// not metadata, which remains resilient.

@_fixed_layout
@objc public class FixedLayoutNSObjectSubclass : FixedLayoutNSObjectOutsideParent {}

@_fixed_layout
public class AnotherFixedLayoutNSObjectSubclass : FixedLayoutNSObjectOutsideParent {}

extension FixedLayoutNSObjectOutsideParent {
@objc public func categoryOneMethod() {}
}

extension AnotherFixedLayoutNSObjectSubclass {
@objc public func categoryTwoMethod() {}
}
4 changes: 2 additions & 2 deletions test/decl/ext/extension-generic-objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class A<T> : NSObject {
}
extension A {
// This should throw an error
@objc func a1() {} // expected-error{{members of extensions of generic classes cannot be declared @objc}}
@objc func a1() {} // expected-error{{extensions of generic classes cannot contain '@objc' members}}
// This should *not* throw an error
func a2() {}
}
Expand Down Expand Up @@ -51,6 +51,6 @@ class Outer<T> {

extension Outer.Inner {
@objc func outerInner1() {}
// expected-error@-1{{members of extensions of classes from generic context cannot be declared @objc}}
// expected-error@-1{{extensions of classes from generic context cannot contain '@objc' members}}
func outerInner2() {}
}

0 comments on commit 8ecb83e

Please sign in to comment.