Skip to content

Commit 707ce7e

Browse files
committed
[Explicit Module Builds] Get source locations from the dependency scanner and emit diagnostics with them
- Adopt new libSwiftScan API to get source location for each emitted diagnostic: buffer identifier, line number, column number.
1 parent 43828dd commit 707ce7e

File tree

6 files changed

+182
-15
lines changed

6 files changed

+182
-15
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

+11
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ typedef struct swiftscan_dependency_info_s *swiftscan_dependency_info_t;
4444
typedef struct swiftscan_dependency_graph_s *swiftscan_dependency_graph_t;
4545
typedef struct swiftscan_import_set_s *swiftscan_import_set_t;
4646
typedef struct swiftscan_diagnostic_info_s *swiftscan_diagnostic_info_t;
47+
typedef struct swiftscan_source_location_s *swiftscan_source_location_t;
4748

4849
typedef enum {
4950
SWIFTSCAN_DIAGNOSTIC_SEVERITY_ERROR = 0,
@@ -270,9 +271,19 @@ typedef struct {
270271
(*swiftscan_diagnostic_get_message)(swiftscan_diagnostic_info_t);
271272
swiftscan_diagnostic_severity_t
272273
(*swiftscan_diagnostic_get_severity)(swiftscan_diagnostic_info_t);
274+
swiftscan_source_location_t
275+
(*swiftscan_diagnostic_get_source_location)(swiftscan_diagnostic_info_t);
273276
void
274277
(*swiftscan_diagnostics_set_dispose)(swiftscan_diagnostic_set_t*);
275278

279+
//=== Source Location -----------------------------------------------------===//
280+
swiftscan_string_ref_t
281+
(*swiftscan_source_location_get_buffer_identifier)(swiftscan_source_location_t);
282+
int64_t
283+
(*swiftscan_source_location_get_line_number)(swiftscan_source_location_t);
284+
int64_t
285+
(*swiftscan_source_location_get_column_number)(swiftscan_source_location_t);
286+
276287
//=== Scanner Cache Functions ---------------------------------------------===//
277288
void (*swiftscan_scanner_cache_serialize)(swiftscan_scanner_t scanner, const char * path);
278289
bool (*swiftscan_scanner_cache_load)(swiftscan_scanner_t scanner, const char * path);

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

+7
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ public class InterModuleDependencyOracle {
167167
return swiftScan.canQueryPerScanDiagnostics
168168
}
169169

170+
@_spi(Testing) public func supportsDiagnosticSourceLocations() throws -> Bool {
171+
guard let swiftScan = swiftScanLibInstance else {
172+
fatalError("Attempting to query supported scanner API with no scanner instance.")
173+
}
174+
return swiftScan.supportsDiagnosticSourceLocations
175+
}
176+
170177
@_spi(Testing) public func getScannerDiagnostics() throws -> [ScannerDiagnosticPayload]? {
171178
guard let swiftScan = swiftScanLibInstance else {
172179
fatalError("Attempting to reset scanner cache with no scanner instance.")

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

+20-9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import class Foundation.JSONEncoder
2222
import class Foundation.JSONDecoder
2323
import var Foundation.EXIT_SUCCESS
2424

25+
private extension String {
26+
func stripNewline() -> String {
27+
return self.hasSuffix("\n") ? String(self.dropLast()) : self
28+
}
29+
}
30+
2531
extension Diagnostic.Message {
2632
static func warn_scan_dylib_not_found() -> Diagnostic.Message {
2733
.warning("Unable to locate libSwiftScan. Fallback to `swift-frontend` dependency scanner invocation.")
@@ -33,16 +39,16 @@ extension Diagnostic.Message {
3339
.error("Swift Caching enabled - libSwiftScan load failed (\(libPath)).")
3440
}
3541
static func scanner_diagnostic_error(_ message: String) -> Diagnostic.Message {
36-
.error(message)
42+
return .error(message.stripNewline())
3743
}
3844
static func scanner_diagnostic_warn(_ message: String) -> Diagnostic.Message {
39-
.warning(message)
45+
.warning(message.stripNewline())
4046
}
4147
static func scanner_diagnostic_note(_ message: String) -> Diagnostic.Message {
42-
.note(message)
48+
.note(message.stripNewline())
4349
}
4450
static func scanner_diagnostic_remark(_ message: String) -> Diagnostic.Message {
45-
.remark(message)
51+
.remark(message.stripNewline())
4652
}
4753
}
4854

@@ -249,15 +255,20 @@ public extension Driver {
249255
for diagnostic in diagnostics {
250256
switch diagnostic.severity {
251257
case .error:
252-
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
258+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message),
259+
location: diagnostic.sourceLocation)
253260
case .warning:
254-
diagnosticEngine.emit(.scanner_diagnostic_warn(diagnostic.message))
261+
diagnosticEngine.emit(.scanner_diagnostic_warn(diagnostic.message),
262+
location: diagnostic.sourceLocation)
255263
case .note:
256-
diagnosticEngine.emit(.scanner_diagnostic_note(diagnostic.message))
264+
diagnosticEngine.emit(.scanner_diagnostic_note(diagnostic.message),
265+
location: diagnostic.sourceLocation)
257266
case .remark:
258-
diagnosticEngine.emit(.scanner_diagnostic_remark(diagnostic.message))
267+
diagnosticEngine.emit(.scanner_diagnostic_remark(diagnostic.message),
268+
location: diagnostic.sourceLocation)
259269
case .ignored:
260-
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
270+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message),
271+
location: diagnostic.sourceLocation)
261272
}
262273
}
263274
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

+42-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import struct Foundation.Data
2121
import protocol TSCBasic.DiagnosticData
2222
import struct TSCBasic.AbsolutePath
2323
import struct TSCBasic.Diagnostic
24+
import protocol TSCBasic.DiagnosticLocation
2425

2526
public enum DependencyScanningError: LocalizedError, DiagnosticData, Equatable {
2627
case missingRequiredSymbol(String)
@@ -70,9 +71,19 @@ public enum DependencyScanningError: LocalizedError, DiagnosticData, Equatable {
7071
}
7172
}
7273

73-
@_spi(Testing) public struct ScannerDiagnosticPayload {
74-
@_spi(Testing) public let severity: Diagnostic.Behavior
75-
@_spi(Testing) public let message: String
74+
public struct ScannerDiagnosticSourceLocation : DiagnosticLocation {
75+
public var description: String {
76+
return "\(bufferIdentifier):\(lineNumber):\(columnNumber)"
77+
}
78+
public let bufferIdentifier: String
79+
public let lineNumber: Int
80+
public let columnNumber: Int
81+
}
82+
83+
public struct ScannerDiagnosticPayload {
84+
public let severity: Diagnostic.Behavior
85+
public let message: String
86+
public let sourceLocation: ScannerDiagnosticSourceLocation?
7687
}
7788

7889
internal extension swiftscan_diagnostic_severity_t {
@@ -345,6 +356,13 @@ internal extension swiftscan_diagnostic_severity_t {
345356
api.swiftscan_import_set_get_diagnostics != nil
346357
}
347358

359+
@_spi(Testing) public var supportsDiagnosticSourceLocations : Bool {
360+
return api.swiftscan_diagnostic_get_source_location != nil &&
361+
api.swiftscan_source_location_get_buffer_identifier != nil &&
362+
api.swiftscan_source_location_get_line_number != nil &&
363+
api.swiftscan_source_location_get_column_number != nil
364+
}
365+
348366
func serializeScannerCache(to path: AbsolutePath) {
349367
api.swiftscan_scanner_cache_serialize(scanner,
350368
path.description.cString(using: String.Encoding.utf8))
@@ -369,7 +387,22 @@ internal extension swiftscan_diagnostic_severity_t {
369387
}
370388
let message = try toSwiftString(api.swiftscan_diagnostic_get_message(diagnosticRef))
371389
let severity = api.swiftscan_diagnostic_get_severity(diagnosticRef)
372-
result.append(ScannerDiagnosticPayload(severity: severity.toDiagnosticBehavior(), message: message))
390+
391+
var sourceLoc: ScannerDiagnosticSourceLocation? = nil
392+
if supportsDiagnosticSourceLocations {
393+
let sourceLocRefOrNull = api.swiftscan_diagnostic_get_source_location(diagnosticRef)
394+
if let sourceLocRef = sourceLocRefOrNull {
395+
let bufferName = try toSwiftString(api.swiftscan_source_location_get_buffer_identifier(sourceLocRef))
396+
let lineNumber = api.swiftscan_source_location_get_line_number(sourceLocRef)
397+
let columnNumber = api.swiftscan_source_location_get_column_number(sourceLocRef)
398+
sourceLoc = ScannerDiagnosticSourceLocation(bufferIdentifier: bufferName,
399+
lineNumber: Int(lineNumber),
400+
columnNumber: Int(columnNumber))
401+
}
402+
}
403+
result.append(ScannerDiagnosticPayload(severity: severity.toDiagnosticBehavior(),
404+
message: message,
405+
sourceLocation: sourceLoc))
373406
}
374407
return result
375408
}
@@ -597,6 +630,11 @@ private extension swiftscan_functions_t {
597630
self.swiftscan_cache_replay_result_get_stderr = try loadOptional("swiftscan_cache_replay_result_get_stderr")
598631
self.swiftscan_cache_replay_result_dispose = try loadOptional("swiftscan_cache_replay_result_dispose")
599632

633+
self.swiftscan_diagnostic_get_source_location = try loadOptional("swiftscan_diagnostic_get_source_location")
634+
self.swiftscan_source_location_get_buffer_identifier = try loadOptional("swiftscan_source_location_get_buffer_identifier")
635+
self.swiftscan_source_location_get_line_number = try loadOptional("swiftscan_source_location_get_line_number")
636+
self.swiftscan_source_location_get_column_number = try loadOptional("swiftscan_source_location_get_column_number")
637+
600638
// Swift Overlay Dependencies
601639
self.swiftscan_swift_textual_detail_get_swift_overlay_dependencies =
602640
try loadOptional("swiftscan_swift_textual_detail_get_swift_overlay_dependencies")

Sources/swift-driver/main.swift

+7
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ do {
127127
}
128128

129129
let jobs = try driver.planBuild()
130+
131+
// Planning may result in further errors emitted
132+
// due to dependency scanning failures.
133+
guard !driver.diagnosticEngine.hasErrors else {
134+
throw Driver.ErrorDiagnostics.emitted
135+
}
136+
130137
try driver.run(jobs: jobs)
131138

132139
if driver.diagnosticEngine.hasErrors {

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

+95-2
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
14201420
throw XCTSkip("libSwiftScan does not support diagnostics query.")
14211421
}
14221422

1423+
// Missing Swift Interface dependency
14231424
try withTemporaryDirectory { path in
14241425
let main = path.appending(component: "testDependencyScanning.swift")
14251426
try localFileSystem.writeFileContents(main, bytes: "import S;")
@@ -1462,8 +1463,22 @@ final class ExplicitModuleBuildTests: XCTestCase {
14621463
XCTAssertEqual(potentialDiags?.count, 5)
14631464
let diags = try XCTUnwrap(potentialDiags)
14641465
let error = diags[0]
1465-
XCTAssertEqual(error.message, "Unable to find module dependency: 'unknown_module'")
14661466
XCTAssertEqual(error.severity, .error)
1467+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1468+
XCTAssertEqual(error.message,
1469+
"""
1470+
Unable to find module dependency: 'unknown_module'
1471+
import unknown_module
1472+
^
1473+
1474+
""")
1475+
let sourceLoc = try XCTUnwrap(error.sourceLocation)
1476+
XCTAssertTrue(sourceLoc.bufferIdentifier.hasSuffix("I.swiftinterface"))
1477+
XCTAssertEqual(sourceLoc.lineNumber, 3)
1478+
XCTAssertEqual(sourceLoc.columnNumber, 8)
1479+
} else {
1480+
XCTAssertEqual(error.message, "Unable to find module dependency: 'unknown_module'")
1481+
}
14671482
let noteI = diags[1]
14681483
XCTAssertTrue(noteI.message.starts(with: "a dependency of Swift module 'I':"))
14691484
XCTAssertEqual(noteI.severity, .note)
@@ -1474,7 +1489,85 @@ final class ExplicitModuleBuildTests: XCTestCase {
14741489
XCTAssertTrue(noteS.message.starts(with: "a dependency of Swift module 'S':"))
14751490
XCTAssertEqual(noteS.severity, .note)
14761491
let noteTest = diags[4]
1477-
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1492+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1493+
XCTAssertEqual(noteTest.message,
1494+
"""
1495+
a dependency of main module 'testDependencyScanning'
1496+
import unknown_module
1497+
^
1498+
1499+
"""
1500+
)
1501+
} else {
1502+
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1503+
}
1504+
XCTAssertEqual(noteTest.severity, .note)
1505+
}
1506+
1507+
// Missing main module dependency
1508+
try withTemporaryDirectory { path in
1509+
let main = path.appending(component: "testDependencyScanning.swift")
1510+
try localFileSystem.writeFileContents(main, bytes: "import FooBar")
1511+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1512+
var driver = try Driver(args: ["swiftc",
1513+
"-I", stdlibPath.nativePathString(escaped: true),
1514+
"-I", shimsPath.nativePathString(escaped: true),
1515+
"-explicit-module-build",
1516+
"-working-directory", path.nativePathString(escaped: true),
1517+
"-disable-clang-target",
1518+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1519+
env: ProcessEnv.vars)
1520+
let resolver = try ArgsResolver(fileSystem: localFileSystem)
1521+
var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) }
1522+
if scannerCommand.first == "-frontend" {
1523+
scannerCommand.removeFirst()
1524+
}
1525+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
1526+
let _ =
1527+
try dependencyOracle.getDependencies(workingDirectory: path,
1528+
commandLine: scannerCommand,
1529+
diagnostics: &scanDiagnostics)
1530+
let potentialDiags: [ScannerDiagnosticPayload]?
1531+
if try dependencyOracle.supportsPerScanDiagnostics() {
1532+
potentialDiags = scanDiagnostics
1533+
print("Using Per-Scan diagnostics")
1534+
} else {
1535+
potentialDiags = try dependencyOracle.getScannerDiagnostics()
1536+
print("Using Scanner-Global diagnostics")
1537+
}
1538+
XCTAssertEqual(potentialDiags?.count, 2)
1539+
let diags = try XCTUnwrap(potentialDiags)
1540+
let error = diags[0]
1541+
XCTAssertEqual(error.severity, .error)
1542+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1543+
XCTAssertEqual(error.message,
1544+
"""
1545+
Unable to find module dependency: 'FooBar'
1546+
import FooBar
1547+
^
1548+
1549+
""")
1550+
1551+
let sourceLoc = try XCTUnwrap(error.sourceLocation)
1552+
XCTAssertTrue(sourceLoc.bufferIdentifier.hasSuffix("testDependencyScanning.swift"))
1553+
XCTAssertEqual(sourceLoc.lineNumber, 1)
1554+
XCTAssertEqual(sourceLoc.columnNumber, 8)
1555+
} else {
1556+
XCTAssertEqual(error.message, "Unable to find module dependency: 'FooBar'")
1557+
}
1558+
let noteTest = diags[1]
1559+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1560+
XCTAssertEqual(noteTest.message,
1561+
"""
1562+
a dependency of main module 'testDependencyScanning'
1563+
import FooBar
1564+
^
1565+
1566+
"""
1567+
)
1568+
} else {
1569+
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1570+
}
14781571
XCTAssertEqual(noteTest.severity, .note)
14791572
}
14801573
}

0 commit comments

Comments
 (0)