Skip to content

Commit 70e6d99

Browse files
Internals for the typechecker for testing
1 parent 5422ba6 commit 70e6d99

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
868868
def appliedToArgs(args: List[Tree])(implicit ctx: Context): Apply =
869869
Apply(tree, args)
870870

871+
/** An applied node that accepts only varargs as arguments */
872+
def appliedToVarargs(args: List[Tree], tpt: Tree)(given Context): Tree =
873+
appliedTo(repeated(args, tpt))
874+
871875
/** The current tree applied to given argument lists:
872876
* `tree (argss(0)) ... (argss(argss.length -1))`
873877
*/
@@ -881,6 +885,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
881885
def appliedToType(targ: Type)(implicit ctx: Context): Tree =
882886
appliedToTypes(targ :: Nil)
883887

888+
/** The current tree applied to given type argument: `tree[targ]` */
889+
def appliedToTypeTree(targ: Tree)(implicit ctx: Context): Tree =
890+
appliedToTypeTrees(targ :: Nil)
891+
884892
/** The current tree applied to given type arguments: `tree[targ0, ..., targN]` */
885893
def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree =
886894
appliedToTypeTrees(targs map (TypeTree(_)))
@@ -1357,5 +1365,11 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13571365
val targs = atp.argTypes
13581366
tpd.applyOverloaded(New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
13591367
}
1368+
1369+
/** Convert a list of trees to a vararg-compatible tree.
1370+
* Used to make arguments for methods that accept varargs.
1371+
*/
1372+
def repeated(trees: List[Tree], tpt: Tree)(given ctx: Context): Tree =
1373+
ctx.typeAssigner.arrayToRepeated(JavaSeqLiteral(trees, tpt))
13601374
}
13611375

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,14 @@ class Definitions {
225225
@tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code")
226226
@tu lazy val Compiletime_summonFrom : Symbol = CompiletimePackageObject.requiredMethod("summonFrom")
227227
@tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package")
228-
@tu lazy val CompiletimeTesting_typeChecks : Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks")
228+
@tu lazy val CompiletimeTesting_typeChecks: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks")
229+
@tu lazy val CompiletimeTesting_typeCheckErrors: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeCheckErrors")
230+
@tu lazy val CompiletimeTesting_ErrorClass: ClassSymbol = ctx.requiredClass("scala.compiletime.testing.Error")
231+
@tu lazy val CompiletimeTesting_Error: Symbol = ctx.requiredModule("scala.compiletime.testing.Error")
232+
@tu lazy val CompiletimeTesting_Error_apply = CompiletimeTesting_Error.requiredMethod(nme.apply)
233+
@tu lazy val CompiletimeTesting_ErrorKind: Symbol = ctx.requiredModule("scala.compiletime.testing.ErrorKind")
234+
@tu lazy val CompiletimeTesting_ErrorKind_Parser: Symbol = CompiletimeTesting_ErrorKind.requiredMethod("Parser")
235+
@tu lazy val CompiletimeTesting_ErrorKind_Typer: Symbol = CompiletimeTesting_ErrorKind.requiredMethod("Typer")
229236

230237
/** The `scalaShadowing` package is used to safely modify classes and
231238
* objects in scala so that they can be used from dotty. They will
@@ -389,6 +396,8 @@ class Definitions {
389396
methodNames.map(getWrapVarargsArrayModule.requiredMethod(_))
390397
})
391398

399+
@tu lazy val ListClass: ClassSymbol = ctx.requiredClass("scala.collection.immutable.List")
400+
@tu lazy val ListModule: Symbol = ListClass.companionModule
392401
@tu lazy val NilModule: Symbol = ctx.requiredModule("scala.collection.immutable.Nil")
393402

394403
@tu lazy val SingletonClass: ClassSymbol =

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ object Inliner {
6767
*/
6868
def inlineCall(tree: Tree)(implicit ctx: Context): Tree = {
6969
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
70+
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
7071

7172
/** Set the position of all trees logically contained in the expansion of
7273
* inlined call `call` to the position of `call`. This transform is necessary
@@ -193,10 +194,12 @@ object Inliner {
193194
}
194195

195196
object Intrinsics {
197+
import dotty.tools.dotc.reporting.diagnostic.messages.Error
198+
private enum ErrorKind
199+
case Parser, Typer
196200

197-
/** Expand call to scala.compiletime.testing.typeChecks */
198-
def typeChecks(tree: Tree)(implicit ctx: Context): Tree = {
199-
assert(tree.symbol == defn.CompiletimeTesting_typeChecks)
201+
private def compileForErrors(tree: Tree, stopAfterParser: Boolean)(given ctx: Context): List[(ErrorKind, Error)] =
202+
assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors)
200203
def stripTyped(t: Tree): Tree = t match {
201204
case Typed(t2, _) => stripTyped(t2)
202205
case _ => t
@@ -205,20 +208,51 @@ object Inliner {
205208
val Apply(_, codeArg :: Nil) = tree
206209
ConstFold(stripTyped(codeArg.underlyingArgument)).tpe.widenTermRefExpr match {
207210
case ConstantType(Constant(code: String)) =>
208-
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer)
209-
val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block()
210-
val res =
211-
if (ctx2.reporter.hasErrors) false
212-
else {
213-
ctx2.typer.typed(tree2)(ctx2)
214-
!ctx2.reporter.hasErrors
215-
}
216-
Literal(Constant(res))
211+
val source2 = SourceFile.virtual("tasty-reflect", code)
212+
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer).setSource(source2)
213+
val tree2 = new Parser(source2)(ctx2).block()
214+
val res = collection.mutable.ListBuffer.empty[(ErrorKind, Error)]
215+
216+
val parseErrors = ctx2.reporter.allErrors.toList
217+
res ++= parseErrors.map(e => ErrorKind.Parser -> e)
218+
if !stopAfterParser || res.isEmpty
219+
ctx2.typer.typed(tree2)(ctx2)
220+
val typerErrors = ctx2.reporter.allErrors.filterNot(parseErrors.contains)
221+
res ++= typerErrors.map(e => ErrorKind.Typer -> e)
222+
res.toList
217223
case t =>
218224
assert(ctx.reporter.hasErrors) // at least: argument to inline parameter must be a known value
219-
EmptyTree
225+
Nil
220226
}
221-
}
227+
228+
private def packError(kind: ErrorKind, error: Error)(given Context): Tree =
229+
def lit(x: Any) = Literal(Constant(x))
230+
val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply)
231+
val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser)
232+
val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer)
233+
234+
constructor.appliedTo(
235+
lit(error.message),
236+
lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse),
237+
lit(error.pos.column),
238+
if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind)
239+
240+
private def packErrors(errors: List[(ErrorKind, Error)])(given Context): Tree =
241+
val individualErrors: List[Tree] = errors.map(packError)
242+
val errorTpt = ref(defn.CompiletimeTesting_ErrorClass)
243+
ref(defn.ListModule).select(nme.apply)
244+
.appliedToTypeTree(errorTpt)
245+
.appliedToVarargs(individualErrors, errorTpt)
246+
247+
/** Expand call to scala.compiletime.testing.typeChecks */
248+
def typeChecks(tree: Tree)(given Context): Tree =
249+
val errors = compileForErrors(tree, true)
250+
Literal(Constant(errors.isEmpty))
251+
252+
/** Expand call to scala.compiletime.testing.typeCheckErrors */
253+
def typeCheckErrors(tree: Tree)(given Context): Tree =
254+
val errors = compileForErrors(tree, false)
255+
packErrors(errors)
222256
}
223257
}
224258

library/src/scala/compiletime/testing/package.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import scala.quoted._
44

55
package object testing {
66

7+
/** Represents a compile-time error */
78
case class Error(message: String, lineContent: String, column: Int, kind: ErrorKind)
89

910
/** An error can be either a parse-time or a typecheck-time */
1011
sealed trait ErrorKind // TODO make this enum, so far not doable because ScalaJS compilation fails on it
11-
1212
object ErrorKind
1313
case object Parser extends ErrorKind
1414
case object Typer extends ErrorKind
@@ -24,4 +24,14 @@ package object testing {
2424
inline def typeChecks(inline code: String): Boolean =
2525
error("`typeChecks` was not checked by the compiler")
2626

27+
/** Whether the code type checks in the current context?
28+
*
29+
* @param code The code to be type checked
30+
*
31+
* @return a list of errors encountered during parsing and typechecking.
32+
*
33+
* The code should be a sequence of expressions or statements that may appear in a block.
34+
*/
35+
inline def typeCheckErrors(inline code: String): List[Error] =
36+
error("`typeCheckErrors` was not checked by the compiler")
2737
}

0 commit comments

Comments
 (0)