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

Implicit conversions with singleton type parameters are not checked like they are in Scala2 #14040

Closed
lihaoyi opened this issue Dec 5, 2021 · 7 comments

Comments

@lihaoyi
Copy link
Contributor

lihaoyi commented Dec 5, 2021

Compiler version

lihaoyi dotty$ git show
commit 6b1a662dfe7d201407f8e01e61c278dbbf4f82bc (HEAD -> master, origin/master, origin/HEAD)
Merge: 1003d7cc22 c6ee5e3bd7
Author: Dale Wijnand <[email protected]>
Date:   Thu Dec 2 22:42:51 2021 +0000

Minimized code

Dotty behavior:

dotty$ sbt repl
Welcome to Scala 3.1.2-RC1-bin-SNAPSHOT-nonbootstrapped-git-6b1a662 (11.0.7, Java OpenJDK 64-Bit Server VM).

scala> implicit def f(s: String with Singleton): Int = s.length
def f(s: String & Singleton): Int

scala> val notAConstant: String = "123"
val notAConstant: String = 123

scala> notAConstant: Int
val res0: Int = 3

Scala 2 behavior:

$ scala
Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM, Java 11.0.7).
Type in expressions for evaluation. Or try :help.

scala> implicit def f(s: String with Singleton): Int = s.length
f: (s: String with Singleton)Int

scala> val notAConstant: String = "123"
notAConstant: String = 123

scala> notAConstant: Int
<console>:14: error: type mismatch;
 found   : String
 required: Int
       notAConstant: Int
       ^

Expectation

I expect both Scala2 and Scala3 snippets to have the same behavior. Bumped into this in the context of com-lihaoyi/fansi#42

@odersky
Copy link
Contributor

odersky commented Dec 5, 2021

Scala 3's behavior is correct here. notAConstant is both a String and a Singleton, so the implicit conversion should apply.

I believe it has to do with the fact that Scala 2 widen's types more eagerly than Scala 3 does.

@odersky odersky closed this as completed Dec 5, 2021
@smarter
Copy link
Member

smarter commented Dec 5, 2021

If you want to limit the conversions to constants, you can use a typeclass constraint:

implicit def f[T <: String with Singleton: ValueOf](s: T): Int = s.length

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Dec 5, 2021

@odersky In the example I am explicity annotating notAConstant as String only. Surely the fact that it's RHS is a singleton should not be able to leak through that annotation?

@lihaoyi
Copy link
Contributor Author

lihaoyi commented Dec 5, 2021

@odersky Anyway, this issue can be reproduced even when notAConstant is defined to clearly not be a singleton type, and varies at runtime:

scala> implicit def f(s: String with Singleton): Int = s.length
there were 1 feature warning(s); re-run with -feature for details
def f(s: String & Singleton): Int

scala> val notAConstant: String = "123" + java.lang.Math.random()
val notAConstant: String = 1230.1577068006814316

scala>  notAConstant: Int
val res0: Int = 21

scala> def foo(x: String): Int = x: Int
def foo(x: String): Int

scala> foo("lol")
val res1: Int = 3

scala> foo("xyz")
val res2: Int = 3

Unless "singleton" has some other meaning that's not "literal constant"? That's what I thought it's meant to mean

@som-snytt
Copy link
Contributor

I think it means just

scala> x: x.type
val res0: String = xxx

which you can't do with an unstable path.

@smarter
Copy link
Member

smarter commented Dec 5, 2021

Unless "singleton" has some other meaning that's not "literal constant"?

Singleton means a singleton type, the type of x inside foo is x.type which is a singleton type by definition because x cannot be re-assigned to something else inside foo.

@smarter
Copy link
Member

smarter commented Dec 5, 2021

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

No branches or pull requests

4 participants