Skip to content

Avoid invalidating @_objcImplementation #76270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,9 @@ class DeclAttribute : public AttributeBase {
isUnchecked : 1
);

SWIFT_INLINE_BITFIELD(ObjCImplementationAttr, DeclAttribute, 2,
SWIFT_INLINE_BITFIELD(ObjCImplementationAttr, DeclAttribute, 3,
isCategoryNameInvalid : 1,
hasInvalidImplicitLangAttrs : 1,
isEarlyAdopter : 1
);

Expand Down Expand Up @@ -2434,6 +2435,7 @@ class ObjCImplementationAttr final : public DeclAttribute {
: DeclAttribute(DeclAttrKind::ObjCImplementation, AtLoc, Range, Implicit),
CategoryName(CategoryName) {
Bits.ObjCImplementationAttr.isCategoryNameInvalid = isCategoryNameInvalid;
Bits.ObjCImplementationAttr.hasInvalidImplicitLangAttrs = false;
Bits.ObjCImplementationAttr.isEarlyAdopter = isEarlyAdopter;
}

Expand All @@ -2451,6 +2453,16 @@ class ObjCImplementationAttr final : public DeclAttribute {
Bits.ObjCImplementationAttr.isCategoryNameInvalid = newValue;
}

/// Has at least one implicitly ObjC member failed to validate? If so,
/// diagnostics that might be duplicative will be suppressed.
bool hasInvalidImplicitLangAttrs() const {
return Bits.ObjCImplementationAttr.hasInvalidImplicitLangAttrs;
}

void setHasInvalidImplicitLangAttrs(bool newValue = true) {
Bits.ObjCImplementationAttr.hasInvalidImplicitLangAttrs = newValue;
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::ObjCImplementation;
}
Expand Down
40 changes: 28 additions & 12 deletions lib/Sema/TypeCheckDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ void ObjCReason::describe(const Decl *D) const {
}

void ObjCReason::setAttrInvalid() const {
if (requiresAttr(kind))
if (!requiresAttr(kind))
return;

if (kind == MemberOfObjCImplementationExtension)
cast<ObjCImplementationAttr>(getAttr())->setHasInvalidImplicitLangAttrs();
else
getAttr()->setInvalid();
}

Expand Down Expand Up @@ -3074,6 +3079,17 @@ class ObjCImplementationChecker {

bool hasDiagnosed = false;

bool hasInvalidLangAttr(ValueDecl *cand) {
// If isObjC() found a problem, it will have set the invalid bit on either
// the candidate's ObjCAttr, if it has one, or the controlling
// ObjCImplementationAttr otherwise.
if (auto objc = cand->getAttrs()
.getAttribute<ObjCAttr>(/*AllowInvalid=*/true))
return objc->isInvalid();

return getAttr()->hasInvalidImplicitLangAttrs() || getAttr()->isInvalid();
}

public:
ObjCImplementationChecker(Decl *D)
: decl(D), hasDiagnosed(getAttr()->isInvalid())
Expand Down Expand Up @@ -3158,18 +3174,11 @@ class ObjCImplementationChecker {
return;

// Don't diagnose if we already diagnosed an unrelated ObjC interop issue,
// like an un-representable type. If there's an `@objc` attribute on the
// member, this will be indicated by its `isInvalid()` bit; otherwise we'll
// use the enclosing extension's `@_objcImplementation` attribute.
DeclAttribute *attr = afd->getAttrs()
.getAttribute<ObjCAttr>(/*AllowInvalid=*/true);
if (!attr)
attr = member->getDeclContext()->getAsDecl()->getAttrs()
.getAttribute<ObjCImplementationAttr>(/*AllowInvalid=*/true);
assert(attr && "expected @_objcImplementation on context of member checked "
"by ObjCImplementationChecker");
if (attr->isInvalid())
// like an un-representable type.
if (hasInvalidLangAttr(member)) {
hasDiagnosed = true;
return;
}

if (auto init = dyn_cast<ConstructorDecl>(afd)) {
if (!init->isObjC() && (init->isRequired() ||
Expand Down Expand Up @@ -3651,6 +3660,13 @@ class ObjCImplementationChecker {

void diagnoseOutcome(MatchOutcome outcome, ValueDecl *req, ValueDecl *cand,
ObjCSelector explicitObjCName) {
// If the candidate was invalid, we've already diagnosed the likely cause of
// the mismatch. Don't dogpile.
if (hasInvalidLangAttr(cand)) {
hasDiagnosed = true;
return;
}

auto reqObjCName = getObjCName(req);

switch (outcome) {
Expand Down
4 changes: 4 additions & 0 deletions test/Serialization/Inputs/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ module CLibrary {
module RawLayoutCXX {
header "raw_layout_cxx.h"
}

module objc_implementation {
header "objc_implementation.h"
}
11 changes: 11 additions & 0 deletions test/Serialization/Inputs/objc_implementation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ObjCImpl : NSObject

- (void)goodMethod;

@end

NS_ASSUME_NONNULL_END
25 changes: 25 additions & 0 deletions test/Serialization/objc_implementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %empty-directory(%t)
// RUN: %target-typecheck-verify-swift -target %target-stable-abi-triple -module-name main -I %S/Inputs %s
// RUN: %target-swift-frontend -emit-module -target %target-stable-abi-triple -module-name main -o %t -I %S/Inputs %s
// RUN: llvm-bcanalyzer %t/main.swiftmodule > %t/main.swiftmodule.txt
// RUN: %target-swift-ide-test -print-module -target %target-stable-abi-triple -module-to-print=main -I %t -source-filename=%s >%t/main.txt
// RUN: %FileCheck %s --input-file=%t/main.txt

// REQUIRES: objc_interop

import Foundation
import objc_implementation

// rdar://134730183 - ensure that errors reduced to warnings by early adopter
// syntax don't invalidate the @implementation attribute (and cause it to not
// be serialized)

// CHECK-LABEL: @_objcImplementation extension ObjCImpl
@_objcImplementation extension ObjCImpl {
// CHECK-DAG: func cannotBeObjCMethod(_ value: Int?)
private func cannotBeObjCMethod(_ value: Int?) {}
// expected-warning@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}

// CHECK-DAG: @objc func goodMethod()
@objc public func goodMethod() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker, but it would be nice if this test also demonstrated end-to-end that a client can also deserialize and use the emitted module here, since that's where the failure manifested IIUC.

}
11 changes: 11 additions & 0 deletions test/decl/ext/Inputs/objc_implementation.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@

@end

@interface ObjCClass (InvalidMembers)

- (void)unimplementedMember;
- (void)nonObjCMethod:(id)value;

@end

@interface ObjCClass (EmptyCategory)
@end

Expand Down Expand Up @@ -162,6 +169,10 @@
- (nullable id)nullableResult;
- (void)nullableArgument:(nullable id)arg;

@end

@interface ObjCClass (TypeMatchOptionalityInvalid)

- (int)nonPointerResult;
- (void)nonPointerArgument:(int)arg;

Expand Down
21 changes: 16 additions & 5 deletions test/decl/ext/objc_implementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@ protocol EmptySwiftProto {}
// rdar://122280735 - crash when the parameter of a block property needs @escaping
let rdar122280735: (() -> ()) -> Void = { _ in }
// expected-error@-1 {{property 'rdar122280735' of type '(() -> ()) -> Void' does not match type '(@escaping () -> Void) -> Void' declared by the header}}

private func privateNonObjCMethod(_: EmptySwiftProto) {
// expected-error@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}
}

@objc(PresentAdditions) @implementation extension ObjCClass {
Expand Down Expand Up @@ -432,11 +427,27 @@ protocol EmptySwiftProto {}

func nullableResult() -> Any { fatalError() } // expected-error {{instance method 'nullableResult()' of type '() -> Any' does not match type '() -> Any?' declared by the header}}
func nullableArgument(_: Any) {} // expected-error {{instance method 'nullableArgument' of type '(Any) -> ()' does not match type '(Any?) -> Void' declared by the header}}
}

@objc(TypeMatchOptionalityInvalid) @implementation extension ObjCClass {
func nonPointerResult() -> CInt! { fatalError() } // expected-error{{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because its result type cannot be represented in Objective-C}}
func nonPointerArgument(_: CInt!) {} // expected-error {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
}

@objc(InvalidMembers) @implementation extension ObjCClass {
// expected-error@-1 {{extension for category 'InvalidMembers' should provide implementation for instance method 'unimplementedMember()'}}

func nonObjCMethod(_: EmptySwiftProto) {
// expected-error@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}

private func privateNonObjCMethod(_: EmptySwiftProto) {
// expected-error@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}
}

// Intentionally using `@_objcImplementation` for this test; do not upgrade!
@_objcImplementation(EmptyCategory) extension ObjCClass {
// expected-warning@-1 {{'@_objcImplementation' is deprecated; use '@implementation' instead}} {{1-36=@implementation}} {{1-1=@objc(EmptyCategory) }}
Expand Down
21 changes: 16 additions & 5 deletions test/decl/ext/objc_implementation_early_adopter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@ protocol EmptySwiftProto {}
// rdar://122280735 - crash when the parameter of a block property needs @escaping
let rdar122280735: (() -> ()) -> Void = { _ in }
// expected-warning@-1 {{property 'rdar122280735' of type '(() -> ()) -> Void' does not match type '(@escaping () -> Void) -> Void' declared by the header}}

private func privateNonObjCMethod(_: EmptySwiftProto) {
// expected-warning@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}
}

@_objcImplementation(PresentAdditions) extension ObjCClass {
Expand Down Expand Up @@ -432,11 +427,27 @@ protocol EmptySwiftProto {}

func nullableResult() -> Any { fatalError() } // expected-warning {{instance method 'nullableResult()' of type '() -> Any' does not match type '() -> Any?' declared by the header}}
func nullableArgument(_: Any) {} // expected-warning {{instance method 'nullableArgument' of type '(Any) -> ()' does not match type '(Any?) -> Void' declared by the header}}
}

@_objcImplementation(TypeMatchOptionalityInvalid) extension ObjCClass {
func nonPointerResult() -> CInt! { fatalError() } // expected-warning{{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because its result type cannot be represented in Objective-C}}
func nonPointerArgument(_: CInt!) {} // expected-warning {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
}

@_objcImplementation(InvalidMembers) extension ObjCClass {
// expected-warning@-1 {{extension for category 'InvalidMembers' should provide implementation for instance method 'unimplementedMember()'}}

func nonObjCMethod(_: EmptySwiftProto) {
// expected-warning@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}

private func privateNonObjCMethod(_: EmptySwiftProto) {
// expected-warning@-1 {{method cannot be in an @objc @implementation extension of a class (without final or @nonobjc) because the type of the parameter cannot be represented in Objective-C}}
// expected-note@-2 {{protocol-constrained type containing protocol 'EmptySwiftProto' cannot be represented in Objective-C}}
}
}

// Intentionally using `@_objcImplementation` for this test; do not upgrade!
@_objcImplementation(EmptyCategory) extension ObjCClass {
// expected-warning@-1 {{'@_objcImplementation' is deprecated; use '@implementation' instead}} {{1-36=@implementation}} {{1-1=@objc(EmptyCategory) }}
Expand Down