Skip to content

Commit 6576929

Browse files
authored
Merge pull request #78642 from tshortli/warn-unrecognized-language-feature
Frontend: Implement optional parsing diagnostics for enabled language features
2 parents b59280c + 24f5632 commit 6576929

File tree

10 files changed

+137
-57
lines changed

10 files changed

+137
-57
lines changed

include/swift/AST/DiagnosticGroups.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ GROUP(DeprecatedDeclaration, "DeprecatedDeclaration.md")
2626
GROUP(Unsafe, "Unsafe.md")
2727
GROUP(UnknownWarningGroup, "UnknownWarningGroup.md")
2828
GROUP(PreconcurrencyImport, "PreconcurrencyImport.md")
29+
GROUP(StrictLanguageFeatures, "StrictLanguageFeatures.md")
2930

3031
#define UNDEFINE_DIAGNOSTIC_GROUPS_MACROS
3132
#include "swift/AST/DefineDiagnosticGroupsMacros.h"

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,18 @@ ERROR(error_unsupported_target_arch, none,
3737
"unsupported target architecture: '%0'", (StringRef))
3838

3939
WARNING(warning_upcoming_feature_on_by_default, none,
40-
"upcoming feature '%0' is already enabled as of Swift version %1",
41-
(StringRef, unsigned))
40+
"upcoming feature '%0' is already enabled as of Swift version %1",
41+
(StringRef, unsigned))
42+
43+
GROUPED_WARNING(unrecognized_feature, StrictLanguageFeatures, DefaultIgnore,
44+
"'%0' is not a recognized "
45+
"%select{experimental|upcoming}1 feature",
46+
(StringRef, bool))
47+
48+
GROUPED_WARNING(feature_not_experimental, StrictLanguageFeatures, DefaultIgnore,
49+
"'%0' is not an experimental feature, "
50+
"use -%select{disable|enable}1-upcoming-feature instead",
51+
(StringRef, bool))
4252

4353
ERROR(error_unknown_library_level, none,
4454
"unknown library level '%0', "

include/swift/Frontend/Frontend.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,17 @@ class CompilerInvocation {
116116
public:
117117
CompilerInvocation();
118118

119-
/// Initializes the compiler invocation for the list of arguments.
119+
/// Initializes the compiler invocation and diagnostic engine for the list of
120+
/// arguments.
120121
///
121122
/// All parsing should be additive, i.e. options should not be reset to their
122123
/// default values given the /absence/ of a flag. This is because \c parseArgs
123124
/// may be used to modify an already partially configured invocation.
124125
///
126+
/// As a side-effect of parsing, the diagnostic engine will be configured with
127+
/// the options specified by the parsed arguments. This ensures that the
128+
/// arguments can effect the behavior of diagnostics emitted during parsing.
129+
///
125130
/// Any configuration files loaded as a result of parsing arguments will be
126131
/// stored in \p ConfigurationFileBuffers, if non-null. The contents of these
127132
/// buffers should \e not be interpreted by the caller; they are only present
@@ -160,6 +165,9 @@ class CompilerInvocation {
160165
StringRef SDKPath,
161166
StringRef ResourceDir);
162167

168+
/// Configures the diagnostic engine for the invocation's options.
169+
void setUpDiagnosticEngine(DiagnosticEngine &diags);
170+
163171
void setTargetTriple(const llvm::Triple &Triple);
164172
void setTargetTriple(StringRef Triple);
165173

lib/Frontend/CompilerInvocation.cpp

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "swift/Basic/Assertions.h"
2121
#include "swift/Basic/Feature.h"
2222
#include "swift/Basic/Platform.h"
23+
#include "swift/Basic/Version.h"
2324
#include "swift/Option/Options.h"
2425
#include "swift/Option/SanitizerOptions.h"
2526
#include "swift/Parse/Lexer.h"
@@ -92,20 +93,6 @@ void CompilerInvocation::setMainExecutablePath(StringRef Path) {
9293
llvm::sys::path::remove_filename(clangPath);
9394
llvm::sys::path::append(clangPath, "clang");
9495
ClangImporterOpts.clangPath = std::string(clangPath);
95-
96-
llvm::SmallString<128> DiagnosticDocsPath(Path);
97-
llvm::sys::path::remove_filename(DiagnosticDocsPath); // Remove /swift
98-
llvm::sys::path::remove_filename(DiagnosticDocsPath); // Remove /bin
99-
llvm::sys::path::append(DiagnosticDocsPath, "share", "doc", "swift",
100-
"diagnostics");
101-
DiagnosticOpts.DiagnosticDocumentationPath = std::string(DiagnosticDocsPath.str());
102-
103-
// Compute the path to the diagnostic translations in the toolchain/build.
104-
llvm::SmallString<128> DiagnosticMessagesDir(Path);
105-
llvm::sys::path::remove_filename(DiagnosticMessagesDir); // Remove /swift
106-
llvm::sys::path::remove_filename(DiagnosticMessagesDir); // Remove /bin
107-
llvm::sys::path::append(DiagnosticMessagesDir, "share", "swift", "diagnostics");
108-
DiagnosticOpts.LocalizationPath = std::string(DiagnosticMessagesDir.str());
10996
}
11097

11198
static std::string
@@ -758,6 +745,8 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
758745
auto &option = A->getOption();
759746
StringRef value = A->getValue();
760747
bool enableUpcoming = option.matches(OPT_enable_upcoming_feature);
748+
bool isUpcomingFlag =
749+
enableUpcoming || option.matches(OPT_disable_upcoming_feature);
761750
bool enableFeature =
762751
enableUpcoming || option.matches(OPT_enable_experimental_feature);
763752

@@ -770,20 +759,28 @@ static bool ParseEnabledFeatureArgs(LangOptions &Opts, ArgList &Args,
770759
continue;
771760
}
772761

773-
// If this was specified as an "upcoming feature", it must be recognized
774-
// as one.
775762
auto feature = getUpcomingFeature(value);
776-
if (enableUpcoming || option.matches(OPT_disable_upcoming_feature)) {
777-
if (!feature)
763+
if (feature) {
764+
// Diagnose upcoming features enabled with -enable-experimental-feature.
765+
if (!isUpcomingFlag)
766+
Diags.diagnose(SourceLoc(), diag::feature_not_experimental, value,
767+
enableFeature);
768+
} else {
769+
// If -enable-upcoming-feature was used and an upcoming feature was not
770+
// found, diagnose and continue.
771+
if (isUpcomingFlag) {
772+
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value,
773+
/*upcoming=*/true);
778774
continue;
779-
}
775+
}
780776

781-
// If it's not recognized as either an upcoming feature or an experimental
782-
// feature, skip it.
783-
if (!feature) {
777+
// If the feature is also not a recognized experimental feature, skip it.
784778
feature = getExperimentalFeature(value);
785-
if (!feature)
779+
if (!feature) {
780+
Diags.diagnose(SourceLoc(), diag::unrecognized_feature, value,
781+
/*upcoming=*/false);
786782
continue;
783+
}
787784
}
788785

789786
// Skip features that are already enabled or disabled.
@@ -2369,6 +2366,9 @@ static bool ParseSearchPathArgs(SearchPathOptions &Opts, ArgList &Args,
23692366

23702367
static bool ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args,
23712368
DiagnosticEngine &Diags) {
2369+
// NOTE: This executes at the beginning of parsing the command line and cannot
2370+
// depend on the results of parsing other options.
2371+
23722372
using namespace options;
23732373

23742374
if (Args.hasArg(OPT_verify))
@@ -2492,6 +2492,56 @@ static bool ParseDiagnosticArgs(DiagnosticOptions &Opts, ArgList &Args,
24922492
return false;
24932493
}
24942494

2495+
static void configureDiagnosticEngine(
2496+
const DiagnosticOptions &Options,
2497+
std::optional<version::Version> effectiveLanguageVersion,
2498+
StringRef mainExecutablePath, DiagnosticEngine &Diagnostics) {
2499+
if (Options.ShowDiagnosticsAfterFatalError) {
2500+
Diagnostics.setShowDiagnosticsAfterFatalError();
2501+
}
2502+
if (Options.SuppressWarnings) {
2503+
Diagnostics.setSuppressWarnings(true);
2504+
}
2505+
if (Options.SuppressRemarks) {
2506+
Diagnostics.setSuppressRemarks(true);
2507+
}
2508+
Diagnostics.setWarningsAsErrorsRules(Options.WarningsAsErrorsRules);
2509+
Diagnostics.setPrintDiagnosticNamesMode(Options.PrintDiagnosticNames);
2510+
2511+
std::string docsPath = Options.DiagnosticDocumentationPath;
2512+
if (docsPath.empty()) {
2513+
// Default to a location relative to the compiler.
2514+
llvm::SmallString<128> docsPathBuffer(mainExecutablePath);
2515+
llvm::sys::path::remove_filename(docsPathBuffer); // Remove /swift
2516+
llvm::sys::path::remove_filename(docsPathBuffer); // Remove /bin
2517+
llvm::sys::path::append(docsPathBuffer, "share", "doc", "swift",
2518+
"diagnostics");
2519+
docsPath = docsPathBuffer.str();
2520+
}
2521+
Diagnostics.setDiagnosticDocumentationPath(docsPath);
2522+
2523+
if (!Options.LocalizationCode.empty()) {
2524+
std::string locPath = Options.LocalizationPath;
2525+
if (locPath.empty()) {
2526+
llvm::SmallString<128> locPathBuffer(mainExecutablePath);
2527+
llvm::sys::path::remove_filename(locPathBuffer); // Remove /swift
2528+
llvm::sys::path::remove_filename(locPathBuffer); // Remove /bin
2529+
llvm::sys::path::append(locPathBuffer, "share", "swift", "diagnostics");
2530+
locPath = locPathBuffer.str();
2531+
}
2532+
Diagnostics.setLocalization(Options.LocalizationCode, locPath);
2533+
}
2534+
2535+
if (effectiveLanguageVersion)
2536+
Diagnostics.setLanguageVersion(*effectiveLanguageVersion);
2537+
}
2538+
2539+
/// Configures the diagnostic engine for the invocation's options.
2540+
void CompilerInvocation::setUpDiagnosticEngine(DiagnosticEngine &diags) {
2541+
configureDiagnosticEngine(DiagnosticOpts, LangOpts.EffectiveLanguageVersion,
2542+
FrontendOpts.MainExecutablePath, diags);
2543+
}
2544+
24952545
/// Parse -enforce-exclusivity=... options
24962546
void parseExclusivityEnforcementOptions(const llvm::opt::Arg *A,
24972547
SILOptions &Opts,
@@ -3742,6 +3792,16 @@ bool CompilerInvocation::parseArgs(
37423792
return true;
37433793
}
37443794

3795+
// Parse options that control diagnostic behavior as early as possible, so
3796+
// that they can influence the behavior of diagnostics emitted during the
3797+
// rest of parsing.
3798+
if (ParseDiagnosticArgs(DiagnosticOpts, ParsedArgs, Diags)) {
3799+
return true;
3800+
}
3801+
configureDiagnosticEngine(DiagnosticOpts,
3802+
/*effectiveLanguageVersion=*/std::nullopt,
3803+
mainExecutablePath, Diags);
3804+
37453805
ParseAssertionArgs(ParsedArgs);
37463806

37473807
if (ParseFrontendArgs(FrontendOpts, ParsedArgs, Diags,
@@ -3796,10 +3856,6 @@ bool CompilerInvocation::parseArgs(
37963856
return true;
37973857
}
37983858

3799-
if (ParseDiagnosticArgs(DiagnosticOpts, ParsedArgs, Diags)) {
3800-
return true;
3801-
}
3802-
38033859
if (ParseMigratorArgs(MigratorOpts, LangOpts, FrontendOpts,
38043860
SearchPathOpts.RuntimeResourcePath, ParsedArgs, Diags)) {
38053861
return true;

lib/Frontend/Frontend.cpp

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -727,28 +727,11 @@ void CompilerInstance::setUpLLVMArguments() {
727727
}
728728

729729
void CompilerInstance::setUpDiagnosticOptions() {
730-
if (Invocation.getDiagnosticOptions().ShowDiagnosticsAfterFatalError) {
731-
Diagnostics.setShowDiagnosticsAfterFatalError();
732-
}
733-
if (Invocation.getDiagnosticOptions().SuppressWarnings) {
734-
Diagnostics.setSuppressWarnings(true);
735-
}
736-
if (Invocation.getDiagnosticOptions().SuppressRemarks) {
737-
Diagnostics.setSuppressRemarks(true);
738-
}
739-
Diagnostics.setWarningsAsErrorsRules(
740-
Invocation.getDiagnosticOptions().WarningsAsErrorsRules);
741-
Diagnostics.setPrintDiagnosticNamesMode(
742-
Invocation.getDiagnosticOptions().PrintDiagnosticNames);
743-
Diagnostics.setDiagnosticDocumentationPath(
744-
Invocation.getDiagnosticOptions().DiagnosticDocumentationPath);
745-
Diagnostics.setLanguageVersion(
746-
Invocation.getLangOptions().EffectiveLanguageVersion);
747-
if (!Invocation.getDiagnosticOptions().LocalizationCode.empty()) {
748-
Diagnostics.setLocalization(
749-
Invocation.getDiagnosticOptions().LocalizationCode,
750-
Invocation.getDiagnosticOptions().LocalizationPath);
751-
}
730+
// As a side-effect, argument parsing will have already partially configured
731+
// the diagnostic engine. However, it needs to be reconfigured here in case
732+
// there is any new configuration that should effect diagnostics for the rest
733+
// of the compile.
734+
Invocation.setUpDiagnosticEngine(Diagnostics);
752735
}
753736

754737
// The ordering of ModuleLoaders is important!

lib/Frontend/ModuleInterfaceLoader.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,10 +2248,6 @@ InterfaceSubContextDelegateImpl::runInSubCompilerInstance(StringRef moduleName,
22482248
subInstance.addDiagnosticConsumer(&noopConsumer);
22492249
}
22502250

2251-
// Eagerly suppress warnings if necessary, before parsing arguments.
2252-
if (subInvocation.getDiagnosticOptions().SuppressWarnings)
2253-
subInstance.getDiags().setSuppressWarnings(true);
2254-
22552251
SwiftInterfaceInfo interfaceInfo;
22562252
// Extract compiler arguments from the interface file and use them to configure
22572253
// the compiler invocation.

test/Frontend/strict_features.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %target-swift-frontend -typecheck -enable-upcoming-feature UnknownFeature %s 2>&1 | %FileCheck %s --check-prefix=CHECK-NO-DIAGS --allow-empty
2+
3+
// With -Wwarning StrictLanguageFeatures, emit extra diagnostics for
4+
// misspecified features.
5+
// RUN: %target-swift-frontend -typecheck -Wwarning StrictLanguageFeatures -enable-upcoming-feature UnknownFeature %s 2>&1 | %FileCheck %s --check-prefix=CHECK-UNKNOWN-UPCOMING
6+
// RUN: %target-swift-frontend -typecheck -Wwarning StrictLanguageFeatures -enable-experimental-feature UnknownFeature %s 2>&1 | %FileCheck %s --check-prefix=CHECK-UNKNOWN-EXPERIMENTAL
7+
// RUN: %target-swift-frontend -typecheck -Wwarning StrictLanguageFeatures -swift-version 5 -enable-experimental-feature ConciseMagicFile %s 2>&1 | %FileCheck %s --check-prefix=CHECK-NOT-EXPERIMENTAL-ENABLE
8+
// RUN: %target-swift-frontend -typecheck -Wwarning StrictLanguageFeatures -swift-version 5 -enable-experimental-feature ConciseMagicFile -disable-experimental-feature ConciseMagicFile %s 2>&1 | %FileCheck %s --check-prefix=CHECK-NOT-EXPERIMENTAL-DISABLE
9+
10+
// REQUIRES: swift_feature_ConciseMagicFile
11+
12+
// CHECK-NO-DIAGS-NOT: warning:
13+
// CHECK-NO-DIAGS-NOT: error:
14+
15+
// CHECK-UNKNOWN-UPCOMING: warning: 'UnknownFeature' is not a recognized upcoming feature
16+
// CHECK-UNKNOWN-EXPERIMENTAL: warning: 'UnknownFeature' is not a recognized experimental feature
17+
// CHECK-NOT-EXPERIMENTAL-ENABLE: warning: 'ConciseMagicFile' is not an experimental feature, use -enable-upcoming-feature instead
18+
// CHECK-NOT-EXPERIMENTAL-DISABLE: warning: 'ConciseMagicFile' is not an experimental feature, use -disable-upcoming-feature instead

test/Frontend/upcoming_feature.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
// RUN: %target-swift-frontend -typecheck -disable-experimental-feature ConciseMagicFile -swift-version 6 %s 2>&1 | %FileCheck %s
3838

3939
// REQUIRES: swift_feature_ConciseMagicFile
40-
// REQUIRES: !swift_feature_UnknownFeature
4140

4241
// CHECK: warning: upcoming feature 'ConciseMagicFile' is already enabled as of Swift version 6
4342

test/Misc/verify-swift-feature-testing.test-sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ EXCEPTIONAL_FILES = [
1616
pathlib.Path("test/Frontend/experimental-features-no-asserts.swift"),
1717
# Tests for UnknownFeature not existing
1818
pathlib.Path("test/Frontend/upcoming_feature.swift"),
19+
pathlib.Path("test/Frontend/strict_features.swift"),
1920
# Tests for ModuleInterfaceExportAs being ignored
2021
pathlib.Path("test/ModuleInterface/swift-export-as.swift"),
2122
# Uses the pseudo-feature AvailabilityMacro=
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Strict language feature enablement
2+
3+
By default, if an unrecognized feature name is specified with the
4+
`-enable-upcoming-feature` or `-enable-experimental-feature` flags, the compiler
5+
will ignore it without emitting a diagnostic since some projects must be
6+
simultaneously compatible with multiple versions of the language and toolchain.
7+
However, this warning group can be enabled to opt-in to detailed diagnostics
8+
about misspecified features.

0 commit comments

Comments
 (0)