[Proposal] Remove behavior on AnyObject that allows any obj-c method to be called on it


(Kevin Lundberg) #1

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

···

--
Kevin Lundberg
kevin@klundberg.com


(Haravikk) #2

I’m pretty much a +1 by default to anything that avoids unexpected runtime errors =D

But yeah, I agree that Swift’s type inference shouldn’t result in types that we can’t trust, and that it’s better to force the developer to declare what they want/expect the type to be. Most occurrences of AnyObject are one of only a few possible types, and well-documented (otherwise you’d be unable to do anything with them), so while looking up the types you need to check for is an added step, I think it’s worth the extra safety, especially with so much useful code still written in Objective-C.

···

On 23 Mar 2016, at 01:59, Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

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


(Jordan Rose) #3

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

Jordan

···

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

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


(Joe Pamer) #4

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

I should be pushing a branch that does both of the above “real soon now”. (This branch also includes my recent experiments in inhibiting implicit bridging conversions.) I’ll be curious to know what people think once they’ve had a chance to play with these changes.

Thanks!
- Joe

···

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com> wrote:

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Kevin Lundberg) #5

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

I should be pushing a branch that does both of the above “real soon now”. (This branch also includes my recent experiments in inhibiting implicit bridging conversions.) I’ll be curious to know what people think once they’ve had a chance to play with these changes.

Thanks!
- Joe

Good to hear! Is this change going to make this work on linux/etc so that behavior is consistent on all platforms too?

If it turns out that it's not practical to remove this behavior outright, what if we move the behavior to a new type that has this expectation built in, perhaps `ObjcObject` or something similar? If the importer keeps every use of `id` from objective-c imported as AnyObject, then a developer could opt-in to this behavior by casting to ObjcObject explicitly:

for thing in things {
  (thing as ObjcObject).bar()
}

If we can't get this behavior to work on all platforms, exposing it through a dedicated ObjcObject type like this would help identify whether code that uses it is able to be used cross platform or not.

···

On Mar 23, 2016, at 3:45 PM, Joseph Pamer <jpamer@apple.com> wrote:

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Pamer) #6

Just an update on this… (Sorry about the delay!)

After experimenting with the changes outlined below, and discussing the matter with a few folks off-list, it seems like a better compromise would be to only adopt option #2. We would keep the direct member access syntax and wrap the results of any dynamic member access expressions in an Optional, as opposed to an IUO. In practice this feels like a nice compromise that will prevent many users from shooting themselves in the foot, as changing the wrapping to optional forces users to immediately account for failures without having to jump through too many hoops.

Thoughts?

Thanks!
- Joe

···

On Mar 23, 2016, at 12:45 PM, Joseph Pamer <jpamer@apple.com> wrote:

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

I should be pushing a branch that does both of the above “real soon now”. (This branch also includes my recent experiments in inhibiting implicit bridging conversions.) I’ll be curious to know what people think once they’ve had a chance to play with these changes.

Thanks!
- Joe

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Andrey Tarantsov) #7

Just thinking out loud — ‘dynamic’ as an attribute on an object, allowing arbitrary method calls that are dispatched dynamically...

let foo: @dynamic AnyObject = Foo()
foo.someWeirdMethod()

for thing in things {
    (thing as @dynamic).bar()
}

Dynamic as a type:

let foo: dynamic = Foo()
foo.someWeirdMethod()

for thing in things {
    (thing as dynamic).bar()
}

Since the dynamic keyword defines a function that goes through runtime dispatch, the same very runtime dispatch that current AnyObject calls go through, it would make sense for the new stuff to be called something like dynamic or dynamic_binding or DynamicObject or whatever.

A.


(Chris Lattner) #8

+1 for using a non implicit optional type.

-Chris

···

On Apr 18, 2016, at 8:01 PM, Joe Pamer via swift-evolution <swift-evolution@swift.org> wrote:

Just an update on this… (Sorry about the delay!)

After experimenting with the changes outlined below, and discussing the matter with a few folks off-list, it seems like a better compromise would be to only adopt option #2. We would keep the direct member access syntax and wrap the results of any dynamic member access expressions in an Optional, as opposed to an IUO. In practice this feels like a nice compromise that will prevent many users from shooting themselves in the foot, as changing the wrapping to optional forces users to immediately account for failures without having to jump through too many hoops.

Thoughts?

Thanks!
- Joe

On Mar 23, 2016, at 12:45 PM, Joseph Pamer <jpamer@apple.com> wrote:

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com> wrote:

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

I should be pushing a branch that does both of the above “real soon now”. (This branch also includes my recent experiments in inhibiting implicit bridging conversions.) I’ll be curious to know what people think once they’ve had a chance to play with these changes.

Thanks!
- Joe

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com

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

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


(Charles Srstka) #9

Making things optional is fine; I would, however, like to give a strong -1 to the original proposal in which everything would have to be cast to an explicit type, since Cocoa contains APIs that give you objects of opaque types, returned as id/AnyObject, and expects you to be able to call methods on them. For example, -[NSTreeController arrangedObjects] returns an id/AnyObject which the documentation promises will respond to -childNodes and -descendantNodeAtIndexPath:, but no type information is given. If the original proposal were implemented, this API would become impossible to use from Swift.

Charles

···

On Apr 18, 2016, at 10:01 PM, Joe Pamer via swift-evolution <swift-evolution@swift.org> wrote:

Just an update on this… (Sorry about the delay!)

After experimenting with the changes outlined below, and discussing the matter with a few folks off-list, it seems like a better compromise would be to only adopt option #2. We would keep the direct member access syntax and wrap the results of any dynamic member access expressions in an Optional, as opposed to an IUO. In practice this feels like a nice compromise that will prevent many users from shooting themselves in the foot, as changing the wrapping to optional forces users to immediately account for failures without having to jump through too many hoops.

Thoughts?

Thanks!
- Joe

On Mar 23, 2016, at 12:45 PM, Joseph Pamer <jpamer@apple.com <mailto:jpamer@apple.com>> wrote:

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com <mailto:jordan_rose@apple.com>> wrote:

The most common use case I've seen for this has been drilling into a heterogeneous collection without specifying intermediate types, i.e. `pluginProperties["actions"][0]["name"]`. Some possible variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) -> AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal), change the IUO-wrapped results (the current behavior) to use Optional, requiring developers to explicitly deal with the possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is used by introducing a warning for it in the type checker. Before making any changes here we'd want to know how it affects real-world projects.

I should be pushing a branch that does both of the above “real soon now”. (This branch also includes my recent experiments in inhibiting implicit bridging conversions.) I’ll be curious to know what people think once they’ve had a chance to play with these changes.

Thanks!
- Joe

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is described as part of how AnyObject works:

“You can also call any Objective-C method and access any property without casting to a more specific class type." … "However, because the specific type of an object typed as AnyObject is not known until runtime, it is possible to inadvertently write unsafe code. As in Objective-C, if you invoke a method or access a property that does not exist on an AnyObject typed object, it is a runtime error.”

I propose that we remove this behavior entirely to push swift further in the direction of type safety.

Rationale:
Even if you don’t mean to write code that relies on this behavior, it's easy to accidentally do so when interfacing with various Cocoa APIs due to type inference. A developer may not even realize that their code is unsafe since their code will compile just fine when calling obj-c visible methods. Removing this behavior would alleviate any confusion that a developer may have while writing this, especially as it is not a highly advertised feature of AnyObject. Furthermore, anyone who reads swift code using this will know with more certainty what types the author expects to be using here since an explicit cast will be required.

Considerations:
If this is done, the way I see AnyObject behaving is similar to Any, where you need to manually downcast in order to call methods on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
  thing.bar() // happens to work but not verified by compiler, may crash in the future
}

to something like this:

//...

for thing in things {
  if let foo = thing as? Foo { // needs an explicit cast
    foo.bar() // type checked, verified by compiler, won’t crash due to missing method
        }
}

One ancillary benefit that I can see of doing this is that it could make AnyObject consistent across darwin and other platforms. As far as I can tell, this behavior only exists on platforms where swift integrates with the objective-c runtime, and doing this will help swift code be more portable as it doesn’t rely on this implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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


(Brent Royal-Gordon) #10

Just thinking out loud — ‘dynamic’ as an attribute on an object, allowing arbitrary method calls that are dispatched dynamically...

let foo: @dynamic AnyObject = Foo()
foo.someWeirdMethod()

for thing in things {
    (thing as @dynamic).bar()
}

Dynamic as a type:

let foo: dynamic = Foo()
foo.someWeirdMethod()

for thing in things {
    (thing as dynamic).bar()
}

Since the dynamic keyword defines a function that goes through runtime dispatch, the same very runtime dispatch that current AnyObject calls go through, it would make sense for the new stuff to be called something like dynamic or dynamic_binding or DynamicObject or whatever.

Or add a "here there be dragons" keyword on the expression, like the `try` keyword. Straw syntax:

  let foo = Foo()
  yolo foo.someWeirdMethod()

···

--
Brent Royal-Gordon
Architechies


(Kevin Lundberg) #11

I just saw these replies while searching for this thread, as I've been
thinking of this proposal again recently.

Regarding the API you point out, would it not be more appropriate for
the API to change to provide an object conforming to some protocol
definition instead? I don't like the idea of possible language changes
being held back just because of some poorly defined APIs that were
designed for a different language. Objective-C and cocoa frameworks have
been changing a lot in the past two years to better interoperate with
swift, and I don't see this example as something that needs to be
handled at all differently.

That said, if there are too many of these kinds of examples in Apple's
frameworks to completely resolve, would a more explicit use of the
feature be palatable? I mentioned in another branch of this thread that
if it is absolutely necessary to keep this behavior around, it could be
separated from AnyObject, perhaps into another protocol called something
like ObjcObject, so that code would need to do something like this to
compile and work:

let thing: ObjcObject = treeController.arrangedObjects()
let nodes = thing.childNodes?()

Unless theres a lot of pushback or it's too late for this kind of change
to go into Swift 3, I'd like to make some kind of proposal along these
lines.

- Kevin

···

On 4/18/2016 11:12 PM, Charles Srstka via swift-evolution wrote:

Making things optional is fine; I would, however, like to give a
strong -1 to the original proposal in which everything would have to
be cast to an explicit type, since Cocoa contains APIs that give you
objects of opaque types, returned as id/AnyObject, and expects you to
be able to call methods on them. For example, -[NSTreeController
arrangedObjects] returns an id/AnyObject which the documentation
promises will respond to -childNodes and -descendantNodeAtIndexPath:,
but no type information is given. If the original proposal were
implemented, this API would become impossible to use from Swift.

Charles

On Apr 18, 2016, at 10:01 PM, Joe Pamer via swift-evolution >> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Just an update on this… (Sorry about the delay!)

After experimenting with the changes outlined below, and discussing
the matter with a few folks off-list, it seems like a better
compromise would be to only adopt option #2. We would keep the direct
member access syntax and wrap the results of any dynamic member
access expressions in an Optional, as opposed to an IUO. In practice
this feels like a nice compromise that will prevent many users from
shooting themselves in the foot, as changing the wrapping to optional
forces users to immediately account for failures without having to
jump through too many hoops.

Thoughts?

Thanks!
- Joe

On Mar 23, 2016, at 12:45 PM, Joseph Pamer <jpamer@apple.com >>> <mailto:jpamer@apple.com>> wrote:

On Mar 23, 2016, at 11:29 AM, Jordan Rose <jordan_rose@apple.com >>>> <mailto:jordan_rose@apple.com>> wrote:

The most common use case I've seen for this has been drilling into
a heterogeneous collection without specifying intermediate types,
i.e. `pluginProperties["actions"][0]["name"]`. Some possible
variations on this proposal could continue to allow that:

- Remove all lookup except the subscripts `(AnyObject) ->
AnyObject?` and `(Int) -> AnyObject`.
- Instead of removing AnyObject lookup completely (the proposal),
change the IUO-wrapped results (the current behavior) to use
Optional, requiring developers to explicitly deal with the
possibility of failure.
- Both of the above.

I know Joe Pamer has been looking into seeing how this feature is
used by introducing a warning for it in the type checker. Before
making any changes here we'd want to know how it affects real-world
projects.

I should be pushing a branch that does both of the above “real soon
now”. (This branch also includes my recent experiments in inhibiting
implicit bridging conversions.) I’ll be curious to know what people
think once they’ve had a chance to play with these changes.

Thanks!
- Joe

Jordan

On Mar 22, 2016, at 18:59 , Kevin Lundberg via swift-evolution >>>>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

In "Using Swift with Cocoa and Objective-C", this behavior is
described as part of how AnyObject works:

/“You can also call any Objective-C method and access any property
without casting to a more specific class type." … "However,
because the specific type of an object typed as AnyObject is not
known until runtime, it is possible to inadvertently write unsafe
code. As in Objective-C, if you invoke a method or access a
property that does not exist on an AnyObject typed object, it is a
runtime error.”/

I propose that we remove this behavior entirely to push swift
further in the direction of type safety.

*Rationale:*
Even if you don’t mean to write code that relies on this behavior,
it's easy to accidentally do so when interfacing with various
Cocoa APIs due to type inference. A developer may not even realize
that their code is unsafe since their code will compile just fine
when calling obj-c visible methods. Removing this behavior would
alleviate any confusion that a developer may have while writing
this, especially as it is not a highly advertised feature of
AnyObject. Furthermore, anyone who reads swift code using this
will know with more certainty what types the author expects to be
using here since an explicit cast will be required.

*Considerations:*
If this is done, the way I see AnyObject behaving is similar to
Any, where you need to manually downcast in order to call methods
on things. Code would change from this:

class Foo: NSObject { func bar() {} }
let things = NSOrderedSet(object: Foo())

for thing in things { // thing is AnyObject
thing.bar() // happens to work but not verified by compiler, may
crash in the future
}

to something like this:

//...

for thing in things {
if let foo = thing as? Foo { // needs an explicit cast
foo.bar() // type checked, verified by compiler, won’t crash due
to missing method
        }
}

One ancillary benefit that I can see of doing this is that it
could make AnyObject consistent across darwin and other platforms.
As far as I can tell, this behavior only exists on platforms where
swift integrates with the objective-c runtime, and doing this will
help swift code be more portable as it doesn’t rely on this
implicit behavior.

Any thoughts?

--
Kevin Lundberg
kevin@klundberg.com <mailto:kevin@klundberg.com>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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