Skip to content

Commit 077c599

Browse files
committed
Make sure language imports are detectable syntactically
Make sure that something is a language import if and only if it looks like one. i.e. is of one of the forms import language.xyz import scala.language.xyz import _root_.scala.language.xyz
1 parent ab3bc41 commit 077c599

File tree

6 files changed

+69
-26
lines changed

6 files changed

+69
-26
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,22 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
251251
case TypeDefs(_) => true
252252
case _ => isUsingClause(params)
253253

254+
private val languageImportParts = List(nme.language, nme.scala, nme.ROOTPKG)
255+
private val languageImportNested = Set[Name](nme.experimental)
256+
257+
/** Strip the name of any local object in `scala.language` from `path` */
258+
def stripLanguageNested(path: Tree): Tree = path match
259+
case Select(qual, name) if languageImportNested.contains(name) => qual
260+
case _ => path
261+
262+
/** Does this `path` look like a language import? */
263+
def isLanguageImport(path: Tree): Boolean =
264+
def parts(tree: Tree): List[Name] = tree match
265+
case tree: RefTree => tree.name :: parts(tree.qualifier)
266+
case EmptyTree => Nil
267+
case _ => EmptyTermName :: Nil
268+
!path.isEmpty && languageImportParts.startsWith(parts(stripLanguageNested(path)))
269+
254270
/** The underlying pattern ignoring any bindings */
255271
def unbind(x: Tree): Tree = unsplice(x) match {
256272
case Bind(_, y) => unbind(y)

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,8 @@ object Contexts {
474474
else fresh.setOwner(exprOwner)
475475

476476
/** A new context that summarizes an import statement */
477-
def importContext(imp: Import[?], sym: Symbol): FreshContext = {
478-
val impNameOpt = imp.expr match {
479-
case ref: RefTree[?] => Some(ref.name.asTermName)
480-
case _ => None
481-
}
482-
fresh.setImportInfo(ImportInfo(sym, imp.selectors, impNameOpt))
483-
}
477+
def importContext(imp: Import[?], sym: Symbol): FreshContext =
478+
fresh.setImportInfo(ImportInfo(sym, imp.selectors, imp.expr))
484479

485480
/** Is the debug option set? */
486481
def debug: Boolean = base.settings.Ydebug.value

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3068,11 +3068,7 @@ object Parsers {
30683068

30693069
/** Create an import node and handle source version imports */
30703070
def mkImport(outermost: Boolean = false): ImportConstr = (tree, selectors) =>
3071-
val isLanguageImport = tree match
3072-
case Ident(nme.language) => true
3073-
case Select(Ident(nme.scala), nme.language) => true
3074-
case _ => false
3075-
if isLanguageImport then
3071+
if isLanguageImport(tree) then
30763072
for
30773073
case ImportSelector(id @ Ident(imported), EmptyTree, _) <- selectors
30783074
if allSourceVersionNames.contains(imported)

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,9 +734,15 @@ trait Checking {
734734
}
735735

736736
/** Check that `path` is a legal prefix for an import clause */
737-
def checkLegalImportPath(path: Tree)(using Context): Unit = {
737+
def checkLegalImportPath(path: Tree)(using Context): Unit =
738738
checkLegalImportOrExportPath(path, "import prefix")
739-
}
739+
val normPath = stripLanguageNested(path)
740+
if isLanguageImport(normPath) then
741+
if normPath.symbol != defn.LanguageModule then
742+
report.error(em"import looks like a language import, but refers to something else: ${normPath.symbol.showLocated}", path.srcPos)
743+
else
744+
if normPath.tpe.classSymbols.contains(defn.LanguageModule.moduleClass) then
745+
report.error(em"no aliases can be used to refer to a language import", path.srcPos)
740746

741747
/** Check that `path` is a legal prefix for an export clause */
742748
def checkLegalExportPath(path: Tree, selectors: List[untpd.ImportSelector])(using Context): Unit =

compiler/src/dotty/tools/dotc/typer/ImportInfo.scala

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ object ImportInfo {
3333
val expr = tpd.Ident(ref.refFn()) // refFn must be called in the context of ImportInfo.sym
3434
tpd.Import(expr, selectors).symbol
3535

36-
ImportInfo(sym, selectors, None, isRootImport = true)
36+
ImportInfo(sym, selectors, untpd.EmptyTree, isRootImport = true)
3737

3838
extension (c: Context)
3939
def withRootImports(rootRefs: List[RootRef])(using Context): Context =
@@ -42,22 +42,26 @@ object ImportInfo {
4242
def withRootImports: Context =
4343
given Context = c
4444
c.withRootImports(defn.rootImportFns)
45-
4645
}
4746

4847
/** Info relating to an import clause
4948
* @param sym The import symbol defined by the clause
5049
* @param selectors The selector clauses
51-
* @param symNameOpt Optionally, the name of the import symbol. None for root imports.
50+
* @param qualifier The import qualifier, or EmptyTree for root imports.
5251
* Defined for all explicit imports from ident or select nodes.
5352
* @param isRootImport true if this is one of the implicit imports of scala, java.lang,
5453
* scala.Predef in the start context, false otherwise.
5554
*/
5655
class ImportInfo(symf: Context ?=> Symbol,
5756
val selectors: List[untpd.ImportSelector],
58-
symNameOpt: Option[TermName],
57+
val qualifier: untpd.Tree,
5958
val isRootImport: Boolean = false) extends Showable {
6059

60+
private def symNameOpt = qualifier match {
61+
case ref: untpd.RefTree => Some(ref.name.asTermName)
62+
case _ => None
63+
}
64+
6165
def sym(using Context): Symbol = {
6266
if (mySym == null) {
6367
mySym = symf
@@ -177,6 +181,8 @@ class ImportInfo(symf: Context ?=> Symbol,
177181
assert(myUnimported != null)
178182
myUnimported
179183

184+
private val isLanguageImport: Boolean = untpd.isLanguageImport(qualifier)
185+
180186
private var myUnimported: Symbol = _
181187

182188
private var myOwner: Symbol = null
@@ -185,14 +191,15 @@ class ImportInfo(symf: Context ?=> Symbol,
185191
/** Does this import clause or a preceding import clause import `owner.feature`? */
186192
def featureImported(feature: TermName, owner: Symbol)(using Context): Boolean =
187193

188-
def compute =
189-
val isImportOwner = site.typeSymbol.eq(owner)
190-
if isImportOwner && forwardMapping.contains(feature) then true
191-
else if isImportOwner && excluded.contains(feature) then false
192-
else
193-
var c = ctx.outer
194-
while c.importInfo eq ctx.importInfo do c = c.outer
195-
(c.importInfo != null) && c.importInfo.featureImported(feature, owner)(using c)
194+
def compute: Boolean =
195+
if isLanguageImport then
196+
val isImportOwner = site.typeSymbol.eq(owner)
197+
if isImportOwner then
198+
if forwardMapping.contains(feature) then return true
199+
if excluded.contains(feature) then return false
200+
var c = ctx.outer
201+
while c.importInfo eq ctx.importInfo do c = c.outer
202+
(c.importInfo != null) && c.importInfo.featureImported(feature, owner)(using c)
196203

197204
if myOwner.ne(owner) || !myResults.contains(feature) then
198205
myOwner = owner

tests/neg/language-import.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
object a with
2+
val l = _root_.scala.language
3+
import l.noAutoTupling // error
4+
import l.experimental.genericNumberLiterals // error
5+
val scala = c
6+
import scala.language.noAutoTupling // error
7+
val language = b
8+
import language.experimental.genericNumberLiterals // error
9+
10+
object b with
11+
val strictEquality = 22
12+
object experimental with
13+
val genericNumberLiterals = 22
14+
15+
object c with
16+
val language = b
17+
import b.strictEquality
18+
19+
object d with
20+
import language.experimental.genericNumberLiterals // ok
21+
import scala.language.noAutoTupling // ok
22+
import _root_.scala.language.strictEquality // ok
23+

0 commit comments

Comments
 (0)