Additionally, is there any way to know if `TypeRes...
# compiler
f
Additionally, is there any way to know if
TypeResolutionInterceptorExtension
will become non-internal anytime soon? I'd like to be able to use it in my compiler plugin 🙂
đźš« 1
r
No, in contrary we are going to remove it eventually. Could you please explain what is your case?
f
Sure, no problem. I have a
Copy code
abstract class Parent {
    fun action() = apply {
        // do action
    }
}
which then gets extended by a bunch of children, like
Copy code
class Child : Parent() {
}
and I want
child.action()
to return the instance of
Child
Because I know that in the library my plugin is for, that function will always return the type that it was called on
Thinking about it, there would have to be an IrGenerationExtension to create the cast operation, but that wouldn't suffice would it? Wouldn't the type system still not realize it would be of the new type
Child
?
r
I would suggest something like
Copy code
abstract class Parent<T: Parent> {
    open fun action(): T = apply {
        // do action
    }
}

class Child : Parent<Child>() {
 // override action(): Child
}
So in that case you could always inject override member
fun action(): Child
which will be properly compiled into what you exactly need including bridge method.
f
Hmm, that doesn't compile though, it says the reference to
Parent
in the bound for
T
needs a type argument
And it also would mean that all references to
Parent
would now need to have a type specified, even if that is just
*
r
but that wouldn’t suffice would it
If you goal is to make sure that types are correct it should to be done automatically since compiler inserts “implicit cast” wherever types are mismatch.
f
Oh interesting, but how, if possible, could I make it so the compiler knows the returned object is of the same type the function was called on?
r
Ok so am I correct that you need to keep override-chain. I mean
action
in
Child
has to override it from
Parent
?
Oh, I see
f
So, preferably I wouldn't override anything in
Child
, rather, whenever i had an instance of
Child
and called any method from
Parent
on it, it would return the type
Child
instead of
Parent
, because I know that in my library it will always return the exact same instance that it was called with
r
So why I just don’t insert?
Copy code
class Child : Parent() {
  override fun action(): Child { .. }
}
And yes, you could transform your code using
IrGenerationExtension
if you just add cast like this
Copy code
use1(p.action()) -> use1(cast<Parent>(p.action()))
use2(c.action()) -> use2(cast<Child>(c.action()))
f
Oh, like synthetically insert those? I guess that is possible, but a little inconvenient, because
Parent
has a load of methods that would need overriding. Additionally,
Parent
and
Child
are classes in a library, and it was suggested in the 1.4 release notes that libraries shouldn't compile with IR quite yet, but I guess I can require that the library use the IR compiler
what do you mean by
use1
and that code block? I'm slightly confused to the context where that would be used
The optimal situation would be if I could simply make call-sites of those parent functions understand it returns the child's type and cast there, but I can probably live with the synthetic override solution
but in a case like:
Copy code
val child: Child
child = Parent().action()
the code doesn't even make it to the IrGenerationExtension because it fails type inference, saying that the inferred type is
Parent
, not
Child
, so I can't automatically insert a cast, somehow I'd need to tell the compiler the type earlier, and as you said, it might implicitly cast
r
use1/2(..)
is just a meta of any possible usage. In our snippet such usage is assign into
child
So to make long story short as I understood you need something like downcast for invocations of some specific method
Override/class body modification is not good since it’s in external library.
f
Yes precisely. The method doesn't have to be specific per se, rather, its any method on
Parent
that returns the type
Parent
, but I think the general point is the same, yes.
r
I think we need to get level up. What the reason that there is a need to violate type system? I mean they original goal
f
Well, currently a lot of code using the library has assignments that require casts, i.e
Copy code
val myChild = Child().action() as Child
because later on, code often needs to use functions declared specifically in the child,
Copy code
myChild.childAction()
which means the
myChild
variable needs to be of type
Child
. It's not too bad writing it with casts, but there are also a lot of parent functions that are infix, have lambdas, etc. and sometimes the assignment expression become complex, which means we need to wrap the left hand side of the cast in parentheses, just making the code much worse to read
r
I see, thanks. And as I understand it’s close to impossible to fix the Parent’s interface adding required methods there.
f
Do you mean that there probably isn't a good solution available by purely changing the
Parent
class? If so, yeah I thought that might be the case, which is why I started looking into making a compiler plugin in the first place
r
Yes, at this moment at least. I agree that plugin api has to be as flexible as possible but at the same time its ability should be in some reasonable bounds 🙂 Your case probably worth to put some thoughts in it. Using Interceptor API is not safe and we very wish to drop as soon as possible. It was originally “designed” (quotes means that is hack) to capture some additional information from callsite.
f
Gotcha, makes sense. I'll try to keep updated on the matter, thanks so much for your help, and I hope that the API eventually will be able to support a feature like this 🙂
r
You are always welcome 🙂
f
oh by the way, should I file some kind of youtrack issue for this (with this slack thread for context)?
đź‘Ś 1
r
Yeah, it would be great 🙂
f
I've opened the issue here, https://youtrack.jetbrains.com/issue/KT-41267, thanks again for your time
🙏 1