From ade6e55b0c0c51f7d1113fc3627f0129e244acd3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 18 Sep 2025 08:54:09 -0700 Subject: [PATCH] [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. --- include/swift/AST/DiagnosticsSema.def | 4 +++ lib/Sema/CSApply.cpp | 19 ++++++++---- lib/Sema/TypeCheckEmbedded.cpp | 17 +++++++++++ lib/Sema/TypeCheckEmbedded.h | 9 +++++- test/embedded/existential-generic-error.swift | 5 ++-- test/embedded/restrictions.swift | 29 +++++++++++++++++++ userdocs/diagnostics/embedded-restrictions.md | 19 +++++++++++- 7 files changed, 93 insertions(+), 9 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 215172ecfce..94f9946a0fb 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8615,6 +8615,10 @@ GROUPED_WARNING(untyped_throws_in_embedded_swift, EmbeddedRestrictions, 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)) +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 diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index c0d41b10160..bae0d42b3fe 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -21,6 +21,7 @@ #include "MiscDiagnostics.h" #include "OpenedExistentials.h" #include "TypeCheckConcurrency.h" +#include "TypeCheckEmbedded.h" #include "TypeCheckMacros.h" #include "TypeCheckProtocol.h" #include "TypeCheckType.h" @@ -924,8 +925,9 @@ namespace { /// \returns An OpaqueValueExpr that provides a reference to the value /// stored within the expression or its metatype (if the base was a /// metatype). - Expr *openExistentialReference(Expr *base, ExistentialArchetypeType *archetype, - ValueDecl *member) { + Expr *openExistentialReference(Expr *base, + ExistentialArchetypeType *archetype, + ValueDecl *member, SourceLoc memberLoc) { assert(archetype && "archetype not already opened?"); // Dig out the base type. @@ -955,6 +957,11 @@ namespace { 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 // rvalue, turn the base into an rvalue now. This results in // better SILGen. @@ -1983,7 +1990,8 @@ namespace { (!member->getDeclContext()->getSelfProtocolDecl() && baseIsInstance && member->isInstanceMember())) { // Open the existential before performing the member reference. - base = openExistentialReference(base, knownOpened->second, member); + base = openExistentialReference(base, knownOpened->second, member, + memberLoc.getBaseNameLoc()); baseTy = baseOpenedTy; selfTy = baseTy; openedExistential = true; @@ -2490,7 +2498,8 @@ namespace { auto memberLoc = cs.getCalleeLocator(cs.getConstraintLocator(locator)); auto knownOpened = solution.OpenedExistentialTypes.find(memberLoc); if (knownOpened != solution.OpenedExistentialTypes.end()) { - base = openExistentialReference(base, knownOpened->second, subscript); + base = openExistentialReference(base, knownOpened->second, subscript, + args->getLoc()); baseTy = knownOpened->second; } @@ -6551,7 +6560,7 @@ ArgumentList *ExprRewriter::coerceCallArguments( cs.getConstraintLocator(argLoc)); if (knownOpened != solution.OpenedExistentialTypes.end()) { argExpr = openExistentialReference( - argExpr, knownOpened->second, callee.getDecl()); + argExpr, knownOpened->second, callee.getDecl(), apply->getLoc()); argType = cs.getType(argExpr); } } diff --git a/lib/Sema/TypeCheckEmbedded.cpp b/lib/Sema/TypeCheckEmbedded.cpp index defaf18fdb4..4c8c230246c 100644 --- a/lib/Sema/TypeCheckEmbedded.cpp +++ b/lib/Sema/TypeCheckEmbedded.cpp @@ -152,3 +152,20 @@ void swift::diagnoseUntypedThrowsInEmbedded( .limitBehavior(*behavior) .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); + } +} diff --git a/lib/Sema/TypeCheckEmbedded.h b/lib/Sema/TypeCheckEmbedded.h index a5d2133d503..07a4a007ab0 100644 --- a/lib/Sema/TypeCheckEmbedded.h +++ b/lib/Sema/TypeCheckEmbedded.h @@ -25,7 +25,9 @@ class AbstractFunctionDecl; class DeclContext; struct DiagnosticBehavior; class SourceLoc; - +class Type; +class ValueDecl; + /// Whether we should diagnose language-level limitations of Embedded Swift /// 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. 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 diff --git a/test/embedded/existential-generic-error.swift b/test/embedded/existential-generic-error.swift index 895fa877af3..aa865fbb030 100644 --- a/test/embedded/existential-generic-error.swift +++ b/test/embedded/existential-generic-error.swift @@ -9,9 +9,10 @@ public protocol MyProtocol: AnyObject { } 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) { - 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}} } diff --git a/test/embedded/restrictions.swift b/test/embedded/restrictions.swift index 42146d1670f..33b9fc8ca50 100644 --- a/test/embedded/restrictions.swift +++ b/test/embedded/restrictions.swift @@ -85,6 +85,35 @@ class MyGenericClass { // 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(_ value: T) + func okay() +} + +extension Q { + public func g(_ value: T) { + f(value) + } + + public mutating func h(_ 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 // --------------------------------------------------------------------------- diff --git a/userdocs/diagnostics/embedded-restrictions.md b/userdocs/diagnostics/embedded-restrictions.md index 72b9181ffc5..18e056c111a 100644 --- a/userdocs/diagnostics/embedded-restrictions.md +++ b/userdocs/diagnostics/embedded-restrictions.md @@ -1,6 +1,6 @@ # 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. @@ -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 } +* 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(on value: T) + } + + func testGenerics(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 - [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)