fmonniot / scala3mock

Mocking framework for Scala 3
https://francois.monniot.eu/scala3mock
Other
19 stars 1 forks source link

parameterized trait is indirectly implemented #48

Open voidcontext opened 3 weeks ago

voidcontext commented 3 weeks ago

I am not sure if this is a bug or I am not doing something right, but the following code (scastie)

import eu.monniot.scala3mock.context.MockContext
import eu.monniot.scala3mock.macros.mock
import cats.Applicative
import cats.effect.IO

trait Foo[F[_]: Applicative]

trait Bar[F[_]: Applicative] extends Foo[F]

def barMock(using MockContext): Bar[IO] = mock[Bar[IO]]

gives an error:

parameterized trait Foo is indirectly implemented,
needs to be implemented directly so that arguments can be passed

Is there a workaround for this?

fmonniot commented 3 weeks ago

That's definitively a bug! When using mockWithDebuggingOutput instead of mock we can see that the generated code is as follow:

class BarMock
    extends java.lang.Object
    with Bar[IO]()(null)
    with eu.monniot.scala3mock.context.Mock:

Notice how the mock class extends Object instead of Bar directly. The bug is in the detection of trait vs class, the library currently doesn't make the distinction between a trait with parameters and one without. Let's see if I can come up with a fix in the next few days, I'm not entirely sure how complex this part of the Quotes API is.

fmonniot commented 3 weeks ago

Oh no, it's actually more complex than that. Here is what the expanded AST of a class that extends Bar looks like:

class Test()
    extends Foo[scala.List]()(fixtures.Applicative.given_Applicative_List)
    with Bar[scala.List]()(fixtures.Applicative.given_Applicative_List)

So apparently for trait with parameters we will have to add it to the list of extended class as well. That definitively make it a bit more complicated to fix.

fmonniot commented 3 weeks ago

That turned out to be a really fun one. I think I got something in https://github.com/fmonniot/scala3mock/pull/49, will try to get it done over the weekend.

voidcontext commented 3 weeks ago

That turned out to be a really fun one. I think I got something in #49, will try to get it done over the weekend.

Hi @fmonniot, thank you for the quick reply! And thank you for this library! It is amazing, and very useful!

fmonniot commented 3 weeks ago

The PR fixing this issue got released as a snapshot with version 0.6.0+30-40a82d66-SNAPSHOT. If you could let me know if that fix the issue in your code base that would be great. If I don't hear anything from you, I'll probably tag 0.6.1 sometime next week.

voidcontext commented 3 weeks ago

Hm, it seems the non simplified, real world code still doesn't compile, and it might be because the "root" trait (Foo in the example), actually has 2 not just one type parameters.

voidcontext commented 3 weeks ago

The error message is "Found Null, required OtherTypeClass[...]"

fmonniot commented 3 weeks ago

Interesting, would you be able to share a small reproducer? My immediate thinking is that scala3mock's behavior of passing null to all parameters is not pleasing the compiler in the case of context bounds somehow. Unfortunately I'm not entirely sure why.

fmonniot commented 2 weeks ago

FYI I have released version 0.6.1 with the parameterized trait fix included. I also tried modifying the tests to add a second parameter type:

--- a/core/src/test/scala/fixtures/ContextBoundInheritance.scala
+++ b/core/src/test/scala/fixtures/ContextBoundInheritance.scala
@@ -3,10 +3,10 @@ package fixtures
 import eu.monniot.scala3mock.context.MockContext
 import eu.monniot.scala3mock.macros.mock

-trait ContextBoundInheritance[F[_]: Applicative]
+trait ContextBoundInheritance[F[_]: Applicative, G[_]: Applicative]

 trait ContextBoundInheritanceChild[F[_]: App2]
-    extends ContextBoundInheritance[F]
+    extends ContextBoundInheritance[F, F]

 trait App2[G[_]] extends Applicative[G]
 object App2 {

but couldn't reproduce the error you mentionned.