Skip to content

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

@Joseph-Cursio

Description

@Joseph-Cursio

New Issue Checklist

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

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:
only_rules:
  - prefer_key_path

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUnexpected and reproducible misbehavior.discussionTopics that cannot be categorized as bugs or enhancements yet. They require further discussions.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions