JetBrains / kotlin-native

Kotlin/Native infrastructure
Apache License 2.0
7.02k stars 566 forks source link

error: unable to call non-designated initializer as super constructor #2010

Closed soywiz closed 3 years ago

soywiz commented 6 years ago

This is valid swift code. But doesn't work on Kotlin/Native:

screen shot 2018-09-06 at 00 42 12

Related issue: https://github.com/JetBrains/kotlin-native/issues/1995 This class is from the platform.AppKit so cannot do the trick of disableDesignatedInitializerChecks

SvyatoslavScherbina commented 6 years ago

Default initializer here is not designated even in Swift. Your Swift code snippet is not equivalent to Kotlin one: it doesn't delegate to super.init because it doesn't declare any initializer: by default Swift inherits all initializers from super class.

If you add init() { super.init() } to your Swift class, then you will see that Swift doesn't allow to call super.init() too.

Please delegate to a proper designated initializer, e.g. initWithFrame:.

soywiz commented 6 years ago

In the case this helps someone else:

screen shot 2018-09-06 at 11 05 15

I find it a bit strange. Not sure how swift generates the default init from NSResponder. Maybe because the failable initializers?

Now I somehow understand how it is like that. But I guess other people will have similar issues. Will try to port code from Swift to K/N that do not provide other initializers, and will stumble upon this problem.

Would be nice if inherited K/N classes from Obj-C/Swift autogenerate proper constructors when no default constructor is provided. Though not sure how complex would be do that. Or at least a quickfix to generate all the constructors automatically, and detect the error at editing time. Specially I'm not sure about how the default init from NSResponder is generated for MyOpenglView, so couldn't reproduce for other classes without manually instantiating the required arguments. Maybe they are providing default values like zero-filled memory for value types? No clue here.

SvyatoslavScherbina commented 6 years ago

I find it a bit strange. Not sure how swift generates the default init from NSResponder.

It is inherited from NSOpenGLView, which inherits it from NSView, which inherits it from NSResponder.

LouisCAD commented 5 years ago

In the case of UIViewController from iOS UIKit, passing a default NSCoder() instance results in a crash, and adding a no-args constructor override results in this same compilation error (that IntelliJ doesn't catch).

Is there a workaround to be able to subclass UIViewController, something very basic for making an iOS app?

LouisCAD commented 5 years ago

Here's an example of a subclass that compiles and can be instantiated if referenced from a storyboard:

@ExportObjCClass
class OldViewController : UIViewController {

    @OverrideInit
    constructor(coder: NSCoder) : super(coder)

    @ObjCOutlet
    lateinit var label: UILabel

    @ObjCOutlet
    lateinit var textField: UITextField

    @ObjCOutlet
    lateinit var button: UIButton

    @ObjCAction
    fun buttonPressed() {
        label.text = "Konan says: 'Hello, ${textField.text}!'"
    }
}

However, if you want to be able to instantiate it from code, breaking free from storyboards, you're out of luck as adding the following results in the compilation error of that issue:

    @OverrideInit
    constructor() : super()
SvyatoslavScherbina commented 5 years ago

https://github.com/JetBrains/kotlin-native/issues/2010#issuecomment-418986603

Please delegate to a proper designated initializer, e.g. initWithFrame:.

LouisCAD commented 5 years ago

@SvyatoslavScherbina How? I don't see how I'd translate that to Kotlin.

LouisCAD commented 5 years ago

I found a way to make my UIViewController subclass usable from Kotlin! There's a third constructor I didn't notice that takes 2 nullable parameters, and passing them null works just fine, and can be set in default parameters from Kotlin! Here's an example snippet:

ExportObjCClass
class MyViewController @OverrideInit constructor(
    nibName: String? = null,
    bundle: NSBundle? = null
) : UIViewController(nibName, bundle) {

    init {
        title = "MyViewController"
    }

    private val button: UIButton = UIButton.buttonWithType(UIButtonTypeSystem).apply {
        setTitle("Finally!", UIControlStateNormal)
        view.addSubview(this)
        setFrame(CGRectMake(x = 10.0, y = 25.0, width = 125.0, height = 75.0))
    }

    suspend fun run() {
        var counter = 0
        while (true) {
            button.awaitOneClick() // Custom suspending extension function
            button.setTitle("Clicked ${++counter} times!", UIControlStateNormal)
        }
    }
}
SvyatoslavScherbina commented 5 years ago

Please note that you don't need @ExportObjCClass and @OverrideInit if you instantiate MyViewController manually.

Btw,

init {
    title = "MyViewController"
}

How would you do this in Swift without overriding initializers?

LouisCAD commented 5 years ago

That's right, here's a smaller version that works:

private class MyNavViewController : UINavigationController(null, bundle = null) {
    // named argument is needed here to disambiguate overload resolution from the navigation UIController
    ...
}

As for the init block, I think Kotlin wins on that one, Swift doesn't seem to be able to do it without overriding the initializers, although I am not so familiar with it.

SBNTT commented 5 years ago

Trying to make the same thing using appkit NSViewController to build a desktop app without storyboard but this code lead to a crash

class ViewController : NSViewController(null, bundle = null) {   }
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: kobjc2 in bundle (null).'

Can't get rid of Unable to call non-designated initializer as super constructor either, and i don't know what to do with disableDesignatedInitializerChecks to try as it's a simple program without any .def file

Any advices would be apreciated :(

roths commented 4 years ago

i got this error, when i try to inherit IOS WKWebView. Does K/N currently not support this case?

artdfel commented 4 years ago

We already discussed @roths question in the Kotlin Slack, so posting it just for the record. One can inherit from WKWebView like this:

import kotlinx.cinterop.*
import platform.WebKit.*
import platform.Foundation.NSZeroRect

class myWebView : WKWebView(frame = NSZeroRect.readValue(), configuration = WKWebViewConfiguration())
Takhion commented 4 years ago

@SBNTT: Trying to make the same thing using appkit NSViewController to build a desktop app without storyboard but this code lead to a crash [...]

It's a bit hidden, but here's what it says in the [“Discussion”] section of [NSViewController]'s [init(nibName:bundle:)] method reference:

If you pass in nil for nibNameOrNil, [nibName] returns nil and [loadView()] throws an exception; in this case you must set [view] before [view] is invoked, or override [loadView()].

So, one way to work around the issue is:

import platform.AppKit.NSView
import platform.AppKit.NSViewController
import platform.AppKit.NSWindow

@Suppress("FunctionName")
fun NSViewController(view: NSView) =
    object : NSViewController(nibName = null, bundle = null) {
        init {
            this.view = view
        }
    }

fun example(view: NSView): NSWindow =
    NSWindow.windowWithContentViewController(NSViewController(view))

[NSViewController]: https://developer.apple.com/documentation/appkit/nsviewcontroller

[init(nibName:bundle:)]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434481-init

[“Discussion”]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434481-init#discussion

[nibName]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434472-nibname

[loadView()]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434405-loadview

[view]: https://developer.apple.com/documentation/appkit/nsviewcontroller/1434401-view

SvyatoslavScherbina commented 3 years ago

Although this error report is not a bug in Kotlin, it still can be improved. Follow this issue: https://youtrack.jetbrains.com/issue/KT-47992