Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow _ as a type lambda placeholder in -Ykind-projector:underscores compatiblity mode #12378

Merged
merged 1 commit into from May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
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
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
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
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.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
}

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.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 =
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
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
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
@@ -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
@@ -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
}
59 changes: 59 additions & 0 deletions 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]]]