diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e6c2648880b85..0341337fe0587 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -269,7 +269,7 @@ /unittests/AST/ @hborla @slavapestov @xedin /unittests/AST/*Evaluator* @CodaFi @slavapestov /unittests/DependencyScan/ @artemcm @cachemeifyoucan -/unittests/FrontendTool/ @artemcm @tshortli +/unittests/Frontend*/ @artemcm @tshortli /unittests/Parse/ @ahoppen @bnbarham @CodaFi @DougGregor @hamishknight @rintaro /unittests/Reflection/ @slavapestov /unittests/SIL/ @jckarter diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index 5bb3bc47b4213..fde029998a1a5 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -50,6 +50,20 @@ GROUPED_WARNING(feature_not_experimental, StrictLanguageFeatures, DefaultIgnore, "use -%select{disable|enable}1-upcoming-feature instead", (StringRef, bool)) +GROUPED_WARNING(invalid_feature_mode, StrictLanguageFeatures, none, + "'%0' is not a recognized mode for feature '%1'" + "%select{|; did you mean '%2'?}3", + (StringRef, StringRef, StringRef, bool)) + +GROUPED_WARNING(cannot_disable_feature_with_mode, StrictLanguageFeatures, none, + "'%0' argument '%1' cannot specify a mode", + (StringRef, StringRef)) + +GROUPED_WARNING(feature_does_not_support_adoption_mode, StrictLanguageFeatures, + none, + "feature '%0' does not support adoption mode", + (StringRef)) + ERROR(error_unknown_library_level, none, "unknown library level '%0', " "expected one of 'api', 'spi', 'ipi', or 'other'", (StringRef)) diff --git a/include/swift/Basic/BasicBridging.h b/include/swift/Basic/BasicBridging.h index 7e93c1755672d..6b5674d316198 100644 --- a/include/swift/Basic/BasicBridging.h +++ b/include/swift/Basic/BasicBridging.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// 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 @@ -214,7 +214,8 @@ void BridgedData_free(BridgedData data); //===----------------------------------------------------------------------===// enum ENUM_EXTENSIBILITY_ATTR(open) BridgedFeature { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) FeatureName, +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + FeatureName, #include "swift/Basic/Features.def" }; diff --git a/include/swift/Basic/Feature.h b/include/swift/Basic/Feature.h index a2f2d45361ae3..4122d9f4f75b2 100644 --- a/include/swift/Basic/Feature.h +++ b/include/swift/Basic/Feature.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -21,14 +21,16 @@ namespace swift { class LangOptions; /// Enumeration describing all of the named features. -enum class Feature { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) FeatureName, +enum class Feature : uint16_t { +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + FeatureName, #include "swift/Basic/Features.def" }; constexpr unsigned numFeatures() { enum Features { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) FeatureName, +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + FeatureName, #include "swift/Basic/Features.def" NumFeatures }; @@ -61,6 +63,9 @@ std::optional getExperimentalFeature(llvm::StringRef name); /// \c None if it does not have such a version. std::optional getFeatureLanguageVersion(Feature feature); +/// Determine whether the given feature supports adoption mode. +bool isFeatureAdoptable(Feature feature); + /// Determine whether this feature should be included in the /// module interface bool includeInModuleInterface(Feature feature); diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 4598154205bfd..27a137b04fc2a 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -14,7 +14,7 @@ // features. // // -// LANGUAGE_FEATURE(FeatureName, SENumber, Description) +// LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) // // The LANGUAGE_FEATURE macro describes each named feature that is // introduced in Swift. It allows Swift code to check for a particular @@ -22,6 +22,20 @@ // // FeatureName: The name given to this feature to be used in source code, // e.g., AsyncAwait. +// IsAdoptable: Whether the feature implements adoption mode. +// +// If the feature is upcoming (source-breaking) and provides for a +// mechanical code migration, it should implement adoption mode. +// +// Adoption mode is a feature-oriented code migration mechanism: a mode +// of operation that should produce compiler warnings with attached +// fix-its that can be applied to preserve the behavior of the code once +// the upcoming feature is enacted. +// These warnings must belong to a diagnostic group named after the +// feature. Adoption mode itself *and* the fix-its it produces must be +// source and binary compatible with how the code is compiled when the +// feature is disabled. +// // SENumber: The number assigned to this feature in the Swift Evolution // process, or 0 if there isn't one. // Description: A string literal describing the feature. @@ -91,13 +105,14 @@ #endif #ifndef SUPPRESSIBLE_LANGUAGE_FEATURE -# define SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - LANGUAGE_FEATURE(FeatureName, SENumber, Description) + #define SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, SENumber, \ + Description) #endif #ifndef OPTIONAL_LANGUAGE_FEATURE -# define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - LANGUAGE_FEATURE(FeatureName, SENumber, Description) + #define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, SENumber, Description) #endif // A feature that's both conditionally-suppressible and experimental. @@ -116,20 +131,35 @@ #endif #ifndef CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE -# define CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - LANGUAGE_FEATURE(FeatureName, SENumber, Description) + #define CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, \ + Description) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, SENumber, \ + Description) +#endif + +// An upcoming feature that supports adoption mode. +#ifndef ADOPTABLE_UPCOMING_FEATURE + #if defined(UPCOMING_FEATURE) + #define ADOPTABLE_UPCOMING_FEATURE(FeatureName, SENumber, Version) \ + UPCOMING_FEATURE(FeatureName, SENumber, Version) + #else + #define ADOPTABLE_UPCOMING_FEATURE(FeatureName, SENumber, Version) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/true, SENumber, \ + #FeatureName) + #endif #endif #ifndef UPCOMING_FEATURE -# define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ - LANGUAGE_FEATURE(FeatureName, SENumber, #FeatureName) + #define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, SENumber, \ + #FeatureName) #endif #ifndef EXPERIMENTAL_FEATURE -// Warning: setting `AvailableInProd` to `true` on a feature means that the flag -// cannot be dropped in the future. -# define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ - LANGUAGE_FEATURE(FeatureName, 0, #FeatureName) + // Warning: setting `AvailableInProd` to `true` on a feature means that the + // flag cannot be dropped in the future. + #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, 0, #FeatureName) #endif #ifndef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE @@ -138,8 +168,9 @@ #endif #ifndef BASELINE_LANGUAGE_FEATURE -# define BASELINE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - LANGUAGE_FEATURE(FeatureName, SENumber, Description) + #define BASELINE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ + LANGUAGE_FEATURE(FeatureName, /*IsAdoptable=*/false, SENumber, \ + Description) #endif BASELINE_LANGUAGE_FEATURE(AsyncAwait, 296, "async/await") @@ -210,10 +241,14 @@ BASELINE_LANGUAGE_FEATURE(BodyMacros, 415, "Function body macros") SUPPRESSIBLE_LANGUAGE_FEATURE(SendingArgsAndResults, 430, "Sending arg and results") BASELINE_LANGUAGE_FEATURE(BorrowingSwitch, 432, "Noncopyable type pattern matching") CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE(IsolatedAny, 431, "@isolated(any) function types") -LANGUAGE_FEATURE(IsolatedAny2, 431, "@isolated(any) function types") -LANGUAGE_FEATURE(ObjCImplementation, 436, "@objc @implementation extensions") -LANGUAGE_FEATURE(NonescapableTypes, 446, "Nonescapable types") -LANGUAGE_FEATURE(BuiltinEmplaceTypedThrows, 0, "Builtin.emplace typed throws") +LANGUAGE_FEATURE(IsolatedAny2, /*IsAdoptable=*/false, 431, + "@isolated(any) function types") +LANGUAGE_FEATURE(ObjCImplementation, /*IsAdoptable=*/false, 436, + "@objc @implementation extensions") +LANGUAGE_FEATURE(NonescapableTypes, /*IsAdoptable=*/false, 446, + "Nonescapable types") +LANGUAGE_FEATURE(BuiltinEmplaceTypedThrows, /*IsAdoptable=*/false, 0, + "Builtin.emplace typed throws") SUPPRESSIBLE_LANGUAGE_FEATURE(MemorySafetyAttributes, 458, "@unsafe attribute") // Swift 6 @@ -234,7 +269,7 @@ UPCOMING_FEATURE(NonfrozenEnumExhaustivity, 192, 6) UPCOMING_FEATURE(GlobalActorIsolatedTypesUsability, 0434, 6) // Swift 7 -UPCOMING_FEATURE(ExistentialAny, 335, 7) +ADOPTABLE_UPCOMING_FEATURE(ExistentialAny, 335, 7) UPCOMING_FEATURE(InternalImportsByDefault, 409, 7) UPCOMING_FEATURE(MemberImportVisibility, 444, 7) @@ -476,6 +511,7 @@ EXPERIMENTAL_FEATURE(IsolatedConformances, true) #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE +#undef ADOPTABLE_UPCOMING_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 dbb6313525518..a849f980d7f7b 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -20,7 +20,6 @@ #include "swift/Basic/CXXStdlibKind.h" #include "swift/Basic/Feature.h" -#include "swift/Basic/FixedBitSet.h" #include "swift/Basic/FunctionBodySkipping.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/PlaygroundOption.h" @@ -725,19 +724,6 @@ namespace swift { return cxxInteropCompatVersion.isVersionAtLeast(major, minor); } - /// Determine whether the given feature is enabled. - bool hasFeature(Feature feature) const; - - /// Determine whether the given feature is enabled, looking up the feature - /// by name. - bool hasFeature(llvm::StringRef featureName) const; - - /// Enable the given feature. - void enableFeature(Feature feature) { Features.insert(feature); } - - /// Disable the given feature. - void disableFeature(Feature feature) { Features.remove(feature); } - /// Sets the "_hasAtomicBitWidth" conditional. void setHasAtomicBitWidth(llvm::Triple triple); @@ -822,10 +808,68 @@ namespace swift { PlatformConditionValues; llvm::SmallVector CustomConditionalCompilationFlags; - /// The set of features that have been enabled. Doesn't include upcoming - /// features, which are checked against the language version in - /// `hasFeature`. - FixedBitSet Features; + public: + // MARK: Features + // ========================================================================= + + /// A wrapper around the feature state enumeration. + struct FeatureState { + enum Kind : uint8_t { Off, EnabledForAdoption, Enabled }; + + private: + Feature feature; + Kind state; + + public: + FeatureState(Feature feature, Kind state) + : feature(feature), state(state) {} + + /// Returns whether the feature is enabled. + bool isEnabled() const; + + /// Returns whether the feature is enabled in adoption mode. Should only + /// be called if the feature is known to support this mode. + bool isEnabledForAdoption() const; + + operator Kind() const { return state; } + }; + + private: + class FeatureStateStorage { + std::vector states; + + public: + FeatureStateStorage(); + + /// Sets the given state for the given feature. + void setState(Feature feature, FeatureState::Kind state); + + /// Retrieves the state of the given feature. + FeatureState getState(Feature feature) const; + }; + + /// The states of language features. + FeatureStateStorage featureStates; + + public: + /// Retrieve the state of the given feature. + FeatureState getFeatureState(Feature feature) const; + + /// Returns whether the given feature is enabled. + bool hasFeature(Feature feature) const; + + /// Returns whether a feature with the given name is enabled. Returns + /// `false` if a feature by this name is not known. + bool hasFeature(llvm::StringRef featureName) const; + + /// Enables the given feature (enables in adoption mode if `forAdoption` is + /// `true`). + void enableFeature(Feature feature, bool forAdoption = false); + + /// Disables the given feature. + void disableFeature(Feature feature); + + // ========================================================================= }; class TypeCheckerOptions final { diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 424063cef5768..cee6442be11a8 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -1,7 +1,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -3267,7 +3267,7 @@ suppressingFeatureExecutionAttribute(PrintOptions &options, static void suppressingFeature(PrintOptions &options, Feature feature, llvm::function_ref action) { switch (feature) { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ case Feature::FeatureName: \ llvm_unreachable("not a suppressible feature"); #define SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 1f339b8ef1b5c..471f3164859c9 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2024 - 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 @@ -45,7 +45,7 @@ static bool usesTypeMatching(Decl *decl, llvm::function_ref fn) { #define BASELINE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ static bool usesFeature##FeatureName(Decl *decl) { return false; } -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) #include "swift/Basic/Features.def" #define UNINTERESTING_FEATURE(FeatureName) \ @@ -542,7 +542,7 @@ void FeatureSet::collectFeaturesUsed(Decl *decl, InsertOrRemove operation) { // Go through each of the features, checking whether the // declaration uses that feature. -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ if (CHECK(usesFeature##FeatureName)) \ collectRequiredFeature(Feature::FeatureName, operation); #define SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ diff --git a/lib/Basic/Feature.cpp b/lib/Basic/Feature.cpp index 13b1b9e3a6c8d..269579c792b75 100644 --- a/lib/Basic/Feature.cpp +++ b/lib/Basic/Feature.cpp @@ -18,7 +18,7 @@ using namespace swift; bool swift::isFeatureAvailableInProduction(Feature feature) { switch (feature) { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ case Feature::FeatureName: \ return true; #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ @@ -31,7 +31,7 @@ bool swift::isFeatureAvailableInProduction(Feature feature) { llvm::StringRef swift::getFeatureName(Feature feature) { switch (feature) { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ case Feature::FeatureName: \ return #FeatureName; #include "swift/Basic/Features.def" @@ -41,7 +41,7 @@ llvm::StringRef swift::getFeatureName(Feature feature) { std::optional swift::getUpcomingFeature(llvm::StringRef name) { return llvm::StringSwitch>(name) -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) #define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ .Case(#FeatureName, Feature::FeatureName) #include "swift/Basic/Features.def" @@ -50,7 +50,7 @@ std::optional swift::getUpcomingFeature(llvm::StringRef name) { std::optional swift::getExperimentalFeature(llvm::StringRef name) { return llvm::StringSwitch>(name) -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) \ .Case(#FeatureName, Feature::FeatureName) #include "swift/Basic/Features.def" @@ -59,7 +59,7 @@ std::optional swift::getExperimentalFeature(llvm::StringRef name) { std::optional swift::getFeatureLanguageVersion(Feature feature) { switch (feature) { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) #define UPCOMING_FEATURE(FeatureName, SENumber, Version) \ case Feature::FeatureName: \ return Version; @@ -69,9 +69,18 @@ std::optional swift::getFeatureLanguageVersion(Feature feature) { } } +bool swift::isFeatureAdoptable(Feature feature) { + switch (feature) { +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + case Feature::FeatureName: \ + return IsAdoptable; +#include "swift/Basic/Features.def" + } +} + bool swift::includeInModuleInterface(Feature feature) { switch (feature) { -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ case Feature::FeatureName: \ return true; #define EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE(FeatureName, \ diff --git a/lib/Basic/LangOptions.cpp b/lib/Basic/LangOptions.cpp index c686cb1199ebb..a2c2d0a439448 100644 --- a/lib/Basic/LangOptions.cpp +++ b/lib/Basic/LangOptions.cpp @@ -17,6 +17,7 @@ #include "swift/Basic/LangOptions.h" #include "swift/AST/DiagnosticEngine.h" +#include "swift/Basic/Assertions.h" #include "swift/Basic/Feature.h" #include "swift/Basic/FileTypes.h" #include "swift/Basic/Platform.h" @@ -34,8 +35,8 @@ using namespace swift; LangOptions::LangOptions() { // Add all promoted language features -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - Features.insert(Feature::FeatureName); +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + this->enableFeature(Feature::FeatureName); #define UPCOMING_FEATURE(FeatureName, SENumber, Version) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) #define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) @@ -44,14 +45,16 @@ LangOptions::LangOptions() { // Special case: remove macro support if the compiler wasn't built with a // host Swift. #if !SWIFT_BUILD_SWIFT_SYNTAX - Features.removeAll({Feature::Macros, Feature::FreestandingExpressionMacros, - Feature::AttachedMacros, Feature::ExtensionMacros}); + this->disableFeature(Feature::Macros); + this->disableFeature(Feature::FreestandingExpressionMacros); + this->disableFeature(Feature::AttachedMacros); + this->disableFeature(Feature::ExtensionMacros); #endif // Note: Introduce default-on language options here. - Features.insert(Feature::NoncopyableGenerics); - Features.insert(Feature::BorrowingSwitch); - Features.insert(Feature::MoveOnlyPartialConsumption); + this->enableFeature(Feature::NoncopyableGenerics); + this->enableFeature(Feature::BorrowingSwitch); + this->enableFeature(Feature::MoveOnlyPartialConsumption); // Enable any playground options that are enabled by default. #define PLAYGROUND_OPTION(OptionName, Description, DefaultOn, HighPerfOn) \ @@ -291,8 +294,50 @@ bool LangOptions::isCustomConditionalCompilationFlagSet(StringRef Name) const { != CustomConditionalCompilationFlags.end(); } +bool LangOptions::FeatureState::isEnabled() const { + return this->state != FeatureState::Off; +} + +bool LangOptions::FeatureState::isEnabledForAdoption() const { + ASSERT(isFeatureAdoptable(this->feature) && + "You forgot to make the feature adoptable!"); + + return this->state == FeatureState::EnabledForAdoption; +} + +LangOptions::FeatureStateStorage::FeatureStateStorage() + : states(numFeatures(), FeatureState::Off) {} + +void LangOptions::FeatureStateStorage::setState(Feature feature, + FeatureState::Kind state) { + auto index = size_t(feature); + + this->states[index] = state; +} + +LangOptions::FeatureState +LangOptions::FeatureStateStorage::getState(Feature feature) const { + auto index = size_t(feature); + + return FeatureState(feature, this->states[index]); +} + +LangOptions::FeatureState LangOptions::getFeatureState(Feature feature) const { + auto state = this->featureStates.getState(feature); + if (state.isEnabled()) + return state; + + if (auto version = getFeatureLanguageVersion(feature)) { + if (this->isSwiftVersionAtLeast(*version)) { + return FeatureState(feature, FeatureState::Enabled); + } + } + + return state; +} + bool LangOptions::hasFeature(Feature feature) const { - if (Features.contains(feature)) + if (this->featureStates.getState(feature).isEnabled()) return true; if (auto version = getFeatureLanguageVersion(feature)) @@ -303,7 +348,7 @@ bool LangOptions::hasFeature(Feature feature) const { bool LangOptions::hasFeature(llvm::StringRef featureName) const { auto feature = llvm::StringSwitch>(featureName) -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ .Case(#FeatureName, Feature::FeatureName) #include "swift/Basic/Features.def" .Default(std::nullopt); @@ -313,6 +358,19 @@ bool LangOptions::hasFeature(llvm::StringRef featureName) const { return false; } +void LangOptions::enableFeature(Feature feature, bool forAdoption) { + if (forAdoption) { + ASSERT(isFeatureAdoptable(feature)); + this->featureStates.setState(feature, FeatureState::EnabledForAdoption); + } else { + this->featureStates.setState(feature, FeatureState::Enabled); + } +} + +void LangOptions::disableFeature(Feature feature) { + this->featureStates.setState(feature, FeatureState::Off); +} + void LangOptions::setHasAtomicBitWidth(llvm::Triple triple) { // We really want to use Clang's getMaxAtomicInlineWidth(), but that requires // a Clang::TargetInfo and we're setting up lang opts very early in the diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 90057f7a30bc2..676d7482f9896 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -749,50 +749,56 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, OPT_enable_experimental_feature, OPT_disable_experimental_feature, OPT_enable_upcoming_feature, OPT_disable_upcoming_feature)) { auto &option = A->getOption(); - StringRef value = A->getValue(); - bool enableUpcoming = option.matches(OPT_enable_upcoming_feature); - bool isUpcomingFlag = - enableUpcoming || option.matches(OPT_disable_upcoming_feature); - bool enableFeature = - enableUpcoming || option.matches(OPT_enable_experimental_feature); + const StringRef argValue = A->getValue(); + + bool isEnableUpcomingFeatureFlag = + option.matches(OPT_enable_upcoming_feature); + bool isUpcomingFeatureFlag = isEnableUpcomingFeatureFlag || + option.matches(OPT_disable_upcoming_feature); + bool isEnableFeatureFlag = isEnableUpcomingFeatureFlag || + option.matches(OPT_enable_experimental_feature); // Collect some special case pseudo-features which should be processed // separately. - if (value.starts_with("StrictConcurrency") || - value.starts_with("AvailabilityMacro=")) { - if (enableFeature) - psuedoFeatures.push_back(value); + if (argValue.starts_with("StrictConcurrency") || + argValue.starts_with("AvailabilityMacro=")) { + if (isEnableFeatureFlag) + psuedoFeatures.push_back(argValue); continue; } - auto feature = getUpcomingFeature(value); + // For all other features, the argument format is `[:adoption]`. + StringRef featureName; + std::optional featureMode; + std::tie(featureName, featureMode) = argValue.rsplit(':'); + if (featureMode.value().empty()) { + featureMode = std::nullopt; + } + + auto feature = getUpcomingFeature(featureName); if (feature) { // Diagnose upcoming features enabled with -enable-experimental-feature. - if (!isUpcomingFlag) - Diags.diagnose(SourceLoc(), diag::feature_not_experimental, value, - enableFeature); + if (!isUpcomingFeatureFlag) + Diags.diagnose(SourceLoc(), diag::feature_not_experimental, featureName, + isEnableFeatureFlag); } else { // If -enable-upcoming-feature was used and an upcoming feature was not // found, diagnose and continue. - if (isUpcomingFlag) { - Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value, + if (isUpcomingFeatureFlag) { + Diags.diagnose(SourceLoc(), diag::unrecognized_feature, featureName, /*upcoming=*/true); continue; } // If the feature is also not a recognized experimental feature, skip it. - feature = getExperimentalFeature(value); + feature = getExperimentalFeature(featureName); if (!feature) { - Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value, + Diags.diagnose(SourceLoc(), diag::unrecognized_feature, featureName, /*upcoming=*/false); continue; } } - // Skip features that are already enabled or disabled. - if (!seenFeatures.insert(*feature).second) - continue; - // If the current language mode enables the feature by default then // diagnose and skip it. if (auto firstVersion = getFeatureLanguageVersion(*feature)) { @@ -809,14 +815,47 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args, if (Opts.RestrictNonProductionExperimentalFeatures && !isFeatureAvailableInProduction(*feature)) { Diags.diagnose(SourceLoc(), - diag::experimental_not_supported_in_production, value); + diag::experimental_not_supported_in_production, + featureName); HadError = true; continue; } + if (featureMode) { + if (isEnableFeatureFlag) { + const auto isAdoptable = isFeatureAdoptable(*feature); + + // Diagnose an invalid mode. + StringRef validModeName = "adoption"; + if (*featureMode != validModeName) { + Diags.diagnose(SourceLoc(), diag::invalid_feature_mode, *featureMode, + featureName, + /*didYouMean=*/validModeName, + /*showDidYouMean=*/isAdoptable); + continue; + } + + if (!isAdoptable) { + Diags.diagnose(SourceLoc(), + diag::feature_does_not_support_adoption_mode, + featureName); + continue; + } + } else { + // `-disable-*-feature` flags do not support a mode specifier. + Diags.diagnose(SourceLoc(), diag::cannot_disable_feature_with_mode, + option.getPrefixedName(), argValue); + continue; + } + } + + // Skip features that are already enabled or disabled. + if (!seenFeatures.insert(*feature).second) + continue; + // Enable the feature if requested. - if (enableFeature) - Opts.enableFeature(*feature); + if (isEnableFeatureFlag) + Opts.enableFeature(*feature, /*forAdoption=*/featureMode.has_value()); } // Since pseudo-features don't have a boolean on/off state, process them in diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 965311faf8676..08e2ee423aa27 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 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 @@ -46,6 +46,7 @@ #include "swift/AST/TypeResolutionStage.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/EnumMap.h" +#include "swift/Basic/FixedBitSet.h" #include "swift/Basic/SourceManager.h" #include "swift/Basic/Statistic.h" #include "swift/Basic/StringExtras.h" @@ -6504,7 +6505,15 @@ class ExistentialTypeSyntaxChecker : public ASTWalker { /*isAlias=*/isa(decl))); } - diag->warnUntilSwiftVersion(7); + // If the feature is enabled in adoption mode, warn unconditionally. + // Otherwise, until Swift 7. + if (Ctx.LangOpts.getFeatureState(Feature::ExistentialAny) + .isEnabledForAdoption()) { + diag->limitBehavior(DiagnosticBehavior::Warning); + } else { + diag->warnUntilSwiftVersion(7); + } + emitInsertAnyFixit(*diag, T); } diff --git a/lib/Sema/TypeCheckType.h b/lib/Sema/TypeCheckType.h index 1c57d8c58bc25..ecf18d2f05e00 100644 --- a/lib/Sema/TypeCheckType.h +++ b/lib/Sema/TypeCheckType.h @@ -19,7 +19,6 @@ #include "swift/AST/Type.h" #include "swift/AST/TypeResolutionStage.h" #include "swift/AST/Types.h" -#include "swift/Basic/LangOptions.h" namespace swift { diff --git a/test/Frontend/features/adoption_mode.swift b/test/Frontend/features/adoption_mode.swift new file mode 100644 index 0000000000000..b372ccd0d19f0 --- /dev/null +++ b/test/Frontend/features/adoption_mode.swift @@ -0,0 +1,57 @@ +// NB: Repetitions are intentional. We are testing that a diagnostic is emitted +// for each option, not just the last one found. + +// RUN: %target-swift-frontend -parse -swift-version 5 \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid1 \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid2 \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:adoption \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:adoption \ +// RUN: -enable-experimental-feature DynamicActorIsolation:invalid3 \ +// RUN: -enable-experimental-feature DynamicActorIsolation:invalid4 \ +// RUN: -enable-experimental-feature DynamicActorIsolation:adoption \ +// RUN: -enable-experimental-feature DynamicActorIsolation:adoption \ +// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid5 \ +// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid6 \ +// RUN: -disable-upcoming-feature DynamicActorIsolation:adoption \ +// RUN: -disable-experimental-feature DynamicActorIsolation:invalid7 \ +// RUN: -disable-experimental-feature DynamicActorIsolation:invalid8 \ +// RUN: -disable-experimental-feature DynamicActorIsolation:adoption \ +// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-5 + +// RUN: %target-swift-frontend -parse -swift-version 6 \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid1 \ +// RUN: -enable-upcoming-feature DynamicActorIsolation:adoption \ +// RUN: -enable-experimental-feature DynamicActorIsolation:invalid2 \ +// RUN: -enable-experimental-feature DynamicActorIsolation:adoption \ +// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid3 \ +// RUN: -disable-upcoming-feature DynamicActorIsolation:adoption \ +// RUN: -disable-experimental-feature DynamicActorIsolation:invalid4 \ +// RUN: -disable-experimental-feature DynamicActorIsolation:adoption \ +// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-6 + +// REQUIRES: swift_feature_DynamicActorIsolation + +// CHECK-NOT: error: + +// CHECK-SWIFT-5-NOT: warning: +// CHECK-SWIFT-5: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:adoption' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:invalid8' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:invalid7' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:adoption' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:invalid6' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:invalid5' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: feature 'DynamicActorIsolation' does not support adoption mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: feature 'DynamicActorIsolation' does not support adoption mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid4' is not a recognized mode for feature 'DynamicActorIsolation'{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid3' is not a recognized mode for feature 'DynamicActorIsolation'{{$}} +// CHECK-SWIFT-5-NEXT: warning: feature 'DynamicActorIsolation' does not support adoption mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: feature 'DynamicActorIsolation' does not support adoption mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid2' is not a recognized mode for feature 'DynamicActorIsolation'{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid1' is not a recognized mode for feature 'DynamicActorIsolation'{{$}} +// CHECK-SWIFT-5-NOT: warning: + +// CHECK-SWIFT-6-NOT: warning: +// CHECK-SWIFT-6-COUNT-8: warning: upcoming feature 'DynamicActorIsolation' is already enabled as of Swift version 6{{$}} +// CHECK-SWIFT-6-NOT: warning: + +// CHECK-NOT: error: diff --git a/test/Frontend/features/adoption_mode_adoptable_feature.swift b/test/Frontend/features/adoption_mode_adoptable_feature.swift new file mode 100644 index 0000000000000..a1dbd95f035c2 --- /dev/null +++ b/test/Frontend/features/adoption_mode_adoptable_feature.swift @@ -0,0 +1,38 @@ +// NB: Repetitions are intentional. We are testing that a diagnostic is emitted +// for each option, not just the last one found. + +// RUN: %target-swift-frontend -parse -swift-version 6 \ +// RUN: -enable-upcoming-feature ExistentialAny:invalid1 \ +// RUN: -enable-upcoming-feature ExistentialAny:adoption \ +// RUN: -enable-experimental-feature ExistentialAny:invalid2 \ +// RUN: -enable-experimental-feature ExistentialAny:adoption \ +// RUN: -disable-upcoming-feature ExistentialAny:adoption \ +// RUN: -disable-experimental-feature ExistentialAny:adoption \ +// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-5 + +// RUN: %target-swift-frontend -parse -swift-version 7 \ +// RUN: -enable-upcoming-feature ExistentialAny:invalid1 \ +// RUN: -enable-upcoming-feature ExistentialAny:adoption \ +// RUN: -enable-experimental-feature ExistentialAny:invalid2 \ +// RUN: -enable-experimental-feature ExistentialAny:adoption \ +// RUN: -disable-upcoming-feature ExistentialAny:adoption \ +// RUN: -disable-experimental-feature ExistentialAny:adoption \ +// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-7 + +// REQUIRES: swift_feature_ExistentialAny +// REQUIRES: asserts + +// CHECK-NOT: error: + +// CHECK-SWIFT-5-NOT: warning: +// CHECK-SWIFT-5: warning: '-disable-experimental-feature' argument 'ExistentialAny:adoption' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'ExistentialAny:adoption' cannot specify a mode{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid2' is not a recognized mode for feature 'ExistentialAny'; did you mean 'adoption'?{{$}} +// CHECK-SWIFT-5-NEXT: warning: 'invalid1' is not a recognized mode for feature 'ExistentialAny'; did you mean 'adoption'?{{$}} +// CHECK-SWIFT-5-NOT: warning: + +// CHECK-SWIFT-7-NOT: warning: +// CHECK-SWIFT-7-COUNT-6: warning: upcoming feature 'ExistentialAny' is already enabled as of Swift version 7{{$}} +// CHECK-SWIFT-7-NOT: warning: + +// CHECK-NOT: error: diff --git a/test/lit.swift-features.cfg.inc b/test/lit.swift-features.cfg.inc index 44457af0e0368..feb3a8c3dd035 100644 --- a/test/lit.swift-features.cfg.inc +++ b/test/lit.swift-features.cfg.inc @@ -3,7 +3,7 @@ /* * This source file is part of the Swift.org open source project * - * Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors + * Copyright (c) 2014 - 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 @@ -30,7 +30,7 @@ def language_feature(feature_name, enabled): #define UPCOMING_FEATURE(FeatureName, SENumber, Version) language_feature(#FeatureName, True) #define EXPERIMENTAL_FEATURE(FeatureName, AvailableInProd) language_feature(#FeatureName, #AvailableInProd == "true") -#define LANGUAGE_FEATURE(FeatureName, SENumber, Description) language_feature(#FeatureName, True) -#define OPTIONAL_LANGUAGE_FEATURE(FeatureName, SENumber, Description) language_feature(#FeatureName, True) +#define LANGUAGE_FEATURE(FeatureName, IsAdoptable, SENumber, Description) \ + language_feature(#FeatureName, True) #include diff --git a/test/type/explicit_existential.swift b/test/type/explicit_existential.swift index f1fe28f628e47..a7684a18956d8 100644 --- a/test/type/explicit_existential.swift +++ b/test/type/explicit_existential.swift @@ -1,7 +1,19 @@ -// RUN: %target-typecheck-verify-swift -verify-additional-prefix default-swift-mode- -// RUN: %target-typecheck-verify-swift -swift-version 6 -verify-additional-prefix swift-6- -// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ExistentialAny -verify-additional-prefix explicit-any- -verify-additional-prefix default-swift-mode- -// RUN: %target-typecheck-verify-swift -enable-experimental-feature ExistentialAny -verify-additional-prefix explicit-any- -verify-additional-prefix default-swift-mode- +// RUN: %target-typecheck-verify-swift \ +// RUN: -verify-additional-prefix default-swift-mode- + +// RUN: %target-typecheck-verify-swift -swift-version 6 \ +// RUN: -verify-additional-prefix swift-6- + +// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ExistentialAny \ +// RUN: -verify-additional-prefix default-swift-mode- \ +// RUN: -verify-additional-prefix explicit-any- + +// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ExistentialAny:adoption \ +// To verify that the message is not followed by +// "; this will be an error ...". +// RUN: -print-diagnostic-groups \ +// RUN: -verify-additional-prefix default-swift-mode- \ +// RUN: -verify-additional-prefix explicit-any-adopt- // REQUIRES: swift_feature_ExistentialAny @@ -20,8 +32,10 @@ protocol Bar { class Bistro { convenience init(_: Bar){ self.init()} // expected-explicit-any-warning@-1 {{use of protocol 'Bar' as a type must be written 'any Bar'; this will be an error in a future Swift language mode}}{{23-26=any Bar}} + // expected-explicit-any-adopt-warning@-2 {{use of protocol 'Bar' as a type must be written 'any Bar' [ExistentialAny]}}{{23-26=any Bar}} class func returnBar() -> Bar {} // expected-explicit-any-warning@-1 {{use of protocol 'Bar' as a type must be written 'any Bar'; this will be an error in a future Swift language mode}}{{29-32=any Bar}} + // expected-explicit-any-adopt-warning@-2 {{use of protocol 'Bar' as a type must be written 'any Bar' [ExistentialAny]}}{{29-32=any Bar}} } func useBarAsType(_ x: any Bar) {} @@ -221,6 +235,7 @@ enum E1: RawRepresentable { var rawValue: P1 { // expected-explicit-any-warning@-1 {{use of protocol 'P1' as a type must be written 'any P1'; this will be an error in a future Swift language mode}}{{17-19=any P1}} + // expected-explicit-any-adopt-warning@-2 {{use of protocol 'P1' as a type must be written 'any P1' [ExistentialAny]}}{{17-19=any P1}} return ConcreteComposition() } } @@ -318,8 +333,10 @@ enum EE : Equatable, any Empty { // expected-error {{raw type 'any Empty' is not // Protocols from a serialized module (the standard library). do { + // expected-explicit-any-adopt-warning@+2 {{use of protocol 'Decodable' as a type must be written 'any Decodable' [ExistentialAny]}} // expected-explicit-any-warning@+1 {{use of protocol 'Decodable' as a type must be written 'any Decodable'; this will be an error in a future Swift language mode}} let _: Decodable + // expected-explicit-any-adopt-warning@+2 {{use of 'Codable' (aka 'Decodable & Encodable') as a type must be written 'any Codable' (aka 'any Decodable & Encodable') [ExistentialAny]}} // expected-explicit-any-warning@+1 {{use of 'Codable' (aka 'Decodable & Encodable') as a type must be written 'any Codable' (aka 'any Decodable & Encodable'); this will be an error in a future Swift language mode}} let _: Codable } @@ -547,6 +564,7 @@ func testEnumAssociatedValue() { case c1((any HasAssoc) -> Void) // expected-warning@+1 {{use of protocol 'HasAssoc' as a type must be written 'any HasAssoc'}} case c2((HasAssoc) -> Void) + // expected-explicit-any-adopt-warning@+2 {{use of protocol 'P' as a type must be written 'any P' [ExistentialAny]}} // expected-explicit-any-warning@+1 {{use of protocol 'P' as a type must be written 'any P'; this will be an error in a future Swift language mode}} case c3((P) -> Void) } @@ -574,5 +592,7 @@ func f(_ x: Objectlike) {} typealias Copy = Copyable func h(_ z1: Copy, // expected-explicit-any-warning@-1 {{use of 'Copy' (aka 'Copyable') as a type must be written 'any Copy' (aka 'any Copyable'); this will be an error in a future Swift language mode}} + // expected-explicit-any-adopt-warning@-2 {{use of 'Copy' (aka 'Copyable') as a type must be written 'any Copy' (aka 'any Copyable') [ExistentialAny]}} _ z2: Copyable) {} // expected-explicit-any-warning@-1 {{use of protocol 'Copyable' as a type must be written 'any Copyable'; this will be an error in a future Swift language mode}} + // expected-explicit-any-adopt-warning@-2 {{use of protocol 'Copyable' as a type must be written 'any Copyable' [ExistentialAny]}} diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 41e0048aa865c..ec5642120cfa9 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -8,6 +8,7 @@ if(SWIFT_INCLUDE_TOOLS) add_subdirectory(Basic) add_subdirectory(ClangImporter) add_subdirectory(DependencyScan) + add_subdirectory(Frontend) add_subdirectory(FrontendTool) add_subdirectory(Localization) add_subdirectory(IDE) diff --git a/unittests/Frontend/ArgParsingTest.cpp b/unittests/Frontend/ArgParsingTest.cpp new file mode 100644 index 0000000000000..9e7aaeef91a67 --- /dev/null +++ b/unittests/Frontend/ArgParsingTest.cpp @@ -0,0 +1,53 @@ +//===-- ArgParsingTest.cpp --------------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#include "ArgParsingTest.h" + +using namespace swift; + +ArgParsingTest::ArgParsingTest() : diags(sourceMgr) {} + +void ArgParsingTest::parseArgs(const Args &args) { + std::vector adjustedArgs; + + if (this->langMode) { + adjustedArgs.reserve(args.size() + 2); + adjustedArgs.push_back("-swift-version"); + adjustedArgs.push_back(this->langMode->data()); + } else { + adjustedArgs.reserve(args.size()); + } + + for (auto &arg : args) { + adjustedArgs.push_back(arg.data()); + } + + this->invocation.parseArgs(adjustedArgs, this->diags); +} + +LangOptions &ArgParsingTest::getLangOptions() { + return this->invocation.getLangOptions(); +} + +void PrintTo(const Args &value, std::ostream *os) { + *os << '"'; + + if (!value.empty()) { + const auto lastIdx = value.size() - 1; + for (size_t idx = 0; idx != lastIdx; ++idx) { + *os << value[idx] << ' '; + } + *os << value[lastIdx]; + } + + *os << '"'; +} diff --git a/unittests/Frontend/ArgParsingTest.h b/unittests/Frontend/ArgParsingTest.h new file mode 100644 index 0000000000000..cf4501053feed --- /dev/null +++ b/unittests/Frontend/ArgParsingTest.h @@ -0,0 +1,55 @@ +//===-- ArgParsingTest.h ----------------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#ifndef ARG_PARSING_TEST_H +#define ARG_PARSING_TEST_H + +#include "swift/Frontend/Frontend.h" +#include "gtest/gtest.h" + +using Args = std::vector; + +class ArgParsingTest : public ::testing::Test { + swift::CompilerInvocation invocation; + swift::SourceManager sourceMgr; + swift::DiagnosticEngine diags; + +protected: + std::optional langMode; + +public: + ArgParsingTest(); + + void parseArgs(const Args &args); + + swift::LangOptions &getLangOptions(); +}; + +template +struct ArgParsingTestCase final { + Args args; + T expectedResult; + + ArgParsingTestCase(Args args, T expectedResult) + : args(std::move(args)), expectedResult(std::move(expectedResult)) {} +}; + +// MARK: - Printers + +void PrintTo(const Args &, std::ostream *); + +template +void PrintTo(const ArgParsingTestCase &value, std::ostream *os) { + PrintTo(value.args, os); +} + +#endif // ARG_PARSING_TEST_H diff --git a/unittests/Frontend/CMakeLists.txt b/unittests/Frontend/CMakeLists.txt new file mode 100644 index 0000000000000..c1d232b59dad4 --- /dev/null +++ b/unittests/Frontend/CMakeLists.txt @@ -0,0 +1,9 @@ +add_swift_unittest(SwiftFrontendTests + ArgParsingTest.cpp + FeatureParsingTest.cpp + IsFeatureEnabledTests.cpp + StrictConcurrencyTests.cpp) + +target_link_libraries(SwiftFrontendTests + PRIVATE + swiftFrontend) diff --git a/unittests/Frontend/FeatureParsingTest.cpp b/unittests/Frontend/FeatureParsingTest.cpp new file mode 100644 index 0000000000000..ee4025059f460 --- /dev/null +++ b/unittests/Frontend/FeatureParsingTest.cpp @@ -0,0 +1,62 @@ +//===-- FeatureParsingTest.cpp ----------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#include "FeatureParsingTest.h" + +using namespace swift; + +const std::string FeatureParsingTest::defaultLangMode = "5"; + +FeatureParsingTest::FeatureParsingTest() : ArgParsingTest() { + this->langMode = defaultLangMode; +} + +FeatureWrapper::FeatureWrapper(Feature id) + : id(id), name(getFeatureName(id).data()) { + auto langMode = getFeatureLanguageVersion(id); + if (langMode) { + this->langMode = std::to_string(*langMode); + } +} + +void swift::PrintTo(const StrictConcurrency &value, std::ostream *os) { + switch (value) { + case StrictConcurrency::Minimal: + *os << "Minimal"; + break; + case StrictConcurrency::Targeted: + *os << "Targeted"; + break; + case StrictConcurrency::Complete: + *os << "Complete"; + break; + } +} + +void swift::PrintTo(const LangOptions::FeatureState &value, std::ostream *os) { + PrintTo((const LangOptions::FeatureState::Kind &)value, os); +} + +void swift::PrintTo(const LangOptions::FeatureState::Kind &value, + std::ostream *os) { + switch (value) { + case LangOptions::FeatureState::Off: + *os << "Off"; + break; + case LangOptions::FeatureState::EnabledForAdoption: + *os << "EnabledForAdoption"; + break; + case LangOptions::FeatureState::Enabled: + *os << "Enabled"; + break; + } +} diff --git a/unittests/Frontend/FeatureParsingTest.h b/unittests/Frontend/FeatureParsingTest.h new file mode 100644 index 0000000000000..59d93ebacd738 --- /dev/null +++ b/unittests/Frontend/FeatureParsingTest.h @@ -0,0 +1,45 @@ +//===-- FeatureParsingTest.h ------------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#ifndef FEATURE_PARSING_TEST_H +#define FEATURE_PARSING_TEST_H + +#include "ArgParsingTest.h" +#include "swift/Basic/Feature.h" + +struct FeatureParsingTest : public ArgParsingTest { + static const std::string defaultLangMode; + + FeatureParsingTest(); +}; + +struct FeatureWrapper final { + swift::Feature id; + std::string name; + std::string langMode; + + FeatureWrapper(swift::Feature id); + + operator swift::Feature() const { return id; } +}; + +// MARK: - Printers + +// Google Test requires custom printers to be declared in the same namespace as +// the type they take. +namespace swift { +void PrintTo(const StrictConcurrency &, std::ostream *); +void PrintTo(const LangOptions::FeatureState &, std::ostream *); +void PrintTo(const LangOptions::FeatureState::Kind &, std::ostream *); +} // end namespace swift + +#endif // FEATURE_PARSING_TEST_H diff --git a/unittests/Frontend/IsFeatureEnabledTests.cpp b/unittests/Frontend/IsFeatureEnabledTests.cpp new file mode 100644 index 0000000000000..b0308093dccf6 --- /dev/null +++ b/unittests/Frontend/IsFeatureEnabledTests.cpp @@ -0,0 +1,641 @@ +//===-- IsFeatureEnabledTests.cpp -------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#include "FeatureParsingTest.h" +#include + +using namespace swift; + +namespace { + +static const FeatureWrapper baselineF(Feature::AsyncAwait); +static const FeatureWrapper upcomingF(Feature::DynamicActorIsolation); +static const FeatureWrapper adoptableUpcomingF(Feature::ExistentialAny); +static const FeatureWrapper experimentalF(Feature::NamedOpaqueTypes); +static const FeatureWrapper strictConcurrencyF(Feature::StrictConcurrency); + +using FeatureState = LangOptions::FeatureState; + +using IsFeatureEnabledTestCase = + ArgParsingTestCase>; + +class IsFeatureEnabledTest + : public FeatureParsingTest, + public ::testing::WithParamInterface {}; + +// Test that the chosen features for testing match our expectations. +TEST_F(IsFeatureEnabledTest, VerifyTestedFeatures) { + auto feature = baselineF; + { + ASSERT_FALSE(getUpcomingFeature(feature.name)); + ASSERT_FALSE(getExperimentalFeature(feature.name)); + ASSERT_FALSE(isFeatureAdoptable(feature)); + } + + feature = upcomingF; + { + ASSERT_TRUE(getUpcomingFeature(feature.name)); + ASSERT_FALSE(isFeatureAdoptable(feature)); + ASSERT_LT(defaultLangMode, feature.langMode); + } + + feature = adoptableUpcomingF; + { + ASSERT_TRUE(getUpcomingFeature(feature.name)); + ASSERT_TRUE(isFeatureAdoptable(feature)); + ASSERT_LT(defaultLangMode, feature.langMode); + } + + feature = strictConcurrencyF; + { + ASSERT_TRUE(getUpcomingFeature(feature.name)); + ASSERT_FALSE(isFeatureAdoptable(feature)); + ASSERT_LT(defaultLangMode, feature.langMode); + } + + feature = experimentalF; + { + ASSERT_TRUE(getExperimentalFeature(feature.name)); + ASSERT_FALSE(isFeatureAdoptable(feature)); + } +} + +TEST_P(IsFeatureEnabledTest, ) { + auto &testCase = GetParam(); + parseArgs(testCase.args); + + for (auto &pair : testCase.expectedResult) { + auto feature = pair.first; + auto actualState = getLangOptions().getFeatureState(feature); + auto expectedState = pair.second; + ASSERT_EQ(actualState, expectedState) + << "Feature: " + getFeatureName(feature).str(); + } +} + +// MARK: - Default state + +// clang-format off +static const IsFeatureEnabledTestCase defaultStateTestCases[] = { + IsFeatureEnabledTestCase( + {}, { + {baselineF, FeatureState::Enabled}, + {upcomingF, FeatureState::Off}, + {adoptableUpcomingF, FeatureState::Off}, + {strictConcurrencyF, FeatureState::Off}, + {experimentalF, FeatureState::Off}, + }), + IsFeatureEnabledTestCase( + {"-swift-version", upcomingF.langMode}, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-swift-version", strictConcurrencyF.langMode}, + {{strictConcurrencyF, FeatureState::Enabled}}), +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(DefaultState, IsFeatureEnabledTest, + ::testing::ValuesIn(defaultStateTestCases)); + +// MARK: - Single enable + +// clang-format off +static const IsFeatureEnabledTestCase singleEnableTestCases[] = { + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", baselineF.name}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", baselineF.name + ":undef"}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", baselineF.name + ":adoption"}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", baselineF.name}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", baselineF.name + ":undef"}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", baselineF.name + ":adoption"}, + {{baselineF, FeatureState::Enabled}}), + + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", upcomingF.name}, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", upcomingF.name + ":undef"}, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", upcomingF.name + ":adoption"}, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", upcomingF.name}, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", upcomingF.name + ":undef"}, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", upcomingF.name + ":adoption"}, + {{upcomingF, FeatureState::Off}}), + + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", adoptableUpcomingF.name}, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", adoptableUpcomingF.name + ":undef"}, + {{adoptableUpcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption"}, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), +// Swift 7 is asserts-only. +#ifndef NDEBUG + // Requesting adoption mode in target language mode has no effect. + IsFeatureEnabledTestCase({ + "-swift-version", adoptableUpcomingF.langMode, + "-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), +#endif + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", adoptableUpcomingF.name}, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", adoptableUpcomingF.name + ":undef"}, + {{adoptableUpcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", adoptableUpcomingF.name + ":adoption"}, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), +// Swift 7 is asserts-only. +#ifndef NDEBUG + // Requesting adoption mode in target language mode has no effect. + IsFeatureEnabledTestCase({ + "-swift-version", adoptableUpcomingF.langMode, + "-enable-experimental-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), +#endif + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name}, + {{strictConcurrencyF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + ":undef"}, + {{strictConcurrencyF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + ":adoption"}, + {{strictConcurrencyF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name}, + {{strictConcurrencyF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + ":undef"}, + {{strictConcurrencyF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + ":adoption"}, + {{strictConcurrencyF, FeatureState::Off}}), + + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", experimentalF.name}, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", experimentalF.name + ":undef"}, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-upcoming-feature", experimentalF.name + ":adoption"}, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", experimentalF.name}, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", experimentalF.name + ":undef"}, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-enable-experimental-feature", experimentalF.name + ":adoption"}, + {{experimentalF, FeatureState::Off}}), +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(SingleEnable, IsFeatureEnabledTest, + ::testing::ValuesIn(singleEnableTestCases)); + +// MARK: - Single disable + +// clang-format off +static const IsFeatureEnabledTestCase singleDisableTestCases[] = { + IsFeatureEnabledTestCase( + {"-disable-upcoming-feature", baselineF.name}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-disable-experimental-feature", baselineF.name}, + {{baselineF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase( + {"-disable-upcoming-feature", upcomingF.name}, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-disable-experimental-feature", upcomingF.name}, + {{upcomingF, FeatureState::Off}}), + + // Disabling in target language mode has no effect. + IsFeatureEnabledTestCase({ + "-swift-version", upcomingF.langMode, + "-disable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-swift-version", upcomingF.langMode, + "-disable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + + IsFeatureEnabledTestCase( + {"-disable-upcoming-feature", strictConcurrencyF.name}, + {{strictConcurrencyF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-disable-experimental-feature", strictConcurrencyF.name}, + {{strictConcurrencyF, FeatureState::Off}}), + + // Disabling in target language mode has no effect. + IsFeatureEnabledTestCase({ + "-disable-upcoming-feature", strictConcurrencyF.name, + "-swift-version", strictConcurrencyF.langMode, + }, + {{strictConcurrencyF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-experimental-feature", strictConcurrencyF.name, + "-swift-version", strictConcurrencyF.langMode, + }, + {{strictConcurrencyF, FeatureState::Enabled}}), + + IsFeatureEnabledTestCase( + {"-disable-upcoming-feature", experimentalF.name}, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase( + {"-disable-experimental-feature", experimentalF.name}, + {{experimentalF, FeatureState::Off}}), +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(SingleDisable, IsFeatureEnabledTest, + ::testing::ValuesIn(singleDisableTestCases)); + +// MARK: - Double enable + +// clang-format off +static const IsFeatureEnabledTestCase doubleEnableTestCases[] = { + + // MARK: Non-adoptable & upcoming + + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name + ":undef", + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name + ":adoption", + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name + ":undef", + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name + ":adoption", + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name + ":undef", + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name + ":adoption", + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name + ":undef", + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name + ":adoption", + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + + // MARK: Adoptable & upcoming + + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name + ":undef", + "-enable-upcoming-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption", + "-enable-upcoming-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name, + "-enable-upcoming-feature", adoptableUpcomingF.name + ":undef", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name, + "-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name + ":undef", + "-enable-experimental-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption", + "-enable-experimental-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name, + "-enable-experimental-feature", adoptableUpcomingF.name + ":undef", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", adoptableUpcomingF.name, + "-enable-experimental-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name + ":undef", + "-enable-upcoming-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name + ":adoption", + "-enable-upcoming-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name, + "-enable-upcoming-feature", adoptableUpcomingF.name + ":undef", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name, + "-enable-upcoming-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name + ":undef", + "-enable-experimental-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name + ":adoption", + "-enable-experimental-feature", adoptableUpcomingF.name, + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name, + "-enable-experimental-feature", adoptableUpcomingF.name + ":undef", + }, + {{adoptableUpcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", adoptableUpcomingF.name, + "-enable-experimental-feature", adoptableUpcomingF.name + ":adoption", + }, + {{adoptableUpcomingF, FeatureState::EnabledForAdoption}}), + + // MARK: Experimental + + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name + ":undef", + "-enable-experimental-feature", experimentalF.name, + }, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name + ":adoption", + "-enable-experimental-feature", experimentalF.name, + }, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name, + "-enable-experimental-feature", experimentalF.name + ":undef", + }, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name, + "-enable-experimental-feature", experimentalF.name + ":adoption", + }, + {{experimentalF, FeatureState::Enabled}}), +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(DoubleEnable, IsFeatureEnabledTest, + ::testing::ValuesIn(doubleEnableTestCases)); + +// MARK: - Enable / disable + +// clang-format off +static const IsFeatureEnabledTestCase enableDisableTestCases[] = { + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-upcoming-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-experimental-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Off}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name + ":undef", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", upcomingF.name, + "-disable-experimental-feature", upcomingF.name + ":adoption", + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-experimental-feature", upcomingF.name, + "-enable-experimental-feature", upcomingF.name, + }, + {{upcomingF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name, + "-disable-experimental-feature", experimentalF.name, + }, + {{experimentalF, FeatureState::Off}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name, + "-disable-experimental-feature", experimentalF.name + ":undef", + }, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-enable-experimental-feature", experimentalF.name, + "-disable-experimental-feature", experimentalF.name + ":adoption", + }, + {{experimentalF, FeatureState::Enabled}}), + IsFeatureEnabledTestCase({ + "-disable-experimental-feature", experimentalF.name, + "-enable-experimental-feature", experimentalF.name, + }, + {{experimentalF, FeatureState::Enabled}}), +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(EnableDisable, IsFeatureEnabledTest, + ::testing::ValuesIn(enableDisableTestCases)); + +// MARK: - Last option wins + +// clang-format off +static const IsFeatureEnabledTestCase lastOptionWinsTestCases[] = { + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", experimentalF.name, + "-disable-upcoming-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name, + "-disable-experimental-feature", experimentalF.name, + "-disable-upcoming-feature", upcomingF.name, + }, { + {upcomingF, FeatureState::Off}, + {experimentalF, FeatureState::Off} + }), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", upcomingF.name, + "-disable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", experimentalF.name, + "-disable-upcoming-feature", upcomingF.name, + "-enable-upcoming-feature", upcomingF.name, + "-disable-experimental-feature", experimentalF.name, + "-disable-upcoming-feature", upcomingF.name, + "-enable-experimental-feature", experimentalF.name, + "-enable-upcoming-feature", upcomingF.name, + }, { + {upcomingF, FeatureState::Enabled}, + {experimentalF, FeatureState::Enabled} + }), + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", strictConcurrencyF.name + "=targeted", + "-disable-upcoming-feature", strictConcurrencyF.name, + "-enable-upcoming-feature", strictConcurrencyF.name + "=minimal", + }, + {{strictConcurrencyF, FeatureState::Off}}), // FIXME? + IsFeatureEnabledTestCase({ + "-enable-upcoming-feature", strictConcurrencyF.name + "=targeted", + "-enable-upcoming-feature", strictConcurrencyF.name + "=complete", + "-disable-upcoming-feature", strictConcurrencyF.name, + }, + {{strictConcurrencyF, FeatureState::Enabled}}), // FIXME? +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(LastOptionWins, IsFeatureEnabledTest, + ::testing::ValuesIn(lastOptionWinsTestCases)); + +} // end anonymous namespace diff --git a/unittests/Frontend/StrictConcurrencyTests.cpp b/unittests/Frontend/StrictConcurrencyTests.cpp new file mode 100644 index 0000000000000..3e911291883af --- /dev/null +++ b/unittests/Frontend/StrictConcurrencyTests.cpp @@ -0,0 +1,122 @@ +//===-- StrictConcurrencyTests.cpp ------------------------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 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 +// +//===----------------------------------------------------------------------===// + +#include "FeatureParsingTest.h" + +using namespace swift; + +namespace { + +static const FeatureWrapper strictConcurrencyF(Feature::StrictConcurrency); + +using StrictConcurrencyTestCase = ArgParsingTestCase; + +class StrictConcurrencyTest + : public FeatureParsingTest, + public ::testing::WithParamInterface {}; + +TEST_P(StrictConcurrencyTest, ) { + auto &testCase = GetParam(); + parseArgs(testCase.args); + ASSERT_EQ(getLangOptions().StrictConcurrencyLevel, testCase.expectedResult); +} + +// clang-format off +static const StrictConcurrencyTestCase strictConcurrencyTestCases[] = { + StrictConcurrencyTestCase( + {}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-swift-version", strictConcurrencyF.langMode}, + StrictConcurrency::Complete), + StrictConcurrencyTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name}, + StrictConcurrency::Complete), + StrictConcurrencyTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + ":adoption"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name}, + StrictConcurrency::Complete), + StrictConcurrencyTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + ":adoption"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-upcoming-feature", strictConcurrencyF.name}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-experimental-feature", strictConcurrencyF.name}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + "=minimal"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + "=minimal"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-upcoming-feature", strictConcurrencyF.name + "=minimal"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-experimental-feature", strictConcurrencyF.name + "=minimal"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + "=targeted"}, + StrictConcurrency::Targeted), + StrictConcurrencyTestCase({ + "-enable-upcoming-feature", + strictConcurrencyF.name + "=targeted:adoption", + }, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + "=targeted"}, + StrictConcurrency::Targeted), + StrictConcurrencyTestCase({ + "-enable-experimental-feature", + strictConcurrencyF.name + "=targeted:adoption", + }, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-upcoming-feature", strictConcurrencyF.name + "=targeted"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-experimental-feature", strictConcurrencyF.name + "=targeted"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-enable-upcoming-feature", strictConcurrencyF.name + "=complete"}, + StrictConcurrency::Complete), + StrictConcurrencyTestCase( + {"-enable-experimental-feature", strictConcurrencyF.name + "=complete"}, + StrictConcurrency::Complete), + StrictConcurrencyTestCase( + {"-disable-upcoming-feature", strictConcurrencyF.name + "=complete"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase( + {"-disable-experimental-feature", strictConcurrencyF.name + "=complete"}, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase({ + "-enable-upcoming-feature", strictConcurrencyF.name + "=targeted", + "-disable-upcoming-feature", strictConcurrencyF.name, + "-enable-upcoming-feature", strictConcurrencyF.name + "=minimal", + }, + StrictConcurrency::Minimal), + StrictConcurrencyTestCase({ + "-enable-upcoming-feature", strictConcurrencyF.name + "=targeted", + "-enable-upcoming-feature", strictConcurrencyF.name + "=complete", + "-disable-upcoming-feature", strictConcurrencyF.name, + }, + StrictConcurrency::Complete), // FIXME? +}; +// clang-format on +INSTANTIATE_TEST_SUITE_P(, StrictConcurrencyTest, + ::testing::ValuesIn(strictConcurrencyTestCases)); + +} // end anonymous namespace diff --git a/unittests/FrontendTool/CMakeLists.txt b/unittests/FrontendTool/CMakeLists.txt index e726099af6b86..fa10b091e130f 100644 --- a/unittests/FrontendTool/CMakeLists.txt +++ b/unittests/FrontendTool/CMakeLists.txt @@ -1,8 +1,8 @@ -add_swift_unittest(SwiftFrontendTests +add_swift_unittest(SwiftFrontendToolTests FrontendToolTests.cpp ModuleLoadingTests.cpp) -target_link_libraries(SwiftFrontendTests +target_link_libraries(SwiftFrontendToolTests PRIVATE swiftFrontend swiftFrontendTool)