Skip to content

Commit 27335e1

Browse files
authored
Merge pull request scala#6764 from som-snytt/issue/yimports-2.13
Support -Yimports for implicit preamble imports
2 parents 30e689c + 15b1222 commit 27335e1

29 files changed

+273
-59
lines changed

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import scala.language.existentials
1515
import scala.annotation.elidable
1616
import scala.tools.util.PathResolver.Defaults
1717
import scala.collection.mutable
18+
import scala.reflect.internal.util.StringContextStripMarginOps
1819

1920
trait ScalaSettings extends AbsScalaSettings
2021
with StandardScalaSettings
@@ -201,7 +202,30 @@ trait ScalaSettings extends AbsScalaSettings
201202
val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.")
202203
val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.")
203204
val noimports = BooleanSetting ("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")
205+
.withPostSetHook(bs => if (bs) imports.value = Nil)
204206
val nopredef = BooleanSetting ("-Yno-predef", "Compile without importing Predef.")
207+
.withPostSetHook(bs => if (bs && !noimports) imports.value = "java.lang" :: "scala" :: Nil)
208+
val imports = MultiStringSetting(name="-Yimports", arg="import", descr="Custom root imports, default is `java.lang,scala,scala.Predef`.", helpText=Some(
209+
sm"""|Specify a list of packages and objects to import from as "root" imports.
210+
|Root imports form the root context in which all Scala source is evaluated.
211+
|The names supplied to `-Yimports` must be fully-qualified.
212+
|
213+
|For example, the default scala.Predef results in an `import scala.Predef._`.
214+
|Ordinary access and scoping rules apply. Root imports increase the scoping
215+
|depth, so that later root imports shadow earlier ones. In addition,
216+
|names bound by root imports have lowest binding precedence, so that they
217+
|cannot induce ambiguities in user code, where definitions and imports
218+
|always have a higher precedence. Root imports are imports of last resort.
219+
|
220+
|By convention, an explicit import from a root import object such as
221+
|Predef disables that root import for the current source file. The import
222+
|is disabled when the import expression is compiled, so, also by convention,
223+
|the import should be placed early in source code order. The textual name
224+
|in the import does not need to match the value of `-Yimports`; the import
225+
|works in the usual way, subject to renames and name binding precedence.
226+
|
227+
"""
228+
))
205229
val Yrecursion = IntSetting ("-Yrecursion", "Set recursion depth used when locking symbols.", 0, Some((0, Int.MaxValue)), (_: String) => None)
206230
val Xshowtrees = BooleanSetting ("-Yshow-trees", "(Requires -Xprint:) Print detailed ASTs in formatted form.")
207231
val XshowtreesCompact

src/compiler/scala/tools/nsc/typechecker/Contexts.scala

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,31 +79,63 @@ trait Contexts { self: Analyzer =>
7979

8080
var lastAccessCheckDetails: String = ""
8181

82-
/** List of symbols to import from in a root context. Typically that
83-
* is `java.lang`, `scala`, and [[scala.Predef]], in that order. Exceptions:
82+
val rootImportsCached = perRunCaches.newMap[CompilationUnit, List[Symbol]]
83+
84+
val excludedRootImportsCached = perRunCaches.newMap[CompilationUnit, List[Symbol]]
85+
86+
// register an import for the narrow purpose of excluding root imports of predef modules
87+
def registerImport(ctx: Context, imp: Import): Unit = {
88+
val sym = imp.expr.symbol
89+
if (!sym.isPackage && ctx.enclosingNonImportContext.owner.isPackage && rootImports(ctx.unit).contains(sym)) {
90+
var current = excludedRootImportsCached.get(ctx.unit).getOrElse(Nil)
91+
current = sym :: current
92+
excludedRootImportsCached += ctx.unit -> current
93+
}
94+
}
95+
96+
/** List of symbols to import from in a root context. By default, that
97+
* is `java.lang`, `scala`, and [[scala.Predef]], in that order.
8498
*
85-
* - if option `-Yno-imports` is given, nothing is imported
86-
* - if the unit is java defined, only `java.lang` is imported
87-
* - if option `-Yno-predef` is given, if the unit body has an import of Predef
99+
* - if option `-Yimports` is supplied, then that specifies the preamble imports
100+
* - if the unit body has an import of Predef
88101
* among its leading imports, or if the tree is [[scala.Predef]], `Predef` is not imported.
102+
* Similarly for any module among the preamble imports.
103+
* - if the unit is java defined, only `java.lang` is imported
104+
*
105+
* The root imports for a unit are cached.
89106
*/
90107
protected def rootImports(unit: CompilationUnit): List[Symbol] = {
91108
assert(definitions.isDefinitionsInitialized, "definitions uninitialized")
92109

93-
if (settings.noimports) Nil
94-
else if (unit.isJava) RootImports.javaList
95-
else if (settings.nopredef || treeInfo.noPredefImportForUnit(unit.body)) {
96-
// scala/bug#8258 Needed for the presentation compiler using -sourcepath, otherwise cycles can occur. See the commit
97-
// message for this ticket for an example.
98-
debuglog("Omitted import of Predef._ for " + unit)
99-
RootImports.javaAndScalaList
110+
if (unit.isJava) RootImports.javaList
111+
else rootImportsCached.get(unit).getOrElse {
112+
val calculated = defaultRootImports
113+
rootImportsCached += unit -> calculated
114+
calculated
100115
}
101-
else RootImports.completeList
102116
}
103117

118+
private def defaultRootImports: List[Symbol] = {
119+
import rootMirror.{getModuleIfDefined, getPackageObjectIfDefined, getPackageIfDefined}
120+
121+
if (settings.imports.isSetByUser)
122+
settings.imports.value.map {
123+
case "java.lang" => JavaLangPackage
124+
case "scala" => ScalaPackage
125+
case "scala.Predef" => PredefModule
126+
case s =>
127+
getModuleIfDefined(s) orElse
128+
getPackageObjectIfDefined(s) orElse
129+
getPackageIfDefined(s) orElse {
130+
error(s"bad preamble import $s")
131+
NoSymbol
132+
}
133+
}
134+
else RootImports.completeList
135+
}
104136

105137
def rootContext(unit: CompilationUnit, tree: Tree = EmptyTree, throwing: Boolean = false, checking: Boolean = false): Context = {
106-
val rootImportsContext = rootImports(unit).foldLeft(startContext)((c, sym) => c.make(gen.mkWildcardImport(sym)))
138+
val rootImportsContext = rootImports(unit).foldLeft(startContext)((c, sym) => c.make(gen.mkWildcardImport(sym), unit = unit))
107139

108140
// there must be a scala.xml package when xml literals were parsed in this unit
109141
if (unit.hasXml && ScalaXmlPackage == NoSymbol)
@@ -975,7 +1007,7 @@ trait Contexts { self: Analyzer =>
9751007
for (sym <- syms.toList if isQualifyingImplicit(sym.name, sym, pre, imported)) yield
9761008
new ImplicitInfo(sym.name, pre, sym)
9771009

978-
private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = {
1010+
private def collectImplicitImports(imp: ImportInfo): List[ImplicitInfo] = if (isExcludedRootImport(imp)) List() else {
9791011
val qual = imp.qual
9801012

9811013
val pre = qual.tpe
@@ -1085,12 +1117,6 @@ trait Contexts { self: Analyzer =>
10851117
def mt1 = t1 memberType imp1Symbol
10861118
def mt2 = t2 memberType imp2Symbol
10871119

1088-
def characterize = List(
1089-
s"types: $t1 =:= $t2 ${t1 =:= t2} members: ${mt1 =:= mt2}",
1090-
s"member type 1: $mt1",
1091-
s"member type 2: $mt2"
1092-
).mkString("\n ")
1093-
10941120
if (!ambiguous || !imp2Symbol.exists) Some(imp1)
10951121
else if (!imp1Symbol.exists) Some(imp2)
10961122
else (
@@ -1110,7 +1136,10 @@ trait Contexts { self: Analyzer =>
11101136
Some(imp1)
11111137
}
11121138
else {
1113-
log(s"Import is genuinely ambiguous:\n " + characterize)
1139+
log(s"""Import is genuinely ambiguous:
1140+
| types: $t1 =:= $t2 ${t1 =:= t2} members: ${mt1 =:= mt2}
1141+
| member type 1: $mt1
1142+
| member type 2: $mt2""".stripMargin)
11141143
None
11151144
}
11161145
)
@@ -1138,7 +1167,11 @@ trait Contexts { self: Analyzer =>
11381167
* if any such symbol is accessible from this context.
11391168
*/
11401169
private def importedAccessibleSymbol(imp: ImportInfo, name: Name, requireExplicit: Boolean, record: Boolean): Symbol =
1141-
imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
1170+
if (isExcludedRootImport(imp)) NoSymbol
1171+
else imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))
1172+
1173+
private def isExcludedRootImport(imp: ImportInfo): Boolean =
1174+
imp.isRootImport && excludedRootImportsCached.get(unit).exists(_.contains(imp.qual.symbol))
11421175

11431176
private def requiresQualifier(s: Symbol): Boolean = (
11441177
s.owner.isClass
@@ -1177,11 +1210,10 @@ trait Contexts { self: Analyzer =>
11771210
case _ => LookupSucceeded(qual, sym)
11781211
}
11791212
)
1180-
def finishDefSym(sym: Symbol, pre0: Type): NameLookup =
1181-
if (requiresQualifier(sym))
1182-
finish(gen.mkAttributedQualifier(pre0), sym)
1183-
else
1184-
finish(EmptyTree, sym)
1213+
def finishDefSym(sym: Symbol, pre0: Type): NameLookup = {
1214+
val qual = if (requiresQualifier(sym)) gen.mkAttributedQualifier(pre0) else EmptyTree
1215+
finish(qual, sym)
1216+
}
11851217

11861218
def lookupInPrefix(name: Name) = {
11871219
val sym = pre.member(name).filter(qualifies)

src/compiler/scala/tools/nsc/typechecker/Namers.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1781,6 +1781,7 @@ trait Namers extends MethodSynthesis {
17811781
val newImport = treeCopy.Import(imp, expr1, selectors).asInstanceOf[Import]
17821782
checkSelectors(newImport)
17831783
context.unit.transformed(imp) = newImport
1784+
registerImport(context, newImport)
17841785
// copy symbol and type attributes back into old expression
17851786
// so that the structure builder will find it.
17861787
expr setSymbol expr1.symbol setType expr1.tpe

src/reflect/scala/reflect/internal/TreeInfo.scala

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ abstract class TreeInfo {
835835
}
836836

837837
/** Does list of trees start with a definition of
838-
* a class of module with given name (ignoring imports)
838+
* a class or module with given name (ignoring imports)
839839
*/
840840
def firstDefinesClassOrObject(trees: List[Tree], name: Name): Boolean = trees match {
841841
case Import(_, _) :: xs => firstDefinesClassOrObject(xs, name)
@@ -859,25 +859,6 @@ abstract class TreeInfo {
859859
}
860860
}
861861

862-
/** Is this file the body of a compilation unit which should not
863-
* have Predef imported?
864-
*/
865-
def noPredefImportForUnit(body: Tree) = {
866-
// Top-level definition whose leading imports include Predef.
867-
def isLeadingPredefImport(defn: Tree): Boolean = defn match {
868-
case PackageDef(_, defs1) => defs1 exists isLeadingPredefImport
869-
case Import(expr, _) => isReferenceToPredef(expr)
870-
case _ => false
871-
}
872-
// Compilation unit is class or object 'name' in package 'scala'
873-
def isUnitInScala(tree: Tree, name: Name) = tree match {
874-
case PackageDef(Ident(nme.scala_), defs) => firstDefinesClassOrObject(defs, name)
875-
case _ => false
876-
}
877-
878-
isUnitInScala(body, nme.Predef) || isLeadingPredefImport(body)
879-
}
880-
881862
def isAbsTypeDef(tree: Tree) = tree match {
882863
case TypeDef(_, _, _, TypeBoundsTree(_, _)) => true
883864
case TypeDef(_, _, _, rhs) => rhs.tpe.isInstanceOf[TypeBounds]

src/reflect/scala/reflect/internal/Trees.scala

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,17 +1307,6 @@ trait Trees extends api.Trees {
13071307
}
13081308
}
13091309

1310-
// Belongs in TreeInfo but then I can't reach it from Printers.
1311-
def isReferenceToScalaMember(t: Tree, Id: Name) = t match {
1312-
case Ident(Id) => true
1313-
case Select(Ident(nme.scala_), Id) => true
1314-
case Select(Select(Ident(nme.ROOTPKG), nme.scala_), Id) => true
1315-
case _ => false
1316-
}
1317-
/** Is the tree Predef, scala.Predef, or _root_.scala.Predef?
1318-
*/
1319-
def isReferenceToPredef(t: Tree) = isReferenceToScalaMember(t, nme.Predef)
1320-
13211310
// --- modifiers implementation ---------------------------------------
13221311

13231312
/** @param privateWithin the qualifier for a private (a type name)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
C_2.scala:8: error: not found: type Numb
2+
val v: Numb = Answer
3+
^
4+
C_2.scala:9: error: not found: value println
5+
def greet() = println("hello, world!")
6+
^
7+
two errors found
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// scalac: -Yimports:hello.world.minidef
2+
3+
import hello.{world => hw}
4+
import hw.minidef.{Magic => Answer}
5+
6+
// Finds the answer, but dumb to forget Numb
7+
class C {
8+
val v: Numb = Answer
9+
def greet() = println("hello, world!")
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// scalac: -Yimports:scala
2+
3+
package hello.world
4+
5+
object minidef {
6+
type Numb = Int
7+
final val Magic = 42
8+
}

test/files/neg/yimports-custom.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
C_2.scala:5: error: not found: value println
2+
def greet() = println("hello, world!")
3+
^
4+
one error found
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// scalac: -Yimports:hello.world.minidef
2+
3+
class C {
4+
val v: Numb = Magic
5+
def greet() = println("hello, world!")
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
package hello.world
3+
4+
object minidef {
5+
type Numb = Int
6+
final val Magic = 42
7+
}

test/files/neg/yimports-masked.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
C_2.scala:11: error: not found: type Numb
2+
val v: Numb = Answer
3+
^
4+
C_2.scala:12: error: not found: value println
5+
def greet() = println("hello, world!")
6+
^
7+
two errors found
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// scalac: -Yimports:scala,hello.world.minidef
2+
3+
// import at top level or top of package disables implicit import.
4+
// the import can appear at any statement position, here, end of package.
5+
// Update: with new trick, the import has to be completed before usages.
6+
7+
import hello.world.minidef.{Magic => Answer}
8+
9+
package p {
10+
class C {
11+
val v: Numb = Answer
12+
def greet() = println("hello, world!")
13+
}
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
package hello.world
3+
4+
object minidef {
5+
type Numb = Int
6+
final val Magic = 42
7+
}

test/files/neg/yimports-nojava.check

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
yimports-nojava.scala:5: error: not found: type Integer
2+
def g() = new Integer(42)
3+
^
4+
yimports-nojava.scala:6: error: not found: value Thread
5+
def sleep() = Thread.sleep(42000L)
6+
^
7+
two errors found

test/files/neg/yimports-nojava.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// scalac: -Yimports:scala,scala.Predef
2+
3+
trait T {
4+
def f() = println("hello, world!")
5+
def g() = new Integer(42)
6+
def sleep() = Thread.sleep(42000L)
7+
}

test/files/neg/yimports-nosuch.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
error: bad preamble import skala
2+
error: bad preamble import scala.Predeff
3+
two errors found

test/files/neg/yimports-nosuch.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// scalac: -Yimports:skala,scala.Predeff
2+
//
3+
class C

test/files/neg/yimports-order.check

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
yimports-order.scala:9: error: not found: value Map
2+
def f() = Map("hello" -> "world")
3+
^
4+
yimports-order.scala:9: error: value -> is not a member of String
5+
def f() = Map("hello" -> "world")
6+
^
7+
yimports-order.scala:10: error: not found: value println
8+
def g() = println(f())
9+
^
10+
three errors found

test/files/neg/yimports-order.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
package top {
3+
package middle {
4+
class C {
5+
def c() = println("hello, world")
6+
}
7+
import Predef.{Map => _}
8+
object Test {
9+
def f() = Map("hello" -> "world")
10+
def g() = println(f())
11+
}
12+
}
13+
}

test/files/neg/yimports-predef.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
yimports-predef.scala:6: error: value + is not a member of type parameter A
2+
def f[A](x: A) = x + 42
3+
^
4+
one error found

test/files/neg/yimports-predef.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// scalac: -Yimports:scala,scala.Predef
2+
//
3+
import Predef.{any2stringadd => _, _}
4+
5+
class classic {
6+
def f[A](x: A) = x + 42
7+
}

test/files/neg/yimports-stable.check

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
error: bad preamble import hello.world.potions
2+
one error found
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// scalac: -Yimports:scala,scala.Predef,hello.world.potions
2+
//
3+
class C {
4+
val v: Numb = magic
5+
def greet() = println("hello, world!")
6+
}

0 commit comments

Comments
 (0)