Skip to content

Commit b5adfcb

Browse files
committed
Fix #7139: Implement kind-projector compatibility
This change adds support for a subset of kind-projector's syntax behind the existing -Ykind-projector flag. It supports the following kind-projector features: - * placeholder (Functor[F[L, *]] instead of Functor[[x] => F[L, x]]). - * in tuple types (Functor[(A, *)] instead of Functor[[x] => (A, x)]). - * in function types (both Functor[S => *] and Functor[* => T] work). - λ syntax (Functor[λ[x => (x, x)]] for Functor[[x] => (x, x)]). There are a few things kind-projector provides that the flag doesn't: - ? as a placeholder (since it collides with wildcards). - * as a placeholder in infix types. - Lambda as an alternative for λ. - λ arguments of a kind other than * (no λ[f[_] => Functor[f]]). - Variance annotations on either * or λ arguments. - Polymorphic lambda values (λ[Vector ~> List](_.toList)). The changes have no effect on parsing if -Ykind-projector isn't enabled.
1 parent 899c59b commit b5adfcb

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

compiler/src/dotty/tools/dotc/core/StdNames.scala

+1
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ object StdNames {
630630
final val BAR : N = "|"
631631
final val DOLLAR: N = "$"
632632
final val GE: N = ">="
633+
final val LAMBDA: N = "λ"
633634
final val LE: N = "<="
634635
final val MINUS: N = "-"
635636
final val NE: N = "!="

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+78-4
Original file line numberDiff line numberDiff line change
@@ -1358,8 +1358,15 @@ object Parsers {
13581358
def functionRest(params: List[Tree]): Tree =
13591359
atSpan(start, accept(ARROW)) {
13601360
val t = typ()
1361+
13611362
if (imods.isOneOf(Given | Erased)) new FunctionWithMods(params, t, imods)
1362-
else Function(params, t)
1363+
else if (ctx.settings.YkindProjector.value) {
1364+
val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t)
1365+
1366+
lambdaAbstract(tparams, Function(newParams, newT))
1367+
} else {
1368+
Function(params, t)
1369+
}
13631370
}
13641371
def funArgTypesRest(first: Tree, following: () => Tree) = {
13651372
val buf = new ListBuffer[Tree] += first
@@ -1449,6 +1456,26 @@ object Parsers {
14491456
}
14501457
}
14511458

1459+
private def makeKindProjectorTypeDef(name: TypeName): TypeDef =
1460+
TypeDef(name, TypeBoundsTree(EmptyTree, EmptyTree)).withFlags(Param)
1461+
1462+
/** Replaces kind-projector's `*` in a list of types arguments with synthetic names,
1463+
* returning the new argument list and the synthetic type definitions.
1464+
*/
1465+
private def replaceKindProjectorPlaceholders(params: List[Tree]): (List[Tree], List[TypeDef]) = {
1466+
val tparams = new ListBuffer[TypeDef]
1467+
1468+
val newParams = params.mapConserve {
1469+
case param @ Ident(tpnme.raw.STAR) =>
1470+
val name = WildcardParamName.fresh().toTypeName
1471+
tparams += makeKindProjectorTypeDef(name)
1472+
Ident(name)
1473+
case other => other
1474+
}
1475+
1476+
(newParams, tparams.toList)
1477+
}
1478+
14521479
private def implicitKwPos(start: Int): Span =
14531480
Span(start, start + nme.IMPLICITkw.asSimpleName.length)
14541481

@@ -1565,7 +1592,6 @@ object Parsers {
15651592
typeBounds().withSpan(Span(start, in.lastOffset, start))
15661593
}
15671594
else if (isIdent(nme.*) && ctx.settings.YkindProjector.value) {
1568-
syntaxError("`*` placeholders are not implemented yet")
15691595
typeIdent()
15701596
}
15711597
else if (isSplice)
@@ -1586,8 +1612,56 @@ object Parsers {
15861612
private def simpleTypeRest(t: Tree): Tree = in.token match {
15871613
case HASH => simpleTypeRest(typeProjection(t))
15881614
case LBRACKET => simpleTypeRest(atSpan(startOffset(t)) {
1589-
AppliedTypeTree(rejectWildcardType(t), typeArgs(namedOK = false, wildOK = true)) })
1590-
case _ => t
1615+
val applied = rejectWildcardType(t)
1616+
val args = typeArgs(namedOK = false, wildOK = true)
1617+
1618+
if (ctx.settings.YkindProjector.value) {
1619+
def fail(): Tree = {
1620+
syntaxError(
1621+
"λ requires a single argument of the form X => ... or (X, Y) => ...",
1622+
Span(t.span.start, in.lastOffset)
1623+
)
1624+
AppliedTypeTree(applied, args)
1625+
}
1626+
1627+
applied match {
1628+
case Ident(tpnme.raw.LAMBDA) =>
1629+
args match {
1630+
case List(Function(params, body)) =>
1631+
val typeDefs = params.collect {
1632+
case param @ Ident(name) => makeKindProjectorTypeDef(name.toTypeName).withSpan(param.span)
1633+
}
1634+
if (typeDefs.length != params.length) fail()
1635+
else LambdaTypeTree(typeDefs, body)
1636+
case _ =>
1637+
fail()
1638+
}
1639+
case _ =>
1640+
val (newArgs, tparams) = replaceKindProjectorPlaceholders(args)
1641+
1642+
lambdaAbstract(tparams, AppliedTypeTree(applied, newArgs))
1643+
}
1644+
1645+
} else {
1646+
AppliedTypeTree(applied, args)
1647+
}
1648+
})
1649+
case _ =>
1650+
if (ctx.settings.YkindProjector.value) {
1651+
t match {
1652+
case Tuple(params) =>
1653+
val (newParams, tparams) = replaceKindProjectorPlaceholders(params)
1654+
1655+
if (tparams.isEmpty) {
1656+
t
1657+
} else {
1658+
LambdaTypeTree(tparams, Tuple(newParams))
1659+
}
1660+
case _ => t
1661+
}
1662+
} else {
1663+
t
1664+
}
15911665
}
15921666

15931667
private def typeProjection(t: Tree): Tree = {

compiler/test/dotty/tools/dotc/CompilationTests.scala

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class CompilationTests extends ParallelTesting {
6262
compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")),
6363
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
6464
compileFile("tests/pos-special/i7575.scala", defaultOptions.and("-language:dynamics")),
65+
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
6566
).checkCompile()
6667
}
6768

@@ -151,6 +152,7 @@ class CompilationTests extends ParallelTesting {
151152
compileFile("tests/neg-custom-args/extmethods-tparams.scala", defaultOptions.and("-deprecation", "-Xfatal-warnings")),
152153
compileDir("tests/neg-custom-args/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
153154
compileFile("tests/neg/i7575.scala", defaultOptions.and("-language:_")),
155+
compileFile("tests/neg-custom-args/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
154156
).checkExpectedErrors()
155157
}
156158

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Error: tests/neg-custom-args/kind-projector.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+
-- Error: tests/neg-custom-args/kind-projector.scala:5:23 --------------------------------------------------------------
6+
5 |class Bar1 extends Foo[Either[*, *]] // error
7+
| ^^^^^^^^^^^^
8+
| Type argument Either has not the same kind as its bound <: [_$1] => Any
9+
-- Error: tests/neg-custom-args/kind-projector.scala:6:22 --------------------------------------------------------------
10+
6 |class Bar2 extends Foo[*] // error
11+
| ^
12+
| Type argument _$4 has not the same kind as its bound <: [_$1] => Any
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
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
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package kind_projector
2+
3+
trait Foo[F[_]]
4+
trait Qux[F[_, _]]
5+
trait Baz[F[_], A, B]
6+
7+
class Bar1 extends Foo[Either[Int, *]]
8+
class Bar2 extends Foo[Either[*, Int]]
9+
class Bar3 extends Foo[* => Int]
10+
class Bar4 extends Foo[Int => *]
11+
class Bar5 extends Foo[(Int, *, Int)]
12+
class Bar6 extends Foo[λ[x => Either[Int, x]]]
13+
class Bar7 extends Qux[λ[(x, y) => Either[y, x]]]
14+
class Bar8 extends Foo[Baz[Int => *, *, Int]]
15+
class Bar9 extends Foo[λ[x => Baz[x => *, Int, x]]]

0 commit comments

Comments
 (0)