Skip to content

Commit 818fcdd

Browse files
committed
Add “swift -e” support
“swift -e” can be used to run code specified directly on the command line rather than passed through stdin or a source file. The values of the “-e” switches are each treated as one line of source code, which are concatenated together into a temporary main.swift file and passed to the frontend jobs. Fixes rdar://17258540.
1 parent 2f65431 commit 818fcdd

File tree

5 files changed

+52
-6
lines changed

5 files changed

+52
-6
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ public struct Driver {
562562
swiftCompilerPrefixArgs: self.swiftCompilerPrefixArgs)
563563

564564
// Classify and collect all of the input files.
565-
let inputFiles = try Self.collectInputFiles(&self.parsedOptions, diagnosticsEngine: diagnosticsEngine)
565+
let inputFiles = try Self.collectInputFiles(&self.parsedOptions, diagnosticsEngine: diagnosticsEngine, fileSystem: self.fileSystem)
566566
self.inputFiles = inputFiles
567567
self.recordedInputModificationDates = .init(uniqueKeysWithValues:
568568
Set(inputFiles).compactMap {
@@ -1791,10 +1791,11 @@ extension Driver {
17911791
/// Collect all of the input files from the parsed options, translating them into input files.
17921792
private static func collectInputFiles(
17931793
_ parsedOptions: inout ParsedOptions,
1794-
diagnosticsEngine: DiagnosticsEngine
1794+
diagnosticsEngine: DiagnosticsEngine,
1795+
fileSystem: FileSystem
17951796
) throws -> [TypedVirtualPath] {
17961797
var swiftFiles = [String: String]() // [Basename: Path]
1797-
return try parsedOptions.allInputs.map { input in
1798+
var paths = try parsedOptions.allInputs.map { input in
17981799
// Standard input is assumed to be Swift code.
17991800
if input == "-" {
18001801
return TypedVirtualPath(file: .standardInput, type: .swift)
@@ -1824,6 +1825,29 @@ extension Driver {
18241825

18251826
return TypedVirtualPath(file: inputHandle, type: fileType)
18261827
}
1828+
1829+
if parsedOptions.hasArgument(.e) {
1830+
if let mainPath = swiftFiles["main.swift"] {
1831+
diagnosticsEngine.emit(.error_two_files_same_name(basename: "main.swift", firstPath: mainPath, secondPath: "-e"))
1832+
diagnosticsEngine.emit(.note_explain_two_files_same_name)
1833+
throw Diagnostics.fatalError
1834+
}
1835+
1836+
try withTemporaryDirectory(dir: fileSystem.tempDirectory, removeTreeOnDeinit: false) { absPath in
1837+
let filePath = VirtualPath.absolute(absPath.appending(component: "main.swift"))
1838+
1839+
try fileSystem.writeFileContents(filePath) { file in
1840+
file <<< ###"#sourceLocation(file: "-e", line: 1)\###n"###
1841+
for option in parsedOptions.arguments(for: .e) {
1842+
file <<< option.argument.asSingle <<< "\n"
1843+
}
1844+
}
1845+
1846+
paths.append(TypedVirtualPath(file: filePath.intern(), type: .swift))
1847+
}
1848+
}
1849+
1850+
return paths
18271851
}
18281852

18291853
/// Determine the primary compiler and linker output kinds.

Sources/SwiftOptions/OptionParsing.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ extension OptionTable {
6161
parsedOptions.addInput(argument)
6262

6363
// In interactive mode, synthesize a "--" argument for all args after the first input.
64-
if driverKind == .interactive && index < arguments.endIndex {
64+
if driverKind == .interactive && index < arguments.endIndex && !parsedOptions.lookupWithoutConsuming(.e).isEmpty {
6565
parsedOptions.addOption(.DASHDASH, argument: .multiple(Array(arguments[index...])))
6666
break
6767
}

Sources/SwiftOptions/Options.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ extension Option {
246246
public static let emitDigesterBaselinePath: Option = Option("-emit-digester-baseline-path", .separate, attributes: [.noInteractive, .argumentIsPath, .supplementaryOutput], metaVar: "<path>", helpText: "Emit a baseline file for the module to <path> using the API digester")
247247
public static let emitDigesterBaseline: Option = Option("-emit-digester-baseline", .flag, attributes: [.noInteractive, .supplementaryOutput], helpText: "Emit a baseline file for the module using the API digester")
248248
public static let emitExecutable: Option = Option("-emit-executable", .flag, attributes: [.noInteractive, .doesNotAffectIncrementalBuild], helpText: "Emit a linked executable", group: .modes)
249+
public static let emitExtensionBlockSymbols: Option = Option("-emit-extension-block-symbols", .flag, attributes: [.helpHidden, .frontend, .noInteractive, .supplementaryOutput], helpText: "Emit 'swift.extension' symbols for extensions to external types instead of directly associating members and conformances with the extended nominal when generating symbol graphs")
249250
public static let emitFineGrainedDependencySourcefileDotFiles: Option = Option("-emit-fine-grained-dependency-sourcefile-dot-files", .flag, attributes: [.helpHidden, .frontend], helpText: "Emit dot files for every source file.")
250251
public static let emitFixitsPath: Option = Option("-emit-fixits-path", .separate, attributes: [.frontend, .noDriver], metaVar: "<path>", helpText: "Output compiler fixits as source edits to <path>")
251252
public static let emitImportedModules: Option = Option("-emit-imported-modules", .flag, attributes: [.frontend, .noInteractive, .doesNotAffectIncrementalBuild], helpText: "Emit a list of the imported modules", group: .modes)
@@ -394,6 +395,7 @@ extension Option {
394395
public static let driverExplicitModuleBuild: Option = Option("-explicit-module-build", .flag, attributes: [.helpHidden], helpText: "Prebuild module dependencies to make them explicit")
395396
public static let explicitSwiftModuleMap: Option = Option("-explicit-swift-module-map-file", .separate, attributes: [.frontend, .noDriver], metaVar: "<path>", helpText: "Specify a JSON file containing information of explicit Swift modules")
396397
public static let externalPassPipelineFilename: Option = Option("-external-pass-pipeline-filename", .separate, attributes: [.helpHidden, .frontend, .noDriver], metaVar: "<pass_pipeline_file>", helpText: "Use the pass pipeline defined by <pass_pipeline_file>")
398+
public static let e: Option = Option("-e", .joinedOrSeparate, attributes: [], helpText: "Executes a line of code provided on the command line")
397399
public static let FEQ: Option = Option("-F=", .joined, alias: Option.F, attributes: [.frontend, .argumentIsPath])
398400
public static let fileCompilationDir: Option = Option("-file-compilation-dir", .separate, attributes: [.frontend], metaVar: "<path>", helpText: "The compilation directory to embed in the debug info. Coverage mapping is not supported yet.")
399401
public static let filePrefixMap: Option = Option("-file-prefix-map", .separate, attributes: [.frontend], metaVar: "<prefix=replacement>", helpText: "Remap source paths in debug, coverage, and index info")
@@ -575,7 +577,8 @@ extension Option {
575577
public static let repl: Option = Option("-repl", .flag, attributes: [.helpHidden, .frontend, .noBatch], helpText: "REPL mode (the default if there is no input file)", group: .modes)
576578
public static let reportErrorsToDebugger: Option = Option("-report-errors-to-debugger", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Deprecated, will be removed in future versions.")
577579
public static let requireExplicitAvailabilityTarget: Option = Option("-require-explicit-availability-target", .separate, attributes: [.frontend, .noInteractive], metaVar: "<target>", helpText: "Suggest fix-its adding @available(<target>, *) to public declarations without availability")
578-
public static let requireExplicitAvailability: Option = Option("-require-explicit-availability", .flag, attributes: [.frontend, .noInteractive], helpText: "Require explicit availability on public declarations")
580+
public static let requireExplicitAvailabilityEQ: Option = Option("-require-explicit-availability=", .joined, attributes: [.frontend, .noInteractive], metaVar: "<error,warn,ignore>", helpText: "Set diagnostic level to report public declarations without an availability attribute")
581+
public static let requireExplicitAvailability: Option = Option("-require-explicit-availability", .flag, attributes: [.frontend, .noInteractive], helpText: "Warn on public declarations without an availability attribute")
579582
public static let requireExplicitSendable: Option = Option("-require-explicit-sendable", .flag, attributes: [.frontend, .noInteractive], helpText: "Require explicit Sendable annotations on public declarations")
580583
public static let requirementMachineMaxConcreteNesting: Option = Option("-requirement-machine-max-concrete-nesting=", .joined, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Set the maximum concrete type nesting depth before giving up")
581584
public static let requirementMachineMaxRuleCount: Option = Option("-requirement-machine-max-rule-count=", .joined, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Set the maximum number of rules before giving up")
@@ -962,6 +965,7 @@ extension Option {
962965
Option.emitDigesterBaselinePath,
963966
Option.emitDigesterBaseline,
964967
Option.emitExecutable,
968+
Option.emitExtensionBlockSymbols,
965969
Option.emitFineGrainedDependencySourcefileDotFiles,
966970
Option.emitFixitsPath,
967971
Option.emitImportedModules,
@@ -1110,6 +1114,7 @@ extension Option {
11101114
Option.driverExplicitModuleBuild,
11111115
Option.explicitSwiftModuleMap,
11121116
Option.externalPassPipelineFilename,
1117+
Option.e,
11131118
Option.FEQ,
11141119
Option.fileCompilationDir,
11151120
Option.filePrefixMap,
@@ -1291,6 +1296,7 @@ extension Option {
12911296
Option.repl,
12921297
Option.reportErrorsToDebugger,
12931298
Option.requireExplicitAvailabilityTarget,
1299+
Option.requireExplicitAvailabilityEQ,
12941300
Option.requireExplicitAvailability,
12951301
Option.requireExplicitSendable,
12961302
Option.requirementMachineMaxConcreteNesting,

Sources/SwiftOptions/ParsedOptions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ extension ParsedOptions {
260260
///
261261
/// This operation does not consume any inputs.
262262
public var hasAnyInput: Bool {
263-
return !lookupWithoutConsuming(.INPUT).isEmpty
263+
return !lookupWithoutConsuming(.INPUT).isEmpty || !lookupWithoutConsuming(.e).isEmpty
264264
}
265265

266266
/// Walk through all of the parsed options, modifying each one.

Tests/SwiftDriverTests/SwiftDriverTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,22 @@ final class SwiftDriverTests: XCTestCase {
298298
let driver4 = try Driver(args: ["swift", "-", "-working-directory" , "-wobble"])
299299
XCTAssertEqual(driver4.inputFiles, [ TypedVirtualPath(file: .standardInput, type: .swift )])
300300
}
301+
302+
func testDashE() throws {
303+
let fs = localFileSystem
304+
305+
let driver1 = try Driver(args: ["swift", "-e", "print(1)", "-eprint(2)", "foo/bar.swift", "baz/quux.swift"], fileSystem: fs)
306+
XCTAssertEqual(driver1.inputFiles.count, 3)
307+
let tempFilesForDriver1 = driver1.inputFiles.filter {
308+
!["bar.swift", "quux.swift"].contains($0.file.basename)
309+
}
310+
XCTAssertEqual(tempFilesForDriver1.count, 1)
311+
XCTAssertEqual(tempFilesForDriver1[0].file.basename, "main.swift")
312+
let tempFileContentsForDriver1 = try fs.readFileContents(tempFilesForDriver1[0].file.absolutePath!)
313+
XCTAssertTrue(tempFileContentsForDriver1.description.hasSuffix("\nprint(1)\nprint(2)\n"))
314+
315+
XCTAssertThrowsError(try Driver(args: ["swift", "-e", "print(1)", "baz/main.swift"], fileSystem: fs))
316+
}
301317

302318
func testRecordedInputModificationDates() throws {
303319
guard let cwd = localFileSystem.currentWorkingDirectory else {

0 commit comments

Comments
 (0)