From 80314793e50ce3c34296c4782311adc5fd3eedd6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 29 Aug 2019 12:15:34 +0200 Subject: [PATCH 1/7] Intrinsify scala.compiletime.testing.typeChecks As know from the start, this funtionallity should not have been added to the reflection API. Nevertheless it is still possible to use its result in a macro. --- .../dotty/tools/dotc/core/Definitions.scala | 2 ++ .../ReflectionCompilerInterface.scala | 21 -------------- .../src/dotty/tools/dotc/typer/Inliner.scala | 28 +++++++++++++++++++ .../scala/compiletime/testing/package.scala | 18 ++++++++++++ .../compiletime/testing/typeChecks.scala | 10 ------- library/src/scala/tasty/Reflection.scala | 13 --------- .../tasty/reflect/CompilerInterface.scala | 11 -------- .../run-macros/reflect-inline/assert_1.scala | 7 ++--- .../reflect-typeChecks/assert_1.scala | 10 ++----- .../scala-tests-typeChecks.scala | 0 10 files changed, 54 insertions(+), 66 deletions(-) create mode 100644 library/src-bootstrapped/scala/compiletime/testing/package.scala delete mode 100644 library/src-bootstrapped/scala/compiletime/testing/typeChecks.scala rename tests/{run-with-compiler => run}/scala-tests-typeChecks.scala (100%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 80254f917f6c..faacc072d653 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -220,6 +220,8 @@ class Definitions { @tu lazy val Compiletime_constValue : Symbol = CompiletimePackageObject.requiredMethod("constValue") @tu lazy val Compiletime_constValueOpt: Symbol = CompiletimePackageObject.requiredMethod("constValueOpt") @tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code") + @tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package") + @tu lazy val CompiletimeTesting_typeChecks : Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks") /** The `scalaShadowing` package is used to safely modify classes and * objects in scala so that they can be used from dotty. They will diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index f26f18f2ea0d..eb0e6cc49ab1 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -12,7 +12,6 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Types.SingletonType import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym} -import dotty.tools.dotc.parsing.Parsers.Parser import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType} import dotty.tools.dotc.util.{SourceFile, SourcePosition, Spans} @@ -82,26 +81,6 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Settings_color(self: Settings): Boolean = self.color.value(rootContext) == "always" - // - // MISC - // - /** Whether the code type checks in the given context? - * - * @param code The code to be type checked - * - * The code should be a sequence of expressions or statements that may appear in a block. - */ - def typeChecks(code: String) given (ctx: Context): Boolean = { - val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer) - val tree = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block() - - if (ctx2.reporter.hasErrors) false - else { - ctx2.typer.typed(tree)(ctx2) - !ctx2.reporter.hasErrors - } - } - // // TREES // diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index b7ac128dc517..823e34e97b37 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -23,6 +23,7 @@ import config.Printers.inlining import ErrorReporting.errorTree import dotty.tools.dotc.tastyreflect.ReflectionImpl import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile, SourcePosition} +import dotty.tools.dotc.parsing.Parsers.Parser import collection.mutable import reporting.trace @@ -68,6 +69,7 @@ object Inliner { * and body that replace it. */ def inlineCall(tree: Tree)(implicit ctx: Context): Tree = { + if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) /** Set the position of all trees logically contained in the expansion of * inlined call `call` to the position of `call`. This transform is necessary @@ -192,6 +194,32 @@ object Inliner { if (callSym.is(Macro)) ref(callSym.topLevelClass.owner).select(callSym.topLevelClass.name).withSpan(pos.span) else Ident(callSym.topLevelClass.typeRef).withSpan(pos.span) } + + object Intrinsics { + + /** Expand call to scala.compiletime.testing.typeChecks */ + def typeChecks(tree: Tree)(implicit ctx: Context): Tree = { + assert(tree.symbol == defn.CompiletimeTesting_typeChecks) + def getCodeArgValue(t: Tree): String = t match { + case Literal(Constant(code: String)) => code + case Typed(t2, _) => getCodeArgValue(t2) + case Inlined(_, Nil, t2) => getCodeArgValue(t2) + case Block(Nil, t2) => getCodeArgValue(t2) + } + val Apply(_, codeArg :: Nil) = tree + val code = getCodeArgValue(codeArg.underlyingArgument) + val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer) + val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block() + val res = + if (ctx2.reporter.hasErrors) false + else { + ctx2.typer.typed(tree2)(ctx2) + !ctx2.reporter.hasErrors + } + Literal(Constant(res)) + } + + } } /** Produces an inlined version of `call` via its `inlined` method. diff --git a/library/src-bootstrapped/scala/compiletime/testing/package.scala b/library/src-bootstrapped/scala/compiletime/testing/package.scala new file mode 100644 index 000000000000..42f92b62bb4c --- /dev/null +++ b/library/src-bootstrapped/scala/compiletime/testing/package.scala @@ -0,0 +1,18 @@ +package scala.compiletime + +import scala.quoted._ + +package object testing { + + /** Whether the code type checks in the current context? + * + * @param code The code to be type checked + * + * @return false if the code has syntax error or type error in the current context, otherwise returns true. + * + * The code should be a sequence of expressions or statements that may appear in a block. + */ + inline def typeChecks(inline code: String): Boolean = + error("`typeChecks` was not checked by the compiler") + +} diff --git a/library/src-bootstrapped/scala/compiletime/testing/typeChecks.scala b/library/src-bootstrapped/scala/compiletime/testing/typeChecks.scala deleted file mode 100644 index b3fd57a92c49..000000000000 --- a/library/src-bootstrapped/scala/compiletime/testing/typeChecks.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala.compiletime.testing - -import scala.quoted._ - -inline def typeChecks(inline code: String): Boolean = ${ typeChecksImpl(code) } - -private def typeChecksImpl(code: String) given (qctx: QuoteContext): Expr[Boolean] = { - import qctx.tasty._ - typing.typeChecks(code).toExpr -} diff --git a/library/src/scala/tasty/Reflection.scala b/library/src/scala/tasty/Reflection.scala index cb1cfc0613d4..bc7a7e1598f1 100644 --- a/library/src/scala/tasty/Reflection.scala +++ b/library/src/scala/tasty/Reflection.scala @@ -28,17 +28,4 @@ class Reflection(private[scala] val internal: CompilerInterface) def typeOf[T: scala.quoted.Type]: Type = implicitly[scala.quoted.Type[T]].unseal.tpe - // TODO move out of Reflection - object typing { - /** Whether the code type checks in the given context? - * - * @param code The code to be type checked - * - * @return false if the code has syntax error or type error in the given context, otherwise returns true. - * - * The code should be a sequence of expressions or statements that may appear in a block. - */ - def typeChecks(code: String)(implicit ctx: Context): Boolean = internal.typeChecks(code) - } - } diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index f4508de375dc..8babf722ee6f 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -186,17 +186,6 @@ trait CompilerInterface { def Settings_color(self: Settings): Boolean - // - // MISC - // - /** Whether the code type checks in the given context? - * - * @param code The code to be type checked - * - * The code should be a sequence of expressions or statements that may appear in a block. - */ - def typeChecks(code: String) given (ctx: Context): Boolean - // // TREES // diff --git a/tests/run-macros/reflect-inline/assert_1.scala b/tests/run-macros/reflect-inline/assert_1.scala index 28f80331011d..45d7a4d73186 100644 --- a/tests/run-macros/reflect-inline/assert_1.scala +++ b/tests/run-macros/reflect-inline/assert_1.scala @@ -8,10 +8,9 @@ object api { x.stripMargin.toExpr inline def typeChecks(inline x: String): Boolean = - ${ typeChecksImpl(x) } + ${ typeChecksImpl(scala.compiletime.testing.typeChecks(x)) } - private def typeChecksImpl(x: String) given (qctx: QuoteContext): Expr[Boolean] = { - import qctx.tasty._ - if (qctx.tasty.typing.typeChecks(x)) true.toExpr else false.toExpr + private def typeChecksImpl(b: Boolean) given (qctx: QuoteContext): Expr[Boolean] = { + if (b) true.toExpr else false.toExpr } } diff --git a/tests/run-macros/reflect-typeChecks/assert_1.scala b/tests/run-macros/reflect-typeChecks/assert_1.scala index bcc6e6b6d1f9..25f604ad8e4d 100644 --- a/tests/run-macros/reflect-typeChecks/assert_1.scala +++ b/tests/run-macros/reflect-typeChecks/assert_1.scala @@ -2,14 +2,10 @@ import scala.quoted._ object scalatest { - inline def assertCompile(inline code: String): Unit = ${ assertImpl(code, true) } - inline def assertNotCompile(inline code: String): Unit = ${ assertImpl(code, false) } - - def assertImpl(code: String, expect: Boolean) given (qctx: QuoteContext): Expr[Unit] = { - import qctx.tasty._ - - val actual = typing.typeChecks(code) + inline def assertCompile(inline code: String): Unit = ${ assertImpl(code, compiletime.testing.typeChecks(code), true) } + inline def assertNotCompile(inline code: String): Unit = ${ assertImpl(code, compiletime.testing.typeChecks(code), false) } + def assertImpl(code: String, actual: Boolean, expect: Boolean) given (qctx: QuoteContext): Expr[Unit] = { '{ assert(${expect.toExpr} == ${actual.toExpr}) } } } diff --git a/tests/run-with-compiler/scala-tests-typeChecks.scala b/tests/run/scala-tests-typeChecks.scala similarity index 100% rename from tests/run-with-compiler/scala-tests-typeChecks.scala rename to tests/run/scala-tests-typeChecks.scala From b81fb3c7a243e58513deabe72dce39ff67ace796 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 29 Aug 2019 15:39:51 +0200 Subject: [PATCH 2/7] Fix #7040: Add regression test --- tests/neg/i7040.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/neg/i7040.scala diff --git a/tests/neg/i7040.scala b/tests/neg/i7040.scala new file mode 100644 index 000000000000..46fae108070a --- /dev/null +++ b/tests/neg/i7040.scala @@ -0,0 +1,12 @@ +import scala.compiletime.testing.typeChecks +import scala.compiletime.error + +inline def assertDoesNotCompile(inline code: String): Unit = { + if (typeChecks(code)) { + error("Type-checking succeeded unexpectedly.") + } else { + } +} + +val test1 = assertDoesNotCompile("1") // error +val test2 = assertDoesNotCompile("1.noSuchMethod") From 48d88bc2fe90fa648112d9761088ec9699bf1398 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 29 Aug 2019 22:59:38 +0200 Subject: [PATCH 3/7] Update minitest --- community-build/community-projects/minitest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/minitest b/community-build/community-projects/minitest index 9d5fbb80dcb0..eaddd4dde4b8 160000 --- a/community-build/community-projects/minitest +++ b/community-build/community-projects/minitest @@ -1 +1 @@ -Subproject commit 9d5fbb80dcb095baac88deb4960d616870745cf9 +Subproject commit eaddd4dde4b8a84ddf62a20a781b939a135b3ea1 From 97af132157b94d044f8696c9d75cc664a89b697d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 2 Sep 2019 10:11:16 +0200 Subject: [PATCH 4/7] Avoid infinite recursion if typechecking non constant code string --- .../src/dotty/tools/dotc/typer/Inliner.scala | 30 +++++++++++-------- tests/neg/typeChecks.scala | 7 +++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 tests/neg/typeChecks.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 823e34e97b37..c1e3222892d6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -200,23 +200,29 @@ object Inliner { /** Expand call to scala.compiletime.testing.typeChecks */ def typeChecks(tree: Tree)(implicit ctx: Context): Tree = { assert(tree.symbol == defn.CompiletimeTesting_typeChecks) - def getCodeArgValue(t: Tree): String = t match { - case Literal(Constant(code: String)) => code + def getCodeArgValue(t: Tree): Option[String] = t match { + case Literal(Constant(code: String)) => Some(code) case Typed(t2, _) => getCodeArgValue(t2) case Inlined(_, Nil, t2) => getCodeArgValue(t2) case Block(Nil, t2) => getCodeArgValue(t2) + case _ => None } val Apply(_, codeArg :: Nil) = tree - val code = getCodeArgValue(codeArg.underlyingArgument) - val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer) - val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block() - val res = - if (ctx2.reporter.hasErrors) false - else { - ctx2.typer.typed(tree2)(ctx2) - !ctx2.reporter.hasErrors - } - Literal(Constant(res)) + getCodeArgValue(codeArg.underlyingArgument) match { + case Some(code) => + val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer) + val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block() + val res = + if (ctx2.reporter.hasErrors) false + else { + ctx2.typer.typed(tree2)(ctx2) + !ctx2.reporter.hasErrors + } + Literal(Constant(res)) + case _ => + EmptyTree + } + } } diff --git a/tests/neg/typeChecks.scala b/tests/neg/typeChecks.scala new file mode 100644 index 000000000000..10d38e04b98e --- /dev/null +++ b/tests/neg/typeChecks.scala @@ -0,0 +1,7 @@ + +import scala.compiletime.testing.typeChecks + +object Test { + + def f(s: String) = typeChecks(s) // error +} From d441773386a1730ad60c0f311f362dfe56e01f0a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 2 Sep 2019 10:40:23 +0200 Subject: [PATCH 5/7] Partially update Scalatest uses of typeChecks --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 485e832c980c..3e3eee5d8323 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 485e832c980c83143cdd7e9f059cdc21e15aafb9 +Subproject commit 3e3eee5d83236fb13b0aa5f8890022d39ea51489 From b4e07f9302009f4d38bf451d8961e235a65804b0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 2 Sep 2019 18:47:29 +0200 Subject: [PATCH 6/7] Disable some tests --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 3e3eee5d8323..c1c567fc59ff 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 3e3eee5d83236fb13b0aa5f8890022d39ea51489 +Subproject commit c1c567fc59ff347ac9b4b05c31b9196694fcb811 From fb814e1d4f60de58c87c168f902442aac3022d28 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 3 Sep 2019 14:23:08 +0200 Subject: [PATCH 7/7] Disable should compile tests --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index c1c567fc59ff..46da553e3d08 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit c1c567fc59ff347ac9b4b05c31b9196694fcb811 +Subproject commit 46da553e3d0800410b62114a244c427e5fc24c76