Skip to content

Make erased capability-safe #23419

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jun 25, 2025

The purpose of erased is to give compile-time evidence without requiring a runtime value. The compile time evidence is typically for a type class instance or a capability. The previous design of erased is not safe in this respect, as any value can be summoned by various means, including

  • an erasedValue expression of the required type,
  • a null value cast to the required type,
  • An erased def or lazy val that is defined as itself using a a loop.

This PR implements a set of changes to make erased a trustable evidence.

  • Drop erased def.
  • Require erased arguments and initializers to be pure expressions.
  • Don't treat erased vals as late initialized.
  • Don't convert erased vals to getters.
  • Add internal and unsafe variants of erasedValue, which, unlike erasedValue itself,
    are treated as pure expressions.

odersky added 11 commits June 27, 2025 14:22
Since we will force erased expressions to be pure values, they are always
initialized.
# Conflicts:
#	compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
 - erasedValue[<ConstantType>] is now considered to be pure
 - calls of synthetic case class apply are considered pure if the case
   class is NoInits
 - Companions of Scala-2 classes Tuple and Some are assumed to be NoInits
@@ -6,15 +6,15 @@ object App {
trait A { type L >: Any}
def upcast(erased a: A)(x: Any): a.L = x
erased val p: A { type L <: Nothing } = p
def coerce(x: Any): Int = upcast(p)(x) // error
def coerce(x: Any): Int = upcast(p)(x) // ok?

def coerceInline(x: Any): Int = upcast(compiletime.erasedValue[A {type L <: Nothing}])(x) // error

trait B { type L <: Nothing }
def upcast_dep_parameter(erased a: B)(x: a.L) : Int = x
erased val q : B { type L >: Any } = compiletime.erasedValue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be rejected? This should be unsafeErasedValue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It gets rejected for a different reason before.

@@ -6,15 +6,15 @@ object App {
trait A { type L >: Any}
def upcast(erased a: A)(x: Any): a.L = x
erased val p: A { type L <: Nothing } = p
def coerce(x: Any): Int = upcast(p)(x) // error
def coerce(x: Any): Int = upcast(p)(x) // ok?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be rejected, perhaps by initialization checker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's not part of the test. If we turn on the initialization checker we won't make it to erasure to detect the other problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, all that remains is a realizability check. The rest is would be detected if the check is omitted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants