Skip to content

Commit f9b6dca

Browse files
authored
Merge pull request #1620 from artemcm/60DepScanSourceLocs
[6.0 🍒][Explicit Module Builds] Get source locations from the dependency scanner and emit diagnostics with them
2 parents f6c8f86 + aaaf4e1 commit f9b6dca

File tree

6 files changed

+207
-26
lines changed

6 files changed

+207
-26
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 11 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ extension Diagnostic.Message {
3333
.error("Swift Caching enabled - libSwiftScan load failed (\(libPath)).")
3434
}
3535
static func scanner_diagnostic_error(_ message: String) -> Diagnostic.Message {
36-
.error(message)
36+
return .error(message)
3737
}
3838
static func scanner_diagnostic_warn(_ message: String) -> Diagnostic.Message {
3939
.warning(message)
@@ -247,15 +247,20 @@ public extension Driver {
247247
for diagnostic in diagnostics {
248248
switch diagnostic.severity {
249249
case .error:
250-
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
250+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message),
251+
location: diagnostic.sourceLocation)
251252
case .warning:
252-
diagnosticEngine.emit(.scanner_diagnostic_warn(diagnostic.message))
253+
diagnosticEngine.emit(.scanner_diagnostic_warn(diagnostic.message),
254+
location: diagnostic.sourceLocation)
253255
case .note:
254-
diagnosticEngine.emit(.scanner_diagnostic_note(diagnostic.message))
256+
diagnosticEngine.emit(.scanner_diagnostic_note(diagnostic.message),
257+
location: diagnostic.sourceLocation)
255258
case .remark:
256-
diagnosticEngine.emit(.scanner_diagnostic_remark(diagnostic.message))
259+
diagnosticEngine.emit(.scanner_diagnostic_remark(diagnostic.message),
260+
location: diagnostic.sourceLocation)
257261
case .ignored:
258-
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
262+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message),
263+
location: diagnostic.sourceLocation)
259264
}
260265
}
261266
}

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 55 additions & 16 deletions
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 {
@@ -92,6 +103,12 @@ internal extension swiftscan_diagnostic_severity_t {
92103
}
93104
}
94105

106+
private extension String {
107+
func stripNewline() -> String {
108+
return self.hasSuffix("\n") ? String(self.dropLast()) : self
109+
}
110+
}
111+
95112
/// Wrapper for libSwiftScan, taking care of initialization, shutdown, and dispatching dependency scanning queries.
96113
@_spi(Testing) public final class SwiftScan {
97114
/// The path to the libSwiftScan dylib.
@@ -148,6 +165,8 @@ internal extension swiftscan_diagnostic_severity_t {
148165
guard let importSetRef = importSetRefOrNull else {
149166
throw DependencyScanningError.dependencyScanFailed("Unable to produce import set")
150167
}
168+
defer { api.swiftscan_import_set_dispose(importSetRef) }
169+
151170
if canQueryPerScanDiagnostics {
152171
let diagnosticsSetRefOrNull = api.swiftscan_import_set_get_diagnostics(importSetRef)
153172
guard let diagnosticsSetRef = diagnosticsSetRefOrNull else {
@@ -156,11 +175,7 @@ internal extension swiftscan_diagnostic_severity_t {
156175
diagnostics = try mapToDriverDiagnosticPayload(diagnosticsSetRef)
157176
}
158177

159-
let importSet = try constructImportSet(from: importSetRef, with: moduleAliases)
160-
// Free the memory allocated for the in-memory representation of the import set
161-
// returned by the scanner, now that we have translated it.
162-
api.swiftscan_import_set_dispose(importSetRef)
163-
return importSet
178+
return try constructImportSet(from: importSetRef, with: moduleAliases)
164179
}
165180

166181
func scanDependencies(workingDirectory: AbsolutePath,
@@ -184,6 +199,8 @@ internal extension swiftscan_diagnostic_severity_t {
184199
guard let graphRef = graphRefOrNull else {
185200
throw DependencyScanningError.dependencyScanFailed("Unable to produce dependency graph")
186201
}
202+
defer { api.swiftscan_dependency_graph_dispose(graphRef) }
203+
187204
if canQueryPerScanDiagnostics {
188205
let diagnosticsSetRefOrNull = api.swiftscan_dependency_graph_get_diagnostics(graphRef)
189206
guard let diagnosticsSetRef = diagnosticsSetRefOrNull else {
@@ -192,12 +209,7 @@ internal extension swiftscan_diagnostic_severity_t {
192209
diagnostics = try mapToDriverDiagnosticPayload(diagnosticsSetRef)
193210
}
194211

195-
let dependencyGraph = try constructGraph(from: graphRef, moduleAliases: moduleAliases)
196-
// Free the memory allocated for the in-memory representation of the dependency
197-
// graph returned by the scanner, now that we have translated it into an
198-
// `InterModuleDependencyGraph`.
199-
api.swiftscan_dependency_graph_dispose(graphRef)
200-
return dependencyGraph
212+
return try constructGraph(from: graphRef, moduleAliases: moduleAliases)
201213
}
202214

203215
func batchScanDependencies(workingDirectory: AbsolutePath,
@@ -345,6 +357,13 @@ internal extension swiftscan_diagnostic_severity_t {
345357
api.swiftscan_import_set_get_diagnostics != nil
346358
}
347359

360+
@_spi(Testing) public var supportsDiagnosticSourceLocations : Bool {
361+
return api.swiftscan_diagnostic_get_source_location != nil &&
362+
api.swiftscan_source_location_get_buffer_identifier != nil &&
363+
api.swiftscan_source_location_get_line_number != nil &&
364+
api.swiftscan_source_location_get_column_number != nil
365+
}
366+
348367
func serializeScannerCache(to path: AbsolutePath) {
349368
api.swiftscan_scanner_cache_serialize(scanner,
350369
path.description.cString(using: String.Encoding.utf8))
@@ -367,9 +386,24 @@ internal extension swiftscan_diagnostic_severity_t {
367386
guard let diagnosticRef = diagnosticRefOrNull else {
368387
throw DependencyScanningError.dependencyScanFailed("Unable to produce scanner diagnostics")
369388
}
370-
let message = try toSwiftString(api.swiftscan_diagnostic_get_message(diagnosticRef))
389+
let message = try toSwiftString(api.swiftscan_diagnostic_get_message(diagnosticRef)).stripNewline()
371390
let severity = api.swiftscan_diagnostic_get_severity(diagnosticRef)
372-
result.append(ScannerDiagnosticPayload(severity: severity.toDiagnosticBehavior(), message: message))
391+
392+
var sourceLoc: ScannerDiagnosticSourceLocation? = nil
393+
if supportsDiagnosticSourceLocations {
394+
let sourceLocRefOrNull = api.swiftscan_diagnostic_get_source_location(diagnosticRef)
395+
if let sourceLocRef = sourceLocRefOrNull {
396+
let bufferName = try toSwiftString(api.swiftscan_source_location_get_buffer_identifier(sourceLocRef))
397+
let lineNumber = api.swiftscan_source_location_get_line_number(sourceLocRef)
398+
let columnNumber = api.swiftscan_source_location_get_column_number(sourceLocRef)
399+
sourceLoc = ScannerDiagnosticSourceLocation(bufferIdentifier: bufferName,
400+
lineNumber: Int(lineNumber),
401+
columnNumber: Int(columnNumber))
402+
}
403+
}
404+
result.append(ScannerDiagnosticPayload(severity: severity.toDiagnosticBehavior(),
405+
message: message,
406+
sourceLocation: sourceLoc))
373407
}
374408
return result
375409
}
@@ -597,6 +631,11 @@ private extension swiftscan_functions_t {
597631
self.swiftscan_cache_replay_result_get_stderr = try loadOptional("swiftscan_cache_replay_result_get_stderr")
598632
self.swiftscan_cache_replay_result_dispose = try loadOptional("swiftscan_cache_replay_result_dispose")
599633

634+
self.swiftscan_diagnostic_get_source_location = try loadOptional("swiftscan_diagnostic_get_source_location")
635+
self.swiftscan_source_location_get_buffer_identifier = try loadOptional("swiftscan_source_location_get_buffer_identifier")
636+
self.swiftscan_source_location_get_line_number = try loadOptional("swiftscan_source_location_get_line_number")
637+
self.swiftscan_source_location_get_column_number = try loadOptional("swiftscan_source_location_get_column_number")
638+
600639
// Swift Overlay Dependencies
601640
self.swiftscan_swift_textual_detail_get_swift_overlay_dependencies =
602641
try loadOptional("swiftscan_swift_textual_detail_get_swift_overlay_dependencies")

Sources/swift-driver/main.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ do {
120120
}
121121

122122
let jobs = try driver.planBuild()
123+
124+
// Planning may result in further errors emitted
125+
// due to dependency scanning failures.
126+
guard !driver.diagnosticEngine.hasErrors else {
127+
throw Driver.ErrorDiagnostics.emitted
128+
}
129+
123130
try driver.run(jobs: jobs)
124131

125132
if driver.diagnosticEngine.hasErrors {

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 116 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,6 +1408,7 @@ final class ExplicitModuleBuildTests: XCTestCase {
14081408
throw XCTSkip("libSwiftScan does not support diagnostics query.")
14091409
}
14101410

1411+
// Missing Swift Interface dependency
14111412
try withTemporaryDirectory { path in
14121413
let main = path.appending(component: "testDependencyScanning.swift")
14131414
try localFileSystem.writeFileContents(main, bytes: "import S;")
@@ -1450,8 +1451,21 @@ final class ExplicitModuleBuildTests: XCTestCase {
14501451
XCTAssertEqual(potentialDiags?.count, 5)
14511452
let diags = try XCTUnwrap(potentialDiags)
14521453
let error = diags[0]
1453-
XCTAssertEqual(error.message, "Unable to find module dependency: 'unknown_module'")
14541454
XCTAssertEqual(error.severity, .error)
1455+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1456+
XCTAssertEqual(error.message,
1457+
"""
1458+
Unable to find module dependency: 'unknown_module'
1459+
import unknown_module
1460+
^
1461+
""")
1462+
let sourceLoc = try XCTUnwrap(error.sourceLocation)
1463+
XCTAssertTrue(sourceLoc.bufferIdentifier.hasSuffix("I.swiftinterface"))
1464+
XCTAssertEqual(sourceLoc.lineNumber, 3)
1465+
XCTAssertEqual(sourceLoc.columnNumber, 8)
1466+
} else {
1467+
XCTAssertEqual(error.message, "Unable to find module dependency: 'unknown_module'")
1468+
}
14551469
let noteI = diags[1]
14561470
XCTAssertTrue(noteI.message.starts(with: "a dependency of Swift module 'I':"))
14571471
XCTAssertEqual(noteI.severity, .note)
@@ -1462,7 +1476,82 @@ final class ExplicitModuleBuildTests: XCTestCase {
14621476
XCTAssertTrue(noteS.message.starts(with: "a dependency of Swift module 'S':"))
14631477
XCTAssertEqual(noteS.severity, .note)
14641478
let noteTest = diags[4]
1465-
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1479+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1480+
XCTAssertEqual(noteTest.message,
1481+
"""
1482+
a dependency of main module 'testDependencyScanning'
1483+
import unknown_module
1484+
^
1485+
"""
1486+
)
1487+
} else {
1488+
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1489+
}
1490+
XCTAssertEqual(noteTest.severity, .note)
1491+
}
1492+
1493+
// Missing main module dependency
1494+
try withTemporaryDirectory { path in
1495+
let main = path.appending(component: "testDependencyScanning.swift")
1496+
try localFileSystem.writeFileContents(main, bytes: "import FooBar")
1497+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1498+
var driver = try Driver(args: ["swiftc",
1499+
"-I", stdlibPath.nativePathString(escaped: true),
1500+
"-I", shimsPath.nativePathString(escaped: true),
1501+
"-explicit-module-build",
1502+
"-working-directory", path.nativePathString(escaped: true),
1503+
"-disable-clang-target",
1504+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1505+
env: ProcessEnv.vars)
1506+
let resolver = try ArgsResolver(fileSystem: localFileSystem)
1507+
var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) }
1508+
if scannerCommand.first == "-frontend" {
1509+
scannerCommand.removeFirst()
1510+
}
1511+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
1512+
let _ =
1513+
try dependencyOracle.getDependencies(workingDirectory: path,
1514+
commandLine: scannerCommand,
1515+
diagnostics: &scanDiagnostics)
1516+
let potentialDiags: [ScannerDiagnosticPayload]?
1517+
if try dependencyOracle.supportsPerScanDiagnostics() {
1518+
potentialDiags = scanDiagnostics
1519+
print("Using Per-Scan diagnostics")
1520+
} else {
1521+
potentialDiags = try dependencyOracle.getScannerDiagnostics()
1522+
print("Using Scanner-Global diagnostics")
1523+
}
1524+
XCTAssertEqual(potentialDiags?.count, 2)
1525+
let diags = try XCTUnwrap(potentialDiags)
1526+
let error = diags[0]
1527+
XCTAssertEqual(error.severity, .error)
1528+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1529+
XCTAssertEqual(error.message,
1530+
"""
1531+
Unable to find module dependency: 'FooBar'
1532+
import FooBar
1533+
^
1534+
""")
1535+
1536+
let sourceLoc = try XCTUnwrap(error.sourceLocation)
1537+
XCTAssertTrue(sourceLoc.bufferIdentifier.hasSuffix("testDependencyScanning.swift"))
1538+
XCTAssertEqual(sourceLoc.lineNumber, 1)
1539+
XCTAssertEqual(sourceLoc.columnNumber, 8)
1540+
} else {
1541+
XCTAssertEqual(error.message, "Unable to find module dependency: 'FooBar'")
1542+
}
1543+
let noteTest = diags[1]
1544+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1545+
XCTAssertEqual(noteTest.message,
1546+
"""
1547+
a dependency of main module 'testDependencyScanning'
1548+
import FooBar
1549+
^
1550+
"""
1551+
)
1552+
} else {
1553+
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1554+
}
14661555
XCTAssertEqual(noteTest.severity, .note)
14671556
}
14681557
}
@@ -1712,8 +1801,31 @@ final class ExplicitModuleBuildTests: XCTestCase {
17121801
for scanIndex in 0..<numFiles {
17131802
let diagnostics = scanDiagnostics[scanIndex]
17141803
XCTAssertEqual(diagnostics.count, 2)
1715-
XCTAssertEqual(diagnostics[0].message, "Unable to find module dependency: 'UnknownModule\(scanIndex)'")
1716-
XCTAssertEqual(diagnostics[1].message, "a dependency of main module 'testParallelDependencyScanningDiagnostics\(scanIndex)'")
1804+
// Diagnostic source locations came after per-scan diagnostics, only meaningful
1805+
// on this test code-path
1806+
if try dependencyOracle.supportsDiagnosticSourceLocations() {
1807+
let sourceLoc = try XCTUnwrap(diagnostics[0].sourceLocation)
1808+
XCTAssertEqual(sourceLoc.lineNumber, 1)
1809+
XCTAssertEqual(sourceLoc.columnNumber, 8)
1810+
XCTAssertEqual(diagnostics[0].message,
1811+
"""
1812+
Unable to find module dependency: 'UnknownModule\(scanIndex)'
1813+
import UnknownModule\(scanIndex);
1814+
^
1815+
""")
1816+
let noteSourceLoc = try XCTUnwrap(diagnostics[1].sourceLocation)
1817+
XCTAssertEqual(noteSourceLoc.lineNumber, 1)
1818+
XCTAssertEqual(noteSourceLoc.columnNumber, 8)
1819+
XCTAssertEqual(diagnostics[1].message,
1820+
"""
1821+
a dependency of main module 'testParallelDependencyScanningDiagnostics\(scanIndex)'
1822+
import UnknownModule\(scanIndex);
1823+
^
1824+
""")
1825+
} else {
1826+
XCTAssertEqual(diagnostics[0].message, "Unable to find module dependency: 'UnknownModule\(scanIndex)'")
1827+
XCTAssertEqual(diagnostics[1].message, "a dependency of main module 'testParallelDependencyScanningDiagnostics\(scanIndex)'")
1828+
}
17171829
}
17181830
} else {
17191831
let globalDiagnostics = try dependencyOracle.getScannerDiagnostics()

0 commit comments

Comments
 (0)