Unexpected type checking recursive problem


#1

The following code makes the compiler complain at the line #4:

class A(code: () -> Unit)
class U {
	val A1 = A({ A2 })
	val A2 = A({ A1 })
}

with the following message:

error: type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly

I’m confused. Nothing seemed could go wrong. That is a simple class instantiation with the lazily evaluated parameter (lambda). The type of a constructor parameter unlikely should affect the inference of the type of the constructor.

Of course, specifying the type of one of the variables eases the compiler. But it looks redundant.

	val A1: A = A({ A2 })
	val A2 = A({ A1 })

Is this a bug or an expected behavior?

PS: There is a new issue created: https://youtrack.jetbrains.com/issue/KT-22287


#2

Well… Suppose you have a function fun A(param: (...) -> ...) = ...

The compiler doesn’t know for sure you’re instantiating class A, I guess.


#3

The code snippet above is self-sufficient. It is what it is. The only fun there is that the compiler can not infer the type of the constructor.


#4

The parameter type of the constructor of A is () -> Unit. The compiler will try to check if this lamba parameter is of that type:

val A1 = A({ A2 })

For it to be able to do that, it needs to know the type of A2. So now the compiler has to check if this lambda parameter is of type () -> Unit:

val A2 = A({ A1 })

And because the compiler needs to know the type of A1 at this point, there is a recursive loop, and the compiler stops with an error.

It is probably possible to write a compiler that can handle this, but I have no idea how much more complex such a compiler would be, and what the compilation performance of such a compiler would be.


#5

I believe you’re right. However, isn’t it weird for the compiler to suppose that the type of a variable, assigned with the constructor of class A, can be anything but A?


#6

I would not call it weird. It is a limitation of the compiler design. The current design checks the whole expression before it knows the type.

It may be possible to write a compiler that ignores some parts of the expression (the lambda parameter in this case). But expressions can be quite complex, so I think ignoring parts of an expression can only be done in a few cases. The added complexity may not be worth it. But I am not a compiler designer, so I could very well be wrong.


#7

If it was impossible to have fun A, that recursive problem would really make no sense.

What I mean by "the compiler doesn’t know you’re instantiating class A" is that the compiler has to discover what A(...) should be, so it does what @jstuyts said.


#8

I’m not a compiler designer too. But it seems as clear as a sunny day that a constructor uniquely defines the type.

In a case like this:

Kate = Girl(sister of Willy)
Willy = Boy(brother of Kate)

it’s almost the same as asking “who is Willy?” when he’s explicitly defined as a Boy.


#9

Well… Maybe in case, there are both constructor A and fun A in the scope, the compiler would struggle to identify what’s A by identifying the type of parameters first. In this scenario, this logic works. However, there’s no option but the constructor. So what else to decide?


#10

You still can always provide explicit type. I think that it is a good policy to provide type explicitely whenever type inference is not obvious. It will not only save compiler work, but would also make code more readable.

I do not think it is redundant in your case. By the way, you can ommit round brackets.


#11

Yes, you’re right, I can omit brackets.

The question is, does this:
val a1: PrettyLongNamedClass = PrettyLongNamedClass(whatever)
look more readable than this:
val a1 = PrettyLongNamedClass(whatever)

And isn’t it obvious that the type of a variable created with an explicit call of a class (T) constructor can’t be anything but its type (T), no matter what parameters it consists of? Except for the case when there’s the same-named function in the scope which returns a different type. (like @KinGamer suggests).


#12

I agree. But compiler obviously can’t infer type of object without first inferring type of arguments (for example, you need it for generics). Turning on inference in one case and turning it of in another could be vary complicated. Do you really use constructs like in your example frequently?


#13

Most likely what you’ve said is what really happens. However, let me suggest an amendment: compiler can’t infer the type of object type parameter (if any) without first inferring types of arguments. But the compiler can univocally infer the type of an object of non-parametrized class by a constructor.
I presume the type inference of an object of a non-parametrized class shouldn’t depend on parameters.
I do not use this feature frequently, possibly because of this small inconvenience I’m talking about.


#14

Please report this to youtrack, if you feel this feature is missing.