If you take a look at the bytecode there are actually three constructors generated, as Roland already pointed out.
public Foo(@NotNull String bar, boolean baz) { ... }
public Foo(String var1, boolean var2, int var3, DefaultConstructorMarker var4) { ... }
public Foo(@NotNull String bar) { ... }
So, there are no duplicate constructor signatures. Now one might ask how Kotlin chooses which overload to take judging from the call-site only.
The overall rationale is that the most specific function/constructor will be chosen from the overload candidates.
This is what the Kotlin language specification says about it:
For each candidate, we count the number of default parameters not specified in the call (i.e., the number of parameters for which we use
the default value);
The candidate with the least number of non-specified default parameters is a more specific candidate;
I know that you intended this to only be an example, but if something like this happens in a real-world situation one should avoid it like the Kotlin Language Documentation (page 76) states:
If you have an object with multiple overloaded constructors that don't
call different superclass constructors and can't be reduced to a
single constructor with default argument values, prefer to replace the
overloaded constructors with factory functions.
class Foo2(val bar: String, val baz: Boolean = true) {
companion object {
fun factoryCreate(s: String) = Foo2(s, false)
}
}
In this case it will always be clear right away (without thinking about the overloading resolution rules) what baz will be after creation.
bar, it just complicates the example. Interesting. You could still callFoo("", true). It seems it would be useful if it warned that the secondary constructor is meaningless because of the default value.Foo(String, Boolean), a syntheticFoo(String, DefaultConstructorMarker)which uses the default value andFoo(String)... so these are 3 distinct constructors... maybe that's coming... on the other hand I don't know when I ever had such a constellation... and also cost/benefit ratio is probably not so good...bar. In the second constructor, you are always delegating to the primary constructor and you are already providing it with a default value which isfalse. Reading from decompiled code, it appears that the compiler generated a synthetic method and where it is providing all signatures with their values. So it does not matter which constructor you call, in the end, all conditions are being met.