From a32782bcbc9833590171ec2aa4db50c806f8536c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 20 May 2025 16:07:44 +0100 Subject: [PATCH 1/7] Make InferIsolatedConformances a migratable upcoming feature When migrating, provide warnings that add 'nonisolated' to nonisolated conformances that don't already have it and would end up being inferred to be isolated under the upcoming feature. --- include/swift/AST/DiagnosticsSema.def | 3 ++ include/swift/Basic/Features.def | 2 +- lib/Sema/TypeCheckProtocol.cpp | 43 +++++++++++++++++++ .../isolated_conformance_migrate.swift | 33 ++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 test/Concurrency/isolated_conformance_migrate.swift diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 6126ba75130f3..418e61454f1a1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8619,6 +8619,9 @@ GROUPED_ERROR(isolated_conformance_with_sendable_simple,IsolatedConformances, GROUPED_ERROR(isolated_conformance_wrong_domain,IsolatedConformances,none, "%0 conformance of %1 to %2 cannot be used in %3 context", (ActorIsolation, Type, DeclName, ActorIsolation)) +GROUPED_WARNING(isolated_conformance_will_become_nonisolated,IsolatedConformances,none, + "conformance of %0 to %1 should be marked 'nonisolated' to retain its behavior with upcoming feature 'InferIsolatedConformances'", + (const ValueDecl *, const ValueDecl *)) //===----------------------------------------------------------------------===// // MARK: @_inheritActorContext diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 7deea93a268a5..519a6620bbf84 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -283,7 +283,7 @@ UPCOMING_FEATURE(GlobalActorIsolatedTypesUsability, 0434, 6) MIGRATABLE_UPCOMING_FEATURE(ExistentialAny, 335, 7) UPCOMING_FEATURE(InternalImportsByDefault, 409, 7) UPCOMING_FEATURE(MemberImportVisibility, 444, 7) -UPCOMING_FEATURE(InferIsolatedConformances, 470, 7) +MIGRATABLE_UPCOMING_FEATURE(InferIsolatedConformances, 470, 7) MIGRATABLE_UPCOMING_FEATURE(NonisolatedNonsendingByDefault, 461, 7) // Optional language features / modes diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 29bfb2f3f23a6..e67e7ac2560d3 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -6675,6 +6675,49 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) { } } + // If we are migrating to InferIsolatedConformances, and the + // nominal type is global-actor-isolated, look for conformances + // that are nonisolated but were not explicitly marked as such. + // These conformances will need to be marked 'nonisolated' to + // retain their current behavior. + if (Context.LangOpts + .getFeatureState(Feature::InferIsolatedConformances) + .isEnabledForMigration() && + getActorIsolation(const_cast(nominal)) + .isGlobalActor()) { + for (auto conformance : conformances) { + auto normal = dyn_cast(conformance); + if (!normal) + continue; + + // Explicit nonisolated and @preconcurrency suppress this. + auto options = normal->getOptions(); + if (options.contains(ProtocolConformanceFlags::Nonisolated) || + options.contains(ProtocolConformanceFlags::Preconcurrency)) + continue; + + // Only consider conformances that were explicitly written in the source. + if (normal->getSourceKind() != ConformanceEntryKind::Explicit) + continue; + + // Only consider conformances to non-marker, nonisolated protocols. + auto proto = normal->getProtocol(); + if (proto->isMarkerProtocol() || getActorIsolation(proto).isActorIsolated()) + continue; + + // Only nonisolated conformances can be affected. + if (!conformance->getIsolation().isNonisolated()) + continue; + + auto nameLoc = normal->getProtocolNameLoc(); + if (nameLoc.isValid()) { + Context.Diags.diagnose( + nameLoc, diag::isolated_conformance_will_become_nonisolated, nominal, proto) + .fixItInsert(nameLoc, "nonisolated "); + } + } + } + if (Context.TypeCheckerOpts.DebugGenericSignatures && !conformances.empty()) { // Now that they're filled out, print out information about the conformances diff --git a/test/Concurrency/isolated_conformance_migrate.swift b/test/Concurrency/isolated_conformance_migrate.swift new file mode 100644 index 0000000000000..41e1b199f43d5 --- /dev/null +++ b/test/Concurrency/isolated_conformance_migrate.swift @@ -0,0 +1,33 @@ +// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-upcoming-feature InferIsolatedConformances:migrate %s + +// REQUIRES: concurrency + + +protocol P { } +protocol Q: P { } + +struct S: P { } + +struct S2: Q { } + +@MainActor +struct MA1: P { } +// expected-warning@-1{{conformance of 'MA1' to 'P' should be marked 'nonisolated' to retain its behavior with upcoming feature 'InferIsolatedConformances'}}{{13-13=nonisolated }} + +@MainActor +struct MA2: Q { } +// expected-warning@-1{{conformance of 'MA2' to 'Q' should be marked 'nonisolated' to retain its behavior with upcoming feature 'InferIsolatedConformances'}}{{13-13=nonisolated }} + +@MainActor +struct MA3 { } + +extension MA3: P, Q { } +// expected-warning@-1{{conformance of 'MA3' to 'P' should be marked 'nonisolated' to retain its behavior with upcoming feature 'InferIsolatedConformances'}}{{16-16=nonisolated }} +// expected-warning@-2{{conformance of 'MA3' to 'Q' should be marked 'nonisolated' to retain its behavior with upcoming feature 'InferIsolatedConformances'}}{{19-19=nonisolated }} + +@MainActor +struct MA4: @MainActor P { } + +@MainActor +struct MA5: nonisolated P { } + From abad2fae0f1d79ed188798fbe0d9ab16011cc2d9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 20 May 2025 17:06:18 +0100 Subject: [PATCH 2/7] Make the optional feature StrictMemorySafety migratable This feature is essentially self-migrating, but fit it into the migration flow by marking it as migratable, adding `-strict-memory-safety:migrate`, and introducing a test. --- include/swift/Basic/Features.def | 8 ++++++- include/swift/Basic/LangOptions.h | 4 +++- include/swift/Option/Options.td | 4 ++++ lib/Basic/Feature.cpp | 3 +++ lib/Basic/LangOptions.cpp | 8 +++++-- lib/Driver/ToolChains.cpp | 3 ++- lib/Frontend/CompilerInvocation.cpp | 2 ++ .../DerivedConformanceDistributedActor.cpp | 2 +- .../DerivedConformanceRawRepresentable.cpp | 2 +- lib/Sema/TypeCheckDeclOverride.cpp | 2 +- lib/Sema/TypeCheckDeclPrimary.cpp | 3 ++- lib/Sema/TypeCheckEffects.cpp | 5 +++-- lib/Sema/TypeCheckProtocol.cpp | 3 ++- test/Unsafe/migrate.swift | 21 +++++++++++++++++++ 14 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 test/Unsafe/migrate.swift diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 519a6620bbf84..b22ae9e1145e9 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -155,6 +155,11 @@ #endif #endif +#ifndef MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE + #define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) \ + OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, #Name) +#endif + #ifndef UPCOMING_FEATURE #define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ LANGUAGE_FEATURE(FeatureName, SENumber, #FeatureName) @@ -290,7 +295,7 @@ MIGRATABLE_UPCOMING_FEATURE(NonisolatedNonsendingByDefault, 461, 7) /// Diagnose uses of language constructs and APIs that can violate memory /// safety. -OPTIONAL_LANGUAGE_FEATURE(StrictMemorySafety, 458, "Strict memory safety") +MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(StrictMemorySafety, 458, "Strict memory safety") // Experimental features @@ -521,6 +526,7 @@ EXPERIMENTAL_FEATURE(ModuleSelector, false) #undef UPCOMING_FEATURE #undef MIGRATABLE_UPCOMING_FEATURE #undef MIGRATABLE_EXPERIMENTAL_FEATURE +#undef MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE #undef BASELINE_LANGUAGE_FEATURE #undef OPTIONAL_LANGUAGE_FEATURE #undef CONDITIONALLY_SUPPRESSIBLE_EXPERIMENTAL_FEATURE diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 9e557da32c21a..66b79d4636457 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -872,7 +872,9 @@ namespace swift { FeatureState getFeatureState(Feature feature) const; /// Returns whether the given feature is enabled. - bool hasFeature(Feature feature) const; + /// + /// If allowMigration is set, also returns true when the feature has been enabled for migration. + bool hasFeature(Feature feature, bool allowMigration = false) const; /// Returns whether a feature with the given name is enabled. Returns /// `false` if a feature by this name is not known. diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 7ac36a3b705ef..f71bdccf98c07 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -1018,6 +1018,10 @@ def strict_memory_safety : Flag<["-"], "strict-memory-safety">, Flags<[FrontendOption, ModuleInterfaceOptionIgnorable, SwiftAPIDigesterOption, SwiftSynthesizeInterfaceOption]>, HelpText<"Enable strict memory safety checking">; +def strict_memory_safety_migrate : Flag<["-"], "strict-memory-safety:migrate">, + Flags<[FrontendOption, ModuleInterfaceOptionIgnorable, + SwiftAPIDigesterOption, SwiftSynthesizeInterfaceOption]>, + HelpText<"Enable migration to strict memory safety checking">; def Rpass_EQ : Joined<["-"], "Rpass=">, Flags<[FrontendOption]>, diff --git a/lib/Basic/Feature.cpp b/lib/Basic/Feature.cpp index 931ba4ffbfff5..597f310d2222f 100644 --- a/lib/Basic/Feature.cpp +++ b/lib/Basic/Feature.cpp @@ -73,6 +73,7 @@ bool Feature::isMigratable() const { switch (kind) { #define MIGRATABLE_UPCOMING_FEATURE(FeatureName, SENumber, Version) #define MIGRATABLE_EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) +#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ case Feature::FeatureName: #include "swift/Basic/Features.def" @@ -82,6 +83,8 @@ bool Feature::isMigratable() const { case Feature::FeatureName: #define MIGRATABLE_EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ case Feature::FeatureName: +#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) \ + case Feature::FeatureName: #include "swift/Basic/Features.def" return true; } diff --git a/lib/Basic/LangOptions.cpp b/lib/Basic/LangOptions.cpp index dcea112958635..76e63fd9afe4f 100644 --- a/lib/Basic/LangOptions.cpp +++ b/lib/Basic/LangOptions.cpp @@ -335,13 +335,17 @@ LangOptions::FeatureState LangOptions::getFeatureState(Feature feature) const { return state; } -bool LangOptions::hasFeature(Feature feature) const { - if (featureStates.getState(feature).isEnabled()) +bool LangOptions::hasFeature(Feature feature, bool allowMigration) const { + auto state = featureStates.getState(feature); + if (state.isEnabled()) return true; if (auto version = feature.getLanguageVersion()) return isSwiftVersionAtLeast(*version); + if (allowMigration && state.isEnabledForMigration()) + return true; + return false; } diff --git a/lib/Driver/ToolChains.cpp b/lib/Driver/ToolChains.cpp index e79eea48605a5..24507fb8dac25 100644 --- a/lib/Driver/ToolChains.cpp +++ b/lib/Driver/ToolChains.cpp @@ -284,7 +284,8 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI, options::OPT_disable_experimental_feature, options::OPT_enable_upcoming_feature, options::OPT_disable_upcoming_feature}); - inputArgs.AddLastArg(arguments, options::OPT_strict_memory_safety); + inputArgs.AddLastArg(arguments, options::OPT_strict_memory_safety, + options::OPT_strict_memory_safety_migrate); inputArgs.AddLastArg(arguments, options::OPT_warn_implicit_overrides); inputArgs.AddLastArg(arguments, options::OPT_typo_correction_limit); inputArgs.AddLastArg(arguments, options::OPT_enable_app_extension); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index ef3e95377b00d..e48ca143a4645 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -996,6 +996,8 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, if (Args.hasArg(OPT_strict_memory_safety)) Opts.enableFeature(Feature::StrictMemorySafety); + else if (Args.hasArg(OPT_strict_memory_safety_migrate)) + Opts.enableFeature(Feature::StrictMemorySafety, /*forMigration=*/true); return HadError; } diff --git a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp index c7b0fc44fd114..3b2f600d7f460 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceDistributedActor.cpp @@ -171,7 +171,7 @@ deriveBodyDistributed_doInvokeOnReturn(AbstractFunctionDecl *afd, void *arg) { new (C) DeclRefExpr(ConcreteDeclRef(returnTypeParam), dloc, implicit))})); - if (C.LangOpts.hasFeature(Feature::StrictMemorySafety)) + if (C.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true)) resultLoadCall = new (C) UnsafeExpr(sloc, resultLoadCall, Type(), true); auto resultPattern = NamedPattern::createImplicit(C, resultVar); diff --git a/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp b/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp index 1463e794c2f84..a56b54b79bcf8 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceRawRepresentable.cpp @@ -106,7 +106,7 @@ deriveBodyRawRepresentable_raw(AbstractFunctionDecl *toRawDecl, void *) { auto *argList = ArgumentList::forImplicitCallTo(functionRef->getName(), {selfRef, typeExpr}, C); Expr *call = CallExpr::createImplicit(C, functionRef, argList); - if (C.LangOpts.hasFeature(Feature::StrictMemorySafety)) + if (C.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true)) call = UnsafeExpr::createImplicit(C, SourceLoc(), call); auto *returnStmt = ReturnStmt::createImplicit(C, call); auto body = BraceStmt::create(C, SourceLoc(), ASTNode(returnStmt), diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 6e5ac72872d1b..3e46cb956d5d6 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -2257,7 +2257,7 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) { diagnoseOverrideForAvailability(override, base); } - if (ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) { + if (ctx.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true)) { // If the override is unsafe but the base declaration is not, then the // inheritance itself is unsafe. auto subs = SubstitutionMap::getOverrideSubstitutions(base, override); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index f7fef359f9466..3136453202b09 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -2413,7 +2413,8 @@ class DeclChecker : public DeclVisitor { // If strict memory safety checking is enabled, check the storage // of the nominal type. - if (Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety) && + if (Ctx.LangOpts.hasFeature( + Feature::StrictMemorySafety, /*allowMigration=*/true) && !isa(nominal)) { checkUnsafeStorage(nominal); } diff --git a/lib/Sema/TypeCheckEffects.cpp b/lib/Sema/TypeCheckEffects.cpp index f5109c20bf0d9..87acdfb88320e 100644 --- a/lib/Sema/TypeCheckEffects.cpp +++ b/lib/Sema/TypeCheckEffects.cpp @@ -4554,7 +4554,8 @@ class CheckEffectsCoverage : public EffectsHandlingWalker if (classification.hasUnsafe()) { // If there is no such effect, complain. if (S->getUnsafeLoc().isInvalid() && - Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) { + Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety, + /*allowMigration=*/true)) { auto insertionLoc = S->getPattern()->getStartLoc(); Ctx.Diags.diagnose(S->getForLoc(), diag::for_unsafe_without_unsafe) .fixItInsert(insertionLoc, "unsafe "); @@ -4801,7 +4802,7 @@ class CheckEffectsCoverage : public EffectsHandlingWalker void diagnoseUncoveredUnsafeSite( const Expr *anchor, ArrayRef unsafeUses) { - if (!Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety)) + if (!Ctx.LangOpts.hasFeature(Feature::StrictMemorySafety, /*allowMigration=*/true)) return; const auto &[loc, insertText] = getFixItForUncoveredSite(anchor, "unsafe"); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index e67e7ac2560d3..bd3846195daa1 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2646,7 +2646,8 @@ checkIndividualConformance(NormalProtocolConformance *conformance) { // If we're enforcing strict memory safety and this conformance hasn't // opted out, look for safe/unsafe witness mismatches. if (conformance->getExplicitSafety() == ExplicitSafety::Unspecified && - Context.LangOpts.hasFeature(Feature::StrictMemorySafety)) { + Context.LangOpts.hasFeature(Feature::StrictMemorySafety, + /*allowMigration=*/true)) { // Collect all of the unsafe uses for this conformance. SmallVector unsafeUses; for (auto requirement: Proto->getMembers()) { diff --git a/test/Unsafe/migrate.swift b/test/Unsafe/migrate.swift new file mode 100644 index 0000000000000..5e422999e6b92 --- /dev/null +++ b/test/Unsafe/migrate.swift @@ -0,0 +1,21 @@ +// RUN: %target-swift-frontend -typecheck -verify -swift-version 6 -strict-memory-safety:migrate %s + +// REQUIRES: concurrency + +@preconcurrency import _Concurrency + +@unsafe func f() { } + +func g() { + f() // expected-warning{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{3-3=unsafe }} + // expected-note@-1{{reference to unsafe global function 'f()'}} +} + +protocol P { + func f() +} + +struct Conforming: P { + // expected-warning@-1{{conformance of 'Conforming' to protocol 'P' involves unsafe code; use '@unsafe' to indicate that the conformance is not memory-safe}}{{20-20=@unsafe }} + @unsafe func f() { } // expected-note{{unsafe instance method 'f()' cannot satisfy safe requirement}} +} From 6a800c98b56cb5cd396bdadc07025317ec075a03 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 May 2025 07:58:30 +0100 Subject: [PATCH 3/7] Add addition test case for "if case unsafe" --- test/Unsafe/safe.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/Unsafe/safe.swift b/test/Unsafe/safe.swift index 4a22410611345..25b0249c4df5f 100644 --- a/test/Unsafe/safe.swift +++ b/test/Unsafe/safe.swift @@ -348,4 +348,14 @@ func testSwitch(se: SomeEnum) { case unsafe someEnumValue: break default: break } + + if case someEnumValue = unsafe se { } + // expected-warning@-1{{expression uses unsafe constructs but is not marked with 'unsafe'}}{{11-11=unsafe }} + // expected-note@-2{{argument #0 in call to operator function '~=' has unsafe type 'SomeEnum'}} + // expected-note@-3{{argument #1 in call to operator function '~=' has unsafe type 'SomeEnum'}} + // expected-note@-4{{reference to unsafe type 'SomeEnum'}} + // expected-note@-5{{reference to unsafe var 'someEnumValue'}} + // expected-note@-6{{reference to let '$match' involves unsafe type 'SomeEnum'}} + + if case unsafe someEnumValue = unsafe se { } } From 7f2649ff332c1d4660c9a31fec0146569f76a0ae Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 May 2025 09:21:13 +0100 Subject: [PATCH 4/7] Add the set of diagnostic categories to each migratable feature The name of a migratable feature might differ from the names of the diagnostic groups containing the diagnostics that are used to drive migration for that feature. Provide the set of diagnostic categories that are associated with each migratable feature as part of the supported features list. --- lib/Basic/SupportedFeatures.cpp | 36 ++++++++++++++++++++ test/Frontend/print-supported-features.swift | 4 +-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/Basic/SupportedFeatures.cpp b/lib/Basic/SupportedFeatures.cpp index 3ddf661b44fbb..8f5318b52fa75 100644 --- a/lib/Basic/SupportedFeatures.cpp +++ b/lib/Basic/SupportedFeatures.cpp @@ -13,6 +13,7 @@ #include #include +#include "swift/AST/DiagnosticGroups.h" #include "swift/Basic/Feature.h" #include "swift/Frontend/Frontend.h" @@ -22,6 +23,32 @@ using namespace swift; namespace swift { namespace features { + +/// The subset of diagnostic groups (called categories by the diagnostic machinery) whose diagnostics should be +/// considered to be part of the migration for this feature. +/// +/// When making a +static std::vector migratableCategories(Feature feature) { + switch (feature) { + case Feature::InnerKind::ExistentialAny: + return { DiagGroupID::ExistentialAny }; + case Feature::InnerKind::InferIsolatedConformances: + return { DiagGroupID::IsolatedConformances }; + case Feature::InnerKind::NonisolatedNonsendingByDefault: + return { DiagGroupID::NonisolatedNonsendingByDefault }; + case Feature::InnerKind::StrictMemorySafety: + return { DiagGroupID::StrictMemorySafety }; + + // Provide unreachable cases for all of the non-migratable features. +#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) case Feature::FeatureName: +#define MIGRATABLE_UPCOMING_FEATURE(FeatureName, SENumber, Version) +#define MIGRATABLE_EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) +#define MIGRATABLE_OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Name) +#include "swift/Basic/Features.def" + llvm_unreachable("Not a migratable feature"); + } +} + /// Print information about what features upcoming/experimental are /// supported by the compiler. /// The information includes whether a feature is adoptable and for @@ -50,6 +77,15 @@ void printSupportedFeatures(llvm::raw_ostream &out) { out << "{ \"name\": \"" << feature.getName() << "\""; if (feature.isMigratable()) { out << ", \"migratable\": true"; + + auto categories = migratableCategories(feature); + out << ", \"categories\": ["; + llvm::interleave(categories, [&out](DiagGroupID diagGroupID) { + out << "\"" << getDiagGroupInfoByID(diagGroupID).name << "\""; + }, [&out] { + out << ", "; + }); + out << "]"; } if (auto version = feature.getLanguageVersion()) { out << ", \"enabled_in\": \"" << *version << "\""; diff --git a/test/Frontend/print-supported-features.swift b/test/Frontend/print-supported-features.swift index a4d499dcbccaf..9f05115d76134 100644 --- a/test/Frontend/print-supported-features.swift +++ b/test/Frontend/print-supported-features.swift @@ -2,9 +2,9 @@ // CHECK: "features": { // CHECK-NEXT: "upcoming": [ -// CHECK: { "name": "{{.*}}"{{, "migratable": true}}, "enabled_in": "{{.*}}" } +// CHECK: { "name": "InferIsolatedConformances", "migratable": true, "categories": ["IsolatedConformances"], "enabled_in": "7" }, // CHECK: ], -// CHECK-NEXT: "experimental": [ +// CHECK: "experimental": [ // CHECK: { "name": "{{.*}}" } // CHECK: ] // CHECK: } From 414adb55ca09c088a45740132da487aab301df5f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 May 2025 09:33:44 +0100 Subject: [PATCH 5/7] Add optional language features to the supported features output Optional language features don't have a specific "-enable-*" flag, because they're rare and don't fit the same upcoming/experimental distinction. Add a flag_name field to provide the flag name as well. --- lib/Basic/SupportedFeatures.cpp | 29 ++++++++++++++++++++ test/Frontend/print-supported-features.swift | 3 ++ 2 files changed, 32 insertions(+) diff --git a/lib/Basic/SupportedFeatures.cpp b/lib/Basic/SupportedFeatures.cpp index 8f5318b52fa75..068f83c52f1b3 100644 --- a/lib/Basic/SupportedFeatures.cpp +++ b/lib/Basic/SupportedFeatures.cpp @@ -49,11 +49,31 @@ static std::vector migratableCategories(Feature feature) { } } +/// For optional language features, return the flag name used by the compiler to enable the feature. For all others, +/// returns an empty optional. +static std::optional optionalFlagName(Feature feature) { + switch (feature) { + case Feature::StrictMemorySafety: + return "-strict-memory-safety"; + +#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) case Feature::FeatureName: +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#include "swift/Basic/Features.def" + return std::nullopt; + } +} + /// Print information about what features upcoming/experimental are /// supported by the compiler. /// The information includes whether a feature is adoptable and for /// upcoming features - what is the first mode it's introduced. void printSupportedFeatures(llvm::raw_ostream &out) { + std::array optional{ +#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) Feature::FeatureName, +#include "swift/Basic/Features.def" + }; + std::array upcoming{ #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) #define UPCOMING_FEATURE(FeatureName, SENumber, Version) Feature::FeatureName, @@ -90,10 +110,19 @@ void printSupportedFeatures(llvm::raw_ostream &out) { if (auto version = feature.getLanguageVersion()) { out << ", \"enabled_in\": \"" << *version << "\""; } + + if (auto flagName = optionalFlagName(feature)) { + out << ", \"flag_name\": \"" << *flagName << "\""; + } + out << " }"; }; out << " \"features\": {\n"; + out << " \"optional\": [\n"; + llvm::interleave(optional, printFeature, [&out] { out << ",\n"; }); + out << "\n ],\n"; + out << " \"upcoming\": [\n"; llvm::interleave(upcoming, printFeature, [&out] { out << ",\n"; }); out << "\n ],\n"; diff --git a/test/Frontend/print-supported-features.swift b/test/Frontend/print-supported-features.swift index 9f05115d76134..78ecb13498d35 100644 --- a/test/Frontend/print-supported-features.swift +++ b/test/Frontend/print-supported-features.swift @@ -1,6 +1,9 @@ // RUN: %target-swift-frontend -print-supported-features | %FileCheck %s // CHECK: "features": { +// CHECK-NEXT: "optional": [ +// CHECK: { "name": "StrictMemorySafety", "migratable": true, "categories": ["StrictMemorySafety"], "flag_name": "-strict-memory-safety" } +// CHECK-NEXT: ], // CHECK-NEXT: "upcoming": [ // CHECK: { "name": "InferIsolatedConformances", "migratable": true, "categories": ["IsolatedConformances"], "enabled_in": "7" }, // CHECK: ], From 2eeb3946e3a25d31df7ca9b7afe3b0aeb1e0a4c0 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 May 2025 12:13:39 +0100 Subject: [PATCH 6/7] Add unnecessary REQUIRES: line for a feature --- test/Concurrency/isolated_conformance_migrate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Concurrency/isolated_conformance_migrate.swift b/test/Concurrency/isolated_conformance_migrate.swift index 41e1b199f43d5..2f2531c110eb2 100644 --- a/test/Concurrency/isolated_conformance_migrate.swift +++ b/test/Concurrency/isolated_conformance_migrate.swift @@ -1,7 +1,7 @@ // RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-upcoming-feature InferIsolatedConformances:migrate %s // REQUIRES: concurrency - +// REQUIRES: swift_feature_InferIsolatedConformances protocol P { } protocol Q: P { } From 32267f4971262e742e3fe7613a491f90fbe2695b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 May 2025 12:15:53 +0100 Subject: [PATCH 7/7] Finish comment --- lib/Basic/SupportedFeatures.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Basic/SupportedFeatures.cpp b/lib/Basic/SupportedFeatures.cpp index 068f83c52f1b3..fc9cd489e9b64 100644 --- a/lib/Basic/SupportedFeatures.cpp +++ b/lib/Basic/SupportedFeatures.cpp @@ -27,7 +27,8 @@ namespace features { /// The subset of diagnostic groups (called categories by the diagnostic machinery) whose diagnostics should be /// considered to be part of the migration for this feature. /// -/// When making a +/// When making a feature migratable, ensure that all of the warnings that are used to drive the migration are +/// part of a diagnostic group, and put that diagnostic group into the list for that feature here. static std::vector migratableCategories(Feature feature) { switch (feature) { case Feature::InnerKind::ExistentialAny: