Skip to content

"Value is declared as erased, but is in fact used" on inline accessor proxy of erased private member #26190

Description

@starswap

Compiler version

3.8.2, same outcome on latest main.

Minimized code

import language.experimental.erasedDefinitions

object Foo:
    private erased val evidence: Boolean = true
    inline def foo = Bar.bar(evidence)

object Bar:
    def bar(erased x: Boolean) = println("Hello World")

def main = 
    Foo.foo

Output

-- Error: tests/pos/inline-private-erased-must-propagate.scala:13:23 -----------
13 |    private erased val evidence: Boolean = true
   |                       ^
   |             value evidence is declared as `erased`, but is in fact used
-- [E217] Type Error: tests/pos/inline-private-erased-must-propagate.scala:20:8 
20 |    Foo.foo
   |    ^^^^^^^
   |    argument to an erased parameter fails to be a pure expression
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from inline-private-erased-must-propagate.scala:14
14 |    inline def foo = Bar.bar(evidence)
   |                             ^^^^^^^^
    ----------------------------------------------------------------------------

Expectation

Should compile. Compare to the following which does compile:

import language.experimental.erasedDefinitions

object Foo:
    erased val evidence: Boolean = true
    inline def foo = Bar.bar(evidence)

object Bar:
    def bar(erased x: Boolean) = println("Hello World")

def main = 
    Foo.foo

(only difference is that we remove private; the uses of evidence stay the same)

Looking at -Vprint:erasure:

package <empty> {
  @experimental(
    
      "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
      
  ) final lazy module val Foo: Foo = new Foo()
  @SourceFile("tests/pos/inline-private-erased-must-propagate.scala")
    @experimental(
    
      "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
      
  ) final module class Foo() extends Object() {
    private def writeReplace(): Object =
      new scala.runtime.ModuleSerializationProxy(classOf[Foo])
    final def inline$evidence(): Boolean = Foo.evidence
  }
  @experimental(
    
      "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
      
  ) final lazy module val Bar: Bar = new Bar()
  @SourceFile("tests/pos/inline-private-erased-must-propagate.scala")
    @experimental(
    
      "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
      
  ) final module class Bar() extends Object() {
    private def writeReplace(): Object =
      new scala.runtime.ModuleSerializationProxy(classOf[Bar])
    def bar(): Unit = println("Hello World")
  }
  @experimental(
    
      "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
      
  ) final lazy module val inline-private-erased-must-propagate$package:
    inline-private-erased-must-propagate$package =
    new inline-private-erased-must-propagate$package()
  @SourceFile("tests/pos/inline-private-erased-must-propagate.scala") final
    module class inline-private-erased-must-propagate$package() extends Object()
     {
    private def writeReplace(): Object =
      new scala.runtime.ModuleSerializationProxy(
        classOf[inline-private-erased-must-propagate$package])
    @experimental(
      
        "Added by top level \u001b[33mimport \u001b[0mlanguage.experimental.erasedDefinitions"
        
    ) def main(): Unit = Bar.bar()
  }
}

We can see that the only use site is in the inline accessor proxy which is itself unused.

Fix

We should copy the erased flag onto the inline accessor proxy if it accesses a property which is erased, so that:

  • If someone uses the inline access proxy in a non-erased context the first error is still thrown ("declared as erased but still used").
  • If the inline access proxy is not referred to (except in an erased context, like in this example), the code is allowed to pass.

The second error is a result of the same problem, because the check runs in a pre-erasure context where we are passing the inline$evidence parameter as for x. Making the accessor proxy erased will also get rid of this error because x is erased and we are allowed to pass erased vals as erased parameters (they are treated as pure) and so the code will compile, which is what we want.

Ran into this while working on my specialized traits work (#26156), as we generate the Specialized evidences as erased.

Metadata

Metadata

Assignees

No one assigned

    Labels

    itype:bugstat:needs triageEvery issue needs to have an "area" and "itype" label

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions