[PITCH] A placeholder for unimplemented functions

While a code change is in-progress, the developer may want to insert placeholders for certain values. For example, while they work on the the definition and implementation of a createWidget(...) function, they may have some code elsewhere like:

let someWidget = /* would use createWidget here, but it's not done */

In this case, if they want the project to still build while they flesh out the createWidget function, it would be handy to do this instead:

let someWidget = unimplemented()

Where unimplemented() could be written, say, as the following:

public func unimplemented<T>(context: String = "") -> T {
  var errorMessage = "Reached unimplemented function"
  if !context.isEmpty {
    errorMessage += ": \(context)"
  }
  fatalError(errorMessage)
}

This is similar to the todo!() and unimplemented!() macros in Rust, and has been discussed for Swift before, such as here.

6 Likes

Personally, I don’t see any real use for it. In your example i normally start with the

func createWidget(name: String) → Widget {
    Widget(name: ā€œā€œ)
}

or even


func createWidget(name: String) → Widget {
    fatalError(ā€œNot implementedā€œ)
}

There you achieve the same result, but the compiler already has the function signature for doing itā€˜s stuff and you have to write it anyways sooner or later.

Also, how would the compiler know in your example what type widget is, without writing let someWidget: Widget = unimplemented. If its nil, it doesn’t help with further implementation.

4 Likes

I would rather see this solved at the tool level (I.e. in de the IDE) than as a language feature.

The tool could simply create the function as proposed by @flexlixrup .

2 Likes

I find todo macro in Rust quite useful and just recently have had thoughts of it would be a good idea to bring similar solution to Swift in form of #todo or #todo(ā€œmessageā€). In overall, I’m for such addition.

3 Likes

I looked into the Rust solution a bit and I think I agree with you.

IIUC they have macro's that wrap panic which seems like their version of fatalError. Whereas we can of course always just use fatalError, perhaps using a macro named todo or unimplemented might signal more intend.

And might help IDE's collect these, just as some are able to spot // TODO: comments.

But I also still think that our IDEs could offer more support for refactoring/creation, something like what IntelliJ does for JAVA.

That’s right, they have the same intentions.

I gave up on IDEs when it comes for Swift and use neovim whenever possible to avoid Xcode. And with Xcode still holding probably the biggest share of Swift developers, relying solely on tooling doesn’t promise a bright future; hopes for JetBrains-level IDE support probably only on Fleet.

1 Like

I think using this should result in a warning so that you remember to go back and implement it. We could implement this as a macro, in which case adding a warning would be easy.

7 Likes

That, and #UnimplementedView that could have some distinct visual representation (I usually do diagonal stripes) and a warning.

In the past, I’ve done this by making a function that wraps fatalError as demonstrated in the original post, and immediately marking it as deprecated. Deprecation warnings aren’t quite semantically correct but it is the easiest way to do this without custom macros.

1 Like

When I came over to Swift many years ago I was missing this as well to be honest. In my case it was coming from Scala with its val example = ??? where ??? is just a method with a funny name with the same semantics :wink:

I wonder if we were to consider such in swift we could make it immediately deprecated, so that usages of it trigger warnings immediately. Or rather, perhaps have it some other warning specific to this (in case one wanted to silence/allow it).

2 Likes

I've played a bit with macros, and came up with freestanding and attached body versions:

func basic() -> Int {
    #todo
}

func custom() -> Int {
    #todo("I want to be free!")
}

@todo
func attachedBasic() -> Int

@todo("Run Forrest, run!")
func attachedCustom() -> Int

// this works as well
let x: Int = #todo

Producing output like this (probably can be improved even more, but good enough for example):

.../todo/Sources/todoClient/main.swift:4:5: warning: TODO: Implement me!
 2 |
 3 | func basic() -> Int {
 4 |     #todo
   |     `- warning: TODO: Implement me!
 5 | }
 6 |

...

.../todo/Sources/todoClient/main.swift:14:1: warning: TODO: Run Forrest, run
13 |
14 | @todo("Run Forrest, run")
15 | func attachedCustom() -> Int
   |      `- warning: TODO: Run Forrest, run
16 |
5 Likes

If you use context.diagnose in your macro instead of emitting #warning in the output, you can choose which node to apply the warning to.

4 Likes

Didn’t think of it, thanks! Makes perfect sense

Could you share the implementation code please?