Skip to content

Commit

Permalink
Allow _ as a type lambda placeholder in `-Ykind-projector:underscor…
Browse files Browse the repository at this point in the history
…es` compatiblity mode

Also allow through `+_` and `-_` only in that mode, these variance markers are ignored since we infer variance.
  • Loading branch information
neko-kai committed May 7, 2021
1 parent a1042c1 commit 9724934
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 16 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
39 changes: 27 additions & 12 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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.isDefault
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
}

Expand Down Expand Up @@ -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.isDefault 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 =
Expand Down Expand Up @@ -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) => ...",
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down Expand Up @@ -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")),
Expand Down
5 changes: 5 additions & 0 deletions docs/docs/reference/changed-features/wildcards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
24 changes: 24 additions & 0 deletions tests/neg-custom-args/kind-projector-underscores.check
Original file line number Diff line number Diff line change
@@ -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]
12 changes: 12 additions & 0 deletions tests/neg-custom-args/kind-projector-underscores.scala
Original file line number Diff line number Diff line change
@@ -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
}
43 changes: 43 additions & 0 deletions tests/pos-special/kind-projector-underscores.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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]]]

0 comments on commit 9724934

Please sign in to comment.