[Embedded] Diagnose uses of generic methods on existential values

Generic methods declared in protocols (and extensions thereof) cannot
be used on existential values, because there is no way to specialize
them for all potential types. Diagnose such cases in Embedded Swift
mode and via `-Wwarning EmbeddedRestrictions`.

This adds a bunch more warnings to the standard library that we'll
need to clean up, probably by `#if`'ing more code out.

Part of rdar://119383905.
This commit is contained in:
Doug Gregor
2025-09-18 08:54:09 -07:00
parent 844ba5f4f8
commit ade6e55b0c
7 changed files with 93 additions and 9 deletions

View File

@@ -8615,6 +8615,10 @@ GROUPED_WARNING(untyped_throws_in_embedded_swift, EmbeddedRestrictions,
GROUPED_ERROR(generic_nonfinal_in_embedded_swift, EmbeddedRestrictions, none, 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", "generic %kind0 in a class %select{must be 'final'|cannot be 'required'}1 in Embedded Swift",
(const Decl *, bool)) (const Decl *, bool))
GROUPED_WARNING(use_generic_member_of_existential_in_embedded_swift,
EmbeddedRestrictions, DefaultIgnore,
"cannot use generic %kind0 on a value of type %1 in Embedded Swift",
(const Decl *, Type))
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// MARK: @abi Attribute // MARK: @abi Attribute

View File

@@ -21,6 +21,7 @@
#include "MiscDiagnostics.h" #include "MiscDiagnostics.h"
#include "OpenedExistentials.h" #include "OpenedExistentials.h"
#include "TypeCheckConcurrency.h" #include "TypeCheckConcurrency.h"
#include "TypeCheckEmbedded.h"
#include "TypeCheckMacros.h" #include "TypeCheckMacros.h"
#include "TypeCheckProtocol.h" #include "TypeCheckProtocol.h"
#include "TypeCheckType.h" #include "TypeCheckType.h"
@@ -924,8 +925,9 @@ namespace {
/// \returns An OpaqueValueExpr that provides a reference to the value /// \returns An OpaqueValueExpr that provides a reference to the value
/// stored within the expression or its metatype (if the base was a /// stored within the expression or its metatype (if the base was a
/// metatype). /// metatype).
Expr *openExistentialReference(Expr *base, ExistentialArchetypeType *archetype, Expr *openExistentialReference(Expr *base,
ValueDecl *member) { ExistentialArchetypeType *archetype,
ValueDecl *member, SourceLoc memberLoc) {
assert(archetype && "archetype not already opened?"); assert(archetype && "archetype not already opened?");
// Dig out the base type. // Dig out the base type.
@@ -955,6 +957,11 @@ namespace {
assert(baseTy->isAnyExistentialType() && "Type must be existential"); assert(baseTy->isAnyExistentialType() && "Type must be existential");
// Embedded Swift has limitations on the use of generic members of
// existentials. Diagnose them here.
diagnoseGenericMemberOfExistentialInEmbedded(
dc, memberLoc, baseTy, member);
// If the base was an lvalue but it will only be treated as an // If the base was an lvalue but it will only be treated as an
// rvalue, turn the base into an rvalue now. This results in // rvalue, turn the base into an rvalue now. This results in
// better SILGen. // better SILGen.
@@ -1983,7 +1990,8 @@ namespace {
(!member->getDeclContext()->getSelfProtocolDecl() && (!member->getDeclContext()->getSelfProtocolDecl() &&
baseIsInstance && member->isInstanceMember())) { baseIsInstance && member->isInstanceMember())) {
// Open the existential before performing the member reference. // Open the existential before performing the member reference.
base = openExistentialReference(base, knownOpened->second, member); base = openExistentialReference(base, knownOpened->second, member,
memberLoc.getBaseNameLoc());
baseTy = baseOpenedTy; baseTy = baseOpenedTy;
selfTy = baseTy; selfTy = baseTy;
openedExistential = true; openedExistential = true;
@@ -2490,7 +2498,8 @@ namespace {
auto memberLoc = cs.getCalleeLocator(cs.getConstraintLocator(locator)); auto memberLoc = cs.getCalleeLocator(cs.getConstraintLocator(locator));
auto knownOpened = solution.OpenedExistentialTypes.find(memberLoc); auto knownOpened = solution.OpenedExistentialTypes.find(memberLoc);
if (knownOpened != solution.OpenedExistentialTypes.end()) { if (knownOpened != solution.OpenedExistentialTypes.end()) {
base = openExistentialReference(base, knownOpened->second, subscript); base = openExistentialReference(base, knownOpened->second, subscript,
args->getLoc());
baseTy = knownOpened->second; baseTy = knownOpened->second;
} }
@@ -6551,7 +6560,7 @@ ArgumentList *ExprRewriter::coerceCallArguments(
cs.getConstraintLocator(argLoc)); cs.getConstraintLocator(argLoc));
if (knownOpened != solution.OpenedExistentialTypes.end()) { if (knownOpened != solution.OpenedExistentialTypes.end()) {
argExpr = openExistentialReference( argExpr = openExistentialReference(
argExpr, knownOpened->second, callee.getDecl()); argExpr, knownOpened->second, callee.getDecl(), apply->getLoc());
argType = cs.getType(argExpr); argType = cs.getType(argExpr);
} }
} }

View File

@@ -152,3 +152,20 @@ void swift::diagnoseUntypedThrowsInEmbedded(
.limitBehavior(*behavior) .limitBehavior(*behavior)
.fixItInsertAfter(throwsLoc, "(<#thrown error type#>)"); .fixItInsertAfter(throwsLoc, "(<#thrown error type#>)");
} }
void swift::diagnoseGenericMemberOfExistentialInEmbedded(
const DeclContext *dc, SourceLoc loc,
Type baseType, const ValueDecl *member) {
// If we are not supposed to diagnose Embedded Swift limitations, do nothing.
auto behavior = shouldDiagnoseEmbeddedLimitations(dc, loc, true);
if (!behavior)
return;
if (isABIMoreGenericThan(
member->getInnermostDeclContext()->getGenericSignatureOfContext(),
member->getDeclContext()->getGenericSignatureOfContext())) {
dc->getASTContext().Diags.diagnose(loc, diag::use_generic_member_of_existential_in_embedded_swift, member,
baseType)
.limitBehavior(*behavior);
}
}

View File

@@ -25,7 +25,9 @@ class AbstractFunctionDecl;
class DeclContext; class DeclContext;
struct DiagnosticBehavior; struct DiagnosticBehavior;
class SourceLoc; class SourceLoc;
class Type;
class ValueDecl;
/// Whether we should diagnose language-level limitations of Embedded Swift /// Whether we should diagnose language-level limitations of Embedded Swift
/// at the given source location, and how. /// at the given source location, and how.
/// ///
@@ -46,5 +48,10 @@ void checkEmbeddedRestrictionsInSignature(const AbstractFunctionDecl *func);
/// Diagnose a declaration of typed throws at the given location. /// Diagnose a declaration of typed throws at the given location.
void diagnoseUntypedThrowsInEmbedded(const DeclContext *dc, SourceLoc throwsLoc); void diagnoseUntypedThrowsInEmbedded(const DeclContext *dc, SourceLoc throwsLoc);
/// Diagnose references to a generic member via an existential type, which are
/// not available in Embedded Swift.
void diagnoseGenericMemberOfExistentialInEmbedded(
const DeclContext *dc, SourceLoc loc,
Type baseType, const ValueDecl *member);
} }
#endif // SWIFT_SEMA_TYPECHECKEMBEDDED_H #endif // SWIFT_SEMA_TYPECHECKEMBEDDED_H

View File

@@ -9,9 +9,10 @@ public protocol MyProtocol: AnyObject {
} }
func test_some(p: some MyProtocol) { func test_some(p: some MyProtocol) {
p.foo(ptr: nil, value: 0) // expected-error {{a protocol type cannot contain a generic method 'foo(ptr:value:)' in embedded Swift}} p.foo(ptr: nil, value: 0) // expected-error {{a protocol type cannot contain a generic method 'foo(ptr:value:)' in embedded Swift}}
} }
public func test_any(p: any MyProtocol) { public func test_any(p: any MyProtocol) {
test_some(p: p) test_some(p: p)
// expected-warning@-1{{cannot use generic global function 'test_some(p:)' on a value of type 'any MyProtocol' in Embedded Swift}}
} }

View File

@@ -85,6 +85,35 @@ class MyGenericClass<T> {
// expected-embedded-error@-1{{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}}
} }
// ---------------------------------------------------------------------------
// generic functions on existentials
// ---------------------------------------------------------------------------
public protocol Q {
func f<T>(_ value: T)
func okay()
}
extension Q {
public func g<T>(_ value: T) {
f(value)
}
public mutating func h<T>(_ value: T) {
f(value)
}
}
public func existentials(q: any AnyObject & Q, i: Int) {
q.okay()
q.f(i) // expected-warning{{cannot use generic instance method 'f' on a value of type 'any AnyObject & Q' in Embedded Swift}}
q.g(i) // expected-warning{{cannot use generic instance method 'g' on a value of type 'any AnyObject & Q' in Embedded Swift}}
var qm = q
qm.h(i) // expected-warning{{cannot use generic instance method 'h' on a value of type 'any AnyObject & Q' in Embedded Swift}}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// #if handling to suppress diagnostics for non-Embedded-only code // #if handling to suppress diagnostics for non-Embedded-only code
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
# Embedded Swift language restrictions (EmbeddedRestrictions) # Embedded Swift language restrictions (EmbeddedRestrictions)
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. Embedded Swift is a subset of the Swift language that that introduces some restrictions on the use of language features to eliminate the need for the Swift runtime. These restrictions are captured by the `EmbeddedRestrictions` diagnostic group.
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. 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.
@@ -22,6 +22,23 @@ Diagnostics in the `EmbeddedRestrictions` group describe those language features
class func h() where T: P { } // warning: generic class method 'h()' in a class must be 'final' in Embedded Swift class func h() where T: P { } // warning: generic class method 'h()' in a class must be 'final' in Embedded Swift
} }
* Generic methods used on values of protocol type, which are prohibited because they cannot be specialized for every possible call site. For example:
protocol P: AnyObject {
func doNothing()
func doSomething<T>(on value: T)
}
func testGenerics<Value: P>(value: value, i: Int) {
value.doNothing() // okay
value.doSomething(on: i) // okay, always specialized
}
func testValuesOfProtocolType(value: any P, i: Int) {
value.doNothing() // okay
value.doSomething(on: i) // warning: cannot use generic instance method 'doSomething(on:)' on a value of type 'any P' in Embedded Swift
}
## See Also ## See Also
- [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md) - [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)