Skip to content

Commit 0889142

Browse files
authored
Merge pull request #77662 from tshortli/disable-upcoming-feature
Frontend: Introduce `-disable-upcoming-feature` and `-disable-experimental-feature`
2 parents fffabae + e76bcac commit 0889142

File tree

4 files changed

+177
-109
lines changed

4 files changed

+177
-109
lines changed

include/swift/Option/Options.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,11 +925,21 @@ def enable_experimental_feature :
925925
Flags<[FrontendOption, ModuleInterfaceOption]>,
926926
HelpText<"Enable an experimental feature">;
927927

928+
def disable_experimental_feature :
929+
Separate<["-"], "disable-experimental-feature">,
930+
Flags<[FrontendOption, ModuleInterfaceOption]>,
931+
HelpText<"Disable an experimental feature">;
932+
928933
def enable_upcoming_feature : Separate<["-"], "enable-upcoming-feature">,
929934
Flags<[FrontendOption, ModuleInterfaceOption]>,
930935
HelpText<"Enable a feature that will be introduced in an upcoming language "
931936
"version">;
932937

938+
def disable_upcoming_feature : Separate<["-"], "disable-upcoming-feature">,
939+
Flags<[FrontendOption, ModuleInterfaceOption]>,
940+
HelpText<"Disable a feature that will be introduced in an upcoming language "
941+
"version">;
942+
933943
def Rpass_EQ : Joined<["-"], "Rpass=">,
934944
Flags<[FrontendOption]>,
935945
HelpText<"Report performed transformations by optimization passes whose "

lib/Frontend/CompilerInvocation.cpp

Lines changed: 147 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,151 @@ static bool ParseCASArgs(CASOptions &Opts, ArgList &Args,
740740
return false;
741741
}
742742

743+
static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
744+
DiagnosticEngine &Diags,
745+
const FrontendOptions &FrontendOpts) {
746+
using namespace options;
747+
748+
bool HadError = false;
749+
750+
// Enable feature upcoming/experimental features if requested. However, leave
751+
// a feature disabled if an -enable-upcoming-feature flag is superseded by a
752+
// -disable-upcoming-feature flag. Since only the last flag specified is
753+
// honored, we iterate over them in reverse order.
754+
std::vector<StringRef> psuedoFeatures;
755+
llvm::SmallSet<Feature, 8> seenFeatures;
756+
for (const Arg *A : Args.filtered_reverse(
757+
OPT_enable_experimental_feature, OPT_disable_experimental_feature,
758+
OPT_enable_upcoming_feature, OPT_disable_upcoming_feature)) {
759+
auto &option = A->getOption();
760+
StringRef value = A->getValue();
761+
bool enableUpcoming = option.matches(OPT_enable_upcoming_feature);
762+
bool enableFeature =
763+
enableUpcoming || option.matches(OPT_enable_experimental_feature);
764+
765+
// Collect some special case pseudo-features which should be processed
766+
// separately.
767+
if (value.starts_with("StrictConcurrency") ||
768+
value.starts_with("AvailabilityMacro=")) {
769+
if (enableFeature)
770+
psuedoFeatures.push_back(value);
771+
continue;
772+
}
773+
774+
// If this was specified as an "upcoming feature", it must be recognized
775+
// as one.
776+
auto feature = getUpcomingFeature(value);
777+
if (enableUpcoming || option.matches(OPT_disable_upcoming_feature)) {
778+
if (!feature)
779+
continue;
780+
}
781+
782+
// If it's not recognized as either an upcoming feature or an experimental
783+
// feature, skip it.
784+
if (!feature) {
785+
feature = getExperimentalFeature(value);
786+
if (!feature)
787+
continue;
788+
}
789+
790+
// Skip features that are already enabled or disabled.
791+
if (!seenFeatures.insert(*feature).second)
792+
continue;
793+
794+
// If the the current language mode enables the feature by default then
795+
// diagnose and skip it.
796+
if (auto firstVersion = getFeatureLanguageVersion(*feature)) {
797+
if (Opts.isSwiftVersionAtLeast(*firstVersion)) {
798+
Diags
799+
.diagnose(SourceLoc(), diag::error_upcoming_feature_on_by_default,
800+
getFeatureName(*feature), *firstVersion)
801+
.limitBehaviorIf(!enableUpcoming, DiagnosticBehavior::Warning);
802+
if (enableUpcoming)
803+
HadError = true;
804+
805+
continue;
806+
}
807+
}
808+
809+
// If this is a known experimental feature, allow it in +Asserts
810+
// (non-release) builds for testing purposes.
811+
if (Opts.RestrictNonProductionExperimentalFeatures &&
812+
!isFeatureAvailableInProduction(*feature)) {
813+
Diags.diagnose(SourceLoc(),
814+
diag::experimental_not_supported_in_production, value);
815+
HadError = true;
816+
continue;
817+
}
818+
819+
// Enable the feature if requested.
820+
if (enableFeature)
821+
Opts.enableFeature(*feature);
822+
}
823+
824+
// Since pseudo-features don't have a boolean on/off state, process them in
825+
// the order they were specified on the command line.
826+
for (auto featureName = psuedoFeatures.rbegin(), end = psuedoFeatures.rend();
827+
featureName != end; ++featureName) {
828+
829+
// Allow StrictConcurrency to have a value that corresponds to the
830+
// -strict-concurrency=<blah> settings.
831+
if (featureName->starts_with("StrictConcurrency")) {
832+
auto decomposed = featureName->split("=");
833+
if (decomposed.first == "StrictConcurrency") {
834+
if (decomposed.second == "") {
835+
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
836+
} else if (auto level = parseStrictConcurrency(decomposed.second)) {
837+
Opts.StrictConcurrencyLevel = *level;
838+
}
839+
}
840+
continue;
841+
}
842+
843+
// Hack: In order to support using availability macros in SPM packages, we
844+
// need to be able to use:
845+
// .enableExperimentalFeature("AvailabilityMacro='...'")
846+
// within the package manifest and the feature recognizer can't recognize
847+
// this form of feature, so specially handle it here until features can
848+
// maybe have extra arguments in the future.
849+
if (featureName->starts_with("AvailabilityMacro=")) {
850+
auto availability = featureName->split("=").second;
851+
Opts.AvailabilityMacros.push_back(availability.str());
852+
continue;
853+
}
854+
}
855+
856+
// Map historical flags over to experimental features. We do this for all
857+
// compilers because that's how existing experimental feature flags work.
858+
if (Args.hasArg(OPT_enable_experimental_static_assert))
859+
Opts.enableFeature(Feature::StaticAssert);
860+
if (Args.hasArg(OPT_enable_experimental_named_opaque_types))
861+
Opts.enableFeature(Feature::NamedOpaqueTypes);
862+
if (Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures))
863+
Opts.enableFeature(Feature::FlowSensitiveConcurrencyCaptures);
864+
if (Args.hasArg(OPT_enable_experimental_move_only)) {
865+
// FIXME: drop addition of Feature::MoveOnly once its queries are gone.
866+
Opts.enableFeature(Feature::MoveOnly);
867+
Opts.enableFeature(Feature::NoImplicitCopy);
868+
Opts.enableFeature(Feature::OldOwnershipOperatorSpellings);
869+
}
870+
if (Args.hasArg(OPT_experimental_one_way_closure_params))
871+
Opts.enableFeature(Feature::OneWayClosureParameters);
872+
if (Args.hasArg(OPT_enable_experimental_forward_mode_differentiation))
873+
Opts.enableFeature(Feature::ForwardModeDifferentiation);
874+
if (Args.hasArg(OPT_enable_experimental_additive_arithmetic_derivation))
875+
Opts.enableFeature(Feature::AdditiveArithmeticDerivedConformances);
876+
877+
if (Args.hasArg(OPT_enable_experimental_opaque_type_erasure))
878+
Opts.enableFeature(Feature::OpaqueTypeErasure);
879+
880+
if (Args.hasArg(OPT_enable_builtin_module))
881+
Opts.enableFeature(Feature::BuiltinModule);
882+
883+
Opts.enableFeature(Feature::LayoutPrespecialization);
884+
885+
return HadError;
886+
}
887+
743888
static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
744889
DiagnosticEngine &Diags,
745890
const FrontendOptions &FrontendOpts) {
@@ -1012,113 +1157,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10121157
Opts.EnableExperimentalStringProcessing = true;
10131158
}
10141159

1015-
auto enableUpcomingFeature = [&Opts, &Diags](Feature feature,
1016-
bool downgradeDiag) -> bool {
1017-
// Check if this feature was introduced already in this language version.
1018-
if (auto firstVersion = getFeatureLanguageVersion(feature)) {
1019-
if (Opts.isSwiftVersionAtLeast(*firstVersion)) {
1020-
Diags
1021-
.diagnose(SourceLoc(), diag::error_upcoming_feature_on_by_default,
1022-
getFeatureName(feature), *firstVersion)
1023-
.limitBehaviorIf(downgradeDiag, DiagnosticBehavior::Warning);
1024-
return !downgradeDiag;
1025-
}
1026-
}
1027-
1028-
Opts.enableFeature(feature);
1029-
return false;
1030-
};
1031-
1032-
// Enable experimental features.
1033-
for (const Arg *A : Args.filtered(OPT_enable_experimental_feature)) {
1034-
// Allow StrictConcurrency to have a value that corresponds to the
1035-
// -strict-concurrency=<blah> settings.
1036-
StringRef value = A->getValue();
1037-
if (value.starts_with("StrictConcurrency")) {
1038-
auto decomposed = value.split("=");
1039-
if (decomposed.first == "StrictConcurrency") {
1040-
if (decomposed.second == "") {
1041-
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
1042-
} else if (auto level = parseStrictConcurrency(decomposed.second)) {
1043-
Opts.StrictConcurrencyLevel = *level;
1044-
}
1045-
}
1046-
}
1047-
1048-
// If this is a known experimental feature, allow it in +Asserts
1049-
// (non-release) builds for testing purposes.
1050-
if (auto feature = getExperimentalFeature(value)) {
1051-
if (Opts.RestrictNonProductionExperimentalFeatures &&
1052-
!isFeatureAvailableInProduction(*feature)) {
1053-
Diags.diagnose(SourceLoc(), diag::experimental_not_supported_in_production,
1054-
A->getValue());
1055-
HadError = true;
1056-
} else {
1057-
Opts.enableFeature(*feature);
1058-
}
1059-
}
1060-
1061-
// For compatibility, upcoming features can be enabled with the
1062-
// -enable-experimental-feature flag too since the feature may have
1063-
// graduated from being experimental.
1064-
if (auto feature = getUpcomingFeature(value)) {
1065-
if (enableUpcomingFeature(*feature, /*downgradeDiag=*/true))
1066-
HadError = true;
1067-
}
1068-
1069-
// Hack: In order to support using availability macros in SPM packages, we
1070-
// need to be able to use:
1071-
// .enableExperimentalFeature("AvailabilityMacro='...'")
1072-
// within the package manifest and the feature recognizer can't recognize
1073-
// this form of feature, so specially handle it here until features can
1074-
// maybe have extra arguments in the future.
1075-
auto strRef = StringRef(A->getValue());
1076-
if (strRef.starts_with("AvailabilityMacro=")) {
1077-
auto availability = strRef.split("=").second;
1078-
1079-
Opts.AvailabilityMacros.push_back(availability.str());
1080-
}
1081-
}
1082-
1083-
// Enable upcoming features.
1084-
for (const Arg *A : Args.filtered(OPT_enable_upcoming_feature)) {
1085-
// Ignore unknown features.
1086-
auto feature = getUpcomingFeature(A->getValue());
1087-
if (!feature)
1088-
continue;
1089-
1090-
if (enableUpcomingFeature(*feature, /*downgradeDiag=*/false))
1091-
HadError = true;
1092-
}
1093-
1094-
// Map historical flags over to experimental features. We do this for all
1095-
// compilers because that's how existing experimental feature flags work.
1096-
if (Args.hasArg(OPT_enable_experimental_static_assert))
1097-
Opts.enableFeature(Feature::StaticAssert);
1098-
if (Args.hasArg(OPT_enable_experimental_named_opaque_types))
1099-
Opts.enableFeature(Feature::NamedOpaqueTypes);
1100-
if (Args.hasArg(OPT_enable_experimental_flow_sensitive_concurrent_captures))
1101-
Opts.enableFeature(Feature::FlowSensitiveConcurrencyCaptures);
1102-
if (Args.hasArg(OPT_enable_experimental_move_only)) {
1103-
// FIXME: drop addition of Feature::MoveOnly once its queries are gone.
1104-
Opts.enableFeature(Feature::MoveOnly);
1105-
Opts.enableFeature(Feature::NoImplicitCopy);
1106-
Opts.enableFeature(Feature::OldOwnershipOperatorSpellings);
1107-
}
1108-
if (Args.hasArg(OPT_experimental_one_way_closure_params))
1109-
Opts.enableFeature(Feature::OneWayClosureParameters);
1110-
if (Args.hasArg(OPT_enable_experimental_forward_mode_differentiation))
1111-
Opts.enableFeature(Feature::ForwardModeDifferentiation);
1112-
if (Args.hasArg(OPT_enable_experimental_additive_arithmetic_derivation))
1113-
Opts.enableFeature(Feature::AdditiveArithmeticDerivedConformances);
1114-
1115-
if (Args.hasArg(OPT_enable_experimental_opaque_type_erasure))
1116-
Opts.enableFeature(Feature::OpaqueTypeErasure);
1117-
1118-
if (Args.hasArg(OPT_enable_builtin_module))
1119-
Opts.enableFeature(Feature::BuiltinModule);
1120-
1121-
Opts.enableFeature(Feature::LayoutPrespecialization);
1160+
if (ParseEnabledFeatureArgs(Opts, Args, Diags, FrontendOpts))
1161+
HadError = true;
11221162

11231163
Opts.EnableAppExtensionLibraryRestrictions |= Args.hasArg(OPT_enable_app_extension_library);
11241164
Opts.EnableAppExtensionRestrictions |= Args.hasArg(OPT_enable_app_extension);

test/Concurrency/experimental_feature_strictconcurrency_targeted.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// RUN: %target-swift-frontend -enable-experimental-feature StrictConcurrency=targeted %s -emit-sil -o /dev/null -verify
22
// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -verify-additional-prefix complete- -strict-concurrency=complete
3+
// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -verify-additional-prefix complete- -enable-experimental-feature StrictConcurrency
4+
// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -verify-additional-prefix complete- -enable-experimental-feature StrictConcurrency=targeted -enable-experimental-feature StrictConcurrency=complete
35
// RUN: %target-swift-frontend %s -emit-sil -o /dev/null -verify -verify-additional-prefix complete- -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation
46

57
// REQUIRES: concurrency

test/Frontend/upcoming_feature.swift

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,36 @@
55
// Make sure that hasFeature(ConciseMagicFile) evaluates true in Swift 6.
66
// RUN: %target-typecheck-verify-swift -swift-version 6
77

8-
// Make sure that hasFeature(ConciseMagicFile) is off prior to Swift 6
8+
// Make sure that hasFeature(ConciseMagicFile) is off prior to Swift 6.
99
// RUN: %target-typecheck-verify-swift -verify-additional-prefix swift5-
1010

11-
// It's fine to provide a feature that we don't know about
11+
// It's fine to provide a feature that we don't know about.
1212
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ConciseMagicFile -enable-upcoming-feature UnknownFeature
1313
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature UnknownFeature -enable-upcoming-feature ConciseMagicFile
1414

15+
// When -disable-upcoming-feature is specified, leave the feature disabled.
16+
// RUN: %target-typecheck-verify-swift -disable-upcoming-feature ConciseMagicFile -verify-additional-prefix swift5-
17+
18+
// When both -enable-upcoming-feature and -disable-upcoming-feature are
19+
// specified, the result depends on the order.
20+
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ConciseMagicFile -disable-upcoming-feature ConciseMagicFile -verify-additional-prefix swift5-
21+
// RUN: %target-typecheck-verify-swift -disable-upcoming-feature ConciseMagicFile -enable-upcoming-feature ConciseMagicFile
22+
1523
// For compatibility when a feature graduates, it's fine to refer to an
1624
// upcoming feature as an experimental feature.
1725
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ConciseMagicFile
1826

27+
// A feature that has graduated can also be disabled as an experimental feature.
28+
// RUN: %target-typecheck-verify-swift -disable-experimental-feature ConciseMagicFile -verify-additional-prefix swift5-
29+
// RUN: %target-typecheck-verify-swift -enable-experimental-feature ConciseMagicFile -disable-experimental-feature ConciseMagicFile -verify-additional-prefix swift5-
30+
// RUN: %target-typecheck-verify-swift -disable-experimental-feature ConciseMagicFile -enable-experimental-feature ConciseMagicFile
31+
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature ConciseMagicFile -disable-experimental-feature ConciseMagicFile -verify-additional-prefix swift5-
32+
1933
// It's not fine to provide a feature that's in the specified language version.
2034
// RUN: not %target-swift-frontend -typecheck -enable-upcoming-feature ConciseMagicFile -swift-version 6 %s 2>&1 | %FileCheck %s --check-prefix=CHECK-ERROR
35+
// RUN: %target-swift-frontend -typecheck -disable-upcoming-feature ConciseMagicFile -swift-version 6 %s 2>&1 | %FileCheck %s --check-prefix=CHECK-WARN
2136
// RUN: %target-swift-frontend -typecheck -enable-experimental-feature ConciseMagicFile -swift-version 6 %s 2>&1 | %FileCheck %s --check-prefix=CHECK-WARN
37+
// RUN: %target-swift-frontend -typecheck -disable-experimental-feature ConciseMagicFile -swift-version 6 %s 2>&1 | %FileCheck %s --check-prefix=CHECK-WARN
2238

2339
// REQUIRES: swift_feature_ConciseMagicFile
2440
// REQUIRES: !swift_feature_UnknownFeature

0 commit comments

Comments
 (0)