Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with methods of multiple param lists where one is vargs #63

Open
NPCRUS opened this issue Jul 29, 2024 · 3 comments
Open

Issue with methods of multiple param lists where one is vargs #63

NPCRUS opened this issue Jul 29, 2024 · 3 comments

Comments

@NPCRUS
Copy link
Contributor

NPCRUS commented Jul 29, 2024

Hello again @fmonniot, I've bumped into another issue trying to mock such service:

trait Service {
  def method(a: Int*)(b: String): Unit
}

compile output with debug:

Mocking type Service
Generated code:
Tree Code: {
  class ServiceMock extends java.lang.Object with Service with eu.monniot.scala3mock.context.Mock {
    private val mocks: scala.collection.immutable.Map[scala.Predef.String, eu.monniot.scala3mock.functions.MockFunction] = scala.Predef.Map.from[java.lang.String, eu.monniot.scala3mock.functions.MockFunction](new scala.Tuple2[java.lang.String, eu.monniot.scala3mock.functions.MockFunction]("method", new eu.monniot.scala3mock.functions.MockFunction2[_*, scala.Predef.String, scala.Unit](TypeCheckTest.currentContext, "<TypeCheckTest.scala#L10> Service.method")(eu.monniot.scala3mock.Default.given_Default_Unit)))
    def accessMockFunction(name: java.lang.String): eu.monniot.scala3mock.functions.MockFunction = ServiceMock.this.mocks.apply(name)
    override def method(a: _*)(b: scala.Predef.String): scala.Unit = ServiceMock.this.mocks.apply("method").asInstanceOf[eu.monniot.scala3mock.functions.MockFunction2[_*, scala.Predef.String, scala.Unit]].apply(a, b)
  }

  (new ServiceMock(): Service & eu.monniot.scala3mock.context.Mock)
}
[error] -- [E007] Type Mismatch Error: /Users/nikitaglushchenko/Desktop/projects/untitled/src/main/scala/TypeCheckTest.scala:11:56 
[error] 11 |  private val service = mockWithDebuggingOutput[Service]
[error]    |                                                        ^
[error]    |                                                    Found:    (a : Int*)
[error]    |                                                    Required: Int
[error]    |----------------------------------------------------------------------------
[error]    |Inline stack trace
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from TypeCheckTest.scala:11
[error]     ----------------------------------------------------------------------------
[error]    |
[error]    | longer explanation available when compiling with `-explain`
[error] one error found

What might be potentially interesting for investigation is that such service:

trait Service {
  def method(a: Int*): Unit
}

compiles fine:

Tree Code: {
  class ServiceMock extends java.lang.Object with Service with eu.monniot.scala3mock.context.Mock {
    private val mocks: scala.collection.immutable.Map[scala.Predef.String, eu.monniot.scala3mock.functions.MockFunction] = scala.Predef.Map.from[java.lang.String, eu.monniot.scala3mock.functions.MockFunction](new scala.Tuple2[java.lang.String, eu.monniot.scala3mock.functions.MockFunction]("method", new eu.monniot.scala3mock.functions.MockFunction1[_*, scala.Unit](TypeCheckTest.currentContext, "<TypeCheckTest.scala#L10> Service.method")(eu.monniot.scala3mock.Default.given_Default_Unit)))
    def accessMockFunction(name: java.lang.String): eu.monniot.scala3mock.functions.MockFunction = ServiceMock.this.mocks.apply(name)
    override def method(a: _*): scala.Unit = ServiceMock.this.mocks.apply("method").asInstanceOf[eu.monniot.scala3mock.functions.MockFunction1[_*, scala.Unit]].apply(a)
  }

  (new ServiceMock(): Service & eu.monniot.scala3mock.context.Mock)
}
@fmonniot
Copy link
Owner

fmonniot commented Jul 30, 2024

Interesting, I'm assuming the issue is because Scala3Mock actually flatten all parameter lists into one under the hood (the val mocks) and that doesn't play nice when the vararg parameter is not the last one.

Not entirely sure if I'll have time to look at it this week, I should have some time over the weekend though.

@fmonniot
Copy link
Owner

fmonniot commented Aug 5, 2024

I looked into some more today. Unfortunately I'm not entirely sure how to go around that one. The mocking portion is relatively easy, we can introspect the method definition and if it's a vararg have special treatment for it (maybe converting it to a Seq). Where it become more complex is in the when handling.

What is actually causing issue here is not the repeated parameters, it's the multiple parameter lists (we have the same issue if the repeated parameters are in the second parameter list). Because when using multiple parameter list, we have to build an anonymous function. Something like the following test:

when(m.curried(_: Int)(_: Double)).expects(1, 2.1).returns("ok")

But Scala doesn't have a way to do so with repeated parameters, so the following is not valid Scala

when(m.curried(_: Int*)(_: Double)).expects(1, 2.1).returns("ok")

So now we have a weird problem. How do we let scala3mock knows that the individually passed are actually part of a repeated parameters? Here we can't really cheat like we do for non-curried functions (where the Seq is infered by the compiler) because we can't explicitely type that sequence. We also can't detect at compile type if one of the parameter is a vararg and transform the MockFunction into accepting a sequence because the type of the mockfunction must be fully typed before application.

So I'm a bit in a bind and am not entirely sure how to move forward on that issue unfortunately.

@NPCRUS
Copy link
Contributor Author

NPCRUS commented Aug 5, 2024

Additions to investigation:

  1. original scalamock library has similar issue: Issues mocking curried method with variable argument parameter paulbutcher/ScalaMock#131
  2. our workaround:
    what I'm doing in scalamock in scala2 is actually wildcard matching these repeated parameters:
    having method: def repeatedParamCurried(x: Int)(ys: String*): String
(repeatedParamCurried(_: Int)(_: String))
  .expects(2, *)
  .returning("hello")

compiles and works, in an interesting way if I do the same in scalamock3's MockSuite.scala:

when(repeatedParamCurried(_: Int)(_: String))
  .expects(2, *)
  .returning("hello")

then I found out that compilation fails only when repeated param stays in the first place:

def method(b: String)(a: Int*): Int // compiles
def method2(a: Int*)(b: String): String // doesn't compile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants