Skip to content

Commit 196868c

Browse files
committed
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.
1 parent a1042c1 commit 196868c

File tree

9 files changed

+136
-16
lines changed

9 files changed

+136
-16
lines changed

Diff for: compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
165165
val YstopBefore: Setting[List[String]] = PhasesSetting("-Ystop-before", "Stop before") // stop before erasure as long as we have not debugged it fully
166166
val YshowSuppressedErrors: Setting[Boolean] = BooleanSetting("-Yshow-suppressed-errors", "Also show follow-on errors and warnings that are normally suppressed.")
167167
val YdetailedStats: Setting[Boolean] = BooleanSetting("-Ydetailed-stats", "Show detailed internal compiler stats (needs Stats.enabled to be set to true).")
168-
val YkindProjector: Setting[Boolean] = BooleanSetting("-Ykind-projector", "Allow `*` as wildcard to be compatible with kind projector.")
168+
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")
169169
val YprintPos: Setting[Boolean] = BooleanSetting("-Yprint-pos", "Show tree positions.")
170170
val YprintPosSyms: Setting[Boolean] = BooleanSetting("-Yprint-pos-syms", "Show symbol definitions positions.")
171171
val YnoDeepSubtypes: Setting[Boolean] = BooleanSetting("-Yno-deep-subtypes", "Throw an exception on deep subtyping call stacks.")

Diff for: compiler/src/dotty/tools/dotc/config/Settings.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ object Settings {
125125
case (ListTag, _) =>
126126
if (argRest.isEmpty) missingArg
127127
else update((argRest split ",").toList, args)
128-
case (StringTag, _) if argRest.nonEmpty =>
128+
case (StringTag, _) if argRest.nonEmpty || choices.exists(_.contains("")) =>
129129
setString(argRest, args)
130130
case (StringTag, arg2 :: args2) =>
131131
if (arg2 startsWith "-") missingArg

Diff for: compiler/src/dotty/tools/dotc/core/StdNames.scala

+5-2
Original file line numberDiff line numberDiff line change
@@ -660,8 +660,11 @@ object StdNames {
660660
final val STAR : N = "*"
661661
final val TILDE: N = "~"
662662

663-
final val MINUS_STAR: N = "-*"
664-
final val PLUS_STAR : N = "+*"
663+
// kind-projector compat symbols
664+
final val MINUS_STAR : N = "-*"
665+
final val PLUS_STAR : N = "+*"
666+
final val MINUS_USCORE: N = "-_"
667+
final val PLUS_USCORE : N = "+_"
665668

666669
final val isUnary: Set[Name] = Set(MINUS, PLUS, TILDE, BANG)
667670
}

Diff for: compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+27-12
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ object Parsers {
13561356
if imods.is(Given) && params.isEmpty then
13571357
syntaxError("context function types require at least one parameter", paramSpan)
13581358
new FunctionWithMods(params, t, imods)
1359-
else if ctx.settings.YkindProjector.value then
1359+
else if !ctx.settings.YkindProjector.isDefault then
13601360
val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t)
13611361

13621362
lambdaAbstract(tparams, Function(newParams, newT))
@@ -1462,12 +1462,16 @@ object Parsers {
14621462
*/
14631463
private def replaceKindProjectorPlaceholders(params: List[Tree]): (List[Tree], List[TypeDef]) = {
14641464
val tparams = new ListBuffer[TypeDef]
1465+
def addParam() = {
1466+
val name = WildcardParamName.fresh().toTypeName
1467+
tparams += makeKindProjectorTypeDef(name)
1468+
Ident(name)
1469+
}
14651470

1471+
val uscores = !ctx.settings.YkindProjector.isDefault
14661472
val newParams = params.mapConserve {
1467-
case param @ Ident(tpnme.raw.STAR | tpnme.raw.MINUS_STAR | tpnme.raw.PLUS_STAR) =>
1468-
val name = WildcardParamName.fresh().toTypeName
1469-
tparams += makeKindProjectorTypeDef(name)
1470-
Ident(name)
1473+
case param @ Ident(tpnme.raw.STAR | tpnme.raw.MINUS_STAR | tpnme.raw.PLUS_STAR) => addParam()
1474+
case param @ Ident(tpnme.USCOREkw | tpnme.raw.MINUS_USCORE | tpnme.raw.PLUS_USCORE) if uscores => addParam()
14711475
case other => other
14721476
}
14731477

@@ -1574,15 +1578,26 @@ object Parsers {
15741578
if isSimpleLiteral then
15751579
SingletonTypeTree(simpleLiteral())
15761580
else if in.token == USCORE then
1577-
if sourceVersion.isAtLeast(future) then
1578-
deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead")
1579-
patch(source, Span(in.offset, in.offset + 1), "?")
1581+
if !ctx.settings.YkindProjector.isDefault then
1582+
val start = in.skipToken()
1583+
Ident(tpnme.USCOREkw).withSpan(Span(start, in.lastOffset, start))
1584+
else
1585+
if sourceVersion.isAtLeast(future) then
1586+
deprecationWarning(em"`_` is deprecated for wildcard arguments of types: use `?` instead")
1587+
patch(source, Span(in.offset, in.offset + 1), "?")
1588+
val start = in.skipToken()
1589+
typeBounds().withSpan(Span(start, in.lastOffset, start))
1590+
// Allow symbols -_ and +_ through for compatibility with code written using kind-projector in Scala 3 underscore mode.
1591+
// While these signify variant type parameters in Scala 2 + kind-projector, we ignore their variance markers since variance is inferred.
1592+
else if (isIdent(nme.MINUS) || isIdent(nme.PLUS)) && in.lookahead.token == USCORE && ctx.settings.YkindProjector.value == "underscores" then
1593+
val identName = in.name.toTypeName ++ nme.USCOREkw
15801594
val start = in.skipToken()
1581-
typeBounds().withSpan(Span(start, in.lastOffset, start))
1595+
in.nextToken()
1596+
Ident(identName).withSpan(Span(start, in.lastOffset, start))
15821597
else if isIdent(nme.?) then
15831598
val start = in.skipToken()
15841599
typeBounds().withSpan(Span(start, in.lastOffset, start))
1585-
else if isIdent(nme.*) && ctx.settings.YkindProjector.value then
1600+
else if isIdent(nme.*) && !ctx.settings.YkindProjector.isDefault then
15861601
typeIdent()
15871602
else
15881603
def singletonArgs(t: Tree): Tree =
@@ -1628,7 +1643,7 @@ object Parsers {
16281643
val applied = rejectWildcardType(t)
16291644
val args = typeArgs(namedOK = false, wildOK = true)
16301645

1631-
if (ctx.settings.YkindProjector.value) {
1646+
if (!ctx.settings.YkindProjector.isDefault) {
16321647
def fail(): Tree = {
16331648
syntaxError(
16341649
"λ requires a single argument of the form X => ... or (X, Y) => ...",
@@ -1660,7 +1675,7 @@ object Parsers {
16601675
}
16611676
})
16621677
case _ =>
1663-
if (ctx.settings.YkindProjector.value) {
1678+
if (!ctx.settings.YkindProjector.isDefault) {
16641679
t match {
16651680
case Tuple(params) =>
16661681
val (newParams, tparams) = replaceKindProjectorPlaceholders(params)

Diff for: compiler/test/dotty/tools/dotc/CompilationTests.scala

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class CompilationTests {
5454
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")),
5555
compileFile("tests/pos-special/i7575.scala", defaultOptions.andLanguageFeature("dynamics")),
5656
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
57+
compileFile("tests/pos-special/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")),
5758
compileFile("tests/run/i5606.scala", defaultOptions.and("-Yretain-trees")),
5859
compileFile("tests/pos-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
5960
compileFile("tests/pos-custom-args/i8875.scala", defaultOptions.and("-Xprint:getters")),
@@ -166,6 +167,7 @@ class CompilationTests {
166167
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-source", "future", "-feature", "-Xfatal-warnings")),
167168
compileFile("tests/neg/i7575.scala", defaultOptions.withoutLanguageFeatures.and("-language:_")),
168169
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
170+
compileFile("tests/neg-custom-args/kind-projector-underscores.scala", defaultOptions.and("-Ykind-projector:underscores")),
169171
compileFile("tests/neg-custom-args/typeclass-derivation2.scala", defaultOptions.and("-language:experimental.erasedDefinitions")),
170172
compileFile("tests/neg-custom-args/i5498-postfixOps.scala", defaultOptions withoutLanguageFeature "postfixOps"),
171173
compileFile("tests/neg-custom-args/deptypes.scala", defaultOptions.and("-language:experimental.dependent")),

Diff for: docs/docs/reference/changed-features/wildcards.md

+5
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,8 @@ option `-Ykind-projector`:
4242
3. In Scala 3.3, `*` is removed again, and all type parameter placeholders will be expressed with `_`.
4343

4444
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`.
45+
46+
There is also a migration path for users that want a one-time transition to syntax with `_` as a type parameter placeholder.
47+
With option `-Ykind-projector:underscores` Scala 3 will regard `_` as a type parameter placeholder, leaving `?` as the only syntax for wildcards.
48+
49+
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- Error: tests/neg-custom-args/kind-projector-underscores.scala:7:23 --------------------------------------------------
2+
7 |class Bar3 extends Foo[λ[List[x] => Int]] // error
3+
| ^^^^^^^^^^^^^^^^^
4+
| λ requires a single argument of the form X => ... or (X, Y) => ...
5+
-- [E095] Syntax Error: tests/neg-custom-args/kind-projector-underscores.scala:10:8 ------------------------------------
6+
10 | type -_ = Int // error -_ not allowed as a type def name without backticks
7+
| ^
8+
| =, >:, or <: expected, but '_' found
9+
10+
longer explanation available when compiling with `-explain`
11+
-- [E095] Syntax Error: tests/neg-custom-args/kind-projector-underscores.scala:11:8 ------------------------------------
12+
11 | type +_ = Int // error +_ not allowed as a type def name without backticks
13+
| ^
14+
| =, >:, or <: expected, but '_' found
15+
16+
longer explanation available when compiling with `-explain`
17+
-- Error: tests/neg-custom-args/kind-projector-underscores.scala:5:23 --------------------------------------------------
18+
5 |class Bar1 extends Foo[Either[_, _]] // error
19+
| ^^^^^^^^^^^^
20+
| Type argument Either does not have the same kind as its bound [_$1]
21+
-- Error: tests/neg-custom-args/kind-projector-underscores.scala:6:22 --------------------------------------------------
22+
6 |class Bar2 extends Foo[_] // error
23+
| ^
24+
| Type argument _ does not have the same kind as its bound [_$1]
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package kind_projector_neg
2+
3+
trait Foo[F[_]]
4+
5+
class Bar1 extends Foo[Either[_, _]] // error
6+
class Bar2 extends Foo[_] // error
7+
class Bar3 extends Foo[λ[List[x] => Int]] // error
8+
9+
object Test {
10+
type -_ = Int // error -_ not allowed as a type def name without backticks
11+
type +_ = Int // error +_ not allowed as a type def name without backticks
12+
}

Diff for: tests/pos-special/kind-projector-underscores.scala

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package kind_projector
2+
3+
trait Foo[F[_]]
4+
trait Qux[F[_, _]]
5+
trait Baz[F[_], A, B]
6+
7+
trait FooPlus[+F[+_]]
8+
trait QuxPlus[+F[+_, +_]]
9+
trait BazPlus[+F[+_], +A, +B]
10+
11+
trait FooMinus[-F[-_]]
12+
trait QuxMinus[-F[-_, -_]]
13+
trait BazMinus[-F[-_], -A, -B]
14+
15+
class Bar1 extends Foo[Either[Int, _]]
16+
class Bar2 extends Foo[Either[_, Int]]
17+
class Bar3 extends Foo[_ => Int]
18+
class Bar4 extends Foo[Int => _]
19+
class Bar5 extends Foo[(Int, _, Int)]
20+
class Bar6 extends Foo[λ[x => Either[Int, x]]]
21+
class Bar7 extends Qux[λ[(x, y) => Either[y, x]]]
22+
class Bar8 extends Foo[Baz[Int => _, _, Int]]
23+
class Bar9 extends Foo[λ[x => Baz[x => _, Int, x]]]
24+
25+
class BarPlus1 extends FooPlus[Either[Int, +_]]
26+
class BarPlus2 extends FooPlus[Either[+_, Int]]
27+
class BarPlus3 extends FooPlus[Int => +_]
28+
class BarPlus4 extends FooPlus[(Int, +_, Int)]
29+
class BarPlus5 extends FooPlus[λ[`+x` => Either[Int, x]]]
30+
class BarPlus6 extends QuxPlus[λ[(`+x`, `+y`) => Either[y, x]]]
31+
class BarPlus7 extends FooPlus[BazPlus[Int => +_, +_, Int]]
32+
33+
class BarMinus1 extends FooMinus[-_ => Int]
34+
35+
class VarianceAnnotationIsActuallyIgnored1 extends FooPlus[Either[Int, -_]]
36+
class VarianceAnnotationIsActuallyIgnored2 extends FooPlus[Either[-_, Int]]
37+
class VarianceAnnotationIsActuallyIgnored3 extends FooMinus[+_ => Int]
38+
class VarianceAnnotationIsActuallyIgnored4 extends FooPlus[Int => -_]
39+
class VarianceAnnotationIsActuallyIgnored5 extends FooPlus[(Int, -_, Int)]
40+
class VarianceAnnotationIsActuallyIgnored6 extends FooPlus[λ[`-x` => Either[Int, x]]]
41+
class VarianceAnnotationIsActuallyIgnored7 extends QuxPlus[λ[(`-x`, `-y`) => Either[y, x]]]
42+
class VarianceAnnotationIsActuallyIgnored8 extends FooPlus[BazPlus[Int => -_, -_, Int]]
43+
class VarianceAnnotationIsActuallyIgnored9 extends Foo[λ[`-x` => BazPlus[x => -_, Int, x]]]
44+
45+
class BackticksAreFine1 extends FooPlus[Either[Int, `-_`]]
46+
class BackticksAreFine2 extends FooPlus[Either[`-_`, Int]]
47+
class BackticksAreFine3 extends FooMinus[`+_` => Int]
48+
class BackticksAreFine4 extends FooPlus[Int => `-_`]
49+
class BackticksAreFine5 extends FooPlus[(Int, `-_`, Int)]
50+
class BackticksAreFine6 extends FooPlus[BazPlus[Int => `-_`, `-_`, Int]]
51+
class BackticksAreFine7 extends Foo[λ[`-x` => BazPlus[x => `-_`, Int, x]]]
52+
53+
class SpacesAreFine1 extends FooPlus[Either[Int, - _ ]]
54+
class SpacesAreFine2 extends FooPlus[Either[ - _ , Int]]
55+
class SpacesAreFine3 extends FooMinus[ + _ => Int]
56+
class SpacesAreFine4 extends FooPlus[Int => - _]
57+
class SpacesAreFine5 extends FooPlus[(Int, - _, Int)]
58+
class SpacesAreFine6 extends FooPlus[BazPlus[Int => - _ , - _, Int]]
59+
class SpacesAreFine7 extends Foo[λ[`-x` => BazPlus[x => - _ , Int, x]]]

0 commit comments

Comments
 (0)