Skip to content

Commit b47f833

Browse files
authored
Merge pull request #6866 from jckarter/warn-on-plus-initialize-method-3.1
[3.1] Sema: Warn when Swift classes attempt to implement ObjC +initialize.
2 parents 490b9fd + 2c621ac commit b47f833

File tree

6 files changed

+68
-10
lines changed

6 files changed

+68
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ CHANGELOG
2020
Swift 3.1
2121
---------
2222

23+
* Swift will now warn when an `NSObject` subclass attempts to override the
24+
class `initialize` method. Swift doesn't guarantee that references to class
25+
names trigger Objective-C class realization if they have no other
26+
side effects, leading to bugs when Swift code attempted to override
27+
`initialize`.
28+
2329
* [SR-2394](https://bugs.swift.org/browse/SR-2394)
2430

2531
C functions that "return twice" are no longer imported into Swift. Instead,

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3143,6 +3143,11 @@ ERROR(objc_class_method_not_permitted,none,
31433143
OBJC_DIAG_SELECT " defines Objective-C class method %2, which is "
31443144
"not permitted by Swift", (unsigned, DeclName, ObjCSelector))
31453145

3146+
WARNING(objc_class_method_not_permitted_swift3_compat_warning,none,
3147+
OBJC_DIAG_SELECT " defines Objective-C class method %2, which is "
3148+
"not guaranteed to be invoked by Swift and will be disallowed in future "
3149+
"versions", (unsigned, DeclName, ObjCSelector))
3150+
31463151
ERROR(objc_witness_selector_mismatch,none,
31473152
"Objective-C method %2 provided by " OBJC_DIAG_SELECT
31483153
" does not match the requirement's selector (%3)",

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ IDENTIFIER(Foundation)
4343
IDENTIFIER(fromRaw)
4444
IDENTIFIER(hashValue)
4545
IDENTIFIER(init)
46+
IDENTIFIER(initialize)
4647
IDENTIFIER(initStorage)
4748
IDENTIFIER(initialValue)
4849
IDENTIFIER(Key)

lib/Sema/TypeCheckDecl.cpp

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,21 +2597,36 @@ void swift::markAsObjC(TypeChecker &TC, ValueDecl *D,
25972597
// Swift does not permit class methods with Objective-C selectors 'load',
25982598
// 'alloc', or 'allocWithZone:'.
25992599
if (!method->isInstanceMember()) {
2600-
auto isForbiddenSelector = [&TC](ObjCSelector sel) {
2600+
auto isForbiddenSelector = [&TC](ObjCSelector sel)
2601+
-> Optional<Diag<unsigned, DeclName, ObjCSelector>> {
26012602
switch (sel.getNumArgs()) {
26022603
case 0:
2603-
return sel.getSelectorPieces().front() == TC.Context.Id_load ||
2604-
sel.getSelectorPieces().front() == TC.Context.Id_alloc;
2604+
if (sel.getSelectorPieces().front() == TC.Context.Id_load ||
2605+
sel.getSelectorPieces().front() == TC.Context.Id_alloc)
2606+
return diag::objc_class_method_not_permitted;
2607+
// Swift 3 and earlier allowed you to override `initialize`, but
2608+
// Swift's semantics do not guarantee that it will be called at
2609+
// the point you expect. It is disallowed in Swift 4 and later.
2610+
if (sel.getSelectorPieces().front() == TC.Context.Id_initialize) {
2611+
if (TC.getLangOpts().isSwiftVersion3())
2612+
return
2613+
diag::objc_class_method_not_permitted_swift3_compat_warning;
2614+
else
2615+
return diag::objc_class_method_not_permitted;
2616+
}
2617+
return None;
26052618
case 1:
2606-
return sel.getSelectorPieces().front()==TC.Context.Id_allocWithZone;
2619+
if (sel.getSelectorPieces().front() == TC.Context.Id_allocWithZone)
2620+
return diag::objc_class_method_not_permitted;
2621+
return None;
26072622
default:
2608-
return false;
2623+
return None;
26092624
}
26102625
};
26112626
auto sel = method->getObjCSelector(&TC);
2612-
if (isForbiddenSelector(sel)) {
2627+
if (auto diagID = isForbiddenSelector(sel)) {
26132628
auto diagInfo = getObjCMethodDiagInfo(method);
2614-
TC.diagnose(method, diag::objc_class_method_not_permitted,
2629+
TC.diagnose(method, *diagID,
26152630
diagInfo.first, diagInfo.second, sel);
26162631
}
26172632
}

test/Compatibility/attr_objc.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -typecheck -verify %s -swift-version 3
2+
3+
class Load1 {
4+
class func load() { }
5+
class func alloc() {}
6+
class func allocWithZone(_: Int) {}
7+
class func initialize() {}
8+
}
9+
10+
@objc class Load2 {
11+
class func load() { } // expected-error{{method 'load()' defines Objective-C class method 'load', which is not permitted by Swift}}
12+
class func alloc() {} // expected-error{{method 'alloc()' defines Objective-C class method 'alloc', which is not permitted by Swift}}
13+
class func allocWithZone(_: Int) {} // expected-error{{method 'allocWithZone' defines Objective-C class method 'allocWithZone:', which is not permitted by Swift}}
14+
class func initialize() {} // expected-warning{{method 'initialize()' defines Objective-C class method 'initialize', which is not guaranteed to be invoked by Swift and will be disallowed in future versions}}
15+
}
16+
17+
@objc class Load3 {
18+
class var load: Load3 {
19+
get { return Load3() } // expected-error{{getter for 'load' defines Objective-C class method 'load', which is not permitted by Swift}}
20+
set { }
21+
}
22+
23+
@objc(alloc) class var prop: Int { return 0 } // expected-error{{getter for 'prop' defines Objective-C class method 'alloc', which is not permitted by Swift}}
24+
@objc(allocWithZone:) class func fooWithZone(_: Int) {} // expected-error{{method 'fooWithZone' defines Objective-C class method 'allocWithZone:', which is not permitted by Swift}}
25+
@objc(initialize) class func barnitialize() {} // expected-warning{{method 'barnitialize()' defines Objective-C class method 'initialize', which is not guaranteed to be invoked by Swift and will be disallowed in future versions}}
26+
}
27+
28+

test/attr/attr_objc.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// RUN: %target-typecheck-verify-swift
2-
// RUN: %target-swift-ide-test -skip-deinit=false -print-ast-typechecked -source-filename %s -function-definitions=true -prefer-type-repr=false -print-implicit-attrs=true -explode-pattern-binding-decls=true -disable-objc-attr-requires-foundation-module | %FileCheck %s
3-
// RUN: not %target-swift-frontend -typecheck -dump-ast -disable-objc-attr-requires-foundation-module %s 2> %t.dump
1+
// RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -typecheck -verify %s -swift-version 4
2+
// RUN: %target-swift-ide-test -skip-deinit=false -print-ast-typechecked -source-filename %s -function-definitions=true -prefer-type-repr=false -print-implicit-attrs=true -explode-pattern-binding-decls=true -disable-objc-attr-requires-foundation-module -swift-version 4 | %FileCheck %s
3+
// RUN: not %target-swift-frontend -typecheck -dump-ast -disable-objc-attr-requires-foundation-module %s -swift-version 4 2> %t.dump
44
// RUN: %FileCheck -check-prefix CHECK-DUMP %s < %t.dump
55
// REQUIRES: objc_interop
66

@@ -1901,12 +1901,14 @@ class Load1 {
19011901
class func load() { }
19021902
class func alloc() {}
19031903
class func allocWithZone(_: Int) {}
1904+
class func initialize() {}
19041905
}
19051906

19061907
@objc class Load2 {
19071908
class func load() { } // expected-error{{method 'load()' defines Objective-C class method 'load', which is not permitted by Swift}}
19081909
class func alloc() {} // expected-error{{method 'alloc()' defines Objective-C class method 'alloc', which is not permitted by Swift}}
19091910
class func allocWithZone(_: Int) {} // expected-error{{method 'allocWithZone' defines Objective-C class method 'allocWithZone:', which is not permitted by Swift}}
1911+
class func initialize() {} // expected-error{{method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift}}
19101912
}
19111913

19121914
@objc class Load3 {
@@ -1917,6 +1919,7 @@ class Load1 {
19171919

19181920
@objc(alloc) class var prop: Int { return 0 } // expected-error{{getter for 'prop' defines Objective-C class method 'alloc', which is not permitted by Swift}}
19191921
@objc(allocWithZone:) class func fooWithZone(_: Int) {} // expected-error{{method 'fooWithZone' defines Objective-C class method 'allocWithZone:', which is not permitted by Swift}}
1922+
@objc(initialize) class func barnitialize() {} // expected-error{{method 'barnitialize()' defines Objective-C class method 'initialize', which is not permitted by Swift}}
19201923
}
19211924

19221925
// Members of protocol extensions cannot be @objc

0 commit comments

Comments
 (0)