From 7d21bc332aca3dfac8aa81ee5c0fce3afae88875 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 14 Sep 2025 08:29:15 -0700 Subject: [PATCH 1/2] [Embedded] Diagnose untyped throws as an Embedded Swift restriction Untyped throws depends on existentials (`any Error`), and is therefore not available in Embedded Swift. Introduce a diagnostic that diagnoses any use of untyped throws, suggesting that one use typed throws instead. Make this an opt-in diagnostic enabled with `-Wwarning EmbeddedRestrictions`, whether in Embedded Swift or not, using the "default ignore" flag on these new warnings. Document this new diagnostic group, and put the existing Embedded Swift error about weak/unowned references in it as well. Part of the general push to have the type checker identify code that will not compile as Embedded Swift earlier, rdar://133874555. --- include/swift/AST/DiagnosticEngine.h | 8 ++ include/swift/AST/DiagnosticGroups.def | 1 + include/swift/AST/DiagnosticsSema.def | 15 +++- lib/Sema/CMakeLists.txt | 1 + lib/Sema/ConstraintSystem.cpp | 4 + lib/Sema/TypeCheckAttr.cpp | 9 ++- lib/Sema/TypeCheckDeclPrimary.cpp | 3 + lib/Sema/TypeCheckEmbedded.cpp | 79 +++++++++++++++++++ lib/Sema/TypeCheckEmbedded.h | 50 ++++++++++++ lib/Sema/TypeCheckType.cpp | 6 ++ test/embedded/restrictions.swift | 65 +++++++++++++++ test/embedded/weak-unowned.swift | 4 +- userdocs/diagnostics/diagnostic-groups.md | 1 + userdocs/diagnostics/embedded-restrictions.md | 13 +++ 14 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 lib/Sema/TypeCheckEmbedded.cpp create mode 100644 lib/Sema/TypeCheckEmbedded.h create mode 100644 test/embedded/restrictions.swift create mode 100644 userdocs/diagnostics/embedded-restrictions.md diff --git a/include/swift/AST/DiagnosticEngine.h b/include/swift/AST/DiagnosticEngine.h index 08a2c93c152bf..8f04b6f982eaf 100644 --- a/include/swift/AST/DiagnosticEngine.h +++ b/include/swift/AST/DiagnosticEngine.h @@ -701,6 +701,10 @@ namespace swift { ignoredDiagnostics[(unsigned)id] = ignored; } + bool isIgnoredDiagnostic(DiagID id) const { + return ignoredDiagnostics[(unsigned)id]; + } + void swap(DiagnosticState &other) { std::swap(showDiagnosticsAfterFatalError, other.showDiagnosticsAfterFatalError); std::swap(suppressWarnings, other.suppressWarnings); @@ -947,6 +951,10 @@ namespace swift { state.setIgnoredDiagnostic(id, true); } + bool isIgnoredDiagnostic(DiagID id) const { + return state.isIgnoredDiagnostic(id); + } + void resetHadAnyError() { state.resetHadAnyError(); } diff --git a/include/swift/AST/DiagnosticGroups.def b/include/swift/AST/DiagnosticGroups.def index 1d63d8d1d6ddf..172e60fa750ea 100644 --- a/include/swift/AST/DiagnosticGroups.def +++ b/include/swift/AST/DiagnosticGroups.def @@ -46,6 +46,7 @@ GROUP(ClangDeclarationImport, "clang-declaration-import") GROUP(ConformanceIsolation, "conformance-isolation") GROUP(DeprecatedDeclaration, "deprecated-declaration") GROUP(DynamicCallable, "dynamic-callable-requirements") +GROUP(EmbeddedRestrictions, "embedded-restrictions") GROUP(ErrorInFutureSwiftVersion, "error-in-future-swift-version") GROUP(ExclusivityViolation, "exclusivity-violation") GROUP(ExistentialAny, "existential-any") diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d7ffcbd2cdd0c..ac9b3996c1c86 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2234,9 +2234,6 @@ ERROR(attr_only_at_non_generic_scope, none, ERROR(attr_only_on_static_properties, none, "properties with attribute %0 must be static", (DeclAttribute)) -ERROR(weak_unowned_in_embedded_swift, none, - "attribute %0 cannot be used in embedded Swift", (ReferenceOwnership)) - ERROR(access_control_in_protocol,none, "%0 modifier cannot be used in protocols", (DeclAttribute)) NOTE(access_control_in_protocol_detail,none, @@ -8606,6 +8603,18 @@ ERROR(inlinearray_literal_incorrect_count,none, ERROR(inline_array_type_backwards,none, "element count must precede inline array element type", ()) +//===----------------------------------------------------------------------===// +// MARK: Embedded Swift +//===----------------------------------------------------------------------===// + +GROUPED_ERROR(weak_unowned_in_embedded_swift, EmbeddedRestrictions, none, + "attribute %0 cannot be used in Embedded Swift", + (ReferenceOwnership)) + +GROUPED_WARNING(untyped_throws_in_embedded_swift, EmbeddedRestrictions, + DefaultIgnore, + "untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'", ()) + //===----------------------------------------------------------------------===// // MARK: @abi Attribute //===----------------------------------------------------------------------===// diff --git a/lib/Sema/CMakeLists.txt b/lib/Sema/CMakeLists.txt index 196d1291e2cd0..8e9e151f5bff7 100644 --- a/lib/Sema/CMakeLists.txt +++ b/lib/Sema/CMakeLists.txt @@ -69,6 +69,7 @@ add_swift_host_library(swiftSema STATIC TypeCheckEffects.cpp TypeCheckExpr.cpp TypeCheckExprObjC.cpp + TypeCheckEmbedded.cpp TypeCheckGeneric.cpp TypeCheckInvertible.cpp TypeCheckMacros.cpp diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index ab747dcdee760..6d6452dd48af9 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -20,6 +20,7 @@ #include "OpenedExistentials.h" #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" +#include "TypeCheckEmbedded.h" #include "TypeCheckMacros.h" #include "TypeCheckType.h" #include "TypeChecker.h" @@ -1456,6 +1457,9 @@ FunctionType::ExtInfo ClosureEffectsRequest::evaluate( bool sendable = expr->getAttrs().hasAttribute(); if (throws || async) { + if (expr->getThrowsLoc().isValid() && !expr->getExplicitThrownTypeRepr()) + diagnoseUntypedThrowsInEmbedded(expr, expr->getThrowsLoc()); + return ASTExtInfoBuilder() .withThrows(throws, /*FIXME:*/Type()) .withAsync(async) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index af34b013ca4b8..42119f94df47b 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -18,6 +18,7 @@ #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" #include "TypeCheckDistributed.h" +#include "TypeCheckEmbedded.h" #include "TypeCheckMacros.h" #include "TypeCheckObjC.h" #include "TypeCheckType.h" @@ -5501,12 +5502,14 @@ Type TypeChecker::checkReferenceOwnershipAttr(VarDecl *var, Type type, } // Embedded Swift prohibits weak/unowned but allows unowned(unsafe). - if (ctx.LangOpts.hasFeature(Feature::Embedded)) { + if (auto behavior = shouldDiagnoseEmbeddedLimitations( + dc, attr->getLocation(), + /*wasAlwaysEmbeddedError=*/true)) { if (ownershipKind == ReferenceOwnership::Weak || ownershipKind == ReferenceOwnership::Unowned) { Diags.diagnose(attr->getLocation(), diag::weak_unowned_in_embedded_swift, - ownershipKind); - attr->setInvalid(); + ownershipKind) + .limitBehavior(*behavior); } } diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index a719d67f280de..ca5e219d5f30b 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -23,6 +23,7 @@ #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" #include "TypeCheckDecl.h" +#include "TypeCheckEmbedded.h" #include "TypeCheckMacros.h" #include "TypeCheckObjC.h" #include "TypeCheckType.h" @@ -3535,6 +3536,7 @@ class DeclChecker : public DeclVisitor { TypeChecker::checkDeclAttributes(FD); TypeChecker::checkDistributedFunc(FD); + checkEmbeddedRestrictionsInSignature(FD); if (!checkOverrides(FD)) { // If a method has an 'override' keyword but does not @@ -3920,6 +3922,7 @@ class DeclChecker : public DeclVisitor { TypeChecker::checkDeclAttributes(CD); TypeChecker::checkParameterList(CD->getParameters(), CD); + checkEmbeddedRestrictionsInSignature(CD); if (CD->getAsyncLoc().isValid()) TypeChecker::checkConcurrencyAvailability(CD->getAsyncLoc(), CD); diff --git a/lib/Sema/TypeCheckEmbedded.cpp b/lib/Sema/TypeCheckEmbedded.cpp new file mode 100644 index 0000000000000..fe408e3461297 --- /dev/null +++ b/lib/Sema/TypeCheckEmbedded.cpp @@ -0,0 +1,79 @@ +//===--- TypeCheckEmbedded.cpp - Embedded ----------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file implements type checking support for Embedded Swift. +// +//===----------------------------------------------------------------------===// + +#include "TypeCheckEmbedded.h" +#include "swift/AST/ASTContext.h" +#include "swift/AST/Decl.h" +#include "swift/AST/DiagnosticsSema.h" +#include "swift/AST/Effects.h" +#include "swift/AST/Types.h" + +using namespace swift; + +std::optional +swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc, + bool wasAlwaysEmbeddedError) { + // In Embedded Swift, things that were always errors will still be emitted + // as errors. Use "unspecified" so we don't change anything. + if (dc->getASTContext().LangOpts.hasFeature(Feature::Embedded) && + wasAlwaysEmbeddedError) { + return DiagnosticBehavior::Unspecified; + } + + // Check one of the Embedded restriction diagnostics that is ignored by + // default. If it's still ignored, we won't diagnose anything. + // limitations. + auto &diags = dc->getASTContext().Diags; + if (diags.isIgnoredDiagnostic(diag::untyped_throws_in_embedded_swift.ID)) + return std::nullopt; + + // If this was always an error in Embedded Swift, we aren't in Embedded Swift + // now, so downgrade to a warning. + if (wasAlwaysEmbeddedError) + return DiagnosticBehavior::Warning; + + // Leave it as-is. + return DiagnosticBehavior::Unspecified; +} + +/// Check embedded restrictions in the signature of the given function. +void swift::checkEmbeddedRestrictionsInSignature( + const AbstractFunctionDecl *func) { + // If we are not supposed to diagnose Embedded Swift limitations, do nothing. + auto behavior = shouldDiagnoseEmbeddedLimitations(func, func->getLoc()); + if (!behavior) + return; + + // Untyped throws is not permitted. + SourceLoc throwsLoc = func->getThrowsLoc(); + if (throwsLoc.isValid() && !func->getThrownTypeRepr() && + !func->hasPolymorphicEffect(EffectKind::Throws)) { + diagnoseUntypedThrowsInEmbedded(func, throwsLoc); + } +} + +void swift::diagnoseUntypedThrowsInEmbedded( + const DeclContext *dc, SourceLoc throwsLoc) { + // If we are not supposed to diagnose Embedded Swift limitations, do nothing. + auto behavior = shouldDiagnoseEmbeddedLimitations(dc, throwsLoc); + if (!behavior) + return; + + dc->getASTContext().Diags.diagnose( + throwsLoc, diag::untyped_throws_in_embedded_swift) + .limitBehavior(*behavior) + .fixItInsertAfter(throwsLoc, "(<#thrown error type#>)"); +} diff --git a/lib/Sema/TypeCheckEmbedded.h b/lib/Sema/TypeCheckEmbedded.h new file mode 100644 index 0000000000000..a5d2133d5030b --- /dev/null +++ b/lib/Sema/TypeCheckEmbedded.h @@ -0,0 +1,50 @@ +//===--- TypeCheckEmbedded.h - Embedded -------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file provides type checking support for Embedded Swift. +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SEMA_TYPECHECKEMBEDDED_H +#define SWIFT_SEMA_TYPECHECKEMBEDDED_H + +#include + +namespace swift { + +class AbstractFunctionDecl; +class DeclContext; +struct DiagnosticBehavior; +class SourceLoc; + +/// Whether we should diagnose language-level limitations of Embedded Swift +/// at the given source location, and how. +/// +/// @param dc The declaration context in which the diagnostic would be emitted. +/// @param loc The source location at which the diagnostic would be emitted. +/// @param wasAlwaysEmbeddedError Whether this diagnostic was always an error +/// in Embedded Swift, which is used to avoid downgrading for +/// source-compatibility reasons. +/// @returns `std::nullopt` if no diagnostic should be emitted. Otherwise, a +/// behavior limit to place on the diagnostic when it is emitted. +std::optional +shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc, + bool wasAlwaysEmbeddedError = false); + +/// Check embedded restrictions in the signature of the given function. +void checkEmbeddedRestrictionsInSignature(const AbstractFunctionDecl *func); + +/// Diagnose a declaration of typed throws at the given location. +void diagnoseUntypedThrowsInEmbedded(const DeclContext *dc, SourceLoc throwsLoc); + +} +#endif // SWIFT_SEMA_TYPECHECKEMBEDDED_H diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 6737c25fcbf64..f2b07f9f890e4 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -19,6 +19,7 @@ #include "NonisolatedNonsendingByDefaultMigration.h" #include "TypeCheckAvailability.h" #include "TypeCheckConcurrency.h" +#include "TypeCheckEmbedded.h" #include "TypeCheckInvertible.h" #include "TypeCheckProtocol.h" #include "TypeChecker.h" @@ -4428,6 +4429,8 @@ NeverNullType TypeResolver::resolveASTFunctionType( thrownTy); } } + } else if (repr->getThrowsLoc().isValid()) { + diagnoseUntypedThrowsInEmbedded(getDeclContext(), repr->getThrowsLoc()); } bool hasSendingResult = @@ -7011,6 +7014,7 @@ Type ExplicitCaughtTypeRequest::evaluate( // Explicit 'throws' implies that this throws 'any Error'. if (closure->getThrowsLoc().isValid()) { + diagnoseUntypedThrowsInEmbedded(closure, closure->getThrowsLoc()); return ctx.getErrorExistentialType(); } @@ -7030,6 +7034,8 @@ Type ExplicitCaughtTypeRequest::evaluate( // If there is no explicitly-specified thrown error type, it's 'any Error'. if (!typeRepr) { + diagnoseUntypedThrowsInEmbedded(doCatch->getDeclContext(), + doCatch->getThrowsLoc()); return ctx.getErrorExistentialType(); } diff --git a/test/embedded/restrictions.swift b/test/embedded/restrictions.swift new file mode 100644 index 0000000000000..003d55d7ef07c --- /dev/null +++ b/test/embedded/restrictions.swift @@ -0,0 +1,65 @@ +// RUN: %target-typecheck-verify-swift -Wwarning EmbeddedRestrictions -verify-additional-prefix nonembedded- +// RUN: %target-typecheck-verify-swift -Wwarning EmbeddedRestrictions -enable-experimental-feature Embedded -verify-additional-prefix embedded- + +// REQUIRES: swift_in_compiler +// REQUIRES: swift_feature_Embedded + +// --------------------------------------------------------------------------- +// Untyped throws +// --------------------------------------------------------------------------- + +enum MyError: Error { +case failed +} + +// expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{28-28=(<#thrown error type#>)}} +func untypedThrows() throws { } + +// expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{41-41=(<#thrown error type#>)}} +func rethrowingFunction(param: () throws -> Void) rethrows { } + +// expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{29-29=(<#thrown error type#>)}} +typealias FnType = () throws -> Void + +func untypedThrowsInBody() { + // expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{12-12=(<#thrown error type#>)}} + do throws { + throw MyError.failed + } catch { + } + + // expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{19-19=(<#thrown error type#>)}} + _ = { (x) throws in x + 1 } +} + +struct SomeStruct { + // expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{16-16=(<#thrown error type#>)}} + init() throws { } + + var value: Int { + // expected-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{15-15=(<#thrown error type#>)}} + get throws { + 0 + } + } +} + +// --------------------------------------------------------------------------- +// weak/unowned references +// --------------------------------------------------------------------------- + +// Note: this have always been an error in Embedded Swift. Make sure they stay +// that way, but are emitted as warnings when the restrictions are enabled. + +public class MyClass { } + +public struct MyStruct { + var normalVar: MyClass + weak var weakVar: MyClass? // expected-nonembedded-warning {{attribute 'weak' cannot be used in Embedded Swift}} + // expected-embedded-error@-1 {{attribute 'weak' cannot be used in Embedded Swift}} + + unowned var unownedVar: MyClass // expected-nonembedded-warning {{attribute 'unowned' cannot be used in Embedded Swift}} + // expected-embedded-error @-1{{attribute 'unowned' cannot be used in Embedded Swift}} + + unowned(unsafe) var unownedUnsafe: MyClass +} diff --git a/test/embedded/weak-unowned.swift b/test/embedded/weak-unowned.swift index 9700b1df6cff1..7ac40301cf07d 100644 --- a/test/embedded/weak-unowned.swift +++ b/test/embedded/weak-unowned.swift @@ -10,7 +10,7 @@ public class MyClass { } public struct MyStruct { var normalVar: MyClass - weak var weakVar: MyClass? // expected-error {{attribute 'weak' cannot be used in embedded Swift}} - unowned var unownedVar: MyClass // expected-error {{attribute 'unowned' cannot be used in embedded Swift}} + weak var weakVar: MyClass? // expected-error {{attribute 'weak' cannot be used in Embedded Swift}} + unowned var unownedVar: MyClass // expected-error {{attribute 'unowned' cannot be used in Embedded Swift}} unowned(unsafe) var unownedUnsafe: MyClass } diff --git a/userdocs/diagnostics/diagnostic-groups.md b/userdocs/diagnostics/diagnostic-groups.md index aba43508cbf30..609e732c154d5 100644 --- a/userdocs/diagnostics/diagnostic-groups.md +++ b/userdocs/diagnostics/diagnostic-groups.md @@ -30,6 +30,7 @@ Or upgrade all warnings except deprecated declaration to errors: - - - +- - - - diff --git a/userdocs/diagnostics/embedded-restrictions.md b/userdocs/diagnostics/embedded-restrictions.md new file mode 100644 index 0000000000000..b9805a028c7ce --- /dev/null +++ b/userdocs/diagnostics/embedded-restrictions.md @@ -0,0 +1,13 @@ +# 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. + +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: + + class Node { + weak var parent: Node? // error: attribute 'weak' cannot be used in Embedded Swift + } + +## See Also + +- [A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md) From 865c643cac7ce82007c9ab0a30050f8f7c3b281b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Sun, 14 Sep 2025 09:52:58 -0700 Subject: [PATCH 2/2] Suppress Embedded Restriction diagnostics in code that won't be compiled as Embedded When emitting diagnostics for the Embedded Swift restrictions outside of Embedded Swift mode, consider `#if $Embedded` and `#if hasFeature(Embedded)` configurations. If the code where we would emit the diagnostic would be disabled in Embedded Swift by one of those checks, don't emit the diagnostic. This helps code that can compile either with Embedded or regular Swift stay within the restrictions on Embedded Swift. --- include/swift/Bridging/ASTGen.h | 5 + lib/ASTGen/Sources/ASTGen/CMakeLists.txt | 1 + .../Sources/ASTGen/EmbeddedSupport.swift | 142 ++++++++++++++++++ lib/ASTGen/Sources/ASTGen/SourceFile.swift | 8 + lib/Sema/TypeCheckEmbedded.cpp | 17 +++ test/embedded/restrictions.swift | 19 +++ 6 files changed, 192 insertions(+) create mode 100644 lib/ASTGen/Sources/ASTGen/EmbeddedSupport.swift diff --git a/include/swift/Bridging/ASTGen.h b/include/swift/Bridging/ASTGen.h index 09248b1a0344d..857a82edf42e7 100644 --- a/include/swift/Bridging/ASTGen.h +++ b/include/swift/Bridging/ASTGen.h @@ -117,6 +117,11 @@ intptr_t swift_ASTGen_configuredRegions( void swift_ASTGen_freeConfiguredRegions( BridgedIfConfigClauseRangeInfo *_Nullable regions, intptr_t numRegions); +intptr_t swift_ASTGen_activeInEmbeddedSwift( + BridgedASTContext astContext, + void *_Nonnull sourceFile, + swift::SourceLoc location); + bool swift_ASTGen_validateUnqualifiedLookup( void *_Nonnull sourceFile, BridgedASTContext astContext, diff --git a/lib/ASTGen/Sources/ASTGen/CMakeLists.txt b/lib/ASTGen/Sources/ASTGen/CMakeLists.txt index 91271f87e84cb..0094fbc0c3f28 100644 --- a/lib/ASTGen/Sources/ASTGen/CMakeLists.txt +++ b/lib/ASTGen/Sources/ASTGen/CMakeLists.txt @@ -9,6 +9,7 @@ add_pure_swift_host_library(swiftASTGen STATIC CXX_INTEROP Decls.swift Diagnostics.swift DiagnosticsBridge.swift + EmbeddedSupport.swift Exprs.swift Fingerprint.swift Generics.swift diff --git a/lib/ASTGen/Sources/ASTGen/EmbeddedSupport.swift b/lib/ASTGen/Sources/ASTGen/EmbeddedSupport.swift new file mode 100644 index 0000000000000..3de35744eeb2f --- /dev/null +++ b/lib/ASTGen/Sources/ASTGen/EmbeddedSupport.swift @@ -0,0 +1,142 @@ +//===--- EmbeddedSupport.swift --------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022-2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ASTBridging +import BasicBridging +import SwiftIfConfig +import SwiftSyntax + +extension ExportedSourceFile { + /// Return the configured regions for this source file. + mutating func configuredRegionsIfEmbedded(astContext: BridgedASTContext) -> ConfiguredRegions { + if let _configuredRegionsIfEmbedded { + return _configuredRegionsIfEmbedded + } + + // Ensure that we've already computed the configured regions, which can + // emit diagnostics. + _ = configuredRegions(astContext: astContext) + + let configuration = EmbeddedBuildConfiguration( + ctx: astContext, + sourceBuffer: buffer + ) + + let regions = syntax.configuredRegions(in: configuration) + _configuredRegionsIfEmbedded = regions + return regions + } +} + +/// Determine whether the given source location is active when this source +/// file is compiled in Embedded Swift. +@_cdecl("swift_ASTGen_activeInEmbeddedSwift") +public func activeInEmbeddedSwift( + astContext: BridgedASTContext, + sourceFilePtr: UnsafeMutableRawPointer, + at location: SourceLoc +) -> Int { + guard let token = findToken( + in: UnsafeRawPointer(sourceFilePtr), + at: location.raw?.assumingMemoryBound(to: UInt8.self) + ) else { + return 0 + } + + let sourceFile = sourceFilePtr.assumingMemoryBound(to: ExportedSourceFile.self) + let regions = sourceFile.pointee.configuredRegionsIfEmbedded(astContext: astContext) + return regions.isActive(token) == .active ? 1 : 0 +} + +/// A build configuration that is a thin shim over the +/// CompilerBuildConfiguration that enables the Embedded language feature, +/// allowing the compiler to determine whether code we're in will be available +/// in an embedded context or not. +struct EmbeddedBuildConfiguration: BuildConfiguration { + let configuration: CompilerBuildConfiguration + + init(ctx: BridgedASTContext, sourceBuffer: UnsafeBufferPointer) { + self.configuration = .init(ctx: ctx, sourceBuffer: sourceBuffer) + } + + func isCustomConditionSet(name: String) throws -> Bool { + // $Embedded is set when building Embedded Swift + if name == "$Embedded" { + return true + } + + return try configuration.isCustomConditionSet(name: name) + } + + func hasFeature(name: String) throws -> Bool { + // The "Embedded" feature is set when building Embedded Swift. + if name == "Embedded" { + return true + } + + return try configuration.hasFeature(name: name) + } + + func hasAttribute(name: String) throws -> Bool { + return try configuration.hasAttribute(name: name) + } + + func canImport( + importPath: [(TokenSyntax, String)], + version: CanImportVersion + ) throws -> Bool { + // FIXME: We would prefer to call the underlying canImport in some kind of + // "replay" mode that is not allowed to produce diagnostics or trigger any + // loading. + return false + } + + func isActiveTargetOS(name: String) throws -> Bool { + return try configuration.isActiveTargetOS(name: name) + } + + func isActiveTargetArchitecture(name: String) throws -> Bool { + return try configuration.isActiveTargetArchitecture(name: name) + } + + func isActiveTargetEnvironment(name: String) throws -> Bool { + return try configuration.isActiveTargetEnvironment(name: name) + } + + func isActiveTargetRuntime(name: String) throws -> Bool { + return try configuration.isActiveTargetRuntime(name: name) + } + + func isActiveTargetPointerAuthentication(name: String) throws -> Bool { + return try configuration.isActiveTargetPointerAuthentication(name: name) + } + + var targetPointerBitWidth: Int { + return configuration.targetPointerBitWidth + } + + var targetAtomicBitWidths: [Int] { + return configuration.targetAtomicBitWidths + } + + var endianness: Endianness { + return configuration.endianness + } + + var languageVersion: VersionTuple { + return configuration.languageVersion + } + + var compilerVersion: VersionTuple { + return configuration.compilerVersion + } +} diff --git a/lib/ASTGen/Sources/ASTGen/SourceFile.swift b/lib/ASTGen/Sources/ASTGen/SourceFile.swift index f545445df088d..c9981bd320063 100644 --- a/lib/ASTGen/Sources/ASTGen/SourceFile.swift +++ b/lib/ASTGen/Sources/ASTGen/SourceFile.swift @@ -43,6 +43,14 @@ public struct ExportedSourceFile { /// This is a cached value; access via configuredRegions(astContext:). var _configuredRegions: ConfiguredRegions? = nil + /// Configured regions for this source file assuming if it were being treated + /// as Embedded Swift. This is used only when we are compiling non-Embedded + /// Swift but diagnosing uses of constructs that aren't allowed in Embedded + /// Swift. + /// + /// This is a cached value; access via configuredRegionsAsEmbedded(astContext:). + var _configuredRegionsIfEmbedded: ConfiguredRegions? = nil + public func position(of location: SourceLoc) -> AbsolutePosition? { let sourceFileBaseAddress = UnsafeRawPointer(buffer.baseAddress!) guard let rawAddress = location.raw else { diff --git a/lib/Sema/TypeCheckEmbedded.cpp b/lib/Sema/TypeCheckEmbedded.cpp index fe408e3461297..17695b3d44193 100644 --- a/lib/Sema/TypeCheckEmbedded.cpp +++ b/lib/Sema/TypeCheckEmbedded.cpp @@ -19,7 +19,10 @@ #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/Effects.h" +#include "swift/AST/SourceFile.h" #include "swift/AST/Types.h" +#include "swift/Basic/SourceLoc.h" +#include "swift/Bridging/ASTGen.h" using namespace swift; @@ -40,6 +43,20 @@ swift::shouldDiagnoseEmbeddedLimitations(const DeclContext *dc, SourceLoc loc, if (diags.isIgnoredDiagnostic(diag::untyped_throws_in_embedded_swift.ID)) return std::nullopt; +#if SWIFT_BUILD_SWIFT_SYNTAX + // If we are not in Embedded Swift, check whether the location we are + // diagnosing at is likely to be active when compiling Embedded Swift. If not, + // suppress the diagnostic. + auto sourceFile = dc->getParentSourceFile(); + if (!dc->getASTContext().LangOpts.hasFeature(Feature::Embedded) && + sourceFile && + !swift_ASTGen_activeInEmbeddedSwift(sourceFile->getASTContext(), + sourceFile->getExportedSourceFile(), + loc)) { + return std::nullopt; + } +#endif + // If this was always an error in Embedded Swift, we aren't in Embedded Swift // now, so downgrade to a warning. if (wasAlwaysEmbeddedError) diff --git a/test/embedded/restrictions.swift b/test/embedded/restrictions.swift index 003d55d7ef07c..4046dded30ef6 100644 --- a/test/embedded/restrictions.swift +++ b/test/embedded/restrictions.swift @@ -63,3 +63,22 @@ public struct MyStruct { unowned(unsafe) var unownedUnsafe: MyClass } + +// --------------------------------------------------------------------------- +// #if handling to suppress diagnostics for non-Embedded-only code +// --------------------------------------------------------------------------- + +#if $Embedded + +// expected-embedded-warning@+1{{untyped throws is not available in Embedded Swift; add a thrown error type with '(type)'}}{{31-31=(<#thrown error type#>)}} +func stillProblematic() throws { } + +#else + +func notProblematicAtAll() throws { } + +#endif + +#if !hasFeature(Embedded) +func stillNotProblematicAtAll() throws { } +#endif