4
votes

I'm trying to write a Specs2 test for a class that takes a Squants Time as a parameter. The trick is that both tools define an implicit that adds a method called "seconds" to convert a number into their own representation (a squants.time.Seconds in one case and a org.specs2.time.Duration in the other), and unfortunately the wrong one seems to take precedence.

val a = new MyClass(10 seconds)  // doesn't build because MyClass wants a Time instead of a Duration

To be clear, I could get around this by not relying on implicits to construct the Squants Time, that's not the question.

I rather like implicits so I decided to add an implicit to convert Durations into Times:

implicit def specsTimeToSquantsTime(t: org.specs2.time.Duration): squants.time.Time = squants.time.Seconds(t.toSeconds)

That got it to typecheck, but when I ran the test I got a stack overflow and the stack trace didn't make a great deal of sense, it said that my implicit conversion was calling itself (which wouldn't typecheck, and even if it would it still isn't possible given the above code!):

[error] package.TestConversions$.specsTimeToSquantsTime(TestConversions.scala:9)
[error] package.TestConversions$.specsTimeToSquantsTime(TestConversions.scala:9)
[error] package.TestConversions$.specsTimeToSquantsTime(TestConversions.scala:9)
...

So I have three questions: What's going on here? Is there a better way to do this? Could I manually hide the Int=>Duration implicit?

1
I can't see a toSeconds method in specs Duration, therefore implicit conversion in implicit conversion.kiritsuku
Arg! I'm flipping blind. That's definitely the answer, thanks! (edit: by which I mean if you convert your comment into an answer I'll gladly mark it as such)teryret

1 Answers

3
votes

Method toSeconds is defined inside Time class:

final class Time private (val value: Double) extends Quantity[Time] {    
  ...
  def toSeconds = to(Seconds)
  ...
}

https://github.com/garyKeorkunian/squants/blob/master/src/main/scala/squants/time/Time.scala

So when compiler sees this method invoked on Duration - it's searching for appropriate implicit and it's specsTimeToSquantsTime, which contains Duration.toSeconds in its turn, which needs an implicit convertion from Duration to Time and so on and so forth. So you're getting infinite recursive call in runtime which is fully correct from compiler's side as theoretically you can stop this recursion and there is no general way (see halting problem) to detect that.

There is NoTimeConversions trait you can mix in Specification to avoid implicit conversions:

This trait can be used to deactivate the time conversions (to avoid conflicts with Akka's conversions for example