From b5adfcb2fb830aea5ea25068f19c7b8833a91655 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Sat, 14 Dec 2019 22:22:00 +0100 Subject: [PATCH] Fix #7139: Implement kind-projector compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/parsing/Parsers.scala | 82 ++++++++++++++++++- .../dotty/tools/dotc/CompilationTests.scala | 2 + tests/neg-custom-args/kind-projector.check | 12 +++ tests/neg-custom-args/kind-projector.scala | 7 ++ tests/pos-special/kind-projector.scala | 15 ++++ 6 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 tests/neg-custom-args/kind-projector.check create mode 100644 tests/neg-custom-args/kind-projector.scala create mode 100644 tests/pos-special/kind-projector.scala diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 746d5d2e3d20..9a91a61d5724 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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 = "!=" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 22717aff41ad..3ee30ca8d00d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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 @@ -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) @@ -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) @@ -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 = { diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 246a7331106b..22c39588de38 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -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() } @@ -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() } diff --git a/tests/neg-custom-args/kind-projector.check b/tests/neg-custom-args/kind-projector.check new file mode 100644 index 000000000000..e0b42e4d2aa4 --- /dev/null +++ b/tests/neg-custom-args/kind-projector.check @@ -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 diff --git a/tests/neg-custom-args/kind-projector.scala b/tests/neg-custom-args/kind-projector.scala new file mode 100644 index 000000000000..56f7894de078 --- /dev/null +++ b/tests/neg-custom-args/kind-projector.scala @@ -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 diff --git a/tests/pos-special/kind-projector.scala b/tests/pos-special/kind-projector.scala new file mode 100644 index 000000000000..1bfd9a36433a --- /dev/null +++ b/tests/pos-special/kind-projector.scala @@ -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]]]