Skip to content

Type-checker errors interface #7510

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

Merged
merged 4 commits into from
Nov 18, 2019
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
29 changes: 28 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def appliedToArgs(args: List[Tree])(implicit ctx: Context): Apply =
Apply(tree, args)

/** An applied node that accepts only varargs as arguments */
def appliedToVarargs(args: List[Tree], tpt: Tree)(given Context): Tree =
appliedTo(repeated(args, tpt))

/** The current tree applied to given argument lists:
* `tree (argss(0)) ... (argss(argss.length -1))`
*/
Expand All @@ -885,6 +889,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def appliedToTypes(targs: List[Type])(implicit ctx: Context): Tree =
appliedToTypeTrees(targs map (TypeTree(_)))

/** The current tree applied to given type argument: `tree[targ]` */
def appliedToTypeTree(targ: Tree)(implicit ctx: Context): Tree =
appliedToTypeTrees(targ :: Nil)

/** The current tree applied to given type argument list: `tree[targs(0), ..., targs(targs.length - 1)]` */
def appliedToTypeTrees(targs: List[Tree])(implicit ctx: Context): Tree =
if (targs.isEmpty) tree else TypeApply(tree, targs)
Expand Down Expand Up @@ -1357,5 +1365,24 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
val targs = atp.argTypes
tpd.applyOverloaded(New(atp.typeConstructor), nme.CONSTRUCTOR, args, targs, atp)
}
}

/** Convert a list of trees to a vararg-compatible tree.
* Used to make arguments for methods that accept varargs.
*/
def repeated(trees: List[Tree], tpt: Tree)(given ctx: Context): Tree =
ctx.typeAssigner.arrayToRepeated(JavaSeqLiteral(trees, tpt))

/** Create a tree representing a list containing all
* the elements of the argument list. A "list of tree to
* tree of list" conversion.
*
* @param trees the elements the list represented by
* the resulting tree should contain.
* @param tpe the type of the elements of the resulting list.
*
*/
def mkList(trees: List[Tree], tpe: Tree)(given Context): Tree =
ref(defn.ListModule).select(nme.apply)
.appliedToTypeTree(tpe)
.appliedToVarargs(trees, tpe)
}
10 changes: 9 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,14 @@ class Definitions {
@tu lazy val Compiletime_code : Symbol = CompiletimePackageObject.requiredMethod("code")
@tu lazy val Compiletime_summonFrom : Symbol = CompiletimePackageObject.requiredMethod("summonFrom")
@tu lazy val CompiletimeTestingPackageObject: Symbol = ctx.requiredModule("scala.compiletime.testing.package")
@tu lazy val CompiletimeTesting_typeChecks : Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks")
@tu lazy val CompiletimeTesting_typeChecks: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeChecks")
@tu lazy val CompiletimeTesting_typeCheckErrors: Symbol = CompiletimeTestingPackageObject.requiredMethod("typeCheckErrors")
@tu lazy val CompiletimeTesting_ErrorClass: ClassSymbol = ctx.requiredClass("scala.compiletime.testing.Error")
@tu lazy val CompiletimeTesting_Error: Symbol = ctx.requiredModule("scala.compiletime.testing.Error")
@tu lazy val CompiletimeTesting_Error_apply = CompiletimeTesting_Error.requiredMethod(nme.apply)
@tu lazy val CompiletimeTesting_ErrorKind: Symbol = ctx.requiredModule("scala.compiletime.testing.ErrorKind")
@tu lazy val CompiletimeTesting_ErrorKind_Parser: Symbol = CompiletimeTesting_ErrorKind.requiredMethod("Parser")
@tu lazy val CompiletimeTesting_ErrorKind_Typer: Symbol = CompiletimeTesting_ErrorKind.requiredMethod("Typer")

/** 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 Expand Up @@ -389,6 +396,7 @@ class Definitions {
methodNames.map(getWrapVarargsArrayModule.requiredMethod(_))
})

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

@tu lazy val SingletonClass: ClassSymbol =
Expand Down
63 changes: 48 additions & 15 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ 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)
if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.owner.companionModule == defn.CompiletimeTestingPackageObject
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(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 @@ -193,10 +195,12 @@ object Inliner {
}

object Intrinsics {
import dotty.tools.dotc.reporting.diagnostic.messages.Error
private enum ErrorKind
case Parser, Typer

/** Expand call to scala.compiletime.testing.typeChecks */
def typeChecks(tree: Tree)(implicit ctx: Context): Tree = {
assert(tree.symbol == defn.CompiletimeTesting_typeChecks)
private def compileForErrors(tree: Tree, stopAfterParser: Boolean)(given ctx: Context): List[(ErrorKind, Error)] =
assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors)
def stripTyped(t: Tree): Tree = t match {
case Typed(t2, _) => stripTyped(t2)
case _ => t
Expand All @@ -205,20 +209,49 @@ object Inliner {
val Apply(_, codeArg :: Nil) = tree
ConstFold(stripTyped(codeArg.underlyingArgument)).tpe.widenTermRefExpr match {
case ConstantType(Constant(code: String)) =>
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))
val source2 = SourceFile.virtual("tasty-reflect", code)
val ctx2 = ctx.fresh.setNewTyperState().setTyper(new Typer).setSource(source2)
val tree2 = new Parser(source2)(ctx2).block()
val res = collection.mutable.ListBuffer.empty[(ErrorKind, Error)]

val parseErrors = ctx2.reporter.allErrors.toList
res ++= parseErrors.map(e => ErrorKind.Parser -> e)
if !stopAfterParser || res.isEmpty
ctx2.typer.typed(tree2)(ctx2)
val typerErrors = ctx2.reporter.allErrors.filterNot(parseErrors.contains)
res ++= typerErrors.map(e => ErrorKind.Typer -> e)
res.toList
case t =>
assert(ctx.reporter.hasErrors) // at least: argument to inline parameter must be a known value
EmptyTree
Nil
}
}

private def packError(kind: ErrorKind, error: Error)(given Context): Tree =
def lit(x: Any) = Literal(Constant(x))
val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply)
val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser)
val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer)

constructor.appliedTo(
lit(error.message),
lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse),
lit(error.pos.column),
if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind)

private def packErrors(errors: List[(ErrorKind, Error)])(given Context): Tree =
val individualErrors: List[Tree] = errors.map(packError)
val errorTpt = ref(defn.CompiletimeTesting_ErrorClass)
mkList(individualErrors, errorTpt)

/** Expand call to scala.compiletime.testing.typeChecks */
def typeChecks(tree: Tree)(given Context): Tree =
val errors = compileForErrors(tree, true)
Literal(Constant(errors.isEmpty))

/** Expand call to scala.compiletime.testing.typeCheckErrors */
def typeCheckErrors(tree: Tree)(given Context): Tree =
val errors = compileForErrors(tree, false)
packErrors(errors)
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ trait QuotesAndSplices {
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
val quotedPattern =
if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx)
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTrees(shape :: Nil)
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTree(shape)
UnApply(
fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil,
Expand Down
12 changes: 12 additions & 0 deletions library/src/scala/compiletime/testing/Error.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package scala.compiletime.testing

/** Represents a compile-time error.
*
* @see scala.compiletime.testing.typeCheckErrors
*
* IMPORTANT: No stability guarantees are provided on the format of these
* errors. This means the format and the API may change from
* version to version. This API is to be used for testing purposes
* only.
*/
final case class Error(message: String, lineContent: String, column: Int, kind: ErrorKind)
7 changes: 7 additions & 0 deletions library/src/scala/compiletime/testing/ErrorKind.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package scala.compiletime.testing

/** An error can be either a parse-time or a typecheck-time */
sealed trait ErrorKind // TODO make this enum, so far not doable because ScalaJS compilation fails on it
object ErrorKind
case object Parser extends ErrorKind
case object Typer extends ErrorKind
17 changes: 15 additions & 2 deletions library/src/scala/compiletime/testing/package.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package scala.compiletime

import scala.quoted._

package object testing {

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

/** Whether the code type checks in the current context? If not,
* returns a list of errors encountered on compilation.
* IMPORTANT: No stability guarantees are provided on the format of these
* errors. This means the format and the API may change from
* version to version. This API is to be used for testing purposes
* only.
*
* @param code The code to be type checked
*
* @return a list of errors encountered during parsing and typechecking.
*
* The code should be a sequence of expressions or statements that may appear in a block.
*/
inline def typeCheckErrors(inline code: String): List[Error] =
error("`typeCheckErrors` was not checked by the compiler")
}
2 changes: 2 additions & 0 deletions tests/run-bootstrapped/typeCheckErrors.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Error(Not found: abc, 1 + abc,16,Typer)
Error(Not found: abc, 1 + abc,16,Typer)
10 changes: 10 additions & 0 deletions tests/run-bootstrapped/typeCheckErrors.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@main def Test =
scala.compiletime.testing.typeCheckErrors("""



1 + abc



""").foreach(println)