ITADN

prefer_key_path autofix produces uncompilable code for closures nested inside a #require/#expect call

#6657OpenJoseph-Cursio 创建于 2026-05-21
J
Joseph-Cursiocommented
### New Issue Checklist - [x] Updated SwiftLint to the latest version (0.63.2) - [x] Searched for existing issues — closest is #5744 / #5765 (see below), which this does not fully cover ### Bug Description `prefer_key_path`'s autofix rewrites a property-access closure into a key path even when the call is nested inside a `#require`/`#expect` macro argument. The rewritten key-path form **fails to compile** inside those macros, so `swiftlint --fix` produces a non-building project. `#5765` ("Silence `prefer_key_path` rule on macro expansion expressions", fixing `#5744`) already handles closures that are a **direct** macro argument, e.g. `#Predicate { $0.isCompleted }`. But the silencing check only inspects the closure's **immediate parent** — in `PreferKeyPathRule.swift`, `isInvalid(...)` guards on `![.macroExpansionExpr, …].contains(parent.kind)`. A closure nested deeper — inside a function-call argument that is itself the macro's argument — has a `FunctionCallExprSyntax` parent, not the `MacroExpansionExprSyntax`, so it is still flagged and still auto-corrected. ### Reproduction ```swift import Testing struct Item { let flag: Bool } @Test func repro() throws { let items = [Item(flag: true)] let found = try #require(items.first(where: { $0.flag })) // flagged — but must not be rewritten #expect(found.flag) } ``` ``` $ swiftlint lint --only-rule prefer_key_path …:7:49: warning: Prefer Key Path Violation: Use a key path argument instead of a closure with property access (prefer_key_path) ``` `swiftlint --fix` rewrites it to `items.first(where: \.flag)`, after which `swift build` fails: ``` macro expansion #require:2:3: error: call can throw, but it is not marked with 'try' and the error is not handled note: call is to 'rethrows' function, but argument function can throw ``` (The key-path form is otherwise valid Swift — it compiles fine outside the macro. `#require`/`#expect` decompose the call via `__checkFunctionCall`, which passes the key path as an opaque closure parameter and loses its non-throwing guarantee. Reported separately at swiftlang/swift-testing#1723. Regardless of the swift-testing fix, `prefer_key_path` should not rewrite closures inside a macro expansion — same rationale as #5744.) ### Expected behavior `prefer_key_path` should not flag (or `--fix` rewrite) a closure that lies anywhere inside a macro-expansion argument — not only when the closure is the direct macro argument. The check could walk ancestors for a `MacroExpansionExprSyntax` / `MacroExpansionDeclSyntax` rather than inspecting only `parent.kind`. ### Environment * SwiftLint version: 0.63.2 * Xcode version: 26.4 * Swift: 6.2.4 * Installation method: installer * Configuration: ```yml only_rules: - prefer_key_path ```
1 条评论