Disfavor the TestPredicate-returning == overloads - #86
Merged
Merged
Conversation
The public `==` operators returning `TestPredicate` (for `expect { a == b }`
diffs) leaked into app code: `let b = x == .someCase` inferred TestPredicate.
Mark both `@_disfavoredOverload` so untyped comparisons infer Bool.
To keep the builder's rich lhs/rhs failure messages, drop
`AssertBuilder.buildExpression(Bool)`: plain Bools reach the existing
disfavored `buildExpression(Bool?)` via value-to-optional, whose extra cost
keeps the tie between the two disfavored paths going to TestPredicate.
Without this, `expect { model.outcome == .x }` lost its diff.
Adds builder snapshots (Int, String, Optional, enum implicit member) pinning
the pre-change output, and TestPredicateOverloadTests (plain import) proving
Bool inference outside a builder.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Swift 6.3 (CI's Xcode 26.3) `expect { model.count == 99 }` now takes the
TestPredicate path and prints a diff (an improvement over the `count == 3`
baseline); on 6.4 it resolves to the concrete Int.== as before. Neither is a
regression, but they can't share one snapshot. Keep the optional and enum
implicit-member snapshots, which require the TestPredicate path and are
identical to baseline on both toolchains.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The two public
==operators inModelTester.swiftthat returnTestPredicateexist so thatexpect { a == b }failures can print both sides. Every file that imports SwiftModel can see them, though, and outside a builder they can win. A downstream app wrotelet immediate = self.outcome == .needsNewPlayerin non-test code, it inferredTestPredicate, and it needed an explicit: Boolto compile.Fix
==overloads are now@_disfavoredOverload, so an untyped comparison infersBool. No other operator overloads returnTestPredicate.buildExpression(Bool)was already disfavored, so each path carried one disfavored declaration, and the solver broke the tie toward the concrete synthesized==. As a result,expect { model.outcome == .needsNewPlayer }lost its− Outcome.needsNewPlayer / + Outcome.pendingdiff. The fix removesAssertBuilder.buildExpression(Bool). A plainBoolnow reaches the existing disfavoredbuildExpression(Bool?)through a value-to-optional conversion, and that extra cost keeps the tie going toTestPredicate. A comment at the overload explains why noBooloverload exists.Verification
builder … == …snapshots inOutputSnapshotTestscover the builder path with no type annotation. Before this PR, no snapshot covered it; the existing one usedlet pred: TestPredicate = …. The shapes are optionalInt? == Intand an enum compared against an implicit member. I recorded both onmainfirst, and the output with this PR is byte-identical on Swift 6.3.3 (Xcode 26) and 6.4 (Xcode 27). With only the operators disfavored, the enum snapshot fails.Int == literalandString == literalresolve differently per toolchain, so they have no snapshot. On 6.3 they now show the rich diff, wheremainprinted onlySimpleCounter.count == 3. On 6.4 they still resolve to the concreteInt.==, as onmain. Neither toolchain regresses. The first CI run failed only because I had pinned the 6.4 output for these two shapes.TestPredicateOverloadTestsuses a plainimport SwiftModel. It assignsx == yto an untypedletand passes the result to a function that only acceptsBool, covering enum implicit-member, optional, struct and generic comparisons. It also checkstype(of:) == Bool.self. The file fails to compile without the fix.swift build --build-testsis warning-free.scripts/testpasses: 721 main tests, 126 snapshot tests and the macro tests. The CounterFact, Onboarding, Search and TodoList examples build for testing. Standups fails inside the third-partyswiftui-navigationcheckout with the local toolchain, which is unrelated to this change.🤖 Generated with Claude Code