Skip to content

Commit 844ba5f

Browse files
committed
[Embedded] Diagnose non-final generic methods in class in the type checker
Move the diagnostic about non-final generic methods in classes up to the type checker, so that it is available to `-Wwarning EmbeddedRestrictions` and earlier in the pipeline. The SIL version of this is still available as a backstop. Yet another part of rdar://133874555.
1 parent c8e6bfd commit 844ba5f

File tree

6 files changed

+117
-9
lines changed

6 files changed

+117
-9
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8612,6 +8612,9 @@ GROUPED_ERROR(weak_unowned_in_embedded_swift, EmbeddedRestrictions, none,
86128612
GROUPED_WARNING(untyped_throws_in_embedded_swift, EmbeddedRestrictions,
86138613
DefaultIgnore,
86148614
"untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'", ())
8615+
GROUPED_ERROR(generic_nonfinal_in_embedded_swift, EmbeddedRestrictions, none,
8616+
"generic %kind0 in a class %select{must be 'final'|cannot be 'required'}1 in Embedded Swift",
8617+
(const Decl *, bool))
86158618

86168619
//===----------------------------------------------------------------------===//
86178620
// MARK: @abi Attribute

lib/Sema/TypeCheckEmbedded.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,22 @@
2626

2727
using namespace swift;
2828

29+
static DiagnosticBehavior
30+
defaultEmbeddedLimitationForError(const DeclContext *dc, SourceLoc loc) {
31+
if (dc->getASTContext().LangOpts.hasFeature(Feature::Embedded))
32+
return DiagnosticBehavior::Unspecified;
33+
34+
return DiagnosticBehavior::Warning;
35+
}
36+
2937
std::optional<DiagnosticBehavior>
3038
swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc,
3139
bool wasAlwaysEmbeddedError) {
3240
// In Embedded Swift, things that were always errors will still be emitted
3341
// as errors. Use "unspecified" so we don't change anything.
3442
if (dc->getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
3543
wasAlwaysEmbeddedError) {
36-
return DiagnosticBehavior::Unspecified;
44+
return defaultEmbeddedLimitationForError(dc, loc);
3745
}
3846

3947
// Check one of the Embedded restriction diagnostics that is ignored by
@@ -66,6 +74,42 @@ swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc,
6674
return DiagnosticBehavior::Unspecified;
6775
}
6876

77+
/// Determine whether the inner signature is more generic than the outer
78+
/// signature, ignoring differences that
79+
static bool isABIMoreGenericThan(GenericSignature innerSig, GenericSignature outerSig) {
80+
auto canInnerSig = innerSig.getCanonicalSignature();
81+
auto canOuterSig = outerSig.getCanonicalSignature();
82+
if (canInnerSig == canOuterSig)
83+
return false;
84+
85+
// The inner signature added generic parameters.
86+
if (canOuterSig.getGenericParams().size() !=
87+
canInnerSig.getGenericParams().size())
88+
return true;
89+
90+
// Look at the requirements of the inner signature that aren't satisfied
91+
// by the outer signature, to see if there are any requirements that aren't
92+
// just marker protocols.
93+
auto requirements = canInnerSig.requirementsNotSatisfiedBy(canOuterSig);
94+
for (const auto &req : requirements) {
95+
switch (req.getKind()) {
96+
case RequirementKind::Conformance:
97+
if (req.getProtocolDecl()->isMarkerProtocol())
98+
continue;
99+
100+
return true;
101+
102+
case RequirementKind::Superclass:
103+
case RequirementKind::Layout:
104+
case RequirementKind::SameShape:
105+
case RequirementKind::SameType:
106+
return true;
107+
}
108+
}
109+
110+
return false;
111+
}
112+
69113
/// Check embedded restrictions in the signature of the given function.
70114
void swift::checkEmbeddedRestrictionsInSignature(
71115
const AbstractFunctionDecl *func) {
@@ -80,6 +124,20 @@ void swift::checkEmbeddedRestrictionsInSignature(
80124
!func->hasPolymorphicEffect(EffectKind::Throws)) {
81125
diagnoseUntypedThrowsInEmbedded(func, throwsLoc);
82126
}
127+
128+
// If we're in a class, one cannot have a non-final generic function.
129+
if (auto classDecl = dyn_cast<ClassDecl>(func->getDeclContext())) {
130+
if (!classDecl->isSemanticallyFinal() &&
131+
((isa<FuncDecl>(func) && !func->isSemanticallyFinal()) ||
132+
(isa<ConstructorDecl>(func) &&
133+
cast<ConstructorDecl>(func)->isRequired())) &&
134+
isABIMoreGenericThan(func->getGenericSignature(),
135+
classDecl->getGenericSignature())) {
136+
func->diagnose(diag::generic_nonfinal_in_embedded_swift, func,
137+
isa<ConstructorDecl>(func))
138+
.limitBehavior(defaultEmbeddedLimitationForError(func, func->getLoc()));
139+
}
140+
}
83141
}
84142

85143
void swift::diagnoseUntypedThrowsInEmbedded(

stdlib/public/core/ExistentialCollection.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ internal class _AnySequenceBox<Element> {
181181
@inlinable
182182
internal var _underestimatedCount: Int { _abstract() }
183183

184+
#if !$Embedded
184185
@inlinable
185186
internal func _map<T>(
186187
_ transform: (Element) throws -> T
187188
) throws -> [T] {
188189
_abstract()
189190
}
191+
#endif
190192

191193
@inlinable
192194
internal func _filter(
@@ -534,12 +536,16 @@ internal final class _SequenceBox<S: Sequence>: _AnySequenceBox<S.Element> {
534536
internal override var _underestimatedCount: Int {
535537
return _base.underestimatedCount
536538
}
539+
540+
#if !$Embedded
537541
@inlinable
538542
internal override func _map<T>(
539543
_ transform: (Element) throws -> T
540544
) throws -> [T] {
541545
try _base.map(transform)
542546
}
547+
#endif
548+
543549
@inlinable
544550
internal override func _filter(
545551
_ isIncluded: (Element) throws -> Bool
@@ -627,12 +633,14 @@ internal final class _CollectionBox<S: Collection>: _AnyCollectionBox<S.Element>
627633
internal override var _underestimatedCount: Int {
628634
return _base.underestimatedCount
629635
}
636+
#if !$Embedded
630637
@inlinable
631638
internal override func _map<T>(
632639
_ transform: (Element) throws -> T
633640
) throws -> [T] {
634641
try _base.map(transform)
635642
}
643+
#endif
636644
@inlinable
637645
internal override func _filter(
638646
_ isIncluded: (Element) throws -> Bool
@@ -822,12 +830,14 @@ internal final class _BidirectionalCollectionBox<S: BidirectionalCollection>
822830
internal override var _underestimatedCount: Int {
823831
return _base.underestimatedCount
824832
}
833+
#if !$Embedded
825834
@inlinable
826835
internal override func _map<T>(
827836
_ transform: (Element) throws -> T
828837
) throws -> [T] {
829838
try _base.map(transform)
830839
}
840+
#endif
831841
@inlinable
832842
internal override func _filter(
833843
_ isIncluded: (Element) throws -> Bool
@@ -1035,12 +1045,14 @@ internal final class _RandomAccessCollectionBox<S: RandomAccessCollection>
10351045
internal override var _underestimatedCount: Int {
10361046
return _base.underestimatedCount
10371047
}
1048+
#if !$Embedded
10381049
@inlinable
10391050
internal override func _map<T>(
10401051
_ transform: (Element) throws -> T
10411052
) throws -> [T] {
10421053
try _base.map(transform)
10431054
}
1055+
#endif
10441056
@inlinable
10451057
internal override func _filter(
10461058
_ isIncluded: (Element) throws -> Bool
@@ -1322,6 +1334,7 @@ extension AnySequence {
13221334
return _box._underestimatedCount
13231335
}
13241336

1337+
#if !$Embedded
13251338
@inlinable
13261339
@_alwaysEmitIntoClient
13271340
public func map<T, E>(
@@ -1334,7 +1347,6 @@ extension AnySequence {
13341347
}
13351348
}
13361349

1337-
#if !$Embedded
13381350
// ABI-only entrypoint for the rethrows version of map, which has been
13391351
// superseded by the typed-throws version. Expressed as "throws", which is
13401352
// ABI-compatible with "rethrows".
@@ -1428,6 +1440,7 @@ extension AnyCollection {
14281440
return _box._underestimatedCount
14291441
}
14301442

1443+
#if !$Embedded
14311444
@inlinable
14321445
@_alwaysEmitIntoClient
14331446
public func map<T, E>(
@@ -1440,7 +1453,6 @@ extension AnyCollection {
14401453
}
14411454
}
14421455

1443-
#if !$Embedded
14441456
// ABI-only entrypoint for the rethrows version of map, which has been
14451457
// superseded by the typed-throws version. Expressed as "throws", which is
14461458
// ABI-compatible with "rethrows".
@@ -1540,6 +1552,7 @@ extension AnyBidirectionalCollection {
15401552
return _box._underestimatedCount
15411553
}
15421554

1555+
#if !$Embedded
15431556
@inlinable
15441557
@_alwaysEmitIntoClient
15451558
public func map<T, E>(
@@ -1552,7 +1565,6 @@ extension AnyBidirectionalCollection {
15521565
}
15531566
}
15541567

1555-
#if !$Embedded
15561568
// ABI-only entrypoint for the rethrows version of map, which has been
15571569
// superseded by the typed-throws version. Expressed as "throws", which is
15581570
// ABI-compatible with "rethrows".
@@ -1654,6 +1666,7 @@ extension AnyRandomAccessCollection {
16541666
return _box._underestimatedCount
16551667
}
16561668

1669+
#if !$Embedded
16571670
@inlinable
16581671
@_alwaysEmitIntoClient
16591672
public func map<T, E>(
@@ -1666,7 +1679,6 @@ extension AnyRandomAccessCollection {
16661679
}
16671680
}
16681681

1669-
#if !$Embedded
16701682
// ABI-only entrypoint for the rethrows version of map, which has been
16711683
// superseded by the typed-throws version. Expressed as "throws", which is
16721684
// ABI-compatible with "rethrows".

test/embedded/classes-non-final-method-no-stdlib.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// REQUIRES: swift_feature_Embedded
55

66
public class MyClass {
7-
public func foo<T>(t: T) { } // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
7+
public func foo<T>(t: T) { } // expected-error {{generic instance method 'foo(t:)' in a class must be 'final' in Embedded Swift}}
88
public func bar() { }
99
}
1010

@@ -24,7 +24,7 @@ func testit2() -> C2<S> {
2424
}
2525

2626
open class C3<X> {
27-
public func foo<T>(t: T) {} // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
27+
public func foo<T>(t: T) {} // expected-error {{generic instance method 'foo(t:)' in a class must be 'final' in Embedded Swift}}
2828
}
2929

3030
func testit3() -> C3<S> {

test/embedded/restrictions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,27 @@ public struct MyStruct {
6464
unowned(unsafe) var unownedUnsafe: MyClass
6565
}
6666

67+
// ---------------------------------------------------------------------------
68+
// generic, non-final functions
69+
// ---------------------------------------------------------------------------
70+
71+
protocol P { }
72+
73+
class MyGenericClass<T> {
74+
func f<U>(value: U) { } // expected-nonembedded-warning{{generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift}}
75+
// expected-embedded-error@-1{{generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift}}
76+
func g() { }
77+
class func h() where T: P { } // expected-nonembedded-warning{{generic class method 'h()' in a class must be 'final' in Embedded Swift}}
78+
// expected-embedded-error@-1{{generic class method 'h()' in a class must be 'final' in Embedded Swift}}
79+
80+
init<U>(value: U) { } // okay, can be directly called
81+
82+
required init() { } // non-generic is okay
83+
84+
required init<V>(something: V) { } // expected-nonembedded-warning{{generic initializer 'init(something:)' in a class cannot be 'required' in Embedded Swift}}
85+
// expected-embedded-error@-1{{generic initializer 'init(something:)' in a class cannot be 'required' in Embedded Swift}}
86+
}
87+
6788
// ---------------------------------------------------------------------------
6889
// #if handling to suppress diagnostics for non-Embedded-only code
6990
// ---------------------------------------------------------------------------
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
# Embedded Swift language restrictions (EmbeddedRestrictions)
22

3-
Embedded Swift is a compilation model of Swift that can produce extremely small binaries without external dependencies, suitable for restricted environments including embedded (microcontrollers) and baremetal setups (no operating system at all), and low-level environments (firmware, kernels, device drivers, low-level components of userspace OS runtimes). While the vast majority of Swift language features are available in Embedded Swift, there are some language features that require the full Swift standard library and runtime, which are not available in Embedded Swift.
3+
Embedded Swift is a subset of the Swift language that compiles to smaller binaries that do not rely on the Swift runtime. Embedded Swift produces some restrictions on the use of the Swift language to eliminate the runtime dependency, which are captured by the `EmbeddedRestrictions` diagnostic group.
44

5-
Diagnostics in the `EmbeddedRestrictions` group describe those language features that cannot be used in Embedded Swift. For example, Embedded Swift uses a simplified reference-counting model that does not support `weak` or `unowned` references. The following will produce a diagnostic in Embedded Swift:
5+
The Embedded Swift compilation model can produce extremely small binaries without external dependencies, suitable for restricted environments including embedded (microcontrollers) and baremetal setups (no operating system at all), and low-level environments (firmware, kernels, device drivers, low-level components of userspace OS runtimes). While the vast majority of Swift language features are available in Embedded Swift, there are some language features that require the full Swift standard library and runtime, which are not available in Embedded Swift.
6+
7+
Diagnostics in the `EmbeddedRestrictions` group describe those language features that cannot be used in Embedded Swift. These include:
8+
9+
* `weak` and `unowned` references, because Embedded Swift uses a simplified reference-counting model that cannot support them. For example:
610

711
class Node {
812
weak var parent: Node? // error: attribute 'weak' cannot be used in Embedded Swift
913
}
1014

15+
* Non-final generic methods in a class, which are prohibited because they cannot be specialized for every possible call site. For example:
16+
17+
class MyGenericClass<T> {
18+
func f<U>(value: U) { } // warning: generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift
19+
20+
func g() { } // okay, not generic relative to the class itself
21+
22+
class func h() where T: P { } // warning: generic class method 'h()' in a class must be 'final' in Embedded Swift
23+
}
24+
1125
## See Also
1226

1327
- [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)

0 commit comments

Comments
 (0)