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

Inconsistency between inferred and declared typing for types with restricted visibility #1800

Open
scabug opened this issue Mar 16, 2009 · 4 comments

Comments

@scabug
Copy link

scabug commented Mar 16, 2009

When declaring objects as private[package/object] or protected[package/object] it is possible to leak out references to these objects into the public api (can be desirable, this in itself is not a problem).

When users of the api receive such private object via a function call, they can create a variable to reference the private object using inferred typing:

val b = getPrivateObject()

However they cannot create such variable using declared typing:

val a: PrivateObject = getPrivateObject()

The line above will generate a compiler error: "class PrivateObject cannot be accessed". Which makes sense, because PrivateObject was declared private. But in this case inferred typing should not work either, otherwise the behaviors of inferred typing and declared typing become inconsistent.

Here is a simplified example that can be tested:

object ObjectHolder {
    private[ObjectHolder] class PrivateObject
    def getPrivateObject = new PrivateObject
}

object Test {
    def main(args: Array[String]) {
        // compiler error: class PrivateObject cannot be accessed
        // in object test.ObjectHolder
        val a: ObjectHolder.PrivateObject = ObjectHolder.getPrivateObject

        // works fine
        val b = ObjectHolder.getPrivateObject
        println(b.getClass)
    }
}

Found on the following platform:[[BR]]
Scala Runtime Version 2.7.30[[BR]]
Netbeans Plugin (Scala Kit), Version 0.15.1

[[BR]]
[[BR]]
THE FOLLOWING IS ONLY RELATED IF THE REPORT IS NOT A BUG BUT THE DESIRED BEHAVIOR[[BR]]
If this is a bug, then save your time and skip the rest of the post.

I was completely amazed about scala being flexible enough to create domain specific libraries with domain specific syntax (which is something that no other language to date can offer). If this behavior of inferred typing is desired, it may limit the ability of the programmers to deliver such libraries.

Here is the actual use-case:

To design a simple library that allows the addition of 3d vectors.

answer set a + b + c + d

The obvious route is to override + to do the addition.
However every time there is an addition, you must create a new object. This negatively impact performance. The second obvious choice is to create a method add(arg, store) where arg is the argument and store is the temp location. However using

a add(b, store) add(c, store) add(d, store)
a +(b, store) +(c, store) + (d, store)

is hardly domain specific.

The third obvious choice is to create a temp object on the first addition. Such temp object must be an instance of a different class and behave just like a vector, so the subsequent additions simply accumulate inside the temp vector.

This is nice, since you don't have to create a new instance for every operation, and overall users are not burdened with managing temp variables manually. However if a user can keep a reference to the temp object it becomes very easy to create subtle bugs, as follows:

val answer = Vec3()
val a = Vec3(1, 1, 1)
val b = Vec3(2, 2, 2)

// t1 and t2 are temp objects
val t1 = a + b // t1 is (3, 3, 3)
val t2 = t1 + b // t1 eq t2; t1 is (5, 5, 5) - not expected!
answer set t1 + t2 
println(answer) // expected (8, 8, 8), got (10, 10, 10)

So the solution is to hide the class of the temp objects by making it package private. If hiding does not work, the api becomes leaky and the solution has to be scrapped.

If visibility rules are enforced you have a domain specific library that is easy to use and efficient.
If the rules are not enforced, you have to chose if you want to deliver something efficient but painful to use or something easy to use but slow. Another option is to think long and hard on how to deliver both. These are not the choices one should face when using a flexible language.

@scabug
Copy link
Author

scabug commented Mar 16, 2009

Imported From: https://issues.scala-lang.org/browse/SI-1800?orig=1
Reporter: @lexn82

@olafurpg
Copy link
Member

Encountered this bug today while running scalafix ExplicitResultTypes on the akka codebase, here is a minimized reproduction

package a {
  private[a] object b {
    class B
  }
  object c { def getB: b.B = new b.B() }
}
package d {
  object e {
    val f = a.c.getB
    // val g: a.b.B = a.c.getB // Error
// foo.scala:10: error: object b in package a cannot be accessed in package a
//     val g: a.b.B = a.c.getB
//              ^
  }
}

@joroKr21
Copy link
Member

I have seen this too in macros when trying to lift user-written code into a temporary variable.
Even this simple use-case is something that can come back and bite you:

def lift[A](c: blackbox.Context)(x: Tree) = {
  import c.universe._
  val tmp = TermName(c.freshName("tmp"))
  q"{ val $tmp: ${x.tpe} = $x; $tmp }"
}

@joroKr21
Copy link
Member

And then I found #6794

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

3 participants