Pitch: Warn on near-miss protocol witness when a default implementation is chosen

When a type declares a same-named property whose type is a refinement of a protocol requirement’s type, and the protocol provides a default implementation in an extension, Swift silently uses the default as the witness. The refined property is then ignored in generic/existential contexts. This follows current witness-matching rules, but the lack of a diagnostic makes it an easy footgun.

Minimal reproducer:

protocol LayoutDescriptor {}
struct ConcreteLayoutDescriptor: LayoutDescriptor { var value: Int }

protocol DefaultLayoutDescribing {
    var defaultLayoutDescriptor: LayoutDescriptor { get }
}

extension DefaultLayoutDescribing {
    var defaultLayoutDescriptor: LayoutDescriptor { ConcreteLayoutDescriptor(value: 0) }
}

struct MyPresenter: DefaultLayoutDescribing {
    let defaultLayoutDescriptor = ConcreteLayoutDescriptor(value: 42)
}

let presenter: any DefaultLayoutDescribing = MyPresenter()
print("Direct:", MyPresenter().defaultLayoutDescriptor) // 42
print("Existential:", presenter.defaultLayoutDescriptor) // 0

SwiftFiddle link.

The second print uses the extension default (value: 0), not the concrete declaration (value: 42). This compiles without any warning.

Why this is a problem:

  • The developer clearly intends MyPresenter.defaultLayoutDescriptor to satisfy the protocol requirement. The name matches exactly, and ConcreteLayoutDescriptor conforms to
    LayoutDescriptor.
  • Direct access works as expected, masking the issue in some testing scenarios.
  • Without the extension default, the compiler correctly errors with "candidate has non-matching type" — so it knows these types don't match. But when a default implementation exists, it
    silently falls back to it instead of warning.

Proposal:

Emit a “near-miss” warning when a member has the same name as a protocol requirement, is not selected as the witness due to a type mismatch, and the mismatched type is a more specific (aka refined) type that could satisfy the requirement via an implicit upcast (e.g. ConcreteLayoutDescriptor vs any LayoutDescriptor). If a default implementation is used instead, warn that the default will be the witness.

Something like:

warning: property 'defaultLayoutDescriptor' with type 'ConcreteLayoutDescriptor' does not satisfy requirement with type 'LayoutDescriptor'; default implementation will be used

note: add explicit type annotation 'LayoutDescriptor' to satisfy the requirement

The compiler already has near-miss diagnostics for @objc protocol conformances. This would extend that infrastructure to cover same-name type mismatches when a
default implementation silently takes over.

Prior discussion: This was reported as SR-10695 in 2019, where Jordan Rose suggested near-miss checking for this case. The issue was closed without the diagnostic being implemented.

Real-world impact: I found some instances of this pattern in our production iOS codebase, all silently using the wrong implementation.

I'd be happy to put together a PR to add a diagnostic for this if the direction seems right.

Quite sure this has been discussed on these forums before—worth checking.

You get the near-miss checking that you want if you declare the protocol conformance in an extension and implement the requirements inside that extension. I don't remember if that near-miss checking was implemented before or after 2019. The wrinkle here is, of course, that you can't implement stored properties in extensions.

For the same reason, though, there wouldn't be a great way to silence a near-miss warning for stored properties if you did want the actual behavior of the code as written. Either (1) the warning only checks for near-misses inside primary declarations if the conformance is also declared there, in which case users would have to silence the warning by moving the conformance to an extension, losing near-miss checking for all other stored properties; or (2) the warning checks for near-misses inside primary declarations no matter where the conformance is declared, in which case it would be impossible to silence the near-miss warning.

Anyway, long story short, I think the answer is resurrecting a proposal to allow same-file extensions to declare stored properties rather than trying to work out new variations on the diagnostics we have.

If we really wanted to, I think extending the near-miss diagnostics to scenario (1) above is probably fine, but I'd want to see someone work out what that means in terms of all the special-cased rules for protocols whose conformances have to be declared on the primary declaration—specifically, whether there are any issues with composing the rules that would result in unsilenceable warnings.

2 Likes

Previously we've discussed having something like override for "this declaration is intended to witness a protocol requirement; error if it does not". I can't remember why it never happens though…

1 Like

Plenty in the forums about that, but probably well out of scope for a diagnostics QoI discussion :)