scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

Visibilty change (protected -> public) not possible with refinement #1352

Open scabug opened 16 years ago

scabug commented 16 years ago
val buffer = new collection.mutable.ArrayBuffer[Int]
{
  // chagne from public to protected
  override def ensureSize(n : Int) = super.ensureSize(n)

  def foo = println("new method")
}

buffer.foo // ok
buffer.ensureSize(100) // error

error: method ensureSize cannot be accessed in scala.collection.mutable.ArrayBuffer[Int]{def foo: Unit}

scabug commented 16 years ago

Imported From: https://issues.scala-lang.org/browse/SI-1352?orig=1 Reporter: @michael-nischt

scabug commented 15 years ago

@paulp said: See also #1994.

scabug commented 15 years ago

@odersky said: This unfortunately would require some major re-engineering. The problem is that right now the type of the anonymous class is defined as the least refinement type that contains the type given. And subtyping does not take protected modifiers into account. I agree the spec is ambiguous on what should happen, so something needs to be done. It won't be easy, though.

scabug commented 12 years ago

@Atry said: Isn't an anonymous class same as a singleton?

object buffer extends collection.mutable.ArrayBuffer[Int]
{
  // chagne from public to protected
  override def ensureSize(n : Int) = super.ensureSize(n)

  def foo = println("new method")
}
scabug commented 11 years ago

@paulp said: These are just bugs. There's no reason for "override def foo()" not to widen the access to public just because it's an anonymous class. If you wanted it to remain protected, you would have written "override protected def foo()".


scala> abstract class FooClass { protected def bar: Int }
defined class FooClass

scala> val x = new FooClass { def bar = 1 }
x: FooClass = $anon$1@7da72267

scala> x.bar
<console>:10: error: method bar in class FooClass cannot be accessed in FooClass
 Access to protected method bar not permitted because
 enclosing object $iw is not a subclass of 
 class FooClass where target is defined
              x.bar
                ^

scala> val x = new FooClass { val bar = 1 }
x: FooClass{val bar: Int} = $anon$1@393ddf54

scala> x.bar
res0: Int = 1

But...

scala> abstract class FooClass { protected val bar: Int }
defined class FooClass

scala> val x = new FooClass { val bar = 1 }
x: FooClass = $anon$1@72f9a516

scala> x.bar
<console>:10: error: value bar in class FooClass cannot be accessed in FooClass
 Access to protected value bar not permitted because
 enclosing object $iw is not a subclass of 
 class FooClass where target is defined
              x.bar
                ^

scala insists I bring SOMETHING new to the table if I want to widen the access: def becomes val, more specific return type... but with no "public" keyword, it is impossible to just say it. But there's no reason we should make you say it anyway, because the absence of access modifiers means public! Why would it infer a more restrictive type than the one we declared?

Here's the kind of desperation one ends up with.

scala> abstract class FooClass { protected val bar: Int }
defined class FooClass

scala> val x = new FooClass { override val bar = 1.asInstanceOf[Int with Nothing] }
x: FooClass{val bar: Int with Nothing} = $anon$1@6e963c02

scala> x.bar
res0: Int with Nothing = 1

scala> x.bar: Int
res1: Int = 1
scabug commented 11 years ago

@gkossakowski said: Unassigning and rescheduling to M7 as previous deadline was missed.

scabug commented 10 years ago

@Atry said:

Welcome to Scala version 2.10.3 (Java HotSpot(TM) Client VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> trait A { protected val foo: Int }
defined trait A

scala> trait B { protected val foo: Int }
defined trait B

scala> new A with B { val foo = 1; val bar = 2 }
res0: A with B{val bar: Int} = $anon$1@1e3c5fd

scala> res0.bar
warning: there were 1 feature warning(s); re-run with -feature for details
res1: Int = 2

scala> res0.foo
<console>:11: error: value foo in trait B cannot be accessed in A with B{val bar: Int}
 Access to protected value foo not permitted because
 enclosing object $iw is not a subclass of
 trait B where target is defined
              res0.foo
                   ^

scala> (new A with B { val foo = 1; val bar = 2 }).foo // It works, why?
res3: Int = 1
scabug commented 10 years ago

@retronym said: You don't really want to know:

    def typedBlock(block0: Block, mode: Mode, pt: Type): Block = {
      val syntheticPrivates = new ListBuffer[Symbol]
      try {
        namer.enterSyms(block0.stats)
        val block = treeCopy.Block(block0, pluginsEnterStats(this, block0.stats), block0.expr)
        for (stat <- block.stats) enterLabelDef(stat)

        if (phaseId(currentPeriod) <= currentRun.typerPhase.id) {
          // This is very tricky stuff, because we are navigating the Skylla and Charybdis of
          // anonymous classes and what to return from them here. On the one hand, we cannot admit
          // every non-private member of an anonymous class as a part of the structural type of the
          // enclosing block. This runs afoul of the restriction that a structural type may not
          // refer to an enclosing type parameter or abstract types (which in turn is necessitated
          // by what can be done in Java reflection). On the other hand, making every term member
          // private conflicts with private escape checking - see ticket #3174 for an example.
          //
          // The cleanest way forward is if we would find a way to suppress structural type checking
          // for these members and maybe defer type errors to the places where members are called.
          // But that would be a big refactoring and also a big departure from existing code. The
          // probably safest fix for 2.8 is to keep members of an anonymous class that are not
          // mentioned in a parent type private (as before) but to disable escape checking for code
          // that's in the same anonymous class. That's what's done here.
          //
          // We really should go back and think hard whether we find a better way to address the
          // problem of escaping idents on the one hand and well-formed structural types on the
          // other.
          block match {
            case Block(List(classDef @ ClassDef(_, _, _, _)), Apply(Select(New(_), _), _)) =>
              val classDecls = classDef.symbol.info.decls
              val visibleMembers = pt match {
                case WildcardType                           => classDecls.toList
                case BoundedWildcardType(TypeBounds(lo, _)) => lo.members
                case _                                      => pt.members
              }
              def matchesVisibleMember(member: Symbol) = visibleMembers exists { vis =>
                (member.name == vis.name) &&
                (member.tpe <:< vis.tpe.substThis(vis.owner, classDef.symbol))
              }
              // The block is an anonymous class definitions/instantiation pair
              //   -> members that are hidden by the type of the block are made private
              val toHide = (
                classDecls filter (member =>
                     member.isTerm
                  && member.isPossibleInRefinement
                  && member.isPublic
                  && !matchesVisibleMember(member)
                ) map (member => member
                  resetFlag (PROTECTED | LOCAL)
                  setFlag (PRIVATE | SYNTHETIC_PRIVATE)
                  setPrivateWithin NoSymbol
                )
              )
              syntheticPrivates ++= toHide
            case _ =>
          }
        }
        val stats1 = if (isPastTyper) block.stats else
          block.stats.flatMap(stat => stat match {
            case vd@ValDef(_, _, _, _) if vd.symbol.isLazy =>
              namer.addDerivedTrees(Typer.this, vd)
            case _ => stat::Nil
            })
        val stats2 = typedStats(stats1, context.owner)
        val expr1 = typed(block.expr, mode &~ (FUNmode | QUALmode), pt)
        treeCopy.Block(block, stats2, expr1)
          .setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst)
      } finally {
        // enable escaping privates checking from the outside and recycle
        // transient flag
        syntheticPrivates foreach (_ resetFlag SYNTHETIC_PRIVATE)
      }
    }