Skip to content

Commit

Permalink
Fix #7139: Implement kind-projector compatibility
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
travisbrown committed Jan 23, 2020
1 parent 899c59b commit b5adfcb
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 4 deletions.
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ object StdNames {
final val BAR : N = "|"
final val DOLLAR: N = "$"
final val GE: N = ">="
final val LAMBDA: N = "λ"
final val LE: N = "<="
final val MINUS: N = "-"
final val NE: N = "!="
Expand Down
82 changes: 78 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1358,8 +1358,15 @@ object Parsers {
def functionRest(params: List[Tree]): Tree =
atSpan(start, accept(ARROW)) {
val t = typ()

if (imods.isOneOf(Given | Erased)) new FunctionWithMods(params, t, imods)
else Function(params, t)
else if (ctx.settings.YkindProjector.value) {
val (newParams :+ newT, tparams) = replaceKindProjectorPlaceholders(params :+ t)

lambdaAbstract(tparams, Function(newParams, newT))
} else {
Function(params, t)
}
}
def funArgTypesRest(first: Tree, following: () => Tree) = {
val buf = new ListBuffer[Tree] += first
Expand Down Expand Up @@ -1449,6 +1456,26 @@ object Parsers {
}
}

private def makeKindProjectorTypeDef(name: TypeName): TypeDef =
TypeDef(name, TypeBoundsTree(EmptyTree, EmptyTree)).withFlags(Param)

/** Replaces kind-projector's `*` in a list of types arguments with synthetic names,
* returning the new argument list and the synthetic type definitions.
*/
private def replaceKindProjectorPlaceholders(params: List[Tree]): (List[Tree], List[TypeDef]) = {
val tparams = new ListBuffer[TypeDef]

val newParams = params.mapConserve {
case param @ Ident(tpnme.raw.STAR) =>
val name = WildcardParamName.fresh().toTypeName
tparams += makeKindProjectorTypeDef(name)
Ident(name)
case other => other
}

(newParams, tparams.toList)
}

private def implicitKwPos(start: Int): Span =
Span(start, start + nme.IMPLICITkw.asSimpleName.length)

Expand Down Expand Up @@ -1565,7 +1592,6 @@ object Parsers {
typeBounds().withSpan(Span(start, in.lastOffset, start))
}
else if (isIdent(nme.*) && ctx.settings.YkindProjector.value) {
syntaxError("`*` placeholders are not implemented yet")
typeIdent()
}
else if (isSplice)
Expand All @@ -1586,8 +1612,56 @@ object Parsers {
private def simpleTypeRest(t: Tree): Tree = in.token match {
case HASH => simpleTypeRest(typeProjection(t))
case LBRACKET => simpleTypeRest(atSpan(startOffset(t)) {
AppliedTypeTree(rejectWildcardType(t), typeArgs(namedOK = false, wildOK = true)) })
case _ => t
val applied = rejectWildcardType(t)
val args = typeArgs(namedOK = false, wildOK = true)

if (ctx.settings.YkindProjector.value) {
def fail(): Tree = {
syntaxError(
"λ requires a single argument of the form X => ... or (X, Y) => ...",
Span(t.span.start, in.lastOffset)
)
AppliedTypeTree(applied, args)
}

applied match {
case Ident(tpnme.raw.LAMBDA) =>
args match {
case List(Function(params, body)) =>
val typeDefs = params.collect {
case param @ Ident(name) => makeKindProjectorTypeDef(name.toTypeName).withSpan(param.span)
}
if (typeDefs.length != params.length) fail()
else LambdaTypeTree(typeDefs, body)
case _ =>
fail()
}
case _ =>
val (newArgs, tparams) = replaceKindProjectorPlaceholders(args)

lambdaAbstract(tparams, AppliedTypeTree(applied, newArgs))
}

} else {
AppliedTypeTree(applied, args)
}
})
case _ =>
if (ctx.settings.YkindProjector.value) {
t match {
case Tuple(params) =>
val (newParams, tparams) = replaceKindProjectorPlaceholders(params)

if (tparams.isEmpty) {
t
} else {
LambdaTypeTree(tparams, Tuple(newParams))
}
case _ => t
}
} else {
t
}
}

private def typeProjection(t: Tree): Tree = {
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 @@ -62,6 +62,7 @@ class CompilationTests extends ParallelTesting {
compileFile("tests/pos-special/notNull.scala", defaultOptions.and("-Yexplicit-nulls")),
compileDir("tests/pos-special/adhoc-extension", defaultOptions.and("-strict", "-feature", "-Xfatal-warnings")),
compileFile("tests/pos-special/i7575.scala", defaultOptions.and("-language:dynamics")),
compileFile("tests/pos-special/kind-projector.scala", defaultOptions.and("-Ykind-projector")),
).checkCompile()
}

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

Expand Down
12 changes: 12 additions & 0 deletions tests/neg-custom-args/kind-projector.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Error: tests/neg-custom-args/kind-projector.scala:7:23 --------------------------------------------------------------
7 |class Bar3 extends Foo[λ[List[x] => Int]] // error
| ^^^^^^^^^^^^^^^^^
| λ requires a single argument of the form X => ... or (X, Y) => ...
-- Error: tests/neg-custom-args/kind-projector.scala:5:23 --------------------------------------------------------------
5 |class Bar1 extends Foo[Either[*, *]] // error
| ^^^^^^^^^^^^
| Type argument Either has not the same kind as its bound <: [_$1] => Any
-- Error: tests/neg-custom-args/kind-projector.scala:6:22 --------------------------------------------------------------
6 |class Bar2 extends Foo[*] // error
| ^
| Type argument _$4 has not the same kind as its bound <: [_$1] => Any
7 changes: 7 additions & 0 deletions tests/neg-custom-args/kind-projector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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
15 changes: 15 additions & 0 deletions tests/pos-special/kind-projector.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kind_projector

trait Foo[F[_]]
trait Qux[F[_, _]]
trait Baz[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]]]

0 comments on commit b5adfcb

Please sign in to comment.