Skip to content

Commit e970df4

Browse files
authored
Merge pull request #9 from fmonniot/fix-issue-4
Add support for by-name parameters
2 parents 8c1adba + 7094d82 commit e970df4

File tree

4 files changed

+36
-17
lines changed

4 files changed

+36
-17
lines changed

core/src/main/scala/eu/monniot/scala3mock/macros/MockImpl.scala

+19-7
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,28 @@ private class MockImpl[T](ctx: Expr[MockContext], debug: Boolean)(using
7070
)
7171

7272
case DefDef(name, params, returnTpt, _) =>
73-
val args = params.flatMap {
74-
case TermParamClause(vals) => vals
75-
case TypeParamClause(_) => List.empty
76-
}
77-
78-
val types = args.map(_.tpt.tpe) :+ returnTpt.tpe
73+
val args = params
74+
.flatMap {
75+
case TermParamClause(vals) => vals.map(_.tpt.tpe)
76+
case TypeParamClause(_) => List.empty
77+
}
78+
.map {
79+
case ByNameType(inner) =>
80+
// By-name types are interesting because internally they have a type-representation,
81+
// but that is not something that we can actually do in "official" Scala. Not removing
82+
// this node means that the by-name value isn't being evaluated and so when we compare
83+
// the expectation (an evaluated value) vs the actual value (a lambda, as it's not evaluated)
84+
// we ends up with some mismatch. By removing this type component, we are effectively saying to
85+
// the compiler that we want to evaluate the argument before passing it to our MockFunction
86+
inner
87+
case otherwise => otherwise
88+
}
7989

8090
(
8191
Symbol.requiredClass(
8292
s"eu.monniot.scala3mock.functions.MockFunction${args.length}"
8393
),
84-
types
94+
args :+ returnTpt.tpe
8595
)
8696

8797
case tree =>
@@ -361,6 +371,8 @@ private class MockImpl[T](ctx: Expr[MockContext], debug: Boolean)(using
361371
None // We can ignore the rest as we are only looking at arguments here
362372
}
363373

374+
// Build the Map access. In scala that would look something like
375+
// MockClassName.this.mocks.asInstanceOf[MockFunctionX].apply(x)
364376
val mockFnType =
365377
AppliedType(mockFunctionClsSym.typeRef, mockFnTypeArgs)
366378

core/src/main/scala/eu/monniot/scala3mock/macros/WhenImpl.scala

+8-5
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,13 @@ private[scala3mock] object WhenImpl:
2020
val mf = ${createMockFunction(f)}.asInstanceOf[MockFunction1[T1, R]]
2121

2222
// used to debug what it is being infered
23-
//println(s"selected MockFunction1 = $mf")
24-
//println(s"T1 = " + ${Expr(Type.show[T1])})
25-
//println(s"R = " + ${Expr(Type.show[R])})
26-
23+
/*
24+
println(s"""|selected MockFunction1 = $mf
25+
|T1 = ${${Expr(Type.show[T1])}}
26+
|T1.repr = ${${Expr(TypeRepr.of[T1].toString())}}
27+
|R = ${${Expr(Type.show[R])}}
28+
|""".stripMargin)
29+
*/
2730
mf
2831
}
2932

@@ -127,7 +130,7 @@ private[scala3mock] object WhenImpl:
127130
// If there are no overload, let's use the method name as the mock key. Otherwise
128131
// append the index of the overload. Using the same sort here and in the mock
129132
// declaration is important for the indices to match.
130-
if overload.length == 1 then name
133+
if overload.length < 2 then name
131134
else {
132135
val idx = overload.indexWhere(_.signature == signature)
133136

core/src/test/scala/eu/monniot/scala3mock/mock/MockSuite.scala

+8-4
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,14 @@ class MockSuite extends munit.FunSuite with MockFunctions {
7777
when(m.repeatedParam _).expects(0, Seq.empty).returning("hello")
7878
assertEquals(m.repeatedParam(0), "hello")
7979

80-
// TODO Not supported yet
81-
// https://github.com/fmonniot/scala3mock/issues/4
82-
// when(m.byNameParam).expects(3).returns("ok")
83-
// assertEquals(m.byNameParam(3), "ok")
80+
// Partially supported. Issue with type inference still. See https://github.com/fmonniot/scala3mock/issues/4
81+
// Interestingly enough here if we do not force the types, the compiler will happily
82+
// infer a type `((=> Int) => String)` which doesn't matches any of our `when` declarations.
83+
// This can also be fixed by having the when/WhenImpl macros takes a by-name parameter. That
84+
// second solution isn't very practical for 3+ matchers as it would explode the number of overload
85+
// required to match all possible cases.
86+
when[Int, String](m.byNameParam).expects(3).returns("ok")
87+
assertEquals(m.byNameParam(3), "ok")
8488

8589
// implicit parameters
8690
given y: Double = 1.23

docs/user-guide/installation.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ title: Installation
2121
- No cross compilation with Scala2
2222
- Support for ScalaTest's `path.FunSpec` removed (help welcome)
2323
- Support for inner types ([help welcome](https://github.com/fmonniot/scala3mock/issues/3))
24-
- Support for by-name parameters ([help welcome](https://github.com/fmonniot/scala3mock/issues/4))
24+
- By-name parameters ([help welcome](https://github.com/fmonniot/scala3mock/issues/4)) currently have issues with type inference, but work fine when fully specifiying types when defining the expectations (eg. `when[String, Int](mock.function).expects("val").returns(1)`)

0 commit comments

Comments
 (0)