Skip to content

Dotty doesn't always detect Constants #8714

Closed
@danlehmann

Description

@danlehmann

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions