Search code examples
scalaoverloadingtype-inferenceimplicit-conversionimplicit

Why can't the compiler select the correct String.contains method when using this lambda shorthand?


Say I want to check if a string contains any of the letters in "cory":

def hasCory(input: String): Boolean = {
  val myName = "cory"
  input.exists(myName.contains)
}

The compiler complains with:

error: type mismatch;
found   : CharSequence => Boolean
required: Char => Boolean

Scala provides the Char-accepting method I want in StringOps:

scaladoc showing the contains method signature

But it appears that the compiler cannot see this method unless I change the code to one of:

input.exists(myName.contains(_))
input.exists(c => myName.contains(c))

Instead in the original example it appears to be using Java String's contains method, which indeed does accept a CharSequence:

documentation of Java String.contains method

Is this working as intended? Why can't the compiler see that I want the Char version of contains?


Solution

  • StringOps is an implicit conversion

    @inline implicit def augmentString(x: String): StringOps = new StringOps(x)
    

    And implicit conversion are applicable in three cases only:

    1. If an expression 𝑒 is of type 𝑇, and 𝑇 does not conform to the expression's expected type pt.
    2. In a selection 𝑒.𝑚 with 𝑒 of type 𝑇, if the selector 𝑚 does not denote an accessible member of 𝑇.
    3. In a selection 𝑒.𝑚(args) with 𝑒 of type 𝑇, if the selector 𝑚 denotes some member(s) of 𝑇, but none of these members is applicable to the arguments args.

    When you write myName.contains it's none of the three cases (in particular,
    not the 2nd case because contains is an accessible member of String) so StringOps can't be applied and it's String#contains(CharSequence) and type mismatch error.

    When you write myName.contains(_) or c => myName.contains(c) it's the 3rd case so StringOps can be applied and it's StringOps#contains(Char) after implicit conversion.

    So yes, it's working as intended.