Skip to content

Overload resolution picks wrong initializer for some metatype arguments #85020

@clackary

Description

@clackary

Description

We noticed a regression (or a change in behavior at the very least) when using metatype arguments.

When two initializers differ only by (a) accepting Any.Type vs Any, and (b) sharing the same external label (of:), calls that pass metatype arguments like Int.self or Any.self sometimes resolve to the instance-taking overload instead of the metatype-taking overload. This produces unexpected stored types (Int.Type, Any.Protocol) and ends up breaking code that relies on equality checks that should otherwise succeed.

The included reproducer uses Any and Int examples, which do reproduce the bug. It also includes an example using String which does not reproduce the bug, so only happens with some types.

Reproduction

  1. Create a single file type-resolution-repro.swift with the following code...
import Foundation

struct Wrapper {
    struct Store: Equatable, CustomStringConvertible {
        let theType: Any.Type

        // Labeled initializers
        init(of theType: Any.Type) { self.theType = theType }
        init(of instance: Any)     { self.init(of: type(of: instance)) }

        // Unlabeled initializers
        init(_ theType: Any.Type)  { self.theType = theType }
        init(_ instance: Any)      { self.init(type(of: instance)) }

        static func == (lhs: Self, rhs: Self) -> Bool { lhs.theType == rhs.theType }

        var description: String { "Store(\(String(reflecting: theType)))" }
    }

    let store: Store

    init(_ theType: Any.Type) {
        self.store = .init(of: theType)
    }
}

func check<T: Equatable & CustomStringConvertible>(_ a: T, _ b: T, _ label: String) {
    print("\(label): \(a) == \(b)\(a == b ? "✅ true" : "❌ false")")
}

check(Wrapper(Any.self).store, .init(of: Any.self), "Any.self vs .init(of: Any.self) (labeled)")
check(Wrapper(Any.self).store, .init(Any.self),     "Any.self vs .init(Any.self) (unlabeled)")

check(Wrapper(Int.self).store, .init(of: Int.self), "Int.self vs .init(of: Int.self) (labeled)")
check(Wrapper(Int.self).store, .init(Int.self),     "Int.self vs .init(Int.self) (unlabeled)")

check(Wrapper(String.self).store, .init(of: "hello"), "String.self vs .init(of: \"hello\") (labeled)")
check(Wrapper(String.self).store, .init("hello"),     "String.self vs .init(\"hello\") (unlabeled)")
  1. Run with swift type-resolution-repro.swift

Expected behavior

Actual behavior against Swift 6.2...

Any.self vs .init(of: Any.self) (labeled): Store(Any) == Store(Any)  →  ✅ true
Any.self vs .init(Any.self) (unlabeled): Store(Any) == Store(Any)  →  ✅ true
Int.self vs .init(of: Int.self) (labeled): Store(Swift.Int) == Store(Swift.Int)  →  ✅ true
Int.self vs .init(Int.self) (unlabeled): Store(Swift.Int) == Store(Swift.Int)  →  ✅ true
String.self vs .init(of: "hello") (labeled): Store(Swift.String) == Store(Swift.String)  →  ✅ true
String.self vs .init("hello") (unlabeled): Store(Swift.String) == Store(Swift.String)  →  ✅ true

The expected behavior against main-snapshots is that it matches the 6.2 behavior.

The actual behavior against nightly main-snapshots...

Any.self vs .init(of: Any.self) (labeled): Store(Any) == Store(Any.Protocol)  →  ❌ false
Any.self vs .init(Any.self) (unlabeled): Store(Any) == Store(Any)  →  ✅ true
Int.self vs .init(of: Int.self) (labeled): Store(Swift.Int) == Store(Swift.Int.Type)  →  ❌ false
Int.self vs .init(Int.self) (unlabeled): Store(Swift.Int) == Store(Swift.Int)  →  ✅ true
String.self vs .init(of: "hello") (labeled): Store(Swift.String) == Store(Swift.String)  →  ✅ true
String.self vs .init("hello") (unlabeled): Store(Swift.String) == Store(Swift.String)  →  ✅ true

Environment

This reproduces on macOS and linux.

Additional information

This seems to be a regression that first landed in the main-snapshot-2025-08-16 nightly toolchain. The latest known-good toolchain was main-snapshot-2025-08-14.

Metadata

Metadata

Assignees

Labels

bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.overload resolutionArea → compiler → type checker: Overload resolution (ranking)type checkerArea → compiler: Semantic analysis

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions