runtimeanalyz.ing

← All capabilities

what_a_change_touches

CLI · MCP Pro

What a signature change affects. For each caller of a method, the argument classes it passed and the messages it sent to the return value, so you can see which callers a signature change affects before you make it.

Invoice safe
debit ( total Money )
Finance::Accounts changing
def debit(amount, memo:)
  Entry.create!(…)
end
Transfer changes
debit ( amount Integer )
pennies
FeeSchedule changes
debit ( fee Integer )
pennies
Refund changes
debit ( amount Money )
entry Finance::Entry
id
sent to the returned entry
Finance::Entry same object id
Each caller's argument classes come from the calls it made, and the returned entry is followed by its identity to the messages sent to it afterwards. The callers whose shape the change breaks are marked.

The problem

Finance::Accounts#debit takes an account and an amount and returns the ledger entry it made. You want to stop returning the entry, because most callers ignore it, and you suspect one caller passes the amount in pennies rather than as Money:

def debit(account, amount, memo:)
  raise InsufficientFunds, "#{account.balance} < #{amount}" if account.balance < amount

  account.balance = account.balance - amount
  account.save!
  entry = @ledger.record(account, Money.zero - amount, memo: memo)
  @audit.log(Reporting::Formatters::Currency.new(amount).to_ledger_s)
  entry
end

To know what each caller passes, and which callers use the return value and how, you have to read every one of them and follow each value by hand. The method has no declared types, so a type checker has nothing to check.

The fix

Ask before you change it:

$ ra what_a_change_touches Finance::Accounts#debit
Finance::Accounts#debit
· 91 calls from 7 callers
argument 1 · account
    Account  Billing::Checkout#complete     app/billing/billing/checkout.rb:12  63
    Account  FeeSchedule#charge             app/services/fee_schedule.rb:6      12
    Account  Refund#process                 app/services/refund.rb:9             7
    Account  Transfer#call                  app/services/transfer.rb:9           5
    Account  spec/finance/accounts_spec.rb  spec/finance/accounts_spec.rb:7      1
    Account  spec/finance/accounts_spec.rb  spec/finance/accounts_spec.rb:12     1
    Account  Invoice#settle                 app/models/invoice.rb:53             1
    Account  bin/traffic                    bin/traffic:44                       1
argument 2 · amount
    Money  Billing::Checkout#complete     app/billing/billing/checkout.rb:12  63
    Money  FeeSchedule#charge             app/services/fee_schedule.rb:6      12
    Money  Refund#process                 app/services/refund.rb:9             7
    Money  Transfer#call                  app/services/transfer.rb:9           5
    Money  spec/finance/accounts_spec.rb  spec/finance/accounts_spec.rb:7      1
    Money  spec/finance/accounts_spec.rb  spec/finance/accounts_spec.rb:12     1
    Money  Invoice#settle                 app/models/invoice.rb:53             1
    Money  bin/traffic                    bin/traffic:44                       1
return value · Finance::Entry
    receives #reference  Refund#process
    unused               Billing::Checkout#complete, spec/finance/accounts_spec.rb, FeeSchedule#charge, Invoice#settle, Transfer#call

Every caller passes Money, so the suspicion about pennies was wrong. The return change affects one caller, Refund#process, which reads #reference from the entry to decide whether the invoice is refunded:

entry = ledger.debit(@invoice.account, @invoice.total, memo: "refund #{@invoice.number}")
@invoice.update!(state: "refunded") if entry.reference

The other callers ignore the return value, so the change does not affect them. Give Refund a method whose name says it returns the entry, and let debit return nothing:

# app/services/refund.rb:9
entry = ledger.record_debit(@invoice.account, @invoice.total, memo: "refund #{@invoice.number}")

Where it shows up

  • Over MCP, your agent can ask before editing a signature and get the per-argument table and the return usage as structured blocks.
  • On the command line, run ra what_a_change_touches Klass#method.

How it works

  1. The callers are every recorded call edge into debit, with the method and line each one came from. A call from a spec body or a script has no calling method, so its file is listed as the caller.
  2. Each call records the class of every argument it passed, which is how each caller’s amount is known to be a Money. The parameter names come from the method’s source.
  3. Each call records the identity of the object it returned. Within the same call tree, any later call whose receiver is that same object is a message sent to the return value. Grouping those messages by caller shows how each caller uses what debit returns: Refund sends #reference and the others send nothing.

Run it on your app

The output on this page comes from our example Rails app, and it is regenerated whenever a tool changes. These are the commands behind it. On your own app, run ra record first, then put your own class, method or file in their place.

ra what_a_change_touches 'Finance::Accounts#debit'

The method is app/finance/finance/accounts.rb:14 and the one caller that reads the entry is app/services/refund.rb:9-10.

Reading the result

  • The list covers what ran. A caller that did not run in any recorded run is missing from it, along with the argument class it would have passed. For a method with as many callers as debit, search the source as well.
  • Use of a return value is read from the messages sent to it. A return value that is passed along untouched, or stored and read later in a different run, receives no messages in the tree that returned it, so the report shows it as unused.

Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.

what_calls

CLI · LSP · MCP free

The callers of a method. Every caller of a method in the recorded runs, with the file and line of each call and how often it was made, including calls made through public_send. what_it_calls lists the methods it calls.

See more →

observed_shape

CLI · LSP · MCP Pro

The interface a call depends on. The messages your code sends to a value, read from recorded runs. They are the interface a call depends on, whether or not anyone declared it.

See more →

what_it_receives

CLI · LSP · MCP free

What a method receives. For each argument of a method, the classes it was given in recorded calls and the messages the method sent to it.

See more →