diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index 0b340dfeb..715289ba9 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -52,6 +52,14 @@ extension Driver { case computed } + /// If the given option is specified but the frontend doesn't support it, throw an error. + func verifyFrontendSupportsOptionIfNecessary(_ option: Option) throws { + if parsedOptions.hasArgument(option) && !isFrontendArgSupported(option) { + diagnosticEngine.emit(.error_unsupported_opt_for_frontend(option: option)) + throw ErrorDiagnostics.emitted + } + } + /// Add frontend options that are common to different frontend invocations. mutating func addCommonFrontendOptions( commandLine: inout [Job.ArgTemplate], @@ -154,6 +162,9 @@ extension Driver { throw ErrorDiagnostics.emitted } + try verifyFrontendSupportsOptionIfNecessary(.disableUpcomingFeature) + try verifyFrontendSupportsOptionIfNecessary(.disableExperimentalFeature) + // Handle the CPU and its preferences. try commandLine.appendLast(.targetCpu, from: &parsedOptions) @@ -275,14 +286,11 @@ extension Driver { if isFrontendArgSupported(.compilerAssertions) { try commandLine.appendLast(.compilerAssertions, from: &parsedOptions) } - if isFrontendArgSupported(.enableExperimentalFeature) { - try commandLine.appendAll( - .enableExperimentalFeature, from: &parsedOptions) - } - if isFrontendArgSupported(.enableUpcomingFeature) { - try commandLine.appendAll( - .enableUpcomingFeature, from: &parsedOptions) - } + try commandLine.appendAll(.enableExperimentalFeature, + .disableExperimentalFeature, + .enableUpcomingFeature, + .disableUpcomingFeature, + from: &parsedOptions) try commandLine.appendAll(.moduleAlias, from: &parsedOptions) if isFrontendArgSupported(.enableBareSlashRegex) { try commandLine.appendLast(.enableBareSlashRegex, from: &parsedOptions) diff --git a/Sources/SwiftDriver/Utilities/Diagnostics.swift b/Sources/SwiftDriver/Utilities/Diagnostics.swift index 37c93ebd9..8b41e2d05 100644 --- a/Sources/SwiftDriver/Utilities/Diagnostics.swift +++ b/Sources/SwiftDriver/Utilities/Diagnostics.swift @@ -35,6 +35,10 @@ extension Diagnostic.Message { .error("unsupported argument '\(argument)' to option '\(option.spelling)'") } + static func error_unsupported_opt_for_frontend(option: Option) -> Diagnostic.Message { + .error("frontend does not support option '\(option.spelling)'") + } + static func error_option_requires_sanitizer(option: Option) -> Diagnostic.Message { .error("option '\(option.spelling)' requires a sanitizer to be enabled. Use -sanitize= to enable a sanitizer") } diff --git a/Sources/SwiftOptions/Options.swift b/Sources/SwiftOptions/Options.swift index 1f6968adb..4612b5cb3 100644 --- a/Sources/SwiftOptions/Options.swift +++ b/Sources/SwiftOptions/Options.swift @@ -184,6 +184,7 @@ extension Option { public static let disableDynamicActorIsolation: Option = Option("-disable-dynamic-actor-isolation", .flag, attributes: [.frontend, .doesNotAffectIncrementalBuild], helpText: "Disable dynamic actor isolation checks") public static let disableEmitGenericClassRoTList: Option = Option("-disable-emit-generic-class-ro_t-list", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable emission of a section with references to class_ro_t of generic class patterns") public static let disableExperimentalClangImporterDiagnostics: Option = Option("-disable-experimental-clang-importer-diagnostics", .flag, attributes: [.helpHidden, .frontend, .noDriver, .moduleInterface], helpText: "Disable experimental diagnostics when importing C, C++, and Objective-C libraries") + public static let disableExperimentalFeature: Option = Option("-disable-experimental-feature", .separate, attributes: [.frontend, .moduleInterface], helpText: "Disable an experimental feature") public static let disableExperimentalLifetimeDependenceInference: Option = Option("-disable-experimental-lifetime-dependence-inference", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable experimental lifetime dependence inference") public static let disableExperimentalOpenedExistentialTypes: Option = Option("-disable-experimental-opened-existential-types", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable experimental support for implicitly opened existentials") public static let disableExperimentalParserRoundTrip: Option = Option("-disable-experimental-parser-round-trip", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable round trip through the new swift parser") @@ -263,6 +264,7 @@ extension Option { public static let disableThrowsPrediction: Option = Option("-disable-throws-prediction", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disables optimization assumption that functions rarely throw errors.") public static let disableTypeLayouts: Option = Option("-disable-type-layout", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable type layout based lowering") public static let disableTypoCorrection: Option = Option("-disable-typo-correction", .flag, attributes: [.frontend, .noDriver], helpText: "Disable typo correction") + public static let disableUpcomingFeature: Option = Option("-disable-upcoming-feature", .separate, attributes: [.frontend, .moduleInterface], helpText: "Disable a feature that will be introduced in an upcoming language version") public static let disableVerifyExclusivity: Option = Option("-disable-verify-exclusivity", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Disable verification of access markers used to enforce exclusivity.") public static let disallowForwardingDriver: Option = Option("-disallow-use-new-driver", .flag, helpText: "Disable using new swift-driver") public static let downgradeTypecheckInterfaceError: Option = Option("-downgrade-typecheck-interface-error", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Downgrade error to warning when typechecking emitted module interfaces") @@ -1087,6 +1089,7 @@ extension Option { Option.disableDynamicActorIsolation, Option.disableEmitGenericClassRoTList, Option.disableExperimentalClangImporterDiagnostics, + Option.disableExperimentalFeature, Option.disableExperimentalLifetimeDependenceInference, Option.disableExperimentalOpenedExistentialTypes, Option.disableExperimentalParserRoundTrip, @@ -1166,6 +1169,7 @@ extension Option { Option.disableThrowsPrediction, Option.disableTypeLayouts, Option.disableTypoCorrection, + Option.disableUpcomingFeature, Option.disableVerifyExclusivity, Option.disallowForwardingDriver, Option.downgradeTypecheckInterfaceError, diff --git a/Tests/SwiftDriverTests/SwiftDriverTests.swift b/Tests/SwiftDriverTests/SwiftDriverTests.swift index 62ae459ba..c02b2ee35 100644 --- a/Tests/SwiftDriverTests/SwiftDriverTests.swift +++ b/Tests/SwiftDriverTests/SwiftDriverTests.swift @@ -8295,6 +8295,45 @@ final class SwiftDriverTests: XCTestCase { XCTAssertTrue(!jobs[0].commandLine.contains("-emit-irgen")) } } + + func testEnableFeatures() throws { + do { + let featureArgs = [ + "-enable-upcoming-feature", "MemberImportVisibility", + "-enable-experimental-feature", "ParserValidation", + "-enable-upcoming-feature", "ConcisePoundFile", + ] + var driver = try Driver(args: ["swiftc", "file.swift"] + featureArgs) + let jobs = try driver.planBuild().removingAutolinkExtractJobs() + XCTAssertEqual(jobs.count, 2) + + // Verify that the order of both upcoming and experimental features is preserved. + XCTAssertTrue(jobs[0].commandLine.contains(subsequence: featureArgs.map { Job.ArgTemplate.flag($0) })) + } + } + + func testDisableFeatures() throws { + let driver = try Driver(args: ["swiftc", "foo.swift"]) + guard driver.isFrontendArgSupported(.disableUpcomingFeature) else { + throw XCTSkip("Skipping: compiler does not support '-disable-upcoming-feature'") + } + + do { + let featureArgs = [ + "-enable-upcoming-feature", "MemberImportVisibility", + "-disable-upcoming-feature", "MemberImportVisibility", + "-disable-experimental-feature", "ParserValidation", + "-enable-experimental-feature", "ParserValidation", + ] + + var driver = try Driver(args: ["swiftc", "file.swift"] + featureArgs) + let jobs = try driver.planBuild().removingAutolinkExtractJobs() + XCTAssertEqual(jobs.count, 2) + + // Verify that the order of both upcoming and experimental features is preserved. + XCTAssertTrue(jobs[0].commandLine.contains(subsequence: featureArgs.map { Job.ArgTemplate.flag($0) })) + } + } } func assertString(