Skip to content

Commit af4aa19

Browse files
Merge pull request #7510 from dotty-staging/typeChecksErros
Type-checker errors interface
2 parents a252641 + 0f6202a commit af4aa19

File tree

9 files changed

+132
-20
lines changed

9 files changed

+132
-20
lines changed

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
876876
def appliedToArgs(args: List[Tree])(implicit ctx: Context): Apply =
877877
Apply(tree, args)
878878

879+
/** An applied node that accepts only varargs as arguments */
880+
def appliedToVarargs(args: List[Tree], tpt: Tree)(given Context): Tree =
881+
appliedTo(repeated(args, tpt))
882+
879883
/** The current tree applied to given argument lists:
880884
* `tree (argss(0)) ... (argss(argss.length -1))`
881885
*/
@@ -893,6 +897,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
893897
def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree =
894898
appliedToTypeTrees(targs map (TypeTree(_)))
895899

900+
/** The current tree applied to given type argument: `tree[targ]` */
901+
def appliedToTypeTree(targ: Tree)(implicit ctx: Context): Tree =
902+
appliedToTypeTrees(targ :: Nil)
903+
896904
/** The current tree applied to given type argument list: `tree[targs(0), ..., targs(targs.length - 1)]` */
897905
def appliedToTypeTrees(targs: List[Tree])(implicit ctx: Context): Tree =
898906
if (targs.isEmpty) tree else TypeApply(tree, targs)
@@ -1365,5 +1373,24 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13651373
val targs = atp.argTypes
13661374
tpd.applyOverloaded(New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
13671375
}
1368-
}
13691376

1377+
/** Convert a list of trees to a vararg-compatible tree.
1378+
* Used to make arguments for methods that accept varargs.
1379+
*/
1380+
def repeated(trees: List[Tree], tpt: Tree)(given ctx: Context): Tree =
1381+
ctx.typeAssigner.arrayToRepeated(JavaSeqLiteral(trees, tpt))
1382+
1383+
/** Create a tree representing a list containing all
1384+
* the elements of the argument list. A "list of tree to
1385+
* tree of list" conversion.
1386+
*
1387+
* @param trees the elements the list represented by
1388+
* the resulting tree should contain.
1389+
* @param tpe the type of the elements of the resulting list.
1390+
*
1391+
*/
1392+
def mkList(trees: List[Tree], tpe: Tree)(given Context): Tree =
1393+
ref(defn.ListModule).select(nme.apply)
1394+
.appliedToTypeTree(tpe)
1395+
.appliedToVarargs(trees, tpe)
1396+
}

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

Lines changed: 9 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
@@ -393,6 +400,7 @@ class Definitions {
393400
methodNames.map(getWrapVarargsArrayModule.requiredMethod(_))
394401
})
395402

403+
@tu lazy val ListModule: Symbol = ctx.requiredModule("scala.collection.immutable.List")
396404
@tu lazy val NilModule: Symbol = ctx.requiredModule("scala.collection.immutable.Nil")
397405

398406
@tu lazy val SingletonClass: ClassSymbol =

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

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ object Inliner {
6767
* and body that replace it.
6868
*/
6969
def inlineCall(tree: Tree)(implicit ctx: Context): Tree = {
70-
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
70+
if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.owner.companionModule == defn.CompiletimeTestingPackageObject
71+
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
72+
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(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
@@ -194,10 +196,12 @@ object Inliner {
194196
}
195197

196198
object Intrinsics {
199+
import dotty.tools.dotc.reporting.diagnostic.messages.Error
200+
private enum ErrorKind
201+
case Parser, Typer
197202

198-
/** Expand call to scala.compiletime.testing.typeChecks */
199-
def typeChecks(tree: Tree)(implicit ctx: Context): Tree = {
200-
assert(tree.symbol == defn.CompiletimeTesting_typeChecks)
203+
private def compileForErrors(tree: Tree, stopAfterParser: Boolean)(given ctx: Context): List[(ErrorKind, Error)] =
204+
assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors)
201205
def stripTyped(t: Tree): Tree = t match {
202206
case Typed(t2, _) => stripTyped(t2)
203207
case _ => t
@@ -206,20 +210,49 @@ object Inliner {
206210
val Apply(_, codeArg :: Nil) = tree
207211
ConstFold(stripTyped(codeArg.underlyingArgument)).tpe.widenTermRefExpr match {
208212
case ConstantType(Constant(code: String)) =>
209-
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer)
210-
val tree2 = new Parser(SourceFile.virtual("tasty-reflect", code))(ctx2).block()
211-
val res =
212-
if (ctx2.reporter.hasErrors) false
213-
else {
214-
ctx2.typer.typed(tree2)(ctx2)
215-
!ctx2.reporter.hasErrors
216-
}
217-
Literal(Constant(res))
213+
val source2 = SourceFile.virtual("tasty-reflect", code)
214+
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer).setSource(source2)
215+
val tree2 = new Parser(source2)(ctx2).block()
216+
val res = collection.mutable.ListBuffer.empty[(ErrorKind, Error)]
217+
218+
val parseErrors = ctx2.reporter.allErrors.toList
219+
res ++= parseErrors.map(e => ErrorKind.Parser -> e)
220+
if !stopAfterParser || res.isEmpty
221+
ctx2.typer.typed(tree2)(ctx2)
222+
val typerErrors = ctx2.reporter.allErrors.filterNot(parseErrors.contains)
223+
res ++= typerErrors.map(e => ErrorKind.Typer -> e)
224+
res.toList
218225
case t =>
219226
assert(ctx.reporter.hasErrors) // at least: argument to inline parameter must be a known value
220-
EmptyTree
227+
Nil
221228
}
222-
}
229+
230+
private def packError(kind: ErrorKind, error: Error)(given Context): Tree =
231+
def lit(x: Any) = Literal(Constant(x))
232+
val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply)
233+
val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser)
234+
val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer)
235+
236+
constructor.appliedTo(
237+
lit(error.message),
238+
lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse),
239+
lit(error.pos.column),
240+
if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind)
241+
242+
private def packErrors(errors: List[(ErrorKind, Error)])(given Context): Tree =
243+
val individualErrors: List[Tree] = errors.map(packError)
244+
val errorTpt = ref(defn.CompiletimeTesting_ErrorClass)
245+
mkList(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)
223256
}
224257
}
225258

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ trait QuotesAndSplices {
368368
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
369369
val quotedPattern =
370370
if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx)
371-
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTrees(shape :: Nil)
371+
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTree(shape)
372372
UnApply(
373373
fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
374374
implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package scala.compiletime.testing
2+
3+
/** Represents a compile-time error.
4+
*
5+
* @see scala.compiletime.testing.typeCheckErrors
6+
*
7+
* IMPORTANT: No stability guarantees are provided on the format of these
8+
* errors. This means the format and the API may change from
9+
* version to version. This API is to be used for testing purposes
10+
* only.
11+
*/
12+
final case class Error(message: String, lineContent: String, column: Int, kind: ErrorKind)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scala.compiletime.testing
2+
3+
/** An error can be either a parse-time or a typecheck-time */
4+
sealed trait ErrorKind // TODO make this enum, so far not doable because ScalaJS compilation fails on it
5+
object ErrorKind
6+
case object Parser extends ErrorKind
7+
case object Typer extends ErrorKind

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package scala.compiletime
22

3-
import scala.quoted._
4-
53
package object testing {
64

75
/** Whether the code type checks in the current context?
@@ -15,4 +13,19 @@ package object testing {
1513
inline def typeChecks(inline code: String): Boolean =
1614
error("`typeChecks` was not checked by the compiler")
1715

16+
/** Whether the code type checks in the current context? If not,
17+
* returns a list of errors encountered on compilation.
18+
* IMPORTANT: No stability guarantees are provided on the format of these
19+
* errors. This means the format and the API may change from
20+
* version to version. This API is to be used for testing purposes
21+
* only.
22+
*
23+
* @param code The code to be type checked
24+
*
25+
* @return a list of errors encountered during parsing and typechecking.
26+
*
27+
* The code should be a sequence of expressions or statements that may appear in a block.
28+
*/
29+
inline def typeCheckErrors(inline code: String): List[Error] =
30+
error("`typeCheckErrors` was not checked by the compiler")
1831
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Error(Not found: abc, 1 + abc,16,Typer)
2+
Error(Not found: abc, 1 + abc,16,Typer)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@main def Test =
2+
scala.compiletime.testing.typeCheckErrors("""
3+
4+
5+
6+
1 + abc
7+
8+
9+
10+
""").foreach(println)

0 commit comments

Comments
 (0)