Dotty: Top-level definitions using opaque types can bypass their opacity

Created on 3 Feb 2020  路  4Comments  路  Source: lampepfl/dotty

minimized code

https://scastie.scala-lang.org/60nHfRoiRVONgdxGJpUjZA

opaque type Foo = Int
object Foo
  def apply(i: Int): Foo = i

opaque type Bar = Int
object Bar
  def apply(i: Int): Bar = i

// ******************************
// Bug example #1, Bar incorrectly acts as subtype of Foo
// when used in a top-level definition.
object FooToString
  // Next line gives a compilation error as expected: "Found: Bar => String, Required: Foo => String"
  //val fooToString: Foo => String = (b: Bar) => "hi from Bar function"  

// This line is identical to the previous line, but it's a
// top-level definition that compiles and is used below.
val fooToString: Foo => String = (b: Bar) => "hi from Bar function" // BUG

// ******************************
// Bug example #2, same as #1 but demonstrated using
// a typeclass instance top-level definition.
trait Show[A]
  def show(a: A): String
object Show
  // Next line gives a compilation error as expected: "Found: Bar => String, Required: Show[Foo]"
  //given Show[Foo] = (f: Bar) => "hi from Show[Foo]"

// This line is identical to the previous line, but it compiles,
// creating an instance of Show[Foo] that's used below.
given Show[Foo] = (f: Bar) => "hi from Show[Foo]" // BUG

def show[A: Show](a: A): String = summon[Show[A]].show(a)

object Main extends App
  println(show(Foo(5)))
  println(fooToString(Foo(17)))

expectation

As shown in the Scastie snippet, this definition

val fooToString: Foo => String = (b: Bar) => "hi from Bar function"

Is expected to fail compilation with the error Found: Bar => String, Required: Foo => String but instead compilation succeeds if the definition is top-level.

bug

Most helpful comment

TIL I don't understand what "scope" means. I thought it meant literally what is "seen".

So I would have said that this disposition is at odds with https://github.com/lampepfl/dotty/issues/7891

But scope does not mean the same thing as telescope or kaleidoscope. It means a target to aim at (skopos). The figurative sense is that the scope of one's power includes all the targets one can hit.

Shakespeare: "Desiring this mans art, and that mans skope." (Sonnet 29, in which he is depressed about his prospects, then gets a mood adjustment from his lover.)

I would still prefer that scope is what I can tell by looking.

Except, of course, for implicit scope.

"Opaque" means obscure because it's in shadow. It's too bad we can't use opaque for shadowed symbols. The sense of "not translucent" comes afterward, from optical science.

OED has another nice line from Spenser: "slipper hope Of mortal men, that swincke and sweate for nought, And shooting wide, doe misse the marked scope." There is an old word "swinch" for "toil", which someone may want to use for their latest Scala project.

All 4 comments

In fact, opaque types are transparent in the scope they appear, not just in their companion object.
For toplevel opaques this means that the definition is visible in all other toplevel definitions of the same file, since these go into one package object together. Objects or classes in the same file do not get wrapped in the object and are consequently our of the scope where an opaque alias is visible.

I have added a section to the docs that explains this.

opaque types are transparent in the scope they appear, not just in their companion object

Got it.

Might we consider a mechanism to make this property known to users at compile-time? Something like

Warning W037:
at 12: given Show[Foo] = (f: Bar) => "hi from Show[Foo]"
                       ^
  The value of type `Bar => String`
      (f: Bar) => "hi from Show[Foo]"
  is assignable to
      given Show[Foo]
  because "opaque type Bar = Long <: opaque type Foo = Long"
  and opaque types are transparent in the scope
  they are defined, not just their companion objects.

Learn  more at www.allaboutopaquetypes.com/W037.
Disable this warning with --stop-whining-about-opaque-types

TIL I don't understand what "scope" means. I thought it meant literally what is "seen".

So I would have said that this disposition is at odds with https://github.com/lampepfl/dotty/issues/7891

But scope does not mean the same thing as telescope or kaleidoscope. It means a target to aim at (skopos). The figurative sense is that the scope of one's power includes all the targets one can hit.

Shakespeare: "Desiring this mans art, and that mans skope." (Sonnet 29, in which he is depressed about his prospects, then gets a mood adjustment from his lover.)

I would still prefer that scope is what I can tell by looking.

Except, of course, for implicit scope.

"Opaque" means obscure because it's in shadow. It's too bad we can't use opaque for shadowed symbols. The sense of "not translucent" comes afterward, from optical science.

OED has another nice line from Spenser: "slipper hope Of mortal men, that swincke and sweate for nought, And shooting wide, doe misse the marked scope." There is an old word "swinch" for "toil", which someone may want to use for their latest Scala project.

@ryanberckmans In my experience it is really hard to do something like what you propose without producing false negatives or annoying warnings.

Was this page helpful?
0 / 5 - 0 ratings