Scala Programming Language
  1. Scala Programming Language
  2. SI-5352

structural types evade access restrictions and other messy parts

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: Scala 2.10.0-M1, Scala 2.10.0
    • Component/s: None
    • Labels:
      None

      Description

      See SI-3197 for a different side of the same coin. This is worse though.

      
      scala> class Bar1 extends Bar { protected def f(): Int = 5 }
      defined class Bar1
      
      scala> class Bar2 extends Bar { protected def f(): Int = 5 }
      defined class Bar2
      
      scala> List(new Bar1, new Bar2)
      res1: List[Bar{protected def f(): Int}] = List(Bar1@55661f7b, Bar2@239cf00a)
      
      scala> type BarF = { def f(): Int }
      defined type alias BarF
      
      scala> var x: BarF = _
      x: BarF = null
      
      scala> x = res1.head
      x: BarF = Bar1@55661f7b
      
      scala> x.f
      res2: Int = 5
      
      scala> (new Bar1).f
      <console>:10: error: method f in class Bar1 cannot be accessed in Bar1
       Access to protected method f not permitted because
       enclosing object $iw is not a subclass of 
       class Bar1 where target is defined
                    (new Bar1).f
                               ^
      

      We really need to establish what is allowed in terms of modifiers within refinement types. You can't even declare them, they have to be inferred:

      scala> type Foo = { protected def f(): Int }
      <console>:1: error: illegal start of declaration
             type Foo = { protected def f(): Int }
                          ^
      

      It loses the local boundary:

      scala> class A {
           |   abstract class Bar { protected[this] def f(): Any }
           |   class Bar1 extends Bar { protected[this] def f(): Int = 5 }
           |   class Bar2 extends Bar { protected[this] def f(): Int = 5 }
           |   
           |   def f = List(new Bar1, new Bar2)
           | }
      defined class A
      
      scala> (new A).f _
      res0: () => List[A#Bar{protected def f(): Int}] = <function0>
      

      It doesn't notice refinements in access on their own, but applies them if it is in the neighborhood refining the return type, leading to unpredictable behavior:

      
      package bippy {
        package dingus {
          abstract class Bar { protected[dingus] def f(): Any }
          class Bar1 extends Bar { protected[bippy] def f(): Int = 5 }
          class Bar2 extends Bar { protected[bippy] def f(): Int = 5 }
          // Infers List[Bar{protected[bippy] def f(): Int}]
          
          abstract class Bar { protected[dingus] def f(): Int } // note no return type refinement
          class Bar1 extends Bar { protected[bippy] def f(): Int = 5 }
          class Bar2 extends Bar { protected[bippy] def f(): Int = 5 }
          // Infers List[Bar]
        }
        import dingus._
        object A { def g2 = List(new Bar1, new Bar2) }
      }
      

      There is no way to widen access if a method was declared protected but may be widened in subclasses, as demonstrated in SI-3197.

      To top it off, all the access modifiers are useless. You can't call a protected method unless you're a member of the same class, but when is this going to arise with an inferred structural type (and again, the access modifiers can only arise through inference.)

      scala> object A {
           |   abstract class Bar { protected def f(): Any }
           |   class Bar1 extends Bar { protected def f(): Int = 5 }
           |   class Bar2 extends Bar { protected def f(): Int = 5 ; def h = g map (_.f()) }
           |   
           |   def g = List(new Bar1, new Bar2)
           | }
      <console>:10: error: method f cannot be accessed in A.Bar{protected def f(): Int}
       Access to protected method f not permitted because
       enclosing class Bar2 in object A is not a subclass of 
       A in object A where target is defined
               class Bar2 extends Bar { protected def f(): Int = 5 ; def h = g map (_.f()) }
                                                                                      ^                                                                                      ^
      

      Now there's a nice confusing error. Regardless of what ought to happen, I don't think there's any way to call that protected f.

      I propose that structural types only infer public signatures. If everything going into the lub doesn't have a public signature, there's no such method in the lub. Simple and removes no capability which actually works.

        Activity

        Hide
        Martin Odersky added a comment -

        I think we need to let only public members leak into refinements. Everything else leads to trouble. Paul, I give you first shot at enforcing that, but feel free to reassign if somebody else should do it.

        Show
        Martin Odersky added a comment - I think we need to let only public members leak into refinements. Everything else leads to trouble. Paul, I give you first shot at enforcing that, but feel free to reassign if somebody else should do it.
        Hide
        Paul Phillips added a comment -

        I'd forgotten, but after running into some other issue, I already did it (in bedb33fd7cb .)

        Show
        Paul Phillips added a comment - I'd forgotten, but after running into some other issue, I already did it (in bedb33fd7cb .)

          People

          • Assignee:
            Paul Phillips
            Reporter:
            Paul Phillips
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development