Skip to content

Dotty doesn't always detect Constants #8714

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

Closed
danlehmann opened this issue Apr 13, 2020 · 4 comments
Closed

Dotty doesn't always detect Constants #8714

danlehmann opened this issue Apr 13, 2020 · 4 comments

Comments

@danlehmann
Copy link

I wrote a test program that tries out four of defining a constant on an Object:

  • val
  • private val
  • inline val
  • inline private val

I also tried typing the constants as Int and as Byte, as well as omitting the type altogether.

Minimized code

import scala.util.Random

object ConstantTester {
  val byteConstant: Byte = 0
  val intConstant: Int = 1
  val untypedConstant = 2

  inline val inlinedByteConstant: Byte = 3
  inline val inlinedIntConstant: Int = 4
  inline val inlinedUntypedConstant = 5

  private val privateByteConstant: Byte = 0
  private val privateIntConstant: Int = 1
  private val privateUntypedConstant = 2

  inline private val privateInlinedByteConstant: Byte = 3
  inline private val privateInlinedIntConstant: Int = 4
  inline private val privateInlinedUntypedConstant = 5

  def main(args: Array[String]): Unit = {
    val int1 = byteConstant + intConstant + untypedConstant
    val int2 = inlinedByteConstant + inlinedIntConstant + inlinedUntypedConstant
    val int3 = privateByteConstant + privateIntConstant + privateUntypedConstant
    val int4 = privateInlinedByteConstant + privateInlinedIntConstant + privateInlinedUntypedConstant
  }
}

Output

$ ~/bin/dotty-0.23.0-RC1/bin/dotc test.scala
failure to convert Constant(true) to TypeRef(ThisType(TypeRef(NoPrefix,module class scala)),class Byte)

I then ran the generated .class through Luyten decompiler. It turns out that depending on how I declared the value, dotc will do one of five things:

  • Have a final field AND an accessor (e.g. byteConstant)
  • Inline the value, but keep the public accessor (e.g. inlinedByteConstant)
  • Inline the accessor, but still have a field (e.g. privateInlinedByteConstant)
  • Properly inline the value, but declare an unused field in the constructor (privateInlinedUntypedConstant)
  • Properly inline the value (privateInlinedUntypedConstant)
import java.io.*;
import scala.runtime.*;

public final class ConstantTester$ implements Serializable
{
    public static final ConstantTester$ MODULE$;
    private final byte byteConstant;
    private final int intConstant;
    private final int untypedConstant;
    private final byte privateByteConstant;
    private final int privateIntConstant;
    private final int privateUntypedConstant;
    private final byte privateInlinedByteConstant;
    private final int privateInlinedIntConstant;
    
    static {
        new ConstantTester$();
    }
    
    private ConstantTester$() {
        MODULE$ = this;
        this.byteConstant = 0;
        this.intConstant = 1;
        this.untypedConstant = 2;
        this.privateByteConstant = 6;
        this.privateIntConstant = 7;
        this.privateUntypedConstant = 8;
        this.privateInlinedByteConstant = 9;
        this.privateInlinedIntConstant = 10;
        final int privateInlinedUntypedConstant = 11;
    }
    
    private Object writeReplace() {
        return new ModuleSerializationProxy((Class)ConstantTester$.class);
    }
    
    public byte byteConstant() {
        return this.byteConstant;
    }
    
    public int intConstant() {
        return this.intConstant;
    }
    
    public int untypedConstant() {
        return this.untypedConstant;
    }
    
    public byte inlinedByteConstant() {
        return 3;
    }
    
    public int inlinedIntConstant() {
        return 4;
    }
    
    public int inlinedUntypedConstant() {
        return 5;
    }
    
    public void main(final String[] args) {
        final int int1 = this.byteConstant() + this.intConstant() + this.untypedConstant();
        final int int2 = this.inlinedByteConstant() + this.inlinedIntConstant() + 5;
        final int int3 = this.privateByteConstant + this.privateIntConstant + this.privateUntypedConstant;
        final int int4 = this.privateInlinedByteConstant + this.privateInlinedIntConstant + 11;
    }
}

Expectation

I had a few expectations that were broken:

  1. I wouldn't expect "val i = 10" to differ from "val i: Int = 10", but only untyped constants ever get fully inlined
  2. Sometimes "inline" only inlines the accessor or inlines a constant into the accessor. My expectation would be that inlining actually gets me the raw constant in every case
  3. Given that these are all on an Object, I'd expect everything to be fully inlined, even if I don't specify "inline" at all
@nicolasstucki
Copy link
Contributor

The specification inline val is the same as the one for final val without an explicit type, except that the type must be inalienable. Inlining is performed based uniquely on the type return type.

  • All inline val should have a literal type as return type
    • This begs the question of how to encode Byte and Short values.
  • All inline val should be effectively final
  • If they do not implement or override any def or val they can be made erased (as with inline def)
  • private inline should not widen their return type

@nicolasstucki
Copy link
Contributor

Now the following code inlines all the inline vals and generates not fields/getters for them.

import scala.util.Random

object ConstantTester {
  val byteConstant: Byte = 0
  val intConstant: Int = 1
  val untypedConstant = 2

  // inline val inlinedByteConstant: Byte = 3
  // inline val inlinedIntConstant: Int = 4
  inline val inlinedUntypedConstant = 5

  private val privateByteConstant: Byte = 0
  private val privateIntConstant: Int = 1
  private val privateUntypedConstant = 2

  // inline private val privateInlinedByteConstant: Byte = 3
  // inline private val privateInlinedIntConstant: Int = 4
  inline private val privateInlinedUntypedConstant = 5

  def main(args: Array[String]): Unit = {
    val int1 = byteConstant + intConstant + untypedConstant
    val int2 = 1 + 2 + inlinedUntypedConstant
    val int3 = privateByteConstant + privateIntConstant + privateUntypedConstant
    val int4 = 2 + 3 + privateInlinedUntypedConstant
  }
}
package <empty> {
  @scala.annotation.internal.SourceFile("Foo.scala") final module class 
    ConstantTester$
   extends Object, java.io.Serializable {
    def <init>(): Unit = 
      {
        super()
        ConstantTester.byteConstant = 0
        ConstantTester.intConstant = 1
        ConstantTester.untypedConstant = 2
        ConstantTester.privateByteConstant = 0
        ConstantTester.privateIntConstant = 1
        ConstantTester.privateUntypedConstant = 2
        ()
      }
    private def writeReplace(): Object = 
      new scala.runtime.ModuleSerializationProxy(classOf[ConstantTester$])
    private val byteConstant: Byte
    def byteConstant(): Byte = ConstantTester.byteConstant
    private val intConstant: Int
    def intConstant(): Int = ConstantTester.intConstant
    private val untypedConstant: Int
    def untypedConstant(): Int = ConstantTester.untypedConstant
    private val privateByteConstant: Byte
    private val privateIntConstant: Int
    private val privateUntypedConstant: Int
    def main(args: String[]): Unit = 
      {
        val int1: Int = 
          ConstantTester.byteConstant().+(ConstantTester.intConstant()).+(
            ConstantTester.untypedConstant()
          )
        val int2: Int = 8
        val int3: Int = 
          ConstantTester.privateByteConstant.+(ConstantTester.privateIntConstant
            )
          .+(ConstantTester.privateUntypedConstant)
        val int4: Int = 10
        ()
      }
  }
  final lazy module val ConstantTester: ConstantTester$ = new ConstantTester$()
}

@nicolasstucki
Copy link
Contributor

Fixed by #8836 and #8724.

@danlehmann
Copy link
Author

Thanks, looking forward to trying this out in the next release!

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

2 participants