Skip to content

Don’t repeat a function in incomingCalls if it contains multiple calls to the same function #1166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Apr 4, 2024

Eg. if we have the following, and we get the call hierarchy of foo, we only want to show bar once, with multiple fromRanges instead of having two entries for bar in the call hierarchy.

func foo() {}
func bar() {
  foo()
  foo()
}

@ahoppen ahoppen requested a review from bnbarham April 4, 2024 18:02
@ahoppen ahoppen requested a review from benlangmuir as a code owner April 4, 2024 18:02
@ahoppen ahoppen force-pushed the dont-repeat-function-in-call-hierarchy branch from 5053fc7 to 4d80ff6 Compare April 4, 2024 18:02
@@ -2128,27 +2128,44 @@ extension SourceKitLSPServer {
callableUsrs += index.occurrences(ofUSR: data.usr, roles: .overrideOf).flatMap { occurrence in
occurrence.relations.filter { $0.roles.contains(.overrideOf) }.map(\.symbol.usr)
}
// callOccurrences are all the places that any of the USRs in callableUsrs is called.
// We also load the `calledBy` roles to get the method that contains the reference to this call.
Copy link
Contributor

@bnbarham bnbarham Apr 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of unrelated to this PR, but it'd be good to add a bunch of test cases for properties:

struct Foo {
  var member: Int = {
    return bar()
  }();

  func bar() {}
}

Call hierarchy for bar probably should give member there. member isn't really the caller, but... I think it's what people would expect to see in the UI. Though probably want to extend that to something accessing member, as I wouldn't necessarily say the call hierarchy should continue after member (or if it did, it should go to the init).

Also just a regular getter:

struct Foo {
  var member: Int {
    return bar()
  }

  func bar() {}
}

Plus explicit getter/setter/etc:

struct Foo {
  var member: Int {
    get {
      return bar()
    }
  }

  func bar() {}
}

Also CC @hamishknight as he's been looking at this a bit recently (both to comment on the above and also a review of this PR in general).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending that with a use of foo seems like a good thing to do as well.

for call in callOccurrences {
// Callers are all `calledBy` relations of a call to a USR in `callableUsrs`, ie. all the functions that contain a
// call to a USR in callableUSRs. In practice, this should always be a single item.
let callers = call.relations.filter { $0.symbol.kind.isCallable }.map(\.symbol)
Copy link
Contributor

@hamishknight hamishknight Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to check $0.roles here, with swiftlang/swift#72930 it ought to be sufficient to check for .containedBy, which would handle the case where the parent is a property (note that isCallable would be false in that case). Though for compatibility with older toolchains it may be better to check if the role contains .calledBy, and if that's empty, fallback to checking for .containedBy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion with checking $0.roles.

There is a subtle difference between calledBy and containedBy relations.

func foo() {}

var testVar: Int {
  get {
    let myVar = foo()
    return 2
  }
}

func test() {
  _ = testVar
}

The call to foo in let myVar = foo() is called by s:4test0A3VarSivg (demangled test.testVar.getter : Swift.Int, display name getter:testVar) while it is contained by s:4test0A3VarSivg02myB0L_ytvp (demangled myVar #1 : () in test.testVar.getter : Swift.Int / display name testVar). But s:4test0A3VarSivg02myB0L_ytvp doesn’t have any callers while s:4test0A3VarSivg does show the call inside test(). So I’ll go with just checking the calledBy relation. Or do you see any advantage of containedBy over calledBy?

Copy link
Contributor

@hamishknight hamishknight Apr 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while it is contained by s:4test0A3VarSivg02myB0L_ytvp

That should have been fixed by swiftlang/swift#72930, is it still an issue? The main advantage of containedBy is that it works for cases like:

struct Foo {
  var member: Int = {
    return bar()
  }()

  func bar() {}
}

Even though member isn't a callable symbol, it probably makes sense to consider it the caller (as we don't really have anything else to anchor it on). Generally, I think the right check is probably "called by, or container as a fallback"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right, sorry, you mentioned that. I was using an open source toolchain snapshot.

Let me try again once use have a toolchain snapshot with swiftlang/swift#72930 because I can’t be bothered to build a toolchain myself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we stop the traversal if we use the container instead? Does it make sense to continue from member there? Or should we have a calledBy to the init?

@ahoppen ahoppen changed the title Don’t repeat a function in incomingCalls if it contains multiple calls to the same function 🚥 #1165 Don’t repeat a function in incomingCalls if it contains multiple calls to the same function Apr 11, 2024
…lls to the same function

Eg. if we have the following, and we get the call hierarchy of `foo`, we only want to show `bar` once, with multiple `fromRanges` instead of having two entries for `bar` in the call hierarchy.

```swift
func foo() {}
func bar() {
  foo()
  foo()
}
```
@ahoppen ahoppen force-pushed the dont-repeat-function-in-call-hierarchy branch from 4d80ff6 to 1e3488b Compare April 11, 2024 21:35
@ahoppen
Copy link
Member Author

ahoppen commented Apr 11, 2024

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Apr 12, 2024

@swift-ci Please test Linux

@ahoppen ahoppen enabled auto-merge April 12, 2024 22:11
@ahoppen ahoppen merged commit 5a5b501 into swiftlang:main Apr 12, 2024
3 checks passed
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.

3 participants