Skip to content

Commit 2abf678

Browse files
committed
Frontend: Parse a feature mode specifier
1 parent f0cfc0b commit 2abf678

File tree

5 files changed

+327
-15
lines changed

5 files changed

+327
-15
lines changed

include/swift/AST/DiagnosticsFrontend.def

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -50,6 +50,15 @@ GROUPED_WARNING(feature_not_experimental, StrictLanguageFeatures, DefaultIgnore,
5050
"use -%select{disable|enable}1-upcoming-feature instead",
5151
(StringRef, bool))
5252

53+
GROUPED_WARNING(invalid_feature_mode, StrictLanguageFeatures, none,
54+
"'%0' is not a recognized mode for feature '%1'; "
55+
"did you mean '%2'?",
56+
(StringRef, StringRef, StringRef))
57+
58+
GROUPED_WARNING(cannot_disable_feature_with_mode, StrictLanguageFeatures, none,
59+
"'%0' argument '%1' cannot specify a mode",
60+
(StringRef, StringRef))
61+
5362
ERROR(error_unknown_library_level, none,
5463
"unknown library level '%0', "
5564
"expected one of 'api', 'spi', 'ipi', or 'other'", (StringRef))

lib/Frontend/CompilerInvocation.cpp

+45-14
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,8 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
749749
OPT_enable_experimental_feature, OPT_disable_experimental_feature,
750750
OPT_enable_upcoming_feature, OPT_disable_upcoming_feature)) {
751751
auto &option = A->getOption();
752-
StringRef value = A->getValue();
752+
const StringRef argValue = A->getValue();
753+
753754
bool isEnableUpcomingFeatureFlag =
754755
option.matches(OPT_enable_upcoming_feature);
755756
bool isUpcomingFeatureFlag = isEnableUpcomingFeatureFlag ||
@@ -759,41 +760,45 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
759760

760761
// Collect some special case pseudo-features which should be processed
761762
// separately.
762-
if (value.starts_with("StrictConcurrency") ||
763-
value.starts_with("AvailabilityMacro=")) {
763+
if (argValue.starts_with("StrictConcurrency") ||
764+
argValue.starts_with("AvailabilityMacro=")) {
764765
if (isEnableFeatureFlag)
765-
psuedoFeatures.push_back(value);
766+
psuedoFeatures.push_back(argValue);
766767
continue;
767768
}
768769

769-
auto feature = getUpcomingFeature(value);
770+
// For all other features, the argument format is `<name>[:adoption]`.
771+
StringRef featureName;
772+
std::optional<StringRef> featureMode;
773+
std::tie(featureName, featureMode) = argValue.rsplit(':');
774+
if (featureMode.value().empty()) {
775+
featureMode = std::nullopt;
776+
}
777+
778+
auto feature = getUpcomingFeature(featureName);
770779
if (feature) {
771780
// Diagnose upcoming features enabled with -enable-experimental-feature.
772781
if (!isUpcomingFeatureFlag)
773-
Diags.diagnose(SourceLoc(), diag::feature_not_experimental, value,
782+
Diags.diagnose(SourceLoc(), diag::feature_not_experimental, featureName,
774783
isEnableFeatureFlag);
775784
} else {
776785
// If -enable-upcoming-feature was used and an upcoming feature was not
777786
// found, diagnose and continue.
778787
if (isUpcomingFeatureFlag) {
779-
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value,
788+
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, featureName,
780789
/*upcoming=*/true);
781790
continue;
782791
}
783792

784793
// If the feature is also not a recognized experimental feature, skip it.
785-
feature = getExperimentalFeature(value);
794+
feature = getExperimentalFeature(featureName);
786795
if (!feature) {
787-
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value,
796+
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, featureName,
788797
/*upcoming=*/false);
789798
continue;
790799
}
791800
}
792801

793-
// Skip features that are already enabled or disabled.
794-
if (!seenFeatures.insert(*feature).second)
795-
continue;
796-
797802
// If the current language mode enables the feature by default then
798803
// diagnose and skip it.
799804
if (auto firstVersion = getFeatureLanguageVersion(*feature)) {
@@ -810,11 +815,37 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
810815
if (Opts.RestrictNonProductionExperimentalFeatures &&
811816
!isFeatureAvailableInProduction(*feature)) {
812817
Diags.diagnose(SourceLoc(),
813-
diag::experimental_not_supported_in_production, value);
818+
diag::experimental_not_supported_in_production,
819+
featureName);
814820
HadError = true;
815821
continue;
816822
}
817823

824+
if (featureMode) {
825+
if (isEnableFeatureFlag) {
826+
// Diagnose an invalid mode.
827+
StringRef validModeName = "adoption";
828+
if (*featureMode != validModeName) {
829+
Diags.diagnose(SourceLoc(), diag::invalid_feature_mode, *featureMode,
830+
featureName,
831+
/*didYouMean=*/validModeName);
832+
continue;
833+
}
834+
} else {
835+
// `-disable-*-feature` flags do not support a mode specifier.
836+
Diags.diagnose(SourceLoc(), diag::cannot_disable_feature_with_mode,
837+
option.getPrefixedName(), argValue);
838+
continue;
839+
}
840+
841+
// Adoption mode is not plumbed through; ignore it.
842+
continue;
843+
}
844+
845+
// Skip features that are already enabled or disabled.
846+
if (!seenFeatures.insert(*feature).second)
847+
continue;
848+
818849
// Enable the feature if requested.
819850
if (isEnableFeatureFlag)
820851
Opts.enableFeature(*feature);
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// NB: Repetitions are intentional. We are testing that a diagnostic is emitted
2+
// for each option, not just the last one found.
3+
4+
// RUN: %target-swift-frontend -parse -swift-version 5 \
5+
// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid1 \
6+
// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid2 \
7+
// RUN: -enable-experimental-feature DynamicActorIsolation:invalid3 \
8+
// RUN: -enable-experimental-feature DynamicActorIsolation:invalid4 \
9+
// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid5 \
10+
// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid6 \
11+
// RUN: -disable-upcoming-feature DynamicActorIsolation:adoption \
12+
// RUN: -disable-experimental-feature DynamicActorIsolation:invalid7 \
13+
// RUN: -disable-experimental-feature DynamicActorIsolation:invalid8 \
14+
// RUN: -disable-experimental-feature DynamicActorIsolation:adoption \
15+
// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-5
16+
17+
// RUN: %target-swift-frontend -parse -swift-version 6 \
18+
// RUN: -enable-upcoming-feature DynamicActorIsolation:invalid1 \
19+
// RUN: -enable-upcoming-feature DynamicActorIsolation:adoption \
20+
// RUN: -enable-experimental-feature DynamicActorIsolation:invalid2 \
21+
// RUN: -enable-experimental-feature DynamicActorIsolation:adoption \
22+
// RUN: -disable-upcoming-feature DynamicActorIsolation:invalid3 \
23+
// RUN: -disable-upcoming-feature DynamicActorIsolation:adoption \
24+
// RUN: -disable-experimental-feature DynamicActorIsolation:invalid4 \
25+
// RUN: -disable-experimental-feature DynamicActorIsolation:adoption \
26+
// RUN: %s 2>&1 | %FileCheck %s --check-prefix=CHECK-SWIFT-6
27+
28+
// REQUIRES: swift_feature_DynamicActorIsolation
29+
30+
// CHECK-NOT: error:
31+
32+
// CHECK-SWIFT-5-NOT: warning:
33+
// CHECK-SWIFT-5: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:adoption' cannot specify a mode{{$}}
34+
// CHECK-SWIFT-5-NEXT: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:invalid8' cannot specify a mode{{$}}
35+
// CHECK-SWIFT-5-NEXT: warning: '-disable-experimental-feature' argument 'DynamicActorIsolation:invalid7' cannot specify a mode{{$}}
36+
// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:adoption' cannot specify a mode{{$}}
37+
// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:invalid6' cannot specify a mode{{$}}
38+
// CHECK-SWIFT-5-NEXT: warning: '-disable-upcoming-feature' argument 'DynamicActorIsolation:invalid5' cannot specify a mode{{$}}
39+
// CHECK-SWIFT-5-NEXT: warning: 'invalid4' is not a recognized mode for feature 'DynamicActorIsolation'; did you mean 'adoption'?{{$}}
40+
// CHECK-SWIFT-5-NEXT: warning: 'invalid3' is not a recognized mode for feature 'DynamicActorIsolation'; did you mean 'adoption'?{{$}}
41+
// CHECK-SWIFT-5-NEXT: warning: 'invalid2' is not a recognized mode for feature 'DynamicActorIsolation'; did you mean 'adoption'?{{$}}
42+
// CHECK-SWIFT-5-NEXT: warning: 'invalid1' is not a recognized mode for feature 'DynamicActorIsolation'; did you mean 'adoption'?{{$}}
43+
// CHECK-SWIFT-5-NOT: warning:
44+
45+
// CHECK-SWIFT-6-NOT: warning:
46+
// CHECK-SWIFT-6-COUNT-8: warning: upcoming feature 'DynamicActorIsolation' is already enabled as of Swift version 6{{$}}
47+
// CHECK-SWIFT-6-NOT: warning:
48+
49+
// CHECK-NOT: error:

0 commit comments

Comments
 (0)