Jump-to-definition to `init` of a C struct does not work
When doing jump to definition on the timespec initializer in the following (jump-to-definition on tv_spec), it doesn’t jump anywhere, probably because the C struct doesn’t have an initializer.
import Darwin
func test() {
_ = timespec(tv_sec: 0, tv_nsec: 0)
}
rdar://126382075
I have come up with a partial solution to this bug,
- Check if the primary entry in the sourcekitd response from CursorInfoRequest has
key.kind: source.lang.swift.ref.function.constructor,key.decl_lang: source.lang.swiftbut neitherkey.filepathnorkey.groupname. If I get it correctly, this indicates a reference to a nil constructor of a C struct. - Get the
TokenSyntaxfrom the syntax tree at the position where CursorInfoRequest was called. Find the initializer parentFunctionCallExprSyntaxof thatTokenSyntaxand check the type ofFunctionCallExprSyntax.calledExpression. - If
calledExpressionisMemberAccessExprSyntax: to be implemented - If
calledExpressionisDeclReferenceExprSyntax, make a CursorInfoRequest on this token's position. - Check the value of
key.kindof the response dictionary. 5.1. Ifkey.kind: source.lang.swift.ref.typealias: get theTokenSyntaxfrom the syntax tree at the position ofcalledExpression, get its parentTypeAliasDeclSyntaxand make a CursorInfoRequest on the position ofTypeAliasDeclSyntax.initializer.value. Back to 5. 5.2. else: I just return the result here. In the future I should check ifkey.kindis only a reference but not the declaration itself and search further.
However I've encountered difficulties to get sourcekitd search beyond the local file. CursorInfoRequest on TS in main.swift returned an error response.
let project = try await SwiftPMTestProject(
files: [
"Sources/MySwiftLibrary/ta.swift":
"""
import Darwin
typealias TS = timespec
""",
"Sources/MySwiftLibrary/main.swift":
"""
var a = TS()
a = TS(2️⃣tv_sec: 0, tv_nsec: 0)
struct A {}
extension A {
init(a: Int) {}
}
String(cString: "123")
""",
],
manifest: """
let package = Package(
name: "MyLibrary",
targets: [
.target(name: "MyLibrary"),
.target(name: "MySwiftLibrary", dependencies: ["MyLibrary"])
]
)
""",
enableBackgroundIndexing: true
)
_ = try project.openDocument("ta.swift")
let (uri, positions) = try project.openDocument("main.swift")
This is probably more a bug on the sourcekitd side and we should fix there, rather than trying to workaround it here. What I expect to be happening is that sourcekitd should return a cursor info response with modulename + is_system + usr set and then that should be used to jump to the generated interface.
From a quick test that is the case, it's just that the initializer is giving Darwin as the module rather than Darwin.POSIX.sys.types._timespec:
{
key.kind: source.lang.swift.ref.struct,
key.name: "timespec",
key.usr: "c:@S@timespec",
key.typename: "timespec.Type",
key.modulename: "Darwin.POSIX.sys.types._timespec",
key.annotated_decl: "<Declaration>struct timespec</Declaration>",
key.is_system: true,
key.decl_lang: source.lang.objc,
...
key.secondary_symbols: [
{
key.kind: source.lang.swift.ref.function.constructor,
key.name: "init(tv_sec:tv_nsec:)",
key.usr: "s:So8timespecV6tv_sec0B5_nsecABSi_Sitcfc",
key.typename: "(timespec.Type) -> (Int, Int) -> timespec",
key.modulename: "Darwin",
...
key.is_system: true,
...
key.decl_lang: source.lang.swift,
}
],
...
}
@bnbarham thanks for your insight! I've spotted the same thing when looking into the source code, but I thought that was the correct behaviour of sourcekitd and it was our responsibility to make sense of it.
but I thought that was the correct behaviour of sourcekitd and it was our responsibility to make sense of it.
Heh, fair enough :) But no, it's definitely a bug on the sourcekitd side - I imagine we just don't handle the synthesized initializer correctly.