Skip to content

Conversation

ahoppen
Copy link
Member

@ahoppen ahoppen commented Oct 19, 2025

The idea is pretty simple: When MemberImportVisibility is enabled, we know that imports can only affect the current source file. So, we can just try and remove every single import declaration in the file, check if a new error occurred and if not, we can safely remove it.

@ahoppen
Copy link
Member Author

ahoppen commented Oct 19, 2025

@swift-ci Please test

@ahoppen ahoppen force-pushed the remove-unused-imports branch from 58deab0 to b51468c Compare October 20, 2025 06:41
@ahoppen
Copy link
Member Author

ahoppen commented Oct 20, 2025

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Oct 20, 2025

@swift-ci Please test Windows

@ahoppen
Copy link
Member Author

ahoppen commented Oct 20, 2025

Sleeping over this, I think there is at least one case where this action could change behavior.

LibA.swift

func foo(_ x: Int) -> Int { "Wrong" }

LibB.swift

func foo(_ x: Double) -> Int { "Correct" }

Test.swift

import LibA
import LibB

print(foo(1.2))

The action will remove the import to LibB because the code still compiles fine without it (we now pick the foo(_:Int) overload instead of foo(_:Double)). But I think these cases are extremely rare, so I wouldn’t worry about them for now.

@tshortli
Copy link
Contributor

It's probably not too impactful to your goal, but MemberImportVisibility does not really guarantee that a source file only depends on its direct imports. Conformances are one example of something that can be used from a source file without a direct import. Re-exports also make the analysis hard in the general case since the same declarations may be reachable through multiple imports in the same file.

@tshortli
Copy link
Contributor

As a concrete example of the difficulty with conformances, an "unused" import in one source file could be the only import in the entire module that makes a necessary conformance visible in another source file. So removing that unused import would cause an error in another file.

@ahoppen
Copy link
Member Author

ahoppen commented Oct 21, 2025

Oh, thanks for mentioning the conformance issue. I only thoughts about members in extensions, not about the actual conformance that might be coming from a different file in the same module.

I’m on the edge now whether we should take this, knowing that it’s technically incorrect or if we say it’s good enough in most cases (which I suspect it is).

Copy link
Contributor

@bnbarham bnbarham left a comment

Choose a reason for hiding this comment

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

I’m on the edge now whether we should take this, knowing that it’s technically incorrect or if we say it’s good enough in most cases (which I suspect it is).

If this was a compiler fix-it then I'd definitely be in the "no" boat, but for a refactoring... it's probably still fine? Worst case the build has an error, which should result in the import being added to the correct file anyway.

@tshortli
Copy link
Contributor

How will the refactoring handle code like this?

import Foundation
import Dispatch

func test(_ queue: DispatchQueue) {
  queue.sync { }
}

Either import can be removed since Foundation is not used directly anywhere but re-exports Dispatch. This is what I mean about re-exports making the analysis difficult. I think ideally only the Dispatch import would be kept as the most specific import that is required by the code, but that can't really be determined outside of the compiler.

@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

The refactoring action just tries to remove imports in the reverse order, so in this case it would remove import Dispatch. If the imports would have been spelled the other way round, import Foundation would have been removed. Which seems good enough for me. Otherwise you could argue that this action would try and minimize imports and should thus also replace import Foundation with import Dispatch if only re-exported declarations are used. And I think that’s out-of-scope (at least for now).

@ahoppen ahoppen force-pushed the remove-unused-imports branch from b51468c to b9984d5 Compare October 22, 2025 06:28
@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

Also added a few comment lines on the known drawbacks of this refactoring action and why we decided to include it despite these.

@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

@swift-ci Please test Windows

1 similar comment
@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

@swift-ci Please test Windows

The idea is pretty simple: When `MemberImportVisibility` is enabled, we know that imports can only affect the current source file. So, we can just try and remove every single `import` declaration in the file, check if a new error occurred and if not, we can safely remove it.
@ahoppen ahoppen force-pushed the remove-unused-imports branch from b9984d5 to 4c95750 Compare October 22, 2025 21:56
@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

@swift-ci Please test

@ahoppen
Copy link
Member Author

ahoppen commented Oct 22, 2025

@swift-ci Please test Windows

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