Skip to content

Commit 93b3e56

Browse files
som-snyttlrytz
authored andcommitted
Nuance import selector errors in Namers
1 parent c80c0ff commit 93b3e56

File tree

13 files changed

+135
-73
lines changed

13 files changed

+135
-73
lines changed

src/compiler/scala/tools/nsc/ast/parser/Parsers.scala

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,46 +2803,59 @@ self =>
28032803
* }}}
28042804
*/
28052805
def importSelectors(): List[ImportSelector] = {
2806-
val selectors0 = inBracesOrNil(commaSeparated(importSelector()))
2807-
2808-
// Treat an import of `*, given` or `given, *` as if it was an import of `*`
2809-
// since the former in Scala 3 has the same semantics as the latter in Scala 2.
2810-
val selectors =
2811-
if (currentRun.isScala3 && selectors0.exists(_.isWildcard))
2812-
selectors0.filterNot(sel => sel.name == nme.`given` && sel.rename == sel.name)
2813-
else
2814-
selectors0
2815-
2816-
for (t <- selectors.init if t.isWildcard) syntaxError(t.namePos, "Wildcard import must be in last position")
2817-
selectors
2806+
def isWilder(sel: ImportSelector) = sel.isWildcard || currentRun.isScala3 && !sel.isRename && sel.name == nme.`given`
2807+
// error on duplicate target names, import x.{a=>z, b=>z}, and fix import x.{given, *} to x._
2808+
def checkSelectors(xs: List[ImportSelector]): List[ImportSelector] = xs match {
2809+
case h :: t =>
2810+
// wildcards must come last, and for -Xsource:3, take trailing given, * (or *, given) as _
2811+
if (isWilder(h)) {
2812+
if (t.exists(!isWilder(_)))
2813+
syntaxError(h.namePos, "wildcard import must be in last position")
2814+
xs.filter(_.isWildcard) match {
2815+
case ws @ (_ :: Nil) => ws
2816+
case Nil =>
2817+
syntaxError(h.namePos, "given requires a wildcard selector")
2818+
ImportSelector.wildAt(h.namePos) :: Nil
2819+
case ws @ (w :: _) =>
2820+
syntaxError(w.namePos, "duplicate wildcard selector")
2821+
w :: Nil
2822+
}
2823+
}
2824+
else {
2825+
if (!h.isMask)
2826+
t.find(_.rename == h.rename).foreach { duplicate =>
2827+
val msg =
2828+
if (h.isRename || duplicate.isRename) s"${h.rename} is an ambiguous name on import"
2829+
else s"${h.rename} is imported twice"
2830+
syntaxError(h.namePos, msg)
2831+
}
2832+
h :: checkSelectors(t)
2833+
}
2834+
case _ => Nil
2835+
}
2836+
checkSelectors(inBracesOrNil(commaSeparated(importSelector())))
28182837
}
28192838

28202839
def wildcardOrIdent() =
2821-
if (in.token == USCORE || currentRun.isScala3 && isRawStar) { in.nextToken() ; nme.WILDCARD }
2840+
if (in.token == USCORE || currentRun.isScala3 && isRawStar) { in.nextToken(); nme.WILDCARD }
28222841
else ident()
28232842

28242843
/** {{{
28252844
* ImportSelector ::= Id [`=>` Id | `=>` `_`]
28262845
* }}}
28272846
*/
28282847
def importSelector(): ImportSelector = {
2829-
val start = in.offset
2830-
val bbq = in.token == BACKQUOTED_IDENT
2831-
val name = wildcardOrIdent()
2832-
var renameOffset = -1
2833-
2834-
val rename =
2848+
val start = in.offset
2849+
val bbq = in.token == BACKQUOTED_IDENT
2850+
val name = wildcardOrIdent()
2851+
val (rename, renameOffset) =
28352852
if (in.token == ARROW || (currentRun.isScala3 && isRawIdent && in.name == nme.as)) {
28362853
in.nextToken()
2837-
renameOffset = in.offset
2838-
if (name == nme.WILDCARD && !bbq) syntaxError(renameOffset, "Wildcard import cannot be renamed")
2839-
wildcardOrIdent()
2840-
}
2841-
else if (name == nme.WILDCARD && !bbq) null
2842-
else {
2843-
renameOffset = start
2844-
name
2854+
if (name == nme.WILDCARD && !bbq) syntaxError(in.offset, "Wildcard import cannot be renamed")
2855+
(wildcardOrIdent(), in.offset)
28452856
}
2857+
else if (name == nme.WILDCARD && !bbq) (null, -1)
2858+
else (name, start)
28462859

28472860
ImportSelector(name, start, rename, renameOffset)
28482861
}

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,12 +1335,7 @@ trait ContextErrors extends splain.SplainErrors {
13351335
ByNameParameter, AbstractVar = Value
13361336
}
13371337

1338-
object DuplicatesErrorKinds extends Enumeration {
1339-
val RenamedTwice, AppearsTwice = Value
1340-
}
1341-
13421338
import SymValidateErrors._
1343-
import DuplicatesErrorKinds._
13441339
import symtab.Flags
13451340

13461341
def TypeSigError(tree: Tree, ex: TypeError) = {
@@ -1433,16 +1428,6 @@ trait ContextErrors extends splain.SplainErrors {
14331428
": parameter may only be referenced in a subsequent parameter section"
14341429
issueSymbolTypeError(sym, "illegal dependent method type" + errorAddendum)(context)
14351430
}
1436-
1437-
def DuplicatesError(tree: Tree, name: Name, kind: DuplicatesErrorKinds.Value) = {
1438-
val msg = kind match {
1439-
case RenamedTwice => "is renamed twice"
1440-
case AppearsTwice => "appears twice as a target of a renaming"
1441-
case x => throw new MatchError(x)
1442-
}
1443-
1444-
issueNormalTypeError(tree, name.decode + " " + msg)
1445-
}
14461431
}
14471432

14481433
def InferredImplicitError(tree: Tree, inferred: Type, cx: Context): Unit =

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ trait Contexts { self: Analyzer =>
11061106
def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match {
11071107
case List() =>
11081108
List()
1109-
case sel :: Nil if sel.isWildcard =>
1109+
case sel :: _ if sel.isWildcard =>
11101110
// Using pre.implicitMembers seems to exposes a problem with out-dated symbols in the IDE,
11111111
// see the example in https://www.assembla.com/spaces/scala-ide/tickets/1002552#/activity/ticket
11121112
// I haven't been able to boil that down the an automated test yet.
@@ -1255,12 +1255,11 @@ trait Contexts { self: Analyzer =>
12551255
)
12561256

12571257
/** If the given import is permitted, fetch the symbol and filter for accessibility.
1258+
* Tests `exists` to complete SymbolLoaders, which sets the symbol's access flags (scala/bug#12736)
12581259
*/
1259-
private[Contexts] def importedAccessibleSymbol(imp: ImportInfo, sym: => Symbol): Symbol = {
1260+
private[Contexts] def importedAccessibleSymbol(imp: ImportInfo, sym: => Symbol): Symbol =
12601261
if (isExcludedRootImport(imp)) NoSymbol
12611262
else sym.filter(s => s.exists && isAccessible(s, imp.qual.tpe, superAccess = false))
1262-
// `exists` above completes SymbolLoaders, which sets the symbol's access flags (scala/bug#12736)
1263-
}
12641263

12651264
private def isExcludedRootImport(imp: ImportInfo): Boolean =
12661265
imp.isRootImport && excludedRootImportsCached.get(unit).exists(_.contains(imp.qual.symbol))
@@ -1988,7 +1987,7 @@ trait Contexts { self: Analyzer =>
19881987

19891988
override def hashCode = tree.##
19901989
override def equals(other: Any) = other match {
1991-
case that: ImportInfo => (tree == that.tree)
1990+
case that: ImportInfo => tree == that.tree
19921991
case _ => false
19931992
}
19941993
override def toString = tree.toString

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,10 +543,10 @@ trait Infer extends Checkable {
543543
*/
544544
def methTypeArgs(fn: Tree, tparams: List[Symbol], formals: List[Type], restpe: Type,
545545
argtpes: List[Type], pt: Type): AdjustedTypeArgs = {
546-
val tvars = tparams map freshVar
547546
if (!sameLength(formals, argtpes))
548547
throw new NoInstance("parameter lists differ in length")
549548

549+
val tvars = tparams.map(freshVar)
550550
val restpeInst = restpe.instantiateTypeParams(tparams, tvars)
551551

552552
// first check if typevars can be fully defined from the expected type.
@@ -576,10 +576,9 @@ trait Infer extends Checkable {
576576

577577
// Note that isCompatible side-effects: subtype checks involving typevars
578578
// are recorded in the typevar's bounds (see TypeConstraint)
579-
if (!isCompatible(tp1, pt1)) {
579+
if (!isCompatible(tp1, pt1))
580580
throw new DeferredNoInstance(() =>
581581
"argument expression's type is not compatible with formal parameter type" + foundReqMsg(tp1, pt1))
582-
}
583582
}
584583
val targs = solvedTypes(tvars, tparams, varianceInTypes(formals), upper = false, lubDepth(formals) max lubDepth(argtpes))
585584
if (settings.warnInferAny && !fn.isEmpty) {

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

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,6 @@ trait Namers extends MethodSynthesis {
537537
}
538538

539539
private def checkSelectors(tree: Import): Unit = {
540-
import DuplicatesErrorKinds._
541540
val Import(expr, selectors) = tree
542541
val base = expr.tpe
543542

@@ -594,22 +593,7 @@ trait Namers extends MethodSynthesis {
594593
checkNotRedundant(tree.pos withPoint fromPos, from, to)
595594
}
596595
}
597-
selectors foreach checkSelector
598-
599-
def noDuplicates(): Unit = {
600-
def loop(xs: List[ImportSelector]): Unit = xs match {
601-
case Nil => ()
602-
case hd :: tl =>
603-
if (!hd.isWildcard && tl.exists(x => !x.isWildcard && x.name == hd.name))
604-
DuplicatesError(tree, hd.name, RenamedTwice)
605-
else if (hd.isRename && tl.exists(x => x.isRename && x.rename == hd.rename))
606-
DuplicatesError(tree, hd.rename, AppearsTwice)
607-
else loop(tl)
608-
}
609-
loop(selectors)
610-
}
611-
// checks on the whole set
612-
noDuplicates()
596+
selectors.foreach(checkSelector)
613597
}
614598

615599
def copyMethodCompleter(copyDef: DefDef): TypeCompleter = {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3311,7 +3311,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
33113311
}
33123312
}
33133313

3314-
def typedImport(imp : Import): Import = transformed.remove(imp) match {
3314+
def typedImport(imp: Import): Import = transformed.remove(imp) match {
33153315
case Some(imp1: Import) => imp1
33163316
case _ => log(s"unhandled import: $imp in $unit"); imp
33173317
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ trait Trees extends api.Trees {
510510
}
511511

512512
case class ImportSelector(name: Name, namePos: Int, rename: Name, renamePos: Int) extends ImportSelectorApi {
513-
assert(name == nme.WILDCARD && rename == null || rename != null, s"Bad import selector $name => $rename")
513+
assert(isWildcard || rename != null, s"Bad import selector $name => $rename")
514514
def isWildcard = name == nme.WILDCARD && rename == null
515515
def isMask = name != nme.WILDCARD && rename == nme.WILDCARD
516516
def isSpecific = !isWildcard
@@ -523,8 +523,8 @@ trait Trees extends api.Trees {
523523
else target != null && sameName(rename, target)
524524
}
525525
object ImportSelector extends ImportSelectorExtractor {
526-
val wild = ImportSelector(nme.WILDCARD, -1, null, -1)
527-
val wildList = List(wild) // OPT This list is shared for performance.
526+
private val wild = ImportSelector(nme.WILDCARD, -1, null, -1)
527+
val wildList = List(wild) // OPT This list is shared for performance. Used for unpositioned synthetic only.
528528
def wildAt(pos: Int) = ImportSelector(nme.WILDCARD, pos, null, -1)
529529
def mask(name: Name) = ImportSelector(name, -1, nme.WILDCARD, -1)
530530
}

test/files/neg/t12813.check

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
t12813.scala:5: error: a is imported twice
2+
import O.{a, a} // error
3+
^
4+
t12813.scala:8: error: b is an ambiguous name on import
5+
import O.{a => b, a => b} // error
6+
^
7+
t12813.scala:10: error: b is an ambiguous name on import
8+
import O.{a => b, toString => b} // error
9+
^
10+
t12813.scala:11: error: toString is an ambiguous name on import
11+
import O.{a => toString, toString} // error
12+
^
13+
t12813.scala:12: error: toString is an ambiguous name on import
14+
import O.{toString, a => toString} // error
15+
^
16+
5 errors

test/files/neg/t12813.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
3+
object O { val a = 1 }
4+
5+
import O.{a, a} // error
6+
import O.{a => b, a} // ok
7+
import O.{a, a => b} // ok
8+
import O.{a => b, a => b} // error
9+
import O.{a => b, a => c} // ok
10+
import O.{a => b, toString => b} // error
11+
import O.{a => toString, toString} // error
12+
import O.{toString, a => toString} // error
13+
import O.{a => _, toString => _} // ok
14+
import O.{given, a, _} // ok
15+
import O.{given, toString, a, _} // ok
16+
import O.{a, given, *} // ok
17+
import O.{a, *, given} // ok
18+
import O.{a, given, *, _} // ok
19+
import O.{a, given} // ok

test/files/neg/t12813b.check

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
t12813b.scala:5: error: a is imported twice
2+
import O.{a, a} // error
3+
^
4+
t12813b.scala:8: error: b is an ambiguous name on import
5+
import O.{a => b, a => b} // error
6+
^
7+
t12813b.scala:10: error: b is an ambiguous name on import
8+
import O.{a => b, toString => b} // error
9+
^
10+
t12813b.scala:11: error: toString is an ambiguous name on import
11+
import O.{a => toString, toString} // error
12+
^
13+
t12813b.scala:12: error: toString is an ambiguous name on import
14+
import O.{toString, a => toString} // error
15+
^
16+
t12813b.scala:14: error: wildcard import must be in last position
17+
import O.{given, a, _} // error 3
18+
^
19+
t12813b.scala:15: error: wildcard import must be in last position
20+
import O.{given, toString, a, _} // error 3
21+
^
22+
t12813b.scala:18: error: duplicate wildcard selector
23+
import O.{a, given, *, _} // error 3
24+
^
25+
t12813b.scala:19: error: given requires a wildcard selector
26+
import O.{a, given} // error 3
27+
^
28+
9 errors

0 commit comments

Comments
 (0)