[Pitch] "unavailable" members shouldn't need an impl


(Austin Zheng) #1

Hello swift-evolutioneers,

Here's an idea. It's technically additive, but it's small and I think it
fits in well with Swift 3's goals, one of which is to establish API
conventions.

Right now, you can declare a function, type member, etc and mark it using
"@available(*, unavailable, renamed:"someNewName()")". Doing so causes a
compile-time error if the user tries to use that member, and if you provide
the new name a fix-it is even generated telling you to use the new name.

However, you can (and still need to) provide an implementation (e.g.
function body). You can just stick a fatalError() inside and be done with
it, but my question is, is an impl even necessary?

My pitch is very simple: the declaration of any member marked with
@available(*, unavailable), or in other words marked as unavailable
regardless of platform or version, should be allowed to omit the
implementation.

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less
kludgy.

What do people think? Are there any potential barriers (implementation or
semantics) that would preclude this?

Best,
Austin


(Brent Royal-Gordon) #2

However, you can (and still need to) provide an implementation (e.g. function body). You can just stick a fatalError() inside and be done with it, but my question is, is an impl even necessary?

Personally, I always figured the best thing to do was to call through to the new API. We don't have library resilience yet, but I'm assuming that's what you'll have to do in resilient code when you want to retire an API, just in case you get loaded by something compiled against an old version.

···

--
Brent Royal-Gordon
Architechies


(Erica Sadun) #3

You ask, we answer. I'd much prefer spelling out { fatalError("unavailable API") }.
It makes the code clearer to read, to maintain, it produces debug and runtime errors. etc. I think
this is an example where concision is overrated.

-- E

···

On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less kludgy.


(John McCall) #4

Hello swift-evolutioneers,

Here's an idea. It's technically additive, but it's small and I think it fits in well with Swift 3's goals, one of which is to establish API conventions.

Right now, you can declare a function, type member, etc and mark it using "@available(*, unavailable, renamed:"someNewName()")". Doing so causes a compile-time error if the user tries to use that member, and if you provide the new name a fix-it is even generated telling you to use the new name.

However, you can (and still need to) provide an implementation (e.g. function body). You can just stick a fatalError() inside and be done with it, but my question is, is an impl even necessary?

My pitch is very simple: the declaration of any member marked with @available(*, unavailable), or in other words marked as unavailable regardless of platform or version, should be allowed to omit the implementation.

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less kludgy.

What do people think? Are there any potential barriers (implementation or semantics) that would preclude this?

I actually just consider it a bug that you're require to implement an always-unavailable function. We can take it through evolution anyway, though.

John.

···

On Jun 10, 2016, at 2:22 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Best,
Austin

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


(Austin Zheng) #5

Sorry, but I'm going to have to disagree with you on this one.

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less
kludgy.

You ask, we answer. I'd much prefer spelling out {
fatalError("unavailable API") }.
It makes the code clearer to read, to maintain,

The member is marked as "unavailable" in the @available annotation. I don't
see how adding a fatalError() in the body makes it any clearer or easier to
read.

it produces debug and runtime errors. etc.

I'm not sure how you can even compile code that uses an API marked as
"unavailable", given that using such an API causes the compiler to error.

···

On Fri, Jun 10, 2016 at 2:26 PM, Erica Sadun <erica@ericasadun.com> wrote:

On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:

I think
this is an example where concision is overrated.

-- E


(Leonardo Pessoa) #6

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

···

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution > <swift-evolution@swift.org> wrote:

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less
kludgy.

You ask, we answer. I'd much prefer spelling out { fatalError("unavailable
API") }.
It makes the code clearer to read, to maintain, it produces debug and
runtime errors. etc. I think
this is an example where concision is overrated.

-- E

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


(Andrew Bennett) #7

Unavailable doesn't mean un-callable.

   - If you're marking an override or required initialiser as unavailable,
   it's still possible it's called dynamically, or by super.
   - If you're marking it unavailable for some OS versions, it could still
   be called by the other OS versions.
   - If it's neither of those two categories, you probably don't even need
   the function declaration.

It's not clear what default behaviour you would want in an unavailable
method, calling super, calling a new method, a runtime error, ...

An undefined implementation lacks clarity, as Erica says, "this is an
example where concision is overrated".

Likewise, as Brent says, you may want the old unavailable API to call
through to the new API. A new version of a library may be dynamically
linked by something compiled against an older version.

···

On Sat, Jun 11, 2016 at 10:47 AM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

> On Jun 10, 2016, at 2:22 PM, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:
>
> Hello swift-evolutioneers,
>
> Here's an idea. It's technically additive, but it's small and I think it
fits in well with Swift 3's goals, one of which is to establish API
conventions.
>
> Right now, you can declare a function, type member, etc and mark it
using "@available(*, unavailable, renamed:"someNewName()")". Doing so
causes a compile-time error if the user tries to use that member, and if
you provide the new name a fix-it is even generated telling you to use the
new name.
>
> However, you can (and still need to) provide an implementation (e.g.
function body). You can just stick a fatalError() inside and be done with
it, but my question is, is an impl even necessary?
>
> My pitch is very simple: the declaration of any member marked with
@available(*, unavailable), or in other words marked as unavailable
regardless of platform or version, should be allowed to omit the
implementation.
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less
kludgy.
>
> What do people think? Are there any potential barriers (implementation
or semantics) that would preclude this?

I actually just consider it a bug that you're require to implement an
always-unavailable function. We can take it through evolution anyway,
though.

John.

>
> Best,
> Austin
>
> _______________________________________________
> 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


(Chris Lattner) #8

I agree with John on both parts: it’s a bug, but considering it in evolution makes sense.

-Chris

···

On Jun 10, 2016, at 5:47 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 10, 2016, at 2:22 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

Hello swift-evolutioneers,

Here's an idea. It's technically additive, but it's small and I think it fits in well with Swift 3's goals, one of which is to establish API conventions.

Right now, you can declare a function, type member, etc and mark it using "@available(*, unavailable, renamed:"someNewName()")". Doing so causes a compile-time error if the user tries to use that member, and if you provide the new name a fix-it is even generated telling you to use the new name.

However, you can (and still need to) provide an implementation (e.g. function body). You can just stick a fatalError() inside and be done with it, but my question is, is an impl even necessary?

My pitch is very simple: the declaration of any member marked with @available(*, unavailable), or in other words marked as unavailable regardless of platform or version, should be allowed to omit the implementation.

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less kludgy.

What do people think? Are there any potential barriers (implementation or semantics) that would preclude this?

I actually just consider it a bug that you're require to implement an always-unavailable function. We can take it through evolution anyway, though.


(Austin Zheng) #9

NSUnimplemented() has nothing to do with the Swift compiler proper, and you
won't find it in the Swift repo. It's a marker used for the Swift
Foundation project to denote methods and APIs that haven't yet been
implemented. It has nothing to do with availability/renamed.

As for the overhead, I don't understand this argument either. Today, the
compiler already has to cross-check the use of an API against a list of
whether or not it's been blacklisted using "unavailable". If it's
"unavailable" the compiler stops with an error and does not need to further
check whether a function body has been defined. As for the grammar, there
are already productions defined for member declarations without
implementations, used for constructing protocols.

Austin

···

On Fri, Jun 10, 2016 at 2:32 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution > <swift-evolution@swift.org> wrote:
> On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution > > <swift-evolution@swift.org> wrote:
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less
> kludgy.
>
>
> You ask, we answer. I'd much prefer spelling out {
fatalError("unavailable
> API") }.
> It makes the code clearer to read, to maintain, it produces debug and
> runtime errors. etc. I think
> this is an example where concision is overrated.
>
> -- E
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


(Mark Lacey) #10

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

You might be thinking of Builtin.unreachable(). I recently replaced the bodies of all the stdlib functions that are completely unavailable with calls to Builtin.unreachable(), which generates a trap instruction (as opposed to having bodies with calls to fatalError that take static strings, which generate more code/data).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

Personally I think it would be nice if you could omit the body of any function (through some special syntax) with the intent that you’d get a warning for these when compiling and that they would trap if ever called. It can be handy to be able to do that while prototyping, e.g. as you’re trying to figure out the design of the types you want to implement, etc. I don’t consider this critical though, as it’s usually not too hard to write a placeholder body that constructs and returns some kind of dummy return value.

Mark

···

On Jun 10, 2016, at 2:32 PM, Leonardo Pessoa via swift-evolution <swift-evolution@swift.org> wrote:

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution > <swift-evolution@swift.org> wrote:

On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution >> <swift-evolution@swift.org> wrote:

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less
kludgy.

You ask, we answer. I'd much prefer spelling out { fatalError("unavailable
API") }.
It makes the code clearer to read, to maintain, it produces debug and
runtime errors. etc. I think
this is an example where concision is overrated.

-- E

_______________________________________________
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


(Leonardo Pessoa) #11

The thing with NSUnimplemented was really just a mention and nothing to do with the issue.

As for the real issue, the blacklist you mention is formed after parsing the source. That's how compilers usually work. The function impl is expected due to the syntax definition that is checked during the parsing phase and before that blacklist is formed. Thus one would have to flex the syntax and allow every func to not have a body and only after parsing the source check if the func had or not to have a body based on the unavailable attribute. That is unless the compiler analyses the source code while it's being parsed and I don't believe the Swift compiler does that (I haven't been that down the rabbit hole so I'm not affirming). Please note that in not saying it cannot be done, only that there may be consequences.

L

···

-----Original Message-----
From: "Austin Zheng" <austinzheng@gmail.com>
Sent: ‎10/‎06/‎2016 06:42 PM
To: "Leonardo Pessoa" <me@lmpessoa.com>
Cc: "Erica Sadun" <erica@ericasadun.com>; "swift-evolution" <swift-evolution@swift.org>
Subject: Re: [swift-evolution] [Pitch] "unavailable" members shouldn't need animpl

NSUnimplemented() has nothing to do with the Swift compiler proper, and you won't find it in the Swift repo. It's a marker used for the Swift Foundation project to denote methods and APIs that haven't yet been implemented. It has nothing to do with availability/renamed.

As for the overhead, I don't understand this argument either. Today, the compiler already has to cross-check the use of an API against a list of whether or not it's been blacklisted using "unavailable". If it's "unavailable" the compiler stops with an error and does not need to further check whether a function body has been defined. As for the grammar, there are already productions defined for member declarations without implementations, used for constructing protocols.

Austin

On Fri, Jun 10, 2016 at 2:32 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution > <swift-evolution@swift.org> wrote:

So, instead of:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int { fatalError() }

You can just have:

@available(*, unavailable, renamed:"someNewAPI()")
public func someOldAPI() -> Int

The intent is, in my opinion, clearer for the latter and it feels less
kludgy.

You ask, we answer. I'd much prefer spelling out { fatalError("unavailable
API") }.
It makes the code clearer to read, to maintain, it produces debug and
runtime errors. etc. I think
this is an example where concision is overrated.

-- E

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


(Austin Zheng) #12

All I want, and all @available(*, unavailable, renamed:) gives me, is the ability to give my users a more pleasant upgrade experience than simply working through whatever errors Xcode spits out and trying to figure out what old APIs correspond with what new APIs.

If resilience is an issue...a library author can always simply choose to remove the old APIs altogether and break compatibility with older consumers. There is nothing Swift currently does to prevent this from happening, nor should it. If the library author wishes to forward use of unavailable APIs to their not-unavailable counterparts, that's their prerogative. There are a couple of other folks who are working on a resilience story for Swift (including things like versioning checks); until they have something to show us I don't see the point of worrying about maintaining resilience that Swift doesn't promise consumers of libraries to begin with.

Austin

···

On Jun 10, 2016, at 6:51 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

Unavailable doesn't mean un-callable.
If you're marking an override or required initialiser as unavailable, it's still possible it's called dynamically, or by super.
If you're marking it unavailable for some OS versions, it could still be called by the other OS versions.
If it's neither of those two categories, you probably don't even need the function declaration.
It's not clear what default behaviour you would want in an unavailable method, calling super, calling a new method, a runtime error, ...

An undefined implementation lacks clarity, as Erica says, "this is an example where concision is overrated".

Likewise, as Brent says, you may want the old unavailable API to call through to the new API. A new version of a library may be dynamically linked by something compiled against an older version.

On Sat, Jun 11, 2016 at 10:47 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On Jun 10, 2016, at 2:22 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> Hello swift-evolutioneers,
>
> Here's an idea. It's technically additive, but it's small and I think it fits in well with Swift 3's goals, one of which is to establish API conventions.
>
> Right now, you can declare a function, type member, etc and mark it using "@available(*, unavailable, renamed:"someNewName()")". Doing so causes a compile-time error if the user tries to use that member, and if you provide the new name a fix-it is even generated telling you to use the new name.
>
> However, you can (and still need to) provide an implementation (e.g. function body). You can just stick a fatalError() inside and be done with it, but my question is, is an impl even necessary?
>
> My pitch is very simple: the declaration of any member marked with @available(*, unavailable), or in other words marked as unavailable regardless of platform or version, should be allowed to omit the implementation.
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less kludgy.
>
> What do people think? Are there any potential barriers (implementation or semantics) that would preclude this?

I actually just consider it a bug that you're require to implement an always-unavailable function. We can take it through evolution anyway, though.

John.

>
> Best,
> Austin
>
> _______________________________________________
> 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


(Charlie Monroe) #13

As Andrew says - I have several cases where I mark a method on a subclass as unavailable to ensure subclasses do not call it directly, but it is required by the root class to be implemented (which it is and gets called).

Example:

class Root {
  func doSomething() {
    print("Root")
  }
}

class Subclass {
  @available(*, unavailable)
  override func doSomething() {
    super.doSomething()
    print("Subclass")
  }
}

And you can still do:

let instance: Root = Subclass()
instance.doSomething()

and it will call Root Subclass.

If it's renamed, you should really first deprecate it and just call the new API and after a while make it unavailable with no change in the code.

If it's meant for abstract classes, then it's kind of a different issue.

···

On Jun 11, 2016, at 3:51 AM, Andrew Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Unavailable doesn't mean un-callable.
If you're marking an override or required initialiser as unavailable, it's still possible it's called dynamically, or by super.
If you're marking it unavailable for some OS versions, it could still be called by the other OS versions.
If it's neither of those two categories, you probably don't even need the function declaration.
It's not clear what default behaviour you would want in an unavailable method, calling super, calling a new method, a runtime error, ...

An undefined implementation lacks clarity, as Erica says, "this is an example where concision is overrated".

Likewise, as Brent says, you may want the old unavailable API to call through to the new API. A new version of a library may be dynamically linked by something compiled against an older version.

On Sat, Jun 11, 2016 at 10:47 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On Jun 10, 2016, at 2:22 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
>
> Hello swift-evolutioneers,
>
> Here's an idea. It's technically additive, but it's small and I think it fits in well with Swift 3's goals, one of which is to establish API conventions.
>
> Right now, you can declare a function, type member, etc and mark it using "@available(*, unavailable, renamed:"someNewName()")". Doing so causes a compile-time error if the user tries to use that member, and if you provide the new name a fix-it is even generated telling you to use the new name.
>
> However, you can (and still need to) provide an implementation (e.g. function body). You can just stick a fatalError() inside and be done with it, but my question is, is an impl even necessary?
>
> My pitch is very simple: the declaration of any member marked with @available(*, unavailable), or in other words marked as unavailable regardless of platform or version, should be allowed to omit the implementation.
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less kludgy.
>
> What do people think? Are there any potential barriers (implementation or semantics) that would preclude this?

I actually just consider it a bug that you're require to implement an always-unavailable function. We can take it through evolution anyway, though.

John.

>
> Best,
> Austin
>
> _______________________________________________
> 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


(Austin Zheng) #14

I won't speculate more about the internals of the compiler, since (like
you) I haven't gone far enough into the code to know how it works in this
regard.

However, if it really is something that the current architecture can't
easily support, I'd be happy for one of the compiler engineers to jump in
and nix the proposal, or for the proposal to be dismissed after five
minutes of deliberation when it goes up for review. Worrying about
implementation cost without understanding whether it would actually be a
problem is premature.

···

On Fri, Jun 10, 2016 at 3:03 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

The thing with NSUnimplemented was really just a mention and nothing to do
with the issue.

As for the real issue, the blacklist you mention is formed after parsing
the source. That's how compilers usually work. The function impl is
expected due to the syntax definition that is checked during the parsing
phase and before that blacklist is formed. Thus one would have to flex the
syntax and allow every func to not have a body and only after parsing the
source check if the func had or not to have a body based on the unavailable
attribute. That is unless the compiler analyses the source code while it's
being parsed and I don't believe the Swift compiler does that (I haven't
been that down the rabbit hole so I'm not affirming). Please note that in
not saying it cannot be done, only that there may be consequences.

L
------------------------------
From: Austin Zheng <austinzheng@gmail.com>
Sent: ‎10/‎06/‎2016 06:42 PM
To: Leonardo Pessoa <me@lmpessoa.com>
Cc: Erica Sadun <erica@ericasadun.com>; swift-evolution
<swift-evolution@swift.org>
Subject: Re: [swift-evolution] [Pitch] "unavailable" members shouldn't
need animpl

NSUnimplemented() has nothing to do with the Swift compiler proper, and
you won't find it in the Swift repo. It's a marker used for the Swift
Foundation project to denote methods and APIs that haven't yet been
implemented. It has nothing to do with availability/renamed.

As for the overhead, I don't understand this argument either. Today, the
compiler already has to cross-check the use of an API against a list of
whether or not it's been blacklisted using "unavailable". If it's
"unavailable" the compiler stops with an error and does not need to further
check whether a function body has been defined. As for the grammar, there
are already productions defined for member declarations without
implementations, used for constructing protocols.

Austin

On Fri, Jun 10, 2016 at 2:32 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution >> <swift-evolution@swift.org> wrote:
> On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution >> > <swift-evolution@swift.org> wrote:
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less
> kludgy.
>
>
> You ask, we answer. I'd much prefer spelling out {
fatalError("unavailable
> API") }.
> It makes the code clearer to read, to maintain, it produces debug and
> runtime errors. etc. I think
> this is an example where concision is overrated.
>
> -- E
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>


(Tony Allevato) #15

The thing with NSUnimplemented was really just a mention and nothing to do
with the issue.

As for the real issue, the blacklist you mention is formed after parsing
the source. That's how compilers usually work. The function impl is
expected due to the syntax definition that is checked during the parsing
phase and before that blacklist is formed. Thus one would have to flex the
syntax and allow every func to not have a body and only after parsing the
source check if the func had or not to have a body based on the unavailable
attribute. That is unless the compiler analyses the source code while it's
being parsed and I don't believe the Swift compiler does that (I haven't
been that down the rabbit hole so I'm not affirming). Please note that in
not saying it cannot be done, only that there may be consequences.

It seems like the whole "has a body" thing only really matters in the case
of non-Void functions, because a Void function can simply do this:

    @available(*, unavailable, renamed:"someNewAPI()")
    public func someOldAPI() {}

The extra two characters (three, if you count the space), don't seem like
the end of the world there.

So, what if unavailable functions were implicitly @noreturn, since that's
effectively what you're doing by calling fatalError anyway? Then you could
write {} even for value-returning functions, and it happens at the semantic
analysis phase where that contextual information is available, rather than
at the syntactic analysis phase.

···

On Fri, Jun 10, 2016 at 3:03 PM Leonardo Pessoa via swift-evolution < swift-evolution@swift.org> wrote:

L
------------------------------
From: Austin Zheng <austinzheng@gmail.com>
Sent: ‎10/‎06/‎2016 06:42 PM
To: Leonardo Pessoa <me@lmpessoa.com>
Cc: Erica Sadun <erica@ericasadun.com>; swift-evolution
<swift-evolution@swift.org>
Subject: Re: [swift-evolution] [Pitch] "unavailable" members shouldn't
need animpl

NSUnimplemented() has nothing to do with the Swift compiler proper, and
you won't find it in the Swift repo. It's a marker used for the Swift
Foundation project to denote methods and APIs that haven't yet been
implemented. It has nothing to do with availability/renamed.

As for the overhead, I don't understand this argument either. Today, the
compiler already has to cross-check the use of an API against a list of
whether or not it's been blacklisted using "unavailable". If it's
"unavailable" the compiler stops with an error and does not need to further
check whether a function body has been defined. As for the grammar, there
are already productions defined for member declarations without
implementations, used for constructing protocols.

Austin

On Fri, Jun 10, 2016 at 2:32 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

I've seen around the Swift source code some uses of a function named
something like NSUnimplemented(). I'm not sure this is available only
inside the Swift source or if we could call it as well (I'm not in
front of a Swift compiler right now so I cannot test).

The idea of being able to drop the body of the function is interesting
but I keep thinking of the overhead of the compiler to check for every
function if it can drop the requirement for a body. Perhaps keeping
the body is well suited here.

On 10 June 2016 at 18:26, Erica Sadun via swift-evolution >> <swift-evolution@swift.org> wrote:
> On Jun 10, 2016, at 3:22 PM, Austin Zheng via swift-evolution >> > <swift-evolution@swift.org> wrote:
>
> So, instead of:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int { fatalError() }
>
> You can just have:
>
> @available(*, unavailable, renamed:"someNewAPI()")
> public func someOldAPI() -> Int
>
> The intent is, in my opinion, clearer for the latter and it feels less
> kludgy.
>
>
> You ask, we answer. I'd much prefer spelling out {
fatalError("unavailable
> API") }.
> It makes the code clearer to read, to maintain, it produces debug and
> runtime errors. etc. I think
> this is an example where concision is overrated.
>
> -- E
>
>
>
> _______________________________________________
> 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


(John McCall) #16

In many of the cases you guys are describing, we ought to have enough information to do the right thing. For example, library evolution shouldn't be done with an ordinary unavailable attribute; it should use something that indicates that the API was added in v2.6c, deprecated in v2.7, and made illegal in v2.8. With that information, we clearly would still require a function body if the build configuration says that we need to support pre-v2.8 clients.

My point was just that there is a clear use case for an attribute that says "don't allow this declaration to be used at all", including indirectly such as via a protocol requirement or overridden method, and those use cases should not require an actual function body. I recognize that there are also use cases for a more relaxed attribute that just prohibits direct uses but still requires a function body. Perhaps that should just be a completely different attribute, or perhaps we can differentiate based on the attribute arguments, or perhaps we can address all of those use cases with targeted language features. However, we should still get to a point where we don't require function bodies for the true-unavailable cases.

John.

···

On Jun 12, 2016, at 9:08 PM, Charlie Monroe <charlie@charliemonroe.net> wrote:

On Jun 11, 2016, at 3:51 AM, Andrew Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Unavailable doesn't mean un-callable.
If you're marking an override or required initialiser as unavailable, it's still possible it's called dynamically, or by super.
If you're marking it unavailable for some OS versions, it could still be called by the other OS versions.
If it's neither of those two categories, you probably don't even need the function declaration.
It's not clear what default behaviour you would want in an unavailable method, calling super, calling a new method, a runtime error, ...

An undefined implementation lacks clarity, as Erica says, "this is an example where concision is overrated".

Likewise, as Brent says, you may want the old unavailable API to call through to the new API. A new version of a library may be dynamically linked by something compiled against an older version.

As Andrew says - I have several cases where I mark a method on a subclass as unavailable to ensure subclasses do not call it directly, but it is required by the root class to be implemented (which it is and gets called).

Example:

class Root {
  func doSomething() {
    print("Root")
  }
}

class Subclass {
  @available(*, unavailable)
  override func doSomething() {
    super.doSomething()
    print("Subclass")
  }
}

And you can still do:

let instance: Root = Subclass()
instance.doSomething()

and it will call Root Subclass.

If it's renamed, you should really first deprecate it and just call the new API and after a while make it unavailable with no change in the code.

If it's meant for abstract classes, then it's kind of a different issue.