Skip to content

Commit

Permalink
Keep language imports around longer
Browse files Browse the repository at this point in the history
Some phases might need to know what language imports are active. Concretely, uner
explicit nulls, the exhaustivity checks for patterns need to know that. We therefore
keep language imports until phase PruneErasedDefs. Other imports are dropped in
FirstTransform, as before.

The handling of statements in MegaPhase was changed so that imports now are visible
in the context for transforming the following statements.

Fixes scala#14346
  • Loading branch information
odersky authored and olsdavis committed Apr 4, 2022
1 parent 2282b1d commit 37dae0f
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 7 deletions.
8 changes: 5 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/FirstTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ object FirstTransform {
}

/** The first tree transform
* - eliminates some kinds of trees: Imports, NamedArgs
* - eliminates some kinds of trees: Imports other than language imports,
* Exports, NamedArgs, type trees other than TypeTree
* - stubs out native methods
* - eliminates self tree in Template and self symbol in ClassInfo
* - collapses all type trees to trees of class TypeTree
Expand Down Expand Up @@ -58,7 +59,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass),
i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree")
case _: TypeTree =>
case _: Import | _: NamedArg | _: TypTree =>
case _: Export | _: NamedArg | _: TypTree =>
assert(false, i"illegal tree: $tree")
case _ =>
}
Expand Down Expand Up @@ -136,7 +137,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
}

override def transformOther(tree: Tree)(using Context): Tree = tree match {
case tree: ImportOrExport => EmptyTree
case tree: Import if untpd.languageImport(tree.expr).isEmpty => EmptyTree
case tree: Export => EmptyTree
case tree: NamedArg => transformAllDeep(tree.arg)
case tree => if (tree.isType) toTypeTree(tree) else tree
}
Expand Down
34 changes: 31 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/MegaPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -432,16 +432,44 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase {
def transformSpecificTree[T <: Tree](tree: T, start: Int)(using Context): T =
transformTree(tree, start).asInstanceOf[T]

def transformStats(trees: List[Tree], exprOwner: Symbol, start: Int)(using Context): List[Tree] = {
def transformStats(trees: List[Tree], exprOwner: Symbol, start: Int)(using Context): List[Tree] =

def transformStat(stat: Tree)(using Context): Tree = stat match {
case _: Import | _: DefTree => transformTree(stat, start)
case Thicket(stats) => cpy.Thicket(stat)(stats.mapConserve(transformStat))
case _ => transformTree(stat, start)(using ctx.exprContext(stat, exprOwner))
}

def restContext(tree: Tree)(using Context): Context = tree match
case imp: Import => ctx.importContext(imp, imp.symbol)
case _ => ctx

// A slower implementation that avoids deep recursions and stack overflows
def transformSlow(trees: List[Tree])(using Context): List[Tree] =
var curCtx = ctx
flatten(trees.mapConserve { tree =>
val tree1 = transformStat(tree)(using curCtx)
curCtx = restContext(tree)(using curCtx)
tree1
})

def recur(trees: List[Tree], count: Int)(using Context): List[Tree] =
if count > MapRecursionLimit then
transformSlow(trees)
else trees match
case tree :: rest =>
val tree1 = transformStat(tree)
val rest1 = recur(rest, count + 1)(using restContext(tree))
if (tree1 eq tree) && (rest1 eq rest) then trees
else tree1 match
case Thicket(elems1) => elems1 ::: rest1
case _ => tree1 :: rest1
case nil => nil

val nestedCtx = prepStats(trees, start)
val trees1 = trees.mapInline(transformStat(_)(using nestedCtx))
val trees1 = recur(trees, 0)(using nestedCtx)
goStats(trees1, start)(using nestedCtx)
}
end transformStats

def transformUnit(tree: Tree)(using Context): Tree = {
val nestedCtx = prepUnit(tree, 0)
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import StdNames.nme
import ast.tpd
import SymUtils._
import config.Feature
import Decorators.*

/** This phase makes all erased term members of classes private so that they cannot
* conflict with non-erased members. This is needed so that subsequent phases like
* ResolveSuper that inspect class members work correctly.
* 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 drops (language-) imports.
*/
class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
import tpd._
Expand Down Expand Up @@ -54,10 +56,18 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
checkErasedInExperimental(tree.symbol)
tree

override def transformOther(tree: Tree)(using Context): Tree = tree match
case tree: Import => EmptyTree
case _ => tree

def checkErasedInExperimental(sym: Symbol)(using Context): Unit =
// Make an exception for Scala 2 experimental macros to allow dual Scala 2/3 macros under non experimental mode
if sym.is(Erased, butNot = Macro) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then
Feature.checkExperimentalFeature("erased", sym.sourcePos)

override def checkPostCondition(tree: Tree)(using Context): Unit = tree match
case _: tpd.Import => assert(false, i"illegal tree: $tree")
case _ =>
}

object PruneErasedDefs {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2556,7 +2556,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
|The selector is not a member of an object or package.""")
else typd(imp.expr, AnySelectionProto)

def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import =
def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Tree =
val expr1 = typedImportQualifier(imp, typedExpr(_, _)(using ctx.withOwner(sym)))
checkLegalImportPath(expr1)
val selectors1 = typedSelectors(imp.selectors)
Expand Down

0 comments on commit 37dae0f

Please sign in to comment.