Skip to content

Swift: Foundation calls bind to same-named project members (unique_name, confidence 0.75) — two of three land on stored properties, so CALLS edges target :Variable nodes #1893

Description

@CaptainMittens

Version

dev build from source at 4da8f877 (tag v0.10.8 lineage). 19 commits behind origin/main; none of those 19 touch the Swift front end or the call-resolution passes, and my own local commits do not either. Should reproduce on stock main.

Platform

macOS (Apple Silicon) — macOS 26.6.2, arm64

Install channel

Built from source

Binary variant

standard

What happened, and what did you expect?

In a Swift project, calls into Foundation are bound to project-local members that merely share the last path component. The call graph then reports edges that do not exist.

Two of the three in the minimal repro below resolve onto stored properties — the graph contains a CALLS edge whose target is a :Variable node, which cannot be called at all.

This is not confined to a low-confidence tier. The minimal case produces all three at strategy=unique_name, confidence=0.75; in the real project the same failure also appears via suffix_match at 0.14, 0.21 and 0.55. So a confidence threshold alone would not catch it.

I expected these calls either to be left unresolved or to be marked as external, not bound to unrelated project symbols.

Same class as the already-recognised family, filed per-language the way this project already does: #476 Perl (closed, fixed), #606 PHP (open), #1276 Python (open), #1572 Python/TS cross-language via unique_name, #1355 same-named symbol binding. Swift is not yet reported.

Worth noting alongside #43 (Swift: CALLS edges not resolved), which is closed as completed. That fix is working — Swift now produces 12,017 CALLS edges in my real project. This report is the accuracy gap that arrived with them.

Related to my #1892 only in that both are Swift front-end gaps; the two are independent.

Reproduction

One Swift file. Three project types whose member names collide with Foundation members; three calls that are unambiguously Foundation's.

import Foundation

struct PickedFile {
    let data: Data          // a stored property, not callable
}

struct DateOnly {
    static func string(from date: Date) -> String { "" }
}

struct Point {
    let date: Date?         // a stored property, not callable
}

final class Caller {
    func fetch() async throws -> Data {
        let url = URL(string: "https://example.com/x")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }

    func readSetting() -> String? {
        return UserDefaults.standard.string(forKey: "someKey")
    }

    func tomorrow(from now: Date) -> Date? {
        return Calendar.current.date(byAdding: .day, value: 1, to: now)
    }
}

With a minimal Package.swift, git init, then index_repository mode=full.

MATCH (a)-[r:CALLS]->(b) RETURN r.callee, r.strategy, r.confidence, b.qualified_name, b.start_line:

callee strategy confidence resolved to target line correct?
URLSession.shared.data unique_name 0.75 Main.data 5 no — let data: Data
UserDefaults.standard.string unique_name 0.75 Main.DateOnly.string 9 no — string(from:) is unrelated
Calendar.current.date unique_name 0.75 Main.date 13 no — let date: Date?

And the structural invariant that is violated — MATCH (a)-[r:CALLS]->(b:Variable):

callee                        resolved_to    line   parent
URLSession.shared.data        Main.data      5      Main.PickedFile
Calendar.current.date         Main.date      13     Main.Point

Rejecting any CALLS edge whose target node is not callable would catch two of the three here without touching the naming heuristics.

On a real project

A 462-file / 64k-line Swift app. Every call into a system framework, with each resolution checked against the source by hand:

callee strategy conf resolved to correct?
URLSession.shared.data suffix_match 0.21 HomeboxUI.PickedFile.data no
UserDefaults.standard.string ×3 unique_name 0.75 AuthDTOs.HomeboxDateOnly.string(from:) no
JSONEncoder().encode suffix_match 0.14 MaintenanceDTOs.MaintenanceEntryCreate.encode(to:) no
Calendar.current.date ×2 suffix_match 0.55 StatisticsDTOs.date (a public let date: Date?) no
Calendar.utcGregorian.startOfDayUTC unique_name 0.75 AuthDTOs.Calendar.startOfDayUTC yes

Seven of eight wrong. The eighth is genuinely correct — the project really does declare extension Calendar { static let utcGregorian; func startOfDayUTC(for:) }, so I am not counting it against the resolver.

Likely root: Swift has no import or LSP tier

Resolution strategies actually used, same binary, two projects indexed minutes apart:

strategy Swift project (12,017 CALLS) Go + TS project (20,650 CALLS)
import_map 0 5,381
lsp_* (11 variants) 0 2,478
unique_name 5,300 4,933
suffix_match 3,827 3,348
field_type_hint 1,673 51
qualified_suffix 723 3
same_module 494 4,246

Swift uses 5 strategies; Go/TS uses 21. With no import map and no LSP tier, there is nothing that can say URLSession is external, so name matching is the only thing left. The Swift graph also carries no Package label at all, where the Go/TS project has 215 Package nodes with an external flag.

Scale, stated honestly

suffix_match confidence spread in the Swift project, all 22 distinct values: 1,151 edges at 0.55, the remaining 2,676 below 0.5 — 70% of suffix_match edges, 22% of all CALLS edges.

I am not claiming those 2,676 are all wrong. Sampling the 0.04 tier turned up vm.loadEntityListViewModel.load, which is plausibly correct despite the confidence. The demonstrable errors are the system-framework ones above. The confidence figures are here to show that a threshold is not the fix — the wrong edges sit at 0.75.

Project scale (if relevant)

Repro: 2 files. Real project: 462 Swift files / 64,101 lines, 3,766 Method nodes, 12,017 CALLS edges.

Metadata

Metadata

Assignees

No one assigned

    Labels

    parsing/qualityGraph extraction bugs, false positives, missing edges

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions