Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intrinsify scala.compiletime.testing.typeChecks #7129

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down Expand Up @@ -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
//
Expand Down
34 changes: 34 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions library/src-bootstrapped/scala/compiletime/testing/package.scala
Original file line number Diff line number Diff line change
@@ -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")

}

This file was deleted.

13 changes: 0 additions & 13 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

}
11 changes: 0 additions & 11 deletions library/src/scala/tasty/reflect/CompilerInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
12 changes: 12 additions & 0 deletions tests/neg/i7040.scala
Original file line number Diff line number Diff line change
@@ -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")
7 changes: 7 additions & 0 deletions tests/neg/typeChecks.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

import scala.compiletime.testing.typeChecks

object Test {

def f(s: String) = typeChecks(s) // error
}
7 changes: 3 additions & 4 deletions tests/run-macros/reflect-inline/assert_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
10 changes: 3 additions & 7 deletions tests/run-macros/reflect-typeChecks/assert_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}) }
}
}