-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Description
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
- Create a single file
type-resolution-repro.swiftwith 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)")- 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.