Constructor overloading

Hi

I just came across that the code below compiles. Why is this legal?

in foo/Foo.kt:
package foo
data class Something(val a: String)

in bar/Bar.kt:
package bar
data class Something(val i: Int)

in Baz.kt:
import foo.*
import bar.Something

// turns out that the line below creates a foo.Something, rather than a compilation error:
val something = Something(“sheep”)

1 Like

Why is this legal?

Because you imported foo.Something in your file. Kotlin (as do most other languages) allow classes with the same simple name (class name) to exist as long as their fully qualified name (package + class) is unique. You can make the code example fail by removing the import statement to the wrong Something class.


To be pedantic: this isn’t constructor overloading. Both foo.Somenthing and bar.Something are distinct classes.

1 Like

Thanks for your answer. However, it’s not that I do not understand why it compiles and does what it does. I should have phrased my question differently. What I meant is that I think it should not be legal, as I find the behaviour quite surprising. With the explicit import of bar.Something, I’d think that I’m telling the compiler that any use of Something means that one.

In my actual code, I need both types of Something. What I would expect is that the code above fails to compile, and that I should either fully qualify the constructor of foo.Something, or import it with “as FooSomething” and then construct it with that.

The compiler will complain about two classes with the same name being imported: Conflicting import, imported name 'Something' is ambiguous. Do you have a scenario where this doesn’t happen?

To elaborate:

If I were to add a secondary constructor to bar.Something() that takes a String, my (unchanged) Something(“sheep”) suddenly - silently - creates a different beast.

I can do:

import foo.Something as FooSomething
import bar.Something

then I can do:
val something1 = FooSomthing(“sheep”) // constructs a foo.Something
val something2 = Something(3) // constructs a bar.Something

Right, but this isn’t confusing at all anymore.

I tried mimicking your original code snippet, and indeed it seems that the star import does allow this kind of conflicting import. I’d chalk that down to a bug, perhaps an oversight.

1 Like

Well, I think it is confusing if you don’t know that Something is also imported with the star. Other than that, we seem to agree.

Just to summarize and make sure I’m on the same page:

The issue is that, although Kotlin does not allow classes of the same name, constructors (or any other method) may have the same name and take different parameters. This applies even when the methods are in different packages. Adding an import with a conflicting method could potentially break your code in a compiler valid way.

Since constructors are a lot closer to normal methods in Kotlin, I can see this as possible being acceptable. Did you try explicitly typing your variables? I’m sure it would be a compiler error if you said val something1: Foo = Something("...")

I think the solution to keeping type safety in a potentially confusing situation is to explicitly type things. Allowing this use of constructors is no more risky than allowing it for normal methods.

I would say that this is a bug (report it on the bug tracker). If there is a specific import, it should ignore wildcard imports. If there are multiple specific imports with the same name (without alias) that should be an error. If there are multiple wildcard imports for the same name, this should be an error. In other words, multiple valid names on the same resolving order should fail as an ambiguity compilation error.

1 Like

Looking at it again, does look like something is odd. My original solution of putting val something1: bar.Something = Something("...") does produce a compiler error–but I missed the importance of specific import overriding star imports.

The reason an error is expected is that import bar.Something, which does not allow calling Something("String"), could be expected block the import foo.* star import’s Something and continue to show an error.

So the final resolution is chosen in order of:

  1. single valid function/constructor ← this is the cause of the behavior. It’s done regardless of imports.
  2. specifically imported function/constructor
  3. star-imports function/constructor
  4. function with the more specific type (but ERROR on multiple classes)

If this behavior is bug, the bug would hold true for both functions and constructors. I can see why this feels risky. You might be using a getProperty() function throughout a file. You hope all incorrect usages would be caught since you specifically imported the housing.getProperty function but one wrong star import and you’d accidentally call the os.env.getProperty function instead, potentially without knowing.

Any thoughts on reasonable ways to solve this? There’s always some risk you call the wrong function. Star imports have always carried the risk of them adding something that breaks your code–even specified imports blocked usages of potential mistakes based on similar names that risk will always exist.
Any solution that throws the expected error would be a breaking change. I wonder if warning and suggested action to add import aliases would be sufficient.

1 Like