Skip to content

Split uninitialized logic from erased definition logic #12096

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Compiler {
new ElimByName, // Expand by-name parameter references
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
new InlinePatterns, // Remove placeholders of inlined patterns
new VCInlineMethods, // Inlines calls to value class methods
new SeqLiterals, // Express vararg arguments as arrays
Expand Down
39 changes: 8 additions & 31 deletions compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ import ast.tpd
* The phase also replaces all expressions that appear in an erased context by
* default values. This is necessary so that subsequent checking phases such
* as IsInstanceOfChecker don't give false negatives.
* Finally, the phase replaces `compiletime.uninitialized` on the right hand side
* of a mutable field definition by `_`. This avoids a "is declared erased, but is
* in fact used" error in Erasure and communicates to Constructors that the
* variable does not have an initializer.
*/
class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
import tpd._
Expand All @@ -34,39 +30,20 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
override def runsAfterGroupsOf: Set[String] = Set(RefChecks.name, ExplicitOuter.name)

override def transformSym(sym: SymDenotation)(using Context): SymDenotation =
if (sym.isEffectivelyErased && sym.isTerm && !sym.is(Private) && sym.owner.isClass)
sym.copySymDenotation(initFlags = sym.flags | Private)
else sym
if !sym.isEffectivelyErased || !sym.isTerm || sym.is(Private) || !sym.owner.isClass then sym
else sym.copySymDenotation(initFlags = sym.flags | Private)

override def transformApply(tree: Apply)(using Context): Tree =
if (tree.fun.tpe.widen.isErasedMethod)
cpy.Apply(tree)(tree.fun, tree.args.map(trivialErasedTree))
else tree

private def hasUninitializedRHS(tree: ValOrDefDef)(using Context): Boolean =
def recur(rhs: Tree): Boolean = rhs match
case rhs: RefTree =>
rhs.symbol == defn.Compiletime_uninitialized
&& tree.symbol.is(Mutable) && tree.symbol.owner.isClass
case closureDef(ddef) if defn.isContextFunctionType(tree.tpt.tpe.dealias) =>
recur(ddef.rhs)
case _ =>
false
recur(tree.rhs)
if !tree.fun.tpe.widen.isErasedMethod then tree
else cpy.Apply(tree)(tree.fun, tree.args.map(trivialErasedTree))

override def transformValDef(tree: ValDef)(using Context): Tree =
val sym = tree.symbol
if tree.symbol.isEffectivelyErased && !tree.rhs.isEmpty then
cpy.ValDef(tree)(rhs = trivialErasedTree(tree))
else if hasUninitializedRHS(tree) then
cpy.ValDef(tree)(rhs = cpy.Ident(tree.rhs)(nme.WILDCARD).withType(tree.tpt.tpe))
else
tree
if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree
else cpy.ValDef(tree)(rhs = trivialErasedTree(tree))

override def transformDefDef(tree: DefDef)(using Context): Tree =
if (tree.symbol.isEffectivelyErased && !tree.rhs.isEmpty)
cpy.DefDef(tree)(rhs = trivialErasedTree(tree))
else tree
if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree
else cpy.DefDef(tree)(rhs = trivialErasedTree(tree))

private def trivialErasedTree(tree: Tree)(using Context): Tree =
tree.tpe.widenTermRefExpr.dealias.normalized match
Expand Down
49 changes: 49 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package dotty.tools.dotc
package transform

import core._
import Contexts._
import DenotTransformers.SymTransformer
import Flags._
import SymDenotations._
import Symbols._
import Types._
import typer.RefChecks
import MegaPhase.MiniPhase
import StdNames.nme
import ast.tpd

/** This phase replaces `compiletime.uninitialized` on the right hand side of a mutable field definition by `_`.
* This avoids a
* ```scala
* "@compileTimeOnly("`uninitialized` can only be used as the right hand side of a mutable field definition")`
* ```
* error in Erasure and communicates to Constructors that the variable does not have an initializer.
*
* @syntax markdown
*/
class UninitializedDefs extends MiniPhase:
import tpd._

override def phaseName: String = UninitializedDefs.name

override def transformValDef(tree: ValDef)(using Context): Tree =
if !hasUninitializedRHS(tree) then tree
else cpy.ValDef(tree)(rhs = cpy.Ident(tree.rhs)(nme.WILDCARD).withType(tree.tpt.tpe))

private def hasUninitializedRHS(tree: ValOrDefDef)(using Context): Boolean =
def recur(rhs: Tree): Boolean = rhs match
case rhs: RefTree =>
rhs.symbol == defn.Compiletime_uninitialized
&& tree.symbol.is(Mutable) && tree.symbol.owner.isClass
case closureDef(ddef) if defn.isContextFunctionType(tree.tpt.tpe.dealias) =>
recur(ddef.rhs)
case _ =>
false
recur(tree.rhs)

end UninitializedDefs

object UninitializedDefs:
val name: String = "uninitializedDefs"
end UninitializedDefs