In Arrow 2.0 `NonEmptyList` has `@JvmInline` annot...
# arrow
o
In Arrow 2.0
NonEmptyList
has
@JvmInline
annotation, with this change
mock
libraries can not mock if
NonEmptyList
is used as a function parameter. Example:
Copy code
interface Operation {
  suspend fun execute(items: NonEmptyList<String>): Either<Throwable, Unit>
}

class NonEmptyListTests : ShouldSpec({
  should("mocking") {
    val operation = mockk<Operation> {
      coEvery { execute(any()) } returns Unit.right()
    }

    operation.execute(NonEmptyList("a", listOf("b", "c"))) shouldBe Unit.right()
  }
})
Fails with the exception (less stack trace for brevity):
Copy code
Caused by: java.lang.TypeNotPresentException: Type <unknown> not present
	at net.bytebuddy.description.type.TypeDefinition$Sort.describeOrNull(TypeDefinition.java:295)
	at net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$ForLoadedType$WildcardUpperBoundTypeList.get(TypeDescription.java:4841)
	at net.bytebuddy.description.type.TypeDescription$Generic$OfWildcardType$ForLoadedType$WildcardUpperBoundTypeList.get(TypeDescription.java:4814)
	at net.bytebuddy.matcher.FilterableList$AbstractBase.getOnly(FilterableList.java:141)
The reason I believe Inline Classes are not open to generating proxies from them. The workaround I did was change my codebase into
List
version of the implementation, but there might be people who might get affected.
3
p
We've switched to using Mokkery as it doesn't have as many issues around value classes. That said, we were bitten by this change to a value class as you cannot use a value class in a
lateinit var
m
I've updated to 2.0 and I have a similar issue to the one listed above in an Android project, except for me the issue is with Dagger2. Most likely, it's related to this issue. For me it seems that if any of my injectable classes have a method (public or private) that references an inline class that uses generics, it will fail to compile with an error similar to this:
Copy code
bad class file: ~/path/to/class.kt
    undeclared type variable: A
    Please remove or make sure it appears in the correct subdirectory of the classpath.
I was curious if it would be a reasonable request to have
NonEmptySet
and
NonEmptyList
changed to interfaces and have all factories that generate instances of these return an
inline
implementation? I know this isn't really an Arrow issue but I can't think of any good workarounds to this. I've ended up having to change most references to
NonEmptyCollection
for compilation to work, which is acceptable but I end up needing to cast or change the type of lists or sets more frequently and I feel that my API's aren't as clear.
Either
way, I'm open to `Option`s. 😅 See what I did there? 😂
p
I'm not entirely sure if there's any real benefit to making NonEmptySet and NonEmptyList inline value classes, given that most of the usages of it will likely be via the
List<T>
or
Set<T>
interfaces (or a supertype of), and thus will always be a boxed instance.
a
not really, people use
NonEmptyList
directly quite often, and in that case the inline implementation directly calls the function in the wrapper value, without having to perform additional boxing
👍 1
I'm going to be very honest here, I'm not very comfortable with changing our implementation because of a bug which is open since 2022. This has been biting us in many different places, and in some cases we've been able to work around the issue (like in
Schedule
) in the past it seems that aligning the names of the generic parameters with that of the underlying type makes Dagger happy, so I'm trying that as part of https://github.com/arrow-kt/arrow/pull/3549
@Oğuzhan Soykan which library are you using for mocking? I was under the impression that "Kotlin-first" mocking libraries do support value classes
o
@Alejandro Serrano.Mena It is kotlin first: mockk and I tried with mockito, too, they both give the same error result.
p
I've just checked the following:
Copy code
import io.mockk.mockk
import kotlin.test.Test

@JvmInline
value class Box<E>(private val value: List<E>): List<E> by value

interface Example {
    fun handle(box: Box<String>)
}

class ValueClassMockksTest {
    @Test
    fun `can mockk it`() {
        mockk<Example>()
    }
}
and #C9EJFT6DB still doesn't like it 🤔 Then again, we've encountered numerous edge cases with value classes and mockk...
@Alejandro Serrano.Mena I don't think the implementation need change, only I'm uncertain if there is much, if any, benefit to having NonEmptyList (and Set) being
@JvmInline value class
over just having them being a normal class. There are (minor) drawbacks to value classes (such as not being able to use one in a
lateinit var
). I'm concerned that every time a
NonEmptyList
or
NonEmptySet
is used as one of it's super-types it will be boxed afresh. For example,
nel.forEach { println(it) }
will invoke
NonEmptyList.box-impl
and cast it to
Iterable
(according to the bytecode)
a
@phldavies the change in type parameter is for Dagger, I don't think MockK would be affected
right now changing it back to a non-value class would be a breaking change, which we don't want to do for some time about the benefits of having a value class, we took the decision based on the fact that with delegation + inlining, most of the time you're accessing the underlying implementing (without any boxing). The other side of the coin is that for extension functions like
forEach
some overhead comes from the boxing (I'm not sure why, though, since
NonEmptyList
is already an
Iterable
)
p
I believe it boxes it due to needing a concrete implementation of the interface (
Iterable
in this case) - although
NonEmptyList
implements it via delegation, the compiler can't assume the value class doesn't override (or wouldn't in the future) any methods from that interface, so it can't simply leave it unboxed.
m
Alejandro, can I try https://github.com/arrow-kt/arrow/pull/3549 using a snapshot or something?
a
it took a while, but
2.0.1-alpha.1
with the change is now in Maven Central. Please tell me if this helps with your problem 🙏
m
@Alejandro Serrano.Mena I appreciate your efforts. Unfortunately, it did not work. 😞 I received a similar error to the one before:
Copy code
> Task :project:module:compileDebugJavaWithJavac FAILED
/project/module/build/generated/ksp/debug/java/path/to/class/Implementation_Factory.java:27: error: cannot access Implementation
public final class Implementation_Factory implements Factory<Implementation> {
                                                                    ^
  bad class file: /project/module/build/tmp/kotlin-classes/debug/path/to/class/Implementation.class
    undeclared type variable: E
    Please remove or make sure it appears in the correct subdirectory of the classpath.
1 error
And the class basically looks like this:
Copy code
import arrow.core.Nel
import arrow.core.toNonEmptyListOrNull
import javax.inject.Inject

class Implementation @Inject constructor() {
  private fun doSomething(nel: Nel<*>) = Unit
}
Would it help if I provided a simple project that replicates this issue for you to experiment with? And can I do anything else to help try to resolve this issue?
a
having a project to replicate would be awesome, yes 🙂 . I don't use these tools myself 😅
m
Understood. I'll see what I can do.
@Alejandro Serrano.Mena I've created a sample project here for you to experiment with. I hope it's helpful. Let me know if you need any additional information or assistance.
thank you color 1
o
https://quarkus.io/ framework also fails because of this, and the alpha version makes it even worse, because it breaks
Raise<...>.f()
, which I cannot fix by changing parameter types in my own code.
a
how does the alpha version makes it worse compared to 2.0.0?
o
Because in 2.0.0
NonEmptyList
can be replaced with
List
to make the code compile. But in 2.0.1-alpha that same error starts manifesting itself in
Raise
, which cannot be replaced by a "different Raise" - there is no replacement. So that makes it worse.
Copy code
bad class file: /Users/ondra/.gradle/caches/modules-2/files-2.1/io.arrow-kt/arrow-core-jvm/2.0.1-alpha.1/73049529baecebbf3f002a5e1f27ea43298e3e5b/arrow-core-jvm-2.0.1-alpha.1.jar(/arrow/core/raise/Raise.class)
    undeclared type variable: E
122 Views