[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.
This commit is contained in:
Doug Gregor
2025-09-17 16:01:58 -07:00
parent c8e6bfd814
commit 844ba5f4f8
6 changed files with 117 additions and 9 deletions

View File

@@ -8612,6 +8612,9 @@ GROUPED_ERROR(weak_unowned_in_embedded_swift, EmbeddedRestrictions, none,
GROUPED_WARNING(untyped_throws_in_embedded_swift, EmbeddedRestrictions,
DefaultIgnore,
"untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'", ())
GROUPED_ERROR(generic_nonfinal_in_embedded_swift, EmbeddedRestrictions, none,
"generic %kind0 in a class %select{must be 'final'|cannot be 'required'}1 in Embedded Swift",
(const Decl *, bool))
//===----------------------------------------------------------------------===//
// MARK: @abi Attribute

View File

@@ -26,6 +26,14 @@
using namespace swift;
static DiagnosticBehavior
defaultEmbeddedLimitationForError(const DeclContext *dc, SourceLoc loc) {
if (dc->getASTContext().LangOpts.hasFeature(Feature::Embedded))
return DiagnosticBehavior::Unspecified;
return DiagnosticBehavior::Warning;
}
std::optional<DiagnosticBehavior>
swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc,
bool wasAlwaysEmbeddedError) {
@@ -33,7 +41,7 @@ swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc,
// as errors. Use "unspecified" so we don't change anything.
if (dc->getASTContext().LangOpts.hasFeature(Feature::Embedded) &&
wasAlwaysEmbeddedError) {
return DiagnosticBehavior::Unspecified;
return defaultEmbeddedLimitationForError(dc, loc);
}
// Check one of the Embedded restriction diagnostics that is ignored by
@@ -66,6 +74,42 @@ swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc,
return DiagnosticBehavior::Unspecified;
}
/// Determine whether the inner signature is more generic than the outer
/// signature, ignoring differences that
static bool isABIMoreGenericThan(GenericSignature innerSig, GenericSignature outerSig) {
auto canInnerSig = innerSig.getCanonicalSignature();
auto canOuterSig = outerSig.getCanonicalSignature();
if (canInnerSig == canOuterSig)
return false;
// The inner signature added generic parameters.
if (canOuterSig.getGenericParams().size() !=
canInnerSig.getGenericParams().size())
return true;
// Look at the requirements of the inner signature that aren't satisfied
// by the outer signature, to see if there are any requirements that aren't
// just marker protocols.
auto requirements = canInnerSig.requirementsNotSatisfiedBy(canOuterSig);
for (const auto &req : requirements) {
switch (req.getKind()) {
case RequirementKind::Conformance:
if (req.getProtocolDecl()->isMarkerProtocol())
continue;
return true;
case RequirementKind::Superclass:
case RequirementKind::Layout:
case RequirementKind::SameShape:
case RequirementKind::SameType:
return true;
}
}
return false;
}
/// Check embedded restrictions in the signature of the given function.
void swift::checkEmbeddedRestrictionsInSignature(
const AbstractFunctionDecl *func) {
@@ -80,6 +124,20 @@ void swift::checkEmbeddedRestrictionsInSignature(
!func->hasPolymorphicEffect(EffectKind::Throws)) {
diagnoseUntypedThrowsInEmbedded(func, throwsLoc);
}
// If we're in a class, one cannot have a non-final generic function.
if (auto classDecl = dyn_cast<ClassDecl>(func->getDeclContext())) {
if (!classDecl->isSemanticallyFinal() &&
((isa<FuncDecl>(func) && !func->isSemanticallyFinal()) ||
(isa<ConstructorDecl>(func) &&
cast<ConstructorDecl>(func)->isRequired())) &&
isABIMoreGenericThan(func->getGenericSignature(),
classDecl->getGenericSignature())) {
func->diagnose(diag::generic_nonfinal_in_embedded_swift, func,
isa<ConstructorDecl>(func))
.limitBehavior(defaultEmbeddedLimitationForError(func, func->getLoc()));
}
}
}
void swift::diagnoseUntypedThrowsInEmbedded(

View File

@@ -181,12 +181,14 @@ internal class _AnySequenceBox<Element> {
@inlinable
internal var _underestimatedCount: Int { _abstract() }
#if !$Embedded
@inlinable
internal func _map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
_abstract()
}
#endif
@inlinable
internal func _filter(
@@ -534,12 +536,16 @@ internal final class _SequenceBox<S: Sequence>: _AnySequenceBox<S.Element> {
internal override var _underestimatedCount: Int {
return _base.underestimatedCount
}
#if !$Embedded
@inlinable
internal override func _map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
try _base.map(transform)
}
#endif
@inlinable
internal override func _filter(
_ isIncluded: (Element) throws -> Bool
@@ -627,12 +633,14 @@ internal final class _CollectionBox<S: Collection>: _AnyCollectionBox<S.Element>
internal override var _underestimatedCount: Int {
return _base.underestimatedCount
}
#if !$Embedded
@inlinable
internal override func _map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
try _base.map(transform)
}
#endif
@inlinable
internal override func _filter(
_ isIncluded: (Element) throws -> Bool
@@ -822,12 +830,14 @@ internal final class _BidirectionalCollectionBox<S: BidirectionalCollection>
internal override var _underestimatedCount: Int {
return _base.underestimatedCount
}
#if !$Embedded
@inlinable
internal override func _map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
try _base.map(transform)
}
#endif
@inlinable
internal override func _filter(
_ isIncluded: (Element) throws -> Bool
@@ -1035,12 +1045,14 @@ internal final class _RandomAccessCollectionBox<S: RandomAccessCollection>
internal override var _underestimatedCount: Int {
return _base.underestimatedCount
}
#if !$Embedded
@inlinable
internal override func _map<T>(
_ transform: (Element) throws -> T
) throws -> [T] {
try _base.map(transform)
}
#endif
@inlinable
internal override func _filter(
_ isIncluded: (Element) throws -> Bool
@@ -1322,6 +1334,7 @@ extension AnySequence {
return _box._underestimatedCount
}
#if !$Embedded
@inlinable
@_alwaysEmitIntoClient
public func map<T, E>(
@@ -1334,7 +1347,6 @@ extension AnySequence {
}
}
#if !$Embedded
// ABI-only entrypoint for the rethrows version of map, which has been
// superseded by the typed-throws version. Expressed as "throws", which is
// ABI-compatible with "rethrows".
@@ -1428,6 +1440,7 @@ extension AnyCollection {
return _box._underestimatedCount
}
#if !$Embedded
@inlinable
@_alwaysEmitIntoClient
public func map<T, E>(
@@ -1440,7 +1453,6 @@ extension AnyCollection {
}
}
#if !$Embedded
// ABI-only entrypoint for the rethrows version of map, which has been
// superseded by the typed-throws version. Expressed as "throws", which is
// ABI-compatible with "rethrows".
@@ -1540,6 +1552,7 @@ extension AnyBidirectionalCollection {
return _box._underestimatedCount
}
#if !$Embedded
@inlinable
@_alwaysEmitIntoClient
public func map<T, E>(
@@ -1552,7 +1565,6 @@ extension AnyBidirectionalCollection {
}
}
#if !$Embedded
// ABI-only entrypoint for the rethrows version of map, which has been
// superseded by the typed-throws version. Expressed as "throws", which is
// ABI-compatible with "rethrows".
@@ -1654,6 +1666,7 @@ extension AnyRandomAccessCollection {
return _box._underestimatedCount
}
#if !$Embedded
@inlinable
@_alwaysEmitIntoClient
public func map<T, E>(
@@ -1666,7 +1679,6 @@ extension AnyRandomAccessCollection {
}
}
#if !$Embedded
// ABI-only entrypoint for the rethrows version of map, which has been
// superseded by the typed-throws version. Expressed as "throws", which is
// ABI-compatible with "rethrows".

View File

@@ -4,7 +4,7 @@
// REQUIRES: swift_feature_Embedded
public class MyClass {
public func foo<T>(t: T) { } // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
public func foo<T>(t: T) { } // expected-error {{generic instance method 'foo(t:)' in a class must be 'final' in Embedded Swift}}
public func bar() { }
}
@@ -24,7 +24,7 @@ func testit2() -> C2<S> {
}
open class C3<X> {
public func foo<T>(t: T) {} // expected-error {{classes cannot have a non-final, generic method 'foo(t:)' in embedded Swift}}
public func foo<T>(t: T) {} // expected-error {{generic instance method 'foo(t:)' in a class must be 'final' in Embedded Swift}}
}
func testit3() -> C3<S> {

View File

@@ -64,6 +64,27 @@ public struct MyStruct {
unowned(unsafe) var unownedUnsafe: MyClass
}
// ---------------------------------------------------------------------------
// generic, non-final functions
// ---------------------------------------------------------------------------
protocol P { }
class MyGenericClass<T> {
func f<U>(value: U) { } // expected-nonembedded-warning{{generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift}}
// expected-embedded-error@-1{{generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift}}
func g() { }
class func h() where T: P { } // expected-nonembedded-warning{{generic class method 'h()' in a class must be 'final' in Embedded Swift}}
// expected-embedded-error@-1{{generic class method 'h()' in a class must be 'final' in Embedded Swift}}
init<U>(value: U) { } // okay, can be directly called
required init() { } // non-generic is okay
required init<V>(something: V) { } // expected-nonembedded-warning{{generic initializer 'init(something:)' in a class cannot be 'required' in Embedded Swift}}
// expected-embedded-error@-1{{generic initializer 'init(something:)' in a class cannot be 'required' in Embedded Swift}}
}
// ---------------------------------------------------------------------------
// #if handling to suppress diagnostics for non-Embedded-only code
// ---------------------------------------------------------------------------

View File

@@ -1,13 +1,27 @@
# Embedded Swift language restrictions (EmbeddedRestrictions)
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.
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.
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:
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.
Diagnostics in the `EmbeddedRestrictions` group describe those language features that cannot be used in Embedded Swift. These include:
* `weak` and `unowned` references, because Embedded Swift uses a simplified reference-counting model that cannot support them. For example:
class Node {
weak var parent: Node? // error: attribute 'weak' cannot be used in Embedded Swift
}
* Non-final generic methods in a class, which are prohibited because they cannot be specialized for every possible call site. For example:
class MyGenericClass<T> {
func f<U>(value: U) { } // warning: generic instance method 'f(value:)' in a class must be 'final' in Embedded Swift
func g() { } // okay, not generic relative to the class itself
class func h() where T: P { } // warning: generic class method 'h()' in a class must be 'final' in Embedded Swift
}
## See Also
- [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)