scalabcryptscala-implicits

Scala Implicit class member is not accessible in object


I am using Scala bCrypt wrapper for encrypting user password, this wrapper provides an implicit class.

package object bcrypt {

  implicit class Password(val pswrd: String) extends AnyVal {
    def bcrypt: String = B.hashpw(pswrd, BCrypt.gensalt())

    def bcrypt(rounds: Int): String = B.hashpw(pswrd, BCrypt.gensalt(rounds))

    def bcrypt(salt: String): String = B.hashpw(pswrd, salt)

    def isBcrypted(hash: String): Boolean = B.checkpw(pswrd, hash)
  }

  def generateSalt: String = B.gensalt()
}

But I am facing a strange problem, whenever I am using this Implicit converision in class it workes fine but converiosn doesnt work with object or case classes.

scala> import com.github.t3hnar.bcrypt._
import com.github.t3hnar.bcrypt._

scala> class Password(secret: String) {
     |   def validate(userSecret: String): Boolean = userSecret.isBcrypted(secret)
     | 
     |   override def toString = secret
     | }
defined class Password

scala> object Password {
     |   def apply(secret: String): Password = new Password(secret)
     | 
     |   def getEncrypted(secret: String) = new Password(secret.bcrypt)
     | }
<console>:18: error: value bcrypt is not a member of String
         def getEncrypted(secret: String) = new Password(secret.bcrypt)
                                                                ^

scala> 

I am not sure what I am doing wrong here.


Solution

  • Any stable identifier shadows imported implicit identifiers. Things that can shadow implicits include val, def, object and the generated companion object of case class. Simple classes and types don't create identifiers and thus don't shadow imported implicit identifiers.

    implicit class Password is just syntactic sugar for a class Password and an implicit def Password, and so an identifier in your code named Password would shadow that implicit def.

    So while this code compiles OK:

    object Foo {
      import bcrypt._
      class Password()
      "123".bcrypt
    }
    

    All of the following snippets will fail to compile:

    object Foo2 {
      import bcrypt._
      val Password = 1
      "123".bcrypt
    }
    
    object Foo3 {
      import bcrypt._
      def Password() = 1
      "123".bcrypt
    }
    
    object Foo4 {
      import bcrypt._
      case class Password()
      "123".bcrypt
    }
    
    object Foo5 {
      import bcrypt._
      object Password
      "123".bcrypt
    }
    

    The solution in your case is simple: rename the implicit class to something else, that would be unlikely to clash with other identifiers. For example, implicit class PasswordExtensions.

    If you can't rename the implicit class in the original code, you can import it under a different name: import com.github.t3hnar.bcrypt.{Password => PasswordExtensions, _}