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

@specialize does not handle super correctly

    Details

      Description

      [Edit: adding a more serious super-related crash.]

      trait C1[+A] {
        def head: A = sys.error("")
      }
      trait C2[@specialized +A] extends C1[A] {
        override def head: A = super.head
      }
      class C3 extends C2[Char] 
      // 
      // error: java.lang.AssertionError: assertion failed: method head
      //  at scala.Predef$.assert(Predef.scala:160)
      //  at scala.tools.nsc.transform.Mixin$$anonfun$scala$tools$nsc$transform$Mixin$$rebindSuper$1.apply(Mixin.scala:129)
      //  at scala.tools.nsc.transform.Mixin$$anonfun$scala$tools$nsc$transform$Mixin$$rebindSuper$1.apply(Mixin.scala:95)
      //  at scala.reflect.internal.SymbolTable.atPhase(SymbolTable.scala:103)
      //  at scala.tools.nsc.transform.Mixin.scala$tools$nsc$transform$Mixin$$rebindSuper(Mixin.scala:95)
      //  at scala.tools.nsc.transform.Mixin$$anonfun$mixinTraitMembers$1$1.apply(Mixin.scala:351)
      //  at scala.tools.nsc.transform.Mixin$$anonfun$mixinTraitMembers$1$1.apply(Mixin.scala:304)
      

      [Original report]

      trait Super[@specialized(Int) A] {
        def superb = 0
      }
      
      object Sub extends Super[Int] {
        super[Super].superb // scala.tools.nsc.symtab.Types$$TypeError: Super does not name a parent class of object Sub
        super.superb        // okay    
        override def superb: Int = super.superb // okay
      }
      
      scala.tools.nsc.symtab.Types$$TypeError: Super does not name a parent class of object Sub
      	at scala.tools.nsc.typechecker.Contexts$$Context.error(Contexts.scala:273)
      	at scala.tools.nsc.typechecker.Infer$$Inferencer.error(Infer.scala:213)
      	at scala.tools.nsc.typechecker.Typers$$Typer.findMixinSuper$$1(Typers.scala:3417)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typedSuper$$1(Typers.scala:3434)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed1(Typers.scala:3951)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed(Typers.scala:4078)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typedQualifier(Typers.scala:4145)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typedQualifier(Typers.scala:4151)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed1(Typers.scala:3964)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed(Typers.scala:4078)
      	at scala.tools.nsc.typechecker.Typers$$Typer$$$$anonfun$$typedApply$$1$$1.apply(Typers.scala:3284)
      	at scala.tools.nsc.typechecker.Typers$$Typer$$$$anonfun$$typedApply$$1$$1.apply(Typers.scala:3284)
      	at scala.tools.nsc.typechecker.Typers$$Typer.silent(Typers.scala:705)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typedApply$$1(Typers.scala:3284)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed1(Typers.scala:3924)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed(Typers.scala:4078)
      	at scala.tools.nsc.typechecker.Typers$$Typer.typed(Typers.scala:4126)
      	at scala.tools.nsc.transform.SpecializeTypes$$$$anon$$6.transform(SpecializeTypes.scala
      [snip]
      

      In the original code, I originally got a different error during mixins, using an unqualified super reference. I haven't reproduced that yet in isolation. It can be reproduced by compiling scalala with SBT from: https://github.com/retronym/Scalala/tree/ab592a34bc1b5492ab2df65d4ba96d6c585ff724

      This results in:

      [info] [running phase mixin on Domain.scala]
      exception when traversing <root>#java#lang#Object with
      <root>#scala#Product2 with
      <root>#scalala#tensor#domain#Product2Domain$$mcII$$sp with
      <root>#scalala#tensor#domain#Product2DomainLike$$mcII$$sp with
      <root>#scala#ScalaObject with <root>#scala#Product {
      
      [snip]
      
      java.lang.UnsupportedOperationException: tail of empty list
             at scala.collection.immutable.Nil$$.tail(List.scala:388)
             at scala.collection.immutable.Nil$$.tail(List.scala:383)
             at scala.tools.nsc.transform.Mixin$$$$anonfun$$scala$$tools$$nsc$$transform$$Mixin$$$$rebindSuper$$1.apply(Mixin.scala:95)
             at scala.tools.nsc.transform.Mixin$$$$anonfun$$scala$$tools$$nsc$$transform$$Mixin$$$$rebindSuper$$1.apply(Mixin.scala:94)
             at scala.tools.nsc.symtab.SymbolTable.atPhase(SymbolTable.scala:103)
      

      I'll try and get a smaller test case for this one, and update this ticket.

      See also SI-3651

      What versions of the following are you using?
      Scala: 2.8.0 / 2.8.1
      Java: 1.6

        Activity

        Hide
        Aleksandar Prokopec added a comment -

        The tail of empty list exception happends due to the following method in mixin which, instead of looking at the new list of base classes which is obtained after specialization, looks at the list of base classes after pickler:

          private def rebindSuper(base: Symbol, member: Symbol, mixinClass: Symbol): Symbol =
            exitingPickler {
              var bcs = base.info.baseClasses.dropWhile(mixinClass != _).tail
        

        I don't know this phase well enough to understand why this is done this way - I'd just extract the base.info.baseClasses call outside of the closure.

        Show
        Aleksandar Prokopec added a comment - The tail of empty list exception happends due to the following method in mixin which, instead of looking at the new list of base classes which is obtained after specialization, looks at the list of base classes after pickler: private def rebindSuper(base: Symbol, member: Symbol, mixinClass: Symbol): Symbol = exitingPickler { var bcs = base.info.baseClasses.dropWhile(mixinClass != _).tail I don't know this phase well enough to understand why this is done this way - I'd just extract the base.info.baseClasses call outside of the closure.
        Hide
        Aleksandar Prokopec added a comment -

        I think the answer to the above is - yes, the `baseClasses` call should go before the `exitingPickler` block.

        However, I don't think that this example is easily fixable. Let me illustrate why. From this:

        trait C1[A] {
          def head: A = sys.error("")
        }
        
        trait C2[@specialized(Int) A] extends C1[A] {
          override def head: A = super.head
        }
        
        class C3 extends C2[Int]
        

        After mixin I get a super accessor defined twice, once coming from the specialized class and once from a generic one:

          class C3 extends Object with C2$mcI$sp {
            override <superaccessor> <specialized> <artifact> def super$head(): Int = unbox(C2$class.head(C3.this));
            override <superaccessor> <specialized> <artifact> def super$head$mcI$sp(): Int = unbox(C2$class.head(C3.this));
            override <specialized> def head(): Int = C2$mcI$sp$class.head(C3.this);
            override <specialized> def head$mcI$sp(): Int = C2$mcI$sp$class.head$mcI$sp(C3.this);
            <superaccessor> <artifact> def super$head(): Object = C1$class.head(C3.this);
            <superaccessor> <specialized> <artifact> def super$head$mcI$sp(): Int = unbox(C1$class.head(C3.this));
            override <bridge> <specialized> <artifact> def head(): Object = scala.Int.box(C3.this.head());
            def <init>(): C3 = {
              C3.super.<init>();
              C1$class./*C1$class*/$init$(C3.this);
              C2$class./*C2$class*/$init$(C3.this);
              C2$mcI$sp$class./*C2$mcI$sp$class*/$init$(C3.this);
              ()
            }
          };
        

        Now, the problem is (I gather) that the names of super accessors are not properly mangled (I'm not sure where exactly that happens, but I guess a makeNotPrivate` call is supposed to do this, and it should happen some time during specialization).

        Even if I fix that, there is still the fundamental problem that I cannot call super.head from C2$mcI$sp:

        abstract <specialized> trait C2$mcI$sp extends Object with C2[Int] {
            ...
            // assuming proper name mangling for a super accessor
            override <specialized> def head$mcI$sp(): Int = C2$mcI$sp.this.C2$mcI$sp$$super$head$mcI$sp();
            ...
          }
        

        because that super.head will call this method:

        class C3 extends Object with C2$mcI$sp {
            ...
            override <superaccessor> <specialized> <artifact> def C2$mcI$sp$$super$head$mcI$sp(): Int = unbox(C2$class.head(C3.this));
        

        which in turn calls:

        abstract trait C2$class extends  {
          ...
          <specialized> def head$mcI$sp($this: C2): Int = scala.Int.unbox($this.head());
        

        And then you start spinning in a cycle of recursive calls and a stack overflow happens.

        Show
        Aleksandar Prokopec added a comment - I think the answer to the above is - yes, the `baseClasses` call should go before the `exitingPickler` block. However, I don't think that this example is easily fixable. Let me illustrate why. From this: trait C1[A] { def head: A = sys.error("") } trait C2[@specialized(Int) A] extends C1[A] { override def head: A = super.head } class C3 extends C2[Int] After mixin I get a super accessor defined twice, once coming from the specialized class and once from a generic one: class C3 extends Object with C2$mcI$sp { override <superaccessor> <specialized> <artifact> def super$head(): Int = unbox(C2$class.head(C3.this)); override <superaccessor> <specialized> <artifact> def super$head$mcI$sp(): Int = unbox(C2$class.head(C3.this)); override <specialized> def head(): Int = C2$mcI$sp$class.head(C3.this); override <specialized> def head$mcI$sp(): Int = C2$mcI$sp$class.head$mcI$sp(C3.this); <superaccessor> <artifact> def super$head(): Object = C1$class.head(C3.this); <superaccessor> <specialized> <artifact> def super$head$mcI$sp(): Int = unbox(C1$class.head(C3.this)); override <bridge> <specialized> <artifact> def head(): Object = scala.Int.box(C3.this.head()); def <init>(): C3 = { C3.super.<init>(); C1$class./*C1$class*/$init$(C3.this); C2$class./*C2$class*/$init$(C3.this); C2$mcI$sp$class./*C2$mcI$sp$class*/$init$(C3.this); () } }; Now, the problem is (I gather) that the names of super accessors are not properly mangled (I'm not sure where exactly that happens, but I guess a makeNotPrivate` call is supposed to do this, and it should happen some time during specialization). Even if I fix that, there is still the fundamental problem that I cannot call super.head from C2$mcI$sp : abstract <specialized> trait C2$mcI$sp extends Object with C2[Int] { ... // assuming proper name mangling for a super accessor override <specialized> def head$mcI$sp(): Int = C2$mcI$sp.this.C2$mcI$sp$$super$head$mcI$sp(); ... } because that super.head will call this method: class C3 extends Object with C2$mcI$sp { ... override <superaccessor> <specialized> <artifact> def C2$mcI$sp$$super$head$mcI$sp(): Int = unbox(C2$class.head(C3.this)); which in turn calls: abstract trait C2$class extends { ... <specialized> def head$mcI$sp($this: C2): Int = scala.Int.unbox($this.head()); And then you start spinning in a cycle of recursive calls and a stack overflow happens.
        Hide
        Aleksandar Prokopec added a comment -

        Assigning to Scala meeting.

        Show
        Aleksandar Prokopec added a comment - Assigning to Scala meeting.
        Hide
        Adriaan Moors added a comment -

        Unassigning and rescheduling to M6 as previous deadline was missed.

        Show
        Adriaan Moors added a comment - Unassigning and rescheduling to M6 as previous deadline was missed.
        Show
        Vlad Ureche added a comment - https://github.com/scala/scala/pull/2903

          People

          • Assignee:
            Vlad Ureche
            Reporter:
            Jason Zaugg
            TracCC:
            Daniel Ramage, Ismael Juma, Seth Tisue
          • Votes:
            1 Vote for this issue
            Watchers:
            8 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development