From 37dae0f84cc02b9c7e03427e8619ba56fafc1f34 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 26 Jan 2022 22:02:55 +0100 Subject: [PATCH] Keep language imports around longer 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 #14346 --- .../tools/dotc/transform/FirstTransform.scala | 8 +++-- .../tools/dotc/transform/MegaPhase.scala | 34 +++++++++++++++++-- .../dotc/transform/PruneErasedDefs.scala | 10 ++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 1ff9edda16d8..03ab3860a127 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -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 @@ -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 _ => } @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 77671fc6498c..a7a19d03e9ff 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index 7d8f0025eb15..e52b77379c27 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -14,6 +14,7 @@ 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 @@ -21,6 +22,7 @@ import config.Feature * 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._ @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4c0c10f7c026..02f3f6b4f164 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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)