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

Matching an AnyVal with private constructor #391

Open
Krever opened this issue Jul 26, 2021 · 8 comments
Open

Matching an AnyVal with private constructor #391

Krever opened this issue Jul 26, 2021 · 8 comments

Comments

@Krever
Copy link
Contributor

Krever commented Jul 26, 2021

I'm not sure if it's limited to AnyVals or applies to any class with a private constructor, but it seems to be impossible to match arguments of such type.

Pseudo-example:

final case class CountryCode private (value: String) extends AnyVal
object CountryCode extends LazyLogging {
  def apply(code: String): Either[String, CountryCode] = { ... }
  }
}

foo.bar(any[CountryCode]).returns(baz)

fails compilations with

constructor CountryCode in class CountryCode cannot be accessed in <$anon: org.mockito.matchers.DefaultValueProvider[CountryCode]> from <$anon: org.mockito.matchers.DefaultValueProvider[CountryCode]>
        .bar(any[CountryCode])

I haven't found a workaround for now

@ultrasecreth
Copy link
Member

Scala version? (iirc on each version I use a different hack impl 😂 )

@Krever
Copy link
Contributor Author

Krever commented Jul 26, 2021

2.13.4

@ultrasecreth
Copy link
Member

Well, here your code doesn't make it any easy, as there's no way for mockito to know how to construct a "default" value for it (value classes need a default value cause a reference to a value class can't ever be null)

Luckily the design of mockito-scala allows you to circumbent this (yay to past me!!) XD

So there's this type class DefaultValueProvider that's failing for you, the default impl is a macro that'll try to construct a "base" or "empty" instance of your type, which in your case is not only not possible given the private constructor, but also cause the apply method returns a different type Either (is not something the macro attempts at the moment, but even if it did so, it wouldn't work here).

In any case, the solution is pretty simple, provide an implicit that knows how to build a default value and that's it, something along these lines.

final case class CountryCode private (value: String) extends AnyVal
object CountryCode extends LazyLogging {
  def apply(code: String): Either[String, CountryCode] = { ... }
  }
}

implicit val defaultValueProvider: DefaultValueProvider[CountryCode] =
      new DefaultValueProvider[CountryCode] {
        override def default: CountryCode = CountryCode("").value
      }

foo.bar(any[CountryCode]).returns(baz)

Let me know how it goes :)

@Krever
Copy link
Contributor Author

Krever commented Jul 27, 2021

Oh wow, that's pretty neat and it worked!
One unfortunate consequence is that in practice my bar had multiple arguments and because of how type inference works,
I had to explicitly provide type params to all wildcards (bar(*[CountryCode], *[Thing1], *[Thing2] and so on). Luckily for me, I had this exact problem in one helper function, but if it was distributed more widely, it would be quite a problem.

One "solution" I see is to use a different type, like DefaultValueProviderException (as in exception from the rule), that would be detected by the default impl and used for types for which it is defined. But this would be rather complicated, so maybe there is a simpler way?

On top of that, do you have control over original error message so it could point to the solution?

@ultrasecreth
Copy link
Member

Were all the params value classes? afaik, you only need to provide the types for value classes.

@Krever
Copy link
Contributor Author

Krever commented Jul 29, 2021

Nope, only a single one but parameters become necessary because once I put DefaultValueProvider[T] in scope, type inference tries to use it everywhere where type param is not provided (infers the type param by the available implicit instance I think).

@ultrasecreth
Copy link
Member

Ohh, you can also make it not-implicit, it is just a param on the any method, so you can pass it manually too

@Krever
Copy link
Contributor Author

Krever commented Jul 29, 2021

Right! I haven't thought of that :) Thanks.

You can keep this issue open if you'd like to improve the error message, otherwise, feel free to close.

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