Skip to content

Swift: computed-property reads create caller edges#1176

Open
nftmago wants to merge 1 commit into
colbymchenry:mainfrom
nftmago:pr-swift-property-callers
Open

Swift: computed-property reads create caller edges#1176
nftmago wants to merge 1 commit into
colbymchenry:mainfrom
nftmago:pr-swift-property-callers

Conversation

@nftmago

@nftmago nftmago commented Jul 5, 2026

Copy link
Copy Markdown

Problem

Reading a Swift computed property — enc.header.isWellFormed, Store.canPublishBundle — runs its getter, so it is behaviourally a call. But the paren-free syntax parsed as neither a call_expression (the call extractor) nor a capitalized Type.member value-read (the static-member pass). So a heavily-used gate property showed "No callers found", silently hiding real dependencies from callers / callees / impact / blast-radius. On a real Swift codebase these are exactly the security choke points (a crypto header's isWellFormed, an identity store's canPublishBundle).

Fix

  • New internal-only property_read reference kind (sibling of function_ref), emitted by the Swift body walker for every navigation_expression value-read (instance and static receivers). Resolution maps it to a calls edge.
  • matchPropertyRead: strict, modelled on matchFunctionRef — binds to computed-property targets only, same language family, same-file then a unique same-directory match, else drop. Swift stored properties extract as field/constant/variable (never property) and stdlib members aren't in the graph, so arr.count, stored-field reads, Type.CONST, and enum values resolve to nothing and drop — no new noise. A wrong "call" edge is worse than none.
  • The same-directory tier handles symlinked/mirrored source trees (a SwiftPM test target re-exposing app files) that put a second same-named property in a distant dir.

Scoped to Swift end-to-end (extractor language check + sameLanguageFamily in the matcher), so no other language's resolution changes. The eval harness has no Swift coverage, so this is neither exercised nor regressed by it; validated via the unit suite (which has Swift resolution tests) plus a real codebase.

Tests

New resolution test: a computed-property gate lists both its readers as callers, while a same-shaped stored field of the same struct produces no bogus caller edge. Full suite green (2121 passed).

Reading a Swift computed property (`obj.isReady`, `Store.canPublish`) runs
its getter — behaviourally a call — but the paren-free syntax parsed as
neither a call_expression (the call extractor) nor a capitalized
Type.member value-read (the static-member pass). So a heavily-used gate
property showed "No callers found", silently hiding real dependencies from
callers / callees / impact / blast-radius. This is the frontier the docs
call out for a Swift codebase (the crypto gates isWellFormed /
canPublishBundle).

- New internal-only referenceKind `property_read`, emitted by the Swift
  body walker for every navigation_expression value-read (instance and
  static receivers). Resolution maps it to a `calls` edge.
- matchPropertyRead: strict, mirrors matchFunctionRef — binds to
  computed-`property` targets ONLY, same language family, same-file then a
  unique same-directory match, else drop. Swift stored properties extract
  as field/constant/variable (never `property`) and stdlib members aren't
  in the graph, so reads of `arr.count` / stored fields / Type.CONST /
  enum values resolve to nothing and drop — no new noise. A wrong "call"
  edge is worse than none.
- The same-directory tier handles symlinked/mirrored source trees (a test
  target re-exposing app files) that put a second same-named property in a
  distant dir.

Scoped to Swift end to end (extractor language check + sameLanguageFamily
in the matcher), so no other language's resolution changes.

Verified: full vitest suite green (2121 passed; the lone intermittent
mcp-daemon timeout is pre-existing flake, passes in isolation). New test
asserts a computed gate lists its readers while a same-shaped stored field
does not. On the Auris crypto codebase, `callers isWellFormed` now returns
receiveMessage and `callers canPublishBundle` returns registerSelf (both
were "No callers found").
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant