From 362786b57a9163c9729f59d8adeb65a5f063399b Mon Sep 17 00:00:00 2001 From: Kai Date: Fri, 7 May 2021 22:37:34 +0100 Subject: [PATCH] Allow `_` as a type lambda placeholder in `-Ykind-projector:underscores` compatiblity mode Also allow through `+_` and `-_` only in that mode, these variance markers are ignored since we infer variance. --- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../dotty/tools/dotc/config/Settings.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 7 ++- .../dotty/tools/dotc/parsing/Parsers.scala | 39 ++++++++---- .../dotty/tools/dotc/CompilationTests.scala | 2 + .../reference/changed-features/wildcards.md | 5 ++ .../kind-projector-underscores.check | 24 ++++++++ .../kind-projector-underscores.scala | 12 ++++ .../kind-projector-underscores.scala | 59 +++++++++++++++++++ 9 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 tests/neg-custom-args/kind-projector-underscores.check create mode 100644 tests/neg-custom-args/kind-projector-underscores.scala create mode 100644 tests/pos-special/kind-projector-underscores.scala diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 9f346f97e742..f54426f5db82 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -165,7 +165,7 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings { val YstopBefore: Setting[List[String]] = PhasesSetting("-Ystop-before", "Stop before") // stop before erasure as long as we have not debugged it fully val YshowSuppressedErrors: Setting[Boolean] = BooleanSetting("-Yshow-suppressed-errors", "Also show follow-on errors and warnings that are normally suppressed.") val YdetailedStats: Setting[Boolean] = BooleanSetting("-Ydetailed-stats", "Show detailed internal compiler stats (needs Stats.enabled to be set to true).") - val YkindProjector: Setting[Boolean] = BooleanSetting("-Ykind-projector", "Allow `*` as wildcard to be compatible with kind projector.") + val YkindProjector: Setting[String] = ChoiceSetting("-Ykind-projector", "[underscores, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Ykind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable") val YprintPos: Setting[Boolean] = BooleanSetting("-Yprint-pos", "Show tree positions.") val YprintPosSyms: Setting[Boolean] = BooleanSetting("-Yprint-pos-syms", "Show symbol definitions positions.") val YnoDeepSubtypes: Setting[Boolean] = BooleanSetting("-Yno-deep-subtypes", "Throw an exception on deep subtyping call stacks.") diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 7a0f18ab9e24..d68d5c433064 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -125,7 +125,7 @@ object Settings { case (ListTag, _) => if (argRest.isEmpty) missingArg else update((argRest split ",").toList, args) - case (StringTag, _) if argRest.nonEmpty => + case (StringTag, _) if argRest.nonEmpty || choices.exists(_.contains("")) => setString(argRest, args) case (StringTag, arg2 :: args2) => if (arg2 startsWith "-") missingArg diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index e7a2dcdbd58f..23555a58abdd 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -660,8 +660,11 @@ object StdNames { final val STAR : N = "*" final val TILDE: N = "~" - final val MINUS_STAR: N = "-*" - final val PLUS_STAR : N = "+*" + // kind-projector compat symbols + final val MINUS_STAR : N = "-*" + final val PLUS_STAR : N = "+*" + final val MINUS_USCORE: N = "-_" + final val PLUS_USCORE : N = "+_" final val isUnary: Set[Name] = Set(MINUS, PLUS, TILDE, BANG) } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 874d3be80f63..8042cf6181e9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1356,7 +1356,7 @@ object Parsers { if imods.is(Given) && params.isEmpty then syntaxError("context function types require at least one parameter", paramSpan) new FunctionWithMods(params, t, imods) - else if ctx.settings.YkindProjector.value then + else if !ctx.settings.YkindProjector.isDefault then val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t) lambdaAbstract(tparams, Function(newParams, newT)) @@ -1462,12 +1462,16 @@ object Parsers { */ private def replaceKindProjectorPlaceholders(params: List[Tree]): (List[Tree], List[TypeDef]) = { val tparams = new ListBuffer[TypeDef] + def addParam() = { + val name = WildcardParamName.fresh().toTypeName + tparams += makeKindProjectorTypeDef(name) + Ident(name) + } + val uscores = ctx.settings.YkindProjector.value == "underscores" val newParams = params.mapConserve { - case param @ Ident(tpnme.raw.STAR | tpnme.raw.MINUS_STAR | tpnme.raw.PLUS_STAR) => - val name = WildcardParamName.fresh().toTypeName - tparams += makeKindProjectorTypeDef(name) - Ident(name) + case param @ Ident(tpnme.raw.STAR | tpnme.raw.MINUS_STAR | tpnme.raw.PLUS_STAR) => addParam() + case param @ Ident(tpnme.USCOREkw | tpnme.raw.MINUS_USCORE | tpnme.raw.PLUS_USCORE) if uscores => addParam() case other => other } @@ -1574,15 +1578,26 @@ object Parsers { if isSimpleLiteral then SingletonTypeTree(simpleLiteral()) else if in.token == USCORE then - if sourceVersion.isAtLeast(future) then - deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") - patch(source, Span(in.offset, in.offset + 1), "?") + if ctx.settings.YkindProjector.value == "underscores" then + val start = in.skipToken() + Ident(tpnme.USCOREkw).withSpan(Span(start, in.lastOffset, start)) + else + if sourceVersion.isAtLeast(future) then + deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead") + patch(source, Span(in.offset, in.offset + 1), "?") + val start = in.skipToken() + typeBounds().withSpan(Span(start, in.lastOffset, start)) + // Allow symbols -_ and +_ through for compatibility with code written using kind-projector in Scala 3 underscore mode. + // While these signify variant type parameters in Scala 2 + kind-projector, we ignore their variance markers since variance is inferred. + else if (isIdent(nme.MINUS) || isIdent(nme.PLUS)) && in.lookahead.token == USCORE && ctx.settings.YkindProjector.value == "underscores" then + val identName = in.name.toTypeName ++ nme.USCOREkw val start = in.skipToken() - typeBounds().withSpan(Span(start, in.lastOffset, start)) + in.nextToken() + Ident(identName).withSpan(Span(start, in.lastOffset, start)) else if isIdent(nme.?) then val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) - else if isIdent(nme.*) && ctx.settings.YkindProjector.value then + else if isIdent(nme.*) && !ctx.settings.YkindProjector.isDefault then typeIdent() else def singletonArgs(t: Tree): Tree = @@ -1628,7 +1643,7 @@ object Parsers { val applied = rejectWildcardType(t) val args = typeArgs(namedOK = false, wildOK = true) - if (ctx.settings.YkindProjector.value) { + if (!ctx.settings.YkindProjector.isDefault) { def fail(): Tree = { syntaxError( "λ requires a single argument of the form X => ... or (X, Y) => ...", @@ -1660,7 +1675,7 @@ object Parsers { } }) case _ => - if (ctx.settings.YkindProjector.value) { + if (!ctx.settings.YkindProjector.isDefault) { t match { case Tuple(params) => val (newParams, tparams) = replaceKindProjectorPlaceholders(params) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index db05ed611a0e..740fd04d4243 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -54,6 +54,7 @@ class CompilationTests { compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")), compileFile("tests/pos-special/i7575.scala", defaultOptions.andLanguageFeature("dynamics")), compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")), + compileFile("tests/pos-special/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")), compileFile("tests/run/i5606.scala", defaultOptions.and("-Yretain-trees")), compileFile("tests/pos-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"), compileFile("tests/pos-custom-args/i8875.scala", defaultOptions.and("-Xprint:getters")), @@ -166,6 +167,7 @@ class CompilationTests { compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")), compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")), compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")), + compileFile("tests/neg-custom-args/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")), compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-language:experimental.erasedDefinitions")), compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"), compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")), diff --git a/docs/docs/reference/changed-features/wildcards.md b/docs/docs/reference/changed-features/wildcards.md index 45d436e2cb19..c3c3adeb5bfe 100644 --- a/docs/docs/reference/changed-features/wildcards.md +++ b/docs/docs/reference/changed-features/wildcards.md @@ -42,3 +42,8 @@ option `-Ykind-projector`: 3. In Scala 3.3, `*` is removed again, and all type parameter placeholders will be expressed with `_`. These rules make it possible to cross build between Scala 2 using the kind projector plugin and Scala 3.0 - 3.2 using the compiler option `-Ykind-projector`. + +There is also a migration path for users that want a one-time transition to syntax with `_` as a type parameter placeholder. +With option `-Ykind-projector:underscores` Scala 3 will regard `_` as a type parameter placeholder, leaving `?` as the only syntax for wildcards. + +To cross-compile with old Scala 2 sources, while using `_` a placeholder, you must use options `-Xsource:3 -P:kind-projector:underscore-placeholders` together with a recent version of kind-projector (`0.13` and higher) and most recent versions of Scala 2 (`2.13.5` and higher and `2.12.14` and higher) diff --git a/tests/neg-custom-args/kind-projector-underscores.check b/tests/neg-custom-args/kind-projector-underscores.check new file mode 100644 index 000000000000..4198a8d2b3b6 --- /dev/null +++ b/tests/neg-custom-args/kind-projector-underscores.check @@ -0,0 +1,24 @@ +-- Error: tests/neg-custom-args/kind-projector-underscores.scala:7:23 -------------------------------------------------- +7 |class Bar3 extends Foo[λ[List[x] => Int]] // error + | ^^^^^^^^^^^^^^^^^ + | λ requires a single argument of the form X => ... or (X, Y) => ... +-- [E095] Syntax Error: tests/neg-custom-args/kind-projector-underscores.scala:10:8 ------------------------------------ +10 | type -_ = Int // error -_ not allowed as a type def name without backticks + | ^ + | =, >:, or <: expected, but '_' found + +longer explanation available when compiling with `-explain` +-- [E095] Syntax Error: tests/neg-custom-args/kind-projector-underscores.scala:11:8 ------------------------------------ +11 | type +_ = Int // error +_ not allowed as a type def name without backticks + | ^ + | =, >:, or <: expected, but '_' found + +longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/kind-projector-underscores.scala:5:23 -------------------------------------------------- +5 |class Bar1 extends Foo[Either[_, _]] // error + | ^^^^^^^^^^^^ + | Type argument Either does not have the same kind as its bound [_$1] +-- Error: tests/neg-custom-args/kind-projector-underscores.scala:6:22 -------------------------------------------------- +6 |class Bar2 extends Foo[_] // error + | ^ + | Type argument _ does not have the same kind as its bound [_$1] diff --git a/tests/neg-custom-args/kind-projector-underscores.scala b/tests/neg-custom-args/kind-projector-underscores.scala new file mode 100644 index 000000000000..aaf76fa93183 --- /dev/null +++ b/tests/neg-custom-args/kind-projector-underscores.scala @@ -0,0 +1,12 @@ +package kind_projector_neg + +trait Foo[F[_]] + +class Bar1 extends Foo[Either[_, _]] // error +class Bar2 extends Foo[_] // error +class Bar3 extends Foo[λ[List[x] => Int]] // error + +object Test { + type -_ = Int // error -_ not allowed as a type def name without backticks + type +_ = Int // error +_ not allowed as a type def name without backticks +} diff --git a/tests/pos-special/kind-projector-underscores.scala b/tests/pos-special/kind-projector-underscores.scala new file mode 100644 index 000000000000..06face862e53 --- /dev/null +++ b/tests/pos-special/kind-projector-underscores.scala @@ -0,0 +1,59 @@ +package kind_projector + +trait Foo[F[_]] +trait Qux[F[_, _]] +trait Baz[F[_], A, B] + +trait FooPlus[+F[+_]] +trait QuxPlus[+F[+_, +_]] +trait BazPlus[+F[+_], +A, +B] + +trait FooMinus[-F[-_]] +trait QuxMinus[-F[-_, -_]] +trait BazMinus[-F[-_], -A, -B] + +class Bar1 extends Foo[Either[Int, _]] +class Bar2 extends Foo[Either[_, Int]] +class Bar3 extends Foo[_ => Int] +class Bar4 extends Foo[Int => _] +class Bar5 extends Foo[(Int, _, Int)] +class Bar6 extends Foo[λ[x => Either[Int, x]]] +class Bar7 extends Qux[λ[(x, y) => Either[y, x]]] +class Bar8 extends Foo[Baz[Int => _, _, Int]] +class Bar9 extends Foo[λ[x => Baz[x => _, Int, x]]] + +class BarPlus1 extends FooPlus[Either[Int, +_]] +class BarPlus2 extends FooPlus[Either[+_, Int]] +class BarPlus3 extends FooPlus[Int => +_] +class BarPlus4 extends FooPlus[(Int, +_, Int)] +class BarPlus5 extends FooPlus[λ[`+x` => Either[Int, x]]] +class BarPlus6 extends QuxPlus[λ[(`+x`, `+y`) => Either[y, x]]] +class BarPlus7 extends FooPlus[BazPlus[Int => +_, +_, Int]] + +class BarMinus1 extends FooMinus[-_ => Int] + +class VarianceAnnotationIsActuallyIgnored1 extends FooPlus[Either[Int, -_]] +class VarianceAnnotationIsActuallyIgnored2 extends FooPlus[Either[-_, Int]] +class VarianceAnnotationIsActuallyIgnored3 extends FooMinus[+_ => Int] +class VarianceAnnotationIsActuallyIgnored4 extends FooPlus[Int => -_] +class VarianceAnnotationIsActuallyIgnored5 extends FooPlus[(Int, -_, Int)] +class VarianceAnnotationIsActuallyIgnored6 extends FooPlus[λ[`-x` => Either[Int, x]]] +class VarianceAnnotationIsActuallyIgnored7 extends QuxPlus[λ[(`-x`, `-y`) => Either[y, x]]] +class VarianceAnnotationIsActuallyIgnored8 extends FooPlus[BazPlus[Int => -_, -_, Int]] +class VarianceAnnotationIsActuallyIgnored9 extends Foo[λ[`-x` => BazPlus[x => -_, Int, x]]] + +class BackticksAreFine1 extends FooPlus[Either[Int, `-_`]] +class BackticksAreFine2 extends FooPlus[Either[`-_`, Int]] +class BackticksAreFine3 extends FooMinus[`+_` => Int] +class BackticksAreFine4 extends FooPlus[Int => `-_`] +class BackticksAreFine5 extends FooPlus[(Int, `-_`, Int)] +class BackticksAreFine6 extends FooPlus[BazPlus[Int => `-_`, `-_`, Int]] +class BackticksAreFine7 extends Foo[λ[`-x` => BazPlus[x => `-_`, Int, x]]] + +class SpacesAreFine1 extends FooPlus[Either[Int, - _ ]] +class SpacesAreFine2 extends FooPlus[Either[ - _ , Int]] +class SpacesAreFine3 extends FooMinus[ + _ => Int] +class SpacesAreFine4 extends FooPlus[Int => - _] +class SpacesAreFine5 extends FooPlus[(Int, - _, Int)] +class SpacesAreFine6 extends FooPlus[BazPlus[Int => - _ , - _, Int]] +class SpacesAreFine7 extends Foo[λ[`-x` => BazPlus[x => - _ , Int, x]]]