DISCLAIMER: I'm not an expert on this, so at some level this is an educated guess, but I'm going to take a stab at this.
Let's take a look at what the compiler might (again, I don't actually know how it works internally) be thinking as it examines those lines. We'll start with something that works.
val s = List("ab").toSet
The compiler looks at that and says, OK, I've got a List[String] there, and the code says to call toSet on that. toSet is defined as toSet[B >: A], so I can think of that as toSet[B >: String]. All right, so s is a Set of some type B that is a supertype of String. OK, I've reached the end of the expression and I should make B the most specific type I can that encompasses everything. So B is String, and s is a Set[String].
Next up, let's look at let's look at what doesn't work.
List("ab").toSet.head.head
The compiler starts out the same way. I've got a List[String] and I'm calling toSet. That's defined as toSet[B >: A] so that means I'm working with a Set of some supertype of String. The expression then says to grab the head of the set. All right, I can do that. I don't know what type I can promise, though. I just know it's a supertype of String. What's he want next? Oh...he wants to call head on that...Bummer. I don't know if B has a head method.
Now, let's look at something else that works.
(List("ab").toSet + "abc").head.head
Compiler again starts the same way. I've got List[String] and I'm calling toSet[B >: String]. OK, so I'm working with a Set of Bs. So what's next. OK, he wants to call + on that, and he's calling that with something that I know is a String. Set is invariant, so if it's a set that can + a String, then my B I'm working with must be a String. Nailed it. I've got a Set[String]. Now he wants the head of that Set. Fantastic. Here's your String. Now he wants the head of that. Gotcha. Here's your Char.
Note that the following won't work.
(List("ab").toSet + "abc".asInstanceOf[Any]).head.head
The compiler follows the same path as the previous example, but discovers that you're trying to + an Any, so you end up with a Set[Any] instead, and the final head call fails.
Slight tangent. I'm not entirely sure why toSet is defined as toSet[B >: A], but I suspect that it has something to do with the fact that Sets are invariant. toList, toStream and toIterator are all defined with [A], and Lists, Streams, and Iterators are all covariant. toBuffer and toSet are both defined as [B >: A], and Buffer and Set are both invariant.
So great. How do we tell it what we really want? Our problems stem from the declaration of toSet[B >: A]. If it were toSet[A], we'd be home free. Fortunately, there's also a method called to[Col_]]: Col[A]. Which means that it returns a collection of the original type, but of a different collection type. That sounds funny, but basically it means that if you call
List("ab").to[Vector]
you get a Vector[String]. And, critically, if you call
List("ab").to[Set]
you get a Set[String] right away. So what you actually want to call in your example is
List("ab").to[Set].head.head