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

Invariant implicit definition is not picked up for its covariant supertype implicit parameter #12231

Open
noresttherein opened this issue Nov 19, 2020 · 22 comments
Labels
fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) implicit
Milestone

Comments

@noresttherein
Copy link

noresttherein commented Nov 19, 2020

reproduction steps

using Scala 2.13.3,

	trait Covariants
	trait Invariants extends Covariants
	trait Implicits
	class Covariant[+T] extends Invariants with Implicits

	class Invariant[T] extends Covariant[T]


	object Implicits {
		implicit val int = new Invariant[Int]
		implicit val short = new Invariant[Short]
	}

	object Covariants {
		implicit def covariant[T: Covariant]: Covariant[Option[T]] = new Covariant
	}

	object Invariants {
		implicit def invariant[T: Invariant]: Invariant[Option[T]] = new Invariant
	}

	println(scala.reflect.runtime.universe.reify{
		implicitly[Covariant[Option[Int]]]
	}.tree)

prints:

Predef.implicitly[Playground.this.Covariant[Option[Int]]](Playground.this.Covariants.covariant(Playground.this.Implicits.int))

If I remove the covariant definition in object Covariant, I get a compiler error of missing implicit, with the following hint:

Playground.this.Invariants.invariant is not a valid implicit value for Playground.Covariant[Option[Int]] because:
hasMatchingSymbol reported error: ambiguous implicit values:
 both value int in object Implicits of type Playground.Invariant[Int]
 and value short in object Implicits of type Playground.Invariant[Short]
 match expected type Playground.Invariant[T]
		implicitly[Covariant[Option[Int]]]
Original report, for amusement purposes (user error due to a typo):
	trait Covariants
	trait Invariants extends Covariants

	class Covariant[+T] extends Invariants

	class Invariant[T] extends Covariant[T]

	object Covariants {
		implicit def covariant[T] :Covariant[T] = new Covariant[T]
	}

	object Ivariants {
		implicit def invariant[T] :Invariant[T] = new Invariant[T]
	}

	println(scala.reflect.runtime.universe.reify{
		implicitly[Covariant[Int]]
	}.tree)

prints:

Predef.implicitly[Playground.this.Covariant[Int]](Playground.this.Covariants.covariant)

problem

I would expect Invariants.invariant[T] in place of Covariants.covariant[Int]

@noresttherein
Copy link
Author

By the way, in incremental builds the compiler doesn't notice that implicit keyword has been removed from a method and still uses the now not implicit definition instead of rerunning the search, but I assume this is well known.

@dwijnand

This comment has been minimized.

@noresttherein

This comment has been minimized.

@joroKr21
Copy link
Member

joroKr21 commented Nov 19, 2020

But object Invariants is the companion object of trait Invariants which is a parent trait of class Covariant[+T] so it is in implicit scope right?

@noresttherein
Copy link
Author

Yes, this example didn't work only due to a typo, but I already got one without it! So sorry, you should have been quicker with closing!

@SethTisue

This comment has been minimized.

@dwijnand

This comment has been minimized.

@som-snytt
Copy link

LOL except argh instead of laugh. I looked at this a minute first thing today, before coffee, and totally missed it.

I guess this is why companions are defined together, I mean close together. It suggests an alternative syntax would have been wise; they must have considered alternatives. A linter could notice names that are too close and also companions that aren't close enough. There is a lint at the office about overloads must be textually contiguous.

@SethTisue
Copy link
Member

I guess this is why companions are defined together, I mean close together.

I'm reminded of this old photo of mine: https://www.flickr.com/photos/tisue/5466211519

@noresttherein
Copy link
Author

I guess this is why companions are defined together, I mean close together.
It won't help if the code of the class (or both) doesn't fit on a single screen, which frankly is always for me, once scaladocs are factored in. In the 'real' case having the whole trait hierarchy, introduced only for the purpose of setting implicit precedence, grouped together was in my mind much preferrable.

I think it's the first time in my ~10y of working with scala that I made such a mistake: note that normally objects are there to put other things than implicits in and, when referencing them explicitly, the typos are easier to spot.
Add that IDEs highlight typos and I don't really think it is an issue in practice.

@som-snytt
Copy link

I added nested companion to my feature request for "export this._". I included "anonymous companion".

Don't worry, they never adopt my suggestions. To be fair, sometimes they'll accept a PR and then either revert it or ignore it.

The flickr stream seems to be bottomless. I think I'll pause now and rest. I suddenly feel very cold. I wonder if "Want a X but can't quit your Y?" is a meme?

That initial picture also works for "anonymous companion."

@joroKr21
Copy link
Member

Now I saw the typo in the original issue 🙈 - when debugging implicit issues you might want to enable -Xlog-implicits:

[info] ~/scratch/src/main/scala/ImplicitFun.scala:24:15: ImplicitFun.this.Invariants.invariant is not a valid implicit value for ImplicitFun.Invariant[T] because:
[info] hasMatchingSymbol reported error: diverging implicit expansion for type ImplicitFun.Invariant[T]
[info] starting with method invariant in object Invariants
[info]     implicitly[Covariant[Option[Int]]]
[info]               ^
[info] ~/scratch/src/main/scala/ImplicitFun.scala:24:15: ImplicitFun.this.Invariants.invariant is not a valid implicit value for ImplicitFun.Covariant[Option[Int]] because:
[info] hasMatchingSymbol reported error: ambiguous implicit values:
[info]  both value int in object Implicits of type ImplicitFun.Invariant[Int]
[info]  and value short in object Implicits of type ImplicitFun.Invariant[Short]
[info]  match expected type ImplicitFun.Invariant[T]
[info]     implicitly[Covariant[Option[Int]]]
[info]       

You can minimise a bit by removing implicit def covariant then it becomes a compilation error.
Because Covariant is ... well covariant Option[Int] becomes only an upper bound for the T in Covariant.
Where the problem happens is that Int is not propagated as the upper bound in the recursive search for invariant.
If you make Covariant invariant and you will observe that it works when the type variable is fully instantiated.
Also: fixed in scala 3

@joroKr21
Copy link
Member

More specifically there is a function that retracts type parameters that were instantiated to Nothing (adjustTypeArgs) and puts them back i the undetermined (undetParams) pile. This function loses all the accumulated upper bounds (lower bounds don't matter because otherwise we would not have instantiated to Nothing) and later the undetermined parameters are replaced by wildcards.

@noresttherein
Copy link
Author

Glad do hear it works in scala 3, seems like a might tighter design, I am starting to look hopeful.

@joroKr21
Copy link
Member

joroKr21 commented Dec 21, 2020

This is a known issue of implicits in Scala 2 - Nothing is used as an indicator that there is no solution although sometimes that is not actually the case. This is more likely to happen with covariant than with invariant implicits. It's fixed in dotty, because Nothing is not "abused" in this way. This is unlikely to progress in Scala 2.

@SethTisue
Copy link
Member

This is a known issue of implicits in Scala 2 - Nothing is used as an indicator that there is no solution although sometimes that is not actually the case.

Is there a ticket of record on that? I'd like to close this a duplicate, especially since the history here is now confusing to follow because of the class naming red herring.

But to close it as a duplicate, there should be something it's a duplicate of :-)

@SethTisue SethTisue added fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) implicit labels Apr 16, 2021
@SethTisue SethTisue modified the milestone: Backlog Apr 16, 2021
@milessabin
Copy link

Isn't this fixed by scala/scala#6037? Available under -Xsource:3.

@som-snytt
Copy link

➜  snips scala t12231b.scala
Predef.implicitly[Covariant[Option[Int]]](Covariants.covariant(Implicits.int))
➜  snips scala -Xsource:3 t12231b.scala
Predef.implicitly[Covariant[Option[Int]]](Covariants.covariant(Implicits.int))

Let's see what Dotty says!

➜  snips sdk use scala 3.0.0-RC1

Using scala version 3.0.0-RC1 in this shell.
➜  snips scala t12231b.scala
-- Error: snips/t12231b.scala:24:8 --------------------------------
24 |        object Test extends App {
   |        ^
   |   Incompatible combinations of tabs and spaces in indentation prefixes.
   |   Previous indent : 1 tab
   |   Latest indent   : 8 spaces
➜  snips vi t12231b.scala
➜  snips scala t12231b.scala
Invariant@681311a7

@joroKr21
Copy link
Member

Isn't this fixed by scala/scala#6037? Available under -Xsource:3.

There is no contravariance involved here

@SethTisue
Copy link
Member

SethTisue commented Apr 27, 2021

when I "remove the covariant definition in object Covariant" as instructed, the error I get is different, I get: could not find implicit value for parameter e: Covariant[Option[Int]]. Scastie: https://scastie.scala-lang.org/r1USvlvLSpykHcFqZ8yesg , and the same thing compiles in Scala 3.0.0-RC3: https://scastie.scala-lang.org/lmu4Zm1rTfWKvcSjg2Gljw

how do I get the "ambiguous implicit values" error instead? which is the "true" error that this bug is about?

@SethTisue SethTisue added this to the Backlog milestone Apr 27, 2021
@joroKr21
Copy link
Member

how do I get the "ambiguous implicit values" error instead? which is the "true" error that this bug is about?

with -Xlog-implicits

which is the "true" error that this bug is about?

the original issue is about which instance is selected and printed not about what compiles

@Jasper-M
Copy link

the history here is now confusing

Strongly agreeing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) implicit
Projects
None yet
Development

No branches or pull requests

7 participants