On ObjectiveC supported platform, A class type T will treat T.self as AnyObject implicitly. And on Linux, we need to spell it out via as AnyObject explicly.
For some value passing methods, passing X.self to the ObjectIdentifier.init will lead to different results.
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
print("RawValue: \(test1(X.self as AnyObject))")
}
test2()
test2()
// Result will be x and y (different value/unexpected)
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
let a = X.self as AnyObject
print("RawValue: \(test1(a))")
}
test2()
test2()
// Result will be x and x (same value/expected)
Ouch.
I made a function that generates the source for "let swiftVersion: String" variable:
generator
func generateSwiftVers(minMajor: Int = 1, maxMajor: Int = 20, minMinor: Int = 0, maxMinor: Int = 20, minBug: Int = 0, maxBug: Int = 20, distantMajor: String = "X", distantMinor: String = "X", distantBug: String = "X") {
for major in minMajor ... maxMajor {
if major == minMajor {
print("#if swift(<\(major + 1))")
} else {
print("#elseif swift(<\(major + 1))")
}
for minor in minMinor ... maxMinor {
if minor == minMinor {
print(" #if swift(<\(major).\(minor + 1))")
} else {
print(" #elseif swift(<\(major).\(minor + 1))")
}
for bug in minBug ... maxBug {
if bug == minBug {
print(" #if swift(<\(major).\(minor).\(bug + 1))")
} else {
print(" #elseif swift(<\(major).\(minor).\(bug + 1))")
}
if bug == 0 {
print(" let swiftVersion = \"\(major).\(minor)\"")
} else {
print(" let swiftVersion = \"\(major).\(minor).\(bug)\"")
}
}
print(" #else")
print(" let swiftVersion = \"\(major).\(minor).\(distantBug)\"")
print(" #endif")
}
print(" #else")
print(" let swiftVersion = \"\(major).\(distantMinor)\"")
print(" #endif")
}
print("#else")
print(" let swiftVersion = \"\(distantMajor)\"")
print("#endif")
}
If you run it without parameters it'll generate a 19K line file that will correctly determine swift version in the range 1.0.0 ... 20.20.20. (20 is chosen as default maximum value for major / minor / bugFix version numbers). The good thing this source doesn't contribute to the binary size at all.
Here's an example output for a narrow range of versions (to keep this post small):
#if swift(<6)
#if swift(<5.8)
#if swift(<5.7.1)
let swiftVersion = "5.7"
#elseif swift(<5.7.2)
let swiftVersion = "5.7.1"
#elseif swift(<5.7.3)
let swiftVersion = "5.7.2"
#elseif swift(<5.7.4)
let swiftVersion = "5.7.3"
#elseif swift(<5.7.5)
let swiftVersion = "5.7.4"
#elseif swift(<5.7.6)
let swiftVersion = "5.7.5"
#elseif swift(<5.7.7)
let swiftVersion = "5.7.6"
#elseif swift(<5.7.8)
let swiftVersion = "5.7.7"
#elseif swift(<5.7.9)
let swiftVersion = "5.7.8"
#elseif swift(<5.7.10)
let swiftVersion = "5.7.9"
#else
let swiftVersion = "5.7.X"
#endif
#elseif swift(<5.9)
#if swift(<5.8.1)
let swiftVersion = "5.8"
#elseif swift(<5.8.2)
let swiftVersion = "5.8.1"
#elseif swift(<5.8.3)
let swiftVersion = "5.8.2"
#elseif swift(<5.8.4)
let swiftVersion = "5.8.3"
#elseif swift(<5.8.5)
let swiftVersion = "5.8.4"
#elseif swift(<5.8.6)
let swiftVersion = "5.8.5"
#elseif swift(<5.8.7)
let swiftVersion = "5.8.6"
#elseif swift(<5.8.8)
let swiftVersion = "5.8.7"
#elseif swift(<5.8.9)
let swiftVersion = "5.8.8"
#elseif swift(<5.8.10)
let swiftVersion = "5.8.9"
#else
let swiftVersion = "5.8.X"
#endif
#elseif swift(<5.10)
#if swift(<5.9.1)
let swiftVersion = "5.9"
#elseif swift(<5.9.2)
let swiftVersion = "5.9.1"
#elseif swift(<5.9.3)
let swiftVersion = "5.9.2"
#elseif swift(<5.9.4)
let swiftVersion = "5.9.3"
#elseif swift(<5.9.5)
let swiftVersion = "5.9.4"
#elseif swift(<5.9.6)
let swiftVersion = "5.9.5"
#elseif swift(<5.9.7)
let swiftVersion = "5.9.6"
#elseif swift(<5.9.8)
let swiftVersion = "5.9.7"
#elseif swift(<5.9.9)
let swiftVersion = "5.9.8"
#elseif swift(<5.9.10)
let swiftVersion = "5.9.9"
#else
let swiftVersion = "5.9.X"
#endif
#else
let swiftVersion = "5.X"
#endif
#else
let swiftVersion = "X"
#endif
I ran the full 19K line detector on godbolt, the detected version is "5.9" when "5.9" is selected in the menu. For "nightly" the detected version is "5.11".
Let's end the discussion of the Swift version and go back to the original problem. What is your Swift version on your Linux test machine? Is it convenient to install Swift 5.9.2 for testing? If the result is still the same on 5.9.2, then the diffs will left to be arch.
I don't have a linux machine, I ran the app on godbolt which happened to be a linux machine (see the details above) and now thanks to my version detector we know the swift version, which is 5.9 (or 5.11 for "nightly").
Class metatype values are only usable as objects themselves with Objective-C interop. On Linux, class metatypes are not objects, and as AnyObject just wraps the value in a generic object wrapper.
Ok, it's neither. I changed your original example and now can see the issue your are talking about on godbolt's 5.9 and 5.11:
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
let a = X.self as AnyObject
let b = X.self as AnyObject
let c = X.self as AnyObject
print("RawValue: \(test1(a))")
print("RawValue: \(test1(b))")
print("RawValue: \(test1(c))")
}
test2()
It "worked" before because the reference object was allocated (by luck) at the same address as the previously deallocated object.
This does not explain the following output here on ubuntu-2204 aarch64 Swift 5.9.2
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
print("RawValue: \(test1(X.self as AnyObject))")
}
test2()
test2()
test2()
test2()
// Result will be x y y y
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
let a = X.self as AnyObject
print("RawValue: \(test1(a))")
}
test2()
test2()
test2()
test2()
// Result will be x x x x
import Foundation
class X {}
func test1(_ obj: AnyObject) -> UInt64 {
UInt64(UInt(bitPattern: ObjectIdentifier(obj)))
}
func test2() {
let a = X.self
print("RawValue: \(test1(a as AnyObject))")
}
test2()
test2()
test2()
test2()
// Result will be x y y y
It should either be x x x x for all test example or x y z w for all test example.
But the result is some ways of passing value will result a different result for the first call. And that's what confuses me.
Also, do we have a plan to fix/unify it? cc @Joe_Groff
Someone also reported that "That would be a step towards resolving a number of other related problems with metatypes on Linux."