Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-11682] Inconsistent inheritance behavior for generic classes. #54091

Closed
swift-ci opened this issue Oct 30, 2019 · 5 comments
Closed

[SR-11682] Inconsistent inheritance behavior for generic classes. #54091

swift-ci opened this issue Oct 30, 2019 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-11682
Radar None
Original Reporter kuanfajardo (JIRA User)
Type Bug
Status Closed
Resolution Won't Do
Environment

Xcode 11.0

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug
Assignee None
Priority Medium

md5: e71dd1d9d60654033c34a9fb81f68e9e

relates to:

  • SR-5676 Can't downcast custom generics structs, only arrays

Issue Description:

There appears to be some sort of inconsistency in how subclasses of generic classes are treated with respect to their inheritance.

1. When using a generic class ("model") as a generic parameter for another class ("client"), the specific model is not treated as a a subclass of the generic model:

## Model.swift ##

// Generic model.
class ValueWrapper<StorageType> {}

// Specific model.
class StringValueWrapper: ValueWrapper<String> {}

## Client.swift ##

// Generic client.
class AbstractClient<T: ValueWrapper<Any>> {}

// Specific client.
class StringClient: AbstractClient<StringValueWrapper> {}  // ERROR: 'AbstractClient' requires that 'StringValueWrapper' inherit from 'ValueWrapper<Any>'

2. However, when conforming to a protocol, the specific model is treated as a subclass:

## Extensions.swift ##

extension ValueWrapper: Foo {
  func bar() { return }
}

extension StringValueWrapper: Foo {} // ERROR: Redundant conformance of 'StringValueWrapper' to protocol 'Foo'

Though this problem can be avoided by avoiding reference types altogether and using a protocol with an associated type, this is not possible in Objective-C.

As a result, it's not possible at all to use this generic client with pre-existing generic models written in Objective-C:

## ValueWrapper.h ##
@interface ValueWrapper<StorageType> : NSObject
@end

@interface StringValueWrapper: ValueWrapper<NSString>
@end

## Client.swift ## (Does not compile)
class AbstractClient<T: ValueWrapper<AnyObject>> {} 

class StringClient: AbstractClient<StringValueWrapper> {}  // ERROR: 'AbstractClient' requires that 'StringValueWrapper' inherit from 'ValueWrapper<AnyObject>'

## Client.h ## (Compiles)
@interface AbstractClient<WrapperType>: NSObject
@end

@interface StringClient: AbstractClient<StringValueWrapper>
@end

I'm not sure how much of this is intended behavior or not, but I do believe that this is inconsistent behavior.

@belkadan
Copy link
Contributor

This is correct behavior, not because of the subclass but because of the different generic parameter. Consider the following:

// Generic model.
class ValueWrapper<StorageType> {
  var storage: StorageType?
}

// Specific model.
class StringValueWrapper: ValueWrapper<String> {}

## Client.swift ##

// Generic client.
class AbstractClient<T: ValueWrapper<Any>> {
  func assignInt(into wrapper: T) {
    wrapper.storage = 3
  }
}

// Specific client.
class StringClient: AbstractClient<StringValueWrapper> {}  // Let's pretend this is allowed.

let wrapper = StringValueWrapper()
let client = StringClient() // okay, T = StringValueWrapper
client.assignInt(into: wrapper) // okay, T = StringValueWrapper
print(type(of: wrapper.storage!)) // oh no, an Int!

@belkadan
Copy link
Contributor

What would work is if AbstractClient's T was constrained to ValueWrapper<String>, or if you made it more generic:

class AbstractClient<Element, Wrapper: ValueWrapper<Element>>

But then you've got a different interface.

@swift-ci
Copy link
Collaborator Author

Comment by Juan Fajardo (JIRA)

Ok, I can get behind that. It seems then that the only way to use Objective-C generic models in generic Swift clients is to use that 2-parameter interface, correct?

## ValueWrapper.h ##
@interface ValueWrapper<StorageType> : NSObject
@end

@interface StringValueWrapper: ValueWrapper<NSString>
@end

## Client.swift ##

// Generic client.
class AbstractClient<Element, Wrapper: ValueWrapper<Element>> {}

// Specific client.
class StringClient: AbstractClient<String, StringValueWrapper> {}

@belkadan
Copy link
Contributor

If you want to have parallel hierarchies like this, yes. If you don't actually care about the wrapper type, you can just be generic over the Element on the Swift side and store a value of type ValueWrapper<Element> instead of Wrapper.

@swift-ci
Copy link
Collaborator Author

Comment by Juan Fajardo (JIRA)

Working as intended.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

2 participants