Still can't derive from a generic class

Hi

I would have hoped by now that it should be possible to do :

class BaseObject<rootType : AnyObject>
{
}

class Test : BaseObject<Test>
{
}

All compiles well but, at runtime, when calling let test = Test(), I get a "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" error.

This is something I have been able to do in C# for many a year. Why oh why can I still not do it in Swift ?

Joanna

···

--
Joanna Carter
Carter Consulting

This works fine for me in a playground in the latest Xcode 9 beta:

class Test { }

class Base<T: AnyObject> { }

class Sub: Base<Test> { }

let sub = Sub()

Jon

···

On Aug 29, 2017, at 1:04 PM, Joanna Carter via swift-users <swift-users@swift.org> wrote:

Hi

I would have hoped by now that it should be possible to do :

class BaseObject<rootType : AnyObject>
{
}

class Test : BaseObject<Test>
{
}

All compiles well but, at runtime, when calling let test = Test(), I get a "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" error.

This is something I have been able to do in C# for many a year. Why oh why can I still not do it in Swift ?

Joanna

--
Joanna Carter
Carter Consulting

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Hi Jon

···

Le 29 août 2017 à 19:20, Jon Shier <jon@jonshier.com> a écrit :

This works fine for me in a playground in the latest Xcode 9 beta:

class Test { }

class Base<T: AnyObject> { }

class Sub: Base<Test> { }

let sub = Sub()

That may well work but what I want is to be able to do is simpler than that :

class Base<T : AnyObject> { }

class Test : Base<Test> { }

let test = Test()

Joanna

--
Joanna Carter
Carter Consulting

Just curious, and because I have a distinct lack of imagination: can you share a concrete case of this pattern?

Thanks!

-Kenny

···

On Aug 29, 2017, at 10:04 AM, Joanna Carter via swift-users <swift-users@swift.org> wrote:

Hi

I would have hoped by now that it should be possible to do :

class BaseObject<rootType : AnyObject>
{
}

class Test : BaseObject<Test>
{
}

All compiles well but, at runtime, when calling let test = Test(), I get a "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" error.

This is something I have been able to do in C# for many a year. Why oh why can I still not do it in Swift ?

Joanna

--
Joanna Carter
Carter Consulting

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Just curious, and because I have a distinct lack of imagination: can you share a concrete case of this pattern?

Thanks!

-Kenny

···

On Aug 29, 2017, at 10:04 AM, Joanna Carter via swift-users <swift-users@swift.org> wrote:

Hi

I would have hoped by now that it should be possible to do :

class BaseObject<rootType : AnyObject>
{
}

class Test : BaseObject<Test>
{
}

All compiles well but, at runtime, when calling let test = Test(), I get a "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" error.

This is something I have been able to do in C# for many a year. Why oh why can I still not do it in Swift ?

Joanna

--
Joanna Carter
Carter Consulting

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Ah, I see. Making a subclass that inherits from a superclass generic to the subclass doesn’t seem like it should be a thing, but I’m not a type theorist. In any case, this is a bug (unexpected runtime crash and all) and you should file it.

Jon

···

On Aug 29, 2017, at 1:25 PM, Joanna Carter <joanna@carterconsulting.org.uk> wrote:

Hi Jon

Le 29 août 2017 à 19:20, Jon Shier <jon@jonshier.com> a écrit :

This works fine for me in a playground in the latest Xcode 9 beta:

class Test { }

class Base<T: AnyObject> { }

class Sub: Base<Test> { }

let sub = Sub()

That may well work but what I want is to be able to do is simpler than that :

class Base<T : AnyObject> { }

class Test : Base<Test> { }

let test = Test()

Joanna

--
Joanna Carter
Carter Consulting

Hi Kenny

Just curious, and because I have a distinct lack of imagination: can you share a concrete case of this pattern?

class BaseObject<rootType>
{
  private let properties: [PartialKeyPath<rootType> : AnyProperty]
  
  init(properties: [PartialKeyPath<rootType> : AnyProperty])
  {
    self.properties = properties
  }
  
  func value<valueType>(for keyPath: KeyPath<rootType, valueType>) -> valueType?
  {
    guard let property = properties[keyPath] else
    {
      return nil
    }
    
    return property.getValue()
  }
  
  func set<valueType>(value: valueType?, for keyPath: KeyPath<rootType, valueType>)
  {
    guard let property = properties[keyPath] else
    {
      return
    }
    
    property.set(value: value)
  }
}

class Test : BaseObject<Test>
{
  var name: String?
  {
    get
    {
      return value(for: \.name)!
    }
    set
    {
      set(value: newValue, for: \.name)
    }
  }
  
  var number: Int?
  {
    get
    {
      return value(for: \.number)!
    }
    set
    {
      set(value: newValue, for: \.number)
    }
  }
  
  init()
  {
    super.init(properties: [\Test.name : Property<String>(), \Test.number : Property<Int>()])
  }
}

Without the generic rootType parameter, all the getter and setter methods need an explicit mention of the derived class for the keypath :

class Test : BaseObject
{
  var name: String?
  {
    get
    {
      return value(for: \Test.name)!
    }
    set
    {
      set(value: newValue, for: \Test.name)
    }
  }
  
  …
}

It was all so simple in C# :(

Joanna

···

--
Joanna Carter
Carter Consulting

Hi Kenny

Just curious, and because I have a distinct lack of imagination: can you share a concrete case of this pattern?

This is likely due to the runtime not handling cycles properly in type metadata initialization—a subclass T needs its base class metadata for BaseObject<T>, which in turn needs the metadata for T to instantiate the generic type. This is something we plan to fix next year since it requires runtime ABI changes. For the particular pattern you've described:

class BaseObject<rootType>
{
private let properties: [PartialKeyPath<rootType> : AnyProperty]

init(properties: [PartialKeyPath<rootType> : AnyProperty])
{
   self.properties = properties
}

func value<valueType>(for keyPath: KeyPath<rootType, valueType>) -> valueType?
{
   guard let property = properties[keyPath] else
   {
     return nil
   }

   return property.getValue()
}

func set<valueType>(value: valueType?, for keyPath: KeyPath<rootType, valueType>)
{
   guard let property = properties[keyPath] else
   {
     return
   }

   property.set(value: value)
}
}

class Test : BaseObject<Test>
{
var name: String?
{
   get
   {
     return value(for: \.name)!
   }
   set
   {
     set(value: newValue, for: \.name)
   }
}

var number: Int?
{
   get
   {
     return value(for: \.number)!
   }
   set
   {
     set(value: newValue, for: \.number)
   }
}

init()
{
   super.init(properties: [\Test.name : Property<String>(), \Test.number : Property<Int>()])
}
}

Without the generic rootType parameter, all the getter and setter methods need an explicit mention of the derived class for the keypath :

class Test : BaseObject
{
var name: String?
{
   get
   {
     return value(for: \Test.name)!
   }
   set
   {
     set(value: newValue, for: \Test.name)
   }
}


}

Would a protocol-based approach suffice? `Self` in a protocol extension would give you access to the concrete type in a similar way:

protocol Base: AnyObject {
  var properties: [PartialKeyPath<Self> : AnyProperty] { get }
}

extension Base {
  func value<T>(for keyPath: KeyPath<Self, T>) -> T?
  {
    guard let property = properties[keyPath] else
    {
      return nil
    }

    return property.getValue()
  }

  /*etc.*/
}

class Test: Base {
  let properties = [\Test.name: Property<String>()]

  var name: String {
    get { return value(for: \.name) }
    set { set(value: newValue, for: \.name) }
  }
}

-Joe

···

On Aug 30, 2017, at 11:17 AM, Joanna Carter via swift-users <swift-users@swift.org> wrote:

Hi Joe

Thanks for your input on this.

This is likely due to the runtime not handling cycles properly in type metadata initialization—a subclass T needs its base class metadata for BaseObject<T>, which in turn needs the metadata for T to instantiate the generic type. This is something we plan to fix next year since it requires runtime ABI changes.

Hmmm. I'm finding this really frustrating, waiting for features that I have been using for years, in another popular language (C#), to see the light of day.

Maybe application programmers are happy with Swift so far but, when it comes to my speciality of designing frameworks, I am constantly finding myself blocked by lack of metadata APIs :-(

I am talking about a couple of frameworks that underpin one of the largest business management systems in Europe, for which the client is asking a MacOS/iOS version.

Would a protocol-based approach suffice? `Self` in a protocol extension would give you access to the concrete type in a similar way:

protocol Base: AnyObject {
var properties: [PartialKeyPath<Self> : AnyProperty] { get }
}

extension Base {
func value<T>(for keyPath: KeyPath<Self, T>) -> T?
{
   guard let property = properties[keyPath] else
   {
     return nil
   }

   return property.getValue()
}

/*etc.*/
}

class Test: Base {
let properties = [\Test.name: Property<String>()]

var name: String {
   get { return value(for: \.name) }
   set { set(value: newValue, for: \.name) }
}
}

At first glance, I thought, Yes! But then I realised that this then requires a final class to cater for the use of Self in the protocol; something that would be a blocker for the class hierarchies that most users of the framework would want to create on top of the Base functionality.

I suppose I could look at using protocol-oriented composition but trying to avoid associated types in protocols in frameworks that use generics extensively is akin to hitting yourself repeatedly on the head with a hammer ;-)

And I really don't want to have to keep on repeating type-erasure boilerplate code to achieve that.

I think the realisation that I may well lose the chance of a pretty massive contract and that my client will lose an important business opportunity is finally dawning :-(

Joanna

···

--
Joanna Carter
Carter Consulting