Skip to content

Swift: static method calls (MemberAccessExpr) don't create caller→type edges + overloaded methods collapsed into single node #1533

Description

@JabberYQ

Bug 1: Static method calls via MemberAccessExpr do not create caller→type edges

Expected

ProductListViewController should have a direct edge to AlertManager because the code calls AlertManager.showConfirmAlert(for:from:) at line 149.

Actual

No edge exists between ProductListViewController and AlertManager. The only connection is a 4-hop path through method nodes:

ProductListViewController → .tableView() --calls [INFERRED]--> .showConfirmAlert() ← AlertManager

The graph has AlertManager --method--> .showConfirmAlert() (ownership direction), but no reverse edge recording that ProductListViewController calls AlertManager.

Source code

ProductListViewController.swift:149 — calls AlertManager directly:

extension ProductListViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let product = products[indexPath.row]
        AlertManager.showConfirmAlert(for: product, from: self)  // ← L149
    }
}

AlertManager.swift:11 — the static method being called:

enum AlertManager {
    static func showConfirmAlert(for product: Product, from viewController: UIViewController) {
        // ...
        let detailVC = ProductDetailViewController(product: product)
        viewController.navigationController?.pushViewController(detailVC, animated: true)
        // ...
    }
}

Graph evidence

ProductListViewController (13 edges) — no edge to AlertManager:

ProductListViewController
  --> UIViewController        [inherits]
  --> Product                 [references]
  --> .loadProducts()         [method]
  --> .tableView()            [method]
  --> UITableView             [references]
  --> UITableViewDataSource   [implements]
  --> UITableViewDelegate     [implements]
  (no AlertManager edge)

AlertManager (2 edges) — no incoming calls:

AlertManager
  --> .showConfirmAlert()     [method]
  <-- AlertManager.swift      [contains]
  (no incoming call edges from any caller)

Root cause

The Swift AST extractor handles MemberAccessExpr (AlertManager.showConfirmAlert) by creating the ownership direction (AlertManager --method--> .showConfirmAlert()), but does not create the caller direction (ProductListViewController --calls--> AlertManager).


Bug 2: Overloaded methods with identical base names are collapsed into a single graph node

Expected

Each overloaded method should produce a distinct graph node, or the node ID should include parameter type information for disambiguation (e.g., .init(product:) vs .init(coder:)).

Actual

Methods sharing the same base name are collapsed into one node. All edges from different overloads are merged together, making it impossible to tell which parameter/return type belongs to which method.

Source code — two init overloads in ProductDetailViewController

init(product:) at line 27:

init(product: Product) {
    self.product = product
    self.viewModel = ProductDetailViewModel(product: product)
    super.init(nibName: nil, bundle: nil)
}

init?(coder:) at line 33:

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

Source code — three tableView overloads in ProductListViewController

tableView(numberOfRowsInSection:) at line 110:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return products.count
}

tableView(cellForRowAt:) at line 114:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // ...
}

tableView(didSelectRowAt:) at line 145:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // ...
    AlertManager.showConfirmAlert(for: product, from: self)
}

Graph evidence — single .init() node with merged edges

One node demo_productdetailviewcontroller_productdetailviewcontroller_init (label: .init()) carries all edges from both inits:

.init()
  --> Product                 [parameter_type]  L27  ← from init(product:)
  --> ProductDetailViewModel  [call]            L29  ← from init(product:)
  --> NSCoder                 [parameter_type]  L33  ← from init?(coder:)

Graph evidence — single .tableView() node with merged edges

One node demo_productlistviewcontroller_productlistviewcontroller_tableview (label: .tableView()) carries edges from all three delegate methods:

.tableView()
  --> Int                    [return_type]     L110  ← numberOfRowsInSection
  --> UITableViewCell        [return_type]     L114  ← cellForRowAt
  --> UITableView            [parameter_type]  L145  ← didSelectRowAt
  --> IndexPath              [parameter_type]  L145  ← didSelectRowAt
  --> .showConfirmAlert()    [calls INFERRED]  L149  ← didSelectRowAt

Impact

For Swift codebases, this is particularly severe because UIKit's UITableViewDataSource and UITableViewDelegate protocols require multiple same-named tableView(...) methods, and NSCoding requires two init variants. The merged nodes lose method-level granularity entirely.

Suggested fix

Include parameter type information in the method node ID, e.g.:

  • xxx_init_product vs xxx_init_coder
  • xxx_tableview_numberofrows vs xxx_tableview_cellforrowat vs xxx_tableview_didselectrowat

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions