Strange generics behavior

In this code snippet:

protocol GDecodable {
	/* ... */
	
	static var replacementType : GDecodable.Type { get }
}

extension GDecodable {
	static var replacementType : GDecodable.Type { Self.self }
}

class Example<T:GDecodable> : GDecodable {
	class var replacementType: GDecodable.Type {
		func buildSelf<T>( _ typeT:T.Type ) -> GDecodable.Type
		where T:GDecodable { Example<T>.self }
		
		//	return buildSelf( T.replacementType )   // why not?
		let typeT = T.replacementType
		return buildSelf( typeT )   // ok
	}
}

I really don't understand why I can write:

		let typeT = T.replacementType
		return buildSelf( typeT )

but if instead I write:

		return buildSelf( T.replacementType )

the compiler reports the following errors:

Error: Generic parameter 'T' could not be inferred
Error: Cannot convert value of type 'any GDecodable.Type' to expected argument type 'T.Type'

What am I missing?

1 Like

Things get weird with existentials during these transitional times, even more so with metatypes.

Regardless that it should work better, what you've got there looks unwieldy. Why are you not using an associated type instead of a property?

protocol GDecodable {
  associatedtype Replacement: GDecodable = Self
}

class Example<T: GDecodable>: GDecodable {
  typealias Replacement = T
}
1 Like

I don't think I can because replacementType returns a type that is only known at runtime. Basically it's to handle the ability to change the name of a class without losing the ability to read archives that used the old name in a library that intends to provide NSKeyedArchiver/Unarchiver functionality to swift.

You can understand what I'm trying to do by reading the last chapter (Obsolete reference types) of my library's UserGuide, here [Preliminary GraphCodable UserGuide].(GraphCodable/UserGuide.md at DirectWrite6 · antofic/GraphCodable · GitHub)

It's still a "work in progress" and my English isn't very good. :frowning:

I think this is a bug of implicitly open existential. You should probably create issue on Issues · apple/swift · GitHub.

In my opinion, a more elegant workaround is this approach:

protocol GDecodable {
  static var replacementType: any GDecodable.Type { get }
}

extension GDecodable {
  static var replacementType: any GDecodable.Type { Self.self }
}

class Example<T: GDecodable>: GDecodable {
  class var replacementType: any GDecodable.Type {
    func buildSelf<U: GDecodable>(with _: U.Type) -> any GDecodable.Type {
      return Example<U>.self
    }
    return buildSelf(with: (T.replacementType as any GDecodable.Type))
  }
}
1 Like

Your version also compiles without employing any:

protocol GDecodable {
	static var replacementType : GDecodable.Type { get }
}

extension GDecodable {
	static var replacementType : GDecodable.Type { Self.self }
}

class Example<T:GDecodable> : GDecodable {
	class var replacementType: GDecodable.Type {
		func buildSelf<T>( _ typeT:T.Type ) -> GDecodable.Type
		where T:GDecodable { Example<T>.self }
		
		return buildSelf( (T.replacementType as GDecodable.Type) )
	}
}

And now it's even less clear why that (T.replacementType as GDecodable.Type) is needed when T.replacementType already returns GDecodable.Type.
I'd say it's definitely a compiler bug.

You need to get used to writing "any" now, because Swift 6 will require it.

2 Likes

I know, but there will be quite a few locations where I have to do this and without even a compiler warning I'm never sure I'll find them all. Is there a way to activate such an option now?
Otherwise I will update the code when swift 6 is available. In the end it's just a syntax change.

No, there's no flag to require any everywhere. The places where any is required now are newer features that will only support any rather than having to support both forms only to remove the non-any form with Swift 6.

1 Like