a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
189 lines
6.4 KiB
Go
189 lines
6.4 KiB
Go
package resolver
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
// swiftObjCBridgeVia marks a synthesized Swift↔ObjC bridge edge.
|
|
const swiftObjCBridgeVia = "swift.objc.bridge"
|
|
|
|
// swiftSelRef pairs a Swift declaration node with one Objective-C selector it
|
|
// is exposed under (a method's selector, or a property's getter / setter).
|
|
type swiftSelRef struct {
|
|
node *graph.Node
|
|
sel string
|
|
}
|
|
|
|
// ResolveSwiftObjCBridge is the framework-dispatch synthesizer for the
|
|
// Swift ↔ Objective-C bridge. The Objective-C extractor names each method
|
|
// node by its canonical selector (`moveFrom:to:`, `viewDidLoad`); the
|
|
// Swift extractor stamps the ObjC selector a method is exposed under
|
|
// (Meta["objc_selector"]) on every @objc method, derived from the
|
|
// argument labels or an explicit @objc(custom:) override. This pass joins
|
|
// the two: for each Swift @objc method whose selector matches an ObjC
|
|
// method node, it synthesizes a pair of EdgeReferences bridge edges (one
|
|
// each way) so navigation and find_usages span the language boundary —
|
|
// an ObjC selector call resolves to the Swift implementation, and a Swift
|
|
// method shows its ObjC-visible counterpart.
|
|
//
|
|
// Full recompute and idempotent: edges are re-derived from the selector
|
|
// metadata, graph.AddEdge dedupes by edge key, and graph.EvictFile drops
|
|
// the bridge in both directions when either side's file is reindexed.
|
|
// Edges ride at ast_inferred (selector-name matching is a heuristic, not
|
|
// a type-checked bind) and carry full synthesizer provenance.
|
|
//
|
|
// Returns the number of Swift methods bridged to at least one ObjC
|
|
// selector counterpart.
|
|
func ResolveSwiftObjCBridge(g graph.Store) int {
|
|
if g == nil {
|
|
return 0
|
|
}
|
|
|
|
objcBySelector := map[string][]*graph.Node{}
|
|
swiftByName := map[string][]*graph.Node{}
|
|
var swiftExact []swiftSelRef
|
|
// Protocol-conformance bridge inputs: Swift @objc protocols by name and
|
|
// the ObjC @interface nodes that adopt protocols.
|
|
swiftObjCProto := map[string]*graph.Node{}
|
|
var objcConformers []*graph.Node
|
|
for _, n := range nodesByKindsOrAll(g, graph.KindMethod, graph.KindFunction, graph.KindField, graph.KindInterface, graph.KindType) {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
switch n.Language {
|
|
case "objc":
|
|
if n.Kind == graph.KindMethod && n.Name != "" {
|
|
objcBySelector[n.Name] = append(objcBySelector[n.Name], n)
|
|
}
|
|
if n.Kind == graph.KindType && n.Meta != nil {
|
|
if protos, _ := n.Meta["objc_protocols"].(string); protos != "" {
|
|
objcConformers = append(objcConformers, n)
|
|
}
|
|
}
|
|
case "swift":
|
|
if n.Name != "" {
|
|
swiftByName[n.Name] = append(swiftByName[n.Name], n)
|
|
}
|
|
if n.Meta != nil {
|
|
// A method exposes one selector; an @objc property exposes a
|
|
// getter (objc_selector) and, when mutable, a setter.
|
|
if sel, _ := n.Meta["objc_selector"].(string); sel != "" {
|
|
swiftExact = append(swiftExact, swiftSelRef{n, sel})
|
|
}
|
|
if sel, _ := n.Meta["objc_setter_selector"].(string); sel != "" {
|
|
swiftExact = append(swiftExact, swiftSelRef{n, sel})
|
|
}
|
|
if n.Kind == graph.KindInterface {
|
|
if isObjC, _ := n.Meta["objc"].(bool); isObjC {
|
|
swiftObjCProto[n.Name] = n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
noSelectorBridge := len(objcBySelector) == 0 || len(swiftByName) == 0
|
|
noProtocolBridge := len(swiftObjCProto) == 0 || len(objcConformers) == 0
|
|
if noSelectorBridge && noProtocolBridge {
|
|
return 0
|
|
}
|
|
|
|
var batch []*graph.Edge
|
|
bridged := map[string]bool{}
|
|
link := func(sm, om *graph.Node, sel string) {
|
|
if sm.ID == om.ID {
|
|
return
|
|
}
|
|
batch = append(batch,
|
|
swiftObjCBridgeEdge(sm, om, sel),
|
|
swiftObjCBridgeEdge(om, sm, sel),
|
|
)
|
|
bridged[sm.ID] = true
|
|
}
|
|
|
|
// Exact pass: a Swift @objc method or property declares the selector(s) it
|
|
// is exposed under; bind each to the ObjC method node of the same selector.
|
|
for _, sx := range swiftExact {
|
|
for _, om := range objcBySelector[sx.sel] {
|
|
link(sx.node, om, sx.sel)
|
|
}
|
|
}
|
|
|
|
// Candidate pass: for Swift methods without explicit selector metadata,
|
|
// reverse the importer's naming rules — derive the Swift base names each
|
|
// ObjC selector could surface under and bind by name. graph.AddEdge dedupes,
|
|
// so a method matched by both passes counts once.
|
|
for sel, objcNodes := range objcBySelector {
|
|
for _, cand := range swiftObjCBaseNameCandidates(sel) {
|
|
for _, sm := range swiftByName[cand] {
|
|
for _, om := range objcNodes {
|
|
link(sm, om, sel)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Protocol-conformance pass: an ObjC @interface adopting a Swift @objc
|
|
// protocol gets a cross-language EdgeImplements to that protocol node.
|
|
for _, oc := range objcConformers {
|
|
protos, _ := oc.Meta["objc_protocols"].(string)
|
|
for _, pname := range strings.Split(protos, ",") {
|
|
pname = strings.TrimSpace(pname)
|
|
if pn := swiftObjCProto[pname]; pn != nil && pn.ID != oc.ID {
|
|
batch = append(batch, swiftObjCImplementsEdge(oc, pn))
|
|
bridged[oc.ID] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, e := range batch {
|
|
g.AddEdge(e)
|
|
}
|
|
return len(bridged)
|
|
}
|
|
|
|
// swiftObjCImplementsEdge builds the cross-language conformance edge from an
|
|
// ObjC class adopting a Swift @objc protocol to that protocol node.
|
|
func swiftObjCImplementsEdge(from, to *graph.Node) *graph.Edge {
|
|
return &graph.Edge{
|
|
From: from.ID,
|
|
To: to.ID,
|
|
Kind: graph.EdgeImplements,
|
|
FilePath: from.FilePath,
|
|
Line: from.StartLine,
|
|
Confidence: 0.6,
|
|
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeImplements, 0.6),
|
|
Origin: graph.OriginASTInferred,
|
|
Meta: map[string]any{
|
|
"via": swiftObjCBridgeVia,
|
|
MetaSynthesizedBy: SynthSwiftObjC,
|
|
MetaProvenance: ProvenanceHeuristic,
|
|
"bridge_from_lang": from.Language,
|
|
"bridge_to_lang": to.Language,
|
|
},
|
|
}
|
|
}
|
|
|
|
// swiftObjCBridgeEdge builds one direction of the cross-language bridge.
|
|
func swiftObjCBridgeEdge(from, to *graph.Node, selector string) *graph.Edge {
|
|
return &graph.Edge{
|
|
From: from.ID,
|
|
To: to.ID,
|
|
Kind: graph.EdgeReferences,
|
|
FilePath: from.FilePath,
|
|
Line: from.StartLine,
|
|
Confidence: 0.6,
|
|
ConfidenceLabel: graph.ConfidenceLabelFor(graph.EdgeReferences, 0.6),
|
|
Origin: graph.OriginASTInferred,
|
|
Meta: map[string]any{
|
|
"via": swiftObjCBridgeVia,
|
|
"objc_selector": selector,
|
|
MetaSynthesizedBy: SynthSwiftObjC,
|
|
MetaProvenance: ProvenanceHeuristic,
|
|
"bridge_from_lang": from.Language,
|
|
"bridge_to_lang": to.Language,
|
|
},
|
|
}
|
|
}
|