Skip to content

asInstanceOf causes type mismatch error when used in method argument position #12739

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

Closed
japgolly opened this issue Jun 8, 2021 · 7 comments · Fixed by #13260
Closed

asInstanceOf causes type mismatch error when used in method argument position #12739

japgolly opened this issue Jun 8, 2021 · 7 comments · Fixed by #13260
Assignees
Milestone

Comments

@japgolly
Copy link
Contributor

japgolly commented Jun 8, 2021

Compiler version

3.0.0

Minimized code

object X {

  class CA[A]
  type C = CA[_]
  val c: C = ???
  def f[A](r: CA[A]) = ()

  // works
  f(c)

  // works
  val x = c.asInstanceOf[C]
  f(x)

  // error
  f(c.asInstanceOf[C])

  // works
  f(c.asInstanceOf[c.type])
}

Output

[error] -- [E007] Type Mismatch Error: a.scala:16:18 
[error] 16 |  f(c.asInstanceOf[C])
[error]    |    ^^^^^^^^^^^^^^^^^
[error]    |    Found:    X.C
[error]    |    Required: X.CA[A]
[error]    |
[error]    |    where:    A is a type variable
[error] one error found

Expectation

No error.

@bishabosha bishabosha changed the title Position of asInstanceOf causes crash asInstanceOf causes type mismatch error when used inline with method argument Jun 8, 2021
@bishabosha bishabosha changed the title asInstanceOf causes type mismatch error when used inline with method argument asInstanceOf causes type mismatch error when used in method argument position Jun 8, 2021
@odersky
Copy link
Contributor

odersky commented Jun 8, 2021

This has to do with the fact that c.asInstanceOf[C] is an expression, not a value.

C[?] is not compatible with any C[A]. If the argument is a singleton value c, an exception is made and we treat
C[?] as "the unknown type instance of C that is the type of c". This alone makes me cringe but we have to do it
since Java also does this sort of "capture conversion". But for expressions as arguments this is unsound in general.

@odersky odersky closed this as completed Jun 8, 2021
@dwijnand
Copy link
Member

dwijnand commented Jun 8, 2021

C[?] is not compatible with any C[A].

I don't quite understand this, as I would expect it to be compatible if A := ?, i.e. that unnamed type that the type alias C is using. I think I would have a better chance at understanding the unsoundness you mention if you (or someone) could mock up an example. I don't see how the 3rd example shouldn't work any different than the 2nd, for instance.

Btw, in Scala 2 the 4th example is an error while the 3rd works... 😄

@japgolly
Copy link
Contributor Author

This isn't the first time I've reported simple substitution not holding (see here and here). Issues like this are 100% going to be considered Scala 3 puzzlers, which is such a shame after such great work was done to close out so many Scala 2 puzzlers and quirks.

@liufengyun
Copy link
Contributor

This is a bug in the compiler, as the following works:

object O {

  class CA[A](var x: A)
  type C = CA[_]

  val c: CA[_] = CA(5)
  def f[A](r: CA[A]): A = r.x

  def g(): CA[_] = CA("hello")

  f(g())
  f(c.asInstanceOf[CA[_]])
}

We have two places where capture conversion kicks in. One is in typer that handles top-level wildcards. One is in TypeComparer which only works for a stable path.

The reason why the first mechanism does not work for f(c.asInstanceOf[C]) is that we forget to dealias in captureWildcards:

https://github.com/lampepfl/dotty/blob/fcd837addc5b466b055da069960e48c5d4d5c1dc/compiler/src/dotty/tools/dotc/typer/Inferencing.scala#L506-L530

With the following change, the original code compiles:

--- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala
+++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala
@@ -503,7 +503,7 @@ object Inferencing {
     *  $i is a skolem of type `scala.internal.TypeBox`, and `CAP` is its
     *  type member. See the documentation of `TypeBox` for a rationale why we do this.
     */
-  def captureWildcards(tp: Type)(using Context): Type = tp match {
+  def captureWildcards(tp: Type)(using Context): Type = tp.dealias match {
     case tp @ AppliedType(tycon, args) if tp.hasWildcardArg =>

@Swoorup

This comment has been minimized.

@dwijnand dwijnand self-assigned this Jul 27, 2021
@SethTisue
Copy link
Member

Note that this isn't asInstanceOf specific, compilation also fails with e.g. def c(): C = ???; f(c()). Presumably the key difference is whether it's a stable path or not.

@SethTisue
Copy link
Member

we have to do it since Java also does this sort of "capture conversion"

(For background on this remark Martin made, see https://docs.oracle.com/javase/specs/jls/se16/html/jls-5.html#jls-5.1.10 — it begins with formal language, but then following that is a nice informal motivation section, in the purple box.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment