Skip to content

Commit

Permalink
Merge pull request #10786 from som-snytt/issue/12999-import-given
Browse files Browse the repository at this point in the history
Import selector can be given
  • Loading branch information
lrytz committed Jun 4, 2024
2 parents 0d8f4ca + 66e9c39 commit 604af3b
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 44 deletions.
60 changes: 32 additions & 28 deletions src/compiler/scala/tools/nsc/ast/parser/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2759,10 +2759,11 @@ self =>
val selectors: List[ImportSelector] = in.token match {
case USCORE =>
List(wildImportSelector()) // import foo.bar._
case IDENTIFIER if currentRun.isScala3 && in.name == raw.STAR =>
List(wildImportSelector()) // import foo.bar.*
case IDENTIFIER if currentRun.isScala3 && (in.name == raw.STAR || in.name == nme.`given`) =>
if (in.name == raw.STAR) List(wildImportSelector()) // import foo.bar.*
else List(importSelector()) // import foo.bar.given
case LBRACE =>
importSelectors() // import foo.bar.{ x, y, z }
importSelectors() // import foo.bar.{x, y, z, given, *}
case _ =>
if (currentRun.isScala3 && lookingAhead { isRawIdent && in.name == nme.as })
List(importSelector()) // import foo.bar as baz
Expand All @@ -2775,7 +2776,7 @@ self =>
in.nextToken()
return loop(t)
}
// import foo.bar.Baz;
// import foo.Bar
else List(makeImportSelector(name, nameOffset))
}
}
Expand Down Expand Up @@ -2804,23 +2805,28 @@ self =>
* }}}
*/
def importSelectors(): List[ImportSelector] = {
def isWilder(sel: ImportSelector) = sel.isWildcard || currentRun.isScala3 && !sel.isRename && sel.name == nme.`given`
def isWilder(sel: ImportSelector) = sel.isWildcard || sel.isGiven
// error on duplicate target names, import x.{a=>z, b=>z}, and fix import x.{given, *} to x._
def checkSelectors(xs: List[ImportSelector]): List[ImportSelector] = xs match {
case h :: t =>
// wildcards must come last, and for -Xsource:3, take trailing given, * (or *, given) as _
// wildcards must come last, and for -Xsource:3, accept trailing given and/or *, converting {given, *} to *
if (isWilder(h)) {
if (t.exists(!isWilder(_)))
syntaxError(h.namePos, "wildcard import must be in last position")
xs.filter(_.isWildcard) match {
case ws @ (_ :: Nil) => ws
case Nil =>
syntaxError(h.namePos, "given requires a wildcard selector")
ImportSelector.wildAt(h.namePos) :: Nil
case ws @ (w :: _) =>
syntaxError(w.namePos, "duplicate wildcard selector")
w :: Nil
}
val wildcard =
if (t.exists(!isWilder(_))) {
syntaxError(h.namePos, "wildcard import must be in last position")
h
}
else t match {
case Nil => h
case other :: Nil if h.isWildcard != other.isWildcard =>
if (h.isWildcard) h else other
case _ =>
val (wilds, givens) = xs.partition(_.isWildcard)
val dupes = if (wilds.length > 1) wilds else givens
syntaxError(dupes(1).namePos, "duplicate wildcard selector")
h
}
wildcard :: Nil
}
else {
if (!h.isMask)
Expand Down Expand Up @@ -2851,17 +2857,15 @@ self =>
val start = in.offset
val bbq = in.token == BACKQUOTED_IDENT
val name = wildcardOrIdent()
val (rename, renameOffset) =
if (in.token == ARROW || (currentRun.isScala3 && isRawIdent && in.name == nme.as)) {
in.nextToken()
if (name == nme.WILDCARD && !bbq) syntaxError(in.offset, "Wildcard import cannot be renamed")
val pos = in.offset
(wildcardOrIdent(), pos)
}
else if (name == nme.WILDCARD && !bbq) (null, -1)
else (name, start)

ImportSelector(name, start, rename, renameOffset)
if (in.token == ARROW || (currentRun.isScala3 && isRawIdent && in.name == nme.as)) {
in.nextToken()
if (name == nme.WILDCARD && !bbq) syntaxError(in.offset, "Wildcard import cannot be renamed")
val renamePos = in.offset
ImportSelector(name, start, rename = wildcardOrIdent(), renamePos = renamePos)
}
else if (name == nme.WILDCARD && !bbq) ImportSelector.wildAt(start)
else if (currentRun.isScala3 && name == nme.`given` && !bbq) ImportSelector.givenAt(start)
else makeImportSelector(name, start)
}

def wildImportSelector(): ImportSelector = {
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/scala/tools/nsc/typechecker/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ trait Contexts { self: Analyzer =>
def collect(sels: List[ImportSelector]): List[ImplicitInfo] = sels match {
case List() =>
List()
case sel :: _ if sel.isWildcard =>
case sel :: _ if sel.isWildcard || sel.isGiven =>
// Using pre.implicitMembers seems to exposes a problem with out-dated symbols in the IDE,
// see the example in https://www.assembla.com/spaces/scala-ide/tickets/1002552#/activity/ticket
// I haven't been able to boil that down the an automated test yet.
Expand Down Expand Up @@ -1950,6 +1950,11 @@ trait Contexts { self: Analyzer =>
renamed = true
else if (current.isWildcard && !renamed && !requireExplicit)
result = maybeNonLocalMember(name)
else if (current.isGiven && !requireExplicit) {
val maybe = maybeNonLocalMember(name)
if (maybe.isImplicit)
result = maybe
}

if (result == NoSymbol)
selectors = selectors.tail
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/typechecker/Namers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ trait Namers extends MethodSynthesis {
lookup(original.toTermName) != NoSymbol || lookup(original.toTypeName) != NoSymbol
}

if (!s.isWildcard && base != ErrorType) {
if (!s.isWildcard && !s.isGiven && base != ErrorType) {
val okay = isValid(from, base) || context.unit.isJava && ( // Java code...
(nme.isModuleName(from) && isValid(from.dropModule, base)) // - importing Scala module classes
|| isValid(from, base.companion) // - importing type members from types
Expand Down
8 changes: 5 additions & 3 deletions src/reflect/scala/reflect/internal/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -512,20 +512,22 @@ trait Trees extends api.Trees {
case class ImportSelector(name: Name, namePos: Int, rename: Name, renamePos: Int) extends ImportSelectorApi {
assert(isWildcard || rename != null, s"Bad import selector $name => $rename")
def isWildcard = name == nme.WILDCARD && rename == null
def isMask = name != nme.WILDCARD && rename == nme.WILDCARD
def isGiven = name == nme.WILDCARD && rename == nme.`given`
def isMask = name != nme.WILDCARD && rename == nme.WILDCARD
def isRename = name != rename && rename != null && rename != nme.WILDCARD
def isSpecific = !isWildcard
def isRename = rename != null && rename != nme.WILDCARD && name != rename
private def isLiteralWildcard = name == nme.WILDCARD && rename == nme.WILDCARD
private def sameName(name: Name, other: Name) = (name eq other) || (name ne null) && name.start == other.start && name.length == other.length
def hasName(other: Name) = sameName(name, other)
def introduces(target: Name) =
if (target == nme.WILDCARD) isLiteralWildcard
else target != null && sameName(rename, target)
else target != null && !isGiven && sameName(rename, target)
}
object ImportSelector extends ImportSelectorExtractor {
private val wild = ImportSelector(nme.WILDCARD, -1, null, -1)
val wildList = List(wild) // OPT This list is shared for performance. Used for unpositioned synthetic only.
def wildAt(pos: Int) = ImportSelector(nme.WILDCARD, pos, null, -1)
def givenAt(pos: Int) = ImportSelector(nme.WILDCARD, pos, nme.`given`, -1)
def mask(name: Name) = ImportSelector(name, -1, nme.WILDCARD, -1)
}

Expand Down
11 changes: 10 additions & 1 deletion test/files/neg/import-future.check
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import-future.scala:15: error: not found: value unrelated
unrelated(1) // error
^
1 error
import-future.scala:40: error: not found: value f
def g = f[Int] // error no f, was given is not a member
^
import-future.scala:44: error: could not find implicit value for parameter t: T[Int]
def g = f[Int] // implicit unavailable
^
import-future.scala:48: error: could not find implicit value for parameter t: T[Int]
def g = f[Int] // implicit unavailable
^
4 errors
24 changes: 23 additions & 1 deletion test/files/neg/import-future.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// scalac: -Xsource:3
//> using options -Xsource:3
//

class D {
Expand All @@ -25,3 +25,25 @@ object Test {
*(1)
}
}

trait T[A] {
def t: A
}
object TX {
implicit def tInt: T[Int] = new T[Int] {
def t: Int = 42
}
def f[A](implicit t: T[A]): A = t.t
}
object X {
import TX.given
def g = f[Int] // error no f, was given is not a member
}
object Y {
import TX.{f, tInt as _, given}
def g = f[Int] // implicit unavailable
}
object Z {
import TX.{tInt as _, *}
def g = f[Int] // implicit unavailable
}
7 changes: 2 additions & 5 deletions test/files/neg/t12813b.check
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,5 @@ import O.{given, toString, a, _} // error 3
^
t12813b.scala:18: error: duplicate wildcard selector
import O.{a, given, *, _} // error 3
^
t12813b.scala:19: error: given requires a wildcard selector
import O.{a, given} // error 3
^
9 errors
^
8 errors
4 changes: 2 additions & 2 deletions test/files/neg/t12813b.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// scalac: -Xsource:3
//> using options -Xsource:3

object O { val a = 1 }

Expand All @@ -16,4 +16,4 @@ import O.{given, toString, a, _} // error 3
import O.{a, given, *} // ok
import O.{a, *, given} // ok
import O.{a, given, *, _} // error 3
import O.{a, given} // error 3
import O.{a, given} // ok
17 changes: 15 additions & 2 deletions test/files/pos/import-future.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// scalac: -Xsource:3
//
//> using options -Xsource:3

import java.io as jio
import scala.{collection as c}
Expand Down Expand Up @@ -31,3 +30,17 @@ object starring {
val f = Future(42)
val r = Await.result(f, D.Inf)
}

trait T[A] {
def t: A
}
object T {
implicit def tInt: T[Int] = new T[Int] {
def t: Int = 42
}
def f[A](implicit t: T[A]): A = t.t
}
object X {
import T.given
def g = T.f[Int] // was given is not a member
}

0 comments on commit 604af3b

Please sign in to comment.