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

Weird opaque types behaviour #15050

Open
jmgimeno opened this issue Apr 27, 2022 · 5 comments
Open

Weird opaque types behaviour #15050

jmgimeno opened this issue Apr 27, 2022 · 5 comments

Comments

@jmgimeno
Copy link

Compiler version

I'be tested this on scala 3.0.0 and scala 3.1.2

Example 1

//> using scala "3.1.2"
import OpaqueBug.*
def g(n: Counter): Counter = n
object OpaqueBug:
  opaque type Counter = Int
  val initial: Counter = 42
  def f(n: Int): Int = g(n) + initial
  @main def run = println(f(21))

And the compiler complains with:

[error] ./OpaqueBug.scala:13:24: value + is not a member of OpaqueBug.Counter, but could be made available as an extension method.
[error] 
[error] One of the following imports might fix the problem:
[error] 
[error]   import Int.int2double
[error]   import Int.int2float
[error]   import Int.int2long
[error]   import math.BigDecimal.int2bigDecimal
[error]   import math.BigInt.int2bigInt
[error]   import math.Numeric.IntIsIntegral.mkNumericOps
[error] 
[error]   def f(n: Int): Int = g(n) + initial
[error]   

I'd would expect this code to compile because when we do the + the compiler should know than Counter is simply an Int.

Example 2

If I move the opaque definition outside of the object:

//> using scala "3.1.2"
opaque type Counter = Int
def g(n: Counter): Counter = n
object OpaqueBug:
  val initial: Counter = 42
  def f(n: Int): Int = g(n) + initial
  @main def run = println(f(21))

Then the compiler complains with

[error] ./OpaqueBug.scala:11:26: Found:    (42 : Int)
[error] Required: Counter
[error]   val initial: Counter = 42
[error]                          ^^
[error] ./OpaqueBug.scala:13:26: Found:    (n : Int)
[error] Required: Counter
[error]   def f(n: Int): Int = g(n) + initial
[error]   

And I'd expect this code to compile without problems.

So, is this a bug or a feature (and it's me that I do not understand how opaque types should work)?

@jmgimeno jmgimeno added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 27, 2022
@smarter
Copy link
Member

smarter commented Apr 27, 2022

Example 1 looks like a bug, Example 2 is expected behavior (though I agree it's confusing): top-level definitions are wrapped into a top-level object generated by the compiler (you can verify this using -Xprint:typer), so other non-top-level definitions in the same file are not in the scope where the opaque type is defined.

@smarter smarter added area:typer area:opaque-types and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 27, 2022
@jmgimeno
Copy link
Author

So in the Example2 the opaque type is only visible at the top level but not inside the nested object, isn't it?

@smarter
Copy link
Member

smarter commented Apr 27, 2022

Top-level definitions are visible in the whole file, but opaque types only behave as type alias in the scope where they're defined, which for top-level definition is a compile-generated object, so you can refer to the opaque type in OpaqueBug but it will behave like an abstract type.

@jmgimeno
Copy link
Author

I worded my conclusion wrong; by visible I meant that the type it aliases, in this case, that Counter is an Int, is only visible for top-level definitions and not for the nested ones (which only know the abstract type).

@odersky
Copy link
Contributor

odersky commented Apr 28, 2022

The first example also won't work. The way things are set up is that inside OpaqueBug, we know that this.Counter = Int, since we know that OpaqueBug.this: OpaqueBug { type Counter = Int }. But outside we do not know that.
Here's the expansion of the program in more detail:

val OpaqueBug: OpaqueBug$ = new OpaqueBug$()
class OpaqueBug$ { this: OpaqueBug$ { type Counter = Int } => ... }

So, OpaqueBug has type OpaqueBug$, which means the alias is lost.

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.

3 participants