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 diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 485e832c980c..46da553e3d08 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 485e832c980c83143cdd7e9f059cdc21e15aafb9 +Subproject commit 46da553e3d0800410b62114a244c427e5fc24c76 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..c1e3222892d6 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,38 @@ 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): 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 + 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 + } + + } + + } } /** 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/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") 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 +} 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