From ecdf9bfa84de11f4c17a740d61316046b93e8b28 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Oct 2020 11:03:13 +0200 Subject: [PATCH 1/4] Change wildcard given selectors Implements a syntax change for wildcard imports. Instead of ```scala import p.{given _} ``` write ```scala import p.given ``` The same applies for wildcard given imports in braces: `given _` is contracted to just `given`. Given-by-type imports remain unaffected: It's still ```scala import p.{given T} ``` for those. The rationale is that we should not make it needlessly hard to write wildcard given imports. The additional underscore after a `given` is not needed for disambiguation and clutters the code. --- .../dotty/tools/dotc/parsing/Parsers.scala | 13 ++++++++---- .../src/dotty/tools/dotc/parsing/Tokens.scala | 2 +- docs/docs/internals/syntax.md | 3 ++- .../reference/contextual/given-imports.md | 15 +++++++------- .../i7853/SummonJsonEncoderTest_2.scala | 2 +- tests/neg-custom-args/impl-conv/B.scala | 2 +- .../implicit-conversions.scala | 2 +- tests/neg-macros/i7048e.scala | 2 +- tests/neg/import-implied.scala | 4 ++-- tests/pos-macros/i7011/Macros_1.scala | 2 +- tests/pos-macros/i7048e.scala | 2 +- tests/pos-macros/i8651b.scala | 2 +- .../fatal-warnings/tasty-parent-unapply.scala | 2 +- tests/pos/i5978.scala | 6 +++--- tests/pos/i7532.scala | 2 +- .../tasty-extractors-owners/quoted_1.scala | 2 +- tests/run-macros/i8007/Test_4.scala | 2 +- .../run-macros/tasty-tree-map/quoted_1.scala | 2 +- tests/run/exports.scala | 2 +- tests/run/implied-priority.scala | 20 +++++++++---------- 20 files changed, 48 insertions(+), 41 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4294ade7501f..364c69a3944d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3094,16 +3094,18 @@ object Parsers { /** ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec * ImportSpec ::= id * | ‘_’ + * | ‘given’ * | ‘{’ ImportSelectors) ‘}’ */ def importExpr(mkTree: ImportConstr): () => Tree = { /** '_' */ def wildcardSelectorId() = atSpan(in.skipToken()) { Ident(nme.WILDCARD) } + def givenSelectorId(start: Offset) = atSpan(start) { Ident(nme.EMPTY) } /** ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors] * | WildCardSelector {‘,’ WildCardSelector} - * WildCardSelector ::= ‘given’ (‘_' | InfixType) + * WildCardSelector ::= ‘given’ [InfixType] * | ‘_' */ def importSelectors(idOK: Boolean): List[ImportSelector] = @@ -3114,12 +3116,13 @@ object Parsers { ImportSelector(wildcardSelectorId()) case GIVEN => val start = in.skipToken() - def givenSelector() = atSpan(start) { Ident(nme.EMPTY) } if in.token == USCORE then in.nextToken() - ImportSelector(givenSelector()) // Let the selector span all of `given _`; needed for -Ytest-pickler + ImportSelector(givenSelectorId(start)) // Let the selector span all of `given _`; needed for -Ytest-pickler + else if canStartTypeTokens.contains(in.token) then + ImportSelector(givenSelectorId(start), bound = infixType()) else - ImportSelector(givenSelector(), bound = infixType()) + ImportSelector(givenSelectorId(start)) case _ => val from = termIdent() if !idOK then syntaxError(i"named imports cannot follow wildcard imports") @@ -3143,6 +3146,8 @@ object Parsers { in.token match case USCORE => mkTree(qual, ImportSelector(wildcardSelectorId()) :: Nil) + case GIVEN => + mkTree(qual, ImportSelector(givenSelectorId(in.skipToken())) :: Nil) case LBRACE => mkTree(qual, inBraces(importSelectors(idOK = true))) case _ => diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index e961f80c0c94..70dce5b85e13 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -261,7 +261,7 @@ object Tokens extends TokensCommon { final val canStartStatTokens3: TokenSet = canStartExprTokens3 | mustStartStatTokens | BitSet( AT, CASE) - final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, RPAREN, RBRACE, RBRACKET, OUTDENT) + final val canEndStatTokens: TokenSet = atomicExprTokens | BitSet(TYPE, GIVEN, RPAREN, RBRACE, RBRACKET, OUTDENT) /** Tokens that stop a lookahead scan search for a `<-`, `then`, or `do`. * Used for disambiguating between old and new syntax. diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f8c2915cdf07..9819bd4ee770 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -348,10 +348,11 @@ Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec Import(expr, sels) ImportSpec ::= id | ‘_’ + | ‘given’ | ‘{’ ImportSelectors) ‘}’ ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors] | WildCardSelector {‘,’ WildCardSelector} -WildCardSelector ::= ‘given’ (‘_' | InfixType) +WildCardSelector ::= ‘given’ [InfixType] | ‘_' Export ::= ‘export’ [‘given’] ImportExpr {‘,’ ImportExpr} diff --git a/docs/docs/reference/contextual/given-imports.md b/docs/docs/reference/contextual/given-imports.md index 69c2b55e1bf1..c2e3a8e48f2a 100644 --- a/docs/docs/reference/contextual/given-imports.md +++ b/docs/docs/reference/contextual/given-imports.md @@ -14,22 +14,22 @@ object A { object B { import A._ - import A.{given _} + import A.given } ``` In the code above, the `import A._` clause of object `B` will import all members -of `A` _except_ the given instance `tc`. Conversely, the second import `import A.{given _}` will import _only_ that given instance. +of `A` _except_ the given instance `tc`. Conversely, the second import `import A.given` will import _only_ that given instance. The two import clauses can also be merged into one: ```scala object B { - import A.{given _, _} + import A.{given, _} } ``` Generally, a normal wildcard selector `_` brings all definitions other than givens or extensions into scope -whereas a `given _` selector brings all givens (including those resulting from extensions) into scope. +whereas a `given` selector brings all givens (including those resulting from extensions) into scope. There are two main benefits arising from these rules: @@ -106,13 +106,13 @@ normal imports to givens and given imports. The following modifications avoid this hurdle to migration. 1. A `given` import selector also brings old style implicits into scope. So, in Scala 3.0 - an old-style implicit definition can be brought into scope either by a `_` or a `given _` wildcard selector. + an old-style implicit definition can be brought into scope either by a `_` or a `given` wildcard selector. 2. In Scala 3.1, old-style implicits accessed through a `_` wildcard import will give a deprecation warning. 3. In some version after 3.1, old-style implicits accessed through a `_` wildcard import will give a compiler error. -These rules mean that library users can use `given _` selectors to access old-style implicits in Scala 3.0, +These rules mean that library users can use `given` selectors to access old-style implicits in Scala 3.0, and will be gently nudged and then forced to do so in later versions. Libraries can then switch to given instances once their user base has migrated. @@ -123,10 +123,11 @@ Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ImportExpr ::= StableId ‘.’ ImportSpec ImportSpec ::= id | ‘_’ + | ‘given’ | ‘{’ ImportSelectors) ‘}’ ImportSelectors ::= id [‘=>’ id | ‘=>’ ‘_’] [‘,’ ImportSelectors] | WildCardSelector {‘,’ WildCardSelector} WildCardSelector ::= ‘_' - | ‘given’ (‘_' | InfixType) + | ‘given’ [InfixType] Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ``` diff --git a/tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala b/tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala index 4891596bfa80..9c51ec6e9c36 100644 --- a/tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala +++ b/tests/disabled/pos-macros/i7853/SummonJsonEncoderTest_2.scala @@ -1,6 +1,6 @@ import scala.deriving._ import scala.quoted._ -import JsonEncoder.{given _, _} +import JsonEncoder.{given, _} object SummonJsonEncoderTest { diff --git a/tests/neg-custom-args/impl-conv/B.scala b/tests/neg-custom-args/impl-conv/B.scala index 219b0617c563..45a51f28daf8 100644 --- a/tests/neg-custom-args/impl-conv/B.scala +++ b/tests/neg-custom-args/impl-conv/B.scala @@ -1,7 +1,7 @@ package implConv object B { - import A.{_, given _} + import A.{_, given} "".foo diff --git a/tests/neg-custom-args/implicit-conversions.scala b/tests/neg-custom-args/implicit-conversions.scala index 95148ff72344..fd75ca6c9e2d 100644 --- a/tests/neg-custom-args/implicit-conversions.scala +++ b/tests/neg-custom-args/implicit-conversions.scala @@ -21,7 +21,7 @@ object D { } object Test { - import D.{given _} + import D.given val x1: A = new B // error under -Xfatal-warnings -feature val x2: B = new A // error under -Xfatal-warnings -feature diff --git a/tests/neg-macros/i7048e.scala b/tests/neg-macros/i7048e.scala index 1130b1bd601b..1ad1891afab6 100644 --- a/tests/neg-macros/i7048e.scala +++ b/tests/neg-macros/i7048e.scala @@ -13,7 +13,7 @@ abstract class Test { { val t: Test = this - import t.{given _} + import t.given println(summon[Type[t.T]].show) // val r = '{Option.empty[t.T]} // access to value t from wrong staging level val r2 = '{Option.empty[${t.T}]} // works diff --git a/tests/neg/import-implied.scala b/tests/neg/import-implied.scala index 0262960bf7e5..f7a38d77c43b 100644 --- a/tests/neg/import-implied.scala +++ b/tests/neg/import-implied.scala @@ -16,12 +16,12 @@ object C { foo(using tc) // ok } object D { - import A.{foo, given _} + import A.{foo, given} foo // ok foo(using tc) // ok } object E { - import A.{_, given _} + import A.{_, given} foo // ok foo(using tc) // ok } diff --git a/tests/pos-macros/i7011/Macros_1.scala b/tests/pos-macros/i7011/Macros_1.scala index f2a10babf8ca..1808c0c1f34e 100644 --- a/tests/pos-macros/i7011/Macros_1.scala +++ b/tests/pos-macros/i7011/Macros_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ inline def mcr(body: => Any): Unit = ${mcrImpl('body)} def mcrImpl[T](body: Expr[Any])(using ctx: QuoteContext) : Expr[Any] = { - import ctx.tasty.{_, given _} + import ctx.tasty.{_, given} val bTree = body.unseal val under = bTree.underlyingArgument diff --git a/tests/pos-macros/i7048e.scala b/tests/pos-macros/i7048e.scala index 145f88b5301e..5d08d59dd841 100644 --- a/tests/pos-macros/i7048e.scala +++ b/tests/pos-macros/i7048e.scala @@ -13,7 +13,7 @@ abstract class Test { { val t: Test = this - import t.{given _} + import t.given println(summon[Type[t.T]].show) // val r = '{Option.empty[t.T]} // access to value t from wrong staging level val r2 = '{Option.empty[${t.T}]} diff --git a/tests/pos-macros/i8651b.scala b/tests/pos-macros/i8651b.scala index 2ee40e81acc5..23739c604bd4 100644 --- a/tests/pos-macros/i8651b.scala +++ b/tests/pos-macros/i8651b.scala @@ -9,7 +9,7 @@ object Macros { inline def coroutine[T](inline body: Any): Coroutine[T] = ${ coroutineImpl('{body}) } def coroutineImpl[T: Type](expr: Expr[_ <: Any])(implicit qtx: QuoteContext): Expr[Coroutine[T]] = { - import qtx.tasty.{_, given _} + import qtx.tasty.{_, given} '{ new Coroutine[T] { diff --git a/tests/pos-special/fatal-warnings/tasty-parent-unapply.scala b/tests/pos-special/fatal-warnings/tasty-parent-unapply.scala index 85e64f5e137b..382b79a18fbb 100644 --- a/tests/pos-special/fatal-warnings/tasty-parent-unapply.scala +++ b/tests/pos-special/fatal-warnings/tasty-parent-unapply.scala @@ -6,7 +6,7 @@ object Macros { def impl(reflect: Reflection): Unit = { - import reflect.{_, given _} + import reflect.{_, given} def foo(tree: Tree, term: Term, typeTree: TypeTree, parent: Tree) = { diff --git a/tests/pos/i5978.scala b/tests/pos/i5978.scala index d52d9af19973..75573f775312 100644 --- a/tests/pos/i5978.scala +++ b/tests/pos/i5978.scala @@ -21,7 +21,7 @@ package p1 { object Testcase { def main(args: Array[String]): Unit = { - import TextParser.{given _, _} + import TextParser.{given, _} val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP val tp_i = summon[TokenParser[Char, Position[CharSequence]]] @@ -45,7 +45,7 @@ package p2 { object Testcase { def main(args: Array[String]): Unit = { - import TextParser.{given _, _} + import TextParser.{given, _} val tp_v: TokenParser[Char, Position[CharSequence]] = TextParser.TP val tp_i = summon[TokenParser[Char, Position[CharSequence]]] @@ -63,7 +63,7 @@ package p3 { object Testcase { def main(args: Array[String]): Unit = { - import TextParser.{_, given _} + import TextParser.{_, given} val co_i: Conversion[Char, Position[CharSequence]] = summon[Conversion[Char, Position[CharSequence]]] diff --git a/tests/pos/i7532.scala b/tests/pos/i7532.scala index 128c639fe723..0ded97411b2c 100644 --- a/tests/pos/i7532.scala +++ b/tests/pos/i7532.scala @@ -12,7 +12,7 @@ class Tasty { object Foo { def impl(using tasty: Tasty) : Unit = { - import tasty.{_, given _} + import tasty.{_, given} val Select() = (??? : Term) } } \ No newline at end of file diff --git a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners/quoted_1.scala b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners/quoted_1.scala index 03d9c9830cca..bb5289b964ce 100644 --- a/tests/run-custom-args/Yretain-trees/tasty-extractors-owners/quoted_1.scala +++ b/tests/run-custom-args/Yretain-trees/tasty-extractors-owners/quoted_1.scala @@ -18,7 +18,7 @@ object Macros { } class MyTraverser[R <: scala.tasty.Reflection & Singleton](val reflect: R)(buff: StringBuilder) extends scala.tasty.reflect.TreeTraverser { - import reflect.{given _, _} + import reflect.{given, _} override def traverseTree(tree: Tree)(implicit ctx: Context): Unit = { tree match { case tree @ DefDef(name, _, _, _, _) => diff --git a/tests/run-macros/i8007/Test_4.scala b/tests/run-macros/i8007/Test_4.scala index a16266fe6fd6..742a1c6e3dea 100644 --- a/tests/run-macros/i8007/Test_4.scala +++ b/tests/run-macros/i8007/Test_4.scala @@ -17,7 +17,7 @@ enum OptInv[+T] { @main def Test() = { import Opt._ - import Eq.{given _, _} + import Eq.{given, _} val t1 = test1(Person("Test", 23)) println(t1) diff --git a/tests/run-macros/tasty-tree-map/quoted_1.scala b/tests/run-macros/tasty-tree-map/quoted_1.scala index de2381cae8cd..654b1a2a53bd 100644 --- a/tests/run-macros/tasty-tree-map/quoted_1.scala +++ b/tests/run-macros/tasty-tree-map/quoted_1.scala @@ -5,7 +5,7 @@ object Macros { implicit inline def identityMaped[T](x: => T): T = ${ impl('x) } def impl[T: Type](x: Expr[T])(using qctx: QuoteContext) : Expr[T] = { - import qctx.tasty.{_, given _} // FIXME: #8919 + import qctx.tasty.{_, given} // FIXME: #8919 val identityMap = new TreeMap { } val transformed = identityMap.transformTerm(x.unseal).seal.cast[T] transformed diff --git a/tests/run/exports.scala b/tests/run/exports.scala index c82143192152..e3bca4329935 100644 --- a/tests/run/exports.scala +++ b/tests/run/exports.scala @@ -18,7 +18,7 @@ object Test extends App { object Copier { val printer = new Printer - export printer.{given _, _} + export printer.{given, _} export Scanner.{scan => scanIt, _} val config2 = summon[Config] diff --git a/tests/run/implied-priority.scala b/tests/run/implied-priority.scala index a1e3a32981ee..5f3a514bcc03 100644 --- a/tests/run/implied-priority.scala +++ b/tests/run/implied-priority.scala @@ -19,7 +19,7 @@ object NormalImplicits extends LowPriorityImplicits { } def test1 = { - import NormalImplicits.{given _} + import NormalImplicits.given assert(summon[E[String]].str == "low") // No Arg available, so only t1 applies { given Arg[String] @@ -43,7 +43,7 @@ object Impl2 { } def test2 = { - import Impl2.{given _} + import Impl2.given assert(summon[E[String]].str == "low") // No Arg available, so only t1 applies { given Arg[String] @@ -64,8 +64,8 @@ object Impl2a { } def test2a = { - import Impl2.{given _} - import Impl2a.{given _} + import Impl2.given + import Impl2a.given given Arg[String] assert(summon[E[String]].str == "hi") @@ -85,11 +85,11 @@ object Override { } def test3 = { - import Impl3.{given _} + import Impl3.given assert(summon[E[String]].str == "low") // only t1 is available - { import Override.{given _} - import Impl3.{given _} + { import Override.given + import Impl3.given assert(summon[E[String]].str == "hi") // `over` takes priority since its result type is a subtype of t1's. } } @@ -111,7 +111,7 @@ object fallback4 { } def test4 = { - import Impl4.{given _} + import Impl4.given import fallback4._ assert(withFallback[String].str == "string") // t1 is applicable assert(withFallback[Int].str == "fallback") // No applicable instances, pick the default @@ -138,8 +138,8 @@ object fallback5 { } def test5 = { - import Impl4.{given _} - import fallback5.{given _} + import Impl4.given + import fallback5.given // All inferred terms go through the given instance in fallback5. // They differ in what implicit argument is synthesized for that instance. From 099074c6a7e191ac04c7189cc7b23dddcc431789 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Oct 2020 16:51:49 +0200 Subject: [PATCH 2/4] Deprecate old syntax --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 364c69a3944d..16ef3886fcbd 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3117,6 +3117,7 @@ object Parsers { case GIVEN => val start = in.skipToken() if in.token == USCORE then + deprecationWarning(em"`given _` is deprecated in imports; replace with just `given`", start) in.nextToken() ImportSelector(givenSelectorId(start)) // Let the selector span all of `given _`; needed for -Ytest-pickler else if canStartTypeTokens.contains(in.token) then From 40b99ffcbecd6beb768518154534c832725329c0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Oct 2020 17:17:09 +0200 Subject: [PATCH 3/4] Reject `?` as an import bound --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/neg/{import-implied.scala => import-given.scala} | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) rename tests/neg/{import-implied.scala => import-given.scala} (87%) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16ef3886fcbd..473d1180efe7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3121,7 +3121,7 @@ object Parsers { in.nextToken() ImportSelector(givenSelectorId(start)) // Let the selector span all of `given _`; needed for -Ytest-pickler else if canStartTypeTokens.contains(in.token) then - ImportSelector(givenSelectorId(start), bound = infixType()) + ImportSelector(givenSelectorId(start), bound = rejectWildcardType(infixType())) else ImportSelector(givenSelectorId(start)) case _ => diff --git a/tests/neg/import-implied.scala b/tests/neg/import-given.scala similarity index 87% rename from tests/neg/import-implied.scala rename to tests/neg/import-given.scala index f7a38d77c43b..9aebd770ea31 100644 --- a/tests/neg/import-implied.scala +++ b/tests/neg/import-given.scala @@ -25,3 +25,6 @@ object E { foo // ok foo(using tc) // ok } +object F: + import A.{given ?} // error: unbound wildcard type + From f48b8166b0c0539c093005a2c4e92635d1817430 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 6 Oct 2020 17:34:36 +0200 Subject: [PATCH 4/4] Revert "Deprecate old syntax" This reverts commit 099074c6a7e191ac04c7189cc7b23dddcc431789. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 473d1180efe7..b4b50dda35b7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3117,7 +3117,6 @@ object Parsers { case GIVEN => val start = in.skipToken() if in.token == USCORE then - deprecationWarning(em"`given _` is deprecated in imports; replace with just `given`", start) in.nextToken() ImportSelector(givenSelectorId(start)) // Let the selector span all of `given _`; needed for -Ytest-pickler else if canStartTypeTokens.contains(in.token) then