Skip to content

Cant use compiletime.ops.any.== with String when type of a value #8392

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
bishabosha opened this issue Feb 27, 2020 · 17 comments · Fixed by #8723
Closed

Cant use compiletime.ops.any.== with String when type of a value #8392

bishabosha opened this issue Feb 27, 2020 · 17 comments · Fixed by #8723
Assignees

Comments

@bishabosha
Copy link
Member

bishabosha commented Feb 27, 2020

minimized code

scala> import compiletime.ops.any._

scala> summon["" == "" =:= true]                                                                                        
val res1: true =:= true = generalized constraint

scala> val t: "" == "" = true
1 |val t: "" == "" = true
  |       ^
  |       Type argument ("" : String) does not conform to upper bound AnyVal 
1 |val t: "" == "" = true
  |             ^
  |       Type argument ("" : String) does not conform to upper bound AnyVal 

expectation

EDIT: the second example behaves according to its definition of type ==[X <: AnyVal, Y <: AnyVal] <: Boolean. However it should behave like the first. The type signature should be changed so that t can be typed

@smarter
Copy link
Member

smarter commented Apr 14, 2020

Comparing string literals is a legitimate thing to do, so can't this be fixed by changing the signature of ==to type ==[X <: Singleton, Y <: Singleton] ?

@bishabosha
Copy link
Member Author

bishabosha commented Apr 14, 2020

what would you say for this case, is the left side of =:= meant to be reduced first?:

scala> import compiletime.ops.int._

scala> class Foo
// defined class Foo

scala> summon[Foo * Foo =:= 0]
1 |summon[Foo * Foo =:= 0]
  |                       ^
  |Cannot prove that Foo * Foo =:= (0 : Int)..
  |I found:
  |
  |    <:<.refl[Nothing]
  |
  |But method refl in object <:< does not match type Foo * Foo =:= (0 : Int).

@smarter
Copy link
Member

smarter commented Apr 14, 2020

Honestly no idea, but that's a separate problem from getting == to work with string which is what #8721 was about, and it was closed in favor of this issue.

@bishabosha bishabosha changed the title Singleton ops constant folds argument before checking bounds Cant use compiletime.ops.any.== with String when type of a value Apr 14, 2020
@bishabosha
Copy link
Member Author

updated the context

@MaximeKjaer
Copy link
Contributor

Singleton might also be overly restrictive, as it then doesn't allow for String == String to be modeled. But it may allow for (String & Singleton) == (String & Singleton), so perhaps that's equivalent?

If the parameter type bounds change to Singleton, it could make sense to change the result type of the operation to Singleton as well (e.g. Boolean & Singleton for ==).

@smarter
Copy link
Member

smarter commented Apr 14, 2020

Yeah, so we could also just have no bounds at all if that's simpler.

@soronpo
Copy link
Contributor

soronpo commented Apr 14, 2020

It's better to have no bounds at all in this context, or to change == from any package to string and int, etc. with the proper bounds. So we will have int.== and string.==.

@soronpo
Copy link
Contributor

soronpo commented Apr 14, 2020

It is my intention to update the singleton-ops library to dotty, so it will take of combining generic == for all the various operation types using match type.

@MaximeKjaer
Copy link
Contributor

Moving them to type-specific subpackages means that we lose the ability to do 1 == "1". I don't know how useful that is in the first place though, as you're guaranteed that constants of different types are not equal, so perhaps that's a trade-off worth making.

@MaximeKjaer
Copy link
Contributor

MaximeKjaer commented Apr 14, 2020

I actually thought String would be a subtype of AnyVal when I wrote those bounds. So no type bounds is also fine by me. It would fit the package name of ops.any, and the semantics are preserved when you apply a compiletime op with something else than a primitive singleton type (i.e. the type is not folded in those cases).

@soronpo
Copy link
Contributor

soronpo commented Apr 14, 2020

Moving them to type-specific subpackages means that we lose the ability to do 1 == "1"

I don't think that is useful, but again in singleton-ops I will be able to do

type ==[L, R] <: Boolean = (L, R) match {
  case (Int, Int) => int.==[L, R]
  case (String, String) => string.==[L, R]
  case (Int, String) => string.==[string.ToString[L], R]
}

@smarter
Copy link
Member

smarter commented Apr 14, 2020

@MaximeKjaer Can you take care of making a PR for this ?

@MaximeKjaer
Copy link
Contributor

Yep, no problem. I'll make a PR later tonight.

noelwelsh pushed a commit to noelwelsh/dotty that referenced this issue Apr 14, 2020
`scala.compiletime.ops.any.==` is incorrectly constrained to subtypes of
`AnyVal`, which prevents it working with `String` literal types.

This change:

- changes the constraint from `AnyVal` to `Singleton`
- improves testing.

A quick note about the tests. As far as I can tell they didn't actually
check much. The tests were in the `neg` directory without a `check`
file so it was irrelevant if they compiled or not. My change includes
moving the compiling tests into the `run` directory, thus ensuring they
must compile for the test to pass, as well as adding cases for `String`
literal types to both success and failure cases. I'm currently have a
strange issue where Dotty thinks my `check` file only expects 8 error
though it clearly has 10 errors, and thus fails the test.

Closes scala#8392
@noelwelsh
Copy link

I have implemented most of a fix, which may be useful:

https://github.com/noelwelsh/dotty/tree/bugfix-singleton-ops-8392

The commit comment details what I have done. I think the most important part of my change is what I have done to the tests. As far as I can tell from the Dotty documentation and my own experiments the existing tests were not actually performing any testing. Which is to say the test would pass regardless of the type tests succeeding or failing.

@smarter
Copy link
Member

smarter commented Apr 14, 2020

@noelwelsh

The tests were in the neg directory without a check
file so it was irrelevant if they compiled or not

Dotty's testing infrastructure looks for // error marker at the end of lines in neg tests and use that to verify which lines produced an error.

@noelwelsh
Copy link

I see. That would explain why my tests are failing.

@MaximeKjaer
Copy link
Contributor

It seems to me that there is something slightly broken about how errors are handled in compiletime ops. Here is the example from #8271 that fails because the bounds of == are AnyVal:

import scala.compiletime.ops.any._
val eq3: "1" == "1" = true
// -- [E057] Type Mismatch Error: tests/neg/singleton-ops-any.scala:2:9 -----------
// 2 |val eq3: "1" == "1" = true
//   |         ^
//   |       Type argument ("1" : String) does not conform to upper bound AnyVal
// -- [E057] Type Mismatch Error: tests/neg/singleton-ops-any.scala:2:16 ----------
// 2 |val eq3: "1" == "1" = true
//   |                ^
//   |       Type argument ("1" : String) does not conform to upper bound AnyVal
// 2 errors found

However, it seems that the type-level constant fold still is taking place, as assigning false to eq3 raises an error:

import scala.compiletime.ops.any._
val eq3: "1" == "1" = false
// -- [E007] Type Mismatch Error: tests/neg/singleton-ops-any.scala:2:22 ----------
// 2 |val eq3: "1" == "1" = false
//   |                      ^^^^^
//   |                      Found:    (false : Boolean)
//   |                      Required: (true : Boolean)
// 1 error found

In fact, if there is another type mismatch error in the file, the fact that == is applied with arguments that don't match the bounds isn't even reported:

import scala.compiletime.ops.any._

val eq2: 1 == 2 = true
val eq3: "1" == "1" = true
// -- [E007] Type Mismatch Error: tests/neg/singleton-ops-any.scala:4:20 ----------
// 4 |  val eq2: 1 == 2 = true
//   |                    ^^^^
//   |                    Found:    (true : Boolean)
//   |                    Required: (false : Boolean)
// 1 error found

With no type bounds on the parameters of == this should in essence be "fixed". But it does seem like that fix is hiding a bug somewhere else.

MaximeKjaer added a commit to MaximeKjaer/dotty that referenced this issue Apr 14, 2020
Fixes scala#8392

String is not a subtype of AnyVal, so the type `==["", ""]` could not
be applied. No type bound is preferable to a type bound of `Singleton`
as it maintains the ability to have an unapplied type `Int == Int`, and
is more in line with the `any` package name.
MaximeKjaer added a commit to MaximeKjaer/dotty that referenced this issue Apr 14, 2020
Fixes scala#8392

String is not a subtype of AnyVal, so the type `==["", ""]` could not
be applied. No type bound is preferable to a type bound of `Singleton`
as it maintains the ability to have an unapplied type `Int == Int`, and
is more in line with the `any` package name.
liufengyun added a commit that referenced this issue Apr 15, 2020
Fix #8392: Remove parameter type bounds from scala.compiletime.ops.any types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants