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

Type inference when casting with asInstanceOf #11700

Open
dwijnand opened this issue Aug 21, 2019 · 9 comments

Comments

@dwijnand
Copy link
Member

commented Aug 21, 2019

It would be useful if asInstanceOf inferred from the expected type.

The workaround is:

def cast[A](x: Any): A = x.asInstanceOf[A]

Some notes from @adriaanm:

currently it's typed like a selection foo.AIO -- could try to special case that in typedSelect to type check as cast(foo)
right now, the type param for asInstanceOf would not be in the context's undetParams when type checking its target
(from memory -- didn't look at the code)

@dwijnand dwijnand added the infer label Aug 21, 2019

@SethTisue SethTisue added this to the Backlog milestone Aug 21, 2019

@psilospore

This comment has been minimized.

Copy link

commented Aug 26, 2019

I'm trying to attempt this but am still newish. So @adriaanm's comment about special casing it in typedSelect and to type check as cast(foo) makes me think it would be something like this.

            result match {
...
              case Select(arg, TermName("$asInstanceOf")) =>
                      //Then return tree to call cast instead 
                      //which I think might be something like this although I have no idea what I'm doing
                      treeCopy.Apply(tree, typed(Select(This(TypeName("Some place where cast could be defined")), TermName("cast"))), List(arg))
@Jasper-M

This comment was marked as outdated.

Copy link
Member

commented Aug 27, 2019

Wouldn't the trick be to just treat asInstanceOf like a normal method def asInstanceOf[A]: A during typechecking?

@adriaanm

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

Tbh, I'm not sure I would label this as a good first issue. Certainly, help-wanted, since I won't have time to look at this soon, but this part of the compiler is pretty hairy :-/

Conceptually, the idea would be to type x.asInstanceOf[A] as if we had written asInstanceOf(x) for an alternative definition: def asInstanceOf[A](x: AnyRef): A.

In the implementation, you'd do this completely differently, of course :-). I haven't pushed this, but it seems like this could work:

--- i/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ w/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -1013,6 +1013,14 @@ trait Infer extends Checkable {
     def inferExprInstance(tree: Tree, tparams: List[Symbol], pt: Type = WildcardType, treeTp0: Type = null, keepNothings: Boolean = true, useWeaklyCompatible: Boolean = false): List[Symbol] = {
       val treeTp = if (treeTp0 eq null) tree.tpe else treeTp0 // can't refer to tree in default for treeTp0
       val tvars  = tparams map freshVar
+
+      tree match {
+        case TypeApply(Select(fun, _), undet :: Nil) if tree.symbol == Any_asInstanceOf =>
+          println(s"instantiate $fun under ${tparams} / $undet expecting $pt")
+          tvars.find(_.origin.typeSymbol == undet.symbol).foreach { tv => tv.addLoBound(pt); println(s"constrained $tv") }
+        case _ =>
+      }
+
       val targs  = exprTypeArgs(tvars, tparams, treeTp, pt, useWeaklyCompatible)
       def infer_s = map3(tparams, tvars, targs)((tparam, tvar, targ) => s"$tparam=$tvar/$targ") mkString ","
       printTyping(tree, s"infer expr instance from pt=$pt, $infer_s")

trying it out in the repl:

Welcome to Scala 2.13.1-pre-0bd9aad (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_181).
Type in expressions for evaluation. Or try :help.

scala> val x: Int = (null: AnyRef).asInstanceOf // print <TAB> 
instantiate (null: AnyRef) under List(type T0) / T0 expecting Int
constrained ?T0

   private[this] val `x `: scala.Int = ((null): scala.AnyRef).asInstanceOf[Int] // : <notype>

@adriaanm

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

I should add that simply because we can implement this, doesn't mean we should 😕

Not sure yet we want to encourage the x.asInstanceOf pattern. It's still a good compiler hacking exercise, but I can also imagine the counter-arguments. My original comment was not meant as a promise that we'll ship this change, but I would consider it.

@dwijnand

This comment has been minimized.

Copy link
Member Author

commented Aug 27, 2019

If the compiler change proves to be unreasonable we can always go back to the idea of introducing def asInstanceOf in the library somewhere.

@Jasper-M

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

If x.asInstanceOf doesn't behave like other generic methods at least there should be a warning instead of only a "can't cast to Nothing" exception at runtime.

@adriaanm

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

it does behave like other methods, though -- the target of a method call does not constrain type params of the selected method

@Jasper-M

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

it does behave like other methods, though -- the target of a method call does not constrain type params of the selected method

Yes, I just realized this, to my own confusion...

scala> class Foo { def cast[A]: A = this.asInstanceOf[A] }
defined class Foo

scala> val a: String = new Foo().cast //print
   private[this] val `a `: scala.Predef.String = new Foo().cast[Nothing] // : <notype>

scala> val a: String = new Foo().cast
java.lang.ClassCastException: Foo cannot be cast to java.lang.String
  ... 36 elided

scala> val a: String = new Foo().asInstanceOf
java.lang.ClassCastException: Foo cannot be cast to scala.runtime.Nothing$
  ... 36 elided

What actually caused my confusion now and in the past is that new Foo().cast is typed as Nothing yet gets cast to String. Unlike new Foo().asInstanceOf. I find it a bit silly to bring referential transparancy into a discussion about casting, but there is something strange about that...

The reason this is "fixed in dotty" is that in dotty the type param of the method does get constrained because of the type of the lhs. I don't think it makes sense to only special case asInstanceOf to work like dotty.

@adriaanm

This comment has been minimized.

Copy link
Member

commented Aug 27, 2019

I don't think it makes sense to only special case asInstanceOf to work like dotty.

I agree -- more generally, I don't really want to touch type inference in 2.14, unless it's really crucial for the 3.0 migration. We should probably close bugs like this as fixed-in-scala-3 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.