Skip to content

Commit b151414

Browse files
authored
Merge pull request #7129 from dotty-staging/intrisify-compiletime-testing-typechecks
Intrinsify scala.compiletime.testing.typeChecks
2 parents 85962b1 + fb814e1 commit b151414

File tree

14 files changed

+81
-68
lines changed

14 files changed

+81
-68
lines changed

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ class Definitions {
220220
@tu lazy val Compiletime_constValue : Symbol = CompiletimePackageObject.requiredMethod("constValue")
221221
@tu lazy val Compiletime_constValueOpt: Symbol = CompiletimePackageObject.requiredMethod("constValueOpt")
222222
@tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code")
223+
@tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package")
224+
@tu lazy val CompiletimeTesting_typeChecks : Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks")
223225

224226
/** The `scalaShadowing` package is used to safely modify classes and
225227
* objects in scala so that they can be used from dotty. They will

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import dotty.tools.dotc.core.Symbols._
1212
import dotty.tools.dotc.core.Decorators._
1313
import dotty.tools.dotc.core.Types.SingletonType
1414
import dotty.tools.dotc.tastyreflect.FromSymbol.{definitionFromSym, packageDefFromSym}
15-
import dotty.tools.dotc.parsing.Parsers.Parser
1615
import dotty.tools.dotc.typer.Implicits.{AmbiguousImplicits, DivergingImplicit, NoMatchingImplicits, SearchFailure, SearchFailureType}
1716
import dotty.tools.dotc.util.{SourceFile, SourcePosition, Spans}
1817

@@ -82,26 +81,6 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
8281

8382
def Settings_color(self: Settings): Boolean = self.color.value(rootContext) == "always"
8483

85-
//
86-
// MISC
87-
//
88-
/** Whether the code type checks in the given context?
89-
*
90-
* @param code The code to be type checked
91-
*
92-
* The code should be a sequence of expressions or statements that may appear in a block.
93-
*/
94-
def typeChecks(code: String) given (ctx: Context): Boolean = {
95-
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer)
96-
val tree = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block()
97-
98-
if (ctx2.reporter.hasErrors) false
99-
else {
100-
ctx2.typer.typed(tree)(ctx2)
101-
!ctx2.reporter.hasErrors
102-
}
103-
}
104-
10584
//
10685
// TREES
10786
//

compiler/src/dotty/tools/dotc/typer/Inliner.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import config.Printers.inlining
2323
import ErrorReporting.errorTree
2424
import dotty.tools.dotc.tastyreflect.ReflectionImpl
2525
import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile, SourcePosition}
26+
import dotty.tools.dotc.parsing.Parsers.Parser
2627

2728
import collection.mutable
2829
import reporting.trace
@@ -68,6 +69,7 @@ object Inliner {
6869
* and body that replace it.
6970
*/
7071
def inlineCall(tree: Tree)(implicit ctx: Context): Tree = {
72+
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
7173

7274
/** Set the position of all trees logically contained in the expansion of
7375
* inlined call `call` to the position of `call`. This transform is necessary
@@ -192,6 +194,38 @@ object Inliner {
192194
if (callSym.is(Macro)) ref(callSym.topLevelClass.owner).select(callSym.topLevelClass.name).withSpan(pos.span)
193195
else Ident(callSym.topLevelClass.typeRef).withSpan(pos.span)
194196
}
197+
198+
object Intrinsics {
199+
200+
/** Expand call to scala.compiletime.testing.typeChecks */
201+
def typeChecks(tree: Tree)(implicit ctx: Context): Tree = {
202+
assert(tree.symbol == defn.CompiletimeTesting_typeChecks)
203+
def getCodeArgValue(t: Tree): Option[String] = t match {
204+
case Literal(Constant(code: String)) => Some(code)
205+
case Typed(t2, _) => getCodeArgValue(t2)
206+
case Inlined(_, Nil, t2) => getCodeArgValue(t2)
207+
case Block(Nil, t2) => getCodeArgValue(t2)
208+
case _ => None
209+
}
210+
val Apply(_, codeArg :: Nil) = tree
211+
getCodeArgValue(codeArg.underlyingArgument) match {
212+
case Some(code) =>
213+
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer)
214+
val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block()
215+
val res =
216+
if (ctx2.reporter.hasErrors) false
217+
else {
218+
ctx2.typer.typed(tree2)(ctx2)
219+
!ctx2.reporter.hasErrors
220+
}
221+
Literal(Constant(res))
222+
case _ =>
223+
EmptyTree
224+
}
225+
226+
}
227+
228+
}
195229
}
196230

197231
/** Produces an inlined version of `call` via its `inlined` method.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package scala.compiletime
2+
3+
import scala.quoted._
4+
5+
package object testing {
6+
7+
/** Whether the code type checks in the current context?
8+
*
9+
* @param code The code to be type checked
10+
*
11+
* @return false if the code has syntax error or type error in the current context, otherwise returns true.
12+
*
13+
* The code should be a sequence of expressions or statements that may appear in a block.
14+
*/
15+
inline def typeChecks(inline code: String): Boolean =
16+
error("`typeChecks` was not checked by the compiler")
17+
18+
}

library/src-bootstrapped/scala/compiletime/testing/typeChecks.scala

Lines changed: 0 additions & 10 deletions
This file was deleted.

library/src/scala/tasty/Reflection.scala

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,4 @@ class Reflection(private[scala] val internal: CompilerInterface)
2828
def typeOf[T: scala.quoted.Type]: Type =
2929
implicitly[scala.quoted.Type[T]].unseal.tpe
3030

31-
// TODO move out of Reflection
32-
object typing {
33-
/** Whether the code type checks in the given context?
34-
*
35-
* @param code The code to be type checked
36-
*
37-
* @return false if the code has syntax error or type error in the given context, otherwise returns true.
38-
*
39-
* The code should be a sequence of expressions or statements that may appear in a block.
40-
*/
41-
def typeChecks(code: String)(implicit ctx: Context): Boolean = internal.typeChecks(code)
42-
}
43-
4431
}

library/src/scala/tasty/reflect/CompilerInterface.scala

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,6 @@ trait CompilerInterface {
186186

187187
def Settings_color(self: Settings): Boolean
188188

189-
//
190-
// MISC
191-
//
192-
/** Whether the code type checks in the given context?
193-
*
194-
* @param code The code to be type checked
195-
*
196-
* The code should be a sequence of expressions or statements that may appear in a block.
197-
*/
198-
def typeChecks(code: String) given (ctx: Context): Boolean
199-
200189
//
201190
// TREES
202191
//

tests/neg/i7040.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.compiletime.testing.typeChecks
2+
import scala.compiletime.error
3+
4+
inline def assertDoesNotCompile(inline code: String): Unit = {
5+
if (typeChecks(code)) {
6+
error("Type-checking succeeded unexpectedly.")
7+
} else {
8+
}
9+
}
10+
11+
val test1 = assertDoesNotCompile("1") // error
12+
val test2 = assertDoesNotCompile("1.noSuchMethod")

tests/neg/typeChecks.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
import scala.compiletime.testing.typeChecks
3+
4+
object Test {
5+
6+
def f(s: String) = typeChecks(s) // error
7+
}

tests/run-macros/reflect-inline/assert_1.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ object api {
88
x.stripMargin.toExpr
99

1010
inline def typeChecks(inline x: String): Boolean =
11-
${ typeChecksImpl(x) }
11+
${ typeChecksImpl(scala.compiletime.testing.typeChecks(x)) }
1212

13-
private def typeChecksImpl(x: String) given (qctx: QuoteContext): Expr[Boolean] = {
14-
import qctx.tasty._
15-
if (qctx.tasty.typing.typeChecks(x)) true.toExpr else false.toExpr
13+
private def typeChecksImpl(b: Boolean) given (qctx: QuoteContext): Expr[Boolean] = {
14+
if (b) true.toExpr else false.toExpr
1615
}
1716
}

tests/run-macros/reflect-typeChecks/assert_1.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ import scala.quoted._
22

33
object scalatest {
44

5-
inline def assertCompile(inline code: String): Unit = ${ assertImpl(code, true) }
6-
inline def assertNotCompile(inline code: String): Unit = ${ assertImpl(code, false) }
7-
8-
def assertImpl(code: String, expect: Boolean) given (qctx: QuoteContext): Expr[Unit] = {
9-
import qctx.tasty._
10-
11-
val actual = typing.typeChecks(code)
5+
inline def assertCompile(inline code: String): Unit = ${ assertImpl(code, compiletime.testing.typeChecks(code), true) }
6+
inline def assertNotCompile(inline code: String): Unit = ${ assertImpl(code, compiletime.testing.typeChecks(code), false) }
127

8+
def assertImpl(code: String, actual: Boolean, expect: Boolean) given (qctx: QuoteContext): Expr[Unit] = {
139
'{ assert(${expect.toExpr} == ${actual.toExpr}) }
1410
}
1511
}

0 commit comments

Comments
 (0)