From 7187197f3d338ad6523bb56b16f1f3168e5bd867 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 17 Aug 2018 18:14:19 +0200 Subject: [PATCH 001/121] Fix #1023: Test that TypeTrees are not in an unexpected tree --- .../tools/dotc/transform/TreeChecker.scala | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index bd3f4f44984b..48db34a98393 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -306,6 +306,7 @@ class TreeChecker extends Phase with SymTransformer { sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) override def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = { + checkTreeNode(tree) val tpdTree = super.typed(tree, pt) Typer.assertPositioned(tree) if (ctx.erasedTypes) @@ -646,4 +647,103 @@ object TreeChecker { tp } }.apply(tp0) + + private val PrivateErased = allOf(Private, Erased) + + /** Check that the tree only contains legal children trees */ + def checkTreeNode(tree: untpd.Tree)(implicit ctx: Context): Unit = { + def assertNotTypeTree(t: untpd.Tree): Unit = assert(!t.isInstanceOf[untpd.TypeTree], tree) + + // TODO add many more sanity checks + tree match { +// case Ident(name) => + case Select(qualifier, name) => + // FIXME this assertion fails only in tests/run/scala2trait-lazyval.scala + // assertNotTypeTree(qual) + // case This(qual) => +// +// case Super(qual, mix) => + case Apply(fun, args) => + assertNotTypeTree(fun) + for (arg <- args) { + assertNotTypeTree(arg) + } + case TypeApply(fun, args) => + assertNotTypeTree(fun) + // case Literal(const) => +// +// case New(tpt) => + case Typed(expr, tpt) => + assertNotTypeTree(expr) + // case NamedArg(name, arg) => + case Assign(lhs, rhs) => + assertNotTypeTree(lhs) + assertNotTypeTree(rhs) + case Block(stats, expr) => + for (stat <- stats) { + assertNotTypeTree(stat) + } + assertNotTypeTree(expr) + case If(cond, thenp, elsep) => + assertNotTypeTree(cond) + assertNotTypeTree(thenp) + assertNotTypeTree(elsep) + // case Closure(env, meth, tpt) => + case Match(selector, cases) => + assertNotTypeTree(selector) + case CaseDef(pat, guard, body) => + assertNotTypeTree(guard) + assertNotTypeTree(body) + case Return(expr, from) => + assertNotTypeTree(expr) + case Try(block, handler, finalizer) => + assertNotTypeTree(block) + assertNotTypeTree(finalizer) + case SeqLiteral(elems, elemtpt) => + for (elem <- elems) { + assertNotTypeTree(elem) + } + case Inlined(call, bindings, expansion) => + assertNotTypeTree(call) + // case TypeTree() => +// +// case SingletonTypeTree(ref) => +// +// case AndTypeTree(left, right) => +// +// case OrTypeTree(left, right) => +// +// case RefinedTypeTree(tpt, refinements) => +// +// case AppliedTypeTree(tpt, args) => +// +// case LambdaTypeTree(tparams, body) => +// +// case ByNameTypeTree(result) => +// +// case TypeBoundsTree(lo, hi) => +// +// case Bind(name, body) => +// +// case Alternative(trees) => +// +// case UnApply(fun, implicits, patterns) => + case tree @ ValDef(name, tpt, _) => + assertNotTypeTree(tree.rhs) + case tree @ DefDef(name, tparams, vparamss, tpt, _) => + assertNotTypeTree(tree.rhs) + // case TypeDef(name, rhs) => +// +// case tree @ Template(constr, parents, self, _) => +// +// case Import(expr, selectors) => +// +// case PackageDef(pid, stats) => +// +// case Annotated(arg, annot) => +// +// case Thicket(ts) => + case _ => + } + } } From 543b1338a3d2500afca75724d98f000bd5a37b7a Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 22 Mar 2022 22:17:13 +0000 Subject: [PATCH 002/121] Reuse TreeTraverser to traverse all tree children, with explicit exceptions --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 1 + .../tools/dotc/transform/TreeChecker.scala | 115 +++--------------- 2 files changed, 18 insertions(+), 98 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index f65154984f50..34d3a0bc1ca8 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1638,6 +1638,7 @@ object Trees { abstract class TreeTraverser extends TreeAccumulator[Unit] { def traverse(tree: Tree)(using Context): Unit + def traverse(trees: List[Tree])(using Context) = apply((), trees) def apply(x: Unit, tree: Tree)(using Context): Unit = traverse(tree) protected def traverseChildren(tree: Tree)(using Context): Unit = foldOver((), tree) } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 48db34a98393..ac743116bd48 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -306,7 +306,7 @@ class TreeChecker extends Phase with SymTransformer { sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) override def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = { - checkTreeNode(tree) + TreeNodeChecker.run(tree) val tpdTree = super.typed(tree, pt) Typer.assertPositioned(tree) if (ctx.erasedTypes) @@ -648,102 +648,21 @@ object TreeChecker { } }.apply(tp0) - private val PrivateErased = allOf(Private, Erased) - /** Check that the tree only contains legal children trees */ - def checkTreeNode(tree: untpd.Tree)(implicit ctx: Context): Unit = { - def assertNotTypeTree(t: untpd.Tree): Unit = assert(!t.isInstanceOf[untpd.TypeTree], tree) - - // TODO add many more sanity checks - tree match { -// case Ident(name) => - case Select(qualifier, name) => - // FIXME this assertion fails only in tests/run/scala2trait-lazyval.scala - // assertNotTypeTree(qual) - // case This(qual) => -// -// case Super(qual, mix) => - case Apply(fun, args) => - assertNotTypeTree(fun) - for (arg <- args) { - assertNotTypeTree(arg) - } - case TypeApply(fun, args) => - assertNotTypeTree(fun) - // case Literal(const) => -// -// case New(tpt) => - case Typed(expr, tpt) => - assertNotTypeTree(expr) - // case NamedArg(name, arg) => - case Assign(lhs, rhs) => - assertNotTypeTree(lhs) - assertNotTypeTree(rhs) - case Block(stats, expr) => - for (stat <- stats) { - assertNotTypeTree(stat) - } - assertNotTypeTree(expr) - case If(cond, thenp, elsep) => - assertNotTypeTree(cond) - assertNotTypeTree(thenp) - assertNotTypeTree(elsep) - // case Closure(env, meth, tpt) => - case Match(selector, cases) => - assertNotTypeTree(selector) - case CaseDef(pat, guard, body) => - assertNotTypeTree(guard) - assertNotTypeTree(body) - case Return(expr, from) => - assertNotTypeTree(expr) - case Try(block, handler, finalizer) => - assertNotTypeTree(block) - assertNotTypeTree(finalizer) - case SeqLiteral(elems, elemtpt) => - for (elem <- elems) { - assertNotTypeTree(elem) - } - case Inlined(call, bindings, expansion) => - assertNotTypeTree(call) - // case TypeTree() => -// -// case SingletonTypeTree(ref) => -// -// case AndTypeTree(left, right) => -// -// case OrTypeTree(left, right) => -// -// case RefinedTypeTree(tpt, refinements) => -// -// case AppliedTypeTree(tpt, args) => -// -// case LambdaTypeTree(tparams, body) => -// -// case ByNameTypeTree(result) => -// -// case TypeBoundsTree(lo, hi) => -// -// case Bind(name, body) => -// -// case Alternative(trees) => -// -// case UnApply(fun, implicits, patterns) => - case tree @ ValDef(name, tpt, _) => - assertNotTypeTree(tree.rhs) - case tree @ DefDef(name, tparams, vparamss, tpt, _) => - assertNotTypeTree(tree.rhs) - // case TypeDef(name, rhs) => -// -// case tree @ Template(constr, parents, self, _) => -// -// case Import(expr, selectors) => -// -// case PackageDef(pid, stats) => -// -// case Annotated(arg, annot) => -// -// case Thicket(ts) => - case _ => - } - } + object TreeNodeChecker extends untpd.TreeTraverser: + import untpd._ + def run(tree: Tree)(using Context) = if !tree.isInstanceOf[TypeTree] then traverse(tree) + def traverse(tree: Tree)(using Context) = tree match + case t: TypeTree => assert(assertion = false, t) + case t @ TypeApply(fun, _targs) => traverse(fun) + case t @ New(_tpt) => + case t @ Typed(expr, _tpt) => traverse(expr) + case t @ Closure(env, meth, _tpt) => traverse(env); traverse(meth) + case t @ SeqLiteral(elems, _elemtpt) => traverse(elems) + case t @ ValDef(_, _tpt, _) => traverse(t.rhs) + case t @ DefDef(_, paramss, _tpt, _) => for params <- paramss do traverse(params); traverse(t.rhs) + case t @ TypeDef(_, _rhs) => + case t @ Template(constr, _parents, self, _) => traverse(constr); traverse(self); traverse(t.body) + case t => traverseChildren(t) + end traverse } From e6e456533d7f97fd97cad2a9dbc3c505884654e4 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Tue, 22 Mar 2022 22:42:22 +0000 Subject: [PATCH 003/121] Move TreeNodeChecker running out of typed --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index ac743116bd48..5313e3f53d0d 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -130,6 +130,7 @@ class TreeChecker extends Phase with SymTransformer { assert(ctx.typerState.constraint.domainLambdas.isEmpty, i"non-empty constraint at end of $fusedPhase: ${ctx.typerState.constraint}, ownedVars = ${ctx.typerState.ownedVars.toList}%, %") assertSelectWrapsNew(ctx.compilationUnit.tpdTree) + TreeNodeChecker.run(ctx.compilationUnit.tpdTree) } val checkingCtx = ctx @@ -306,7 +307,6 @@ class TreeChecker extends Phase with SymTransformer { sym.isEffectivelyErased && sym.is(Private) && !sym.initial.is(Private) override def typed(tree: untpd.Tree, pt: Type = WildcardType)(using Context): Tree = { - TreeNodeChecker.run(tree) val tpdTree = super.typed(tree, pt) Typer.assertPositioned(tree) if (ctx.erasedTypes) From 045466ae5015e2c75045a2ffbbd30915f8ca63b7 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 11 Feb 2022 14:36:51 +0000 Subject: [PATCH 004/121] Switch our string interpolators to use Show/Shown As it was our string interpolators were taking Any values and then trying to pattern match back their classes to try to show them nicely. This inevitably fails for things like the opaque type FlagSet, which interpolates as an indecipherable long number. Now, instead, they take "Shown" arguments, for which there is an implicit conversion in scope, given there is a Show instance for value. I captured some desired results in some new unit test cases. In the process a small handful of bugs were discovered, the only particularly bad one was consuming a Iterator when the "transforms" printer was enabled (accessorDefs), followed by an unintentional eta-expansion of a method with a defaulted argument (showSummary). I also lifted out the Showable and exception fallback function as an extension method, so I could use it more broadly. The use of WrappedResult and its `result` in `showing` was also impacted, because the new expected Shown type was driving `result`'s context lookup. Fortunately I was able to continue to use WrappedResult and `result` as defined by handling this API change inside `showing` alone. I wasn't, however, able to find a solution to the impact the new Shown expected type was having on the `stripModuleClassSuffix` extension method, sadly. JSExportsGen is interpolating a private type, so rather than open its access or giving it a Show instance I changed the string interpolation. SyntaxFormatter was no longer used, since the "hl" interpolator was dropped. --- .../tools/backend/sjs/JSExportsGen.scala | 2 +- .../dotty/tools/dotc/core/Decorators.scala | 44 +++++-- .../dotty/tools/dotc/core/TypeComparer.scala | 19 +-- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../tools/dotc/printing/Formatting.scala | 116 ++++++++++++------ .../dotty/tools/dotc/reporting/messages.scala | 2 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- .../tools/dotc/transform/AccessProxies.scala | 2 +- .../dotc/transform/DropOuterAccessors.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 6 +- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- .../tools/dotc/printing/PrinterTests.scala | 34 ++++- 12 files changed, 160 insertions(+), 73 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index bee783af258f..ec74d2c5e896 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -119,7 +119,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { if (kind != overallKind) { bad = true report.error( - em"export overload conflicts with export of $firstSym: they are of different types ($kind / $overallKind)", + em"export overload conflicts with export of $firstSym: they are of different types (${kind.show} / ${overallKind.show})", info.pos) } } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 08452782fd66..9b974650db5a 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -2,12 +2,13 @@ package dotty.tools package dotc package core -import annotation.tailrec -import Symbols._ -import Contexts._, Names._, Phases._, printing.Texts._ -import collection.mutable.ListBuffer -import dotty.tools.dotc.transform.MegaPhase -import printing.Formatting._ +import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer +import scala.util.control.NonFatal + +import Contexts._, Names._, Phases._, Symbols._ +import printing.{ Printer, Showable }, printing.Formatting._, printing.Texts._ +import transform.MegaPhase /** This object provides useful implicit decorators for types defined elsewhere */ object Decorators { @@ -246,13 +247,30 @@ object Decorators { } extension [T](x: T) - def showing( - op: WrappedResult[T] ?=> String, - printer: config.Printers.Printer = config.Printers.default): T = { - printer.println(op(using WrappedResult(x))) + def showing[U]( + op: WrappedResult[U] ?=> String, + printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] = null): T = { + // either the use of `$result` was driven by the expected type of `Shown` + // which lead to the summoning of `Conversion[T, Shown]` (which we'll invoke) + // or no such conversion was found so we'll consume the result as it is instead + val obj = if c == null then x.asInstanceOf[U] else c(x) + printer.println(op(using WrappedResult(obj))) x } + /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ + def show(using Context)(using z: Show[T] = null): String = x match + case _ if z != null => z.show(x).toString + case x: Showable => + try x.show + catch + case ex: CyclicReference => "... (caught cyclic reference) ..." + case NonFatal(ex) + if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => + val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage } + s"[cannot display due to $msg, raw string = $x]" + case _ => String.valueOf(x) + extension [T](x: T) def assertingErrorsReported(using Context): T = { assert(ctx.reporter.errorsReported) @@ -269,19 +287,19 @@ object Decorators { extension (sc: StringContext) /** General purpose string formatting */ - def i(args: Any*)(using Context): String = + def i(args: Shown*)(using Context): String = new StringFormatter(sc).assemble(args) /** Formatting for error messages: Like `i` but suppress follow-on * error messages after the first one if some of their arguments are "non-sensical". */ - def em(args: Any*)(using Context): String = + def em(args: Shown*)(using Context): String = new ErrorMessageFormatter(sc).assemble(args) /** Formatting with added explanations: Like `em`, but add explanations to * give more info about type variables and to disambiguate where needed. */ - def ex(args: Any*)(using Context): String = + def ex(args: Shown*)(using Context): String = explained(em(args: _*)) extension [T <: AnyRef](arr: Array[T]) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 67cfb12d67be..19c5f3dda4ae 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -2713,15 +2713,16 @@ object TypeComparer { */ val Fresh: Repr = 4 - extension (approx: Repr) - def low: Boolean = (approx & LoApprox) != 0 - def high: Boolean = (approx & HiApprox) != 0 - def addLow: Repr = approx | LoApprox - def addHigh: Repr = approx | HiApprox - def show: String = - val lo = if low then " (left is approximated)" else "" - val hi = if high then " (right is approximated)" else "" - lo ++ hi + object Repr: + extension (approx: Repr) + def low: Boolean = (approx & LoApprox) != 0 + def high: Boolean = (approx & HiApprox) != 0 + def addLow: Repr = approx | LoApprox + def addHigh: Repr = approx | HiApprox + def show: String = + val lo = if low then " (left is approximated)" else "" + val hi = if high then " (right is approximated)" else "" + lo ++ hi end ApproxState type ApproxState = ApproxState.Repr diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16926ecb72b8..d187e33b7190 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -606,7 +606,7 @@ object Parsers { if startIndentWidth <= nextIndentWidth then i"""Line is indented too far to the right, or a `{` is missing before: | - |$t""" + |${t.show}""" else in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 50e18d5587b9..0bbbefb1aba0 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -1,47 +1,103 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package printing import scala.language.unsafeNulls +import scala.collection.mutable + import core._ import Texts._, Types._, Flags._, Symbols._, Contexts._ -import collection.mutable import Decorators._ -import scala.util.control.NonFatal import reporting.Message import util.DiffUtil import Highlighting._ object Formatting { + object ShownDef: + /** Represents a value that has been "shown" and can be consumed by StringFormatter. + * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator. + * Also, it's not a `String | Seq[String]` because then we'd need to a Context to call `Showable#show`. We could + * make Context a requirement for a Show instance but then we'd have lots of instances instead of just one ShowAny + * instance. We could also try to make `Show#show` require the Context, but then that breaks the Conversion. */ + opaque type Shown = Any + object Shown: + given [A: Show]: Conversion[A, Shown] = Show[A].show(_) + + sealed abstract class Show[-T]: + /** Show a value T by returning a "shown" result. */ + def show(x: T): Shown + + /** The base implementation, passing the argument to StringFormatter which will try to `.show` it. */ + object ShowAny extends Show[Any]: + def show(x: Any): Shown = x + + class ShowImplicits1: + given Show[ImplicitRef] = ShowAny + given Show[Names.Designator] = ShowAny + given Show[util.SrcPos] = ShowAny + + object Show extends ShowImplicits1: + inline def apply[A](using inline z: Show[A]): Show[A] = z + + given [X: Show]: Show[Seq[X]] with + def show(x: Seq[X]) = x.map(Show[X].show) + + given [A: Show, B: Show]: Show[(A, B)] with + def show(x: (A, B)) = (Show[A].show(x._1), Show[B].show(x._2)) + + given Show[FlagSet] with + def show(x: FlagSet) = x.flagsString + + given Show[TypeComparer.ApproxState] with + def show(x: TypeComparer.ApproxState) = TypeComparer.ApproxState.Repr.show(x) + + given Show[Showable] = ShowAny + given Show[Shown] = ShowAny + given Show[Int] = ShowAny + given Show[Char] = ShowAny + given Show[Boolean] = ShowAny + given Show[String] = ShowAny + given Show[Class[?]] = ShowAny + given Show[Exception] = ShowAny + given Show[StringBuffer] = ShowAny + given Show[Atoms] = ShowAny + given Show[Highlight] = ShowAny + given Show[HighlightBuffer] = ShowAny + given Show[CompilationUnit] = ShowAny + given Show[Mode] = ShowAny + given Show[Phases.Phase] = ShowAny + given Show[Signature] = ShowAny + given Show[TyperState] = ShowAny + given Show[config.ScalaVersion] = ShowAny + given Show[io.AbstractFile] = ShowAny + given Show[parsing.Scanners.IndentWidth] = ShowAny + given Show[parsing.Scanners.Scanner] = ShowAny + given Show[util.SourceFile] = ShowAny + given Show[util.Spans.Span] = ShowAny + given Show[dotty.tools.tasty.TastyBuffer.Addr] = ShowAny + given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny + given Show[interactive.Completion] = ShowAny + given Show[transform.sjs.JSSymUtils.JSName] = ShowAny + given Show[org.scalajs.ir.Position] = ShowAny + end Show + end ShownDef + export ShownDef.{ Show, Shown } + /** General purpose string formatter, with the following features: * - * 1) On all Showables, `show` is called instead of `toString` - * 2) Exceptions raised by a `show` are handled by falling back to `toString`. - * 3) Sequences can be formatted using the desired separator between two `%` signs, + * 1. Invokes the `show` extension method on the interpolated arguments. + * 2. Sequences can be formatted using the desired separator between two `%` signs, * eg `i"myList = (${myList}%, %)"` - * 4) Safe handling of multi-line margins. Left margins are skipped om the parts + * 3. Safe handling of multi-line margins. Left margins are stripped on the parts * of the string context *before* inserting the arguments. That way, we guard * against accidentally treating an interpolated value as a margin. */ class StringFormatter(protected val sc: StringContext) { - protected def showArg(arg: Any)(using Context): String = arg match { - case arg: Showable => - try arg.show - catch { - case ex: CyclicReference => "... (caught cyclic reference) ..." - case NonFatal(ex) - if !ctx.mode.is(Mode.PrintShowExceptions) && - !ctx.settings.YshowPrintErrors.value => - val msg = ex match - case te: TypeError => te.toMessage - case _ => ex.getMessage - s"[cannot display due to $msg, raw string = ${arg.toString}]" - } - case _ => String.valueOf(arg) - } + protected def showArg(arg: Any)(using Context): String = arg.show - private def treatArg(arg: Any, suffix: String)(using Context): (Any, String) = arg match { + private def treatArg(arg: Shown, suffix: String)(using Context): (Any, String) = arg match { case arg: Seq[?] if suffix.nonEmpty && suffix.head == '%' => val (rawsep, rest) = suffix.tail.span(_ != '%') val sep = StringContext.processEscapes(rawsep) @@ -51,7 +107,7 @@ object Formatting { (showArg(arg), suffix) } - def assemble(args: Seq[Any])(using Context): String = { + def assemble(args: Seq[Shown])(using Context): String = { def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak def stripTrailingPart(s: String) = { val (pre, post) = s.span(c => !isLineBreak(c)) @@ -77,18 +133,6 @@ object Formatting { override protected def showArg(arg: Any)(using Context): String = wrapNonSensical(arg, super.showArg(arg)(using errorMessageCtx)) - class SyntaxFormatter(sc: StringContext) extends StringFormatter(sc) { - override protected def showArg(arg: Any)(using Context): String = - arg match { - case hl: Highlight => - hl.show - case hb: HighlightBuffer => - hb.toString - case _ => - SyntaxHighlighting.highlight(super.showArg(arg)) - } - } - private def wrapNonSensical(arg: Any, str: String)(using Context): String = { import Message._ def isSensical(arg: Any): Boolean = arg match { diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f8fec07e9f9f..be30f5adc044 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1759,7 +1759,7 @@ import transform.SymUtils._ class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) extends NamingMsg(ClassAndCompanionNameClashID) { - def msg = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix}" + def msg = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix.show}" def explain = em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: | - ${cls.owner} defines ${cls} diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 1120b279b2e2..f294863c25b4 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -776,7 +776,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case n: Name => h = nameHash(n, h) case elem => - cannotHash(what = i"`$elem` of unknown class ${elem.getClass}", elem, tree) + cannotHash(what = i"`${elem.show}` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index 15c6d30c3b19..a59ce4d79a48 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -58,7 +58,7 @@ abstract class AccessProxies { /** Add all needed accessors to the `body` of class `cls` */ def addAccessorDefs(cls: Symbol, body: List[Tree])(using Context): List[Tree] = { - val accDefs = accessorDefs(cls) + val accDefs = accessorDefs(cls).toList transforms.println(i"add accessors for $cls: $accDefs%, %") if (accDefs.isEmpty) body else body ++ accDefs } diff --git a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala index ca6a056fd6d5..a363ccaeb0d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropOuterAccessors.scala @@ -80,7 +80,7 @@ class DropOuterAccessors extends MiniPhase with IdentityDenotTransformer: cpy.Block(rhs)(inits.filterNot(dropOuterInit), expr) }) assert(droppedParamAccessors.isEmpty, - i"""Failed to eliminate: $droppedParamAccessors + i"""Failed to eliminate: ${droppedParamAccessors.toList} when dropping outer accessors for ${ctx.owner} with $impl""") cpy.Template(impl)(constr = constr1, body = body1) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ab0c8d449d00..123f2a7aaeb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -266,7 +266,7 @@ object Erasure { def constant(tree: Tree, const: Tree)(using Context): Tree = (if (isPureExpr(tree)) const else Block(tree :: Nil, const)).withSpan(tree.span) - final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary}: ${tree.tpe} into $target") { + final def box(tree: Tree, target: => String = "")(using Context): Tree = trace(i"boxing ${tree.showSummary()}: ${tree.tpe} into $target") { tree.tpe.widen match { case ErasedValueType(tycon, _) => New(tycon, cast(tree, underlyingOfValueClass(tycon.symbol.asClass)) :: Nil) // todo: use adaptToType? @@ -286,7 +286,7 @@ object Erasure { } } - def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary}: ${tree.tpe} as a $pt") { + def unbox(tree: Tree, pt: Type)(using Context): Tree = trace(i"unboxing ${tree.showSummary()}: ${tree.tpe} as a $pt") { pt match { case ErasedValueType(tycon, underlying) => def unboxedTree(t: Tree) = @@ -1031,7 +1031,7 @@ object Erasure { } override def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean)(using Context): Tree = - trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { + trace(i"adapting ${tree.showSummary()}: ${tree.tpe} to $pt", show = true) { if ctx.phase != erasurePhase && ctx.phase != erasurePhase.next then // this can happen when reading annotations loaded during erasure, // since these are loaded at phase typer. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 0525ce805d2e..f41eba593791 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1197,7 +1197,7 @@ trait Checking { case _: TypeTree => case _ => if tree.tpe.typeParams.nonEmpty then - val what = if tree.symbol.exists then tree.symbol else i"type $tree" + val what = if tree.symbol.exists then tree.symbol.show else i"type $tree" report.error(em"$what takes type parameters", tree.srcPos) /** Check that we are in an inline context (inside an inline method or in inline code) */ diff --git a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala index e6b17fade86f..23054cdf0dfb 100644 --- a/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/PrinterTests.scala @@ -1,9 +1,11 @@ -package dotty.tools.dotc.printing +package dotty.tools +package dotc +package printing -import dotty.tools.DottyTest -import dotty.tools.dotc.ast.{Trees,tpd} -import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.Symbols._ +import ast.{ Trees, tpd } +import core.Names._ +import core.Symbols._ +import core.Decorators._ import dotty.tools.dotc.core.Contexts.Context import org.junit.Assert.assertEquals @@ -49,4 +51,26 @@ class PrinterTests extends DottyTest { assertEquals("Int & (Boolean | String)", bar.tpt.show) } } + + @Test def string: Unit = assertEquals("foo", i"${"foo"}") + + import core.Flags._ + @Test def flagsSingle: Unit = assertEquals("final", i"$Final") + @Test def flagsSeq: Unit = assertEquals(", final", i"${Seq(JavaStatic, Final)}%, %") + @Test def flagsTuple: Unit = assertEquals("(,final)", i"${(JavaStatic, Final)}") + @Test def flagsSeqOfTuple: Unit = assertEquals("(final,given), (private,lazy)", i"${Seq((Final, Given), (Private, Lazy))}%, %") + + class StorePrinter extends config.Printers.Printer: + var string: String = "" + override def println(msg: => String) = string = msg + + @Test def testShowing: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=$result", store) + assertEquals("flags=final ", store.string) + + @Test def TestShowingWithOriginalType: Unit = + val store = StorePrinter() + (JavaStatic | Final).showing(i"flags=${if result.is(Private) then result &~ Private else result | Private}", store) + assertEquals("flags=private final ", store.string) } From 382af9417d795c3bcfcaa245b3927606878fe859 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 17 Feb 2022 13:40:01 +0000 Subject: [PATCH 005/121] Define a low-priority instance of Show for Product This removes the need for some of the default instances. More importantly, this reduces the burden and boilerplate that would be needed when introducing a new case class, without defining a Show instance for it or extending Showable. --- .../dotty/tools/dotc/printing/Formatting.scala | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 0bbbefb1aba0..bc1ad6fb163e 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -33,7 +33,10 @@ object Formatting { object ShowAny extends Show[Any]: def show(x: Any): Shown = x - class ShowImplicits1: + class ShowImplicits2: + given Show[Product] = ShowAny + + class ShowImplicits1 extends ShowImplicits2: given Show[ImplicitRef] = ShowAny given Show[Names.Designator] = ShowAny given Show[util.SrcPos] = ShowAny @@ -62,25 +65,15 @@ object Formatting { given Show[Class[?]] = ShowAny given Show[Exception] = ShowAny given Show[StringBuffer] = ShowAny - given Show[Atoms] = ShowAny - given Show[Highlight] = ShowAny - given Show[HighlightBuffer] = ShowAny given Show[CompilationUnit] = ShowAny - given Show[Mode] = ShowAny given Show[Phases.Phase] = ShowAny - given Show[Signature] = ShowAny given Show[TyperState] = ShowAny given Show[config.ScalaVersion] = ShowAny given Show[io.AbstractFile] = ShowAny - given Show[parsing.Scanners.IndentWidth] = ShowAny given Show[parsing.Scanners.Scanner] = ShowAny given Show[util.SourceFile] = ShowAny given Show[util.Spans.Span] = ShowAny - given Show[dotty.tools.tasty.TastyBuffer.Addr] = ShowAny given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny - given Show[interactive.Completion] = ShowAny - given Show[transform.sjs.JSSymUtils.JSName] = ShowAny - given Show[org.scalajs.ir.Position] = ShowAny end Show end ShownDef export ShownDef.{ Show, Shown } From 0ce7c52fc582fecaee988334a3e630e911ddd8b1 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 24 Feb 2022 12:04:38 +0000 Subject: [PATCH 006/121] Drop Show from Any#show extension method I added this at the last minute as a fallback, but I can't remember the use case exactly, and it muddies the waters by mixing concerns. Also other typo fixes and a code tweak. --- compiler/src/dotty/tools/dotc/core/Decorators.scala | 5 ++--- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 +- compiler/src/dotty/tools/dotc/reporting/messages.scala | 4 +++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 9b974650db5a..27a4ba075654 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -251,7 +251,7 @@ object Decorators { op: WrappedResult[U] ?=> String, printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] = null): T = { // either the use of `$result` was driven by the expected type of `Shown` - // which lead to the summoning of `Conversion[T, Shown]` (which we'll invoke) + // which led to the summoning of `Conversion[T, Shown]` (which we'll invoke) // or no such conversion was found so we'll consume the result as it is instead val obj = if c == null then x.asInstanceOf[U] else c(x) printer.println(op(using WrappedResult(obj))) @@ -259,8 +259,7 @@ object Decorators { } /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ - def show(using Context)(using z: Show[T] = null): String = x match - case _ if z != null => z.show(x).toString + def show(using Context): String = x match case x: Showable => try x.show catch diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index bc1ad6fb163e..b93798c37bf0 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -18,7 +18,7 @@ object Formatting { object ShownDef: /** Represents a value that has been "shown" and can be consumed by StringFormatter. * Not just a string because it may be a Seq that StringFormatter will intersperse with the trailing separator. - * Also, it's not a `String | Seq[String]` because then we'd need to a Context to call `Showable#show`. We could + * Also, it's not a `String | Seq[String]` because then we'd need a Context to call `Showable#show`. We could * make Context a requirement for a Show instance but then we'd have lots of instances instead of just one ShowAny * instance. We could also try to make `Show#show` require the Context, but then that breaks the Conversion. */ opaque type Shown = Any diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index be30f5adc044..18e2d234452d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1759,7 +1759,9 @@ import transform.SymUtils._ class ClassAndCompanionNameClash(cls: Symbol, other: Symbol)(using Context) extends NamingMsg(ClassAndCompanionNameClashID) { - def msg = em"Name clash: both ${cls.owner} and its companion object defines ${cls.name.stripModuleClassSuffix.show}" + def msg = + val name = cls.name.stripModuleClassSuffix + em"Name clash: both ${cls.owner} and its companion object defines $name" def explain = em"""|A ${cls.kindString} and its companion object cannot both define a ${hl("class")}, ${hl("trait")} or ${hl("object")} with the same name: | - ${cls.owner} defines ${cls} From 480eb03b33f7fff7ca0f0e8c450e209e4bd96e06 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Fri, 25 Feb 2022 18:27:12 +0000 Subject: [PATCH 007/121] Rename to Any#tryToShow as it's not Show-based It was also being used over TypeComparer.ApproxState's show extension method (for unclear reasons), so let's avoid name confusion too. --- compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala | 2 +- compiler/src/dotty/tools/dotc/core/Decorators.scala | 2 +- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 2 +- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 +- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala index ec74d2c5e896..c9dbdaf68812 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSExportsGen.scala @@ -119,7 +119,7 @@ final class JSExportsGen(jsCodeGen: JSCodeGen)(using Context) { if (kind != overallKind) { bad = true report.error( - em"export overload conflicts with export of $firstSym: they are of different types (${kind.show} / ${overallKind.show})", + em"export overload conflicts with export of $firstSym: they are of different types (${kind.tryToShow} / ${overallKind.tryToShow})", info.pos) } } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 27a4ba075654..05286001cd8b 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -259,7 +259,7 @@ object Decorators { } /** Instead of `toString` call `show` on `Showable` values, falling back to `toString` if an exception is raised. */ - def show(using Context): String = x match + def tryToShow(using Context): String = x match case x: Showable => try x.show catch diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index d187e33b7190..fab132a36035 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -606,7 +606,7 @@ object Parsers { if startIndentWidth <= nextIndentWidth then i"""Line is indented too far to the right, or a `{` is missing before: | - |${t.show}""" + |${t.tryToShow}""" else in.spaceTabMismatchMsg(startIndentWidth, nextIndentWidth), in.next.offset diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index b93798c37bf0..1c8213325ae6 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -88,7 +88,7 @@ object Formatting { * against accidentally treating an interpolated value as a margin. */ class StringFormatter(protected val sc: StringContext) { - protected def showArg(arg: Any)(using Context): String = arg.show + protected def showArg(arg: Any)(using Context): String = arg.tryToShow private def treatArg(arg: Shown, suffix: String)(using Context): (Any, String) = arg match { case arg: Seq[?] if suffix.nonEmpty && suffix.head == '%' => diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index f294863c25b4..c22172b3e10f 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -776,7 +776,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder { case n: Name => h = nameHash(n, h) case elem => - cannotHash(what = i"`${elem.show}` of unknown class ${elem.getClass}", elem, tree) + cannotHash(what = i"`${elem.tryToShow}` of unknown class ${elem.getClass}", elem, tree) h end iteratorHash From 579e14bf0e0b33893473e6eee38ec6b84f2966d2 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 23 Mar 2022 08:52:26 +0000 Subject: [PATCH 008/121] Fix: use the value not the eta-expanded method! Another win for Show/Shown over Any. --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 15628c3b83e8..c97653725a92 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -161,7 +161,7 @@ object Completion { | prefix = ${completer.prefix}, | term = ${completer.mode.is(Mode.Term)}, | type = ${completer.mode.is(Mode.Type)} - | results = $backtickCompletions%, %""") + | results = $backtickedCompletions%, %""") (offset, backtickedCompletions) } From e5ed401834e0bbdc6337d5724ebb634d35333c71 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 31 Mar 2022 15:00:07 +0200 Subject: [PATCH 009/121] Fixes now that explicit nulls are on --- compiler/src/dotty/tools/dotc/core/Decorators.scala | 4 ++-- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 05286001cd8b..ce488ef23885 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -249,7 +249,7 @@ object Decorators { extension [T](x: T) def showing[U]( op: WrappedResult[U] ?=> String, - printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] = null): T = { + printer: config.Printers.Printer = config.Printers.default)(using c: Conversion[T, U] | Null = null): T = { // either the use of `$result` was driven by the expected type of `Shown` // which led to the summoning of `Conversion[T, Shown]` (which we'll invoke) // or no such conversion was found so we'll consume the result as it is instead @@ -268,7 +268,7 @@ object Decorators { if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value => val msg = ex match { case te: TypeError => te.toMessage case _ => ex.getMessage } s"[cannot display due to $msg, raw string = $x]" - case _ => String.valueOf(x) + case _ => String.valueOf(x).nn extension [T](x: T) def assertingErrorsReported(using Context): T = { diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 1c8213325ae6..0cef7fd3e833 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -50,6 +50,9 @@ object Formatting { given [A: Show, B: Show]: Show[(A, B)] with def show(x: (A, B)) = (Show[A].show(x._1), Show[B].show(x._2)) + given [X: Show]: Show[X | Null] with + def show(x: X | Null) = if x == null then "null" else Show[X].show(x.nn) + given Show[FlagSet] with def show(x: FlagSet) = x.flagsString From 250533c018f6d364b5fd5a91a6049f45631af09e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 28 Mar 2022 15:32:41 +0200 Subject: [PATCH 010/121] Handle this prefix in classes (in quote patterns) #14732 Fixes #14788 --- .../quoted/runtime/impl/QuoteMatcher.scala | 2 + .../tasty-inspector/i14788.scala | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/run-custom-args/tasty-inspector/i14788.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 06a064860ac4..0e1ad9a54f4b 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -249,6 +249,8 @@ object QuoteMatcher { case TermRef(qual: TermRef, _) => tpd.ref(qual) =?= qual2 case TermRef(qual: ThisType, _) if qual.classSymbol.is(Module, butNot = Package) => tpd.ref(qual.classSymbol.companionModule) =?= qual2 + case TermRef(qual, _) if qual.typeSymbol.isClass && qual2.symbol == defn.QuotedRuntimePatterns_patternHole => + tpd.desugarIdentPrefix(ref) =?= qual2 case _ => matched /* Match reference */ case _: Ident if symbolMatch(scrutinee, pattern) => matched diff --git a/tests/run-custom-args/tasty-inspector/i14788.scala b/tests/run-custom-args/tasty-inspector/i14788.scala new file mode 100644 index 000000000000..29af31386499 --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/i14788.scala @@ -0,0 +1,55 @@ + +import scala.quoted.* +import scala.tasty.inspector.Inspector +import scala.tasty.inspector.Tasty +import scala.tasty.inspector.TastyInspector +import scala.reflect.ClassTag + +import scala.quoted.* +import scala.tasty.inspector.* + +@main def Test: Unit = { + // Artefact of the current test infrastructure + // TODO improve infrastructure to avoid needing this code on each test + val classpath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(_.contains("runWithCompiler")).get + val allTastyFiles = dotty.tools.io.Path(classpath).walkFilter(_.extension == "tasty").map(_.toString).toList + val tastyFiles = allTastyFiles.filter(_.contains("MySeq")) + + TastyInspector.inspectTastyFiles(tastyFiles)(new MyInspector) +} + +class MySeq(override val length: Int) extends collection.Seq[String] { + def foo: Int = length // error + + def apply(v1: Int): String = ??? + def iterator: Iterator[String] = ??? +} + +class MyInspector extends Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + import quotes.reflect.* + val traverser = new TreeTraverser { + override def traverseTree(tree: Tree)(owner: Symbol): Unit = { + if (tree.isExpr) { + try { + tree.asExpr match { + case '{ ($x: collection.Seq[t]).length } => + super.traverseTree(tree)(owner) + case _ => + super.traverseTree(tree)(owner) + } + } catch { + case e => + report.error(s"unexpected error ${e}", tree.pos) + throw e + } + } else { + super.traverseTree(tree)(owner) + } + } + } + tastys.foreach{ tasty => + traverser.traverseTree(tasty.ast)(tasty.ast.symbol) + } + } +} From 1a210f69ddf323a85dcfa140252520a1dbc05a75 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 31 Mar 2022 18:34:37 +0200 Subject: [PATCH 011/121] Simplify quote pattern Ident prefix logic --- .../src/scala/quoted/runtime/impl/QuoteMatcher.scala | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 0e1ad9a54f4b..0d4a6a463274 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -244,14 +244,10 @@ object QuoteMatcher { case Select(qual2, _) if symbolMatch(scrutinee, pattern) => ref match case Select(qual1, _) => qual1 =?= qual2 - case ref: Ident => - ref.tpe match - case TermRef(qual: TermRef, _) => tpd.ref(qual) =?= qual2 - case TermRef(qual: ThisType, _) if qual.classSymbol.is(Module, butNot = Package) => - tpd.ref(qual.classSymbol.companionModule) =?= qual2 - case TermRef(qual, _) if qual.typeSymbol.isClass && qual2.symbol == defn.QuotedRuntimePatterns_patternHole => - tpd.desugarIdentPrefix(ref) =?= qual2 - case _ => matched + case ref: Ident if qual2.symbol == defn.QuotedRuntimePatterns_patternHole => + tpd.desugarIdentPrefix(ref) =?= qual2 + case ref: Ident => matched + /* Match reference */ case _: Ident if symbolMatch(scrutinee, pattern) => matched /* Match type */ From fe40c594a1694e8de7c1d797ed22769c3a61164b Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Apr 2022 12:23:59 +0200 Subject: [PATCH 012/121] Fix Scala Wart about implicit () class parameters Fixes #2576 As the discussion in #2576 shows, we still have some problems with the implicitly inserted empty parameter lists for class constructors. We do need that empty list to support syntax like `C()` and `new C()`. But it gets in the way if a class has using clauses. Example from the issue: ```scala class Bar(using x: Int)(y: String) given Int = ??? def test = new Bar("") ``` Here, an implicitly inserted `()` in front makes the last line fail. We'd need `new Bar()("")`. If a class has only using clauses as parameters we now insert a `()` at the end instead of at the start. That makes the example compile. For old-style implicit parameters we don't have a choice. We still need the `()` at the start since otherwise we'd change the meaning of calls with explicit arguments for the implicit parameters. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 14 ++++--- .../tools/dotc/config/PathResolver.scala | 8 +++- .../src/dotty/tools/dotc/core/NamerOps.scala | 17 +++++++-- .../dotty/tools/dotc/transform/Bridges.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 37 +++++++++++-------- tests/neg/i12344.scala | 20 +++++----- tests/pos/given-constrapps.scala | 1 - tests/pos/i2576.scala | 3 ++ tests/run-macros/i12021/Macro_1.scala | 1 + tests/run/i2567.scala | 4 +- 10 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 tests/pos/i2576.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 9cd30b94d692..28c1214762fb 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -585,12 +585,16 @@ object desugar { // new C[Ts](paramss) lazy val creatorExpr = { - val vparamss = constrVparamss match { - case (vparam :: _) :: _ if vparam.mods.isOneOf(GivenOrImplicit) => // add a leading () to match class parameters + val vparamss = constrVparamss match + case (vparam :: _) :: _ if vparam.mods.is(Implicit) => // add a leading () to match class parameters Nil :: constrVparamss case _ => - constrVparamss - } + if constrVparamss.nonEmpty && constrVparamss.forall { + case vparam :: _ => vparam.mods.is(Given) + case _ => false + } + then constrVparamss :+ Nil // add a trailing () to match class parameters + else constrVparamss val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) => val app = Apply(nu, vparams.map(refOfDef)) vparams match { @@ -818,7 +822,7 @@ object desugar { } flatTree(cdef1 :: companions ::: implicitWrappers ::: enumScaffolding) - }.showing(i"desugared: $result", Printers.desugar) + }.showing(i"desugared: $cdef --> $result", Printers.desugar) /** Expand * diff --git a/compiler/src/dotty/tools/dotc/config/PathResolver.scala b/compiler/src/dotty/tools/dotc/config/PathResolver.scala index f9c49c1a7723..afa30e38dc2a 100644 --- a/compiler/src/dotty/tools/dotc/config/PathResolver.scala +++ b/compiler/src/dotty/tools/dotc/config/PathResolver.scala @@ -131,7 +131,9 @@ object PathResolver { def fromPathString(path: String)(using Context): ClassPath = { val settings = ctx.settings.classpath.update(path) - new PathResolver()(using ctx.fresh.setSettings(settings)).result + inContext(ctx.fresh.setSettings(settings)) { + new PathResolver().result + } } /** Show values in Environment and Defaults when no argument is provided. @@ -147,7 +149,9 @@ object PathResolver { val ArgsSummary(sstate, rest, errors, warnings) = ctx.settings.processArguments(args.toList, true, ctx.settingsState) errors.foreach(println) - val pr = new PathResolver()(using ctx.fresh.setSettings(sstate)) + val pr = inContext(ctx.fresh.setSettings(sstate)) { + new PathResolver() + } println(" COMMAND: 'scala %s'".format(args.mkString(" "))) println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" "))) diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 56022e9fcaf2..1a878c9547b1 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -18,14 +18,23 @@ object NamerOps: case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef)) case _ => ctor.owner.typeRef - /** if isConstructor, make sure it has one leading non-implicit parameter list */ + /** If isConstructor, make sure it has at least one non-implicit parameter list + * This is done by adding a () in front of a leading old style implicit parameter, + * or by adding a () as last -- or only -- parameter list if the constructor has + * only using clauses as parameters. + */ def normalizeIfConstructor(paramss: List[List[Symbol]], isConstructor: Boolean)(using Context): List[List[Symbol]] = if !isConstructor then paramss else paramss match - case Nil :: _ => paramss - case TermSymbols(vparam :: _) :: _ if !vparam.isOneOf(GivenOrImplicit) => paramss case TypeSymbols(tparams) :: paramss1 => tparams :: normalizeIfConstructor(paramss1, isConstructor) - case _ => Nil :: paramss + case TermSymbols(vparam :: _) :: _ if vparam.is(Implicit) => Nil :: paramss + case _ => + if paramss.forall { + case TermSymbols(vparams) => vparams.nonEmpty && vparams.head.is(Given) + case _ => true + } + then paramss :+ Nil + else paramss /** The method type corresponding to given parameters and result type */ def methodType(paramss: List[List[Symbol]], resultType: Type, isJava: Boolean = false)(using Context): Type = diff --git a/compiler/src/dotty/tools/dotc/transform/Bridges.scala b/compiler/src/dotty/tools/dotc/transform/Bridges.scala index 912b8ca69131..071109147b45 100644 --- a/compiler/src/dotty/tools/dotc/transform/Bridges.scala +++ b/compiler/src/dotty/tools/dotc/transform/Bridges.scala @@ -170,7 +170,7 @@ class Bridges(root: ClassSymbol, thisPhase: DenotTransformer)(using Context) { * time deferred methods in `stats` that are replaced by a bridge with the same signature. */ def add(stats: List[untpd.Tree]): List[untpd.Tree] = - val opc = new BridgesCursor()(using preErasureCtx) + val opc = inContext(preErasureCtx) { new BridgesCursor } while opc.hasNext do if !opc.overriding.is(Deferred) then addBridgeIfNeeded(opc.overriding, opc.overridden) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2ae55032d7ae..bdbf1066377d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2356,25 +2356,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** If `ref` is an implicitly parameterized trait, pass an implicit argument list. * Otherwise, if `ref` is a parameterized trait, error. - * Note: Traits and classes currently always have at least an empty parameter list () - * before the implicit parameters (this is inserted if not given in source). - * We skip this parameter list when deciding whether a trait is parameterless or not. + * Note: Traits and classes have sometimes a synthesized empty parameter list () + * in front or after the implicit parameter(s). See NamerOps.normalizeIfConstructor. + * We synthesize a () argument at the correct place in this case. * @param ref The tree referring to the (parent) trait * @param psym Its type symbol - * @param cinfo The info of its constructor */ - def maybeCall(ref: Tree, psym: Symbol): Tree = psym.primaryConstructor.info.stripPoly match - case cinfo @ MethodType(Nil) if cinfo.resultType.isImplicitMethod => + def maybeCall(ref: Tree, psym: Symbol): Tree = + def appliedRef = typedExpr(untpd.New(untpd.TypedSplice(ref)(using superCtx), Nil))(using superCtx) - case cinfo @ MethodType(Nil) if !cinfo.resultType.isInstanceOf[MethodType] => - ref - case cinfo: MethodType => - if !ctx.erasedTypes then // after constructors arguments are passed in super call. - typr.println(i"constr type: $cinfo") - report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos) - ref - case _ => - ref + def dropContextual(tp: Type): Type = tp.stripPoly match + case mt: MethodType if mt.isContextualMethod => dropContextual(mt.resType) + case _ => tp + psym.primaryConstructor.info.stripPoly match + case cinfo @ MethodType(Nil) + if cinfo.resultType.isImplicitMethod && !cinfo.resultType.isContextualMethod => + appliedRef + case cinfo => + val cinfo1 = dropContextual(cinfo) + cinfo1 match + case cinfo1 @ MethodType(Nil) if !cinfo1.resultType.isInstanceOf[MethodType] => + if cinfo1 ne cinfo then appliedRef else ref + case cinfo1: MethodType if !ctx.erasedTypes => + report.error(ParameterizedTypeLacksArguments(psym), ref.srcPos) + ref + case _ => + ref val seenParents = mutable.Set[Symbol]() diff --git a/tests/neg/i12344.scala b/tests/neg/i12344.scala index 88daf535e699..b2fd6d27c37c 100644 --- a/tests/neg/i12344.scala +++ b/tests/neg/i12344.scala @@ -2,15 +2,15 @@ import scala.quoted.* class C(using q: Quotes)(i: Int = 1, f: q.reflect.Flags = q.reflect.Flags.EmptyFlags) -def test1a(using q: Quotes) = new C() // error -def test2a(using q: Quotes) = new C(1) // error -def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy) // error -def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy) // error +def test1a(using q: Quotes) = new C() +def test2a(using q: Quotes) = new C(1) +def test3a(using q: Quotes) = new C(1, q.reflect.Flags.Lazy) +def test4a(using q: Quotes) = new C(f = q.reflect.Flags.Lazy) -def test1b(using q: Quotes) = C() // error -def test2b(using q: Quotes) = C(1) // error -def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy) // error -def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy) // error +def test1b(using q: Quotes) = C() +def test2b(using q: Quotes) = C(1) +def test3b(using q: Quotes) = C(1, q.reflect.Flags.Lazy) +def test4b(using q: Quotes) = C(f = q.reflect.Flags.Lazy) def test1c(using q: Quotes) = new C(using q)() def test2c(using q: Quotes) = new C(using q)(1) @@ -22,5 +22,5 @@ def test2d(using q: Quotes) = C(using q)(1) def test3d(using q: Quotes) = C(using q)(1, q.reflect.Flags.Lazy) def test4d(using q: Quotes) = C(using q)(f = q.reflect.Flags.Lazy) -def test1e(using q: Quotes) = new C()() -def test2e(using q: Quotes) = C()() +def test1e(using q: Quotes) = new C()() // error +def test2e(using q: Quotes) = C()() // error diff --git a/tests/pos/given-constrapps.scala b/tests/pos/given-constrapps.scala index f92653542861..cdea7967f15e 100644 --- a/tests/pos/given-constrapps.scala +++ b/tests/pos/given-constrapps.scala @@ -19,7 +19,6 @@ class Foo(using TC) { object Test extends App { new C(using tc) - new C()(using tc) new C(using tc) {} new C2(1)(using tc)(using List(tc)) new C2(1)(using tc)(using List(tc)) {} diff --git a/tests/pos/i2576.scala b/tests/pos/i2576.scala new file mode 100644 index 000000000000..ac8cdefb1b30 --- /dev/null +++ b/tests/pos/i2576.scala @@ -0,0 +1,3 @@ +class Bar(using x: Int)(y: String) +given Int = ??? +def test = new Bar("") diff --git a/tests/run-macros/i12021/Macro_1.scala b/tests/run-macros/i12021/Macro_1.scala index 4c10cf2ca17d..81703dfbab3d 100644 --- a/tests/run-macros/i12021/Macro_1.scala +++ b/tests/run-macros/i12021/Macro_1.scala @@ -9,6 +9,7 @@ def inspect2[A: Type](using Quotes): Expr[String] = { val ps = TypeRepr.of[A].typeSymbol.primaryConstructor.tree match case DefDef(_, List(Nil, ps: TermParamClause), _, _) => ps + case DefDef(_, List(ps: TermParamClause, Nil), _, _) => ps case DefDef(_, List(ps: TermParamClause), _, _) => ps val names = ps.params.map(p => s"${p.name}: ${p.tpt.show}").mkString("(", ", ", ")") diff --git a/tests/run/i2567.scala b/tests/run/i2567.scala index c9625aeb33d1..4cd60080faa7 100644 --- a/tests/run/i2567.scala +++ b/tests/run/i2567.scala @@ -10,7 +10,7 @@ object Test extends App { new Foo new Foo(using tc) new Foo() - new Foo()(using tc) + new Foo(using tc) Foo() - Foo()(using tc) + Foo(using tc) } \ No newline at end of file From 3763e6be1d1585564ed16f6c74953ec50730a1f6 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Apr 2022 14:00:27 +0200 Subject: [PATCH 013/121] Update semanticDB expect file --- tests/semanticdb/metac.expect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 732e9b07aa85..436eebfe27b5 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -1808,7 +1808,7 @@ givens/InventedNames$package.given_String. => final implicit lazy val given meth givens/InventedNames$package.given_X. => final implicit given object given_X extends Object with X { self: given_X.type => +2 decls } givens/InventedNames$package.given_X.doX(). => method doX => Int <: givens/X#doX(). givens/InventedNames$package.given_Y# => implicit given class given_Y extends Object with Y { self: given_Y => +3 decls } -givens/InventedNames$package.given_Y#``(). => primary ctor ()(implicit val given param x$1: X): given_Y +givens/InventedNames$package.given_Y#``(). => primary ctor (implicit val given param x$1: X)(): given_Y givens/InventedNames$package.given_Y#``().(x$1) => implicit val given param x$1: X givens/InventedNames$package.given_Y#doY(). => method doY => String <: givens/Y#doY(). givens/InventedNames$package.given_Y#x$1. => protected implicit val given method x$1 X From 2fc2aa2881ed0bf4a20ac6f337f6aedc39fc2648 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Apr 2022 14:15:26 +0200 Subject: [PATCH 014/121] Harden patch logic --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bdbf1066377d..fe5290c7ab2d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3408,7 +3408,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def isContextBoundParams = wtp.stripPoly match case MethodType(EvidenceParamName(_) :: _) => true case _ => false - if sourceVersion == `future-migration` && isContextBoundParams + if sourceVersion == `future-migration` && isContextBoundParams && pt.args.nonEmpty then // Under future-migration, don't infer implicit arguments yet for parameters // coming from context bounds. Issue a warning instead and offer a patch. report.migrationWarning( From 00a839e3cd5db3e4b68c8f0790f8923bc2029b4a Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 4 Apr 2022 22:48:30 +0200 Subject: [PATCH 015/121] Make TreeUnpickler read pre-3.2 Tasty files correctly This is needed to ensure backwards Tasty compatibility --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 06dc4872c7f3..cd45baf26b72 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -27,7 +27,7 @@ import typer.ConstFold import typer.Checking.checkNonCyclic import typer.Nullables._ import util.Spans._ -import util.SourceFile +import util.{SourceFile, Property} import ast.{Trees, tpd, untpd} import Trees._ import Decorators._ @@ -1135,7 +1135,23 @@ class TreeUnpickler(reader: TastyReader, tpd.Super(qual, mixId, mixTpe.typeSymbol) case APPLY => val fn = readTerm() - tpd.Apply(fn, until(end)(readTerm())) + val args = until(end)(readTerm()) + // Adapt constructor calls where class has only using clauses from old to new scheme. + // Old: leading (), new: trailing (). + // This is neccessary so that we can read pre-3.2 Tasty correctly. There, + // constructor calls use the old scheme, but constructor definitions already + // use the new scheme, since they are reconstituted with normalizeIfConstructor. + if fn.symbol.isConstructor && fn.tpe.widen.isContextualMethod && args.isEmpty then + fn.withAttachment(SuppressedApplyToNone, ()) + else + val res = tpd.Apply(fn, args) + if fn.removeAttachment(SuppressedApplyToNone).isEmpty then + res + else res.tpe.widen match + case MethodType(Nil) => + res.appliedToNone + case mt: MethodType if mt.isContextualMethod => + res.withAttachment(SuppressedApplyToNone, ()) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => @@ -1523,4 +1539,9 @@ object TreeUnpickler { inline val AllDefs = 2 // add everything class TreeWithoutOwner extends Exception + + /** An attachment key indicating that an old-style leading () in a constructor + * call that has otherwise only using clauses was suppressed. + */ + val SuppressedApplyToNone: Property.Key[Unit] = Property.Key() } From df9f466ac4963f1cd36322b451aa4ff23ec9d856 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Tue, 5 Apr 2022 13:32:29 +0200 Subject: [PATCH 016/121] Test tasty backwards compatibility This currently fail with: MethodType(List(y), List(TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix,module class )),object scala),Predef),String)), TypeRef(ThisType(TypeRef(NoPrefix,module class )),class Bar)) (of class dotty.tools.dotc.core.Types$CachedMethodType) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTerm$1(TreeUnpickler.scala:1154) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTerm(TreeUnpickler.scala:1313) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTerm$1(TreeUnpickler.scala:1137) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTerm(TreeUnpickler.scala:1313) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.$anonfun$24(TreeUnpickler.scala:1138) at dotty.tools.tasty.TastyReader.until(TastyReader.scala:125) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTerm$1(TreeUnpickler.scala:1138) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTerm(TreeUnpickler.scala:1313) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStat(TreeUnpickler.scala:998) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStats$$anonfun$1(TreeUnpickler.scala:1036) at dotty.tools.tasty.TastyReader.until(TastyReader.scala:125) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readIndexedStats(TreeUnpickler.scala:1036) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readStats(TreeUnpickler.scala:1040) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTerm$1(TreeUnpickler.scala:1166) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTerm(TreeUnpickler.scala:1313) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readLengthTerm$1(TreeUnpickler.scala:1158) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.readTerm(TreeUnpickler.scala:1313) at dotty.tools.dotc.core.tasty.TreeUnpickler$TreeReader.createMemberSymbol$$anonfun$1(TreeUnpickler.scala:614) at dotty.tools.dotc.core.Annotations$.dotty$tools$dotc$core$Annotations$$anon$2$$_$$lessinit$greater$$anonfun$1(Annotations.scala:162) at dotty.tools.dotc.core.Annotations$LazyBodyAnnotation.tree(Annotations.scala:150) at dotty.tools.dotc.typer.Inliner$.bodyToInline(Inliner.scala:53) at dotty.tools.dotc.typer.Inliner$.inlineCall(Inliner.scala:157) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:70) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform$$anonfun$1(Trees.scala:1494) at scala.collection.immutable.List.mapConserve(List.scala:472) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1494) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1388) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:73) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:78) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformBlock$$anonfun$1$$anonfun$1(tpd.scala:1211) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1193) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1206) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformBlock(tpd.scala:1211) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1402) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:49) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:66) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:56) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:64) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1206) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1206) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1208) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:64) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:64) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1465) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:73) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:64) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1206) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1206) at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1208) at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1476) at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:73) at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:78) at dotty.tools.dotc.transform.Inlining$$anon$2.transform(Inlining.scala:56) at dotty.tools.dotc.transform.MacroTransform.run(MacroTransform.scala:18) at dotty.tools.dotc.transform.Inlining.run(Inlining.scala:28) --- tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala | 6 ++++++ tests/run/backwardsCompat-implicitParens/Test_2.scala | 5 +++++ 2 files changed, 11 insertions(+) create mode 100644 tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala create mode 100644 tests/run/backwardsCompat-implicitParens/Test_2.scala diff --git a/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala new file mode 100644 index 000000000000..7d8462ccad04 --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala @@ -0,0 +1,6 @@ +class Bar(using x: Int)(y: String) +object Bar: + given Int = 1 + inline def foo = + println(new Bar()("")) + println(Bar()("")) diff --git a/tests/run/backwardsCompat-implicitParens/Test_2.scala b/tests/run/backwardsCompat-implicitParens/Test_2.scala new file mode 100644 index 000000000000..ae2acba2835b --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens/Test_2.scala @@ -0,0 +1,5 @@ +@main def Test = + given Int = 1 + println(new Bar("")) + println(Bar("")) + println(Bar.foo) From 6e9be57fe3ef68a6e740f739eff50d24382f2eef Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 5 Apr 2022 15:49:14 +0200 Subject: [PATCH 017/121] Fix unpickler logic and beef up test --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 53 +++++++++++++------ .../run/backwardsCompat-implicitParens.check | 20 +++++++ .../A_1_c3.0.2.scala | 28 +++++++++- .../Test_2.scala | 14 +++++ 4 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 tests/run/backwardsCompat-implicitParens.check diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cd45baf26b72..15cfc656f222 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1125,6 +1125,36 @@ class TreeUnpickler(reader: TastyReader, readPathTerm() } + /** Adapt constructor calls where class has only using clauses from old to new scheme. + * or class has mixed using clauses and other clauses. + * Old: leading (), new: nothing, or trailing () if all clauses are using clauses. + * This is neccessary so that we can read pre-3.2 Tasty correctly. There, + * constructor calls use the old scheme, but constructor definitions already + * use the new scheme, since they are reconstituted with normalizeIfConstructor. + */ + def constructorApply(fn: Tree, args: List[Tree]): Tree = + if fn.tpe.widen.isContextualMethod && args.isEmpty then + fn.withAttachment(SuppressedApplyToNone, ()) + else + val fn1 = fn match + case Apply(fn1, Nil) if fn.removeAttachment(InsertedApplyToNone).isDefined => + // We thought we inserted a final `()` but hit a user-written `()` instead. + // Remove the inserted `()`. + fn1 + case _ => + fn + val res = tpd.Apply(fn1, args) + if fn.removeAttachment(SuppressedApplyToNone).isEmpty then + res + else res.tpe.widen match + case mt @ MethodType(params) => + if params.isEmpty then + // Assume it's the final synthesized `()` parameter + res.appliedToNone.withAttachment(InsertedApplyToNone, ()) + else if mt.isContextualMethod then + res.withAttachment(SuppressedApplyToNone, ()) + else res + def readLengthTerm(): Tree = { val end = readEnd() val result = @@ -1136,22 +1166,8 @@ class TreeUnpickler(reader: TastyReader, case APPLY => val fn = readTerm() val args = until(end)(readTerm()) - // Adapt constructor calls where class has only using clauses from old to new scheme. - // Old: leading (), new: trailing (). - // This is neccessary so that we can read pre-3.2 Tasty correctly. There, - // constructor calls use the old scheme, but constructor definitions already - // use the new scheme, since they are reconstituted with normalizeIfConstructor. - if fn.symbol.isConstructor && fn.tpe.widen.isContextualMethod && args.isEmpty then - fn.withAttachment(SuppressedApplyToNone, ()) - else - val res = tpd.Apply(fn, args) - if fn.removeAttachment(SuppressedApplyToNone).isEmpty then - res - else res.tpe.widen match - case MethodType(Nil) => - res.appliedToNone - case mt: MethodType if mt.isContextualMethod => - res.withAttachment(SuppressedApplyToNone, ()) + if fn.symbol.isConstructor then constructorApply(fn, args) + else tpd.Apply(fn, args) case TYPEAPPLY => tpd.TypeApply(readTerm(), until(end)(readTpt())) case TYPED => @@ -1544,4 +1560,9 @@ object TreeUnpickler { * call that has otherwise only using clauses was suppressed. */ val SuppressedApplyToNone: Property.Key[Unit] = Property.Key() + + /** An attachment key indicating that an trailing () in a constructor + * call that has otherwise only using clauses was inserted. + */ + val InsertedApplyToNone: Property.Key[Unit] = Property.Key() } diff --git a/tests/run/backwardsCompat-implicitParens.check b/tests/run/backwardsCompat-implicitParens.check new file mode 100644 index 000000000000..1a471019f925 --- /dev/null +++ b/tests/run/backwardsCompat-implicitParens.check @@ -0,0 +1,20 @@ +Bar +Bar +Bar +Bar +() +Bat +Bat +Bat +Bat +() +Bax +Bax +Bax +Bax +() +Baz +Baz +Baz +Baz +() diff --git a/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala index 7d8462ccad04..77a77baf86e6 100644 --- a/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala +++ b/tests/run/backwardsCompat-implicitParens/A_1_c3.0.2.scala @@ -1,6 +1,32 @@ -class Bar(using x: Int)(y: String) +class Bar(using x: Int)(y: String): + override def toString = "Bar" object Bar: given Int = 1 inline def foo = println(new Bar()("")) println(Bar()("")) + +class Bat(using x: Int): + override def toString = "Bat" +object Bat: + given Int = 1 + inline def foo = + println(new Bat()) + println(Bat()) + +class Bax(using x: Int)(): + override def toString = "Bax" +object Bax: + given Int = 1 + inline def foo = + println(new Bax()) + println(Bax()) + +class Baz(using x: Int)(using y: String): + override def toString = "Baz" +object Baz: + given Int = 1 + given String = "x" + inline def foo = + println(new Baz()) + println(Baz()) diff --git a/tests/run/backwardsCompat-implicitParens/Test_2.scala b/tests/run/backwardsCompat-implicitParens/Test_2.scala index ae2acba2835b..b22b5b411b0c 100644 --- a/tests/run/backwardsCompat-implicitParens/Test_2.scala +++ b/tests/run/backwardsCompat-implicitParens/Test_2.scala @@ -1,5 +1,19 @@ @main def Test = given Int = 1 + given String = "x" + println(new Bar("")) println(Bar("")) println(Bar.foo) + + println(new Bat()) + println(Bat()) + println(Bat.foo) + + println(new Bax()) + println(Bax()) + println(Bax.foo) + + println(new Baz()) + println(Baz()) + println(Baz.foo) From f6812a170841a525c19d1b8c59a938cf88fde606 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 6 Apr 2022 14:07:19 +0200 Subject: [PATCH 018/121] Fix isAbsType prediction in TreeUnpickler TreeUnpickler assumed that a type was an abstract type if its RHS was a TypeBounds tree. But TypeBounds trees also encode alias types, so this needs to be refined. Fixes #14858 --- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 24 ++++++++++++++----- tests/pos/i14858/A_2.scala | 3 +++ tests/pos/i14858/M_1.scala | 10 ++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 tests/pos/i14858/A_2.scala create mode 100644 tests/pos/i14858/M_1.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c71ad928a7d2..9cd882cfab37 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4206,7 +4206,7 @@ object Types { * This is the case if tycon is higher-kinded. This means * it is a subtype of a hk-lambda, but not a match alias. * (normal parameterized aliases are removed in `appliedTo`). - * Applications of hgher-kinded type constructors to wildcard arguments + * Applications of higher-kinded type constructors to wildcard arguments * are equivalent to existential types, which are not supported. */ def isUnreducibleWild(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 06dc4872c7f3..ced7557bb7c9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -500,16 +500,28 @@ class TreeUnpickler(reader: TastyReader, flags } - def isAbstractType(ttag: Int)(using Context): Boolean = nextUnsharedTag match { + def isAbstractType(name: Name)(using Context): Boolean = nextByte match + case SHAREDtype => + val lookAhead = fork + lookAhead.reader.readByte() + val sharedReader = forkAt(lookAhead.reader.readAddr()) + sharedReader.isAbstractType(name) case LAMBDAtpt => val rdr = fork rdr.reader.readByte() // tag rdr.reader.readNat() // length rdr.skipParams() // tparams - rdr.isAbstractType(rdr.nextUnsharedTag) - case TYPEBOUNDS | TYPEBOUNDStpt => true + rdr.isAbstractType(name) + case TYPEBOUNDS => + val rdr = fork + rdr.reader.readByte() // tag + val end = rdr.reader.readEnd() + rdr.skipTree() // alias, or lower bound + val res = !rdr.nothingButMods(end) + //if !res then println(i"NOT ABSTRACT $name, ${rdr.reader.nextByte}") + res + case TYPEBOUNDStpt => true case _ => false - } /** Create symbol of definition node and enter in symAtAddr map * @return the created symbol @@ -554,7 +566,7 @@ class TreeUnpickler(reader: TastyReader, if (tag == TYPEDEF || tag == TYPEPARAM) name = name.toTypeName skipParams() val ttag = nextUnsharedTag - val isAbsType = isAbstractType(ttag) + val isAbsType = isAbstractType(name) val isClass = ttag == TEMPLATE val templateStart = currentAddr skipTree() // tpt @@ -562,7 +574,7 @@ class TreeUnpickler(reader: TastyReader, val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() val (givenFlags, annotFns, privateWithin) = readModifiers(end) - pickling.println(i"creating symbol $name at $start with flags $givenFlags") + pickling.println(i"creating symbol $name at $start with flags ${givenFlags.flagsString}, isAbsType = $isAbsType, $ttag") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = if (flags.is(Module)) adjustModuleCompleter(completer, name) else completer diff --git a/tests/pos/i14858/A_2.scala b/tests/pos/i14858/A_2.scala new file mode 100644 index 000000000000..1335f4795508 --- /dev/null +++ b/tests/pos/i14858/A_2.scala @@ -0,0 +1,3 @@ +import p.* + +type NAME2 = C[?] \ No newline at end of file diff --git a/tests/pos/i14858/M_1.scala b/tests/pos/i14858/M_1.scala new file mode 100644 index 000000000000..6f431d0b523e --- /dev/null +++ b/tests/pos/i14858/M_1.scala @@ -0,0 +1,10 @@ +package p + +object M { + class C[N]() +} + +export M.* + +type CC[N] = M.C[N] +type CCC = M.C[Int] From df39d8f743c51ef57967225e359e497c1402ddb2 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 7 Apr 2022 15:09:35 +0200 Subject: [PATCH 019/121] Clarify and test rules for newline suppression Clarify and test rules for when to suppress a newline insertion based on indentation. Fixes #12554 --- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- .../dotty/tools/dotc/parsing/Scanners.scala | 27 +++++++++++---- .../other-new-features/indentation.md | 34 ++++++++++++++++++- tests/neg/i12554a.scala | 32 +++++++++++++++++ tests/neg/i12554b.scala | 30 ++++++++++++++++ 5 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 tests/neg/i12554a.scala create mode 100644 tests/neg/i12554b.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 16926ecb72b8..c0a6cc942e0d 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -172,7 +172,7 @@ object Parsers { class Parser(source: SourceFile)(using Context) extends ParserCommon(source) { val in: Scanner = new Scanner(source) - //in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting + // in.debugTokenStream = true // uncomment to see the token stream of the standard scanner, but not syntax highlighting /** This is the general parse entry point. * Overridden by ScriptParser diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ca9346112862..0718cc4b8748 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -469,12 +469,6 @@ object Scanners { true } - def isContinuing(lastToken: Token) = - (openParensTokens.contains(token) || lastToken == RETURN) - && !pastBlankLine - && !migrateTo3 - && !noindentSyntax - /** The indentation width of the given offset. */ def indentWidth(offset: Offset): IndentWidth = import IndentWidth.{Run, Conc} @@ -565,6 +559,25 @@ object Scanners { handler(r) case _ => + /** Is this line seen as a continuation of last line? We assume that + * - last line ended in a token that can end a statement + * - current line starts with a token that can start a statement + * - current line does not start with a leading infix operator + * The answer is different for Scala-2 and Scala-3. + * - In Scala 2: Only `{` is treated as continuing, irrespective of indentation. + * But this is in fact handled by Parser.argumentStart which skips a NEWLINE, + * so we always assume false here. + * - In Scala 3: Only indented statements are treated as continuing, as long as + * they start with `(`, `[` or `{`, or the last statement ends in a `return`. + * The Scala 2 rules apply under source `3.0-migration` or under `-no-indent`. + */ + def isContinuing = + lastWidth < nextWidth + && (openParensTokens.contains(token) || lastToken == RETURN) + && !pastBlankLine + && !migrateTo3 + && !noindentSyntax + currentRegion match case r: Indented => indentIsSignificant = indentSyntax @@ -582,7 +595,7 @@ object Scanners { && canEndStatTokens.contains(lastToken) && canStartStatTokens.contains(token) && !isLeadingInfixOperator(nextWidth) - && !(lastWidth < nextWidth && isContinuing(lastToken)) + && !isContinuing then insert(if (pastBlankLine) NEWLINES else NEWLINE, lineOffset) else if indentIsSignificant then diff --git a/docs/_docs/reference/other-new-features/indentation.md b/docs/_docs/reference/other-new-features/indentation.md index bc089d7eb0b4..e886cb916757 100644 --- a/docs/_docs/reference/other-new-features/indentation.md +++ b/docs/_docs/reference/other-new-features/indentation.md @@ -243,6 +243,38 @@ case 5 => print("V") println(".") ``` +### Using Indentation to Signal Statement Continuation + +Indentation is used in some situations to decide whether to insert a virtual semicolon between +two consecutive lines or to treat them as one statement. Virtual semicolon insertion is +suppressed if the second line is indented more relative to the first one, and either the second line +starts with "`(`", "`[`", or "`{`" or the first line ends with `return`. Examples: + +```scala +f(x + 1) + (2, 3) // equivalent to `f(x + 1)(2, 3)` + +g(x + 1) +(2, 3) // equivalent to `g(x + 1); (2, 3)` + +h(x + 1) + {} // equivalent to `h(x + 1){}` + +i(x + 1) +{} // equivalent to `i(x + 1); {}` + +if x < 0 then return + a + b // equivalent to `if x < 0 then return a + b` + +if x < 0 then return +println(a + b) // equivalent to `if x < 0 then return; println(a + b)` +``` +In Scala 2, a line starting with "`{`" always continues the function call on the preceding line, +irrespective of indentation, whereas a virtual semicolon is inserted in all other cases. +The Scala-2 behavior is retained under source `-no-indent` or `-source 3.0-migration`. + + + ### The End Marker Indentation-based syntax has many advantages over other conventions. But one possible problem is that it makes it hard to discern when a large indentation region ends, since there is no specific token that delineates the end. Braces are not much better since a brace by itself also contains no information about what region is closed. @@ -404,7 +436,7 @@ end IndentWidth ### Settings and Rewrites -Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-language:Scala2`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. +Significant indentation is enabled by default. It can be turned off by giving any of the options `-no-indent`, `-old-syntax` and `-source 3.0-migration`. If indentation is turned off, it is nevertheless checked that indentation conforms to the logical program structure as defined by braces. If that is not the case, the compiler issues a warning. The Scala 3 compiler can rewrite source code to indented code and back. When invoked with options `-rewrite -indent` it will rewrite braces to diff --git a/tests/neg/i12554a.scala b/tests/neg/i12554a.scala new file mode 100644 index 000000000000..3ec6725d5fa4 --- /dev/null +++ b/tests/neg/i12554a.scala @@ -0,0 +1,32 @@ +object Test { + def f(s: String) = s + + def g: (Int, Int) = { + f("Foo") + (1, 2) // error, ok in Scala 2 + } + def g2: (Int, Int) = { + f("Foo") + (1, 2) // ok, ok in Scala 2 + } + + def h: Unit = { + f("Foo") + {} // error, error in Scala 2 + } + + def i: Unit = { + f("Foo") + {} // ok, error in Scala 2 + } + + def j: Int = { + return // error, error in Scala 2 + 1 + 2 + } + + def k: Int = { + return // ok, error in Scala 2 + 1 + 2 + } +} diff --git a/tests/neg/i12554b.scala b/tests/neg/i12554b.scala new file mode 100644 index 000000000000..61c841c799dd --- /dev/null +++ b/tests/neg/i12554b.scala @@ -0,0 +1,30 @@ +import language.`3.0-migration` // behavior should be the same as for Scala-2 +object Test { + + def f(s: String) = s + + def g: (Int, Int) = { + f("Foo") + (1, 2) // ok + } + + def h: Unit = { + f("Foo") + {} // error + } + + def i: Unit = { + f("Foo") + {} // error + } + + def j: Int = { + return // error + 1 + 2 + } + + def k: Int = { + return // error + 1 + 2 + } +} \ No newline at end of file From e133e3ff0f0898149dc870643a1c77304583d884 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 7 Apr 2022 15:32:39 +0100 Subject: [PATCH 020/121] Call TreeNodeChecker.traverse directly Now that we're not running this on every `typed` invocation, the compilation unit tree isn't going to be a top-level TypeTree. --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 5313e3f53d0d..2b8bb9e34470 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -130,7 +130,7 @@ class TreeChecker extends Phase with SymTransformer { assert(ctx.typerState.constraint.domainLambdas.isEmpty, i"non-empty constraint at end of $fusedPhase: ${ctx.typerState.constraint}, ownedVars = ${ctx.typerState.ownedVars.toList}%, %") assertSelectWrapsNew(ctx.compilationUnit.tpdTree) - TreeNodeChecker.run(ctx.compilationUnit.tpdTree) + TreeNodeChecker.traverse(ctx.compilationUnit.tpdTree) } val checkingCtx = ctx @@ -651,7 +651,6 @@ object TreeChecker { /** Check that the tree only contains legal children trees */ object TreeNodeChecker extends untpd.TreeTraverser: import untpd._ - def run(tree: Tree)(using Context) = if !tree.isInstanceOf[TypeTree] then traverse(tree) def traverse(tree: Tree)(using Context) = tree match case t: TypeTree => assert(assertion = false, t) case t @ TypeApply(fun, _targs) => traverse(fun) From f7ab15b0e848b667823541a2ac949f31103f8d14 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 7 Apr 2022 15:33:01 +0100 Subject: [PATCH 021/121] Give TreeNodeChecker a slightly better error message --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 2b8bb9e34470..8326c386d354 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -652,7 +652,7 @@ object TreeChecker { object TreeNodeChecker extends untpd.TreeTraverser: import untpd._ def traverse(tree: Tree)(using Context) = tree match - case t: TypeTree => assert(assertion = false, t) + case t: TypeTree => assert(assertion = false, i"TypeTree not expected: $t") case t @ TypeApply(fun, _targs) => traverse(fun) case t @ New(_tpt) => case t @ Typed(expr, _tpt) => traverse(expr) From 7af2560854fe66e403ffe553ad3af9efae984cf0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 7 Apr 2022 15:33:46 +0100 Subject: [PATCH 022/121] Check template parents in TreeNodeChecker. I'm not sure what I had hit that lead me to this exclusion, but I'm not hitting now.. --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 8326c386d354..be0a8420cdfd 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -661,7 +661,7 @@ object TreeChecker { case t @ ValDef(_, _tpt, _) => traverse(t.rhs) case t @ DefDef(_, paramss, _tpt, _) => for params <- paramss do traverse(params); traverse(t.rhs) case t @ TypeDef(_, _rhs) => - case t @ Template(constr, _parents, self, _) => traverse(constr); traverse(self); traverse(t.body) + case t @ Template(constr, parents, self, _) => traverse(constr); traverse(parents); traverse(self); traverse(t.body) case t => traverseChildren(t) end traverse } From 01329075452ac0002381d46621b99fcfcf2684ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 13:36:13 +0200 Subject: [PATCH 023/121] Remove isInfix method --- .../dotty/tools/scaladoc/tasty/SyntheticSupport.scala | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 735d9c74d733..a509127608bd 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -29,8 +29,6 @@ object SyntheticsSupport: import reflect._ s.flags.is(Flags.Opaque) - def isInfix: Boolean = hackIsInfix(s) - def getmembers: List[reflect.Symbol] = hackGetmembers(s) end extension @@ -50,14 +48,6 @@ object SyntheticsSupport: c.constructor.leadingTypeParams.nonEmpty && end <= typesEnd + 1 } - // TODO: #49 Remove it after TASTY-Reflect release with published flag Extension - private def hackIsInfix(using Quotes)(rsym: reflect.Symbol): Boolean = { - import reflect._ - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol] - ctx.definitions.isInfix(sym) - } /* We need there to filter out symbols with certain flagsets, because these symbols come from compiler and TASTY can't handle them well. They are valdefs that describe case companion objects and cases from enum. TASTY crashed when calling _.tree on them. From 0c77c36d38d6575171f8edcc89a1fdffb81aec03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 16:35:27 +0200 Subject: [PATCH 024/121] Remove Position existence check in Scaladoc --- .../scaladoc/tasty/ClassLikeSupport.scala | 9 ++- .../scaladoc/tasty/SyntheticSupport.scala | 14 +---- .../tools/scaladoc/tasty/TastyParser.scala | 56 +++++++++++-------- 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 7d5886193c6b..5acbfd440fe9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -96,7 +96,9 @@ trait ClassLikeSupport: getSupertypesGraph(LinkToType(selfSignature, classDef.symbol.dri, bareClasslikeKind(classDef.symbol)), unpackTreeToClassDef(classDef).parents) ) - val baseMember = mkMember(classDef.symbol, kindForClasslike(classDef), selfSignature)( + val kind = if intrinsicClassDefs.contains(classDef.symbol) then Kind.Class(Nil, Nil) else kindForClasslike(classDef) + + val baseMember = mkMember(classDef.symbol, kind, selfSignature)( modifiers = modifiers, graph = graph, deprecated = classDef.symbol.isDeprecated(), @@ -253,8 +255,9 @@ trait ClassLikeSupport: } def getParentsAsTreeSymbolTuples: List[(Tree, Symbol)] = - for - parentTree <- c.parents if isValidPos(parentTree.pos) // We assume here that order is correct + if noPosClassDefs.contains(c.symbol) then Nil + else for + parentTree <- c.parents if parentTree.pos.start != parentTree.pos.end // We assume here that order is correct parentSymbol = parentTree match case t: TypeTree => t.tpe.typeSymbol case tree if tree.symbol.isClassConstructor => tree.symbol.owner diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index a509127608bd..c21c6f259c13 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -33,15 +33,12 @@ object SyntheticsSupport: end extension - def isValidPos(using Quotes)(pos: reflect.Position) = - if hackExists(pos) then pos.start != pos.end else false - def isSyntheticField(using Quotes)(c: reflect.Symbol) = import reflect._ c.flags.is(Flags.CaseAccessor) || (c.flags.is(Flags.Module) && !c.flags.is(Flags.Given)) def constructorWithoutParamLists(using Quotes)(c: reflect.ClassDef): Boolean = - !isValidPos(c.constructor.pos) || { + c.constructor.pos.start == c.constructor.pos.end || { val end = c.constructor.pos.end val typesEnd = c.constructor.leadingTypeParams.lastOption.fold(end - 1)(_.pos.end) val classDefTree = c.constructor.show @@ -77,15 +74,6 @@ object SyntheticsSupport: baseTypes.asInstanceOf[List[(Symbol, TypeRepr)]] } - private def hackExists(using Quotes)(rpos: reflect.Position) = { - import reflect._ - import dotty.tools.dotc - import dotty.tools.dotc.util.Spans._ - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val pos = rpos.asInstanceOf[dotc.util.SourcePosition] - pos.exists - } - def getSupertypes(using Quotes)(c: reflect.ClassDef) = hackGetSupertypes(c).tail def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala index 76416c793b1d..b78dab1e28df 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala @@ -123,27 +123,17 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe topLevels ++= parser.parseRootTree(treeRoot) } - val defn = ctx.compilerContext.definitions - if ctx.args.documentSyntheticTypes then - val intrinsicClassDefs = Seq( - defn.AnyClass, - defn.MatchableClass, - defn.AnyKindClass, - defn.AnyValClass, - defn.NullClass, - defn.NothingClass, - defn.SingletonClass, - defn.andType, - defn.orType, - ).map { s => - "scala" -> s.asInstanceOf[parser.qctx.reflect.Symbol].tree.match { - case cd: parser.qctx.reflect.ClassDef => parser.parseClasslike(cd) - case td: parser.qctx.reflect.TypeDef => parser.parseTypeDef(td) - } + import parser.qctx.reflect._ + val intrinsicTypeDefs = parser.intrinsicTypeDefs.toSeq.map { s => + "scala" -> parser.parseTypeDef(s.tree.asInstanceOf[TypeDef]) + } + val intrinsicClassDefs = parser.intrinsicClassDefs.toSeq.map { s => + "scala" -> parser.parseClasslike(s.tree.asInstanceOf[ClassDef]) } topLevels ++= intrinsicClassDefs - val scalaPckg = defn.ScalaPackageVal.asInstanceOf[parser.qctx.reflect.Symbol] + topLevels ++= intrinsicTypeDefs + val scalaPckg = defn.ScalaPackage given parser.qctx.type = parser.qctx topLevels += "scala" -> Member(scalaPckg.fullName, scalaPckg.dri, Kind.Package) topLevels += mergeAnyRefAliasAndObject(parser) @@ -167,12 +157,13 @@ case class ScaladocTastyInspector()(using ctx: DocContext) extends DocTastyInspe }.toList -> rootDoc def mergeAnyRefAliasAndObject(parser: TastyParser) = - val defn = ctx.compilerContext.definitions - val oM = parser.parseClasslike(defn.ObjectClass.asInstanceOf[parser.qctx.reflect.Symbol].tree.asInstanceOf[parser.qctx.reflect.ClassDef]) - val aM = parser.parseTypeDef(defn.AnyRefAlias.asInstanceOf[parser.qctx.reflect.Symbol].tree.asInstanceOf[parser.qctx.reflect.TypeDef]) + import parser.qctx.reflect._ + val javaLangObjectDef = defn.ObjectClass.tree.asInstanceOf[ClassDef] + val objectMembers = parser.extractPatchedMembers(javaLangObjectDef) + val aM = parser.parseTypeDef(defn.AnyRefClass.tree.asInstanceOf[TypeDef]) "scala" -> aM.copy( - kind = oM.kind, - members = oM.members + kind = Kind.Class(Nil, Nil), + members = objectMembers ) /** Parses a single Tasty compilation unit. */ case class TastyParser( @@ -187,6 +178,25 @@ case class TastyParser( private given qctx.type = qctx + val intrinsicClassDefs = Set( + defn.AnyClass, + defn.MatchableClass, + defn.ScalaPackage.typeMember("AnyKind"), + defn.AnyValClass, + defn.NullClass, + defn.NothingClass, + defn.ScalaPackage.typeMember("Singleton"), + ) + + val noPosClassDefs = intrinsicClassDefs ++ Set( + defn.ObjectClass, + defn.AnyRefClass + ) + + val intrinsicTypeDefs = Set( + defn.ScalaPackage.typeMember("&"), + defn.ScalaPackage.typeMember("|"), + ) def processTree[T](tree: Tree)(op: => T): Option[T] = try Option(op) catch case e: Exception => report.warning(throwableToString(e), tree.pos) From c50ca10933eba19cc52d1e92310557dc3bab5e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 16:44:23 +0200 Subject: [PATCH 025/121] Remove method finding supertypes in compiler internals --- .../tools/scaladoc/tasty/SyntheticSupport.scala | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index c21c6f259c13..8fbabb8500b4 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -63,18 +63,8 @@ object SyntheticsSupport: }.toList } - private def hackGetSupertypes(using Quotes)(rdef: reflect.ClassDef) = { - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val classdef = rdef.asInstanceOf[dotc.ast.tpd.TypeDef] - val ref = classdef.symbol.info.asInstanceOf[dotc.core.Types.ClassInfo].appliedRef - val baseTypes: List[(dotc.core.Symbols.Symbol, dotc.core.Types.Type)] = - ref.baseClasses.map(b => b -> ref.baseType(b)) - baseTypes.asInstanceOf[List[(Symbol, TypeRepr)]] - } - - def getSupertypes(using Quotes)(c: reflect.ClassDef) = hackGetSupertypes(c).tail + def getSupertypes(using Quotes)(c: reflect.ClassDef) = + c.symbol.typeRef.baseClasses.map(b => b -> c.symbol.typeRef.baseType(b)).tail def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = import reflect._ From 632b0e47abd37ef5b9c839d8067b2641d786b9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 16:53:17 +0200 Subject: [PATCH 026/121] Remove internal API usage from typeForClass --- .../src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 8fbabb8500b4..76d5ca51c2fb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -67,11 +67,7 @@ object SyntheticsSupport: c.symbol.typeRef.baseClasses.map(b => b -> c.symbol.typeRef.baseType(b)).tail def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val cSym = c.symbol.asInstanceOf[dotc.core.Symbols.Symbol] - cSym.typeRef.appliedTo(cSym.typeParams.map(_.typeRef)).asInstanceOf[TypeRepr] + c.symbol.typeRef.appliedTo(c.symbol.typeMembers.filter(_.isTypeParam).map(_.typeRef)) def memberInfo(using Quotes)(c: reflect.ClassDef, symbol: reflect.Symbol): reflect.TypeRepr = import reflect._ From fc2bf60df3b723c635f7bae134fe2fcbc49b29e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 16:56:30 +0200 Subject: [PATCH 027/121] Move hacky methods at the end of file --- .../scaladoc/tasty/SyntheticSupport.scala | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 76d5ca51c2fb..1c4375ba8ab9 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -49,19 +49,6 @@ object SyntheticsSupport: They are valdefs that describe case companion objects and cases from enum. TASTY crashed when calling _.tree on them. */ - private def hackGetmembers(using Quotes)(rsym: reflect.Symbol): List[reflect.Symbol] = { - import reflect._ - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol] - sym.namedType.allMembers.iterator.map(_.symbol) - .collect { - case sym if - (!sym.is(dotc.core.Flags.ModuleVal) || sym.is(dotc.core.Flags.Given)) && - !sym.flags.isAllOf(dotc.core.Flags.Enum | dotc.core.Flags.Case | dotc.core.Flags.JavaStatic) => - sym.asInstanceOf[Symbol] - }.toList - } def getSupertypes(using Quotes)(c: reflect.ClassDef) = c.symbol.typeRef.baseClasses.map(b => b -> c.symbol.typeRef.baseType(b)).tail @@ -76,3 +63,17 @@ object SyntheticsSupport: typeForClass(c).asInstanceOf[dotc.core.Types.Type] .memberInfo(symbol.asInstanceOf[dotc.core.Symbols.Symbol]) .asInstanceOf[TypeRepr] + + private def hackGetmembers(using Quotes)(rsym: reflect.Symbol): List[reflect.Symbol] = { + import reflect._ + import dotty.tools.dotc + given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx + val sym = rsym.asInstanceOf[dotc.core.Symbols.Symbol] + sym.namedType.allMembers.iterator.map(_.symbol) + .collect { + case sym if + (!sym.is(dotc.core.Flags.ModuleVal) || sym.is(dotc.core.Flags.Given)) && + !sym.flags.isAllOf(dotc.core.Flags.Enum | dotc.core.Flags.Case | dotc.core.Flags.JavaStatic) => + sym.asInstanceOf[Symbol] + }.toList + } From 32dd051f79990fceb843c68dd4975d67a9c5cc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Fri, 8 Apr 2022 17:08:05 +0200 Subject: [PATCH 028/121] Remove isCompiletimeAppliedType method --- .../dotty/tools/scaladoc/tasty/SyntheticSupport.scala | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index 1c4375ba8ab9..f04ef6689767 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -5,17 +5,6 @@ import scala.quoted._ object SyntheticsSupport: - extension (using Quotes)(t: reflect.TypeRepr) - - def isCompiletimeAppliedType: Boolean = t.hackIsCompiletimeAppliedType(t) - - private def hackIsCompiletimeAppliedType(rtpe: reflect.TypeRepr): Boolean = - import dotty.tools.dotc - given ctx: dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - val tpe = rtpe.asInstanceOf[dotc.core.Types.Type] - ctx.definitions.isCompiletimeAppliedType(tpe.typeSymbol) - end extension - extension (using Quotes)(s: reflect.Symbol) def isSyntheticFunc: Boolean = import reflect._ From ff4c53f16788eea43cd77a8869866f66aa0532f2 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Apr 2022 18:37:18 +0200 Subject: [PATCH 029/121] Handle case where abstract overrides miss an implementation Fixes #11170 --- .../src/dotty/tools/dotc/core/NameOps.scala | 9 ++++++ .../tools/dotc/transform/ResolveSuper.scala | 7 +++-- tests/neg/i11170.check | 4 +++ tests/neg/i11170.scala | 8 ++++++ tests/neg/i11170a.scala | 28 +++++++++++++++++++ tests/pos/MathSpec.scala | 21 ++++++++++++++ 6 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i11170.check create mode 100644 tests/neg/i11170.scala create mode 100644 tests/neg/i11170a.scala create mode 100644 tests/pos/MathSpec.scala diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 08dd47af56c2..a19f35a9cd9a 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -361,5 +361,14 @@ object NameOps { case raw.BANG => UNARY_! case _ => name } + + /** If this is a super accessor name, its underlying name, which is the name + * of the method that the super accessor forwards to. + */ + def originalOfSuperAccessorName: TermName = name match + case SuperAccessorName(name1) => name1.originalOfSuperAccessorName + case ExpandedName(_, name1) => name1.originalOfSuperAccessorName + case ExpandPrefixName(_, name1) => name1.originalOfSuperAccessorName + case _ => name } } diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 47422512823e..621935df6bb8 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -118,7 +118,10 @@ object ResolveSuper { report.error(IllegalSuperAccessor(base, memberName, targetName, acc, accTp, other.symbol, otherTp), base.srcPos) bcs = bcs.tail } - assert(sym.exists, i"cannot rebind $acc, ${acc.targetName} $memberName") - sym + sym.orElse { + val originalName = acc.name.asTermName.originalOfSuperAccessorName + report.error(em"Member method ${originalName.debugString} of mixin ${acc.owner} is missing a concrete super implementation in $base.", base.srcPos) + acc + } } } diff --git a/tests/neg/i11170.check b/tests/neg/i11170.check new file mode 100644 index 000000000000..dc54df923e74 --- /dev/null +++ b/tests/neg/i11170.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i11170.scala:7:15 ---------------------------------------------------------------------------------- +7 |abstract class A extends U[String] // error + | ^ + | Member method foo of mixin trait U is missing a concrete super implementation in class A. diff --git a/tests/neg/i11170.scala b/tests/neg/i11170.scala new file mode 100644 index 000000000000..25a0a4df3926 --- /dev/null +++ b/tests/neg/i11170.scala @@ -0,0 +1,8 @@ +trait T[X] { + def foo(x: X): X +} +trait U[X] extends T[X] { + abstract override def foo(x: X): X = super.foo(x) +} +abstract class A extends U[String] // error + diff --git a/tests/neg/i11170a.scala b/tests/neg/i11170a.scala new file mode 100644 index 000000000000..5268c506f33f --- /dev/null +++ b/tests/neg/i11170a.scala @@ -0,0 +1,28 @@ +package apackage { + + abstract class A { + protected val x: Int + } + abstract class A2 { + val x: Int + } +} + +package bpackage { + import apackage._ + + trait B extends A { + println(x) + } + trait B2 extends A2 { + println(x) + } +} + +package cpackage { + import apackage._ + import bpackage._ + + case class C(override protected val x: Int) extends A with B // error + case class C2(override val x: Int) extends A2 with B2 +} \ No newline at end of file diff --git a/tests/pos/MathSpec.scala b/tests/pos/MathSpec.scala new file mode 100644 index 000000000000..7c0fdd8a1f3a --- /dev/null +++ b/tests/pos/MathSpec.scala @@ -0,0 +1,21 @@ +trait MathSig { + def sqrt(x: Double): Double +} + +trait MathSpec extends MathSig { + val epsilon = 0.00001 + abstract override def sqrt(x: Double) = { + require(x >= 0) + super.sqrt(x) + } ensuring { result => + (x * x - result).abs < epsilon + } +} + +trait MathImpl extends MathSig { + def sqrt(x: Double): Double = + ??? +} + +object Math extends MathImpl + with MathSpec \ No newline at end of file From 60d7d6cbdad86b0dbe1e4220cff5ca3c8694149c Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 8 Apr 2022 19:06:01 +0200 Subject: [PATCH 030/121] Treat RecTypes as potentially uncheckable Fixes #11097 --- .../dotty/tools/dotc/transform/TypeTestsCasts.scala | 1 + tests/neg-custom-args/fatal-warnings/i11097.scala | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/neg-custom-args/fatal-warnings/i11097.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index c2367e6bfde0..fe06e90340a7 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -151,6 +151,7 @@ object TypeTestsCasts { case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) case AnnotatedType(t, _) => recur(X, t) case tp2: RefinedType => recur(X, tp2.parent) && TypeComparer.hasMatchingMember(tp2.refinedName, X, tp2) + case tp2: RecType => recur(X, tp2.parent) case _ => true }) diff --git a/tests/neg-custom-args/fatal-warnings/i11097.scala b/tests/neg-custom-args/fatal-warnings/i11097.scala new file mode 100644 index 000000000000..763babff81e2 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i11097.scala @@ -0,0 +1,13 @@ +@main def test: Unit = { + class C { type T1; type T2 } + + def pmatch(s: C): s.T2 = s match { + case p: (C { type T1 = Int; type T2 >: T1 } & s.type) => // error + (3: p.T1): p.T2 + case p: (C { type T1 = String; type T2 >: T1 } & s.type) => // error + ("this branch should be matched": p.T1): p.T2 + } + + // ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String + val x = pmatch(new C { type T1 = String; type T2 = String }) +} \ No newline at end of file From 7ed0041e9eb2afc57936670007f46ac33c803cf2 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 9 Apr 2022 18:11:03 +0200 Subject: [PATCH 031/121] Avoid redundant type test for irrefutable @unchecked patterns Fixes #14896 --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- tests/pos/i14896.scala | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i14896.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index a43d32e591ee..d80d4f366636 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1378,7 +1378,7 @@ trait Applications extends Compatibility { val unapplyPatterns = bunchedArgs.lazyZip(argTypes) map (typed(_, _)) val result = assignType(cpy.UnApply(tree)(unapplyFn, unapplyImplicits(unapplyApp), unapplyPatterns), ownType) unapp.println(s"unapply patterns = $unapplyPatterns") - if ((ownType eq selType) || ownType.isError) result + if (ownType.stripped eq selType.stripped) || ownType.isError then result else tryWithTypeTest(Typed(result, TypeTree(ownType)), selType) case tp => val unapplyErr = if (tp.isError) unapplyFn else notAnExtractor(unapplyFn) diff --git a/tests/pos/i14896.scala b/tests/pos/i14896.scala new file mode 100644 index 000000000000..2ef595fbcaa4 --- /dev/null +++ b/tests/pos/i14896.scala @@ -0,0 +1,2 @@ +object Ex { def unapply(p: Any): Option[_ <: Int] = null } +object Foo { val Ex(_) = null: @unchecked } \ No newline at end of file From f70b899d26109abd92a2a456875b88d475f06473 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 9 Apr 2022 18:42:25 +0200 Subject: [PATCH 032/121] Avoid double @unchecked annotation Avoid double @unchecked annotation if selector is marked checkIrrefutable. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ac8d6152812e..380fcd7a5eeb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1599,7 +1599,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer result match { case result @ Match(sel, CaseDef(pat, _, _) :: _) => tree.selector.removeAttachment(desugar.CheckIrrefutable) match { - case Some(checkMode) => + case Some(checkMode) if !sel.tpe.hasAnnotation(defn.UncheckedAnnot) => val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration`) if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked") From b8df54405058b7c4133bd7becdbdce42e81bd809 Mon Sep 17 00:00:00 2001 From: Tom Grigg Date: Sat, 9 Apr 2022 22:04:58 -0700 Subject: [PATCH 033/121] Add regression test for Scala.js `js.Tuple2` example Since support for `isInstanceOf[Null]` is subject to change. --- .../testsuite/compiler/RegressionTestScala3.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala index 08aa26726dc7..9561c0866e7e 100644 --- a/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala +++ b/tests/sjs-junit/test/org/scalajs/testsuite/compiler/RegressionTestScala3.scala @@ -119,6 +119,16 @@ object RegressionTestScala3 { def bar(x: Long = 0): Foo = new Foo(x) } } + + object Issue14896 { + val obj = new js.Object { + val a = 42 + val b = "foo" + } + + val entries = js.Object.entries(obj) + val js.Tuple2(k, v) = entries(0): @unchecked + } } // This class needs to be at the top-level, not in an object, to reproduce the issue From 05f5a6f4deffb385c33225c540eef691a1df0d4a Mon Sep 17 00:00:00 2001 From: Tom Grigg Date: Sat, 9 Apr 2022 22:36:25 -0700 Subject: [PATCH 034/121] Add regression test for #14821 --- tests/pos/i14821.scala | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/pos/i14821.scala diff --git a/tests/pos/i14821.scala b/tests/pos/i14821.scala new file mode 100644 index 000000000000..9c007de53322 --- /dev/null +++ b/tests/pos/i14821.scala @@ -0,0 +1,29 @@ +trait Statement +trait Definition extends Statement + +trait ClassDef extends Definition: + def constructor: DefDef + +object ClassDef: + def copy(constr: DefDef): ClassDef = ??? + +// >>> This abstract implementation of DefDef causes a compilation error in transform... +type DefDef <: Definition +val DefDef: DefDefModule = ??? +trait DefDefModule: + def unapply(ddef: DefDef): (String, List[AnyRef]) +// ...unless this given TypeTest is commented out, in which case we get only a type test warning +given scala.reflect.TypeTest[Statement, DefDef] = ??? + +// >>> This alternative works +// trait DefDef extends Definition +// object DefDef: +// def unapply(ddef: DefDef): (String, List[AnyRef]) = ??? + +// >>> This alternative also works +// case class DefDef(name: String, paramss: List[AnyRef]) extends Definition + +def transform(tree: Statement): Statement = tree match + case tree: ClassDef => + val constructor @ DefDef(_, _) = transform(tree.constructor): @unchecked + ClassDef.copy(constructor) From 1af2f94b79251c49aec3e49222bad060067f0b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Mon, 11 Apr 2022 10:12:53 +0200 Subject: [PATCH 035/121] Remove memberInfo usage --- .../tools/scaladoc/tasty/ClassLikeSupport.scala | 4 ++-- .../tools/scaladoc/tasty/SyntheticSupport.scala | 17 ++++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 5acbfd440fe9..1e855e6e541a 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -485,7 +485,7 @@ trait ClassLikeSupport: def unwrapMemberInfo(c: ClassDef, symbol: Symbol): MemberInfo = - val baseTypeRepr = memberInfo(c, symbol) + val baseTypeRepr = typeForClass(c).memberType(symbol) def isSyntheticEvidence(name: String) = if !name.startsWith(NameKinds.EvidenceParamName.separator) then false else @@ -554,4 +554,4 @@ trait ClassLikeSupport: if parameters(0).symbol.flags.is(Flags.Given) then "using " else if parameters(0).symbol.flags.is(Flags.Implicit) then "implicit " else "" - else "" \ No newline at end of file + else "" diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala index f04ef6689767..8b98e40fd6b0 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/SyntheticSupport.scala @@ -34,25 +34,16 @@ object SyntheticsSupport: c.constructor.leadingTypeParams.nonEmpty && end <= typesEnd + 1 } - /* We need there to filter out symbols with certain flagsets, because these symbols come from compiler and TASTY can't handle them well. - They are valdefs that describe case companion objects and cases from enum. - TASTY crashed when calling _.tree on them. - */ - def getSupertypes(using Quotes)(c: reflect.ClassDef) = c.symbol.typeRef.baseClasses.map(b => b -> c.symbol.typeRef.baseType(b)).tail def typeForClass(using Quotes)(c: reflect.ClassDef): reflect.TypeRepr = c.symbol.typeRef.appliedTo(c.symbol.typeMembers.filter(_.isTypeParam).map(_.typeRef)) - def memberInfo(using Quotes)(c: reflect.ClassDef, symbol: reflect.Symbol): reflect.TypeRepr = - import reflect._ - import dotty.tools.dotc - given dotc.core.Contexts.Context = quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx - typeForClass(c).asInstanceOf[dotc.core.Types.Type] - .memberInfo(symbol.asInstanceOf[dotc.core.Symbols.Symbol]) - .asInstanceOf[TypeRepr] - + /* We need there to filter out symbols with certain flagsets, because these symbols come from compiler and TASTY can't handle them well. + They are valdefs that describe case companion objects and cases from enum. + TASTY crashed when calling _.tree on them. + */ private def hackGetmembers(using Quotes)(rsym: reflect.Symbol): List[reflect.Symbol] = { import reflect._ import dotty.tools.dotc From 02799ef7e316d4e9b4ad9b9e9770ad93c7c28139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 11 Apr 2022 16:55:59 +0200 Subject: [PATCH 036/121] Set development version to 3.2.0-RC1 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index 7006bb934c89..ea1dd641b002 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -59,7 +59,7 @@ object Build { val referenceVersion = "3.1.2" - val baseVersion = "3.1.3-RC1" + val baseVersion = "3.2.0-RC1" // Versions used by the vscode extension to create a new project // This should be the latest published releases. From dad8e3301db99f1931d26f4370b2d86f159ae622 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 11 Apr 2022 17:50:11 +0200 Subject: [PATCH 037/121] Allow multiple wildcard vals Allow multiple definitions of the form val _: T = e in a scope. Translate them by changing the `_` to fresh wildcardParamNames. --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 10 +++++++--- tests/run/wildcard-vals.scala | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/run/wildcard-vals.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index de0e78bcc38d..0cb2ed4d7acc 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -6,7 +6,7 @@ import core._ import util.Spans._, Types._, Contexts._, Constants._, Names._, NameOps._, Flags._ import Symbols._, StdNames._, Trees._, ContextOps._ import Decorators._, transform.SymUtils._ -import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName} +import NameKinds.{UniqueName, EvidenceParamName, DefaultGetterName, WildcardParamName} import typer.{Namer, Checking} import util.{Property, SourceFile, SourcePosition, Chars} import config.Feature.{sourceVersion, migrateTo3, enabled} @@ -1109,8 +1109,12 @@ object desugar { * ValDef or DefDef. */ def makePatDef(original: Tree, mods: Modifiers, pat: Tree, rhs: Tree)(using Context): Tree = pat match { - case IdPattern(named, tpt) => - derivedValDef(original, named, tpt, rhs, mods) + case IdPattern(id, tpt) => + val id1 = + if id.name == nme.WILDCARD + then cpy.Ident(id)(WildcardParamName.fresh()) + else id + derivedValDef(original, id1, tpt, rhs, mods) case _ => def filterWildcardGivenBinding(givenPat: Bind): Boolean = diff --git a/tests/run/wildcard-vals.scala b/tests/run/wildcard-vals.scala new file mode 100644 index 000000000000..e972a14c445d --- /dev/null +++ b/tests/run/wildcard-vals.scala @@ -0,0 +1,14 @@ +object O: + val _ = 1 + val _ = 2 + val _: Int = 3 + val _: Int = 4 + def x = 2 + +@main def Test = + assert(O.x == 2) + val _ = 1 + val _ = 2 + val _: Int = 3 + val _: Int = 4 + From 26b2265a7d50ff54bfd0c1094e51cfa56328bbde Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Apr 2022 11:00:59 +0200 Subject: [PATCH 038/121] Align PositionBridge#toString with Zinc pos.toString formats the position as an offset rather than a line Fixes https://github.com/sbt/zinc/issues/1089 --- sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java index 075efd5b53e6..d00c67b51a00 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java +++ b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java @@ -123,7 +123,11 @@ public Optional pointerSpace() { @Override public String toString() { - return pos.toString(); + Optional path = sourcePath(); + if (path.isPresent()) + return path.get() + ":" + line().orElse(-1).toString(); + else + return ""; } @Override From 998dcbb202643c05dec1383512cecbef3f9f7eb7 Mon Sep 17 00:00:00 2001 From: Adrien Piquerez Date: Tue, 12 Apr 2022 15:49:10 +0200 Subject: [PATCH 039/121] Add column in PositionBridge#toString --- sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java index d00c67b51a00..6b3c25e2e27c 100644 --- a/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java +++ b/sbt-bridge/src/dotty/tools/xsbt/PositionBridge.java @@ -123,11 +123,13 @@ public Optional pointerSpace() { @Override public String toString() { - Optional path = sourcePath(); - if (path.isPresent()) - return path.get() + ":" + line().orElse(-1).toString(); + String path = sourcePath().orElse(""); + Optional l = line(); + Integer column = pointer().orElse(1); + if (l.isPresent()) + return String.format("%s:%d:%d", path, l.get(), column); else - return ""; + return path; } @Override From f8a8b48297fdacbf886c3129c318dc869e617b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Tue, 12 Apr 2022 17:54:06 +0200 Subject: [PATCH 040/121] Remove excluded filters from mima --- project/MiMaFilters.scala | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 4c90fd39f361..ac68190d441d 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,32 +3,5 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - // Experimental APIs that can be added in 3.2.0 or later - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.typeArgs"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromProductTyped"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.deriving.Mirror.fromTuple"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), - - // TupledFunction - ProblemFilters.exclude[MissingClassProblem]("scala.runtime.TupledFunctions"), - ProblemFilters.exclude[MissingClassProblem]("scala.runtime.TupledFunctions$"), - ProblemFilters.exclude[MissingClassProblem]("scala.util.TupledFunction"), - ProblemFilters.exclude[MissingClassProblem]("scala.util.TupledFunction$"), - - // Private inner classes, but we emit all classes as public in Java bytecode - ProblemFilters.exclude[InaccessibleClassProblem]("scala.quoted.FromExpr$PrimitiveFromExpr"), - ProblemFilters.exclude[InaccessibleClassProblem]("scala.quoted.Type$ValueOf$"), - ProblemFilters.exclude[InaccessibleClassProblem]("scala.reflect.Selectable$DefaultSelectable"), ) } From 3b14fcab2196b947a39ed5ae664b4764f5cab1b7 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Apr 2022 10:09:48 +0200 Subject: [PATCH 041/121] Also hoist lifted arguments in super calls Super calls with default and named parameters can have lifted arguments in val defs preceding the constructor call. If these are complex, we need to hoist them out as well. Fixes #14164 --- .../tools/dotc/transform/HoistSuperArgs.scala | 14 ++++++++++++-- tests/run/i14164.scala | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/run/i14164.scala diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index 3233601310ae..d12c5ce6739d 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -173,13 +173,23 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase } } + /** Hoist super arg from a lifted parameter that gets evaluated before the call */ + def hoistSuperArgFromDef(paramDef: Tree, cdef: DefDef): Tree = paramDef match + case vdef: ValDef => + cpy.ValDef(vdef)(rhs = hoistSuperArg(vdef.rhs, cdef)) + case ddef: DefDef => + cpy.DefDef(ddef)(rhs = hoistSuperArg(ddef.rhs, cdef)) + case _ => + paramDef + /** Hoist complex arguments in super call out of the class. */ - def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match { + def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match + case Block(defs, expr) => + cpy.Block(superCall)(defs.mapconserve(hoistSuperArgFromDef(_, cdef)), hoistSuperArgsFromCall(expr, cdef)) case Apply(fn, args) => cpy.Apply(superCall)(hoistSuperArgsFromCall(fn, cdef), args.mapconserve(hoistSuperArg(_, cdef))) case _ => superCall - } /** Hoist complex arguments in this-constructor call of secondary constructor out of the class. */ def hoistSuperArgsFromConstr(stat: Tree): Tree = stat match { diff --git a/tests/run/i14164.scala b/tests/run/i14164.scala new file mode 100644 index 000000000000..fb340e303cd6 --- /dev/null +++ b/tests/run/i14164.scala @@ -0,0 +1,13 @@ +object Test: + class Base(a: String = "x", param: String) + + class Child extends Base( + param = + for x <- Seq("a") yield x + "param" + ) + + new Child + + def main(args: Array[String]) = () + From ea7cbc634538f2f310026f12c7a7974a8447fdfb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Apr 2022 14:44:53 +0200 Subject: [PATCH 042/121] Add regression test Closes #14001 --- tests/neg-macros/i14001/Macro_1.scala | 16 ++++++++++++++++ tests/neg-macros/i14001/Test_2.scala | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/neg-macros/i14001/Macro_1.scala create mode 100644 tests/neg-macros/i14001/Test_2.scala diff --git a/tests/neg-macros/i14001/Macro_1.scala b/tests/neg-macros/i14001/Macro_1.scala new file mode 100644 index 000000000000..d1d413b32b37 --- /dev/null +++ b/tests/neg-macros/i14001/Macro_1.scala @@ -0,0 +1,16 @@ +package test +import scala.quoted.* +object Y { + inline def testStuff[T]: T = + ${testStuffImpl} // problem: T inferred to Nothing + // solution add T explicitly + + def testStuffImpl[T: Type](using Quotes): Expr[T] = { + import quotes.reflect.* + + Apply( + Ref(Symbol.requiredMethod("test.A.C.apply")), + List(Literal(IntConstant(1))) + ).asExprOf[T] + } +} diff --git a/tests/neg-macros/i14001/Test_2.scala b/tests/neg-macros/i14001/Test_2.scala new file mode 100644 index 000000000000..a6a613edf8ef --- /dev/null +++ b/tests/neg-macros/i14001/Test_2.scala @@ -0,0 +1,10 @@ +package test +trait B { + case class C(i: Int) +} + +object A extends B + +object X { + val z = Y.testStuff[A.C] // error: Expr cast exception +} From 02654397c735490276d648868dd3330f08206bd8 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Apr 2022 14:59:41 +0200 Subject: [PATCH 043/121] Take lifted arguments into accountfor supercall argumemts Since we never housted arguments if the supercall had lifted parameters, we did not exercise that case before. Some fixes were necessary to make it work. --- .../tools/dotc/transform/HoistSuperArgs.scala | 68 +++++++++++-------- tests/run/i14164.scala | 14 ++++ 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index d12c5ce6739d..565e39e40369 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -60,11 +60,12 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase /** If argument is complex, hoist it out into its own method and refer to the * method instead. - * @param arg The argument that might be hoisted - * @param cdef The definition of the constructor from which the call is made + * @param arg The argument that might be hoisted + * @param cdef The definition of the constructor from which the call is made + * @param lifted Argument definitions that were lifted out in a call prefix * @return The argument after possible hoisting */ - private def hoistSuperArg(arg: Tree, cdef: DefDef): Tree = { + private def hoistSuperArg(arg: Tree, cdef: DefDef, lifted: List[Symbol]): Tree = { val constr = cdef.symbol lazy val origParams = // The parameters that can be accessed in the supercall if (constr == cls.primaryConstructor) @@ -92,11 +93,14 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase val argTypeWrtConstr = argType.widenTermRefExpr.subst(origParams, allParamRefs(constr.info)) // argType with references to paramRefs of the primary constructor instead of // local parameter accessors + val abstractedArgType = + if lifted.isEmpty then argTypeWrtConstr + else MethodType.fromSymbols(lifted, argTypeWrtConstr) newSymbol( owner = methOwner, name = SuperArgName.fresh(cls.name.toTermName), flags = Synthetic | Private | Method | staticFlag, - info = replaceResult(constr.info, argTypeWrtConstr), + info = replaceResult(constr.info, abstractedArgType), coord = constr.coord ).enteredAfter(thisPhase) } @@ -122,6 +126,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase case ntp: NamedType => val owner = ntp.symbol.maybeOwner (owner == cls || owner == constr) && ntp.symbol.isParamOrAccessor + || lifted.contains(ntp.symbol) case _ => false } @@ -134,7 +139,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase if pref.isType then pref.tpe.typeSymbol else pref.symbol) val tmap = new TreeTypeMap( typeMap = new TypeMap { - lazy val origToParam = origParams.zip(paramSyms).toMap + lazy val origToParam = (origParams ::: lifted).zip(paramSyms).toMap def apply(tp: Type) = tp match { case tp: NamedType if needsRewire(tp) => origToParam.get(tp.symbol) match { @@ -164,45 +169,52 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase Nil } val (typeParams, termParams) = origParams.span(_.isType) - val res = ref(superMeth) + var res = ref(superMeth) .appliedToTypes(typeParams.map(_.typeRef)) .appliedToArgss(termParamRefs(constr.info, termParams)) + if lifted.nonEmpty then + res = res.appliedToArgs(lifted.map(ref)) report.log(i"hoist $arg, cls = $cls = $res") res case _ => arg } } - /** Hoist super arg from a lifted parameter that gets evaluated before the call */ - def hoistSuperArgFromDef(paramDef: Tree, cdef: DefDef): Tree = paramDef match - case vdef: ValDef => - cpy.ValDef(vdef)(rhs = hoistSuperArg(vdef.rhs, cdef)) - case ddef: DefDef => - cpy.DefDef(ddef)(rhs = hoistSuperArg(ddef.rhs, cdef)) - case _ => - paramDef - /** Hoist complex arguments in super call out of the class. */ - def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef): Tree = superCall match + def hoistSuperArgsFromCall(superCall: Tree, cdef: DefDef, lifted: mutable.ListBuffer[Symbol]): Tree = superCall match case Block(defs, expr) => - cpy.Block(superCall)(defs.mapconserve(hoistSuperArgFromDef(_, cdef)), hoistSuperArgsFromCall(expr, cdef)) + cpy.Block(superCall)( + stats = defs.mapconserve { + case vdef: ValDef => + try cpy.ValDef(vdef)(rhs = hoistSuperArg(vdef.rhs, cdef, lifted.toList)) + finally lifted += vdef.symbol + case ddef: DefDef => + try cpy.DefDef(ddef)(rhs = hoistSuperArg(ddef.rhs, cdef, lifted.toList)) + finally lifted += ddef.symbol + case stat => + stat + }, + expr = hoistSuperArgsFromCall(expr, cdef, lifted)) case Apply(fn, args) => - cpy.Apply(superCall)(hoistSuperArgsFromCall(fn, cdef), args.mapconserve(hoistSuperArg(_, cdef))) + cpy.Apply(superCall)( + hoistSuperArgsFromCall(fn, cdef, lifted), + args.mapconserve(hoistSuperArg(_, cdef, lifted.toList))) case _ => superCall /** Hoist complex arguments in this-constructor call of secondary constructor out of the class. */ def hoistSuperArgsFromConstr(stat: Tree): Tree = stat match { - case stat: DefDef if stat.symbol.isClassConstructor => - cpy.DefDef(stat)(rhs = - stat.rhs match { - case Block(superCall :: stats, expr) => - val superCall1 = hoistSuperArgsFromCall(superCall, stat) - if (superCall1 eq superCall) stat.rhs - else cpy.Block(stat.rhs)(superCall1 :: stats, expr) + case constr: DefDef if constr.symbol.isClassConstructor => + val lifted = new mutable.ListBuffer[Symbol] + cpy.DefDef(constr)(rhs = + constr.rhs match + case Block(stats @ (superCall :: stats1), expr: Literal) => + cpy.Block(constr.rhs)( + stats.derivedCons(hoistSuperArgsFromCall(superCall, constr, lifted), stats1), + expr) case _ => - hoistSuperArgsFromCall(stat.rhs, stat) - }) + hoistSuperArgsFromCall(constr.rhs, constr, lifted) + ) case _ => stat } @@ -212,7 +224,7 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase tdef.rhs match { case impl @ Template(cdef, superCall :: others, _, _) => val hoist = new Hoister(tdef.symbol) - val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef) + val hoistedSuperCall = hoist.hoistSuperArgsFromCall(superCall, cdef, new mutable.ListBuffer) val hoistedBody = impl.body.mapconserve(hoist.hoistSuperArgsFromConstr) if (hoist.superArgDefs.isEmpty) tdef else { diff --git a/tests/run/i14164.scala b/tests/run/i14164.scala index fb340e303cd6..2a8476625634 100644 --- a/tests/run/i14164.scala +++ b/tests/run/i14164.scala @@ -1,3 +1,4 @@ + object Test: class Base(a: String = "x", param: String) @@ -11,3 +12,16 @@ object Test: def main(args: Array[String]) = () +end Test + +class Test2: + class Inner(withDefault: String = "inner")( + dependentDefault: String = withDefault) extends Object { + def this(x: Int) = this(x.toString)() + } + +class Test3: + class Inner(withDefault: () => String = () => "inner")( + dependentDefault: String = withDefault()) extends Object { + def this(x: Int) = this(() => x.toString)() + } From 8b87a42f8da36b07f4ab2f7ac80c7bd07d6dcced Mon Sep 17 00:00:00 2001 From: Philippus Date: Wed, 13 Apr 2022 15:33:33 +0200 Subject: [PATCH 044/121] Update asm to 9.3 --- compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala | 1 + compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 2 +- project/Build.scala | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 77a0bf36397e..b63a080dcead 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -53,6 +53,7 @@ trait BCodeIdiomatic { case "16" => asm.Opcodes.V16 case "17" => asm.Opcodes.V17 case "18" => asm.Opcodes.V18 + case "19" => asm.Opcodes.V19 } lazy val majorVersion: Int = (classfileVersion & 0xFF) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b163acb1fa9..d94d2568a56b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -16,7 +16,7 @@ class ScalaSettings extends SettingGroup with AllScalaSettings object ScalaSettings: // Keep synchronized with `classfileVersion` in `BCodeIdiomatic` private val minTargetVersion = 8 - private val maxTargetVersion = 18 + private val maxTargetVersion = 19 def supportedTargetVersions: List[String] = (minTargetVersion to maxTargetVersion).toList.map(_.toString) diff --git a/project/Build.scala b/project/Build.scala index 3a4480dd84e3..7ac172161d10 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -519,7 +519,7 @@ object Build { // get libraries onboard libraryDependencies ++= Seq( - "org.scala-lang.modules" % "scala-asm" % "9.2.0-scala-1", // used by the backend + "org.scala-lang.modules" % "scala-asm" % "9.3.0-scala-1", // used by the backend Dependencies.oldCompilerInterface, // we stick to the old version to avoid deprecation warnings "org.jline" % "jline-reader" % "3.19.0", // used by the REPL "org.jline" % "jline-terminal" % "3.19.0", From dc52f844bb01079b8c3512e377dfa19754f40752 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 13 Apr 2022 15:45:37 +0200 Subject: [PATCH 045/121] Don't always hoist super arguments referring to outer classes --- compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala index 565e39e40369..edbfbd1552c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala +++ b/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala @@ -107,19 +107,18 @@ class HoistSuperArgs extends MiniPhase with IdentityDenotTransformer { thisPhase /** Type of a reference implies that it needs to be hoisted */ def refNeedsHoist(tp: Type): Boolean = tp match { - case tp: ThisType => !tp.cls.isStaticOwner && tp.cls != cls + case tp: ThisType => !tp.cls.isStaticOwner && !cls.isContainedIn(tp.cls) case tp: TermRef => refNeedsHoist(tp.prefix) case _ => false } /** Super call argument is complex, needs to be hoisted */ - def needsHoist(tree: Tree) = tree match { + def needsHoist(tree: Tree) = tree match case _: DefDef => true case _: Template => true case _: New => !tree.tpe.typeSymbol.isStatic case _: RefTree | _: This => refNeedsHoist(tree.tpe) case _ => false - } /** Only rewire types that are owned by the current Hoister and is an param or accessor */ def needsRewire(tp: Type) = tp match { From c690d40ca3b2b9f6ce739234540650a9a9d5979c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 13 Apr 2022 16:41:33 +0200 Subject: [PATCH 046/121] Revert "Revert "Upgrade to Scala.js 1.9.0."" --- .../dotty/tools/backend/sjs/JSCodeGen.scala | 9 +++- .../dotc/transform/sjs/PrepJSInterop.scala | 33 +++++++++++--- project/plugins.sbt | 2 +- .../native-load-spec-need-explicit-name.check | 44 ++++++++++++++++++- .../native-load-spec-need-explicit-name.scala | 44 +++++++++++++++++-- 5 files changed, 118 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 0462ed9d08d5..45254b1e2325 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -4558,7 +4558,14 @@ class JSCodeGen()(using genCtx: Context) { val module = annot.argumentConstantString(0).getOrElse { unexpected("could not read the module argument as a string literal") } - val path = annot.argumentConstantString(1).fold[List[String]](Nil)(parsePath) + val path = annot.argumentConstantString(1).fold { + if (annot.arguments.sizeIs < 2) + parsePath(sym.defaultJSName) + else + Nil + } { pathName => + parsePath(pathName) + } val importSpec = Import(module, path) annot.argumentConstantString(2).fold[js.JSNativeLoadSpec] { importSpec diff --git a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala index 2d9934d5526c..97bdc138f571 100644 --- a/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala +++ b/compiler/src/dotty/tools/dotc/transform/sjs/PrepJSInterop.scala @@ -14,7 +14,8 @@ import Contexts._ import Decorators._ import DenotTransformers._ import Flags._ -import NameKinds.DefaultGetterName +import NameKinds.{DefaultGetterName, ModuleClassName} +import NameOps._ import StdNames._ import Symbols._ import SymUtils._ @@ -559,9 +560,14 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP case Some(annot) if annot.symbol == jsdefn.JSGlobalAnnot => checkJSGlobalLiteral(annot) val pathName = annot.argumentConstantString(0).getOrElse { - if ((enclosingOwner is OwnerKind.ScalaMod) && !sym.owner.isPackageObject) { + val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName + if (symTermName == nme.apply) { report.error( - "Native JS members inside non-native objects must have an explicit name in @JSGlobal", + "Native JS definitions named 'apply' must have an explicit name in @JSGlobal", + annot.tree) + } else if (symTermName.isSetterName) { + report.error( + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal", annot.tree) } sym.defaultJSName @@ -570,6 +576,18 @@ class PrepJSInterop extends MacroTransform with IdentityDenotTransformer { thisP case Some(annot) if annot.symbol == jsdefn.JSImportAnnot => checkJSImportLiteral(annot) + if (annot.arguments.sizeIs < 2) { + val symTermName = sym.name.exclude(NameKinds.ModuleClassName).toTermName + if (symTermName == nme.apply) { + report.error( + "Native JS definitions named 'apply' must have an explicit name in @JSImport", + annot.tree) + } else if (symTermName.isSetterName) { + report.error( + "Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport", + annot.tree) + } + } annot.argumentConstantString(2).foreach { globalPathName => checkGlobalRefPath(globalPathName) } @@ -1107,18 +1125,19 @@ object PrepJSInterop { */ private def checkJSImportLiteral(annot: Annotation)(using Context): Unit = { val args = annot.arguments - assert(args.size == 2 || args.size == 3, - i"@JSImport annotation $annot does not have exactly 2 or 3 arguments") + val argCount = args.size + assert(argCount >= 1 && argCount <= 3, + i"@JSImport annotation $annot does not have between 1 and 3 arguments") val firstArgIsValid = annot.argumentConstantString(0).isDefined if (!firstArgIsValid) report.error("The first argument to @JSImport must be a literal string.", args.head) - val secondArgIsValid = annot.argumentConstantString(1).isDefined || args(1).symbol == jsdefn.JSImportNamespaceModule + val secondArgIsValid = argCount < 2 || annot.argumentConstantString(1).isDefined || args(1).symbol == jsdefn.JSImportNamespaceModule if (!secondArgIsValid) report.error("The second argument to @JSImport must be literal string or the JSImport.Namespace object.", args(1)) - val thirdArgIsValid = args.size < 3 || annot.argumentConstantString(2).isDefined + val thirdArgIsValid = argCount < 3 || annot.argumentConstantString(2).isDefined if (!thirdArgIsValid) report.error("The third argument to @JSImport, when present, must be a literal string.", args(2)) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 1716bb51b7f1..9d743cec8252 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,7 +2,7 @@ // // e.g. addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.9.0") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.10") diff --git a/tests/neg-scalajs/native-load-spec-need-explicit-name.check b/tests/neg-scalajs/native-load-spec-need-explicit-name.check index 9d91d4897d10..263acca9a2ca 100644 --- a/tests/neg-scalajs/native-load-spec-need-explicit-name.check +++ b/tests/neg-scalajs/native-load-spec-need-explicit-name.check @@ -1,8 +1,48 @@ -- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:6:2 ---------------------------------------------- 6 | @JSGlobal // error | ^^^^^^^^^ - | Native JS members inside non-native objects must have an explicit name in @JSGlobal + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal -- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:10:2 --------------------------------------------- 10 | @JSGlobal // error | ^^^^^^^^^ - | Native JS members inside non-native objects must have an explicit name in @JSGlobal + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:14:2 --------------------------------------------- +14 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:20:2 --------------------------------------------- +20 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:19:2 --------------------------------------------- +19 | @js.native // error + | ^^^^^^^^^^ + | @js.native is not allowed on vars, lazy vals and setter defs +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:24:2 --------------------------------------------- +24 | @JSGlobal // error + | ^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSGlobal +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:30:3 --------------------------------------------- +30 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:34:3 --------------------------------------------- +34 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:38:3 --------------------------------------------- +38 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:44:3 --------------------------------------------- +44 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions with a name ending in '_=' must have an explicit name in @JSImport +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:43:2 --------------------------------------------- +43 | @js.native // error + | ^^^^^^^^^^ + | @js.native is not allowed on vars, lazy vals and setter defs +-- Error: tests/neg-scalajs/native-load-spec-need-explicit-name.scala:48:3 --------------------------------------------- +48 | @JSImport("bar.js") // error + | ^^^^^^^^^^^^^^^^^^^ + | Native JS definitions named 'apply' must have an explicit name in @JSImport diff --git a/tests/neg-scalajs/native-load-spec-need-explicit-name.scala b/tests/neg-scalajs/native-load-spec-need-explicit-name.scala index fc08d414dd63..7235fa4e7a36 100644 --- a/tests/neg-scalajs/native-load-spec-need-explicit-name.scala +++ b/tests/neg-scalajs/native-load-spec-need-explicit-name.scala @@ -1,14 +1,52 @@ import scala.scalajs.js import scala.scalajs.js.annotation.* -object A { +object A1 { @js.native @JSGlobal // error - class B extends js.Object + class apply extends js.Object @js.native @JSGlobal // error - object C extends js.Object + object apply extends js.Object + + @js.native + @JSGlobal // error + class foo_= extends js.Object +} + +object A2 { + @js.native // error + @JSGlobal // error + def foo_=(x: Int): Unit = js.native + + @js.native + @JSGlobal // error + def apply(x: Int): Int = js.native +} + +object B1 { + @js.native + @JSImport("bar.js") // error + class apply extends js.Object + + @js.native + @JSImport("bar.js") // error + object apply extends js.Object + + @js.native + @JSImport("bar.js") // error + class foo_= extends js.Object +} + +object B2 { + @js.native // error + @JSImport("bar.js") // error + def foo_=(x: Int): Unit = js.native + + @js.native + @JSImport("bar.js") // error + def apply(x: Int): Int = js.native } // scala-js#2401 From da7e6c70a962290c6c9e20777653224c98e67a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 13 Apr 2022 17:53:07 +0200 Subject: [PATCH 047/121] Re-add mima exclusions for methods added to Quotes --- project/MiMaFilters.scala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index ac68190d441d..2c4fd4992432 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,5 +3,13 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( + + // Those are OK because user code is not allowed to inherit from Quotes: + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), ) } From 4bcad5665b716fadec1df8f9ac1ebc454fe3a375 Mon Sep 17 00:00:00 2001 From: Xavientois Date: Wed, 13 Apr 2022 12:26:18 -0400 Subject: [PATCH 048/121] Fix safe-init error in Trees.scala Fixes the following safe-init error that occurs during bootstrapping: ``` [error] -- Error: /***********/dotty/compiler/src/dotty/tools/dotc/ast/Trees.scala:815:22 [error] 815 | assert(isEmpty || tpt != genericEmptyTree[T]) [error] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ [error] |Call method ValDef.this.tpt.!=(dotty.tools.dotc.ast.Trees.genericEmptyTree[T]) on a value with an unknown initialization. Calling trace: [error] |-> @sharable val theEmptyValDef = new EmptyValDef[Type]() [ Trees.scala:970 ] [error] | ^^^^^^^^^^^^^^^^^^^^^^^ [error] |-> class EmptyValDef[T >: Untyped] extends ValDef[T]( [ Trees.scala:961 ] [error] | ^ [error] |-> case class ValDef[-T >: Untyped] private[ast] (name: TermName, tpt: Tree[T], private var preRhs: LazyTree[T @uncheckedVariance])(implicit @constructorOnly src: SourceFile) [ Trees.scala:812 ] [error] | ^ [error] |-> assert(isEmpty || tpt != genericEmptyTree[T]) [ Trees.scala:815 ] [error] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [error] |-> if !assertion then scala.runtime.Scala3RunTime.assertFailed() [ Predef.scala:10 ] ``` --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index daa1754f7883..cb20a7c9da37 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -812,7 +812,7 @@ object Trees { case class ValDef[-T >: Untyped] private[ast] (name: TermName, tpt: Tree[T], private var preRhs: LazyTree[T @uncheckedVariance])(implicit @constructorOnly src: SourceFile) extends ValOrDefDef[T], ValOrTypeDef[T] { type ThisTree[-T >: Untyped] = ValDef[T] - assert(isEmpty || tpt != genericEmptyTree) + assert(isEmpty || (tpt ne genericEmptyTree)) def unforced: LazyTree[T] = preRhs protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x } @@ -822,7 +822,7 @@ object Trees { paramss: List[ParamClause[T]], tpt: Tree[T], private var preRhs: LazyTree[T @uncheckedVariance])(implicit @constructorOnly src: SourceFile) extends ValOrDefDef[T] { type ThisTree[-T >: Untyped] = DefDef[T] - assert(tpt != genericEmptyTree) + assert(tpt ne genericEmptyTree) def unforced: LazyTree[T] = preRhs protected def force(x: Tree[T @uncheckedVariance]): Unit = preRhs = x From 28bc87eedf02f1353fbb0688880c6edbae661fc1 Mon Sep 17 00:00:00 2001 From: Chris Kipp Date: Fri, 4 Mar 2022 15:08:39 +0100 Subject: [PATCH 049/121] Add in initial support for code coverage. Co-Authored-By: Quentin Jaquier --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../tools/dotc/config/ScalaSettings.scala | 3 + .../dotty/tools/dotc/core/Definitions.scala | 3 + .../dotty/tools/dotc/coverage/Coverage.scala | 30 + .../dotty/tools/dotc/coverage/Location.scala | 38 + .../tools/dotc/coverage/Serializer.scala | 83 + .../transform/CoverageTransformMacro.scala | 204 ++ .../dotty/tools/dotc/typer/EtaExpansion.scala | 24 + .../tools/dotc/coverage/CoverageTests.scala | 75 + library/src/scala/runtime/Invoker.scala | 74 + project/Build.scala | 6 +- tests/coverage/expect/ContextFunctions.scala | 30 + tests/coverage/expect/Enum.scala | 42 + .../coverage/expect/MultiversalEquality.scala | 31 + .../coverage/expect/ParameterUntupling.scala | 19 + tests/coverage/expect/PatternMatching.scala | 87 + tests/coverage/expect/StructuralTypes.scala | 29 + tests/coverage/expect/TraitParams.scala | 22 + tests/coverage/expect/TypeLambdas.scala | 19 + tests/coverage/expect/UnionTypes.scala | 44 + tests/coverage/scoverage.coverage.expect | 3030 +++++++++++++++++ 21 files changed, 3892 insertions(+), 2 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/coverage/Coverage.scala create mode 100644 compiler/src/dotty/tools/dotc/coverage/Location.scala create mode 100644 compiler/src/dotty/tools/dotc/coverage/Serializer.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala create mode 100644 compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala create mode 100644 library/src/scala/runtime/Invoker.scala create mode 100644 tests/coverage/expect/ContextFunctions.scala create mode 100644 tests/coverage/expect/Enum.scala create mode 100644 tests/coverage/expect/MultiversalEquality.scala create mode 100644 tests/coverage/expect/ParameterUntupling.scala create mode 100644 tests/coverage/expect/PatternMatching.scala create mode 100644 tests/coverage/expect/StructuralTypes.scala create mode 100644 tests/coverage/expect/TraitParams.scala create mode 100644 tests/coverage/expect/TypeLambdas.scala create mode 100644 tests/coverage/expect/UnionTypes.scala create mode 100644 tests/coverage/scoverage.coverage.expect diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 44e28bcdd446..1c11267641fd 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -46,6 +46,7 @@ class Compiler { List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only) List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols + List(new CoverageTransformMacro) :: // Perform instrumentation for coverage transform (if -coverage is present) Nil /** Phases dealing with TASTY tree pickling and unpickling */ diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b163acb1fa9..10e9876db20f 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -116,6 +116,9 @@ trait CommonScalaSettings: val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes")) val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) + /* Coverage settings */ + val coverageOutputDir = PathSetting("-coverage", "Destination for coverage classfiles and instrumentation data.", "") + val coverageSourceroot = PathSetting("-coverage-sourceroot", "An alternative root dir of your sources used to relativize.", ".") /* Other settings */ val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index dc672690702c..b769c4251135 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -13,6 +13,7 @@ import typer.ImportInfo.RootRef import Comments.CommentsContext import Comments.Comment import util.Spans.NoSpan +import Symbols.requiredModuleRef import scala.annotation.tailrec @@ -460,6 +461,8 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef + @tu lazy val InvokerModuleRef = requiredMethodRef("scala.runtime.Invoker") + @tu lazy val ImplicitScrutineeTypeSym = newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala new file mode 100644 index 000000000000..b4365e1c4389 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc +package coverage + +import scala.collection.mutable + +class Coverage { + private val statementsById = mutable.Map[Int, Statement]() + + def statements = statementsById.values + + def addStatement(stmt: Statement): Unit = statementsById.put(stmt.id, stmt) +} + +case class Statement( + source: String, + location: Location, + id: Int, + start: Int, + end: Int, + line: Int, + desc: String, + symbolName: String, + treeName: String, + branch: Boolean, + var count: Int = 0, + ignored: Boolean = false +) { + def invoked(): Unit = count = count + 1 + def isInvoked = count > 0 +} diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala new file mode 100644 index 000000000000..47f712a49da5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -0,0 +1,38 @@ +package dotty.tools.dotc +package coverage + +import ast.tpd._ +import dotty.tools.dotc.core.Contexts.Context + +/** @param packageName + * the name of the encosing package + * @param className + * the name of the closes enclosing class + * @param fullClassName + * the fully qualified name of the closest enclosing class + */ +final case class Location( + packageName: String, + className: String, + fullClassName: String, + classType: String, + method: String, + sourcePath: String +) + +object Location { + def apply(tree: Tree)(using ctx: Context): Location = { + + val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString() + val className = ctx.owner.denot.enclosingClass.name.toSimpleName.toString() + + Location( + packageName, + className, + s"$packageName.$className", + "Class" /* TODO refine this further */, + ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), + ctx.source.file.absolute.toString() + ) + } +} diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala new file mode 100644 index 000000000000..43fe45774249 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -0,0 +1,83 @@ +package dotty.tools.dotc +package coverage + +import java.io._ + +import scala.io.Source + +object Serializer { + + val coverageFileName = "scoverage.coverage" + val coverageDataFormatVersion = "3.0" + // Write out coverage data to the given data directory, using the default coverage filename + def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit = + serialize(coverage, coverageFile(dataDir), new File(sourceRoot)) + + // Write out coverage data to given file. + def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = { + val writer = new BufferedWriter(new FileWriter(file)) + serialize(coverage, writer, sourceRoot) + writer.close() + } + + def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit = { + + def getRelativePath(filePath: String): String = { + val base = sourceRoot.getCanonicalFile().toPath() + val relPath = base.relativize(new File(filePath).getCanonicalFile().toPath()) + relPath.toString + } + + def writeHeader(writer: Writer): Unit = { + writer.write(s"""# Coverage data, format version: $coverageDataFormatVersion + |# Statement data: + |# - id + |# - source path + |# - package name + |# - class name + |# - class type (Class, Object or Trait) + |# - full class name + |# - method name + |# - start offset + |# - end offset + |# - line number + |# - symbol name + |# - tree name + |# - is branch + |# - invocations count + |# - is ignored + |# - description (can be multi-line) + |# '\f' sign + |# ------------------------------------------ + |""".stripMargin) + } + def writeStatement(stmt: Statement, writer: Writer): Unit = { + writer.write(s"""${stmt.id} + |${getRelativePath(stmt.location.sourcePath)} + |${stmt.location.packageName} + |${stmt.location.className} + |${stmt.location.classType} + |${stmt.location.fullClassName} + |${stmt.location.method} + |${stmt.start} + |${stmt.end} + |${stmt.line} + |${stmt.symbolName} + |${stmt.treeName} + |${stmt.branch} + |${stmt.count} + |${stmt.ignored} + |${stmt.desc} + |\f + |""".stripMargin) + } + + writeHeader(writer) + coverage.statements.toVector + .sortBy(_.id) + .foreach(stmt => writeStatement(stmt, writer)) + } + + def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath) + def coverageFile(dataDir: String): File = new File(dataDir, coverageFileName) +} diff --git a/compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala b/compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala new file mode 100644 index 000000000000..2d827dd25cac --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala @@ -0,0 +1,204 @@ +package dotty.tools.dotc +package transform + +import java.io.File +import java.util.concurrent.atomic.AtomicInteger + +import collection.mutable +import core.Flags.JavaDefined +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer +import dotty.tools.dotc.coverage.Coverage +import dotty.tools.dotc.coverage.Statement +import dotty.tools.dotc.coverage.Serializer +import dotty.tools.dotc.coverage.Location +import dotty.tools.dotc.core.Symbols.defn +import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Decorators.toTermName +import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.core.Constants.Constant +import dotty.tools.dotc.typer.LiftCoverage + +import scala.quoted + +/** Phase that implements code coverage, executed when the "-coverage + * OUTPUT_PATH" is added to the compilation. + */ +class CoverageTransformMacro extends MacroTransform with IdentityDenotTransformer { + import ast.tpd._ + + override def phaseName = "coverage" + + // Atomic counter used for assignation of IDs to difference statements + val statementId = new AtomicInteger(0) + + var outputPath = "" + + // Main class used to store all instrumented statements + val coverage = new Coverage + + override def run(using ctx: Context): Unit = { + + if (ctx.settings.coverageOutputDir.value.nonEmpty) { + outputPath = ctx.settings.coverageOutputDir.value + + // Ensure the dir exists + val dataDir = new File(outputPath) + val newlyCreated = dataDir.mkdirs() + + if (!newlyCreated) { + // If the directory existed before, let's clean it up. + dataDir.listFiles + .filter(_.getName.startsWith("scoverage")) + .foreach(_.delete) + } + + super.run + + + Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value) + } + } + + protected def newTransformer(using Context): Transformer = + new CoverageTransormer + + class CoverageTransormer extends Transformer { + var instrumented = false + + override def transform(tree: Tree)(using Context): Tree = { + tree match { + case tree: If => + cpy.If(tree)( + cond = transform(tree.cond), + thenp = instrument(transform(tree.thenp), branch = true), + elsep = instrument(transform(tree.elsep), branch = true) + ) + case tree: Try => + cpy.Try(tree)( + expr = instrument(transform(tree.expr), branch = true), + cases = instrumentCasees(tree.cases), + finalizer = instrument(transform(tree.finalizer), true) + ) + case Apply(fun, _) + if ( + fun.symbol.exists && + fun.symbol.isInstanceOf[Symbol] && + fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_|| + ) => + super.transform(tree) + case tree @ Apply(fun, args) if (fun.isInstanceOf[Apply]) => + // We have nested apply, we have to lift all arguments + // Example: def T(x:Int)(y:Int) + // T(f())(1) // should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} + liftApply(tree) + case tree: Apply => + if (LiftCoverage.needsLift(tree)) { + liftApply(tree) + } else { + super.transform(tree) + } + case Select(qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined)) => + //Java class can't be used as a value, we can't instrument the + //qualifier ({;System}.xyz() is not possible !) instrument it + //as it is + instrument(tree) + case tree: Select => + if (tree.qualifier.isInstanceOf[New]) { + instrument(tree) + } else { + cpy.Select(tree)(transform(tree.qualifier), tree.name) + } + case tree: CaseDef => instrumentCaseDef(tree) + + case tree: Literal => instrument(tree) + case tree: Ident if (isWildcardArg(tree)) => + // We don't want to instrument wildcard arguments. `var a = _` can't be instrumented + tree + case tree: New => instrument(tree) + case tree: This => instrument(tree) + case tree: Super => instrument(tree) + case tree: PackageDef => + // We don't instrument the pid of the package, but we do instrument the statements + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) + case tree: Assign => cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) + case tree: Template => + // Don't instrument the parents (extends) of a template since it + // causes problems if the parent constructor takes parameters + cpy.Template(tree)( + constr = super.transformSub(tree.constr), + body = transform(tree.body) + ) + case tree: Import => tree + // Catch EmptyTree since we can't match directly on it + case tree: Thicket if tree.isEmpty => tree + // For everything else just recurse and transform + case _ => + report.warning( + "Unmatched: " + tree.getClass + " " + tree.symbol, + tree.sourcePos + ) + super.transform(tree) + } + } + + def liftApply(tree: Apply)(using Context) = { + val buffer = mutable.ListBuffer[Tree]() + // NOTE: that if only one arg needs to be lifted, we just lift everything + val lifted = LiftCoverage.liftForCoverage(buffer, tree) + val instrumented = buffer.toList.map(transform) + //We can now instrument the apply as it is with a custom position to point to the function + Block( + instrumented, + instrument( + lifted, + tree.sourcePos, + false + ) + ) + } + + def instrumentCasees(cases: List[CaseDef])(using Context): List[CaseDef] = { + cases.map(instrumentCaseDef) + } + + def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = { + cpy.CaseDef(tree)(tree.pat, transform(tree.guard), transform(tree.body)) + } + + def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = { + instrument(tree, tree.sourcePos, branch) + } + + def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = { + if (pos.exists && !pos.span.isZeroExtent && !tree.isType) { + val id = statementId.incrementAndGet() + val statement = new Statement( + source = ctx.source.file.name, + location = Location(tree), + id = id, + start = pos.start, + end = pos.end, + line = ctx.source.offsetToLine(pos.point), + desc = tree.source.content.slice(pos.start, pos.end).mkString, + symbolName = tree.symbol.name.toSimpleName.toString(), + treeName = tree.getClass.getSimpleName, + branch + ) + coverage.addStatement(statement) + Block(List(invokeCall(id)), tree) + } else { + tree + } + } + + def invokeCall(id: Int)(using Context): Tree = { + ref(defn.InvokerModuleRef) + .select("invoked".toTermName) + .appliedToArgs( + List(Literal(Constant(id)), Literal(Constant(outputPath))) + ) + } + } + +} diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index c0c6ede200ed..ab36dd0395df 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -155,6 +155,30 @@ class LiftComplex extends Lifter { } object LiftComplex extends LiftComplex +/** Lift complex + lift the prefixes */ +object LiftCoverage extends LiftComplex { + + var liftEverything = false + + /** Return true if the apply needs a lift in the coverage phase + Return false if the args are empty, if one or more will be lifter by a + complex lifter. + */ + def needsLift(tree: tpd.Apply)(using Context): Boolean = + !tree.args.isEmpty && !tree.args.forall(super.noLift(_)) + + override def noLift(expr: tpd.Tree)(using Context) = + !liftEverything && super.noLift(expr) + + def liftForCoverage(defs: mutable.ListBuffer[tpd.Tree], tree: tpd.Apply)(using Context) = { + val liftedFun = liftApp(defs, tree.fun) + liftEverything = true + val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args) + liftEverything = false + tpd.cpy.Apply(tree)(liftedFun, liftedArgs) + } +} + object LiftErased extends LiftComplex: override def isErased = true diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala new file mode 100644 index 000000000000..9dde3b9e8221 --- /dev/null +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -0,0 +1,75 @@ +package dotty.tools.dotc.coverage + +import java.util.stream.Collectors +import org.junit.Assert._ +import dotty.BootstrappedOnlyTests +import org.junit.experimental.categories.Category +import org.junit.Test +import java.nio.file.Files +import dotty.tools.dotc.Main +import scala.jdk.CollectionConverters._ + +import java.io.File +import java.nio.file.Path +import java.nio.file.FileSystems +import java.nio.file.Paths +import java.nio.charset.StandardCharsets +import scala.io.Source +import java.io.BufferedOutputStream +import java.io.FileOutputStream + +@main def updateExpect = + CoverageTests().runExpectTest(updateExpectFiles = true) + +@Category(Array(classOf[BootstrappedOnlyTests])) +class CoverageTests { + + val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") + val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")) + val expectSrc = rootSrc.resolve("expect") + + @Category(Array(classOf[dotty.SlowTests])) + @Test def expectTests: Unit = + if (!scala.util.Properties.isWin) runExpectTest(updateExpectFiles = false) + + def runExpectTest(updateExpectFiles: Boolean): Unit = { + val target = generateCoverage(updateExpectFiles) + val input = Source.fromFile(new File(target.toString, "scoverage.coverage")) + val expectFile = new File(expectSrc.resolveSibling("scoverage.coverage.expect").toUri) + + if (updateExpectFiles) { + val outputStream = new BufferedOutputStream(new FileOutputStream(expectFile)) + try { + input.foreach(outputStream.write(_)) + } finally outputStream.close + } else { + val expected = new String(Files.readAllBytes(expectFile.toPath), StandardCharsets.UTF_8) + val obtained = input.mkString + + assertEquals(expected, obtained) + } + } + + def inputFiles(): List[Path] = { + val ls = Files.walk(expectSrc) + val files = + try ls.filter(p => scalaFile.matches(p)).collect(Collectors.toList).asScala + finally ls.close() + + files.toList + } + + def generateCoverage(updateExpectFiles: Boolean): Path = { + val target = Files.createTempDirectory("coverage") + val args = Array( + "-coverage", + target.toString, + "-coverage-sourceroot", + if (updateExpectFiles) "../" else ".", + "-usejavacp" + ) ++ inputFiles().map(_.toString) + val exit = Main.process(args) + assertFalse(s"dotc errors: ${exit.errorCount}", exit.hasErrors) + target + } +} diff --git a/library/src/scala/runtime/Invoker.scala b/library/src/scala/runtime/Invoker.scala new file mode 100644 index 000000000000..6a22427cbd18 --- /dev/null +++ b/library/src/scala/runtime/Invoker.scala @@ -0,0 +1,74 @@ +package scala.runtime + +import scala.collection.mutable +import java.nio.file.Path +import scala.collection.concurrent.TrieMap +import java.nio.file.Paths +import java.nio.file.Files +import java.nio.file.StandardOpenOption +import java.io.FileWriter +import java.io.File + +object Invoker { + private val runtimeUUID = java.util.UUID.randomUUID() + + private val MeasurementsPrefix = "scoverage.measurements." + private val threadFiles = new ThreadLocal[mutable.HashMap[String, FileWriter]] + + // For each data directory we maintain a thread-safe set tracking the ids + // that we've already seen and recorded. We're using a map as a set, so we + // only care about its keys and can ignore its values. + private val dataDirToIds = TrieMap.empty[String, TrieMap[Int, Any]] + + /** We record that the given id has been invoked by appending its id to the coverage data file. + * + * This will happen concurrently on as many threads as the application is using, so we use one + * file per thread, named for the thread id. + * + * This method is not thread-safe if the threads are in different JVMs, because the thread IDs + * may collide. You may not use `scoverage` on multiple processes in parallel without risking + * corruption of the measurement file. + * + * @param id + * the id of the statement that was invoked + * @param dataDir + * the directory where the measurement data is held + */ + def invoked(id: Int, dataDir: String): Unit = { + // [sam] we can do this simple check to save writing out to a file. + // This won't work across JVMs but since there's no harm in writing out the same id multiple + // times since for coverage we only care about 1 or more, (it just slows things down to + // do it more than once), anything we can do to help is good. This helps especially with code + // that is executed many times quickly, eg tight loops. + if (!dataDirToIds.contains(dataDir)) { + // Guard against SI-7943: "TrieMap method getOrElseUpdate is not thread-safe". + dataDirToIds.synchronized { + if (!dataDirToIds.contains(dataDir)) { + dataDirToIds(dataDir) = TrieMap.empty[Int, Any] + } + } + } + val ids = dataDirToIds(dataDir) + if (!ids.contains(id)) { + // Each thread writes to a separate measurement file, to reduce contention + // and because file appends via FileWriter are not atomic on Windows. + var files = threadFiles.get() + if (files == null) { + files = mutable.HashMap.empty[String, FileWriter] + threadFiles.set(files) + } + val writer = files.getOrElseUpdate( + dataDir, + new FileWriter(measurementFile(dataDir), true) + ) + + writer.append(Integer.toString(id)).append("\n").flush() + ids.put(id, ()) + } + } + + def measurementFile(dataDir: String): File = new File( + dataDir, + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId + ) +} diff --git a/project/Build.scala b/project/Build.scala index 3a4480dd84e3..05cb63ef80be 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -572,8 +572,10 @@ object Build { ) }, - javaOptions += ( - s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}" + javaOptions ++= Seq( + s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}", + s"-Ddotty.tools.dotc.coverage.test=${(ThisBuild / baseDirectory).value/"tests"/"coverage"}", + ), testCompilation := Def.inputTaskDyn { diff --git a/tests/coverage/expect/ContextFunctions.scala b/tests/coverage/expect/ContextFunctions.scala new file mode 100644 index 000000000000..e1338b1a1f75 --- /dev/null +++ b/tests/coverage/expect/ContextFunctions.scala @@ -0,0 +1,30 @@ +package example + +/** + * Intersection Types: https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html + * Taken from https://github.com/scala/scala3-example-project + */ +object IntersectionTypes: + + sealed trait X: + def x: Double + def tpe: X + + sealed trait Y: + def y: Double + def tpe: Y + + type P = Y & X + type PP = X & Y + + final case class Point(x: Double, y: Double) extends X with Y: + override def tpe: X & Y = ??? + + def test(): Unit = + def euclideanDistance(p1: X & Y, p2: X & Y) = + Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) + + val p1: P = Point(3, 4) + val p2: PP = Point(6, 8) + println(euclideanDistance(p1, p2)) + diff --git a/tests/coverage/expect/Enum.scala b/tests/coverage/expect/Enum.scala new file mode 100644 index 000000000000..6f99daae5240 --- /dev/null +++ b/tests/coverage/expect/Enum.scala @@ -0,0 +1,42 @@ +package example + +/** + * Enum Types: https://dotty.epfl.ch/docs/reference/enums/adts.html + * Taken from https://github.com/scala/scala3-example-project + */ +object EnumTypes: + + enum ListEnum[+A]: + case Cons(h: A, t: ListEnum[A]) + case Empty + + + enum Planet(mass: Double, radius: Double): + private final val G = 6.67300E-11 + def surfaceGravity = G * mass / (radius * radius) + def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity + + case Mercury extends Planet(3.303e+23, 2.4397e6) + case Venus extends Planet(4.869e+24, 6.0518e6) + case Earth extends Planet(5.976e+24, 6.37814e6) + case Mars extends Planet(6.421e+23, 3.3972e6) + case Jupiter extends Planet(1.9e+27, 7.1492e7) + case Saturn extends Planet(5.688e+26, 6.0268e7) + case Uranus extends Planet(8.686e+25, 2.5559e7) + case Neptune extends Planet(1.024e+26, 2.4746e7) + end Planet + + def test(): Unit = + val emptyList = ListEnum.Empty + val list = ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + println("Example 1: \n"+emptyList) + println(s"${list}\n") + + def calculateEarthWeightOnPlanets(earthWeight: Double) = + val mass = earthWeight/Planet.Earth.surfaceGravity + for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + + println("Example 2:") + calculateEarthWeightOnPlanets(80) + end test diff --git a/tests/coverage/expect/MultiversalEquality.scala b/tests/coverage/expect/MultiversalEquality.scala new file mode 100644 index 000000000000..ace2fd642c2c --- /dev/null +++ b/tests/coverage/expect/MultiversalEquality.scala @@ -0,0 +1,31 @@ +package example + +import scala.language.strictEquality + +/** + * Multiversal Equality: https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html + * scala.CanEqual definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala + * Taken from https://github.com/scala/scala3-example-project + */ +object MultiversalEquality: + + def test(): Unit = + given CanEqual[Int, String] = CanEqual.derived + println(3 == "3") + + println(3 == 5.1) + + println(List(1, 2) == Vector(1, 2)) + + class A(a: Int) + class B(b: Int) + + val a = A(4) + val b = B(4) + + given CanEqual[A, B] = CanEqual.derived + given CanEqual[B, A] = CanEqual.derived + + println(a != b) + println(b == a) + diff --git a/tests/coverage/expect/ParameterUntupling.scala b/tests/coverage/expect/ParameterUntupling.scala new file mode 100644 index 000000000000..533c4c3f3362 --- /dev/null +++ b/tests/coverage/expect/ParameterUntupling.scala @@ -0,0 +1,19 @@ +package example + +/** + * Parameter Untupling: https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html + * Taken from https://github.com/scala/scala3-example-project + */ +object ParameterUntupling: + + def test(): Unit = + val xs: List[String] = List("d", "o", "t", "t", "y") + + /** + * Current behaviour in Scala 2.12.2 : + * error: missing parameter type + * Note: The expected type requires a one-argument function accepting a 2-Tuple. + * Consider a pattern matching anonymous function, `{ case (s, i) => ... }` + */ + xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + diff --git a/tests/coverage/expect/PatternMatching.scala b/tests/coverage/expect/PatternMatching.scala new file mode 100644 index 000000000000..e2f0403ef87e --- /dev/null +++ b/tests/coverage/expect/PatternMatching.scala @@ -0,0 +1,87 @@ + +package example + +/** + * Pattern Matching: https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html + * Taken from https://github.com/scala/scala3-example-project + */ +object PatternMatching: + + object booleanPattern: + + object Even: + def unapply(s: String): Boolean = s.length % 2 == 0 + + + object productPattern: + + class Person(name: String, age: Int) extends Product: + // if we not define that, it will give compile error. + // we change the order + def _1 = age + def _2 = name + + // Not used by pattern matching: Product is only used as a marker trait. + def canEqual(that: Any): Boolean = ??? + def productArity: Int = ??? + def productElement(n: Int): Any = ??? + + object Person: + def unapply(a: (String, Int)): Person = Person(a._1, a._2) + + + object seqPattern: + + // adapted from https://danielwestheide.com/blog/the-neophytes-guide-to-scala-part-2-extracting-sequences/ + object Names: + def unapplySeq(name: String): Option[Seq[String]] = + val names = name.trim.split(" ") + if names.size < 2 then None + else Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) + + + object namePattern: + + class Name(val name: String): + def get: String = name + def isEmpty = name.isEmpty + + object Name: + def unapply(s: String): Name = Name(s) + + + def test(): Unit = + import booleanPattern.* + + "even" match + case s @ Even() => println(s"$s has an even number of characters") + case s => println(s"$s has an odd number of characters") + + // https://dotty.epfl.ch/docs/reference/changed-features/vararg-splices.html + def containsConsecutive(list: List[Int]): Boolean = list match + case List(a, b, xs*) => a == b || containsConsecutive(b :: xs.toList) + case Nil | List(_, _*) => false + + println(containsConsecutive(List(1, 2, 3, 4, 5))) + println(containsConsecutive(List(1, 2, 3, 3, 5))) + + import productPattern.* + ("john", 42) match + case Person(n, a) => println(s"name: $n, age: $a") + + import seqPattern.* + + def greet(fullName: String) = fullName match + case Names(lastName, firstName, _*) => "Good morning, " + firstName + " " + lastName + "!" + case _ => "Welcome! Please make sure to fill in your name!" + + println(greet("Alan Turing")) + println(greet("john")) + println(greet("Wolfgang Amadeus Mozart")) + + import namePattern.* + "alice" match + case Name(n) => println(s"name is $n") + case _ => println("empty name") + +end PatternMatching diff --git a/tests/coverage/expect/StructuralTypes.scala b/tests/coverage/expect/StructuralTypes.scala new file mode 100644 index 000000000000..4b2ee1991ce6 --- /dev/null +++ b/tests/coverage/expect/StructuralTypes.scala @@ -0,0 +1,29 @@ +package example + +/** + * Structural Types: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html + * Taken from https://github.com/scala/scala3-example-project + */ +object StructuralTypes: + + case class Record(elems: (String, Any)*) extends Selectable: + def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2 + + type Person = Record { + val name: String + val age: Int + } + + val person = Record("name" -> "Emma", "age" -> 42, "salary" -> 320L).asInstanceOf[Person] + + val invalidPerson = Record("name" -> "John", "salary" -> 42).asInstanceOf[Person] + + def test(): Unit = + println(person.name) + println(person.age) + + println(invalidPerson.name) + // age field is java.util.NoSuchElementException: None.get + //println(invalidPerson.age) + +end StructuralTypes diff --git a/tests/coverage/expect/TraitParams.scala b/tests/coverage/expect/TraitParams.scala new file mode 100644 index 000000000000..fa98a9b23805 --- /dev/null +++ b/tests/coverage/expect/TraitParams.scala @@ -0,0 +1,22 @@ +package example + +/** + * Trait Parameters: https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html + * Taken from https://github.com/scala/scala3-example-project + */ +object TraitParams: + + trait Base(val msg: String) + class A extends Base("Hello") + class B extends Base("Dotty!") + + // Union types only exist in Scala 3, so there's no chance that this will accidentally be compiled with Scala 2 + private def printMessages(msgs: (A | B)*) = println(msgs.map(_.msg).mkString(" ")) + + def test(): Unit = + printMessages(new A, new B) + + // Sanity check the classpath: this won't run if the Scala 3 jar is not present. + val x: Int => Int = identity + x(1) + diff --git a/tests/coverage/expect/TypeLambdas.scala b/tests/coverage/expect/TypeLambdas.scala new file mode 100644 index 000000000000..9769869b7805 --- /dev/null +++ b/tests/coverage/expect/TypeLambdas.scala @@ -0,0 +1,19 @@ +package example + +/** + * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html + * Taken from https://github.com/scala/scala3-example-project + */ +object TypeLambdas: + + type M = [X, Y] =>> Map[Y, X] + + type Tuple = [X] =>> (X, X) + + def test(): Unit = + val m: M[String, Int] = Map(1 -> "1") + println(m) + + val tuple: Tuple[String] = ("a", "b") + println(tuple) + diff --git a/tests/coverage/expect/UnionTypes.scala b/tests/coverage/expect/UnionTypes.scala new file mode 100644 index 000000000000..a79d8dd8fca4 --- /dev/null +++ b/tests/coverage/expect/UnionTypes.scala @@ -0,0 +1,44 @@ + +package example + +/** + * Union Types: https://dotty.epfl.ch/docs/reference/new-types/union-types.html + * Taken from https://github.com/scala/scala3-example-project + */ +object UnionTypes: + + sealed trait Division + final case class DivisionByZero(msg: String) extends Division + final case class Success(double: Double) extends Division + + // You can create type aliases for your union types (sum types). + type DivisionResult = DivisionByZero | Success + + sealed trait List[+A] + case object Empty extends List[Nothing] + final case class Cons[+A](h: A, t: List[A]) extends List[A] + + private def safeDivide(a: Double, b: Double): DivisionResult = + if b == 0 then DivisionByZero("DivisionByZeroException") else Success(a / b) + + private def either(division: Division) = division match + case DivisionByZero(m) => Left(m) + case Success(d) => Right(d) + + def test(): Unit = + val divisionResultSuccess: DivisionResult = safeDivide(4, 2) + + // commutative + val divisionResultFailure: Success | DivisionByZero = safeDivide(4, 0) + + // calling `either` function with union typed value. + println(either(divisionResultSuccess)) + + // calling `either` function with union typed value. + println(either(divisionResultFailure)) + + val list: Cons[Int] | Empty.type = Cons(1, Cons(2, Cons(3, Empty))) + val emptyList: Empty.type | Cons[Any] = Empty + println(list) + println(emptyList) + diff --git a/tests/coverage/scoverage.coverage.expect b/tests/coverage/scoverage.coverage.expect new file mode 100644 index 000000000000..93a8107f4c15 --- /dev/null +++ b/tests/coverage/scoverage.coverage.expect @@ -0,0 +1,3030 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +276 +279 +9 + +Literal +false +0 +false +"d" + +2 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +281 +284 +9 + +Literal +false +0 +false +"o" + +3 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +286 +289 +9 + +Literal +false +0 +false +"t" + +4 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +291 +294 +9 + +Literal +false +0 +false +"t" + +5 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +296 +299 +9 + +Literal +false +0 +false +"y" + +6 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +271 +300 +9 +apply +Apply +false +0 +false +List("d", "o", "t", "t", "y") + +7 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +$anonfun +615 +617 +17 + +Literal +false +0 +false +: + +8 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +$anonfun +613 +619 +17 +apply +Apply +false +0 +false +$i: $s + +9 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +$anonfun +611 +620 +17 +s +Apply +false +0 +false +s"$i: $s" + +10 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +$anonfun +603 +621 +17 +println +Apply +false +0 +false +println(s"$i: $s") + +11 +tests/coverage/expect/ParameterUntupling.scala +example +ParameterUntupling$ +Class +example.ParameterUntupling$ +test +573 +622 +17 +map +Apply +false +0 +false +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + +12 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +450 +451 +13 + +Literal +false +0 +false +3 + +13 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +455 +458 +13 + +Literal +false +0 +false +"3" + +14 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +442 +459 +13 +println +Apply +false +0 +false +println(3 == "3") + +15 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +473 +474 +15 + +Literal +false +0 +false +3 + +16 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +478 +481 +15 + +Literal +false +0 +false +5.1 + +17 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +501 +502 +17 + +Literal +false +0 +false +1 + +18 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +504 +505 +17 + +Literal +false +0 +false +2 + +19 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +496 +506 +17 +apply +Apply +false +0 +false +List(1, 2) + +20 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +517 +518 +17 + +Literal +false +0 +false +1 + +21 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +520 +521 +17 + +Literal +false +0 +false +2 + +22 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +510 +522 +17 +apply +Apply +false +0 +false +Vector(1, 2) + +23 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +496 +522 +17 +== +Apply +false +0 +false +List(1, 2) == Vector(1, 2) + +24 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +488 +523 +17 +println +Apply +false +0 +false +println(List(1, 2) == Vector(1, 2)) + +25 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +578 +579 +22 + +Select +false +0 +false +A + +26 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +580 +581 +22 + +Literal +false +0 +false +4 + +27 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +595 +596 +23 + +Select +false +0 +false +B + +28 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +597 +598 +23 + +Literal +false +0 +false +4 + +29 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +694 +709 +28 +println +Apply +false +0 +false +println(a != b) + +30 +tests/coverage/expect/MultiversalEquality.scala +example +MultiversalEquality$ +Class +example.MultiversalEquality$ +test +714 +729 +29 +println +Apply +false +0 +false +println(b == a) + +31 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +547 +558 +24 +- +Apply +false +0 +false +p2.y - p1.y + +32 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +560 +561 +24 + +Literal +false +0 +false +2 + +33 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +538 +562 +24 +pow +Apply +false +0 +false +Math.pow(p2.y - p1.y, 2) + +34 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +574 +585 +24 +- +Apply +false +0 +false +p2.x - p1.x + +35 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +587 +588 +24 + +Literal +false +0 +false +2 + +36 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +565 +589 +24 +pow +Apply +false +0 +false +Math.pow(p2.x - p1.x, 2) + +37 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +538 +589 +24 ++ +Apply +false +0 +false +Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2) + +38 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +euclideanDistance +528 +590 +24 +sqrt +Apply +false +0 +false +Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) + +39 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +test +614 +615 +26 + +Literal +false +0 +false +3 + +40 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +test +617 +618 +26 + +Literal +false +0 +false +4 + +41 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +test +643 +644 +27 + +Literal +false +0 +false +6 + +42 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +test +646 +647 +27 + +Literal +false +0 +false +8 + +43 +tests/coverage/expect/ContextFunctions.scala +example +IntersectionTypes$ +Class +example.IntersectionTypes$ +test +653 +687 +28 +println +Apply +false +0 +false +println(euclideanDistance(p1, p2)) + +44 +tests/coverage/expect/TypeLambdas.scala +example +TypeLambdas$ +Class +example.TypeLambdas$ +test +310 +311 +13 + +Literal +false +0 +false +1 + +45 +tests/coverage/expect/TypeLambdas.scala +example +TypeLambdas$ +Class +example.TypeLambdas$ +test +315 +318 +13 + +Literal +false +0 +false +"1" + +46 +tests/coverage/expect/TypeLambdas.scala +example +TypeLambdas$ +Class +example.TypeLambdas$ +test +306 +319 +13 +apply +Apply +false +0 +false +Map(1 -> "1") + +47 +tests/coverage/expect/TypeLambdas.scala +example +TypeLambdas$ +Class +example.TypeLambdas$ +test +368 +371 +16 + +Literal +false +0 +false +"a" + +48 +tests/coverage/expect/TypeLambdas.scala +example +TypeLambdas$ +Class +example.TypeLambdas$ +test +373 +376 +16 + +Literal +false +0 +false +"b" + +49 +tests/coverage/expect/StructuralTypes.scala +example +Record +Class +example.Record +selectDynamic +319 +343 +9 +find +Apply +false +0 +false +elems.find(_._1 == name) + +50 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +442 +448 +16 + +Literal +false +0 +false +"name" + +51 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +452 +458 +16 + +Literal +false +0 +false +"Emma" + +52 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +460 +465 +16 + +Literal +false +0 +false +"age" + +53 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +469 +471 +16 + +Literal +false +0 +false +42 + +54 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +473 +481 +16 + +Literal +false +0 +false +"salary" + +55 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +485 +489 +16 + +Literal +false +0 +false +320L + +56 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +435 +490 +16 +apply +Apply +false +0 +false +Record("name" -> "Emma", "age" -> 42, "salary" -> 320L) + +57 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +542 +548 +18 + +Literal +false +0 +false +"name" + +58 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +552 +558 +18 + +Literal +false +0 +false +"John" + +59 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +560 +568 +18 + +Literal +false +0 +false +"salary" + +60 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +572 +574 +18 + +Literal +false +0 +false +42 + +61 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ + +535 +575 +18 +apply +Apply +false +0 +false +Record("name" -> "John", "salary" -> 42) + +62 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ +test +623 +643 +21 +println +Apply +false +0 +false +println(person.name) + +63 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ +test +648 +667 +22 +println +Apply +false +0 +false +println(person.age) + +64 +tests/coverage/expect/StructuralTypes.scala +example +StructuralTypes$ +Class +example.StructuralTypes$ +test +673 +700 +24 +println +Apply +false +0 +false +println(invalidPerson.name) + +65 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +safeDivide +663 +664 +21 + +Literal +false +0 +false +0 + +66 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +safeDivide +685 +710 +21 + +Literal +false +0 +false +"DivisionByZeroException" + +67 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +safeDivide +670 +711 +21 +apply +Apply +true +0 +false +DivisionByZero("DivisionByZeroException") + +68 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +safeDivide +717 +731 +21 +apply +Apply +false +0 +false +Success(a / b) + +69 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +safeDivide +717 +731 +21 + +Block +true +0 +false +Success(a / b) + +70 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +949 +950 +28 + +Literal +false +0 +false +4 + +71 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +952 +953 +28 + +Literal +false +0 +false +2 + +72 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1044 +1045 +31 + +Literal +false +0 +false +4 + +73 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1047 +1048 +31 + +Literal +false +0 +false +0 + +74 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1112 +1150 +34 +println +Apply +false +0 +false +println(either(divisionResultSuccess)) + +75 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1213 +1251 +37 +println +Apply +false +0 +false +println(either(divisionResultFailure)) + +76 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1297 +1298 +39 + +Literal +false +0 +false +1 + +77 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1305 +1306 +39 + +Literal +false +0 +false +2 + +78 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1313 +1314 +39 + +Literal +false +0 +false +3 + +79 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1300 +1323 +39 +apply +Apply +false +0 +false +Cons(2, Cons(3, Empty)) + +80 +tests/coverage/expect/UnionTypes.scala +example +UnionTypes$ +Class +example.UnionTypes$ +test +1292 +1324 +39 +apply +Apply +false +0 +false +Cons(1, Cons(2, Cons(3, Empty))) + +81 +tests/coverage/expect/PatternMatching.scala +example +Even$ +Class +example.Even$ +unapply +296 +306 +12 +% +Select +false +0 +false +s.length % + +82 +tests/coverage/expect/PatternMatching.scala +example +Even$ +Class +example.Even$ +unapply +307 +308 +12 + +Literal +false +0 +false +2 + +83 +tests/coverage/expect/PatternMatching.scala +example +Even$ +Class +example.Even$ +unapply +312 +313 +12 + +Literal +false +0 +false +0 + +84 +tests/coverage/expect/PatternMatching.scala +example +Person$ +Class +example.Person$ +unapply +797 +803 +29 + +Select +false +0 +false +Person + +85 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1047 +1062 +37 +split +Select +false +0 +false +name.trim.split + +86 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1063 +1066 +37 + +Literal +false +0 +false +" " + +87 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1092 +1093 +38 + +Literal +false +0 +false +2 + +88 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1099 +1103 +38 +None +Ident +true +0 +false +None + +89 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1161 +1162 +39 + +Literal +false +0 +false +1 + +90 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1150 +1163 +39 +refArrayOps +Apply +false +0 +false +names.drop(1) + +91 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1174 +1175 +39 + +Literal +false +0 +false +1 + +92 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1150 +1176 +39 +wrapRefArray +Apply +false +0 +false +names.drop(1).dropRight(1) + +93 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1117 +1184 +39 +apply +Apply +false +0 +false +Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) + +94 +tests/coverage/expect/PatternMatching.scala +example +Names$ +Class +example.Names$ +unapplySeq +1117 +1184 +39 + +Block +true +0 +false +Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) + +95 +tests/coverage/expect/PatternMatching.scala +example +Name$ +Class +example.Name$ +unapply +1361 +1365 +49 + +Select +false +0 +false +Name + +96 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1425 +1431 +55 + +Literal +false +0 +false +"even" + +97 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1475 +1508 +56 + +Literal +false +0 +false + has an even number of characters + +98 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1473 +1508 +56 +apply +Apply +false +0 +false +$s has an even number of characters + +99 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1471 +1509 +56 +s +Apply +false +0 +false +s"$s has an even number of characters" + +100 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1463 +1510 +56 +println +Apply +false +0 +false +println(s"$s has an even number of characters") + +101 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1548 +1580 +57 + +Literal +false +0 +false + has an odd number of characters + +102 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1546 +1580 +57 +apply +Apply +false +0 +false +$s has an odd number of characters + +103 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1544 +1581 +57 +s +Apply +false +0 +false +s"$s has an odd number of characters" + +104 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1536 +1582 +57 +println +Apply +false +0 +false +println(s"$s has an odd number of characters") + +105 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +containsConsecutive +1775 +1810 +61 +containsConsecutive +Apply +false +0 +false +containsConsecutive(b :: xs.toList) + +106 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +containsConsecutive +1843 +1848 +62 + +Literal +false +0 +false +false + +107 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1887 +1888 +64 + +Literal +false +0 +false +1 + +108 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1890 +1891 +64 + +Literal +false +0 +false +2 + +109 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1893 +1894 +64 + +Literal +false +0 +false +3 + +110 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1896 +1897 +64 + +Literal +false +0 +false +4 + +111 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1899 +1900 +64 + +Literal +false +0 +false +5 + +112 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1882 +1901 +64 +apply +Apply +false +0 +false +List(1, 2, 3, 4, 5) + +113 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1862 +1902 +64 +containsConsecutive +Apply +false +0 +false +containsConsecutive(List(1, 2, 3, 4, 5)) + +114 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1854 +1903 +64 +println +Apply +false +0 +false +println(containsConsecutive(List(1, 2, 3, 4, 5))) + +115 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1941 +1942 +65 + +Literal +false +0 +false +1 + +116 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1944 +1945 +65 + +Literal +false +0 +false +2 + +117 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1947 +1948 +65 + +Literal +false +0 +false +3 + +118 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1950 +1951 +65 + +Literal +false +0 +false +3 + +119 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1953 +1954 +65 + +Literal +false +0 +false +5 + +120 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1936 +1955 +65 +apply +Apply +false +0 +false +List(1, 2, 3, 3, 5) + +121 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1916 +1956 +65 +containsConsecutive +Apply +false +0 +false +containsConsecutive(List(1, 2, 3, 3, 5)) + +122 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1908 +1957 +65 +println +Apply +false +0 +false +println(containsConsecutive(List(1, 2, 3, 3, 5))) + +123 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +1992 +1998 +68 + +Literal +false +0 +false +"john" + +124 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2000 +2002 +68 + +Literal +false +0 +false +42 + +125 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2048 +2054 +69 + +Literal +false +0 +false +name: + +126 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2056 +2063 +69 + +Literal +false +0 +false +, age: + +127 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2048 +2065 +69 +apply +Apply +false +0 +false +name: $n, age: $a + +128 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2046 +2066 +69 +s +Apply +false +0 +false +s"name: $n, age: $a" + +129 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2038 +2067 +69 +println +Apply +false +0 +false +println(s"name: $n, age: $a") + +130 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +greet +2189 +2205 +74 + +Literal +false +0 +false +"Good morning, " + +131 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +greet +2220 +2223 +74 + +Literal +false +0 +false +" " + +132 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +greet +2237 +2240 +74 + +Literal +false +0 +false +"!" + +133 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +greet +2286 +2335 +75 + +Literal +false +0 +false +"Welcome! Please make sure to fill in your name!" + +134 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2359 +2372 +77 + +Literal +false +0 +false +"Alan Turing" + +135 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2345 +2374 +77 +println +Apply +false +0 +false +println(greet("Alan Turing")) + +136 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2393 +2399 +78 + +Literal +false +0 +false +"john" + +137 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2379 +2401 +78 +println +Apply +false +0 +false +println(greet("john")) + +138 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2420 +2445 +79 + +Literal +false +0 +false +"Wolfgang Amadeus Mozart" + +139 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2406 +2447 +79 +println +Apply +false +0 +false +println(greet("Wolfgang Amadeus Mozart")) + +140 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2478 +2485 +82 + +Literal +false +0 +false +"alice" + +141 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2525 +2533 +83 + +Literal +false +0 +false +name is + +142 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2525 +2535 +83 +apply +Apply +false +0 +false +name is $n + +143 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2523 +2536 +83 +s +Apply +false +0 +false +s"name is $n" + +144 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2515 +2537 +83 +println +Apply +false +0 +false +println(s"name is $n") + +145 +tests/coverage/expect/PatternMatching.scala +example +PatternMatching$ +Class +example.PatternMatching$ +test +2568 +2580 +84 + +Literal +false +0 +false +"empty name" + +146 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +printMessages +475 +490 +13 +map +Apply +false +0 +false +msgs.map(_.msg) + +147 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +printMessages +500 +503 +13 + +Literal +false +0 +false +" " + +148 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +printMessages +467 +505 +13 +println +Apply +false +0 +false +println(msgs.map(_.msg).mkString(" ")) + +149 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +test +550 +551 +16 + +Select +false +0 +false +A + +150 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +test +557 +558 +16 + +Select +false +0 +false +B + +151 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +test +532 +559 +16 +printMessages +Apply +false +0 +false +printMessages(new A, new B) + +152 +tests/coverage/expect/TraitParams.scala +example +TraitParams$ +Class +example.TraitParams$ +test +685 +686 +20 + +Literal +false +0 +false +1 + +153 +tests/coverage/expect/Enum.scala +example +Planet +Class +example.Planet + +326 +337 +14 + +Literal +false +0 +false +6.67300E-11 + +154 +tests/coverage/expect/Enum.scala +example +Planet +Class +example.Planet +surfaceGravity +363 +390 +15 +/ +Apply +false +0 +false +G * mass / (radius * radius + +155 +tests/coverage/expect/Enum.scala +example +Planet +Class +example.Planet +surfaceWeight +436 +462 +16 +* +Apply +false +0 +false +otherMass * surfaceGravity + +156 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +988 +989 +30 + +Literal +false +0 +false +1 + +157 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1005 +1006 +30 + +Literal +false +0 +false +2 + +158 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1022 +1023 +30 + +Literal +false +0 +false +3 + +159 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1008 +1040 +30 +apply +Apply +false +0 +false +ListEnum.Cons(3, ListEnum.Empty) + +160 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +991 +1041 +30 +apply +Apply +false +0 +false +ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) + +161 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +974 +1042 +30 +apply +Apply +false +0 +false +ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + +162 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1055 +1070 +31 + +Literal +false +0 +false +"Example 1: \n" + +163 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1047 +1081 +31 +println +Apply +false +0 +false +println("Example 1: \n"+emptyList) + +164 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1103 +1105 +32 + +Literal +false +0 +false +\n + +165 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1096 +1105 +32 +apply +Apply +false +0 +false +${list}\n + +166 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1094 +1106 +32 +s +Apply +false +0 +false +s"${list}\n" + +167 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1086 +1107 +32 +println +Apply +false +0 +false +println(s"${list}\n") + +168 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +calculateEarthWeightOnPlanets +1187 +1226 +35 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +169 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +calculateEarthWeightOnPlanets +1242 +1255 +36 +refArrayOps +Apply +false +0 +false +Planet.values + +170 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +$anonfun +1277 +1292 +37 + +Literal +false +0 +false +Your weight on + +171 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +$anonfun +1294 +1298 +37 + +Literal +false +0 +false + is + +172 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +$anonfun +1277 +1322 +37 +apply +Apply +false +0 +false +Your weight on $p is ${p.surfaceWeight(mass)} + +173 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +$anonfun +1275 +1323 +37 +s +Apply +false +0 +false +s"Your weight on $p is ${p.surfaceWeight(mass)}" + +174 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +$anonfun +1267 +1324 +37 +println +Apply +false +0 +false +println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +175 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +calculateEarthWeightOnPlanets +1233 +1324 +36 +foreach +Apply +false +0 +false +for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +176 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1338 +1350 +39 + +Literal +false +0 +false +"Example 2:" + +177 +tests/coverage/expect/Enum.scala +example +EnumTypes$ +Class +example.EnumTypes$ +test +1386 +1388 +40 + +Literal +false +0 +false +80 + From a4549e7c4f45144b142a42372a111399eede1f1c Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 4 Mar 2022 15:46:56 +0100 Subject: [PATCH 050/121] Move coverage phase to before FirstTransform and handle more trees --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../dotty/tools/dotc/coverage/Coverage.scala | 17 +- .../dotty/tools/dotc/coverage/Location.scala | 6 +- .../tools/dotc/coverage/Serializer.scala | 42 ++--- ...rmMacro.scala => InstrumentCoverage.scala} | 177 ++++++++++-------- .../dotty/tools/dotc/typer/EtaExpansion.scala | 18 +- library/src/scala/runtime/Invoker.scala | 6 +- 7 files changed, 140 insertions(+), 128 deletions(-) rename compiler/src/dotty/tools/dotc/transform/{CoverageTransformMacro.scala => InstrumentCoverage.scala} (52%) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 1c11267641fd..8ed6f2a70fe5 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -46,7 +46,6 @@ class Compiler { List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only) List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols - List(new CoverageTransformMacro) :: // Perform instrumentation for coverage transform (if -coverage is present) Nil /** Phases dealing with TASTY tree pickling and unpickling */ @@ -60,6 +59,7 @@ class Compiler { /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = + List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage setting is set) List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index b4365e1c4389..ceb646e86bca 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -3,13 +3,12 @@ package coverage import scala.collection.mutable -class Coverage { +class Coverage: private val statementsById = mutable.Map[Int, Statement]() - def statements = statementsById.values + def statements: Iterable[Statement] = statementsById.values - def addStatement(stmt: Statement): Unit = statementsById.put(stmt.id, stmt) -} + def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt case class Statement( source: String, @@ -24,7 +23,9 @@ case class Statement( branch: Boolean, var count: Int = 0, ignored: Boolean = false -) { - def invoked(): Unit = count = count + 1 - def isInvoked = count > 0 -} +): + def invoked(): Unit = + count += 1 + + def isInvoked: Boolean = + count > 0 diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala index 47f712a49da5..65f8d8adb5ee 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Location.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -20,8 +20,8 @@ final case class Location( sourcePath: String ) -object Location { - def apply(tree: Tree)(using ctx: Context): Location = { +object Location: + def apply(tree: Tree)(using ctx: Context): Location = val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString() val className = ctx.owner.denot.enclosingClass.name.toSimpleName.toString() @@ -34,5 +34,3 @@ object Location { ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), ctx.source.file.absolute.toString() ) - } -} diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index 43fe45774249..6cc70de72c75 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -5,31 +5,34 @@ import java.io._ import scala.io.Source -object Serializer { +/** + * Serializes scoverage data. + * @see https://github.com/scoverage/scalac-scoverage-plugin/blob/main/scalac-scoverage-plugin/src/main/scala/scoverage/Serializer.scala + */ +object Serializer: - val coverageFileName = "scoverage.coverage" - val coverageDataFormatVersion = "3.0" - // Write out coverage data to the given data directory, using the default coverage filename + private val CoverageFileName = "scoverage.coverage" + private val CoverageDataFormatVersion = "3.0" + + /** Write out coverage data to the given data directory, using the default coverage filename */ def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit = serialize(coverage, coverageFile(dataDir), new File(sourceRoot)) - // Write out coverage data to given file. - def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = { - val writer = new BufferedWriter(new FileWriter(file)) + /** Write out coverage data to given file. */ + def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = + val writer = BufferedWriter(FileWriter(file)) serialize(coverage, writer, sourceRoot) writer.close() - } - def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit = { + def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit = - def getRelativePath(filePath: String): String = { + def getRelativePath(filePath: String): String = val base = sourceRoot.getCanonicalFile().toPath() - val relPath = base.relativize(new File(filePath).getCanonicalFile().toPath()) + val relPath = base.relativize(File(filePath).getCanonicalFile().toPath()) relPath.toString - } - def writeHeader(writer: Writer): Unit = { - writer.write(s"""# Coverage data, format version: $coverageDataFormatVersion + def writeHeader(writer: Writer): Unit = + writer.write(s"""# Coverage data, format version: $CoverageDataFormatVersion |# Statement data: |# - id |# - source path @@ -50,8 +53,8 @@ object Serializer { |# '\f' sign |# ------------------------------------------ |""".stripMargin) - } - def writeStatement(stmt: Statement, writer: Writer): Unit = { + + def writeStatement(stmt: Statement, writer: Writer): Unit = writer.write(s"""${stmt.id} |${getRelativePath(stmt.location.sourcePath)} |${stmt.location.packageName} @@ -70,14 +73,11 @@ object Serializer { |${stmt.desc} |\f |""".stripMargin) - } writeHeader(writer) - coverage.statements.toVector + coverage.statements.toSeq .sortBy(_.id) .foreach(stmt => writeStatement(stmt, writer)) - } def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath) - def coverageFile(dataDir: String): File = new File(dataDir, coverageFileName) -} + def coverageFile(dataDir: String): File = File(dataDir, CoverageFileName) diff --git a/compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala similarity index 52% rename from compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala rename to compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 2d827dd25cac..a283382f87d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/CoverageTransformMacro.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -21,53 +21,60 @@ import dotty.tools.dotc.typer.LiftCoverage import scala.quoted -/** Phase that implements code coverage, executed when the "-coverage - * OUTPUT_PATH" is added to the compilation. - */ -class CoverageTransformMacro extends MacroTransform with IdentityDenotTransformer { +/** Implements code coverage by inserting calls to scala.runtime.Invoker + * ("instruments" the source code). + * The result can then be consumed by the Scoverage tool. + */ +class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: import ast.tpd._ - override def phaseName = "coverage" + override def phaseName = InstrumentCoverage.name + + override def description = InstrumentCoverage.description + + // Enabled by argument "-coverage OUTPUT_DIR" + override def isEnabled(using ctx: Context) = + ctx.settings.coverageOutputDir.value.nonEmpty // Atomic counter used for assignation of IDs to difference statements - val statementId = new AtomicInteger(0) + private val statementId = AtomicInteger(0) - var outputPath = "" + private var outputPath = "" // Main class used to store all instrumented statements - val coverage = new Coverage - - override def run(using ctx: Context): Unit = { + private val coverage = Coverage() - if (ctx.settings.coverageOutputDir.value.nonEmpty) { - outputPath = ctx.settings.coverageOutputDir.value + override def run(using ctx: Context): Unit = + outputPath = ctx.settings.coverageOutputDir.value - // Ensure the dir exists - val dataDir = new File(outputPath) - val newlyCreated = dataDir.mkdirs() + // Ensure the dir exists + val dataDir = new File(outputPath) + val newlyCreated = dataDir.mkdirs() - if (!newlyCreated) { - // If the directory existed before, let's clean it up. - dataDir.listFiles - .filter(_.getName.startsWith("scoverage")) - .foreach(_.delete) - } + if (!newlyCreated) { + // If the directory existed before, let's clean it up. + dataDir.listFiles + .filter(_.getName.startsWith("scoverage")) + .foreach(_.delete) + } - super.run + super.run + Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value) - Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value) - } - } + override protected def newTransformer(using Context) = CoverageTransormer() - protected def newTransformer(using Context): Transformer = - new CoverageTransormer + private class CoverageTransormer extends Transformer: - class CoverageTransormer extends Transformer { - var instrumented = false + override def transform(tree: Tree)(using Context): Tree = + println(tree.show + tree.toString) + tree match + // simple cases + case tree: (Literal | Import | Export) => tree + case tree: (New | This | Super) => instrument(tree) + case tree if (tree.isEmpty || tree.isType) => tree // empty Thicket, Ident, TypTree, ... - override def transform(tree: Tree)(using Context): Tree = { - tree match { + // branches case tree: If => cpy.If(tree)( cond = transform(tree.cond), @@ -77,51 +84,43 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme case tree: Try => cpy.Try(tree)( expr = instrument(transform(tree.expr), branch = true), - cases = instrumentCasees(tree.cases), + cases = instrumentCases(tree.cases), finalizer = instrument(transform(tree.finalizer), true) ) - case Apply(fun, _) - if ( - fun.symbol.exists && - fun.symbol.isInstanceOf[Symbol] && - fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_|| - ) => - super.transform(tree) - case tree @ Apply(fun, args) if (fun.isInstanceOf[Apply]) => - // We have nested apply, we have to lift all arguments - // Example: def T(x:Int)(y:Int) - // T(f())(1) // should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} - liftApply(tree) + + // f(args) case tree: Apply => - if (LiftCoverage.needsLift(tree)) { + if needsLift(tree) then liftApply(tree) - } else { + else super.transform(tree) - } + + // (f(x))[args] + case tree @ TypeApply(fun: Apply, args) => + cpy.TypeApply(tree)(transform(fun), args) + + // a.b case Select(qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined)) => //Java class can't be used as a value, we can't instrument the //qualifier ({;System}.xyz() is not possible !) instrument it //as it is instrument(tree) case tree: Select => - if (tree.qualifier.isInstanceOf[New]) { + if tree.qualifier.isInstanceOf[New] then instrument(tree) - } else { + else cpy.Select(tree)(transform(tree.qualifier), tree.name) - } + case tree: CaseDef => instrumentCaseDef(tree) + case tree: ValDef => + // only transform the rhs + cpy.ValDef(tree)(rhs = transform(tree.rhs)) - case tree: Literal => instrument(tree) - case tree: Ident if (isWildcardArg(tree)) => - // We don't want to instrument wildcard arguments. `var a = _` can't be instrumented - tree - case tree: New => instrument(tree) - case tree: This => instrument(tree) - case tree: Super => instrument(tree) case tree: PackageDef => - // We don't instrument the pid of the package, but we do instrument the statements + // only transform the statements of the package cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) - case tree: Assign => cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) + case tree: Assign => + cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) case tree: Template => // Don't instrument the parents (extends) of a template since it // causes problems if the parent constructor takes parameters @@ -129,9 +128,7 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme constr = super.transformSub(tree.constr), body = transform(tree.body) ) - case tree: Import => tree - // Catch EmptyTree since we can't match directly on it - case tree: Thicket if tree.isEmpty => tree + // For everything else just recurse and transform case _ => report.warning( @@ -139,15 +136,13 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme tree.sourcePos ) super.transform(tree) - } - } - def liftApply(tree: Apply)(using Context) = { + def liftApply(tree: Apply)(using Context) = val buffer = mutable.ListBuffer[Tree]() // NOTE: that if only one arg needs to be lifted, we just lift everything val lifted = LiftCoverage.liftForCoverage(buffer, tree) val instrumented = buffer.toList.map(transform) - //We can now instrument the apply as it is with a custom position to point to the function + // We can now instrument the apply as it is with a custom position to point to the function Block( instrumented, instrument( @@ -156,22 +151,18 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme false ) ) - } - def instrumentCasees(cases: List[CaseDef])(using Context): List[CaseDef] = { + def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = cases.map(instrumentCaseDef) - } - def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = { + def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = cpy.CaseDef(tree)(tree.pat, transform(tree.guard), transform(tree.body)) - } - def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = { + def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = instrument(tree, tree.sourcePos, branch) - } - def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = { - if (pos.exists && !pos.span.isZeroExtent && !tree.isType) { + def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = + if pos.exists && !pos.span.isZeroExtent then val id = statementId.incrementAndGet() val statement = new Statement( source = ctx.source.file.name, @@ -187,18 +178,40 @@ class CoverageTransformMacro extends MacroTransform with IdentityDenotTransforme ) coverage.addStatement(statement) Block(List(invokeCall(id)), tree) - } else { + else tree - } - } - def invokeCall(id: Int)(using Context): Tree = { + def invokeCall(id: Int)(using Context): Tree = ref(defn.InvokerModuleRef) .select("invoked".toTermName) .appliedToArgs( List(Literal(Constant(id)), Literal(Constant(outputPath))) ) - } - } -} + /** + * Checks if the apply needs a lift in the coverage phase. + * In case of a nested application, we have to lift all arguments + * Example: + * ``` + * def T(x:Int)(y:Int) + * T(f())(1) + * ``` + * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} + */ + def needsLift(tree: Apply)(using Context): Boolean = + // We don't want to lift a || getB(), to avoid calling getB if a is true. + // Same idea with a && getB(): if a is false, getB shouldn't be called. + def isBooleanOperator(fun: Tree) = + fun.symbol.exists && + fun.symbol.isInstanceOf[Symbol] && + fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_|| + + val fun = tree.fun + + fun.isInstanceOf[Apply] || // nested apply + !isBooleanOperator(fun) || + !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) + +object InstrumentCoverage: + val name: String = "instrumentCoverage" + val description: String = "instrument code for coverage cheking" diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index ab36dd0395df..32427120514a 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -11,6 +11,7 @@ import Symbols._ import Names._ import NameKinds.UniqueName import util.Spans._ +import util.Property import collection.mutable import Trees._ @@ -158,23 +159,20 @@ object LiftComplex extends LiftComplex /** Lift complex + lift the prefixes */ object LiftCoverage extends LiftComplex { - var liftEverything = false + private val LiftEverything = new Property.Key[Boolean] - /** Return true if the apply needs a lift in the coverage phase - Return false if the args are empty, if one or more will be lifter by a - complex lifter. - */ - def needsLift(tree: tpd.Apply)(using Context): Boolean = - !tree.args.isEmpty && !tree.args.forall(super.noLift(_)) + private def liftEverything(using Context): Boolean = + ctx.property(LiftEverything).contains(true) + + private def liftEverythingContext(using Context): Context = + ctx.fresh.setProperty(LiftEverything, true) override def noLift(expr: tpd.Tree)(using Context) = !liftEverything && super.noLift(expr) def liftForCoverage(defs: mutable.ListBuffer[tpd.Tree], tree: tpd.Apply)(using Context) = { val liftedFun = liftApp(defs, tree.fun) - liftEverything = true - val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args) - liftEverything = false + val liftedArgs = liftArgs(defs, tree.fun.tpe, tree.args)(using liftEverythingContext) tpd.cpy.Apply(tree)(liftedFun, liftedArgs) } } diff --git a/library/src/scala/runtime/Invoker.scala b/library/src/scala/runtime/Invoker.scala index 6a22427cbd18..94e39c2f9348 100644 --- a/library/src/scala/runtime/Invoker.scala +++ b/library/src/scala/runtime/Invoker.scala @@ -62,13 +62,15 @@ object Invoker { new FileWriter(measurementFile(dataDir), true) ) - writer.append(Integer.toString(id)).append("\n").flush() + writer.append(Integer.toString(id)) + writer.append("\n") + writer.flush() ids.put(id, ()) } } def measurementFile(dataDir: String): File = new File( dataDir, - MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.getId + MeasurementsPrefix + runtimeUUID + "." + Thread.currentThread.nn.getId ) } From b1729229f821950b3a224db47f6d8f0d4b73c983 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 10 Mar 2022 19:58:12 +0100 Subject: [PATCH 051/121] Improve Invoker performance with a BitSet According to my JMH benchmarks, this improves the performance by at least 40%, even when multiple threads use the Invoker. Details on the benchmark: - 10k calls to Invoker.invoked(id,dir) with a % of ids that are repeted in a loop and a % of ids that appear only once. But this doesn't change the results much. - 1 thread or 4 threads at the same time. The single-thread performance is, as expected, much better (3x). --- library/src/scala/runtime/Invoker.scala | 63 ++++++++----------------- 1 file changed, 20 insertions(+), 43 deletions(-) diff --git a/library/src/scala/runtime/Invoker.scala b/library/src/scala/runtime/Invoker.scala index 94e39c2f9348..b1b953b7cbc6 100644 --- a/library/src/scala/runtime/Invoker.scala +++ b/library/src/scala/runtime/Invoker.scala @@ -1,11 +1,8 @@ package scala.runtime -import scala.collection.mutable -import java.nio.file.Path +import scala.collection.mutable.{BitSet, AnyRefMap} import scala.collection.concurrent.TrieMap -import java.nio.file.Paths import java.nio.file.Files -import java.nio.file.StandardOpenOption import java.io.FileWriter import java.io.File @@ -13,12 +10,8 @@ object Invoker { private val runtimeUUID = java.util.UUID.randomUUID() private val MeasurementsPrefix = "scoverage.measurements." - private val threadFiles = new ThreadLocal[mutable.HashMap[String, FileWriter]] - - // For each data directory we maintain a thread-safe set tracking the ids - // that we've already seen and recorded. We're using a map as a set, so we - // only care about its keys and can ignore its values. - private val dataDirToIds = TrieMap.empty[String, TrieMap[Int, Any]] + private val threadFiles = new ThreadLocal[AnyRefMap[String, FileWriter]] + private val dataDirToSet = TrieMap.empty[String, BitSet] /** We record that the given id has been invoked by appending its id to the coverage data file. * @@ -34,40 +27,24 @@ object Invoker { * @param dataDir * the directory where the measurement data is held */ - def invoked(id: Int, dataDir: String): Unit = { - // [sam] we can do this simple check to save writing out to a file. - // This won't work across JVMs but since there's no harm in writing out the same id multiple - // times since for coverage we only care about 1 or more, (it just slows things down to - // do it more than once), anything we can do to help is good. This helps especially with code - // that is executed many times quickly, eg tight loops. - if (!dataDirToIds.contains(dataDir)) { - // Guard against SI-7943: "TrieMap method getOrElseUpdate is not thread-safe". - dataDirToIds.synchronized { - if (!dataDirToIds.contains(dataDir)) { - dataDirToIds(dataDir) = TrieMap.empty[Int, Any] - } - } - } - val ids = dataDirToIds(dataDir) - if (!ids.contains(id)) { - // Each thread writes to a separate measurement file, to reduce contention - // and because file appends via FileWriter are not atomic on Windows. - var files = threadFiles.get() - if (files == null) { - files = mutable.HashMap.empty[String, FileWriter] - threadFiles.set(files) + def invoked(id: Int, dataDir: String): Unit = + val set = dataDirToSet.getOrElseUpdate(dataDir, BitSet.empty) + if !set.contains(id) then + val added = set.synchronized { + set.add(id) } - val writer = files.getOrElseUpdate( - dataDir, - new FileWriter(measurementFile(dataDir), true) - ) - - writer.append(Integer.toString(id)) - writer.append("\n") - writer.flush() - ids.put(id, ()) - } - } + if added then + var writers = threadFiles.get() + if writers == null then + writers = AnyRefMap.empty + threadFiles.set(writers) + val writer = writers.getOrElseUpdate( + dataDir, + FileWriter(measurementFile(dataDir), true) + ) + writer.write(Integer.toString(id)) + writer.write('\n') + writer.flush() def measurementFile(dataDir: String): File = new File( dataDir, From d37d13b15362e752d796256a7f196e8b9cb3cb49 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sat, 12 Mar 2022 16:53:02 +0100 Subject: [PATCH 052/121] Mark Invoker as sharable to bypass -Ycheck-reentrant --- library/src/scala/runtime/Invoker.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/src/scala/runtime/Invoker.scala b/library/src/scala/runtime/Invoker.scala index b1b953b7cbc6..44b0703fb8d2 100644 --- a/library/src/scala/runtime/Invoker.scala +++ b/library/src/scala/runtime/Invoker.scala @@ -5,7 +5,9 @@ import scala.collection.concurrent.TrieMap import java.nio.file.Files import java.io.FileWriter import java.io.File +import scala.annotation.internal.sharable +@sharable // avoids false positive by -Ycheck-reentrant object Invoker { private val runtimeUUID = java.util.UUID.randomUUID() From cb66c7a777ae7ad0eab3ebf17a042edacad34ef8 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Mon, 14 Mar 2022 15:35:18 +0100 Subject: [PATCH 053/121] Cut coverage tests in small parts and avoid lifting literals --- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../dotty/tools/dotc/coverage/Coverage.scala | 2 +- .../tools/dotc/coverage/Serializer.scala | 30 +- .../dotc/transform/InstrumentCoverage.scala | 85 +- .../dotty/tools/dotc/typer/EtaExpansion.scala | 2 +- .../tools/dotc/coverage/CoverageTests.scala | 94 +- .../dotty/tools/vulpix/ParallelTesting.scala | 10 +- docs/_docs/usage/coverage.md | 60 + project/Build.scala | 1 - tests/coverage/expect/ContextFunctions.scala | 30 - tests/coverage/expect/Enum.scala | 5 +- tests/coverage/expect/Enum.scoverage.check | 446 +++ tests/coverage/expect/Givens.scala | 24 + tests/coverage/expect/Givens.scoverage.check | 224 ++ tests/coverage/expect/Lifting.scala | 12 + tests/coverage/expect/Lifting.scoverage.check | 122 + tests/coverage/expect/Literals.scala | 11 + .../coverage/expect/Literals.scoverage.check | 88 + tests/coverage/expect/MatchCaseClasses.scala | 15 + .../expect/MatchCaseClasses.scoverage.check | 139 + tests/coverage/expect/MatchNumbers.scala | 12 + .../expect/MatchNumbers.scoverage.check | 105 + .../coverage/expect/MultiversalEquality.scala | 31 - .../coverage/expect/ParameterUntupling.scala | 19 - .../expect/ParamsAndInterpolation.scala | 11 + .../ParamsAndInterpolation.scoverage.check | 190 ++ tests/coverage/expect/PatternMatching.scala | 87 - tests/coverage/expect/Select.scala | 20 + tests/coverage/expect/Select.scoverage.check | 139 + tests/coverage/expect/StructuralTypes.scala | 21 +- .../expect/StructuralTypes.scoverage.check | 156 + tests/coverage/expect/TraitParams.scala | 22 - tests/coverage/expect/TypeLambdas.scala | 2 +- .../expect/TypeLambdas.scoverage.check | 122 + tests/coverage/expect/UnionTypes.scala | 44 - tests/coverage/scoverage.coverage.expect | 3030 ----------------- 36 files changed, 2019 insertions(+), 3394 deletions(-) create mode 100644 docs/_docs/usage/coverage.md delete mode 100644 tests/coverage/expect/ContextFunctions.scala create mode 100644 tests/coverage/expect/Enum.scoverage.check create mode 100644 tests/coverage/expect/Givens.scala create mode 100644 tests/coverage/expect/Givens.scoverage.check create mode 100644 tests/coverage/expect/Lifting.scala create mode 100644 tests/coverage/expect/Lifting.scoverage.check create mode 100644 tests/coverage/expect/Literals.scala create mode 100644 tests/coverage/expect/Literals.scoverage.check create mode 100644 tests/coverage/expect/MatchCaseClasses.scala create mode 100644 tests/coverage/expect/MatchCaseClasses.scoverage.check create mode 100644 tests/coverage/expect/MatchNumbers.scala create mode 100644 tests/coverage/expect/MatchNumbers.scoverage.check delete mode 100644 tests/coverage/expect/MultiversalEquality.scala delete mode 100644 tests/coverage/expect/ParameterUntupling.scala create mode 100644 tests/coverage/expect/ParamsAndInterpolation.scala create mode 100644 tests/coverage/expect/ParamsAndInterpolation.scoverage.check delete mode 100644 tests/coverage/expect/PatternMatching.scala create mode 100644 tests/coverage/expect/Select.scala create mode 100644 tests/coverage/expect/Select.scoverage.check create mode 100644 tests/coverage/expect/StructuralTypes.scoverage.check delete mode 100644 tests/coverage/expect/TraitParams.scala create mode 100644 tests/coverage/expect/TypeLambdas.scoverage.check delete mode 100644 tests/coverage/expect/UnionTypes.scala delete mode 100644 tests/coverage/scoverage.coverage.expect diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 10e9876db20f..e37168fce20b 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -117,7 +117,7 @@ trait CommonScalaSettings: val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) /* Coverage settings */ - val coverageOutputDir = PathSetting("-coverage", "Destination for coverage classfiles and instrumentation data.", "") + val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "") val coverageSourceroot = PathSetting("-coverage-sourceroot", "An alternative root dir of your sources used to relativize.", ".") /* Other settings */ diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index ceb646e86bca..05ced1075edc 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -4,7 +4,7 @@ package coverage import scala.collection.mutable class Coverage: - private val statementsById = mutable.Map[Int, Statement]() + private val statementsById = new mutable.LongMap[Statement](256) def statements: Iterable[Statement] = statementsById.values diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index 6cc70de72c75..c5e6440afffd 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -1,9 +1,9 @@ package dotty.tools.dotc package coverage -import java.io._ - -import scala.io.Source +import java.nio.file.{Path, Paths, Files} +import java.io.Writer +import scala.language.unsafeNulls /** * Serializes scoverage data. @@ -16,19 +16,22 @@ object Serializer: /** Write out coverage data to the given data directory, using the default coverage filename */ def serialize(coverage: Coverage, dataDir: String, sourceRoot: String): Unit = - serialize(coverage, coverageFile(dataDir), new File(sourceRoot)) + serialize(coverage, Paths.get(dataDir, CoverageFileName).toAbsolutePath, Paths.get(sourceRoot).toAbsolutePath) - /** Write out coverage data to given file. */ - def serialize(coverage: Coverage, file: File, sourceRoot: File): Unit = - val writer = BufferedWriter(FileWriter(file)) - serialize(coverage, writer, sourceRoot) - writer.close() + /** Write out coverage data to a file. */ + def serialize(coverage: Coverage, file: Path, sourceRoot: Path): Unit = + val writer = Files.newBufferedWriter(file) + try + serialize(coverage, writer, sourceRoot) + finally + writer.close() - def serialize(coverage: Coverage, writer: Writer, sourceRoot: File): Unit = + /** Write out coverage data (info about each statement that can be covered) to a writer. + */ + def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path): Unit = def getRelativePath(filePath: String): String = - val base = sourceRoot.getCanonicalFile().toPath() - val relPath = base.relativize(File(filePath).getCanonicalFile().toPath()) + val relPath = sourceRoot.relativize(Paths.get(filePath).toAbsolutePath) relPath.toString def writeHeader(writer: Writer): Unit = @@ -78,6 +81,3 @@ object Serializer: coverage.statements.toSeq .sortBy(_.id) .foreach(stmt => writeStatement(stmt, writer)) - - def coverageFile(dataDir: File): File = coverageFile(dataDir.getAbsolutePath) - def coverageFile(dataDir: String): File = File(dataDir, CoverageFileName) diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index a283382f87d7..29d2209e1ec3 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -15,12 +15,10 @@ import dotty.tools.dotc.coverage.Location import dotty.tools.dotc.core.Symbols.defn import dotty.tools.dotc.core.Symbols.Symbol import dotty.tools.dotc.core.Decorators.toTermName -import dotty.tools.dotc.util.SourcePosition +import dotty.tools.dotc.util.{SourcePosition, Property} import dotty.tools.dotc.core.Constants.Constant import dotty.tools.dotc.typer.LiftCoverage -import scala.quoted - /** Implements code coverage by inserting calls to scala.runtime.Invoker * ("instruments" the source code). * The result can then be consumed by the Scoverage tool. @@ -51,29 +49,40 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val dataDir = new File(outputPath) val newlyCreated = dataDir.mkdirs() - if (!newlyCreated) { + if !newlyCreated then // If the directory existed before, let's clean it up. - dataDir.listFiles - .filter(_.getName.startsWith("scoverage")) - .foreach(_.delete) - } - + dataDir.listFiles.nn + .filter(_.nn.getName.nn.startsWith("scoverage")) + .foreach(_.nn.delete()) + end if super.run Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value) override protected def newTransformer(using Context) = CoverageTransormer() + /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */ private class CoverageTransormer extends Transformer: + private val IgnoreLiterals = new Property.Key[Boolean] + + private def ignoreLiteralsContext(using ctx: Context): Context = + ctx.fresh.setProperty(IgnoreLiterals, true) - override def transform(tree: Tree)(using Context): Tree = - println(tree.show + tree.toString) + override def transform(tree: Tree)(using ctx: Context): Tree = tree match // simple cases - case tree: (Literal | Import | Export) => tree - case tree: (New | This | Super) => instrument(tree) + case tree: (Import | Export | This | Super | New) => tree case tree if (tree.isEmpty || tree.isType) => tree // empty Thicket, Ident, TypTree, ... + // Literals must be instrumented (at least) when returned by a def, + // otherwise `def d = "literal"` is not covered when called from a test. + // They can be left untouched when passed in a parameter of an Apply. + case tree: Literal => + if ctx.property(IgnoreLiterals).contains(true) then + tree + else + instrument(tree) + // branches case tree: If => cpy.If(tree)( @@ -88,28 +97,42 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: finalizer = instrument(transform(tree.finalizer), true) ) + // a.f(args) + case tree @ Apply(fun: Select, args) => + // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) + if needsLift(tree) then + val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted + instrumentLifted(transformed)(using ignoreLiteralsContext) + else + val transformed = cpy.Apply(tree)(transformedFun, transform(args)) + instrument(transformed)(using ignoreLiteralsContext) + // f(args) case tree: Apply => if needsLift(tree) then - liftApply(tree) + instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals else - super.transform(tree) + instrument(super.transform(tree)(using ignoreLiteralsContext)) // (f(x))[args] - case tree @ TypeApply(fun: Apply, args) => + case TypeApply(fun: Apply, args) => cpy.TypeApply(tree)(transform(fun), args) // a.b - case Select(qual, _) if (qual.symbol.exists && qual.symbol.is(JavaDefined)) => - //Java class can't be used as a value, we can't instrument the - //qualifier ({;System}.xyz() is not possible !) instrument it - //as it is - instrument(tree) - case tree: Select => - if tree.qualifier.isInstanceOf[New] then + case Select(qual, name) => + if qual.symbol.exists && qual.symbol.is(JavaDefined) then + //Java class can't be used as a value, we can't instrument the + //qualifier ({;System}.xyz() is not possible !) instrument it + //as it is instrument(tree) else - cpy.Select(tree)(transform(tree.qualifier), tree.name) + val transformed = cpy.Select(tree)(transform(qual), name) + if transformed.qualifier.isDef then + // instrument calls to methods without parameter list + instrument(transformed) + else + transformed case tree: CaseDef => instrumentCaseDef(tree) case tree: ValDef => @@ -120,6 +143,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // only transform the statements of the package cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) case tree: Assign => + // only transform the rhs cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) case tree: Template => // Don't instrument the parents (extends) of a template since it @@ -131,13 +155,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // For everything else just recurse and transform case _ => - report.warning( - "Unmatched: " + tree.getClass + " " + tree.symbol, - tree.sourcePos - ) super.transform(tree) - def liftApply(tree: Apply)(using Context) = + def instrumentLifted(tree: Apply)(using Context) = val buffer = mutable.ListBuffer[Tree]() // NOTE: that if only one arg needs to be lifted, we just lift everything val lifted = LiftCoverage.liftForCoverage(buffer, tree) @@ -170,10 +190,10 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: id = id, start = pos.start, end = pos.end, - line = ctx.source.offsetToLine(pos.point), + line = pos.line, desc = tree.source.content.slice(pos.start, pos.end).mkString, symbolName = tree.symbol.name.toSimpleName.toString(), - treeName = tree.getClass.getSimpleName, + treeName = tree.getClass.getSimpleName.nn, branch ) coverage.addStatement(statement) @@ -209,8 +229,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val fun = tree.fun fun.isInstanceOf[Apply] || // nested apply - !isBooleanOperator(fun) || - !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) + !isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) object InstrumentCoverage: val name: String = "instrumentCoverage" diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index 32427120514a..0d955651dbd9 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -161,7 +161,7 @@ object LiftCoverage extends LiftComplex { private val LiftEverything = new Property.Key[Boolean] - private def liftEverything(using Context): Boolean = + private inline def liftEverything(using Context): Boolean = ctx.property(LiftEverything).contains(true) private def liftEverythingContext(using Context): Context = diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 9dde3b9e8221..4e11957cb034 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -1,75 +1,63 @@ package dotty.tools.dotc.coverage -import java.util.stream.Collectors -import org.junit.Assert._ -import dotty.BootstrappedOnlyTests -import org.junit.experimental.categories.Category import org.junit.Test -import java.nio.file.Files +import org.junit.Assert.* +import org.junit.experimental.categories.Category + +import dotty.BootstrappedOnlyTests import dotty.tools.dotc.Main -import scala.jdk.CollectionConverters._ -import java.io.File +import java.nio.file.Files import java.nio.file.Path import java.nio.file.FileSystems import java.nio.file.Paths -import java.nio.charset.StandardCharsets -import scala.io.Source -import java.io.BufferedOutputStream -import java.io.FileOutputStream +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.StandardCopyOption @main def updateExpect = - CoverageTests().runExpectTest(updateExpectFiles = true) + CoverageTests().runExpectTest(updateCheckfiles = true) @Category(Array(classOf[BootstrappedOnlyTests])) -class CoverageTests { +class CoverageTests: - val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") - val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")) - val expectSrc = rootSrc.resolve("expect") + private val scalaFile = FileSystems.getDefault.nn.getPathMatcher("glob:**.scala").nn + private val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")).nn + private val expectDir = rootSrc.resolve("expect").nn @Category(Array(classOf[dotty.SlowTests])) @Test def expectTests: Unit = - if (!scala.util.Properties.isWin) runExpectTest(updateExpectFiles = false) - - def runExpectTest(updateExpectFiles: Boolean): Unit = { - val target = generateCoverage(updateExpectFiles) - val input = Source.fromFile(new File(target.toString, "scoverage.coverage")) - val expectFile = new File(expectSrc.resolveSibling("scoverage.coverage.expect").toUri) - - if (updateExpectFiles) { - val outputStream = new BufferedOutputStream(new FileOutputStream(expectFile)) - try { - input.foreach(outputStream.write(_)) - } finally outputStream.close - } else { - val expected = new String(Files.readAllBytes(expectFile.toPath), StandardCharsets.UTF_8) - val obtained = input.mkString - - assertEquals(expected, obtained) - } - } - - def inputFiles(): List[Path] = { - val ls = Files.walk(expectSrc) - val files = - try ls.filter(p => scalaFile.matches(p)).collect(Collectors.toList).asScala - finally ls.close() - - files.toList - } - - def generateCoverage(updateExpectFiles: Boolean): Path = { + runExpectTest(dotty.Properties.testsUpdateCheckfile) + + /** Runs the tests */ + def runExpectTest(updateCheckfiles: Boolean): Unit = + val sourceRoot = if updateCheckfiles then "../" else "." + + Files.walk(expectDir).nn.filter(scalaFile.matches).nn.forEach(p => { + val path = p.nn + val fileName = path.getFileName.nn.toString.nn.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(Seq(path), sourceRoot).nn + val targetFile = targetDir.resolve(s"scoverage.coverage").nn + val expectFile = expectDir.resolve(s"$fileName.scoverage.check").nn + + if updateCheckfiles then + Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) + else + val expected = new String(Files.readAllBytes(expectFile), UTF_8) + val obtained = new String(Files.readAllBytes(targetFile), UTF_8) + assertEquals(expected, obtained) + + }) + + /** Generates the coverage report for the given input file, in a temporary directory. */ + def computeCoverageInTmp(inputFiles: Seq[Path], sourceRoot: String): Path = val target = Files.createTempDirectory("coverage") val args = Array( - "-coverage", + "-coverage-out", target.toString, "-coverage-sourceroot", - if (updateExpectFiles) "../" else ".", + sourceRoot, "-usejavacp" - ) ++ inputFiles().map(_.toString) + ) ++ inputFiles.map(_.toString) val exit = Main.process(args) - assertFalse(s"dotc errors: ${exit.errorCount}", exit.hasErrors) - target - } -} + assertFalse(s"Compilation failed, ${exit.errorCount} errors", exit.hasErrors) + target.nn diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index 6df8c734ad3e..cf051e5f01ad 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -666,7 +666,7 @@ trait ParallelTesting extends RunnerOrchestration { self => if (didFail) { reportFailed() failedTestSources.toSet.foreach(addFailedTest) - reproduceInstructions.iterator.foreach(addReproduceInstruction) + reproduceInstructions.foreach(addReproduceInstruction) } else reportPassed() } @@ -980,7 +980,7 @@ trait ParallelTesting extends RunnerOrchestration { self => cleanup() if (!shouldFail && test.didFail) { - fail(s"Expected no errors when compiling, failed for the following reason(s):\n${ reasonsForFailure(test) }") + fail(s"Expected no errors when compiling, failed for the following reason(s):\n${reasonsForFailure(test)}\n") } else if (shouldFail && !test.didFail) { fail("Pos test should have failed, but didn't") @@ -1077,14 +1077,16 @@ trait ParallelTesting extends RunnerOrchestration { self => /** Extract `Failure` set and render from `Test` */ private def reasonsForFailure(test: Test): String = { val failureReport = - if (test.failureCount == 0) "" - else s"\n - encountered ${test.failureCount} test failures(s)" + if test.failureCount == 0 then "" + else s"encountered ${test.failureCount} test failure(s):\n" failureReport + test.failureReasons.collect { case test.TimeoutFailure(title) => s" - test '$title' timed out" case test.JavaCompilationFailure(msg) => s" - java compilation failed with:\n${ msg.linesIterator.map(" " + _).mkString("\n") }" + case test.Generic => + " - generic failure (see test output)" }.mkString("\n") } diff --git a/docs/_docs/usage/coverage.md b/docs/_docs/usage/coverage.md new file mode 100644 index 000000000000..838865fcd014 --- /dev/null +++ b/docs/_docs/usage/coverage.md @@ -0,0 +1,60 @@ +--- +layout: doc-page +title: "Code Coverage for Scala 3" +--- + +## Instrument code for coverage analysis + +[PR#13880](https://github.com/lampepfl/dotty/pull/13880) has implemented code coverage support for Dotty. +In general, code coverage works in three steps: +1. The program is "instrumented" at compilation time: code is inserted to record which statement are called. This does not change the behavior of the program. Also, a list of all the coverable statements is produced. +2. The program is run, usually by unit tests, and the instrumentation code saves its measurements. +3. Some tool processes the data to generate a fancy coverage report, for instance a web page. + +In Scala 2, all these steps were performed by external tools. In particular, step 1 was implemented by a compiler plugin. + +In Scala 3, the compiler itself takes care of step 1. To use this feature, add the compile option `-coverage-out:DIR`, where `DIR` is the destination of the measurement files. + +You can also set `-coverage-sourceroot:PATHS_ROOT` to customize how the path of your source files are resolved. + +## How-to with sbt + +For now, the Scoverage sbt plugin doesn't apply the above options automatically. +However, you can easily do it yourself, and use the plugin to generate user-friendly reports. + +1. Add the scoverage sbt plugin by appending this line to your `project/plugins.sbt` +```scala +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0-M4") +``` + +2. Compile your project with +```scala +Compile/compile/scalacOptions += + s"-coverage-out:target/scala-${scalaVersion.value}/scoverage-data" +``` + +2. Run the tests: `sbt test` +3. Generate xml and html reports: `sbt coverageReport` + +## Details: how the code is instrumented + +When the `-coverage-out` option is set, a new phase `instrumentCoverage` runs, just before `firstTransform`. +For a carefully selected list of tree types, it adds a call to `scala.runtime.Invoker.invoked(statementId, DIR)`. + +For instance, this code: +``` +def method() = + println(f()) +``` + +with `-coverage-out:target/cov` be turned to +``` +def method() = + Invoker.invoked(2, "target/cov") + println({ + Invoker.invoked(1, "target/cov") + f() + }) +``` + +At the end of the phase, the list of all the instrumented statements is serialized to the file `DIR/scoverage.coverage`. diff --git a/project/Build.scala b/project/Build.scala index 05cb63ef80be..64022cde42f8 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -575,7 +575,6 @@ object Build { javaOptions ++= Seq( s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}", s"-Ddotty.tools.dotc.coverage.test=${(ThisBuild / baseDirectory).value/"tests"/"coverage"}", - ), testCompilation := Def.inputTaskDyn { diff --git a/tests/coverage/expect/ContextFunctions.scala b/tests/coverage/expect/ContextFunctions.scala deleted file mode 100644 index e1338b1a1f75..000000000000 --- a/tests/coverage/expect/ContextFunctions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package example - -/** - * Intersection Types: https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html - * Taken from https://github.com/scala/scala3-example-project - */ -object IntersectionTypes: - - sealed trait X: - def x: Double - def tpe: X - - sealed trait Y: - def y: Double - def tpe: Y - - type P = Y & X - type PP = X & Y - - final case class Point(x: Double, y: Double) extends X with Y: - override def tpe: X & Y = ??? - - def test(): Unit = - def euclideanDistance(p1: X & Y, p2: X & Y) = - Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) - - val p1: P = Point(3, 4) - val p2: PP = Point(6, 8) - println(euclideanDistance(p1, p2)) - diff --git a/tests/coverage/expect/Enum.scala b/tests/coverage/expect/Enum.scala index 6f99daae5240..b5710432f6b6 100644 --- a/tests/coverage/expect/Enum.scala +++ b/tests/coverage/expect/Enum.scala @@ -1,4 +1,4 @@ -package example +package covtest /** * Enum Types: https://dotty.epfl.ch/docs/reference/enums/adts.html @@ -6,10 +6,9 @@ package example */ object EnumTypes: - enum ListEnum[+A]: + enum ListEnum[+A]: case Cons(h: A, t: ListEnum[A]) case Empty - enum Planet(mass: Double, radius: Double): private final val G = 6.67300E-11 diff --git a/tests/coverage/expect/Enum.scoverage.check b/tests/coverage/expect/Enum.scoverage.check new file mode 100644 index 000000000000..001fb41321a1 --- /dev/null +++ b/tests/coverage/expect/Enum.scoverage.check @@ -0,0 +1,446 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet + +322 +333 +13 + +Literal +false +0 +false +6.67300E-11 + +2 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +3 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +4 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +371 +386 +14 +* +Apply +false +0 +false +radius * radius + +5 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +359 +386 +14 +/ +Apply +false +0 +false +G * mass / (radius * radius + +6 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +432 +458 +15 +* +Apply +false +0 +false +otherMass * surfaceGravity + +7 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1004 +1036 +29 +apply +Apply +false +0 +false +ListEnum.Cons(3, ListEnum.Empty) + +8 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +987 +1037 +29 +apply +Apply +false +0 +false +ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) + +9 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +970 +1038 +29 +apply +Apply +false +0 +false +ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) + +10 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1051 +1076 +30 ++ +Apply +false +0 +false +"Example 1: \n"+emptyList + +11 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1043 +1077 +30 +println +Apply +false +0 +false +println("Example 1: \n"+emptyList) + +12 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1092 +1101 +31 +apply +Apply +false +0 +false +${list}\n + +13 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1092 +1101 +31 +apply +Apply +false +0 +false +${list}\n + +14 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1090 +1102 +31 +s +Apply +false +0 +false +s"${list}\n" + +15 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1082 +1103 +31 +println +Apply +false +0 +false +println(s"${list}\n") + +16 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1183 +1222 +34 +/ +Apply +false +0 +false +earthWeight/Planet.Earth.surfaceGravity + +17 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1238 +1251 +35 +refArrayOps +Apply +false +0 +false +Planet.values + +18 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +$anonfun +1273 +1318 +36 +apply +Apply +false +0 +false +Your weight on $p is ${p.surfaceWeight(mass)} + +19 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +$anonfun +1273 +1318 +36 +apply +Apply +false +0 +false +Your weight on $p is ${p.surfaceWeight(mass)} + +20 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +$anonfun +1296 +1317 +36 +surfaceWeight +Apply +false +0 +false +p.surfaceWeight(mass) + +21 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +$anonfun +1271 +1319 +36 +s +Apply +false +0 +false +s"Your weight on $p is ${p.surfaceWeight(mass)}" + +22 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +$anonfun +1263 +1320 +36 +println +Apply +false +0 +false +println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +23 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1229 +1320 +35 +foreach +Apply +false +0 +false +for p <- Planet.values do + println(s"Your weight on $p is ${p.surfaceWeight(mass)}") + +24 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1326 +1347 +38 +println +Apply +false +0 +false +println("Example 2:") + +25 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Class +covtest.EnumTypes$ +test +1352 +1385 +39 +calculateEarthWeightOnPlanets +Apply +false +0 +false +calculateEarthWeightOnPlanets(80) + diff --git a/tests/coverage/expect/Givens.scala b/tests/coverage/expect/Givens.scala new file mode 100644 index 000000000000..ae29f6eb7f8f --- /dev/null +++ b/tests/coverage/expect/Givens.scala @@ -0,0 +1,24 @@ +package covtest + +import scala.language.strictEquality + +class Context(val id: Int) + +object Givens: + + def test(): Unit = + given CanEqual[Int, String] = CanEqual.derived + println(3 == "3") + println(3 == 5.1) + + def printContext(msg: String)(using ctx: Context): Unit = + println(msg) + println(ctx.id) + + def getMessage(i: Int): String = i.toString + + def test2() = + // TODO FIXME: this does not work if we remove the def declaration (compiler crash in LambdaLift) + given c: Context = Context(0) + printContext("test")(using c) + printContext(getMessage(123)) diff --git a/tests/coverage/expect/Givens.scoverage.check b/tests/coverage/expect/Givens.scoverage.check new file mode 100644 index 000000000000..1235a0387f5c --- /dev/null +++ b/tests/coverage/expect/Givens.scoverage.check @@ -0,0 +1,224 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test +183 +191 +10 +== +Apply +false +0 +false +3 == "3" + +2 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test +175 +192 +10 +println +Apply +false +0 +false +println(3 == "3") + +3 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test +205 +213 +11 +== +Apply +false +0 +false +3 == 5.1 + +4 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test +197 +214 +11 +println +Apply +false +0 +false +println(3 == 5.1) + +5 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +printContext +280 +292 +14 +println +Apply +false +0 +false +println(msg) + +6 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +printContext +297 +312 +15 +println +Apply +false +0 +false +println(ctx.id) + +7 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +getMessage +349 +359 +17 +toString +Apply +false +0 +false +i.toString + +8 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test2 +510 +511 +21 + +Literal +false +0 +false +0 + +9 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test2 +502 +512 +21 + +Apply +false +0 +false +Context(0) + +10 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test2 +517 +546 +22 +printContext +Apply +false +0 +false +printContext("test")(using c) + +11 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test2 +564 +579 +23 +getMessage +Apply +false +0 +false +getMessage(123) + +12 +tests/coverage/expect/Givens.scala +covtest +Givens$ +Class +covtest.Givens$ +test2 +551 +580 +23 +printContext +Apply +false +0 +false +printContext(getMessage(123)) + diff --git a/tests/coverage/expect/Lifting.scala b/tests/coverage/expect/Lifting.scala new file mode 100644 index 000000000000..5e41eb172db3 --- /dev/null +++ b/tests/coverage/expect/Lifting.scala @@ -0,0 +1,12 @@ +package covtest + +class A: + def msg(a: Int, b: Int, c: Int) = "string" + def integer: Int = 0 + def ex: this.type = this + +def test(): Unit = + val a = A() + val i = 123 + a.msg(i, 0, a.integer) + a.ex.msg(i, 0, a.ex.integer) diff --git a/tests/coverage/expect/Lifting.scoverage.check b/tests/coverage/expect/Lifting.scoverage.check new file mode 100644 index 000000000000..b77989de97a1 --- /dev/null +++ b/tests/coverage/expect/Lifting.scoverage.check @@ -0,0 +1,122 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Lifting.scala +covtest +A +Class +covtest.A +msg +62 +70 +3 + +Literal +false +0 +false +"string" + +2 +tests/coverage/expect/Lifting.scala +covtest +A +Class +covtest.A +integer +92 +93 +4 + +Literal +false +0 +false +0 + +3 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Class +covtest.Lifting$package$ +test +151 +154 +8 + +Apply +false +0 +false +A() + +4 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Class +covtest.Lifting$package$ +test +165 +168 +9 + +Literal +false +0 +false +123 + +5 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Class +covtest.Lifting$package$ +test +171 +193 +10 +msg +Apply +false +0 +false +a.msg(i, 0, a.integer) + +6 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Class +covtest.Lifting$package$ +test +196 +224 +11 +msg +Apply +false +0 +false +a.ex.msg(i, 0, a.ex.integer) + diff --git a/tests/coverage/expect/Literals.scala b/tests/coverage/expect/Literals.scala new file mode 100644 index 000000000000..e8b55349d300 --- /dev/null +++ b/tests/coverage/expect/Literals.scala @@ -0,0 +1,11 @@ + +def mustBeInstrumented = "literal" + +def thisOneToo = + println("not this") // this literal should not be instrumented, only the println call + 12 // this literal must instrumented + +def f(x: Int, y: Int, z: Int)(t: Int) = ??? + +def main: Unit = + f(0,1,2)(3) diff --git a/tests/coverage/expect/Literals.scoverage.check b/tests/coverage/expect/Literals.scoverage.check new file mode 100644 index 000000000000..8087fd68835c --- /dev/null +++ b/tests/coverage/expect/Literals.scoverage.check @@ -0,0 +1,88 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Literals.scala + +Literals$package$ +Class +.Literals$package$ +mustBeInstrumented +26 +35 +1 + +Literal +false +0 +false +"literal" + +2 +tests/coverage/expect/Literals.scala + +Literals$package$ +Class +.Literals$package$ +thisOneToo +56 +75 +4 +println +Apply +false +0 +false +println("not this") + +3 +tests/coverage/expect/Literals.scala + +Literals$package$ +Class +.Literals$package$ +thisOneToo +144 +146 +5 + +Literal +false +0 +false +12 + +4 +tests/coverage/expect/Literals.scala + +Literals$package$ +Class +.Literals$package$ +main +246 +257 +10 +f +Apply +false +0 +false +f(0,1,2)(3) + diff --git a/tests/coverage/expect/MatchCaseClasses.scala b/tests/coverage/expect/MatchCaseClasses.scala new file mode 100644 index 000000000000..9e94be869a37 --- /dev/null +++ b/tests/coverage/expect/MatchCaseClasses.scala @@ -0,0 +1,15 @@ +package covtest + +case class Pat1(x: Int) +case class Pat2(x: Int, y: Any) + +object MatchCaseClasses: + def f(x: Any): Unit = x match + case Pat1(0) => println("a") + case Pat1(_) => println("b") + case p @ Pat2(1, -1) => println("c") + case Pat2(_, y: String) => + println(y) + println("d") + case p: Pat2 => println("e") + case _ => println("other") diff --git a/tests/coverage/expect/MatchCaseClasses.scoverage.check b/tests/coverage/expect/MatchCaseClasses.scoverage.check new file mode 100644 index 000000000000..8a5cb6333f67 --- /dev/null +++ b/tests/coverage/expect/MatchCaseClasses.scoverage.check @@ -0,0 +1,139 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +151 +163 +7 +println +Apply +false +0 +false +println("a") + +2 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +184 +196 +8 +println +Apply +false +0 +false +println("b") + +3 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +225 +237 +9 +println +Apply +false +0 +false +println("c") + +4 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +275 +285 +11 +println +Apply +false +0 +false +println(y) + +5 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +292 +304 +12 +println +Apply +false +0 +false +println("d") + +6 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +325 +337 +13 +println +Apply +false +0 +false +println("e") + +7 +tests/coverage/expect/MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Class +covtest.MatchCaseClasses$ +f +352 +368 +14 +println +Apply +false +0 +false +println("other") + diff --git a/tests/coverage/expect/MatchNumbers.scala b/tests/coverage/expect/MatchNumbers.scala new file mode 100644 index 000000000000..f160ab883402 --- /dev/null +++ b/tests/coverage/expect/MatchNumbers.scala @@ -0,0 +1,12 @@ +package covtest + +object MatchNumbers: + type Integer = Int | Long + + def f(i: Integer): Int = i match + case x: Int if x < 0 => -1 + case x: Int => x + case y: Long => y.toInt + + f(0) + f(1L) diff --git a/tests/coverage/expect/MatchNumbers.scoverage.check b/tests/coverage/expect/MatchNumbers.scoverage.check new file mode 100644 index 000000000000..78fae5188293 --- /dev/null +++ b/tests/coverage/expect/MatchNumbers.scoverage.check @@ -0,0 +1,105 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/MatchNumbers.scala +covtest +MatchNumbers$ +Class +covtest.MatchNumbers$ +f +125 +126 +6 + +Literal +false +0 +false +0 + +2 +tests/coverage/expect/MatchNumbers.scala +covtest +MatchNumbers$ +Class +covtest.MatchNumbers$ +f +121 +126 +6 +< +Apply +false +0 +false +x < 0 + +3 +tests/coverage/expect/MatchNumbers.scala +covtest +MatchNumbers$ +Class +covtest.MatchNumbers$ +f +130 +132 +6 + +Literal +false +0 +false +-1 + +4 +tests/coverage/expect/MatchNumbers.scala +covtest +MatchNumbers$ +Class +covtest.MatchNumbers$ + +185 +189 +10 +f +Apply +false +0 +false +f(0) + +5 +tests/coverage/expect/MatchNumbers.scala +covtest +MatchNumbers$ +Class +covtest.MatchNumbers$ + +192 +197 +11 +f +Apply +false +0 +false +f(1L) + diff --git a/tests/coverage/expect/MultiversalEquality.scala b/tests/coverage/expect/MultiversalEquality.scala deleted file mode 100644 index ace2fd642c2c..000000000000 --- a/tests/coverage/expect/MultiversalEquality.scala +++ /dev/null @@ -1,31 +0,0 @@ -package example - -import scala.language.strictEquality - -/** - * Multiversal Equality: https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html - * scala.CanEqual definition: https://github.com/lampepfl/dotty/blob/master/library/src/scala/CanEqual.scala - * Taken from https://github.com/scala/scala3-example-project - */ -object MultiversalEquality: - - def test(): Unit = - given CanEqual[Int, String] = CanEqual.derived - println(3 == "3") - - println(3 == 5.1) - - println(List(1, 2) == Vector(1, 2)) - - class A(a: Int) - class B(b: Int) - - val a = A(4) - val b = B(4) - - given CanEqual[A, B] = CanEqual.derived - given CanEqual[B, A] = CanEqual.derived - - println(a != b) - println(b == a) - diff --git a/tests/coverage/expect/ParameterUntupling.scala b/tests/coverage/expect/ParameterUntupling.scala deleted file mode 100644 index 533c4c3f3362..000000000000 --- a/tests/coverage/expect/ParameterUntupling.scala +++ /dev/null @@ -1,19 +0,0 @@ -package example - -/** - * Parameter Untupling: https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html - * Taken from https://github.com/scala/scala3-example-project - */ -object ParameterUntupling: - - def test(): Unit = - val xs: List[String] = List("d", "o", "t", "t", "y") - - /** - * Current behaviour in Scala 2.12.2 : - * error: missing parameter type - * Note: The expected type requires a one-argument function accepting a 2-Tuple. - * Consider a pattern matching anonymous function, `{ case (s, i) => ... }` - */ - xs.zipWithIndex.map((s, i) => println(s"$i: $s")) - diff --git a/tests/coverage/expect/ParamsAndInterpolation.scala b/tests/coverage/expect/ParamsAndInterpolation.scala new file mode 100644 index 000000000000..34d7ae300811 --- /dev/null +++ b/tests/coverage/expect/ParamsAndInterpolation.scala @@ -0,0 +1,11 @@ +package covtest + +object ParamsAndInterpolation: + + def simple(a: Int, b: String): String = + s"$a, ${b.length}" + + def test(): Unit = + val xs: List[String] = List("d", "o", "t", "t", "y") + + xs.zipWithIndex.map((s, i) => println(s"$i: $s")) diff --git a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check new file mode 100644 index 000000000000..00c61ad22b39 --- /dev/null +++ b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check @@ -0,0 +1,190 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +simple +97 +112 +5 +apply +Apply +false +0 +false +$a, ${b.length} + +2 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +simple +97 +112 +5 +apply +Apply +false +0 +false +$a, ${b.length} + +3 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +simple +103 +111 +5 +length +Apply +false +0 +false +b.length + +4 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +simple +95 +113 +5 +s +Apply +false +0 +false +s"$a, ${b.length}" + +5 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +test +163 +192 +8 +apply +Apply +false +0 +false +List("d", "o", "t", "t", "y") + +6 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +$anonfun +238 +244 +10 +apply +Apply +false +0 +false +$i: $s + +7 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +$anonfun +238 +244 +10 +apply +Apply +false +0 +false +$i: $s + +8 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +$anonfun +236 +245 +10 +s +Apply +false +0 +false +s"$i: $s" + +9 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +$anonfun +228 +246 +10 +println +Apply +false +0 +false +println(s"$i: $s") + +10 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Class +covtest.ParamsAndInterpolation$ +test +198 +247 +10 +map +Apply +false +0 +false +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + diff --git a/tests/coverage/expect/PatternMatching.scala b/tests/coverage/expect/PatternMatching.scala deleted file mode 100644 index e2f0403ef87e..000000000000 --- a/tests/coverage/expect/PatternMatching.scala +++ /dev/null @@ -1,87 +0,0 @@ - -package example - -/** - * Pattern Matching: https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html - * Taken from https://github.com/scala/scala3-example-project - */ -object PatternMatching: - - object booleanPattern: - - object Even: - def unapply(s: String): Boolean = s.length % 2 == 0 - - - object productPattern: - - class Person(name: String, age: Int) extends Product: - // if we not define that, it will give compile error. - // we change the order - def _1 = age - def _2 = name - - // Not used by pattern matching: Product is only used as a marker trait. - def canEqual(that: Any): Boolean = ??? - def productArity: Int = ??? - def productElement(n: Int): Any = ??? - - object Person: - def unapply(a: (String, Int)): Person = Person(a._1, a._2) - - - object seqPattern: - - // adapted from https://danielwestheide.com/blog/the-neophytes-guide-to-scala-part-2-extracting-sequences/ - object Names: - def unapplySeq(name: String): Option[Seq[String]] = - val names = name.trim.split(" ") - if names.size < 2 then None - else Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) - - - object namePattern: - - class Name(val name: String): - def get: String = name - def isEmpty = name.isEmpty - - object Name: - def unapply(s: String): Name = Name(s) - - - def test(): Unit = - import booleanPattern.* - - "even" match - case s @ Even() => println(s"$s has an even number of characters") - case s => println(s"$s has an odd number of characters") - - // https://dotty.epfl.ch/docs/reference/changed-features/vararg-splices.html - def containsConsecutive(list: List[Int]): Boolean = list match - case List(a, b, xs*) => a == b || containsConsecutive(b :: xs.toList) - case Nil | List(_, _*) => false - - println(containsConsecutive(List(1, 2, 3, 4, 5))) - println(containsConsecutive(List(1, 2, 3, 3, 5))) - - import productPattern.* - ("john", 42) match - case Person(n, a) => println(s"name: $n, age: $a") - - import seqPattern.* - - def greet(fullName: String) = fullName match - case Names(lastName, firstName, _*) => "Good morning, " + firstName + " " + lastName + "!" - case _ => "Welcome! Please make sure to fill in your name!" - - println(greet("Alan Turing")) - println(greet("john")) - println(greet("Wolfgang Amadeus Mozart")) - - import namePattern.* - "alice" match - case Name(n) => println(s"name is $n") - case _ => println("empty name") - -end PatternMatching diff --git a/tests/coverage/expect/Select.scala b/tests/coverage/expect/Select.scala new file mode 100644 index 000000000000..2c9e63552586 --- /dev/null +++ b/tests/coverage/expect/Select.scala @@ -0,0 +1,20 @@ +package covtest + +trait T: + def print(): Unit + +class A extends T: + def print() = println("A") + def instance = this + +class B extends A: + override def print() = + super.print() + println(this.instance) + +def test(): Unit = + val a = A() + val aNew = new A + + a.instance.print() // should instrument `a.instance` and `(a.instance).print()` + a.print() diff --git a/tests/coverage/expect/Select.scoverage.check b/tests/coverage/expect/Select.scoverage.check new file mode 100644 index 000000000000..783ef3430a92 --- /dev/null +++ b/tests/coverage/expect/Select.scoverage.check @@ -0,0 +1,139 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Select.scala +covtest +A +Class +covtest.A +print +82 +94 +6 +println +Apply +false +0 +false +println("A") + +2 +tests/coverage/expect/Select.scala +covtest +B +Class +covtest.B +print +166 +179 +11 +print +Apply +false +0 +false +super.print() + +3 +tests/coverage/expect/Select.scala +covtest +B +Class +covtest.B +print +184 +206 +12 +println +Apply +false +0 +false +println(this.instance) + +4 +tests/coverage/expect/Select.scala +covtest +Select$package$ +Class +covtest.Select$package$ +test +237 +240 +15 + +Apply +false +0 +false +A() + +5 +tests/coverage/expect/Select.scala +covtest +Select$package$ +Class +covtest.Select$package$ +test +254 +259 +16 + +Apply +false +0 +false +new A + +6 +tests/coverage/expect/Select.scala +covtest +Select$package$ +Class +covtest.Select$package$ +test +263 +281 +18 +print +Apply +false +0 +false +a.instance.print() + +7 +tests/coverage/expect/Select.scala +covtest +Select$package$ +Class +covtest.Select$package$ +test +345 +354 +19 +print +Apply +false +0 +false +a.print() + diff --git a/tests/coverage/expect/StructuralTypes.scala b/tests/coverage/expect/StructuralTypes.scala index 4b2ee1991ce6..f0472345b9d4 100644 --- a/tests/coverage/expect/StructuralTypes.scala +++ b/tests/coverage/expect/StructuralTypes.scala @@ -1,9 +1,5 @@ -package example +package covtest -/** - * Structural Types: https://dotty.epfl.ch/docs/reference/changed-features/structural-types.html - * Taken from https://github.com/scala/scala3-example-project - */ object StructuralTypes: case class Record(elems: (String, Any)*) extends Selectable: @@ -11,19 +7,8 @@ object StructuralTypes: type Person = Record { val name: String - val age: Int } - val person = Record("name" -> "Emma", "age" -> 42, "salary" -> 320L).asInstanceOf[Person] - - val invalidPerson = Record("name" -> "John", "salary" -> 42).asInstanceOf[Person] - def test(): Unit = - println(person.name) - println(person.age) - - println(invalidPerson.name) - // age field is java.util.NoSuchElementException: None.get - //println(invalidPerson.age) - -end StructuralTypes + val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person] + person.name diff --git a/tests/coverage/expect/StructuralTypes.scoverage.check b/tests/coverage/expect/StructuralTypes.scoverage.check new file mode 100644 index 000000000000..1bc6fb449fdc --- /dev/null +++ b/tests/coverage/expect/StructuralTypes.scoverage.check @@ -0,0 +1,156 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/StructuralTypes.scala +covtest +Record +Class +covtest.Record +$anonfun +159 +171 +5 +== +Apply +false +0 +false +_._1 == name + +2 +tests/coverage/expect/StructuralTypes.scala +covtest +Record +Class +covtest.Record +selectDynamic +148 +172 +5 +find +Apply +false +0 +false +elems.find(_._1 == name) + +3 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +277 +283 +12 +ArrowAssoc +Apply +false +0 +false +"name" + +4 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +277 +293 +12 +-> +Apply +false +0 +false +"name" -> "Emma" + +5 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +295 +300 +12 +ArrowAssoc +Apply +false +0 +false +"age" + +6 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +295 +306 +12 +-> +Apply +false +0 +false +"age" -> 42 + +7 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +270 +307 +12 +apply +Apply +false +0 +false +Record("name" -> "Emma", "age" -> 42) + +8 +tests/coverage/expect/StructuralTypes.scala +covtest +StructuralTypes$ +Class +covtest.StructuralTypes$ +test +333 +344 +13 +selectDynamic +Apply +false +0 +false +person.name + diff --git a/tests/coverage/expect/TraitParams.scala b/tests/coverage/expect/TraitParams.scala deleted file mode 100644 index fa98a9b23805..000000000000 --- a/tests/coverage/expect/TraitParams.scala +++ /dev/null @@ -1,22 +0,0 @@ -package example - -/** - * Trait Parameters: https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html - * Taken from https://github.com/scala/scala3-example-project - */ -object TraitParams: - - trait Base(val msg: String) - class A extends Base("Hello") - class B extends Base("Dotty!") - - // Union types only exist in Scala 3, so there's no chance that this will accidentally be compiled with Scala 2 - private def printMessages(msgs: (A | B)*) = println(msgs.map(_.msg).mkString(" ")) - - def test(): Unit = - printMessages(new A, new B) - - // Sanity check the classpath: this won't run if the Scala 3 jar is not present. - val x: Int => Int = identity - x(1) - diff --git a/tests/coverage/expect/TypeLambdas.scala b/tests/coverage/expect/TypeLambdas.scala index 9769869b7805..9c7641c025e2 100644 --- a/tests/coverage/expect/TypeLambdas.scala +++ b/tests/coverage/expect/TypeLambdas.scala @@ -1,4 +1,4 @@ -package example +package covtest /** * Type Lambdas: https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html diff --git a/tests/coverage/expect/TypeLambdas.scoverage.check b/tests/coverage/expect/TypeLambdas.scoverage.check new file mode 100644 index 000000000000..85b157a52cdd --- /dev/null +++ b/tests/coverage/expect/TypeLambdas.scoverage.check @@ -0,0 +1,122 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +310 +311 +13 +ArrowAssoc +Apply +false +0 +false +1 + +2 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +310 +318 +13 +-> +Apply +false +0 +false +1 -> "1" + +3 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +306 +319 +13 +apply +Apply +false +0 +false +Map(1 -> "1") + +4 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +324 +334 +14 +println +Apply +false +0 +false +println(m) + +5 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +367 +377 +16 +apply +Apply +false +0 +false +("a", "b") + +6 +tests/coverage/expect/TypeLambdas.scala +covtest +TypeLambdas$ +Class +covtest.TypeLambdas$ +test +382 +396 +17 +println +Apply +false +0 +false +println(tuple) + diff --git a/tests/coverage/expect/UnionTypes.scala b/tests/coverage/expect/UnionTypes.scala deleted file mode 100644 index a79d8dd8fca4..000000000000 --- a/tests/coverage/expect/UnionTypes.scala +++ /dev/null @@ -1,44 +0,0 @@ - -package example - -/** - * Union Types: https://dotty.epfl.ch/docs/reference/new-types/union-types.html - * Taken from https://github.com/scala/scala3-example-project - */ -object UnionTypes: - - sealed trait Division - final case class DivisionByZero(msg: String) extends Division - final case class Success(double: Double) extends Division - - // You can create type aliases for your union types (sum types). - type DivisionResult = DivisionByZero | Success - - sealed trait List[+A] - case object Empty extends List[Nothing] - final case class Cons[+A](h: A, t: List[A]) extends List[A] - - private def safeDivide(a: Double, b: Double): DivisionResult = - if b == 0 then DivisionByZero("DivisionByZeroException") else Success(a / b) - - private def either(division: Division) = division match - case DivisionByZero(m) => Left(m) - case Success(d) => Right(d) - - def test(): Unit = - val divisionResultSuccess: DivisionResult = safeDivide(4, 2) - - // commutative - val divisionResultFailure: Success | DivisionByZero = safeDivide(4, 0) - - // calling `either` function with union typed value. - println(either(divisionResultSuccess)) - - // calling `either` function with union typed value. - println(either(divisionResultFailure)) - - val list: Cons[Int] | Empty.type = Cons(1, Cons(2, Cons(3, Empty))) - val emptyList: Empty.type | Cons[Any] = Empty - println(list) - println(emptyList) - diff --git a/tests/coverage/scoverage.coverage.expect b/tests/coverage/scoverage.coverage.expect deleted file mode 100644 index 93a8107f4c15..000000000000 --- a/tests/coverage/scoverage.coverage.expect +++ /dev/null @@ -1,3030 +0,0 @@ -# Coverage data, format version: 3.0 -# Statement data: -# - id -# - source path -# - package name -# - class name -# - class type (Class, Object or Trait) -# - full class name -# - method name -# - start offset -# - end offset -# - line number -# - symbol name -# - tree name -# - is branch -# - invocations count -# - is ignored -# - description (can be multi-line) -# ' ' sign -# ------------------------------------------ -1 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -276 -279 -9 - -Literal -false -0 -false -"d" - -2 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -281 -284 -9 - -Literal -false -0 -false -"o" - -3 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -286 -289 -9 - -Literal -false -0 -false -"t" - -4 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -291 -294 -9 - -Literal -false -0 -false -"t" - -5 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -296 -299 -9 - -Literal -false -0 -false -"y" - -6 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -271 -300 -9 -apply -Apply -false -0 -false -List("d", "o", "t", "t", "y") - -7 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -$anonfun -615 -617 -17 - -Literal -false -0 -false -: - -8 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -$anonfun -613 -619 -17 -apply -Apply -false -0 -false -$i: $s - -9 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -$anonfun -611 -620 -17 -s -Apply -false -0 -false -s"$i: $s" - -10 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -$anonfun -603 -621 -17 -println -Apply -false -0 -false -println(s"$i: $s") - -11 -tests/coverage/expect/ParameterUntupling.scala -example -ParameterUntupling$ -Class -example.ParameterUntupling$ -test -573 -622 -17 -map -Apply -false -0 -false -xs.zipWithIndex.map((s, i) => println(s"$i: $s")) - -12 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -450 -451 -13 - -Literal -false -0 -false -3 - -13 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -455 -458 -13 - -Literal -false -0 -false -"3" - -14 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -442 -459 -13 -println -Apply -false -0 -false -println(3 == "3") - -15 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -473 -474 -15 - -Literal -false -0 -false -3 - -16 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -478 -481 -15 - -Literal -false -0 -false -5.1 - -17 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -501 -502 -17 - -Literal -false -0 -false -1 - -18 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -504 -505 -17 - -Literal -false -0 -false -2 - -19 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -496 -506 -17 -apply -Apply -false -0 -false -List(1, 2) - -20 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -517 -518 -17 - -Literal -false -0 -false -1 - -21 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -520 -521 -17 - -Literal -false -0 -false -2 - -22 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -510 -522 -17 -apply -Apply -false -0 -false -Vector(1, 2) - -23 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -496 -522 -17 -== -Apply -false -0 -false -List(1, 2) == Vector(1, 2) - -24 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -488 -523 -17 -println -Apply -false -0 -false -println(List(1, 2) == Vector(1, 2)) - -25 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -578 -579 -22 - -Select -false -0 -false -A - -26 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -580 -581 -22 - -Literal -false -0 -false -4 - -27 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -595 -596 -23 - -Select -false -0 -false -B - -28 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -597 -598 -23 - -Literal -false -0 -false -4 - -29 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -694 -709 -28 -println -Apply -false -0 -false -println(a != b) - -30 -tests/coverage/expect/MultiversalEquality.scala -example -MultiversalEquality$ -Class -example.MultiversalEquality$ -test -714 -729 -29 -println -Apply -false -0 -false -println(b == a) - -31 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -547 -558 -24 -- -Apply -false -0 -false -p2.y - p1.y - -32 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -560 -561 -24 - -Literal -false -0 -false -2 - -33 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -538 -562 -24 -pow -Apply -false -0 -false -Math.pow(p2.y - p1.y, 2) - -34 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -574 -585 -24 -- -Apply -false -0 -false -p2.x - p1.x - -35 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -587 -588 -24 - -Literal -false -0 -false -2 - -36 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -565 -589 -24 -pow -Apply -false -0 -false -Math.pow(p2.x - p1.x, 2) - -37 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -538 -589 -24 -+ -Apply -false -0 -false -Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2) - -38 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -euclideanDistance -528 -590 -24 -sqrt -Apply -false -0 -false -Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)) - -39 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -test -614 -615 -26 - -Literal -false -0 -false -3 - -40 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -test -617 -618 -26 - -Literal -false -0 -false -4 - -41 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -test -643 -644 -27 - -Literal -false -0 -false -6 - -42 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -test -646 -647 -27 - -Literal -false -0 -false -8 - -43 -tests/coverage/expect/ContextFunctions.scala -example -IntersectionTypes$ -Class -example.IntersectionTypes$ -test -653 -687 -28 -println -Apply -false -0 -false -println(euclideanDistance(p1, p2)) - -44 -tests/coverage/expect/TypeLambdas.scala -example -TypeLambdas$ -Class -example.TypeLambdas$ -test -310 -311 -13 - -Literal -false -0 -false -1 - -45 -tests/coverage/expect/TypeLambdas.scala -example -TypeLambdas$ -Class -example.TypeLambdas$ -test -315 -318 -13 - -Literal -false -0 -false -"1" - -46 -tests/coverage/expect/TypeLambdas.scala -example -TypeLambdas$ -Class -example.TypeLambdas$ -test -306 -319 -13 -apply -Apply -false -0 -false -Map(1 -> "1") - -47 -tests/coverage/expect/TypeLambdas.scala -example -TypeLambdas$ -Class -example.TypeLambdas$ -test -368 -371 -16 - -Literal -false -0 -false -"a" - -48 -tests/coverage/expect/TypeLambdas.scala -example -TypeLambdas$ -Class -example.TypeLambdas$ -test -373 -376 -16 - -Literal -false -0 -false -"b" - -49 -tests/coverage/expect/StructuralTypes.scala -example -Record -Class -example.Record -selectDynamic -319 -343 -9 -find -Apply -false -0 -false -elems.find(_._1 == name) - -50 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -442 -448 -16 - -Literal -false -0 -false -"name" - -51 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -452 -458 -16 - -Literal -false -0 -false -"Emma" - -52 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -460 -465 -16 - -Literal -false -0 -false -"age" - -53 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -469 -471 -16 - -Literal -false -0 -false -42 - -54 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -473 -481 -16 - -Literal -false -0 -false -"salary" - -55 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -485 -489 -16 - -Literal -false -0 -false -320L - -56 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -435 -490 -16 -apply -Apply -false -0 -false -Record("name" -> "Emma", "age" -> 42, "salary" -> 320L) - -57 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -542 -548 -18 - -Literal -false -0 -false -"name" - -58 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -552 -558 -18 - -Literal -false -0 -false -"John" - -59 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -560 -568 -18 - -Literal -false -0 -false -"salary" - -60 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -572 -574 -18 - -Literal -false -0 -false -42 - -61 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ - -535 -575 -18 -apply -Apply -false -0 -false -Record("name" -> "John", "salary" -> 42) - -62 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ -test -623 -643 -21 -println -Apply -false -0 -false -println(person.name) - -63 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ -test -648 -667 -22 -println -Apply -false -0 -false -println(person.age) - -64 -tests/coverage/expect/StructuralTypes.scala -example -StructuralTypes$ -Class -example.StructuralTypes$ -test -673 -700 -24 -println -Apply -false -0 -false -println(invalidPerson.name) - -65 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -safeDivide -663 -664 -21 - -Literal -false -0 -false -0 - -66 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -safeDivide -685 -710 -21 - -Literal -false -0 -false -"DivisionByZeroException" - -67 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -safeDivide -670 -711 -21 -apply -Apply -true -0 -false -DivisionByZero("DivisionByZeroException") - -68 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -safeDivide -717 -731 -21 -apply -Apply -false -0 -false -Success(a / b) - -69 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -safeDivide -717 -731 -21 - -Block -true -0 -false -Success(a / b) - -70 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -949 -950 -28 - -Literal -false -0 -false -4 - -71 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -952 -953 -28 - -Literal -false -0 -false -2 - -72 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1044 -1045 -31 - -Literal -false -0 -false -4 - -73 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1047 -1048 -31 - -Literal -false -0 -false -0 - -74 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1112 -1150 -34 -println -Apply -false -0 -false -println(either(divisionResultSuccess)) - -75 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1213 -1251 -37 -println -Apply -false -0 -false -println(either(divisionResultFailure)) - -76 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1297 -1298 -39 - -Literal -false -0 -false -1 - -77 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1305 -1306 -39 - -Literal -false -0 -false -2 - -78 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1313 -1314 -39 - -Literal -false -0 -false -3 - -79 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1300 -1323 -39 -apply -Apply -false -0 -false -Cons(2, Cons(3, Empty)) - -80 -tests/coverage/expect/UnionTypes.scala -example -UnionTypes$ -Class -example.UnionTypes$ -test -1292 -1324 -39 -apply -Apply -false -0 -false -Cons(1, Cons(2, Cons(3, Empty))) - -81 -tests/coverage/expect/PatternMatching.scala -example -Even$ -Class -example.Even$ -unapply -296 -306 -12 -% -Select -false -0 -false -s.length % - -82 -tests/coverage/expect/PatternMatching.scala -example -Even$ -Class -example.Even$ -unapply -307 -308 -12 - -Literal -false -0 -false -2 - -83 -tests/coverage/expect/PatternMatching.scala -example -Even$ -Class -example.Even$ -unapply -312 -313 -12 - -Literal -false -0 -false -0 - -84 -tests/coverage/expect/PatternMatching.scala -example -Person$ -Class -example.Person$ -unapply -797 -803 -29 - -Select -false -0 -false -Person - -85 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1047 -1062 -37 -split -Select -false -0 -false -name.trim.split - -86 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1063 -1066 -37 - -Literal -false -0 -false -" " - -87 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1092 -1093 -38 - -Literal -false -0 -false -2 - -88 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1099 -1103 -38 -None -Ident -true -0 -false -None - -89 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1161 -1162 -39 - -Literal -false -0 -false -1 - -90 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1150 -1163 -39 -refArrayOps -Apply -false -0 -false -names.drop(1) - -91 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1174 -1175 -39 - -Literal -false -0 -false -1 - -92 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1150 -1176 -39 -wrapRefArray -Apply -false -0 -false -names.drop(1).dropRight(1) - -93 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1117 -1184 -39 -apply -Apply -false -0 -false -Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) - -94 -tests/coverage/expect/PatternMatching.scala -example -Names$ -Class -example.Names$ -unapplySeq -1117 -1184 -39 - -Block -true -0 -false -Some(names.last :: names.head :: names.drop(1).dropRight(1).toList) - -95 -tests/coverage/expect/PatternMatching.scala -example -Name$ -Class -example.Name$ -unapply -1361 -1365 -49 - -Select -false -0 -false -Name - -96 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1425 -1431 -55 - -Literal -false -0 -false -"even" - -97 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1475 -1508 -56 - -Literal -false -0 -false - has an even number of characters - -98 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1473 -1508 -56 -apply -Apply -false -0 -false -$s has an even number of characters - -99 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1471 -1509 -56 -s -Apply -false -0 -false -s"$s has an even number of characters" - -100 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1463 -1510 -56 -println -Apply -false -0 -false -println(s"$s has an even number of characters") - -101 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1548 -1580 -57 - -Literal -false -0 -false - has an odd number of characters - -102 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1546 -1580 -57 -apply -Apply -false -0 -false -$s has an odd number of characters - -103 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1544 -1581 -57 -s -Apply -false -0 -false -s"$s has an odd number of characters" - -104 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1536 -1582 -57 -println -Apply -false -0 -false -println(s"$s has an odd number of characters") - -105 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -containsConsecutive -1775 -1810 -61 -containsConsecutive -Apply -false -0 -false -containsConsecutive(b :: xs.toList) - -106 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -containsConsecutive -1843 -1848 -62 - -Literal -false -0 -false -false - -107 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1887 -1888 -64 - -Literal -false -0 -false -1 - -108 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1890 -1891 -64 - -Literal -false -0 -false -2 - -109 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1893 -1894 -64 - -Literal -false -0 -false -3 - -110 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1896 -1897 -64 - -Literal -false -0 -false -4 - -111 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1899 -1900 -64 - -Literal -false -0 -false -5 - -112 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1882 -1901 -64 -apply -Apply -false -0 -false -List(1, 2, 3, 4, 5) - -113 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1862 -1902 -64 -containsConsecutive -Apply -false -0 -false -containsConsecutive(List(1, 2, 3, 4, 5)) - -114 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1854 -1903 -64 -println -Apply -false -0 -false -println(containsConsecutive(List(1, 2, 3, 4, 5))) - -115 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1941 -1942 -65 - -Literal -false -0 -false -1 - -116 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1944 -1945 -65 - -Literal -false -0 -false -2 - -117 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1947 -1948 -65 - -Literal -false -0 -false -3 - -118 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1950 -1951 -65 - -Literal -false -0 -false -3 - -119 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1953 -1954 -65 - -Literal -false -0 -false -5 - -120 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1936 -1955 -65 -apply -Apply -false -0 -false -List(1, 2, 3, 3, 5) - -121 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1916 -1956 -65 -containsConsecutive -Apply -false -0 -false -containsConsecutive(List(1, 2, 3, 3, 5)) - -122 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1908 -1957 -65 -println -Apply -false -0 -false -println(containsConsecutive(List(1, 2, 3, 3, 5))) - -123 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -1992 -1998 -68 - -Literal -false -0 -false -"john" - -124 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2000 -2002 -68 - -Literal -false -0 -false -42 - -125 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2048 -2054 -69 - -Literal -false -0 -false -name: - -126 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2056 -2063 -69 - -Literal -false -0 -false -, age: - -127 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2048 -2065 -69 -apply -Apply -false -0 -false -name: $n, age: $a - -128 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2046 -2066 -69 -s -Apply -false -0 -false -s"name: $n, age: $a" - -129 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2038 -2067 -69 -println -Apply -false -0 -false -println(s"name: $n, age: $a") - -130 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -greet -2189 -2205 -74 - -Literal -false -0 -false -"Good morning, " - -131 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -greet -2220 -2223 -74 - -Literal -false -0 -false -" " - -132 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -greet -2237 -2240 -74 - -Literal -false -0 -false -"!" - -133 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -greet -2286 -2335 -75 - -Literal -false -0 -false -"Welcome! Please make sure to fill in your name!" - -134 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2359 -2372 -77 - -Literal -false -0 -false -"Alan Turing" - -135 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2345 -2374 -77 -println -Apply -false -0 -false -println(greet("Alan Turing")) - -136 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2393 -2399 -78 - -Literal -false -0 -false -"john" - -137 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2379 -2401 -78 -println -Apply -false -0 -false -println(greet("john")) - -138 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2420 -2445 -79 - -Literal -false -0 -false -"Wolfgang Amadeus Mozart" - -139 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2406 -2447 -79 -println -Apply -false -0 -false -println(greet("Wolfgang Amadeus Mozart")) - -140 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2478 -2485 -82 - -Literal -false -0 -false -"alice" - -141 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2525 -2533 -83 - -Literal -false -0 -false -name is - -142 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2525 -2535 -83 -apply -Apply -false -0 -false -name is $n - -143 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2523 -2536 -83 -s -Apply -false -0 -false -s"name is $n" - -144 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2515 -2537 -83 -println -Apply -false -0 -false -println(s"name is $n") - -145 -tests/coverage/expect/PatternMatching.scala -example -PatternMatching$ -Class -example.PatternMatching$ -test -2568 -2580 -84 - -Literal -false -0 -false -"empty name" - -146 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -printMessages -475 -490 -13 -map -Apply -false -0 -false -msgs.map(_.msg) - -147 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -printMessages -500 -503 -13 - -Literal -false -0 -false -" " - -148 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -printMessages -467 -505 -13 -println -Apply -false -0 -false -println(msgs.map(_.msg).mkString(" ")) - -149 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -test -550 -551 -16 - -Select -false -0 -false -A - -150 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -test -557 -558 -16 - -Select -false -0 -false -B - -151 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -test -532 -559 -16 -printMessages -Apply -false -0 -false -printMessages(new A, new B) - -152 -tests/coverage/expect/TraitParams.scala -example -TraitParams$ -Class -example.TraitParams$ -test -685 -686 -20 - -Literal -false -0 -false -1 - -153 -tests/coverage/expect/Enum.scala -example -Planet -Class -example.Planet - -326 -337 -14 - -Literal -false -0 -false -6.67300E-11 - -154 -tests/coverage/expect/Enum.scala -example -Planet -Class -example.Planet -surfaceGravity -363 -390 -15 -/ -Apply -false -0 -false -G * mass / (radius * radius - -155 -tests/coverage/expect/Enum.scala -example -Planet -Class -example.Planet -surfaceWeight -436 -462 -16 -* -Apply -false -0 -false -otherMass * surfaceGravity - -156 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -988 -989 -30 - -Literal -false -0 -false -1 - -157 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1005 -1006 -30 - -Literal -false -0 -false -2 - -158 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1022 -1023 -30 - -Literal -false -0 -false -3 - -159 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1008 -1040 -30 -apply -Apply -false -0 -false -ListEnum.Cons(3, ListEnum.Empty) - -160 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -991 -1041 -30 -apply -Apply -false -0 -false -ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) - -161 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -974 -1042 -30 -apply -Apply -false -0 -false -ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) - -162 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1055 -1070 -31 - -Literal -false -0 -false -"Example 1: \n" - -163 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1047 -1081 -31 -println -Apply -false -0 -false -println("Example 1: \n"+emptyList) - -164 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1103 -1105 -32 - -Literal -false -0 -false -\n - -165 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1096 -1105 -32 -apply -Apply -false -0 -false -${list}\n - -166 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1094 -1106 -32 -s -Apply -false -0 -false -s"${list}\n" - -167 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1086 -1107 -32 -println -Apply -false -0 -false -println(s"${list}\n") - -168 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -calculateEarthWeightOnPlanets -1187 -1226 -35 -/ -Apply -false -0 -false -earthWeight/Planet.Earth.surfaceGravity - -169 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -calculateEarthWeightOnPlanets -1242 -1255 -36 -refArrayOps -Apply -false -0 -false -Planet.values - -170 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -$anonfun -1277 -1292 -37 - -Literal -false -0 -false -Your weight on - -171 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -$anonfun -1294 -1298 -37 - -Literal -false -0 -false - is - -172 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -$anonfun -1277 -1322 -37 -apply -Apply -false -0 -false -Your weight on $p is ${p.surfaceWeight(mass)} - -173 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -$anonfun -1275 -1323 -37 -s -Apply -false -0 -false -s"Your weight on $p is ${p.surfaceWeight(mass)}" - -174 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -$anonfun -1267 -1324 -37 -println -Apply -false -0 -false -println(s"Your weight on $p is ${p.surfaceWeight(mass)}") - -175 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -calculateEarthWeightOnPlanets -1233 -1324 -36 -foreach -Apply -false -0 -false -for p <- Planet.values do - println(s"Your weight on $p is ${p.surfaceWeight(mass)}") - -176 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1338 -1350 -39 - -Literal -false -0 -false -"Example 2:" - -177 -tests/coverage/expect/Enum.scala -example -EnumTypes$ -Class -example.EnumTypes$ -test -1386 -1388 -40 - -Literal -false -0 -false -80 - From bad8b86903ce9007467e58d2f6f415513b50d3fc Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 24 Mar 2022 18:23:39 +0100 Subject: [PATCH 054/121] Fix contexts in instrumentCoverage Also add more tests and fix the remaining todo --- .../dotty/tools/dotc/coverage/Location.scala | 13 +- .../dotc/transform/InstrumentCoverage.scala | 89 +++--- .../dotty/tools/dotc/transform/Mixin.scala | 1 + .../tools/dotc/coverage/CoverageTests.scala | 1 + tests/coverage/expect/Constructor.scala | 11 + .../expect/Constructor.scoverage.check | 88 ++++++ tests/coverage/expect/Enum.scoverage.check | 269 ++++++++++++++--- tests/coverage/expect/Givens.scala | 1 - tests/coverage/expect/Givens.scoverage.check | 69 ++--- tests/coverage/expect/Inheritance.scala | 11 + .../expect/Inheritance.scoverage.check | 173 +++++++++++ tests/coverage/expect/Inlined.scala | 8 + tests/coverage/expect/Inlined.scoverage.check | 275 ++++++++++++++++++ tests/coverage/expect/Lifting.scala | 6 + tests/coverage/expect/Lifting.scoverage.check | 156 ++++++++-- .../coverage/expect/Literals.scoverage.check | 8 +- .../expect/MatchCaseClasses.scoverage.check | 14 +- .../expect/MatchNumbers.scoverage.check | 31 +- .../ParamsAndInterpolation.scoverage.check | 70 +++-- tests/coverage/expect/Select.scoverage.check | 35 ++- .../expect/StructuralTypes.scoverage.check | 12 +- tests/coverage/expect/Trait.scala | 15 + tests/coverage/expect/Trait.scoverage.check | 190 ++++++++++++ .../expect/TypeLambdas.scoverage.check | 12 +- 24 files changed, 1328 insertions(+), 230 deletions(-) create mode 100644 tests/coverage/expect/Constructor.scala create mode 100644 tests/coverage/expect/Constructor.scoverage.check create mode 100644 tests/coverage/expect/Inheritance.scala create mode 100644 tests/coverage/expect/Inheritance.scoverage.check create mode 100644 tests/coverage/expect/Inlined.scala create mode 100644 tests/coverage/expect/Inlined.scoverage.check create mode 100644 tests/coverage/expect/Trait.scala create mode 100644 tests/coverage/expect/Trait.scoverage.check diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala index 65f8d8adb5ee..0c0181baa305 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Location.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -3,6 +3,7 @@ package coverage import ast.tpd._ import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags.* /** @param packageName * the name of the encosing package @@ -23,14 +24,20 @@ final case class Location( object Location: def apply(tree: Tree)(using ctx: Context): Location = - val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString() - val className = ctx.owner.denot.enclosingClass.name.toSimpleName.toString() + val enclosingClass = ctx.owner.denot.enclosingClass + val packageName = ctx.owner.denot.enclosingPackageClass.name.toSimpleName.toString + val className = enclosingClass.name.toSimpleName.toString + + val classType: String = + if enclosingClass.is(Trait) then "Trait" + else if enclosingClass.is(ModuleClass) then "Object" + else "Class" Location( packageName, className, s"$packageName.$className", - "Class" /* TODO refine this further */, + classType, ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), ctx.source.file.absolute.toString() ) diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 29d2209e1ec3..761a69613472 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -6,18 +6,16 @@ import java.util.concurrent.atomic.AtomicInteger import collection.mutable import core.Flags.JavaDefined -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.DenotTransformers.IdentityDenotTransformer -import dotty.tools.dotc.coverage.Coverage -import dotty.tools.dotc.coverage.Statement -import dotty.tools.dotc.coverage.Serializer -import dotty.tools.dotc.coverage.Location -import dotty.tools.dotc.core.Symbols.defn -import dotty.tools.dotc.core.Symbols.Symbol -import dotty.tools.dotc.core.Decorators.toTermName -import dotty.tools.dotc.util.{SourcePosition, Property} -import dotty.tools.dotc.core.Constants.Constant -import dotty.tools.dotc.typer.LiftCoverage +import core.Contexts.{Context, ctx, inContext} +import core.DenotTransformers.IdentityDenotTransformer +import core.Symbols.{defn, Symbol} +import core.Decorators.{toTermName, i} +import core.Constants.Constant +import typer.LiftCoverage +import util.{SourcePosition, Property} +import util.Spans.Span +import coverage.* +import dotty.tools.dotc.util.SourceFile /** Implements code coverage by inserting calls to scala.runtime.Invoker * ("instruments" the source code). @@ -105,7 +103,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted instrumentLifted(transformed)(using ignoreLiteralsContext) else - val transformed = cpy.Apply(tree)(transformedFun, transform(args)) + val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) instrument(transformed)(using ignoreLiteralsContext) // f(args) @@ -136,41 +134,47 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: case tree: CaseDef => instrumentCaseDef(tree) case tree: ValDef => - // only transform the rhs - cpy.ValDef(tree)(rhs = transform(tree.rhs)) + // only transform the rhs, in the local context + val rhs = transform(tree.rhs)(using localCtx(tree)) + cpy.ValDef(tree)(rhs=rhs) + + case tree: DefDef => + // only transform the params (for the default values) and the rhs + val defCtx = localCtx(tree) + val paramss = transformParamss(tree.paramss)(using defCtx) + val rhs = transform(tree.rhs)(using defCtx) + cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs) case tree: PackageDef => // only transform the statements of the package - cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)(using localCtx(tree))) case tree: Assign => // only transform the rhs cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) - case tree: Template => - // Don't instrument the parents (extends) of a template since it - // causes problems if the parent constructor takes parameters - cpy.Template(tree)( - constr = super.transformSub(tree.constr), - body = transform(tree.body) - ) // For everything else just recurse and transform + // Special care for Templates: it's important to set the owner of the `stats`, like super.transform case _ => super.transform(tree) + /** Lifts and instruments an application. + * Note that if only one arg needs to be lifted, we just lift everything. + */ def instrumentLifted(tree: Apply)(using Context) = - val buffer = mutable.ListBuffer[Tree]() - // NOTE: that if only one arg needs to be lifted, we just lift everything - val lifted = LiftCoverage.liftForCoverage(buffer, tree) - val instrumented = buffer.toList.map(transform) - // We can now instrument the apply as it is with a custom position to point to the function - Block( - instrumented, - instrument( - lifted, - tree.sourcePos, - false - ) - ) + // withSource is necessary to position inlined code properly + inContext(ctx.withSource(tree.source)) { + // lifting + val buffer = mutable.ListBuffer[Tree]() + val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) + + // instrumentation + val instrumentedArgs = buffer.toList.map(transform) + val instrumentedApply = instrument(liftedApply) + Block( + instrumentedArgs, + instrumentedApply + ).withSpan(instrumentedApply.span) + } def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = cases.map(instrumentCaseDef) @@ -197,16 +201,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: branch ) coverage.addStatement(statement) - Block(List(invokeCall(id)), tree) + inContext(ctx.withSource(tree.source)) { + val span = Span(pos.start, pos.end) // synthetic span + Block(List(invokeCall(id, span)), tree).withSpan(span) + } else tree - def invokeCall(id: Int)(using Context): Tree = - ref(defn.InvokerModuleRef) - .select("invoked".toTermName) + def invokeCall(id: Int, span: Span)(using Context): Tree = + ref(defn.InvokerModuleRef).withSpan(span) + .select("invoked".toTermName).withSpan(span) .appliedToArgs( List(Literal(Constant(id)), Literal(Constant(outputPath))) - ) + ).withSpan(span) /** * Checks if the apply needs a lift in the coverage phase. diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index b5881d88f5f1..4ab6a5c9a646 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -214,6 +214,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => initFlags = stat.symbol.flags | PrivateLocal ).installAfter(thisPhase) stat.symbol.enteredAfter(thisPhase) + case _ => } (scall, stats ::: inits, args) case _ => diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 4e11957cb034..6142f1ddb5e1 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -52,6 +52,7 @@ class CoverageTests: def computeCoverageInTmp(inputFiles: Seq[Path], sourceRoot: String): Path = val target = Files.createTempDirectory("coverage") val args = Array( + "-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-coverage-sourceroot", diff --git a/tests/coverage/expect/Constructor.scala b/tests/coverage/expect/Constructor.scala new file mode 100644 index 000000000000..251370ec8e6e --- /dev/null +++ b/tests/coverage/expect/Constructor.scala @@ -0,0 +1,11 @@ +package covtest + +class C: + def f(x: Int) = x + def x = 1 + f(x) + +object O: + def g(y: Int) = y + def y = 1 + g(y) diff --git a/tests/coverage/expect/Constructor.scoverage.check b/tests/coverage/expect/Constructor.scoverage.check new file mode 100644 index 000000000000..ebc833756efc --- /dev/null +++ b/tests/coverage/expect/Constructor.scoverage.check @@ -0,0 +1,88 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Constructor.scala +covtest +C +Class +covtest.C +x +56 +57 +4 + +Literal +false +0 +false +1 + +2 +tests/coverage/expect/Constructor.scala +covtest +C +Class +covtest.C + +60 +64 +5 +f +Apply +false +0 +false +f(x) + +3 +tests/coverage/expect/Constructor.scala +covtest +O$ +Object +covtest.O$ +y +106 +107 +9 + +Literal +false +0 +false +1 + +4 +tests/coverage/expect/Constructor.scala +covtest +O$ +Object +covtest.O$ + +110 +114 +10 +g +Apply +false +0 +false +g(y) + diff --git a/tests/coverage/expect/Enum.scoverage.check b/tests/coverage/expect/Enum.scoverage.check index 001fb41321a1..dbcce91d5301 100644 --- a/tests/coverage/expect/Enum.scoverage.check +++ b/tests/coverage/expect/Enum.scoverage.check @@ -62,7 +62,7 @@ surfaceGravity 359 367 14 -* +invoked Apply false 0 @@ -76,6 +76,23 @@ Planet Class covtest.Planet surfaceGravity +359 +367 +14 +* +Apply +false +0 +false +G * mass + +5 +tests/coverage/expect/Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity 371 386 14 @@ -86,7 +103,7 @@ false false radius * radius -5 +6 tests/coverage/expect/Enum.scala covtest Planet @@ -103,7 +120,7 @@ false false G * mass / (radius * radius -6 +7 tests/coverage/expect/Enum.scala covtest Planet @@ -120,11 +137,147 @@ false false otherMass * surfaceGravity -7 +8 tests/coverage/expect/Enum.scala covtest -EnumTypes$ +$anon +Class +covtest.$anon + +485 +512 +17 + +Apply +false +0 +false +Planet(3.303e+23, 2.4397e6) + +9 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +538 +565 +18 + +Apply +false +0 +false +Planet(4.869e+24, 6.0518e6) + +10 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +591 +619 +19 + +Apply +false +0 +false +Planet(5.976e+24, 6.37814e6) + +11 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +645 +672 +20 + +Apply +false +0 +false +Planet(6.421e+23, 3.3972e6) + +12 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +698 +725 +21 + +Apply +false +0 +false +Planet(1.9e+27, 7.1492e7) + +13 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +751 +778 +22 + +Apply +false +0 +false +Planet(5.688e+26, 6.0268e7) + +14 +tests/coverage/expect/Enum.scala +covtest +$anon +Class +covtest.$anon + +804 +831 +23 + +Apply +false +0 +false +Planet(8.686e+25, 2.5559e7) + +15 +tests/coverage/expect/Enum.scala +covtest +$anon Class +covtest.$anon + +857 +884 +24 + +Apply +false +0 +false +Planet(1.024e+26, 2.4746e7) + +16 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Object covtest.EnumTypes$ test 1004 @@ -137,11 +290,11 @@ false false ListEnum.Cons(3, ListEnum.Empty) -8 +17 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 987 @@ -154,11 +307,11 @@ false false ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) -9 +18 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 970 @@ -171,11 +324,11 @@ false false ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) -10 +19 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1051 @@ -188,11 +341,11 @@ false false "Example 1: \n"+emptyList -11 +20 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1043 @@ -205,11 +358,11 @@ false false println("Example 1: \n"+emptyList) -12 +21 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1092 @@ -222,11 +375,28 @@ false false ${list}\n -13 +22 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object +covtest.EnumTypes$ +test +1092 +1101 +31 +invoked +Apply +false +0 +false +${list}\n + +23 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Object covtest.EnumTypes$ test 1092 @@ -239,11 +409,11 @@ false false ${list}\n -14 +24 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1090 @@ -256,11 +426,11 @@ false false s"${list}\n" -15 +25 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1082 @@ -273,11 +443,11 @@ false false println(s"${list}\n") -16 +26 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ calculateEarthWeightOnPlanets 1183 @@ -290,11 +460,11 @@ false false earthWeight/Planet.Earth.surfaceGravity -17 +27 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ calculateEarthWeightOnPlanets 1238 @@ -307,11 +477,11 @@ false false Planet.values -18 +28 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ $anonfun 1273 @@ -324,11 +494,28 @@ false false Your weight on $p is ${p.surfaceWeight(mass)} -19 +29 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object +covtest.EnumTypes$ +$anonfun +1273 +1318 +36 +invoked +Apply +false +0 +false +Your weight on $p is ${p.surfaceWeight(mass)} + +30 +tests/coverage/expect/Enum.scala +covtest +EnumTypes$ +Object covtest.EnumTypes$ $anonfun 1273 @@ -341,11 +528,11 @@ false false Your weight on $p is ${p.surfaceWeight(mass)} -20 +31 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ $anonfun 1296 @@ -358,11 +545,11 @@ false false p.surfaceWeight(mass) -21 +32 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ $anonfun 1271 @@ -375,11 +562,11 @@ false false s"Your weight on $p is ${p.surfaceWeight(mass)}" -22 +33 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ $anonfun 1263 @@ -392,11 +579,11 @@ false false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -23 +34 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ calculateEarthWeightOnPlanets 1229 @@ -410,11 +597,11 @@ false for p <- Planet.values do println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -24 +35 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1326 @@ -427,11 +614,11 @@ false false println("Example 2:") -25 +36 tests/coverage/expect/Enum.scala covtest EnumTypes$ -Class +Object covtest.EnumTypes$ test 1352 diff --git a/tests/coverage/expect/Givens.scala b/tests/coverage/expect/Givens.scala index ae29f6eb7f8f..34d740e146b9 100644 --- a/tests/coverage/expect/Givens.scala +++ b/tests/coverage/expect/Givens.scala @@ -18,7 +18,6 @@ object Givens: def getMessage(i: Int): String = i.toString def test2() = - // TODO FIXME: this does not work if we remove the def declaration (compiler crash in LambdaLift) given c: Context = Context(0) printContext("test")(using c) printContext(getMessage(123)) diff --git a/tests/coverage/expect/Givens.scoverage.check b/tests/coverage/expect/Givens.scoverage.check index 1235a0387f5c..aec733eca38a 100644 --- a/tests/coverage/expect/Givens.scoverage.check +++ b/tests/coverage/expect/Givens.scoverage.check @@ -22,7 +22,7 @@ tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test 183 @@ -39,7 +39,7 @@ false tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test 175 @@ -56,7 +56,7 @@ println(3 == "3") tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test 205 @@ -73,7 +73,7 @@ false tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test 197 @@ -90,7 +90,7 @@ println(3 == 5.1) tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ printContext 280 @@ -107,7 +107,7 @@ println(msg) tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ printContext 297 @@ -124,7 +124,7 @@ println(ctx.id) tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ getMessage 349 @@ -141,29 +141,12 @@ i.toString tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test2 -510 -511 -21 - -Literal -false -0 -false -0 - -9 -tests/coverage/expect/Givens.scala -covtest -Givens$ -Class -covtest.Givens$ -test2 -502 -512 -21 +400 +410 +20 Apply false @@ -171,16 +154,16 @@ false false Context(0) -10 +9 tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test2 -517 -546 -22 +415 +444 +21 printContext Apply false @@ -188,16 +171,16 @@ false false printContext("test")(using c) -11 +10 tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test2 -564 -579 -23 +462 +477 +22 getMessage Apply false @@ -205,16 +188,16 @@ false false getMessage(123) -12 +11 tests/coverage/expect/Givens.scala covtest Givens$ -Class +Object covtest.Givens$ test2 -551 -580 -23 +449 +478 +22 printContext Apply false diff --git a/tests/coverage/expect/Inheritance.scala b/tests/coverage/expect/Inheritance.scala new file mode 100644 index 000000000000..e90fb87237ea --- /dev/null +++ b/tests/coverage/expect/Inheritance.scala @@ -0,0 +1,11 @@ +package covtest + +class A(val x: Int, val y: Int) +class B(x: Int) extends A(x, 0) +class C1 extends B({println("block"); 1}) +class C2 extends B(A(2,2).x) + +def testInheritance = + println(C1().x) // block + // 1 + println(C2().x) // 2 diff --git a/tests/coverage/expect/Inheritance.scoverage.check b/tests/coverage/expect/Inheritance.scoverage.check new file mode 100644 index 000000000000..8fad938ba60b --- /dev/null +++ b/tests/coverage/expect/Inheritance.scoverage.check @@ -0,0 +1,173 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Inheritance.scala +covtest +B +Class +covtest.B + +73 +80 +3 + +Apply +false +0 +false +A(x, 0) + +2 +tests/coverage/expect/Inheritance.scala +covtest +C1 +Class +covtest.C1 + +101 +117 +4 +println +Apply +false +0 +false +println("block") + +3 +tests/coverage/expect/Inheritance.scala +covtest +C1 +Class +covtest.C1 + +98 +122 +4 + +Apply +false +0 +false +B({println("block"); 1}) + +4 +tests/coverage/expect/Inheritance.scala +covtest +C2 +Class +covtest.C2 + +142 +148 +5 + +Apply +false +0 +false +A(2,2) + +5 +tests/coverage/expect/Inheritance.scala +covtest +C2 +Class +covtest.C2 + +140 +151 +5 + +Apply +false +0 +false +B(A(2,2).x) + +6 +tests/coverage/expect/Inheritance.scala +covtest +Inheritance$package$ +Object +covtest.Inheritance$package$ +testInheritance +185 +189 +8 + +Apply +false +0 +false +C1() + +7 +tests/coverage/expect/Inheritance.scala +covtest +Inheritance$package$ +Object +covtest.Inheritance$package$ +testInheritance +177 +192 +8 +println +Apply +false +0 +false +println(C1().x) + +8 +tests/coverage/expect/Inheritance.scala +covtest +Inheritance$package$ +Object +covtest.Inheritance$package$ +testInheritance +235 +239 +10 + +Apply +false +0 +false +C2() + +9 +tests/coverage/expect/Inheritance.scala +covtest +Inheritance$package$ +Object +covtest.Inheritance$package$ +testInheritance +227 +242 +10 +println +Apply +false +0 +false +println(C2().x) + diff --git a/tests/coverage/expect/Inlined.scala b/tests/coverage/expect/Inlined.scala new file mode 100644 index 000000000000..ebe0503716a7 --- /dev/null +++ b/tests/coverage/expect/Inlined.scala @@ -0,0 +1,8 @@ +package covtest + +// Checks that we use the new positions of the inlined code properly +def testInlined(): Unit = + val l = 1 + assert(l == 1) // scala.Predef.assert is inline in dotty + assert(l == List(l).length) + assert(List(l).length == 1) diff --git a/tests/coverage/expect/Inlined.scoverage.check b/tests/coverage/expect/Inlined.scoverage.check new file mode 100644 index 000000000000..41a8b9e0aadd --- /dev/null +++ b/tests/coverage/expect/Inlined.scoverage.check @@ -0,0 +1,275 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +122 +123 +4 + +Literal +false +0 +false +1 + +2 +library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +133 +139 +5 +== +Apply +false +0 +false +l == 1 + +3 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false + + +4 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false + + +5 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false + + +6 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +197 +204 +6 +apply +Apply +false +0 +false +List(l) + +7 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +192 +211 +6 +== +Apply +false +0 +false +l == List(l).length + +8 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false + + +9 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false + + +10 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false + + +11 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +229 +7 +apply +Apply +false +0 +false +List(l) + +12 +library/src/scala/runtime/stdLibPatches/Predef.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +222 +241 +7 +== +Apply +false +0 +false +List(l).length == 1 + +13 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +367 +9 +Scala3RunTime +Select +false +0 +false + + +14 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 +assertFailed +Apply +false +0 +false + + +15 +tests/coverage/expect/Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +340 +382 +9 + +Block +true +0 +false + + diff --git a/tests/coverage/expect/Lifting.scala b/tests/coverage/expect/Lifting.scala index 5e41eb172db3..a948e1ad575a 100644 --- a/tests/coverage/expect/Lifting.scala +++ b/tests/coverage/expect/Lifting.scala @@ -1,5 +1,9 @@ package covtest +class Vals: + val l = List(1) + val ll = l :: List(1,2,3) + class A: def msg(a: Int, b: Int, c: Int) = "string" def integer: Int = 0 @@ -8,5 +12,7 @@ class A: def test(): Unit = val a = A() val i = 123 + def f() = -1 a.msg(i, 0, a.integer) a.ex.msg(i, 0, a.ex.integer) + a.msg(f(), 0, i) diff --git a/tests/coverage/expect/Lifting.scoverage.check b/tests/coverage/expect/Lifting.scoverage.check index b77989de97a1..9f99655a0d9e 100644 --- a/tests/coverage/expect/Lifting.scoverage.check +++ b/tests/coverage/expect/Lifting.scoverage.check @@ -21,13 +21,64 @@ 1 tests/coverage/expect/Lifting.scala covtest +Vals +Class +covtest.Vals + +39 +46 +3 +apply +Apply +false +0 +false +List(1) + +2 +tests/coverage/expect/Lifting.scala +covtest +Vals +Class +covtest.Vals + +63 +74 +4 +apply +Apply +false +0 +false +List(1,2,3) + +3 +tests/coverage/expect/Lifting.scala +covtest +Vals +Class +covtest.Vals + +58 +74 +4 +:: +Apply +false +0 +false +l :: List(1,2,3) + +4 +tests/coverage/expect/Lifting.scala +covtest A Class covtest.A msg -62 -70 -3 +121 +129 +7 Literal false @@ -35,16 +86,16 @@ false false "string" -2 +5 tests/coverage/expect/Lifting.scala covtest A Class covtest.A integer -92 -93 -4 +151 +152 +8 Literal false @@ -52,16 +103,16 @@ false false 0 -3 +6 tests/coverage/expect/Lifting.scala covtest Lifting$package$ -Class +Object covtest.Lifting$package$ test -151 -154 -8 +210 +213 +12 Apply false @@ -69,16 +120,16 @@ false false A() -4 +7 tests/coverage/expect/Lifting.scala covtest Lifting$package$ -Class +Object covtest.Lifting$package$ test -165 -168 -9 +224 +227 +13 Literal false @@ -86,16 +137,33 @@ false false 123 -5 +8 tests/coverage/expect/Lifting.scala covtest Lifting$package$ -Class +Object +covtest.Lifting$package$ +f +240 +242 +14 + +Literal +false +0 +false +-1 + +9 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Object covtest.Lifting$package$ test -171 -193 -10 +245 +267 +15 msg Apply false @@ -103,16 +171,16 @@ false false a.msg(i, 0, a.integer) -6 +10 tests/coverage/expect/Lifting.scala covtest Lifting$package$ -Class +Object covtest.Lifting$package$ test -196 -224 -11 +270 +298 +16 msg Apply false @@ -120,3 +188,37 @@ false false a.ex.msg(i, 0, a.ex.integer) +11 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Object +covtest.Lifting$package$ +test +307 +310 +17 +f +Apply +false +0 +false +f() + +12 +tests/coverage/expect/Lifting.scala +covtest +Lifting$package$ +Object +covtest.Lifting$package$ +test +301 +317 +17 +msg +Apply +false +0 +false +a.msg(f(), 0, i) + diff --git a/tests/coverage/expect/Literals.scoverage.check b/tests/coverage/expect/Literals.scoverage.check index 8087fd68835c..af2bf08f7f19 100644 --- a/tests/coverage/expect/Literals.scoverage.check +++ b/tests/coverage/expect/Literals.scoverage.check @@ -22,7 +22,7 @@ tests/coverage/expect/Literals.scala Literals$package$ -Class +Object .Literals$package$ mustBeInstrumented 26 @@ -39,7 +39,7 @@ false tests/coverage/expect/Literals.scala Literals$package$ -Class +Object .Literals$package$ thisOneToo 56 @@ -56,7 +56,7 @@ println("not this") tests/coverage/expect/Literals.scala Literals$package$ -Class +Object .Literals$package$ thisOneToo 144 @@ -73,7 +73,7 @@ false tests/coverage/expect/Literals.scala Literals$package$ -Class +Object .Literals$package$ main 246 diff --git a/tests/coverage/expect/MatchCaseClasses.scoverage.check b/tests/coverage/expect/MatchCaseClasses.scoverage.check index 8a5cb6333f67..907eaea2e29f 100644 --- a/tests/coverage/expect/MatchCaseClasses.scoverage.check +++ b/tests/coverage/expect/MatchCaseClasses.scoverage.check @@ -22,7 +22,7 @@ tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 151 @@ -39,7 +39,7 @@ println("a") tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 184 @@ -56,7 +56,7 @@ println("b") tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 225 @@ -73,7 +73,7 @@ println("c") tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 275 @@ -90,7 +90,7 @@ println(y) tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 292 @@ -107,7 +107,7 @@ println("d") tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 325 @@ -124,7 +124,7 @@ println("e") tests/coverage/expect/MatchCaseClasses.scala covtest MatchCaseClasses$ -Class +Object covtest.MatchCaseClasses$ f 352 diff --git a/tests/coverage/expect/MatchNumbers.scoverage.check b/tests/coverage/expect/MatchNumbers.scoverage.check index 78fae5188293..3d2b972ce801 100644 --- a/tests/coverage/expect/MatchNumbers.scoverage.check +++ b/tests/coverage/expect/MatchNumbers.scoverage.check @@ -22,24 +22,7 @@ tests/coverage/expect/MatchNumbers.scala covtest MatchNumbers$ -Class -covtest.MatchNumbers$ -f -125 -126 -6 - -Literal -false -0 -false -0 - -2 -tests/coverage/expect/MatchNumbers.scala -covtest -MatchNumbers$ -Class +Object covtest.MatchNumbers$ f 121 @@ -52,11 +35,11 @@ false false x < 0 -3 +2 tests/coverage/expect/MatchNumbers.scala covtest MatchNumbers$ -Class +Object covtest.MatchNumbers$ f 130 @@ -69,11 +52,11 @@ false false -1 -4 +3 tests/coverage/expect/MatchNumbers.scala covtest MatchNumbers$ -Class +Object covtest.MatchNumbers$ 185 @@ -86,11 +69,11 @@ false false f(0) -5 +4 tests/coverage/expect/MatchNumbers.scala covtest MatchNumbers$ -Class +Object covtest.MatchNumbers$ 192 diff --git a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check index 00c61ad22b39..d4e9725fbec6 100644 --- a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check +++ b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check @@ -22,7 +22,7 @@ tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ simple 97 @@ -39,13 +39,13 @@ $a, ${b.length} tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ simple 97 112 5 -apply +invoked Apply false 0 @@ -56,7 +56,24 @@ $a, ${b.length} tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object +covtest.ParamsAndInterpolation$ +simple +97 +112 +5 +apply +Apply +false +0 +false +$a, ${b.length} + +4 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Object covtest.ParamsAndInterpolation$ simple 103 @@ -69,11 +86,11 @@ false false b.length -4 +5 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ simple 95 @@ -86,11 +103,11 @@ false false s"$a, ${b.length}" -5 +6 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ test 163 @@ -103,11 +120,11 @@ false false List("d", "o", "t", "t", "y") -6 +7 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ $anonfun 238 @@ -120,11 +137,28 @@ false false $i: $s -7 +8 +tests/coverage/expect/ParamsAndInterpolation.scala +covtest +ParamsAndInterpolation$ +Object +covtest.ParamsAndInterpolation$ +$anonfun +238 +244 +10 +invoked +Apply +false +0 +false +$i: $s + +9 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ $anonfun 238 @@ -137,11 +171,11 @@ false false $i: $s -8 +10 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ $anonfun 236 @@ -154,11 +188,11 @@ false false s"$i: $s" -9 +11 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ $anonfun 228 @@ -171,11 +205,11 @@ false false println(s"$i: $s") -10 +12 tests/coverage/expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ -Class +Object covtest.ParamsAndInterpolation$ test 198 diff --git a/tests/coverage/expect/Select.scoverage.check b/tests/coverage/expect/Select.scoverage.check index 783ef3430a92..954a787d02f5 100644 --- a/tests/coverage/expect/Select.scoverage.check +++ b/tests/coverage/expect/Select.scoverage.check @@ -41,6 +41,23 @@ covtest B Class covtest.B + +134 +135 +9 + +Apply +false +0 +false +A + +3 +tests/coverage/expect/Select.scala +covtest +B +Class +covtest.B print 166 179 @@ -52,7 +69,7 @@ false false super.print() -3 +4 tests/coverage/expect/Select.scala covtest B @@ -69,11 +86,11 @@ false false println(this.instance) -4 +5 tests/coverage/expect/Select.scala covtest Select$package$ -Class +Object covtest.Select$package$ test 237 @@ -86,11 +103,11 @@ false false A() -5 +6 tests/coverage/expect/Select.scala covtest Select$package$ -Class +Object covtest.Select$package$ test 254 @@ -103,11 +120,11 @@ false false new A -6 +7 tests/coverage/expect/Select.scala covtest Select$package$ -Class +Object covtest.Select$package$ test 263 @@ -120,11 +137,11 @@ false false a.instance.print() -7 +8 tests/coverage/expect/Select.scala covtest Select$package$ -Class +Object covtest.Select$package$ test 345 diff --git a/tests/coverage/expect/StructuralTypes.scoverage.check b/tests/coverage/expect/StructuralTypes.scoverage.check index 1bc6fb449fdc..b53626df23c3 100644 --- a/tests/coverage/expect/StructuralTypes.scoverage.check +++ b/tests/coverage/expect/StructuralTypes.scoverage.check @@ -56,7 +56,7 @@ elems.find(_._1 == name) tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 277 @@ -73,7 +73,7 @@ false tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 277 @@ -90,7 +90,7 @@ false tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 295 @@ -107,7 +107,7 @@ false tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 295 @@ -124,7 +124,7 @@ false tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 270 @@ -141,7 +141,7 @@ Record("name" -> "Emma", "age" -> 42) tests/coverage/expect/StructuralTypes.scala covtest StructuralTypes$ -Class +Object covtest.StructuralTypes$ test 333 diff --git a/tests/coverage/expect/Trait.scala b/tests/coverage/expect/Trait.scala new file mode 100644 index 000000000000..90ad91e74284 --- /dev/null +++ b/tests/coverage/expect/Trait.scala @@ -0,0 +1,15 @@ +package covtest + +trait T1: + def x = 0 + +class Impl1 extends T1 + +trait T2(val p: String) +class Impl2 extends T2("test") with T1 +class Impl3 extends T2(Impl2().p) + +def testTrait = + println(Impl1().x) // 0 + println(Impl2().p) // test + println(Impl3().p) // test diff --git a/tests/coverage/expect/Trait.scoverage.check b/tests/coverage/expect/Trait.scoverage.check new file mode 100644 index 000000000000..44570cc0b03b --- /dev/null +++ b/tests/coverage/expect/Trait.scoverage.check @@ -0,0 +1,190 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +tests/coverage/expect/Trait.scala +covtest +T1 +Trait +covtest.T1 +x +37 +38 +3 + +Literal +false +0 +false +0 + +2 +tests/coverage/expect/Trait.scala +covtest +Impl2 +Class +covtest.Impl2 + +108 +118 +8 + +Apply +false +0 +false +T2("test") + +3 +tests/coverage/expect/Trait.scala +covtest +Impl3 +Class +covtest.Impl3 + +150 +157 +9 + +Apply +false +0 +false +Impl2() + +4 +tests/coverage/expect/Trait.scala +covtest +Impl3 +Class +covtest.Impl3 + +147 +160 +9 + +Apply +false +0 +false +T2(Impl2().p) + +5 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +188 +195 +12 + +Apply +false +0 +false +Impl1() + +6 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +180 +198 +12 +println +Apply +false +0 +false +println(Impl1().x) + +7 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +214 +221 +13 + +Apply +false +0 +false +Impl2() + +8 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +206 +224 +13 +println +Apply +false +0 +false +println(Impl2().p) + +9 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +243 +250 +14 + +Apply +false +0 +false +Impl3() + +10 +tests/coverage/expect/Trait.scala +covtest +Trait$package$ +Object +covtest.Trait$package$ +testTrait +235 +253 +14 +println +Apply +false +0 +false +println(Impl3().p) + diff --git a/tests/coverage/expect/TypeLambdas.scoverage.check b/tests/coverage/expect/TypeLambdas.scoverage.check index 85b157a52cdd..76c28db58b0c 100644 --- a/tests/coverage/expect/TypeLambdas.scoverage.check +++ b/tests/coverage/expect/TypeLambdas.scoverage.check @@ -22,7 +22,7 @@ tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 310 @@ -39,7 +39,7 @@ false tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 310 @@ -56,7 +56,7 @@ false tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 306 @@ -73,7 +73,7 @@ Map(1 -> "1") tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 324 @@ -90,7 +90,7 @@ println(m) tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 367 @@ -107,7 +107,7 @@ false tests/coverage/expect/TypeLambdas.scala covtest TypeLambdas$ -Class +Object covtest.TypeLambdas$ test 382 From a8a643914b98ada8568e3838ee81c98d1ee16ce5 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sat, 26 Mar 2022 12:13:13 +0100 Subject: [PATCH 055/121] Standardize coverage tests Now use scala3-compiler-bootstrapped/testCoverage [--update-checkfiles] --- .../tools/dotc/coverage/CoverageTests.scala | 94 ++++++++++--------- project/Build.scala | 17 ++++ .../expect/Constructor.scoverage.check | 8 +- tests/coverage/expect/Enum.scoverage.check | 72 +++++++------- tests/coverage/expect/Givens.scoverage.check | 22 ++--- .../expect/Inheritance.scoverage.check | 18 ++-- tests/coverage/expect/Inlined.scoverage.check | 48 +++++----- tests/coverage/expect/Lifting.scoverage.check | 24 ++--- .../coverage/expect/Literals.scoverage.check | 8 +- .../expect/MatchCaseClasses.scoverage.check | 14 +-- .../expect/MatchNumbers.scoverage.check | 8 +- .../ParamsAndInterpolation.scoverage.check | 24 ++--- tests/coverage/expect/Select.scoverage.check | 16 ++-- .../expect/StructuralTypes.scoverage.check | 16 ++-- tests/coverage/expect/Trait.scoverage.check | 20 ++-- .../expect/TypeLambdas.scoverage.check | 12 +-- 16 files changed, 224 insertions(+), 197 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 6142f1ddb5e1..b8b430aa218b 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -1,64 +1,74 @@ package dotty.tools.dotc.coverage import org.junit.Test +import org.junit.AfterClass import org.junit.Assert.* import org.junit.experimental.categories.Category -import dotty.BootstrappedOnlyTests -import dotty.tools.dotc.Main - -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.FileSystems -import java.nio.file.Paths -import java.nio.charset.StandardCharsets.UTF_8 -import java.nio.file.StandardCopyOption +import dotty.{BootstrappedOnlyTests, Properties} +import dotty.tools.vulpix.TestConfiguration.* +import dotty.tools.vulpix.* -@main def updateExpect = - CoverageTests().runExpectTest(updateCheckfiles = true) +import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption} +import scala.jdk.CollectionConverters.* +import scala.language.unsafeNulls +import dotty.tools.dotc.Main @Category(Array(classOf[BootstrappedOnlyTests])) class CoverageTests: + import CoverageTests.* + import CoverageTests.given - private val scalaFile = FileSystems.getDefault.nn.getPathMatcher("glob:**.scala").nn - private val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")).nn - private val expectDir = rootSrc.resolve("expect").nn - - @Category(Array(classOf[dotty.SlowTests])) - @Test def expectTests: Unit = - runExpectTest(dotty.Properties.testsUpdateCheckfile) + private val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") + private val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")) + private val expectDir = rootSrc.resolve("expect") - /** Runs the tests */ - def runExpectTest(updateCheckfiles: Boolean): Unit = - val sourceRoot = if updateCheckfiles then "../" else "." + @Test + def checkInstrumentedCode(): Unit = + given TestGroup = TestGroup("instrumentCoverage") + val updateCheckfiles = dotty.Properties.testsUpdateCheckfile + val sourceRoot = rootSrc.toString - Files.walk(expectDir).nn.filter(scalaFile.matches).nn.forEach(p => { - val path = p.nn - val fileName = path.getFileName.nn.toString.nn.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(Seq(path), sourceRoot).nn - val targetFile = targetDir.resolve(s"scoverage.coverage").nn - val expectFile = expectDir.resolve(s"$fileName.scoverage.check").nn + Files.walk(expectDir).filter(scalaFile.matches).forEach(p => { + val path = p + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, sourceRoot) + val targetFile = targetDir.resolve(s"scoverage.coverage") + val expectFile = expectDir.resolve(s"$fileName.scoverage.check") if updateCheckfiles then Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) else - val expected = new String(Files.readAllBytes(expectFile), UTF_8) - val obtained = new String(Files.readAllBytes(targetFile), UTF_8) - assertEquals(expected, obtained) + val expected = Files.readAllLines(expectFile).asScala + val obtained = Files.readAllLines(targetFile).asScala + if expected != obtained then + for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do + Console.err.println(s"wrong line ${i+1}:") + Console.err.println(s" expected: $exp") + Console.err.println(s" actual : $actual") + fail(s"$targetFile differs from expected $expectFile") }) /** Generates the coverage report for the given input file, in a temporary directory. */ - def computeCoverageInTmp(inputFiles: Seq[Path], sourceRoot: String): Path = + def computeCoverageInTmp(inputFile: Path, sourceRoot: String)(using TestGroup): Path = val target = Files.createTempDirectory("coverage") - val args = Array( - "-Ycheck:instrumentCoverage", - "-coverage-out", - target.toString, - "-coverage-sourceroot", - sourceRoot, - "-usejavacp" - ) ++ inputFiles.map(_.toString) - val exit = Main.process(args) - assertFalse(s"Compilation failed, ${exit.errorCount} errors", exit.hasErrors) - target.nn + val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-coverage-sourceroot", sourceRoot) + compileFile(inputFile.toString, options).checkCompile() + target + +object CoverageTests extends ParallelTesting: + import scala.concurrent.duration.* + + def maxDuration = 30.seconds + def numberOfSlaves = 1 + + def safeMode = Properties.testsSafeMode + def testFilter = Properties.testsFilter + def isInteractive = SummaryReport.isInteractive + def updateCheckFiles = Properties.testsUpdateCheckfile + + given summaryReport: SummaryReporting = SummaryReport() + @AfterClass def tearDown(): Unit = + super.cleanup() + summaryReport.echoSummary() diff --git a/project/Build.scala b/project/Build.scala index 64022cde42f8..1ada9d9875f1 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -138,6 +138,9 @@ object Build { // Run tests with filter through vulpix test suite val testCompilation = inputKey[Unit]("runs integration test with the supplied filter") + // Run code coverage instrumentation tests + val testCoverage = inputKey[Unit]("runs code coverage instrumentation test") + // Used to compile files similar to ./bin/scalac script val scalac = inputKey[Unit]("run the compiler using the correct classpath, or the user supplied classpath") @@ -607,6 +610,20 @@ object Build { } }.evaluated, + testCoverage := Def.inputTaskDyn { + val args = spaceDelimited("").parsed + if (args.contains("--help")) { + println("usage: testCoverage [--update-checkfiles]") + (Test / testOnly).toTask(" not.a.test") + } else { + val updateCheckfile = args.contains("--update-checkfiles") + val test = "dotty.tools.dotc.coverage.CoverageTests" + val argUpdateCheckfile = if (updateCheckfile) "-Ddotty.tests.updateCheckfiles=TRUE" else "" + val cmd = s" $test -- $argUpdateCheckfile" + (Test/testOnly).toTask(cmd) + } + }.evaluated, + Compile / mainClass := Some("dotty.tools.dotc.Main"), scala := { diff --git a/tests/coverage/expect/Constructor.scoverage.check b/tests/coverage/expect/Constructor.scoverage.check index ebc833756efc..cf23dd279926 100644 --- a/tests/coverage/expect/Constructor.scoverage.check +++ b/tests/coverage/expect/Constructor.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Constructor.scala +expect/Constructor.scala covtest C Class @@ -36,7 +36,7 @@ false 1 2 -tests/coverage/expect/Constructor.scala +expect/Constructor.scala covtest C Class @@ -53,7 +53,7 @@ false f(x) 3 -tests/coverage/expect/Constructor.scala +expect/Constructor.scala covtest O$ Object @@ -70,7 +70,7 @@ false 1 4 -tests/coverage/expect/Constructor.scala +expect/Constructor.scala covtest O$ Object diff --git a/tests/coverage/expect/Enum.scoverage.check b/tests/coverage/expect/Enum.scoverage.check index dbcce91d5301..b974cb9b57fd 100644 --- a/tests/coverage/expect/Enum.scoverage.check +++ b/tests/coverage/expect/Enum.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -36,7 +36,7 @@ false 6.67300E-11 2 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -53,7 +53,7 @@ false G * mass 3 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -70,7 +70,7 @@ false G * mass 4 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -87,7 +87,7 @@ false G * mass 5 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -104,7 +104,7 @@ false radius * radius 6 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -121,7 +121,7 @@ false G * mass / (radius * radius 7 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest Planet Class @@ -138,7 +138,7 @@ false otherMass * surfaceGravity 8 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -155,7 +155,7 @@ false Planet(3.303e+23, 2.4397e6) 9 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -172,7 +172,7 @@ false Planet(4.869e+24, 6.0518e6) 10 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -189,7 +189,7 @@ false Planet(5.976e+24, 6.37814e6) 11 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -206,7 +206,7 @@ false Planet(6.421e+23, 3.3972e6) 12 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -223,7 +223,7 @@ false Planet(1.9e+27, 7.1492e7) 13 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -240,7 +240,7 @@ false Planet(5.688e+26, 6.0268e7) 14 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -257,7 +257,7 @@ false Planet(8.686e+25, 2.5559e7) 15 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest $anon Class @@ -274,7 +274,7 @@ false Planet(1.024e+26, 2.4746e7) 16 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -291,7 +291,7 @@ false ListEnum.Cons(3, ListEnum.Empty) 17 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -308,7 +308,7 @@ false ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) 18 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -325,7 +325,7 @@ false ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) 19 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -342,7 +342,7 @@ false "Example 1: \n"+emptyList 20 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -359,7 +359,7 @@ false println("Example 1: \n"+emptyList) 21 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -376,7 +376,7 @@ false ${list}\n 22 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -393,7 +393,7 @@ false ${list}\n 23 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -410,7 +410,7 @@ false ${list}\n 24 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -427,7 +427,7 @@ false s"${list}\n" 25 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -444,7 +444,7 @@ false println(s"${list}\n") 26 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -461,7 +461,7 @@ false earthWeight/Planet.Earth.surfaceGravity 27 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -478,7 +478,7 @@ false Planet.values 28 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -495,7 +495,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 29 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -512,7 +512,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 30 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -529,7 +529,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 31 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -546,7 +546,7 @@ false p.surfaceWeight(mass) 32 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -563,7 +563,7 @@ false s"Your weight on $p is ${p.surfaceWeight(mass)}" 33 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -580,7 +580,7 @@ false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 34 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -598,7 +598,7 @@ for p <- Planet.values do println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 35 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object @@ -615,7 +615,7 @@ false println("Example 2:") 36 -tests/coverage/expect/Enum.scala +expect/Enum.scala covtest EnumTypes$ Object diff --git a/tests/coverage/expect/Givens.scoverage.check b/tests/coverage/expect/Givens.scoverage.check index aec733eca38a..93985ded93d9 100644 --- a/tests/coverage/expect/Givens.scoverage.check +++ b/tests/coverage/expect/Givens.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -36,7 +36,7 @@ false 3 == "3" 2 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -53,7 +53,7 @@ false println(3 == "3") 3 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -70,7 +70,7 @@ false 3 == 5.1 4 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -87,7 +87,7 @@ false println(3 == 5.1) 5 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -104,7 +104,7 @@ false println(msg) 6 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -121,7 +121,7 @@ false println(ctx.id) 7 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -138,7 +138,7 @@ false i.toString 8 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -155,7 +155,7 @@ false Context(0) 9 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -172,7 +172,7 @@ false printContext("test")(using c) 10 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object @@ -189,7 +189,7 @@ false getMessage(123) 11 -tests/coverage/expect/Givens.scala +expect/Givens.scala covtest Givens$ Object diff --git a/tests/coverage/expect/Inheritance.scoverage.check b/tests/coverage/expect/Inheritance.scoverage.check index 8fad938ba60b..70acf1456d5e 100644 --- a/tests/coverage/expect/Inheritance.scoverage.check +++ b/tests/coverage/expect/Inheritance.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest B Class @@ -36,7 +36,7 @@ false A(x, 0) 2 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest C1 Class @@ -53,7 +53,7 @@ false println("block") 3 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest C1 Class @@ -70,7 +70,7 @@ false B({println("block"); 1}) 4 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest C2 Class @@ -87,7 +87,7 @@ false A(2,2) 5 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest C2 Class @@ -104,7 +104,7 @@ false B(A(2,2).x) 6 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest Inheritance$package$ Object @@ -121,7 +121,7 @@ false C1() 7 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest Inheritance$package$ Object @@ -138,7 +138,7 @@ false println(C1().x) 8 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest Inheritance$package$ Object @@ -155,7 +155,7 @@ false C2() 9 -tests/coverage/expect/Inheritance.scala +expect/Inheritance.scala covtest Inheritance$package$ Object diff --git a/tests/coverage/expect/Inlined.scoverage.check b/tests/coverage/expect/Inlined.scoverage.check index 41a8b9e0aadd..878bef241f6b 100644 --- a/tests/coverage/expect/Inlined.scoverage.check +++ b/tests/coverage/expect/Inlined.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -36,7 +36,7 @@ false 1 2 -library/src/scala/runtime/stdLibPatches/Predef.scala +../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -53,7 +53,7 @@ false l == 1 3 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -67,10 +67,10 @@ Select false 0 false - +scala.runtime.Scala3RunTime 4 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -84,10 +84,10 @@ Apply false 0 false - +scala.runtime.Scala3RunTime.assertFailed() 5 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -101,10 +101,10 @@ Block true 0 false - +scala.runtime.Scala3RunTime.assertFailed() 6 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -121,7 +121,7 @@ false List(l) 7 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -138,7 +138,7 @@ false l == List(l).length 8 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -152,10 +152,10 @@ Select false 0 false - +scala.runtime.Scala3RunTime 9 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -169,10 +169,10 @@ Apply false 0 false - +scala.runtime.Scala3RunTime.assertFailed() 10 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -186,10 +186,10 @@ Block true 0 false - +scala.runtime.Scala3RunTime.assertFailed() 11 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -206,7 +206,7 @@ false List(l) 12 -library/src/scala/runtime/stdLibPatches/Predef.scala +../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -223,7 +223,7 @@ false List(l).length == 1 13 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -237,10 +237,10 @@ Select false 0 false - +scala.runtime.Scala3RunTime 14 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -254,10 +254,10 @@ Apply false 0 false - +scala.runtime.Scala3RunTime.assertFailed() 15 -tests/coverage/expect/Inlined.scala +expect/Inlined.scala covtest Inlined$package$ Object @@ -271,5 +271,5 @@ Block true 0 false - +scala.runtime.Scala3RunTime.assertFailed() diff --git a/tests/coverage/expect/Lifting.scoverage.check b/tests/coverage/expect/Lifting.scoverage.check index 9f99655a0d9e..32fd554ed5b3 100644 --- a/tests/coverage/expect/Lifting.scoverage.check +++ b/tests/coverage/expect/Lifting.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Vals Class @@ -36,7 +36,7 @@ false List(1) 2 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Vals Class @@ -53,7 +53,7 @@ false List(1,2,3) 3 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Vals Class @@ -70,7 +70,7 @@ false l :: List(1,2,3) 4 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest A Class @@ -87,7 +87,7 @@ false "string" 5 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest A Class @@ -104,7 +104,7 @@ false 0 6 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -121,7 +121,7 @@ false A() 7 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -138,7 +138,7 @@ false 123 8 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -155,7 +155,7 @@ false -1 9 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -172,7 +172,7 @@ false a.msg(i, 0, a.integer) 10 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -189,7 +189,7 @@ false a.ex.msg(i, 0, a.ex.integer) 11 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object @@ -206,7 +206,7 @@ false f() 12 -tests/coverage/expect/Lifting.scala +expect/Lifting.scala covtest Lifting$package$ Object diff --git a/tests/coverage/expect/Literals.scoverage.check b/tests/coverage/expect/Literals.scoverage.check index af2bf08f7f19..f748b7823d83 100644 --- a/tests/coverage/expect/Literals.scoverage.check +++ b/tests/coverage/expect/Literals.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Literals.scala +expect/Literals.scala Literals$package$ Object @@ -36,7 +36,7 @@ false "literal" 2 -tests/coverage/expect/Literals.scala +expect/Literals.scala Literals$package$ Object @@ -53,7 +53,7 @@ false println("not this") 3 -tests/coverage/expect/Literals.scala +expect/Literals.scala Literals$package$ Object @@ -70,7 +70,7 @@ false 12 4 -tests/coverage/expect/Literals.scala +expect/Literals.scala Literals$package$ Object diff --git a/tests/coverage/expect/MatchCaseClasses.scoverage.check b/tests/coverage/expect/MatchCaseClasses.scoverage.check index 907eaea2e29f..c83905bcd9f2 100644 --- a/tests/coverage/expect/MatchCaseClasses.scoverage.check +++ b/tests/coverage/expect/MatchCaseClasses.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -36,7 +36,7 @@ false println("a") 2 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -53,7 +53,7 @@ false println("b") 3 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -70,7 +70,7 @@ false println("c") 4 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -87,7 +87,7 @@ false println(y) 5 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -104,7 +104,7 @@ false println("d") 6 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -121,7 +121,7 @@ false println("e") 7 -tests/coverage/expect/MatchCaseClasses.scala +expect/MatchCaseClasses.scala covtest MatchCaseClasses$ Object diff --git a/tests/coverage/expect/MatchNumbers.scoverage.check b/tests/coverage/expect/MatchNumbers.scoverage.check index 3d2b972ce801..ce14acc72194 100644 --- a/tests/coverage/expect/MatchNumbers.scoverage.check +++ b/tests/coverage/expect/MatchNumbers.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/MatchNumbers.scala +expect/MatchNumbers.scala covtest MatchNumbers$ Object @@ -36,7 +36,7 @@ false x < 0 2 -tests/coverage/expect/MatchNumbers.scala +expect/MatchNumbers.scala covtest MatchNumbers$ Object @@ -53,7 +53,7 @@ false -1 3 -tests/coverage/expect/MatchNumbers.scala +expect/MatchNumbers.scala covtest MatchNumbers$ Object @@ -70,7 +70,7 @@ false f(0) 4 -tests/coverage/expect/MatchNumbers.scala +expect/MatchNumbers.scala covtest MatchNumbers$ Object diff --git a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check index d4e9725fbec6..85b06deccd87 100644 --- a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check +++ b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -36,7 +36,7 @@ false $a, ${b.length} 2 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -53,7 +53,7 @@ false $a, ${b.length} 3 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -70,7 +70,7 @@ false $a, ${b.length} 4 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -87,7 +87,7 @@ false b.length 5 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -104,7 +104,7 @@ false s"$a, ${b.length}" 6 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -121,7 +121,7 @@ false List("d", "o", "t", "t", "y") 7 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -138,7 +138,7 @@ false $i: $s 8 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -155,7 +155,7 @@ false $i: $s 9 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -172,7 +172,7 @@ false $i: $s 10 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -189,7 +189,7 @@ false s"$i: $s" 11 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object @@ -206,7 +206,7 @@ false println(s"$i: $s") 12 -tests/coverage/expect/ParamsAndInterpolation.scala +expect/ParamsAndInterpolation.scala covtest ParamsAndInterpolation$ Object diff --git a/tests/coverage/expect/Select.scoverage.check b/tests/coverage/expect/Select.scoverage.check index 954a787d02f5..b7e661353b4d 100644 --- a/tests/coverage/expect/Select.scoverage.check +++ b/tests/coverage/expect/Select.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Select.scala +expect/Select.scala covtest A Class @@ -36,7 +36,7 @@ false println("A") 2 -tests/coverage/expect/Select.scala +expect/Select.scala covtest B Class @@ -53,7 +53,7 @@ false A 3 -tests/coverage/expect/Select.scala +expect/Select.scala covtest B Class @@ -70,7 +70,7 @@ false super.print() 4 -tests/coverage/expect/Select.scala +expect/Select.scala covtest B Class @@ -87,7 +87,7 @@ false println(this.instance) 5 -tests/coverage/expect/Select.scala +expect/Select.scala covtest Select$package$ Object @@ -104,7 +104,7 @@ false A() 6 -tests/coverage/expect/Select.scala +expect/Select.scala covtest Select$package$ Object @@ -121,7 +121,7 @@ false new A 7 -tests/coverage/expect/Select.scala +expect/Select.scala covtest Select$package$ Object @@ -138,7 +138,7 @@ false a.instance.print() 8 -tests/coverage/expect/Select.scala +expect/Select.scala covtest Select$package$ Object diff --git a/tests/coverage/expect/StructuralTypes.scoverage.check b/tests/coverage/expect/StructuralTypes.scoverage.check index b53626df23c3..9f5fb0cd014b 100644 --- a/tests/coverage/expect/StructuralTypes.scoverage.check +++ b/tests/coverage/expect/StructuralTypes.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest Record Class @@ -36,7 +36,7 @@ false _._1 == name 2 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest Record Class @@ -53,7 +53,7 @@ false elems.find(_._1 == name) 3 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object @@ -70,7 +70,7 @@ false "name" 4 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object @@ -87,7 +87,7 @@ false "name" -> "Emma" 5 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object @@ -104,7 +104,7 @@ false "age" 6 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object @@ -121,7 +121,7 @@ false "age" -> 42 7 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object @@ -138,7 +138,7 @@ false Record("name" -> "Emma", "age" -> 42) 8 -tests/coverage/expect/StructuralTypes.scala +expect/StructuralTypes.scala covtest StructuralTypes$ Object diff --git a/tests/coverage/expect/Trait.scoverage.check b/tests/coverage/expect/Trait.scoverage.check index 44570cc0b03b..8a752e0e0686 100644 --- a/tests/coverage/expect/Trait.scoverage.check +++ b/tests/coverage/expect/Trait.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest T1 Trait @@ -36,7 +36,7 @@ false 0 2 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Impl2 Class @@ -53,7 +53,7 @@ false T2("test") 3 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Impl3 Class @@ -70,7 +70,7 @@ false Impl2() 4 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Impl3 Class @@ -87,7 +87,7 @@ false T2(Impl2().p) 5 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object @@ -104,7 +104,7 @@ false Impl1() 6 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object @@ -121,7 +121,7 @@ false println(Impl1().x) 7 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object @@ -138,7 +138,7 @@ false Impl2() 8 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object @@ -155,7 +155,7 @@ false println(Impl2().p) 9 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object @@ -172,7 +172,7 @@ false Impl3() 10 -tests/coverage/expect/Trait.scala +expect/Trait.scala covtest Trait$package$ Object diff --git a/tests/coverage/expect/TypeLambdas.scoverage.check b/tests/coverage/expect/TypeLambdas.scoverage.check index 76c28db58b0c..51cd040d8b3b 100644 --- a/tests/coverage/expect/TypeLambdas.scoverage.check +++ b/tests/coverage/expect/TypeLambdas.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object @@ -36,7 +36,7 @@ false 1 2 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object @@ -53,7 +53,7 @@ false 1 -> "1" 3 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object @@ -70,7 +70,7 @@ false Map(1 -> "1") 4 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object @@ -87,7 +87,7 @@ false println(m) 5 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object @@ -104,7 +104,7 @@ false ("a", "b") 6 -tests/coverage/expect/TypeLambdas.scala +expect/TypeLambdas.scala covtest TypeLambdas$ Object From 570aa503240ecc24a6d9cfa41a14a91cbb673863 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sat, 26 Mar 2022 19:00:28 +0100 Subject: [PATCH 056/121] Fix coverage instrumentation for curried and context functions --- .../dotc/transform/InstrumentCoverage.scala | 72 +++-- tests/coverage/expect/ContextFunctions.scala | 14 + .../expect/ContextFunctions.scoverage.check | 173 ++++++++++++ tests/coverage/expect/Currying.scala | 16 ++ .../coverage/expect/Currying.scoverage.check | 258 ++++++++++++++++++ tests/coverage/expect/Givens.scala | 2 +- tests/coverage/expect/Givens.scoverage.check | 110 ++++---- 7 files changed, 572 insertions(+), 73 deletions(-) create mode 100644 tests/coverage/expect/ContextFunctions.scala create mode 100644 tests/coverage/expect/ContextFunctions.scoverage.check create mode 100644 tests/coverage/expect/Currying.scala create mode 100644 tests/coverage/expect/Currying.scoverage.check diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 761a69613472..a04e4c98e9a9 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -5,17 +5,18 @@ import java.io.File import java.util.concurrent.atomic.AtomicInteger import collection.mutable -import core.Flags.JavaDefined +import core.Flags.* import core.Contexts.{Context, ctx, inContext} import core.DenotTransformers.IdentityDenotTransformer import core.Symbols.{defn, Symbol} import core.Decorators.{toTermName, i} import core.Constants.Constant +import core.NameOps.isContextFunction +import core.Types.* import typer.LiftCoverage import util.{SourcePosition, Property} import util.Spans.Span import coverage.* -import dotty.tools.dotc.util.SourceFile /** Implements code coverage by inserting calls to scala.runtime.Invoker * ("instruments" the source code). @@ -98,20 +99,26 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // a.f(args) case tree @ Apply(fun: Select, args) => // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` - val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) - if needsLift(tree) then - val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted - instrumentLifted(transformed)(using ignoreLiteralsContext) + if canInstrumentApply(tree) then + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) + if needsLift(tree) then + val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted + instrumentLifted(transformed)(using ignoreLiteralsContext) + else + val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) + instrument(transformed)(using ignoreLiteralsContext) else - val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) - instrument(transformed)(using ignoreLiteralsContext) + tree // f(args) case tree: Apply => - if needsLift(tree) then - instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals + if canInstrumentApply(tree) then + if needsLift(tree) then + instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals + else + instrument(super.transform(tree)(using ignoreLiteralsContext)) else - instrument(super.transform(tree)(using ignoreLiteralsContext)) + tree // (f(x))[args] case TypeApply(fun: Apply, args) => @@ -226,18 +233,49 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} */ def needsLift(tree: Apply)(using Context): Boolean = - // We don't want to lift a || getB(), to avoid calling getB if a is true. - // Same idea with a && getB(): if a is false, getB shouldn't be called. def isBooleanOperator(fun: Tree) = - fun.symbol.exists && - fun.symbol.isInstanceOf[Symbol] && - fun.symbol == defn.Boolean_&& || fun.symbol == defn.Boolean_|| + // We don't want to lift a || getB(), to avoid calling getB if a is true. + // Same idea with a && getB(): if a is false, getB shouldn't be called. + val sym = fun.symbol + sym.exists && + sym == defn.Boolean_&& || sym == defn.Boolean_|| + + def isContextual(fun: Apply): Boolean = + val args = fun.args + args.nonEmpty && args.head.symbol.isAllOf(Given | Implicit) val fun = tree.fun + val nestedApplyNeedsLift = fun match + case a: Apply => needsLift(a) + case _ => false - fun.isInstanceOf[Apply] || // nested apply + nestedApplyNeedsLift || !isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) + def canInstrumentApply(tree: Apply)(using Context): Boolean = + val tpe = tree.typeOpt + tpe match + case AppliedType(tycon: NamedType, _) => + /* If the last expression in a block is a context function, we'll try to + summon its arguments at the current point, even if the expected type + is a function application. Therefore, this is not valid: + ``` + def f = (t: Exception) ?=> (c: String) ?=> result + + ({ + invoked() + f(using e) + })(using s) + ``` + */ + !tycon.name.isContextFunction + case m: MethodType => + // def f(a: Ta)(b: Tb) + // f(a)(b) cannot be rewritten to {invoked();f(a)}(b) + false + case _ => + true + object InstrumentCoverage: val name: String = "instrumentCoverage" val description: String = "instrument code for coverage cheking" diff --git a/tests/coverage/expect/ContextFunctions.scala b/tests/coverage/expect/ContextFunctions.scala new file mode 100644 index 000000000000..04f5b6768c6c --- /dev/null +++ b/tests/coverage/expect/ContextFunctions.scala @@ -0,0 +1,14 @@ +package covtest + +class OnError(op: Exception => Any): + def onError(fallback: Any): Any = fallback + +class Imperative: + + def readName2 = (t: Exception) ?=> (c: String) ?=> { + "name" + } + + def readPerson(s: String) = + val e: Exception = null + OnError((e) => readName2(using e)(using s)).onError(None) diff --git a/tests/coverage/expect/ContextFunctions.scoverage.check b/tests/coverage/expect/ContextFunctions.scoverage.check new file mode 100644 index 000000000000..4dd4e4b3b583 --- /dev/null +++ b/tests/coverage/expect/ContextFunctions.scoverage.check @@ -0,0 +1,173 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +178 +184 +8 + +Literal +false +0 +false +"name" + +2 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +243 +247 +12 + +Literal +false +0 +false +null + +3 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +4 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +5 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +invoked +Apply +false +0 +false +readName2(using e)(using s) + +6 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +$anonfun +267 +294 +13 +apply +Apply +false +0 +false +readName2(using e)(using s) + +7 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 +invoked +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +8 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +295 +13 + +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)) + +9 +expect/ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +252 +309 +13 +onError +Apply +false +0 +false +OnError((e) => readName2(using e)(using s)).onError(None) + diff --git a/tests/coverage/expect/Currying.scala b/tests/coverage/expect/Currying.scala new file mode 100644 index 000000000000..12842cd181e1 --- /dev/null +++ b/tests/coverage/expect/Currying.scala @@ -0,0 +1,16 @@ +package covtest + +class ContextFunctions2: + def f1(a: Int)(b: Int)(c: Int) = a+b+c + def f2 = (a: Int) => (b: Int) => (c: Int) => + a+b+c + + def g1 = (a: Int) ?=> (b: Int) ?=> (c: Int) ?=> + a+b+c + + def g2(using a: Int)(using b: Int)(using c: Int) = a+b+c + + f1(0)(1)(2) + f2(0)(1)(2) + g1(using 0)(using 1)(using 2) + g2(using 0)(using 1)(using 2) diff --git a/tests/coverage/expect/Currying.scoverage.check b/tests/coverage/expect/Currying.scoverage.check new file mode 100644 index 000000000000..246db9966247 --- /dev/null +++ b/tests/coverage/expect/Currying.scoverage.check @@ -0,0 +1,258 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +f1 +77 +80 +3 ++ +Apply +false +0 +false +a+b + +2 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +f1 +77 +82 +3 ++ +Apply +false +0 +false +a+b+c + +3 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +$anonfun +134 +137 +5 ++ +Apply +false +0 +false +a+b + +4 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +$anonfun +134 +139 +5 ++ +Apply +false +0 +false +a+b+c + +5 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +$anonfun +195 +198 +8 ++ +Apply +false +0 +false +a+b + +6 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +$anonfun +195 +200 +8 ++ +Apply +false +0 +false +a+b+c + +7 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +g2 +255 +258 +10 ++ +Apply +false +0 +false +a+b + +8 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 +g2 +255 +260 +10 ++ +Apply +false +0 +false +a+b+c + +9 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +264 +275 +12 +f1 +Apply +false +0 +false +f1(0)(1)(2) + +10 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +278 +283 +13 +apply +Apply +false +0 +false +f2(0) + +11 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +278 +286 +13 +apply +Apply +false +0 +false +f2(0)(1) + +12 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +278 +289 +13 +apply +Apply +false +0 +false +f2(0)(1)(2) + +13 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +292 +321 +14 +apply +Apply +false +0 +false +g1(using 0)(using 1)(using 2) + +14 +expect/Currying.scala +covtest +ContextFunctions2 +Class +covtest.ContextFunctions2 + +324 +353 +15 +g2 +Apply +false +0 +false +g2(using 0)(using 1)(using 2) + diff --git a/tests/coverage/expect/Givens.scala b/tests/coverage/expect/Givens.scala index 34d740e146b9..2f9cbdfd1169 100644 --- a/tests/coverage/expect/Givens.scala +++ b/tests/coverage/expect/Givens.scala @@ -4,7 +4,7 @@ import scala.language.strictEquality class Context(val id: Int) -object Givens: +class Givens: def test(): Unit = given CanEqual[Int, String] = CanEqual.derived diff --git a/tests/coverage/expect/Givens.scoverage.check b/tests/coverage/expect/Givens.scoverage.check index 93985ded93d9..432b85fcf59a 100644 --- a/tests/coverage/expect/Givens.scoverage.check +++ b/tests/coverage/expect/Givens.scoverage.check @@ -21,12 +21,12 @@ 1 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test -183 -191 +182 +190 10 == Apply @@ -38,12 +38,12 @@ false 2 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test -175 -192 +174 +191 10 println Apply @@ -55,12 +55,12 @@ println(3 == "3") 3 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test -205 -213 +204 +212 11 == Apply @@ -72,12 +72,12 @@ false 4 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test -197 -214 +196 +213 11 println Apply @@ -89,12 +89,12 @@ println(3 == 5.1) 5 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens printContext -280 -292 +279 +291 14 println Apply @@ -106,12 +106,12 @@ println(msg) 6 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens printContext -297 -312 +296 +311 15 println Apply @@ -123,12 +123,12 @@ println(ctx.id) 7 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens getMessage -349 -359 +348 +358 17 toString Apply @@ -140,12 +140,12 @@ i.toString 8 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test2 -400 -410 +399 +409 20 Apply @@ -157,12 +157,12 @@ Context(0) 9 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test2 -415 -444 +414 +443 21 printContext Apply @@ -174,12 +174,12 @@ printContext("test")(using c) 10 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test2 -462 -477 +461 +476 22 getMessage Apply @@ -191,12 +191,12 @@ getMessage(123) 11 expect/Givens.scala covtest -Givens$ -Object -covtest.Givens$ +Givens +Class +covtest.Givens test2 -449 -478 +448 +477 22 printContext Apply From 9c01e86631094116fd0e6de4235c25ebbdc8d10c Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 27 Mar 2022 11:40:24 +0200 Subject: [PATCH 057/121] Add aliases to coverage options --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 8ed6f2a70fe5..51891beef79f 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -59,7 +59,7 @@ class Compiler { /** Phases dealing with the transformation from pickled trees to backend trees */ protected def transformPhases: List[List[Phase]] = - List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage setting is set) + List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set) List(new FirstTransform, // Some transformations to put trees into a canonical form new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index e37168fce20b..a1ce1591e9af 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -117,8 +117,8 @@ trait CommonScalaSettings: val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) /* Coverage settings */ - val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "") - val coverageSourceroot = PathSetting("-coverage-sourceroot", "An alternative root dir of your sources used to relativize.", ".") + val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out")) + val coverageSourceroot = PathSetting("-coverage-sourceroot", "An alternative root dir of your sources used to relativize.", ".", aliases = List("--coverage-sourceroot")) /* Other settings */ val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) From 0d9bf9aa06d1965eb53a111bb778901413b9e3d4 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 27 Mar 2022 11:45:59 +0200 Subject: [PATCH 058/121] Move Invoker to scala.runtime.coverage --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- library/src/scala/runtime/{ => coverage}/Invoker.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename library/src/scala/runtime/{ => coverage}/Invoker.scala (98%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index b769c4251135..47780b302c8c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -461,7 +461,7 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef - @tu lazy val InvokerModuleRef = requiredMethodRef("scala.runtime.Invoker") + @tu lazy val InvokerModuleRef = requiredModuleRef("scala.runtime.coverage.Invoker") @tu lazy val ImplicitScrutineeTypeSym = newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered diff --git a/library/src/scala/runtime/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala similarity index 98% rename from library/src/scala/runtime/Invoker.scala rename to library/src/scala/runtime/coverage/Invoker.scala index 44b0703fb8d2..ee37a477cbe6 100644 --- a/library/src/scala/runtime/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -1,4 +1,4 @@ -package scala.runtime +package scala.runtime.coverage import scala.collection.mutable.{BitSet, AnyRefMap} import scala.collection.concurrent.TrieMap From c2bc0a0b9e709302ca5caa6afd68a7579d71a7da Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Sun, 27 Mar 2022 13:43:50 +0200 Subject: [PATCH 059/121] Check the runtime output of some coverage tests --- .../tools/dotc/coverage/CoverageTests.scala | 35 +- project/Build.scala | 6 +- .../coverage/expect/Currying.scoverage.check | 258 ------------- tests/coverage/expect/Lifting.scala | 18 - tests/coverage/expect/Lifting.scoverage.check | 224 ----------- .../ParamsAndInterpolation.scoverage.check | 224 ----------- .../{expect => pos}/Constructor.scala | 0 .../Constructor.scoverage.check | 8 +- .../{expect => pos}/ContextFunctions.scala | 0 .../ContextFunctions.scoverage.check | 18 +- tests/coverage/{expect => pos}/Enum.scala | 0 .../{expect => pos}/Enum.scoverage.check | 72 ++-- tests/coverage/{expect => pos}/Givens.scala | 0 .../{expect => pos}/Givens.scoverage.check | 22 +- tests/coverage/{expect => pos}/Inlined.scala | 0 .../{expect => pos}/Inlined.scoverage.check | 30 +- tests/coverage/{expect => pos}/Literals.scala | 2 +- .../{expect => pos}/Literals.scoverage.check | 12 +- .../{expect => pos}/MatchCaseClasses.scala | 0 .../MatchCaseClasses.scoverage.check | 14 +- .../{expect => pos}/MatchNumbers.scala | 0 .../MatchNumbers.scoverage.check | 8 +- tests/coverage/{expect => pos}/Select.scala | 0 .../{expect => pos}/Select.scoverage.check | 16 +- .../{expect => pos}/StructuralTypes.scala | 0 .../StructuralTypes.scoverage.check | 16 +- .../{expect => pos}/TypeLambdas.scala | 0 .../TypeLambdas.scoverage.check | 12 +- tests/coverage/run/currying/test.check | 4 + .../currying/test.scala} | 13 +- .../run/currying/test.scoverage.check | 326 ++++++++++++++++ tests/coverage/run/inheritance/test.check | 3 + .../inheritance/test.scala} | 5 +- .../inheritance/test.scoverage.check} | 124 +++--- tests/coverage/run/interpolation/test.check | 5 + .../interpolation/test.scala} | 6 +- .../run/interpolation/test.scoverage.check | 224 +++++++++++ tests/coverage/run/lifting/test.check | 3 + tests/coverage/run/lifting/test.scala | 20 + .../coverage/run/lifting/test.scoverage.check | 360 ++++++++++++++++++ tests/coverage/run/trait/test.check | 3 + .../Trait.scala => run/trait/test.scala} | 5 +- .../trait/test.scoverage.check} | 144 +++---- 43 files changed, 1234 insertions(+), 1006 deletions(-) delete mode 100644 tests/coverage/expect/Currying.scoverage.check delete mode 100644 tests/coverage/expect/Lifting.scala delete mode 100644 tests/coverage/expect/Lifting.scoverage.check delete mode 100644 tests/coverage/expect/ParamsAndInterpolation.scoverage.check rename tests/coverage/{expect => pos}/Constructor.scala (100%) rename tests/coverage/{expect => pos}/Constructor.scoverage.check (87%) rename tests/coverage/{expect => pos}/ContextFunctions.scala (100%) rename tests/coverage/{expect => pos}/ContextFunctions.scoverage.check (85%) rename tests/coverage/{expect => pos}/Enum.scala (100%) rename tests/coverage/{expect => pos}/Enum.scoverage.check (88%) rename tests/coverage/{expect => pos}/Givens.scala (100%) rename tests/coverage/{expect => pos}/Givens.scoverage.check (87%) rename tests/coverage/{expect => pos}/Inlined.scala (100%) rename tests/coverage/{expect => pos}/Inlined.scoverage.check (86%) rename tests/coverage/{expect => pos}/Literals.scala (83%) rename tests/coverage/{expect => pos}/Literals.scoverage.check (90%) rename tests/coverage/{expect => pos}/MatchCaseClasses.scala (100%) rename tests/coverage/{expect => pos}/MatchCaseClasses.scoverage.check (85%) rename tests/coverage/{expect => pos}/MatchNumbers.scala (100%) rename tests/coverage/{expect => pos}/MatchNumbers.scoverage.check (88%) rename tests/coverage/{expect => pos}/Select.scala (100%) rename tests/coverage/{expect => pos}/Select.scoverage.check (88%) rename tests/coverage/{expect => pos}/StructuralTypes.scala (100%) rename tests/coverage/{expect => pos}/StructuralTypes.scoverage.check (85%) rename tests/coverage/{expect => pos}/TypeLambdas.scala (100%) rename tests/coverage/{expect => pos}/TypeLambdas.scoverage.check (87%) create mode 100644 tests/coverage/run/currying/test.check rename tests/coverage/{expect/Currying.scala => run/currying/test.scala} (53%) create mode 100644 tests/coverage/run/currying/test.scoverage.check create mode 100644 tests/coverage/run/inheritance/test.check rename tests/coverage/{expect/Inheritance.scala => run/inheritance/test.scala} (84%) rename tests/coverage/{expect/Inheritance.scoverage.check => run/inheritance/test.scoverage.check} (56%) create mode 100644 tests/coverage/run/interpolation/test.check rename tests/coverage/{expect/ParamsAndInterpolation.scala => run/interpolation/test.scala} (72%) create mode 100644 tests/coverage/run/interpolation/test.scoverage.check create mode 100644 tests/coverage/run/lifting/test.check create mode 100644 tests/coverage/run/lifting/test.scala create mode 100644 tests/coverage/run/lifting/test.scoverage.check create mode 100644 tests/coverage/run/trait/test.check rename tests/coverage/{expect/Trait.scala => run/trait/test.scala} (87%) rename tests/coverage/{expect/Trait.scoverage.check => run/trait/test.scoverage.check} (58%) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index b8b430aa218b..c431d6358b5a 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -16,27 +16,28 @@ import dotty.tools.dotc.Main @Category(Array(classOf[BootstrappedOnlyTests])) class CoverageTests: - import CoverageTests.* - import CoverageTests.given + import CoverageTests.{*, given} private val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") private val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")) - private val expectDir = rootSrc.resolve("expect") @Test - def checkInstrumentedCode(): Unit = - given TestGroup = TestGroup("instrumentCoverage") - val updateCheckfiles = dotty.Properties.testsUpdateCheckfile - val sourceRoot = rootSrc.toString + def checkCoverageStatements(): Unit = + checkCoverageIn(rootSrc.resolve("pos"), false) - Files.walk(expectDir).filter(scalaFile.matches).forEach(p => { + @Test + def checkInstrumentedRuns(): Unit = + checkCoverageIn(rootSrc.resolve("run"), true) + + def checkCoverageIn(dir: Path, run: Boolean)(using TestGroup): Unit = + Files.walk(dir).filter(scalaFile.matches).forEach(p => { val path = p val fileName = path.getFileName.toString.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(path, sourceRoot) + val targetDir = computeCoverageInTmp(path, dir, run) val targetFile = targetDir.resolve(s"scoverage.coverage") - val expectFile = expectDir.resolve(s"$fileName.scoverage.check") + val expectFile = p.resolveSibling(s"$fileName.scoverage.check") - if updateCheckfiles then + if updateCheckFiles then Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) else val expected = Files.readAllLines(expectFile).asScala @@ -51,10 +52,14 @@ class CoverageTests: }) /** Generates the coverage report for the given input file, in a temporary directory. */ - def computeCoverageInTmp(inputFile: Path, sourceRoot: String)(using TestGroup): Path = + def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path = val target = Files.createTempDirectory("coverage") - val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-coverage-sourceroot", sourceRoot) - compileFile(inputFile.toString, options).checkCompile() + val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-coverage-sourceroot", sourceRoot.toString) + val test = compileFile(inputFile.toString, options) + if run then + test.checkRuns() + else + test.checkCompile() target object CoverageTests extends ParallelTesting: @@ -72,3 +77,5 @@ object CoverageTests extends ParallelTesting: @AfterClass def tearDown(): Unit = super.cleanup() summaryReport.echoSummary() + + given TestGroup = TestGroup("instrumentCoverage") diff --git a/project/Build.scala b/project/Build.scala index 1ada9d9875f1..c754f7ff1464 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -613,13 +613,15 @@ object Build { testCoverage := Def.inputTaskDyn { val args = spaceDelimited("").parsed if (args.contains("--help")) { - println("usage: testCoverage [--update-checkfiles]") + println("usage: testCoverage [--update-checkfiles] []") (Test / testOnly).toTask(" not.a.test") } else { val updateCheckfile = args.contains("--update-checkfiles") + val otherArgs = args.filter(_ != "--update-checkfiles") val test = "dotty.tools.dotc.coverage.CoverageTests" val argUpdateCheckfile = if (updateCheckfile) "-Ddotty.tests.updateCheckfiles=TRUE" else "" - val cmd = s" $test -- $argUpdateCheckfile" + val argCustom = if (otherArgs.nonEmpty) otherArgs.mkString(" ") else "" + val cmd = s" $test -- $argUpdateCheckfile $argCustom" (Test/testOnly).toTask(cmd) } }.evaluated, diff --git a/tests/coverage/expect/Currying.scoverage.check b/tests/coverage/expect/Currying.scoverage.check deleted file mode 100644 index 246db9966247..000000000000 --- a/tests/coverage/expect/Currying.scoverage.check +++ /dev/null @@ -1,258 +0,0 @@ -# Coverage data, format version: 3.0 -# Statement data: -# - id -# - source path -# - package name -# - class name -# - class type (Class, Object or Trait) -# - full class name -# - method name -# - start offset -# - end offset -# - line number -# - symbol name -# - tree name -# - is branch -# - invocations count -# - is ignored -# - description (can be multi-line) -# ' ' sign -# ------------------------------------------ -1 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -f1 -77 -80 -3 -+ -Apply -false -0 -false -a+b - -2 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -f1 -77 -82 -3 -+ -Apply -false -0 -false -a+b+c - -3 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -$anonfun -134 -137 -5 -+ -Apply -false -0 -false -a+b - -4 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -$anonfun -134 -139 -5 -+ -Apply -false -0 -false -a+b+c - -5 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -$anonfun -195 -198 -8 -+ -Apply -false -0 -false -a+b - -6 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -$anonfun -195 -200 -8 -+ -Apply -false -0 -false -a+b+c - -7 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -g2 -255 -258 -10 -+ -Apply -false -0 -false -a+b - -8 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 -g2 -255 -260 -10 -+ -Apply -false -0 -false -a+b+c - -9 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -264 -275 -12 -f1 -Apply -false -0 -false -f1(0)(1)(2) - -10 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -278 -283 -13 -apply -Apply -false -0 -false -f2(0) - -11 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -278 -286 -13 -apply -Apply -false -0 -false -f2(0)(1) - -12 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -278 -289 -13 -apply -Apply -false -0 -false -f2(0)(1)(2) - -13 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -292 -321 -14 -apply -Apply -false -0 -false -g1(using 0)(using 1)(using 2) - -14 -expect/Currying.scala -covtest -ContextFunctions2 -Class -covtest.ContextFunctions2 - -324 -353 -15 -g2 -Apply -false -0 -false -g2(using 0)(using 1)(using 2) - diff --git a/tests/coverage/expect/Lifting.scala b/tests/coverage/expect/Lifting.scala deleted file mode 100644 index a948e1ad575a..000000000000 --- a/tests/coverage/expect/Lifting.scala +++ /dev/null @@ -1,18 +0,0 @@ -package covtest - -class Vals: - val l = List(1) - val ll = l :: List(1,2,3) - -class A: - def msg(a: Int, b: Int, c: Int) = "string" - def integer: Int = 0 - def ex: this.type = this - -def test(): Unit = - val a = A() - val i = 123 - def f() = -1 - a.msg(i, 0, a.integer) - a.ex.msg(i, 0, a.ex.integer) - a.msg(f(), 0, i) diff --git a/tests/coverage/expect/Lifting.scoverage.check b/tests/coverage/expect/Lifting.scoverage.check deleted file mode 100644 index 32fd554ed5b3..000000000000 --- a/tests/coverage/expect/Lifting.scoverage.check +++ /dev/null @@ -1,224 +0,0 @@ -# Coverage data, format version: 3.0 -# Statement data: -# - id -# - source path -# - package name -# - class name -# - class type (Class, Object or Trait) -# - full class name -# - method name -# - start offset -# - end offset -# - line number -# - symbol name -# - tree name -# - is branch -# - invocations count -# - is ignored -# - description (can be multi-line) -# ' ' sign -# ------------------------------------------ -1 -expect/Lifting.scala -covtest -Vals -Class -covtest.Vals - -39 -46 -3 -apply -Apply -false -0 -false -List(1) - -2 -expect/Lifting.scala -covtest -Vals -Class -covtest.Vals - -63 -74 -4 -apply -Apply -false -0 -false -List(1,2,3) - -3 -expect/Lifting.scala -covtest -Vals -Class -covtest.Vals - -58 -74 -4 -:: -Apply -false -0 -false -l :: List(1,2,3) - -4 -expect/Lifting.scala -covtest -A -Class -covtest.A -msg -121 -129 -7 - -Literal -false -0 -false -"string" - -5 -expect/Lifting.scala -covtest -A -Class -covtest.A -integer -151 -152 -8 - -Literal -false -0 -false -0 - -6 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -210 -213 -12 - -Apply -false -0 -false -A() - -7 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -224 -227 -13 - -Literal -false -0 -false -123 - -8 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -f -240 -242 -14 - -Literal -false -0 -false --1 - -9 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -245 -267 -15 -msg -Apply -false -0 -false -a.msg(i, 0, a.integer) - -10 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -270 -298 -16 -msg -Apply -false -0 -false -a.ex.msg(i, 0, a.ex.integer) - -11 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -307 -310 -17 -f -Apply -false -0 -false -f() - -12 -expect/Lifting.scala -covtest -Lifting$package$ -Object -covtest.Lifting$package$ -test -301 -317 -17 -msg -Apply -false -0 -false -a.msg(f(), 0, i) - diff --git a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check b/tests/coverage/expect/ParamsAndInterpolation.scoverage.check deleted file mode 100644 index 85b06deccd87..000000000000 --- a/tests/coverage/expect/ParamsAndInterpolation.scoverage.check +++ /dev/null @@ -1,224 +0,0 @@ -# Coverage data, format version: 3.0 -# Statement data: -# - id -# - source path -# - package name -# - class name -# - class type (Class, Object or Trait) -# - full class name -# - method name -# - start offset -# - end offset -# - line number -# - symbol name -# - tree name -# - is branch -# - invocations count -# - is ignored -# - description (can be multi-line) -# ' ' sign -# ------------------------------------------ -1 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -simple -97 -112 -5 -apply -Apply -false -0 -false -$a, ${b.length} - -2 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -simple -97 -112 -5 -invoked -Apply -false -0 -false -$a, ${b.length} - -3 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -simple -97 -112 -5 -apply -Apply -false -0 -false -$a, ${b.length} - -4 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -simple -103 -111 -5 -length -Apply -false -0 -false -b.length - -5 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -simple -95 -113 -5 -s -Apply -false -0 -false -s"$a, ${b.length}" - -6 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -test -163 -192 -8 -apply -Apply -false -0 -false -List("d", "o", "t", "t", "y") - -7 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -$anonfun -238 -244 -10 -apply -Apply -false -0 -false -$i: $s - -8 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -$anonfun -238 -244 -10 -invoked -Apply -false -0 -false -$i: $s - -9 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -$anonfun -238 -244 -10 -apply -Apply -false -0 -false -$i: $s - -10 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -$anonfun -236 -245 -10 -s -Apply -false -0 -false -s"$i: $s" - -11 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -$anonfun -228 -246 -10 -println -Apply -false -0 -false -println(s"$i: $s") - -12 -expect/ParamsAndInterpolation.scala -covtest -ParamsAndInterpolation$ -Object -covtest.ParamsAndInterpolation$ -test -198 -247 -10 -map -Apply -false -0 -false -xs.zipWithIndex.map((s, i) => println(s"$i: $s")) - diff --git a/tests/coverage/expect/Constructor.scala b/tests/coverage/pos/Constructor.scala similarity index 100% rename from tests/coverage/expect/Constructor.scala rename to tests/coverage/pos/Constructor.scala diff --git a/tests/coverage/expect/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check similarity index 87% rename from tests/coverage/expect/Constructor.scoverage.check rename to tests/coverage/pos/Constructor.scoverage.check index cf23dd279926..91a3331b002f 100644 --- a/tests/coverage/expect/Constructor.scoverage.check +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Constructor.scala +Constructor.scala covtest C Class @@ -36,7 +36,7 @@ false 1 2 -expect/Constructor.scala +Constructor.scala covtest C Class @@ -53,7 +53,7 @@ false f(x) 3 -expect/Constructor.scala +Constructor.scala covtest O$ Object @@ -70,7 +70,7 @@ false 1 4 -expect/Constructor.scala +Constructor.scala covtest O$ Object diff --git a/tests/coverage/expect/ContextFunctions.scala b/tests/coverage/pos/ContextFunctions.scala similarity index 100% rename from tests/coverage/expect/ContextFunctions.scala rename to tests/coverage/pos/ContextFunctions.scala diff --git a/tests/coverage/expect/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check similarity index 85% rename from tests/coverage/expect/ContextFunctions.scoverage.check rename to tests/coverage/pos/ContextFunctions.scoverage.check index 4dd4e4b3b583..df4ae8c780f1 100644 --- a/tests/coverage/expect/ContextFunctions.scoverage.check +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -36,7 +36,7 @@ false "name" 2 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -53,7 +53,7 @@ false null 3 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -70,7 +70,7 @@ false readName2(using e)(using s) 4 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -87,7 +87,7 @@ false OnError((e) => readName2(using e)(using s)) 5 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -104,7 +104,7 @@ false readName2(using e)(using s) 6 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -121,7 +121,7 @@ false readName2(using e)(using s) 7 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -138,7 +138,7 @@ false OnError((e) => readName2(using e)(using s)) 8 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class @@ -155,7 +155,7 @@ false OnError((e) => readName2(using e)(using s)) 9 -expect/ContextFunctions.scala +ContextFunctions.scala covtest Imperative Class diff --git a/tests/coverage/expect/Enum.scala b/tests/coverage/pos/Enum.scala similarity index 100% rename from tests/coverage/expect/Enum.scala rename to tests/coverage/pos/Enum.scala diff --git a/tests/coverage/expect/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check similarity index 88% rename from tests/coverage/expect/Enum.scoverage.check rename to tests/coverage/pos/Enum.scoverage.check index b974cb9b57fd..1e41a1732a6b 100644 --- a/tests/coverage/expect/Enum.scoverage.check +++ b/tests/coverage/pos/Enum.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -36,7 +36,7 @@ false 6.67300E-11 2 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -53,7 +53,7 @@ false G * mass 3 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -70,7 +70,7 @@ false G * mass 4 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -87,7 +87,7 @@ false G * mass 5 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -104,7 +104,7 @@ false radius * radius 6 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -121,7 +121,7 @@ false G * mass / (radius * radius 7 -expect/Enum.scala +Enum.scala covtest Planet Class @@ -138,7 +138,7 @@ false otherMass * surfaceGravity 8 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -155,7 +155,7 @@ false Planet(3.303e+23, 2.4397e6) 9 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -172,7 +172,7 @@ false Planet(4.869e+24, 6.0518e6) 10 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -189,7 +189,7 @@ false Planet(5.976e+24, 6.37814e6) 11 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -206,7 +206,7 @@ false Planet(6.421e+23, 3.3972e6) 12 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -223,7 +223,7 @@ false Planet(1.9e+27, 7.1492e7) 13 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -240,7 +240,7 @@ false Planet(5.688e+26, 6.0268e7) 14 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -257,7 +257,7 @@ false Planet(8.686e+25, 2.5559e7) 15 -expect/Enum.scala +Enum.scala covtest $anon Class @@ -274,7 +274,7 @@ false Planet(1.024e+26, 2.4746e7) 16 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -291,7 +291,7 @@ false ListEnum.Cons(3, ListEnum.Empty) 17 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -308,7 +308,7 @@ false ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) 18 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -325,7 +325,7 @@ false ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) 19 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -342,7 +342,7 @@ false "Example 1: \n"+emptyList 20 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -359,7 +359,7 @@ false println("Example 1: \n"+emptyList) 21 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -376,7 +376,7 @@ false ${list}\n 22 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -393,7 +393,7 @@ false ${list}\n 23 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -410,7 +410,7 @@ false ${list}\n 24 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -427,7 +427,7 @@ false s"${list}\n" 25 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -444,7 +444,7 @@ false println(s"${list}\n") 26 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -461,7 +461,7 @@ false earthWeight/Planet.Earth.surfaceGravity 27 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -478,7 +478,7 @@ false Planet.values 28 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -495,7 +495,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 29 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -512,7 +512,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 30 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -529,7 +529,7 @@ false Your weight on $p is ${p.surfaceWeight(mass)} 31 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -546,7 +546,7 @@ false p.surfaceWeight(mass) 32 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -563,7 +563,7 @@ false s"Your weight on $p is ${p.surfaceWeight(mass)}" 33 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -580,7 +580,7 @@ false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 34 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -598,7 +598,7 @@ for p <- Planet.values do println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 35 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object @@ -615,7 +615,7 @@ false println("Example 2:") 36 -expect/Enum.scala +Enum.scala covtest EnumTypes$ Object diff --git a/tests/coverage/expect/Givens.scala b/tests/coverage/pos/Givens.scala similarity index 100% rename from tests/coverage/expect/Givens.scala rename to tests/coverage/pos/Givens.scala diff --git a/tests/coverage/expect/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check similarity index 87% rename from tests/coverage/expect/Givens.scoverage.check rename to tests/coverage/pos/Givens.scoverage.check index 432b85fcf59a..9cae23846460 100644 --- a/tests/coverage/expect/Givens.scoverage.check +++ b/tests/coverage/pos/Givens.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -36,7 +36,7 @@ false 3 == "3" 2 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -53,7 +53,7 @@ false println(3 == "3") 3 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -70,7 +70,7 @@ false 3 == 5.1 4 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -87,7 +87,7 @@ false println(3 == 5.1) 5 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -104,7 +104,7 @@ false println(msg) 6 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -121,7 +121,7 @@ false println(ctx.id) 7 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -138,7 +138,7 @@ false i.toString 8 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -155,7 +155,7 @@ false Context(0) 9 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -172,7 +172,7 @@ false printContext("test")(using c) 10 -expect/Givens.scala +Givens.scala covtest Givens Class @@ -189,7 +189,7 @@ false getMessage(123) 11 -expect/Givens.scala +Givens.scala covtest Givens Class diff --git a/tests/coverage/expect/Inlined.scala b/tests/coverage/pos/Inlined.scala similarity index 100% rename from tests/coverage/expect/Inlined.scala rename to tests/coverage/pos/Inlined.scala diff --git a/tests/coverage/expect/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check similarity index 86% rename from tests/coverage/expect/Inlined.scoverage.check rename to tests/coverage/pos/Inlined.scoverage.check index 878bef241f6b..0f5b03f6e2f5 100644 --- a/tests/coverage/expect/Inlined.scoverage.check +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -36,7 +36,7 @@ false 1 2 -../../library/src/scala/runtime/stdLibPatches/Predef.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -53,7 +53,7 @@ false l == 1 3 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -70,7 +70,7 @@ false scala.runtime.Scala3RunTime 4 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -87,7 +87,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 5 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -104,7 +104,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 6 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -121,7 +121,7 @@ false List(l) 7 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -138,7 +138,7 @@ false l == List(l).length 8 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -155,7 +155,7 @@ false scala.runtime.Scala3RunTime 9 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -172,7 +172,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 10 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -189,7 +189,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 11 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -206,7 +206,7 @@ false List(l) 12 -../../library/src/scala/runtime/stdLibPatches/Predef.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -223,7 +223,7 @@ false List(l).length == 1 13 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -240,7 +240,7 @@ false scala.runtime.Scala3RunTime 14 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object @@ -257,7 +257,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 15 -expect/Inlined.scala +Inlined.scala covtest Inlined$package$ Object diff --git a/tests/coverage/expect/Literals.scala b/tests/coverage/pos/Literals.scala similarity index 83% rename from tests/coverage/expect/Literals.scala rename to tests/coverage/pos/Literals.scala index e8b55349d300..62297793bb70 100644 --- a/tests/coverage/expect/Literals.scala +++ b/tests/coverage/pos/Literals.scala @@ -3,7 +3,7 @@ def mustBeInstrumented = "literal" def thisOneToo = println("not this") // this literal should not be instrumented, only the println call - 12 // this literal must instrumented + 12 // this literal must be instrumented def f(x: Int, y: Int, z: Int)(t: Int) = ??? diff --git a/tests/coverage/expect/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check similarity index 90% rename from tests/coverage/expect/Literals.scoverage.check rename to tests/coverage/pos/Literals.scoverage.check index f748b7823d83..25bd06e3e4ac 100644 --- a/tests/coverage/expect/Literals.scoverage.check +++ b/tests/coverage/pos/Literals.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Literals.scala +Literals.scala Literals$package$ Object @@ -36,7 +36,7 @@ false "literal" 2 -expect/Literals.scala +Literals.scala Literals$package$ Object @@ -53,7 +53,7 @@ false println("not this") 3 -expect/Literals.scala +Literals.scala Literals$package$ Object @@ -70,14 +70,14 @@ false 12 4 -expect/Literals.scala +Literals.scala Literals$package$ Object .Literals$package$ main -246 -257 +249 +260 10 f Apply diff --git a/tests/coverage/expect/MatchCaseClasses.scala b/tests/coverage/pos/MatchCaseClasses.scala similarity index 100% rename from tests/coverage/expect/MatchCaseClasses.scala rename to tests/coverage/pos/MatchCaseClasses.scala diff --git a/tests/coverage/expect/MatchCaseClasses.scoverage.check b/tests/coverage/pos/MatchCaseClasses.scoverage.check similarity index 85% rename from tests/coverage/expect/MatchCaseClasses.scoverage.check rename to tests/coverage/pos/MatchCaseClasses.scoverage.check index c83905bcd9f2..51a7e2f6ec42 100644 --- a/tests/coverage/expect/MatchCaseClasses.scoverage.check +++ b/tests/coverage/pos/MatchCaseClasses.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -36,7 +36,7 @@ false println("a") 2 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -53,7 +53,7 @@ false println("b") 3 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -70,7 +70,7 @@ false println("c") 4 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -87,7 +87,7 @@ false println(y) 5 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -104,7 +104,7 @@ false println("d") 6 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object @@ -121,7 +121,7 @@ false println("e") 7 -expect/MatchCaseClasses.scala +MatchCaseClasses.scala covtest MatchCaseClasses$ Object diff --git a/tests/coverage/expect/MatchNumbers.scala b/tests/coverage/pos/MatchNumbers.scala similarity index 100% rename from tests/coverage/expect/MatchNumbers.scala rename to tests/coverage/pos/MatchNumbers.scala diff --git a/tests/coverage/expect/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check similarity index 88% rename from tests/coverage/expect/MatchNumbers.scoverage.check rename to tests/coverage/pos/MatchNumbers.scoverage.check index ce14acc72194..c7fa7c83f015 100644 --- a/tests/coverage/expect/MatchNumbers.scoverage.check +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/MatchNumbers.scala +MatchNumbers.scala covtest MatchNumbers$ Object @@ -36,7 +36,7 @@ false x < 0 2 -expect/MatchNumbers.scala +MatchNumbers.scala covtest MatchNumbers$ Object @@ -53,7 +53,7 @@ false -1 3 -expect/MatchNumbers.scala +MatchNumbers.scala covtest MatchNumbers$ Object @@ -70,7 +70,7 @@ false f(0) 4 -expect/MatchNumbers.scala +MatchNumbers.scala covtest MatchNumbers$ Object diff --git a/tests/coverage/expect/Select.scala b/tests/coverage/pos/Select.scala similarity index 100% rename from tests/coverage/expect/Select.scala rename to tests/coverage/pos/Select.scala diff --git a/tests/coverage/expect/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check similarity index 88% rename from tests/coverage/expect/Select.scoverage.check rename to tests/coverage/pos/Select.scoverage.check index b7e661353b4d..5419667a50ad 100644 --- a/tests/coverage/expect/Select.scoverage.check +++ b/tests/coverage/pos/Select.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/Select.scala +Select.scala covtest A Class @@ -36,7 +36,7 @@ false println("A") 2 -expect/Select.scala +Select.scala covtest B Class @@ -53,7 +53,7 @@ false A 3 -expect/Select.scala +Select.scala covtest B Class @@ -70,7 +70,7 @@ false super.print() 4 -expect/Select.scala +Select.scala covtest B Class @@ -87,7 +87,7 @@ false println(this.instance) 5 -expect/Select.scala +Select.scala covtest Select$package$ Object @@ -104,7 +104,7 @@ false A() 6 -expect/Select.scala +Select.scala covtest Select$package$ Object @@ -121,7 +121,7 @@ false new A 7 -expect/Select.scala +Select.scala covtest Select$package$ Object @@ -138,7 +138,7 @@ false a.instance.print() 8 -expect/Select.scala +Select.scala covtest Select$package$ Object diff --git a/tests/coverage/expect/StructuralTypes.scala b/tests/coverage/pos/StructuralTypes.scala similarity index 100% rename from tests/coverage/expect/StructuralTypes.scala rename to tests/coverage/pos/StructuralTypes.scala diff --git a/tests/coverage/expect/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check similarity index 85% rename from tests/coverage/expect/StructuralTypes.scoverage.check rename to tests/coverage/pos/StructuralTypes.scoverage.check index 9f5fb0cd014b..77616e0b1134 100644 --- a/tests/coverage/expect/StructuralTypes.scoverage.check +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/StructuralTypes.scala +StructuralTypes.scala covtest Record Class @@ -36,7 +36,7 @@ false _._1 == name 2 -expect/StructuralTypes.scala +StructuralTypes.scala covtest Record Class @@ -53,7 +53,7 @@ false elems.find(_._1 == name) 3 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object @@ -70,7 +70,7 @@ false "name" 4 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object @@ -87,7 +87,7 @@ false "name" -> "Emma" 5 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object @@ -104,7 +104,7 @@ false "age" 6 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object @@ -121,7 +121,7 @@ false "age" -> 42 7 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object @@ -138,7 +138,7 @@ false Record("name" -> "Emma", "age" -> 42) 8 -expect/StructuralTypes.scala +StructuralTypes.scala covtest StructuralTypes$ Object diff --git a/tests/coverage/expect/TypeLambdas.scala b/tests/coverage/pos/TypeLambdas.scala similarity index 100% rename from tests/coverage/expect/TypeLambdas.scala rename to tests/coverage/pos/TypeLambdas.scala diff --git a/tests/coverage/expect/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check similarity index 87% rename from tests/coverage/expect/TypeLambdas.scoverage.check rename to tests/coverage/pos/TypeLambdas.scoverage.check index 51cd040d8b3b..9485c5170872 100644 --- a/tests/coverage/expect/TypeLambdas.scoverage.check +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -19,7 +19,7 @@ # ' ' sign # ------------------------------------------ 1 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object @@ -36,7 +36,7 @@ false 1 2 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object @@ -53,7 +53,7 @@ false 1 -> "1" 3 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object @@ -70,7 +70,7 @@ false Map(1 -> "1") 4 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object @@ -87,7 +87,7 @@ false println(m) 5 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object @@ -104,7 +104,7 @@ false ("a", "b") 6 -expect/TypeLambdas.scala +TypeLambdas.scala covtest TypeLambdas$ Object diff --git a/tests/coverage/run/currying/test.check b/tests/coverage/run/currying/test.check new file mode 100644 index 000000000000..d21dbd35bc5c --- /dev/null +++ b/tests/coverage/run/currying/test.check @@ -0,0 +1,4 @@ +3 +3 +3 +3 \ No newline at end of file diff --git a/tests/coverage/expect/Currying.scala b/tests/coverage/run/currying/test.scala similarity index 53% rename from tests/coverage/expect/Currying.scala rename to tests/coverage/run/currying/test.scala index 12842cd181e1..4776b6bde855 100644 --- a/tests/coverage/expect/Currying.scala +++ b/tests/coverage/run/currying/test.scala @@ -1,6 +1,4 @@ -package covtest - -class ContextFunctions2: +object Test: def f1(a: Int)(b: Int)(c: Int) = a+b+c def f2 = (a: Int) => (b: Int) => (c: Int) => a+b+c @@ -10,7 +8,8 @@ class ContextFunctions2: def g2(using a: Int)(using b: Int)(using c: Int) = a+b+c - f1(0)(1)(2) - f2(0)(1)(2) - g1(using 0)(using 1)(using 2) - g2(using 0)(using 1)(using 2) + def main(args: Array[String]): Unit = + println(f1(0)(1)(2)) + println(f2(0)(1)(2)) + println(g1(using 0)(using 1)(using 2)) + println(g2(using 0)(using 1)(using 2)) diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check new file mode 100644 index 000000000000..89ea9c4008c1 --- /dev/null +++ b/tests/coverage/run/currying/test.scoverage.check @@ -0,0 +1,326 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +51 +1 ++ +Apply +false +0 +false +a+b + +2 +currying/test.scala + +Test$ +Object +.Test$ +f1 +48 +53 +1 ++ +Apply +false +0 +false +a+b+c + +3 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +108 +3 ++ +Apply +false +0 +false +a+b + +4 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +105 +110 +3 ++ +Apply +false +0 +false +a+b+c + +5 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +169 +6 ++ +Apply +false +0 +false +a+b + +6 +currying/test.scala + +Test$ +Object +.Test$ +$anonfun +166 +171 +6 ++ +Apply +false +0 +false +a+b+c + +7 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +229 +8 ++ +Apply +false +0 +false +a+b + +8 +currying/test.scala + +Test$ +Object +.Test$ +g2 +226 +231 +8 ++ +Apply +false +0 +false +a+b+c + +9 +currying/test.scala + +Test$ +Object +.Test$ +main +285 +296 +11 +f1 +Apply +false +0 +false +f1(0)(1)(2) + +10 +currying/test.scala + +Test$ +Object +.Test$ +main +277 +297 +11 +println +Apply +false +0 +false +println(f1(0)(1)(2)) + +11 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +315 +12 +apply +Apply +false +0 +false +f2(0) + +12 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +318 +12 +apply +Apply +false +0 +false +f2(0)(1) + +13 +currying/test.scala + +Test$ +Object +.Test$ +main +310 +321 +12 +apply +Apply +false +0 +false +f2(0)(1)(2) + +14 +currying/test.scala + +Test$ +Object +.Test$ +main +302 +322 +12 +println +Apply +false +0 +false +println(f2(0)(1)(2)) + +15 +currying/test.scala + +Test$ +Object +.Test$ +main +335 +364 +13 +apply +Apply +false +0 +false +g1(using 0)(using 1)(using 2) + +16 +currying/test.scala + +Test$ +Object +.Test$ +main +327 +365 +13 +println +Apply +false +0 +false +println(g1(using 0)(using 1)(using 2)) + +17 +currying/test.scala + +Test$ +Object +.Test$ +main +378 +407 +14 +g2 +Apply +false +0 +false +g2(using 0)(using 1)(using 2) + +18 +currying/test.scala + +Test$ +Object +.Test$ +main +370 +408 +14 +println +Apply +false +0 +false +println(g2(using 0)(using 1)(using 2)) + diff --git a/tests/coverage/run/inheritance/test.check b/tests/coverage/run/inheritance/test.check new file mode 100644 index 000000000000..59fbc504fb8c --- /dev/null +++ b/tests/coverage/run/inheritance/test.check @@ -0,0 +1,3 @@ +block +1 +2 \ No newline at end of file diff --git a/tests/coverage/expect/Inheritance.scala b/tests/coverage/run/inheritance/test.scala similarity index 84% rename from tests/coverage/expect/Inheritance.scala rename to tests/coverage/run/inheritance/test.scala index e90fb87237ea..b5be83a378ea 100644 --- a/tests/coverage/expect/Inheritance.scala +++ b/tests/coverage/run/inheritance/test.scala @@ -1,11 +1,10 @@ -package covtest - class A(val x: Int, val y: Int) class B(x: Int) extends A(x, 0) class C1 extends B({println("block"); 1}) class C2 extends B(A(2,2).x) -def testInheritance = +@main +def Test: Unit = println(C1().x) // block // 1 println(C2().x) // 2 diff --git a/tests/coverage/expect/Inheritance.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check similarity index 56% rename from tests/coverage/expect/Inheritance.scoverage.check rename to tests/coverage/run/inheritance/test.scoverage.check index 70acf1456d5e..a714673ea3b5 100644 --- a/tests/coverage/expect/Inheritance.scoverage.check +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -19,15 +19,15 @@ # ' ' sign # ------------------------------------------ 1 -expect/Inheritance.scala -covtest +inheritance/test.scala + B Class -covtest.B +.B -73 -80 -3 +56 +63 +1 Apply false @@ -36,15 +36,15 @@ false A(x, 0) 2 -expect/Inheritance.scala -covtest +inheritance/test.scala + C1 Class -covtest.C1 +.C1 -101 -117 -4 +84 +100 +2 println Apply false @@ -53,15 +53,15 @@ false println("block") 3 -expect/Inheritance.scala -covtest +inheritance/test.scala + C1 Class -covtest.C1 +.C1 -98 -122 -4 +81 +105 +2 Apply false @@ -70,15 +70,15 @@ false B({println("block"); 1}) 4 -expect/Inheritance.scala -covtest +inheritance/test.scala + C2 Class -covtest.C2 +.C2 -142 -148 -5 +125 +131 +3 Apply false @@ -87,15 +87,15 @@ false A(2,2) 5 -expect/Inheritance.scala -covtest +inheritance/test.scala + C2 Class -covtest.C2 +.C2 -140 -151 -5 +123 +134 +3 Apply false @@ -104,15 +104,15 @@ false B(A(2,2).x) 6 -expect/Inheritance.scala -covtest -Inheritance$package$ +inheritance/test.scala + +test$package$ Object -covtest.Inheritance$package$ -testInheritance -185 -189 -8 +.test$package$ +Test +169 +173 +7 Apply false @@ -121,15 +121,15 @@ false C1() 7 -expect/Inheritance.scala -covtest -Inheritance$package$ +inheritance/test.scala + +test$package$ Object -covtest.Inheritance$package$ -testInheritance -177 -192 -8 +.test$package$ +Test +161 +176 +7 println Apply false @@ -138,15 +138,15 @@ false println(C1().x) 8 -expect/Inheritance.scala -covtest -Inheritance$package$ +inheritance/test.scala + +test$package$ Object -covtest.Inheritance$package$ -testInheritance -235 -239 -10 +.test$package$ +Test +219 +223 +9 Apply false @@ -155,15 +155,15 @@ false C2() 9 -expect/Inheritance.scala -covtest -Inheritance$package$ +inheritance/test.scala + +test$package$ Object -covtest.Inheritance$package$ -testInheritance -227 -242 -10 +.test$package$ +Test +211 +226 +9 println Apply false diff --git a/tests/coverage/run/interpolation/test.check b/tests/coverage/run/interpolation/test.check new file mode 100644 index 000000000000..9ce4b367db49 --- /dev/null +++ b/tests/coverage/run/interpolation/test.check @@ -0,0 +1,5 @@ +0: d +1: o +2: t +3: t +4: y diff --git a/tests/coverage/expect/ParamsAndInterpolation.scala b/tests/coverage/run/interpolation/test.scala similarity index 72% rename from tests/coverage/expect/ParamsAndInterpolation.scala rename to tests/coverage/run/interpolation/test.scala index 34d7ae300811..3745367b593d 100644 --- a/tests/coverage/expect/ParamsAndInterpolation.scala +++ b/tests/coverage/run/interpolation/test.scala @@ -1,11 +1,9 @@ -package covtest - -object ParamsAndInterpolation: +object Test: def simple(a: Int, b: String): String = s"$a, ${b.length}" - def test(): Unit = + def main(args: Array[String]): Unit = val xs: List[String] = List("d", "o", "t", "t", "y") xs.zipWithIndex.map((s, i) => println(s"$i: $s")) diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check new file mode 100644 index 000000000000..de100e7c116e --- /dev/null +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -0,0 +1,224 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +62 +77 +3 +apply +Apply +false +0 +false +$a, ${b.length} + +2 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +62 +77 +3 +invoked +Apply +false +0 +false +$a, ${b.length} + +3 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +62 +77 +3 +apply +Apply +false +0 +false +$a, ${b.length} + +4 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +68 +76 +3 +length +Apply +false +0 +false +b.length + +5 +interpolation/test.scala + +Test$ +Object +.Test$ +simple +60 +78 +3 +s +Apply +false +0 +false +s"$a, ${b.length}" + +6 +interpolation/test.scala + +Test$ +Object +.Test$ +main +147 +176 +6 +apply +Apply +false +0 +false +List("d", "o", "t", "t", "y") + +7 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +222 +228 +8 +apply +Apply +false +0 +false +$i: $s + +8 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +222 +228 +8 +invoked +Apply +false +0 +false +$i: $s + +9 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +222 +228 +8 +apply +Apply +false +0 +false +$i: $s + +10 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +220 +229 +8 +s +Apply +false +0 +false +s"$i: $s" + +11 +interpolation/test.scala + +Test$ +Object +.Test$ +$anonfun +212 +230 +8 +println +Apply +false +0 +false +println(s"$i: $s") + +12 +interpolation/test.scala + +Test$ +Object +.Test$ +main +182 +231 +8 +map +Apply +false +0 +false +xs.zipWithIndex.map((s, i) => println(s"$i: $s")) + diff --git a/tests/coverage/run/lifting/test.check b/tests/coverage/run/lifting/test.check new file mode 100644 index 000000000000..fe4a8088d523 --- /dev/null +++ b/tests/coverage/run/lifting/test.check @@ -0,0 +1,3 @@ +string123.0.0 +string123.0.0 +string-1.0.123 diff --git a/tests/coverage/run/lifting/test.scala b/tests/coverage/run/lifting/test.scala new file mode 100644 index 000000000000..2b44168e3aed --- /dev/null +++ b/tests/coverage/run/lifting/test.scala @@ -0,0 +1,20 @@ +class Vals: + val l = List(1) + val ll = l :: List(1,2,3) + +class A: + def msg(a: Int, b: Int, c: Int) = "string" + a + "." + b + "." + c + def integer: Int = 0 + def ex: this.type = this + +@main +def Test: Unit = + val a = A() + val i = 123 + def f() = -1 + var x = a.msg(i, 0, a.integer) + println(x) + x = a.ex.msg(i, 0, a.ex.integer) + println(x) + x = a.msg(f(), 0, i) + println(x) diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check new file mode 100644 index 000000000000..701110a58615 --- /dev/null +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -0,0 +1,360 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +1 +lifting/test.scala + +Vals +Class +.Vals + +22 +29 +1 +apply +Apply +false +0 +false +List(1) + +2 +lifting/test.scala + +Vals +Class +.Vals + +46 +57 +2 +apply +Apply +false +0 +false +List(1,2,3) + +3 +lifting/test.scala + +Vals +Class +.Vals + +41 +57 +2 +:: +Apply +false +0 +false +l :: List(1,2,3) + +4 +lifting/test.scala + +A +Class +.A +msg +104 +112 +5 + +Literal +false +0 +false +"string" + +5 +lifting/test.scala + +A +Class +.A +msg +104 +116 +5 ++ +Apply +false +0 +false +"string" + a + +6 +lifting/test.scala + +A +Class +.A +msg +104 +122 +5 ++ +Apply +false +0 +false +"string" + a + "." + +7 +lifting/test.scala + +A +Class +.A +msg +104 +126 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + +8 +lifting/test.scala + +A +Class +.A +msg +104 +132 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + +9 +lifting/test.scala + +A +Class +.A +msg +104 +136 +5 ++ +Apply +false +0 +false +"string" + a + "." + b + "." + c + +10 +lifting/test.scala + +A +Class +.A +integer +158 +159 +6 + +Literal +false +0 +false +0 + +11 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +221 +224 +11 + +Apply +false +0 +false +A() + +12 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +235 +238 +12 + +Literal +false +0 +false +123 + +13 +lifting/test.scala + +test$package$ +Object +.test$package$ +f +251 +253 +13 + +Literal +false +0 +false +-1 + +14 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +264 +286 +14 +msg +Apply +false +0 +false +a.msg(i, 0, a.integer) + +15 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +289 +299 +15 +println +Apply +false +0 +false +println(x) + +16 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +306 +334 +16 +msg +Apply +false +0 +false +a.ex.msg(i, 0, a.ex.integer) + +17 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +337 +347 +17 +println +Apply +false +0 +false +println(x) + +18 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +360 +363 +18 +f +Apply +false +0 +false +f() + +19 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +354 +370 +18 +msg +Apply +false +0 +false +a.msg(f(), 0, i) + +20 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +373 +383 +19 +println +Apply +false +0 +false +println(x) + diff --git a/tests/coverage/run/trait/test.check b/tests/coverage/run/trait/test.check new file mode 100644 index 000000000000..8c9ca6a4ce24 --- /dev/null +++ b/tests/coverage/run/trait/test.check @@ -0,0 +1,3 @@ +0 +test +test diff --git a/tests/coverage/expect/Trait.scala b/tests/coverage/run/trait/test.scala similarity index 87% rename from tests/coverage/expect/Trait.scala rename to tests/coverage/run/trait/test.scala index 90ad91e74284..54166d77c17e 100644 --- a/tests/coverage/expect/Trait.scala +++ b/tests/coverage/run/trait/test.scala @@ -1,5 +1,3 @@ -package covtest - trait T1: def x = 0 @@ -9,7 +7,8 @@ trait T2(val p: String) class Impl2 extends T2("test") with T1 class Impl3 extends T2(Impl2().p) -def testTrait = +@main +def Test: Unit = println(Impl1().x) // 0 println(Impl2().p) // test println(Impl3().p) // test diff --git a/tests/coverage/expect/Trait.scoverage.check b/tests/coverage/run/trait/test.scoverage.check similarity index 58% rename from tests/coverage/expect/Trait.scoverage.check rename to tests/coverage/run/trait/test.scoverage.check index 8a752e0e0686..ac6000132788 100644 --- a/tests/coverage/expect/Trait.scoverage.check +++ b/tests/coverage/run/trait/test.scoverage.check @@ -19,15 +19,15 @@ # ' ' sign # ------------------------------------------ 1 -expect/Trait.scala -covtest +trait/test.scala + T1 Trait -covtest.T1 +.T1 x -37 -38 -3 +20 +21 +1 Literal false @@ -36,15 +36,15 @@ false 0 2 -expect/Trait.scala -covtest +trait/test.scala + Impl2 Class -covtest.Impl2 +.Impl2 -108 -118 -8 +91 +101 +6 Apply false @@ -53,15 +53,15 @@ false T2("test") 3 -expect/Trait.scala -covtest +trait/test.scala + Impl3 Class -covtest.Impl3 +.Impl3 -150 -157 -9 +133 +140 +7 Apply false @@ -70,15 +70,15 @@ false Impl2() 4 -expect/Trait.scala -covtest +trait/test.scala + Impl3 Class -covtest.Impl3 +.Impl3 -147 -160 -9 +130 +143 +7 Apply false @@ -87,15 +87,15 @@ false T2(Impl2().p) 5 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -188 -195 -12 +.test$package$ +Test +178 +185 +11 Apply false @@ -104,15 +104,15 @@ false Impl1() 6 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -180 -198 -12 +.test$package$ +Test +170 +188 +11 println Apply false @@ -121,15 +121,15 @@ false println(Impl1().x) 7 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -214 -221 -13 +.test$package$ +Test +204 +211 +12 Apply false @@ -138,15 +138,15 @@ false Impl2() 8 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -206 -224 -13 +.test$package$ +Test +196 +214 +12 println Apply false @@ -155,15 +155,15 @@ false println(Impl2().p) 9 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -243 -250 -14 +.test$package$ +Test +233 +240 +13 Apply false @@ -172,15 +172,15 @@ false Impl3() 10 -expect/Trait.scala -covtest -Trait$package$ +trait/test.scala + +test$package$ Object -covtest.Trait$package$ -testTrait -235 -253 -14 +.test$package$ +Test +225 +243 +13 println Apply false From a1d0e641f29c7f5c6a013265875e8d77d91523ab Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 29 Mar 2022 11:44:59 +0200 Subject: [PATCH 060/121] Cleanup coverage instrumentation --- NOTICE.md | 5 + compiler/src/dotty/tools/dotc/ast/Trees.scala | 16 +- .../tools/dotc/config/ScalaSettings.scala | 2 +- .../dotty/tools/dotc/core/Definitions.scala | 3 +- .../dotty/tools/dotc/coverage/Coverage.scala | 3 + .../dotty/tools/dotc/coverage/Location.scala | 20 +- .../tools/dotc/coverage/Serializer.scala | 4 +- .../dotc/transform/InstrumentCoverage.scala | 244 +++++++++--------- .../tools/dotc/coverage/CoverageTests.scala | 9 +- project/Build.scala | 1 - tests/coverage/pos/Inlined.scoverage.check | 22 +- 11 files changed, 172 insertions(+), 157 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 64b5f9122db7..64ebae49efe5 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -88,6 +88,10 @@ major authors were omitted by oversight. docs/js/. Please refer to the license header of the concerned files for details. + * dotty.tools.dotc.coverage: Coverage instrumentation utilities have been + adapted from the scoverage plugin for scala 2 [5], which is under the + Apache 2.0 license. + * The Dotty codebase contains parts which are derived from the ScalaPB protobuf library [4], which is under the Apache 2.0 license. @@ -96,3 +100,4 @@ major authors were omitted by oversight. [2] https://github.com/adriaanm/scala/tree/sbt-api-consolidate/src/compiler/scala/tools/sbt [3] https://github.com/sbt/sbt/tree/0.13/compile/interface/src/main/scala/xsbt [4] https://github.com/lampepfl/dotty/pull/5783/files +[5] https://github.com/scoverage/scalac-scoverage-plugin diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index cb20a7c9da37..67464e4b063a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1367,13 +1367,19 @@ object Trees { /** The context to use when mapping or accumulating over a tree */ def localCtx(tree: Tree)(using Context): Context + /** The context to use when transforming a tree. + * It ensures that the source information is correct. + * TODO: ensure transform is always called with the correct context as argument + * @see https://github.com/lampepfl/dotty/pull/13880#discussion_r836395977 + */ + def transformCtx(tree: Tree)(using Context): Context = + if tree.source.exists && tree.source != ctx.source + then ctx.withSource(tree.source) + else ctx + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(using Context): Tree = { - inContext( - if tree.source != ctx.source && tree.source.exists - then ctx.withSource(tree.source) - else ctx - ){ + inContext(transformCtx(tree)){ Stats.record(s"TreeMap.transform/$getClass") if (skipTransform(tree)) tree else tree match { diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index a1ce1591e9af..7a00c1d5e3ed 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -116,9 +116,9 @@ trait CommonScalaSettings: val explainTypes: Setting[Boolean] = BooleanSetting("-explain-types", "Explain type errors in more detail (deprecated, use -explain instead).", aliases = List("--explain-types", "-explaintypes")) val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.", initialValue = true, aliases = List("--unchecked")) val language: Setting[List[String]] = MultiStringSetting("-language", "feature", "Enable one or more language features.", aliases = List("--language")) + /* Coverage settings */ val coverageOutputDir = PathSetting("-coverage-out", "Destination for coverage classfiles and instrumentation data.", "", aliases = List("--coverage-out")) - val coverageSourceroot = PathSetting("-coverage-sourceroot", "An alternative root dir of your sources used to relativize.", ".", aliases = List("--coverage-sourceroot")) /* Other settings */ val encoding: Setting[String] = StringSetting("-encoding", "encoding", "Specify character encoding used by source files.", Properties.sourceEncoding, aliases = List("--encoding")) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 47780b302c8c..5eb1ccb0f957 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -461,7 +461,8 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef - @tu lazy val InvokerModuleRef = requiredModuleRef("scala.runtime.coverage.Invoker") + @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") + @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") @tu lazy val ImplicitScrutineeTypeSym = newPermanentSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 05ced1075edc..6140c937171e 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -3,6 +3,7 @@ package coverage import scala.collection.mutable +/** Holds a list of statements to include in the coverage reports. */ class Coverage: private val statementsById = new mutable.LongMap[Statement](256) @@ -10,6 +11,7 @@ class Coverage: def addStatement(stmt: Statement): Unit = statementsById(stmt.id) = stmt +/** A statement that can be invoked, and thus counted as "covered" by code coverage tools. */ case class Statement( source: String, location: Location, @@ -24,6 +26,7 @@ case class Statement( var count: Int = 0, ignored: Boolean = false ): + /** Records that this statement has been invoked one more time. */ def invoked(): Unit = count += 1 diff --git a/compiler/src/dotty/tools/dotc/coverage/Location.scala b/compiler/src/dotty/tools/dotc/coverage/Location.scala index 0c0181baa305..faf1e97d0c01 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Location.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Location.scala @@ -4,13 +4,16 @@ package coverage import ast.tpd._ import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags.* +import java.nio.file.Path -/** @param packageName - * the name of the encosing package - * @param className - * the name of the closes enclosing class - * @param fullClassName - * the fully qualified name of the closest enclosing class +/** Information about the location of a coverable piece of code. + * + * @param packageName name of the enclosing package + * @param className name of the closest enclosing class + * @param fullClassName fully qualified name of the closest enclosing class + * @param classType "type" of the closest enclosing class: Class, Trait or Object + * @param method name of the closest enclosing method + * @param sourcePath absolute path of the source file */ final case class Location( packageName: String, @@ -18,10 +21,11 @@ final case class Location( fullClassName: String, classType: String, method: String, - sourcePath: String + sourcePath: Path ) object Location: + /** Extracts the location info of a Tree. */ def apply(tree: Tree)(using ctx: Context): Location = val enclosingClass = ctx.owner.denot.enclosingClass @@ -39,5 +43,5 @@ object Location: s"$packageName.$className", classType, ctx.owner.denot.enclosingMethod.name.toSimpleName.toString(), - ctx.source.file.absolute.toString() + ctx.source.file.absolute.jpath ) diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index c5e6440afffd..c4f633dcdfa2 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -30,8 +30,8 @@ object Serializer: */ def serialize(coverage: Coverage, writer: Writer, sourceRoot: Path): Unit = - def getRelativePath(filePath: String): String = - val relPath = sourceRoot.relativize(Paths.get(filePath).toAbsolutePath) + def getRelativePath(filePath: Path): String = + val relPath = sourceRoot.relativize(filePath) relPath.toString def writeHeader(writer: Writer): Unit = diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index a04e4c98e9a9..cf18f09b044c 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -18,7 +18,7 @@ import util.{SourcePosition, Property} import util.Spans.Span import coverage.* -/** Implements code coverage by inserting calls to scala.runtime.Invoker +/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker * ("instruments" the source code). * The result can then be consumed by the Scoverage tool. */ @@ -29,20 +29,18 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: override def description = InstrumentCoverage.description - // Enabled by argument "-coverage OUTPUT_DIR" + // Enabled by argument "-coverage-out OUTPUT_DIR" override def isEnabled(using ctx: Context) = ctx.settings.coverageOutputDir.value.nonEmpty - // Atomic counter used for assignation of IDs to difference statements - private val statementId = AtomicInteger(0) + // counter to assign a unique id to each statement + private var statementId = 0 - private var outputPath = "" - - // Main class used to store all instrumented statements + // stores all instrumented statements private val coverage = Coverage() override def run(using ctx: Context): Unit = - outputPath = ctx.settings.coverageOutputDir.value + val outputPath = ctx.settings.coverageOutputDir.value // Ensure the dir exists val dataDir = new File(outputPath) @@ -56,132 +54,131 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: end if super.run - Serializer.serialize(coverage, outputPath, ctx.settings.coverageSourceroot.value) + Serializer.serialize(coverage, outputPath, ctx.settings.sourceroot.value) - override protected def newTransformer(using Context) = CoverageTransormer() + override protected def newTransformer(using Context) = CoverageTransformer() /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */ - private class CoverageTransormer extends Transformer: + private class CoverageTransformer extends Transformer: private val IgnoreLiterals = new Property.Key[Boolean] private def ignoreLiteralsContext(using ctx: Context): Context = ctx.fresh.setProperty(IgnoreLiterals, true) override def transform(tree: Tree)(using ctx: Context): Tree = - tree match - // simple cases - case tree: (Import | Export | This | Super | New) => tree - case tree if (tree.isEmpty || tree.isType) => tree // empty Thicket, Ident, TypTree, ... - - // Literals must be instrumented (at least) when returned by a def, - // otherwise `def d = "literal"` is not covered when called from a test. - // They can be left untouched when passed in a parameter of an Apply. - case tree: Literal => - if ctx.property(IgnoreLiterals).contains(true) then - tree - else - instrument(tree) - - // branches - case tree: If => - cpy.If(tree)( - cond = transform(tree.cond), - thenp = instrument(transform(tree.thenp), branch = true), - elsep = instrument(transform(tree.elsep), branch = true) - ) - case tree: Try => - cpy.Try(tree)( - expr = instrument(transform(tree.expr), branch = true), - cases = instrumentCases(tree.cases), - finalizer = instrument(transform(tree.finalizer), true) - ) - - // a.f(args) - case tree @ Apply(fun: Select, args) => - // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` - if canInstrumentApply(tree) then - val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) - if needsLift(tree) then - val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted - instrumentLifted(transformed)(using ignoreLiteralsContext) + inContext(transformCtx(tree)) { // necessary to position inlined code properly + tree match + // simple cases + case tree: (Import | Export | This | Super | New) => tree + case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ... + + // Literals must be instrumented (at least) when returned by a def, + // otherwise `def d = "literal"` is not covered when called from a test. + // They can be left untouched when passed in a parameter of an Apply. + case tree: Literal => + if ctx.property(IgnoreLiterals).contains(true) then + tree else - val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) - instrument(transformed)(using ignoreLiteralsContext) - else - tree - - // f(args) - case tree: Apply => - if canInstrumentApply(tree) then - if needsLift(tree) then - instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals + instrument(tree) + + // branches + case tree: If => + cpy.If(tree)( + cond = transform(tree.cond), + thenp = instrument(transform(tree.thenp), branch = true), + elsep = instrument(transform(tree.elsep), branch = true) + ) + case tree: Try => + cpy.Try(tree)( + expr = instrument(transform(tree.expr), branch = true), + cases = instrumentCases(tree.cases), + finalizer = instrument(transform(tree.finalizer), true) + ) + + // a.f(args) + case tree @ Apply(fun: Select, args) => + // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` + if canInstrumentApply(tree) then + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) + if needsLift(tree) then + val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted + instrumentLifted(transformed)(using ignoreLiteralsContext) + else + val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) + instrument(transformed)(using ignoreLiteralsContext) else - instrument(super.transform(tree)(using ignoreLiteralsContext)) - else - tree - - // (f(x))[args] - case TypeApply(fun: Apply, args) => - cpy.TypeApply(tree)(transform(fun), args) - - // a.b - case Select(qual, name) => - if qual.symbol.exists && qual.symbol.is(JavaDefined) then - //Java class can't be used as a value, we can't instrument the - //qualifier ({;System}.xyz() is not possible !) instrument it - //as it is - instrument(tree) - else - val transformed = cpy.Select(tree)(transform(qual), name) - if transformed.qualifier.isDef then - // instrument calls to methods without parameter list - instrument(transformed) + tree + + // f(args) + case tree: Apply => + if canInstrumentApply(tree) then + if needsLift(tree) then + instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals + else + instrument(super.transform(tree)(using ignoreLiteralsContext)) else - transformed - - case tree: CaseDef => instrumentCaseDef(tree) - case tree: ValDef => - // only transform the rhs, in the local context - val rhs = transform(tree.rhs)(using localCtx(tree)) - cpy.ValDef(tree)(rhs=rhs) - - case tree: DefDef => - // only transform the params (for the default values) and the rhs - val defCtx = localCtx(tree) - val paramss = transformParamss(tree.paramss)(using defCtx) - val rhs = transform(tree.rhs)(using defCtx) - cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs) - - case tree: PackageDef => - // only transform the statements of the package - cpy.PackageDef(tree)(tree.pid, transform(tree.stats)(using localCtx(tree))) - case tree: Assign => - // only transform the rhs - cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) - - // For everything else just recurse and transform - // Special care for Templates: it's important to set the owner of the `stats`, like super.transform - case _ => - super.transform(tree) + tree + + // (f(x))[args] + case TypeApply(fun: Apply, args) => + cpy.TypeApply(tree)(transform(fun), args) + + // a.b + case Select(qual, name) => + if qual.symbol.exists && qual.symbol.is(JavaDefined) then + //Java class can't be used as a value, we can't instrument the + //qualifier ({;System}.xyz() is not possible !) instrument it + //as it is + instrument(tree) + else + val transformed = cpy.Select(tree)(transform(qual), name) + if transformed.qualifier.isDef then + // instrument calls to methods without parameter list + instrument(transformed) + else + transformed + + case tree: CaseDef => instrumentCaseDef(tree) + case tree: ValDef => + // only transform the rhs, in the local context + val rhs = transform(tree.rhs)(using localCtx(tree)) + cpy.ValDef(tree)(rhs=rhs) + + case tree: DefDef => + // only transform the params (for the default values) and the rhs + val defCtx = localCtx(tree) + val paramss = transformParamss(tree.paramss)(using defCtx) + val rhs = transform(tree.rhs)(using defCtx) + cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs) + + case tree: PackageDef => + // only transform the statements of the package + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)(using localCtx(tree))) + case tree: Assign => + // only transform the rhs + cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) + + // For everything else just recurse and transform + // Special care for Templates: it's important to set the owner of the `stats`, like super.transform + case _ => + super.transform(tree) + } /** Lifts and instruments an application. * Note that if only one arg needs to be lifted, we just lift everything. */ def instrumentLifted(tree: Apply)(using Context) = - // withSource is necessary to position inlined code properly - inContext(ctx.withSource(tree.source)) { - // lifting - val buffer = mutable.ListBuffer[Tree]() - val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) - - // instrumentation - val instrumentedArgs = buffer.toList.map(transform) - val instrumentedApply = instrument(liftedApply) - Block( - instrumentedArgs, - instrumentedApply - ).withSpan(instrumentedApply.span) - } + // lifting + val buffer = mutable.ListBuffer[Tree]() + val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) + + // instrumentation + val instrumentedArgs = buffer.toList.map(transform) + val instrumentedApply = instrument(liftedApply) + Block( + instrumentedArgs, + instrumentedApply + ).withSpan(instrumentedApply.span) def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = cases.map(instrumentCaseDef) @@ -194,7 +191,8 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = if pos.exists && !pos.span.isZeroExtent then - val id = statementId.incrementAndGet() + statementId += 1 + val id = statementId val statement = new Statement( source = ctx.source.file.name, location = Location(tree), @@ -208,16 +206,14 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: branch ) coverage.addStatement(statement) - inContext(ctx.withSource(tree.source)) { - val span = Span(pos.start, pos.end) // synthetic span - Block(List(invokeCall(id, span)), tree).withSpan(span) - } + val span = Span(pos.start, pos.end) // synthetic span + Block(List(invokeCall(id, span)), tree).withSpan(span) else tree def invokeCall(id: Int, span: Span)(using Context): Tree = - ref(defn.InvokerModuleRef).withSpan(span) - .select("invoked".toTermName).withSpan(span) + val outputPath = ctx.settings.coverageOutputDir.value + ref(defn.InvokedMethodRef).withSpan(span) .appliedToArgs( List(Literal(Constant(id)), Literal(Constant(outputPath))) ).withSpan(span) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index c431d6358b5a..84e07003cf0d 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -6,20 +6,21 @@ import org.junit.Assert.* import org.junit.experimental.categories.Category import dotty.{BootstrappedOnlyTests, Properties} -import dotty.tools.vulpix.TestConfiguration.* import dotty.tools.vulpix.* +import dotty.tools.vulpix.TestConfiguration.* +import dotty.tools.dotc.Main import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption} import scala.jdk.CollectionConverters.* +import scala.util.Properties.userDir import scala.language.unsafeNulls -import dotty.tools.dotc.Main @Category(Array(classOf[BootstrappedOnlyTests])) class CoverageTests: import CoverageTests.{*, given} private val scalaFile = FileSystems.getDefault.getPathMatcher("glob:**.scala") - private val rootSrc = Paths.get(System.getProperty("dotty.tools.dotc.coverage.test")) + private val rootSrc = Paths.get(userDir, "tests", "coverage") @Test def checkCoverageStatements(): Unit = @@ -54,7 +55,7 @@ class CoverageTests: /** Generates the coverage report for the given input file, in a temporary directory. */ def computeCoverageInTmp(inputFile: Path, sourceRoot: Path, run: Boolean)(using TestGroup): Path = val target = Files.createTempDirectory("coverage") - val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-coverage-sourceroot", sourceRoot.toString) + val options = defaultOptions.and("-Ycheck:instrumentCoverage", "-coverage-out", target.toString, "-sourceroot", sourceRoot.toString) val test = compileFile(inputFile.toString, options) if run then test.checkRuns() diff --git a/project/Build.scala b/project/Build.scala index c754f7ff1464..abe42e6a640f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -577,7 +577,6 @@ object Build { javaOptions ++= Seq( s"-Ddotty.tools.dotc.semanticdb.test=${(ThisBuild / baseDirectory).value/"tests"/"semanticdb"}", - s"-Ddotty.tools.dotc.coverage.test=${(ThisBuild / baseDirectory).value/"tests"/"coverage"}", ), testCompilation := Def.inputTaskDyn { diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check index 0f5b03f6e2f5..016e3ef56ad5 100644 --- a/tests/coverage/pos/Inlined.scoverage.check +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -36,7 +36,7 @@ false 1 2 -../../../library/src/scala/runtime/stdLibPatches/Predef.scala +Inlined.scala covtest Inlined$package$ Object @@ -53,7 +53,7 @@ false l == 1 3 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -70,7 +70,7 @@ false scala.runtime.Scala3RunTime 4 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -87,7 +87,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 5 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -138,7 +138,7 @@ false l == List(l).length 8 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -155,7 +155,7 @@ false scala.runtime.Scala3RunTime 9 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -172,7 +172,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 10 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -206,7 +206,7 @@ false List(l) 12 -../../../library/src/scala/runtime/stdLibPatches/Predef.scala +Inlined.scala covtest Inlined$package$ Object @@ -223,7 +223,7 @@ false List(l).length == 1 13 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -240,7 +240,7 @@ false scala.runtime.Scala3RunTime 14 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object @@ -257,7 +257,7 @@ false scala.runtime.Scala3RunTime.assertFailed() 15 -Inlined.scala +../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ Object From 169593dfd12b761f2a8f84867336dbc53f04e251 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 29 Mar 2022 16:37:37 +0200 Subject: [PATCH 061/121] Make Trees.transformCtx return the local context if needed --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 46 ++++++++----------- .../dotc/transform/InstrumentCoverage.scala | 13 +++--- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 67464e4b063a..5afbb7f416ed 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1368,18 +1368,25 @@ object Trees { def localCtx(tree: Tree)(using Context): Context /** The context to use when transforming a tree. - * It ensures that the source information is correct. + * It ensures that the source is correct, and that the local context is used if + * that's necessary for transforming the whole tree. * TODO: ensure transform is always called with the correct context as argument * @see https://github.com/lampepfl/dotty/pull/13880#discussion_r836395977 */ def transformCtx(tree: Tree)(using Context): Context = - if tree.source.exists && tree.source != ctx.source - then ctx.withSource(tree.source) - else ctx + val sourced = + if tree.source.exists && tree.source != ctx.source + then ctx.withSource(tree.source) + else ctx + tree match + case t: (MemberDef | PackageDef | LambdaTypeTree | TermLambdaTypeTree) => + localCtx(t)(using sourced) + case _ => + sourced abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(using Context): Tree = { - inContext(transformCtx(tree)){ + inContext(transformCtx(tree)) { Stats.record(s"TreeMap.transform/$getClass") if (skipTransform(tree)) tree else tree match { @@ -1436,13 +1443,9 @@ object Trees { case AppliedTypeTree(tpt, args) => cpy.AppliedTypeTree(tree)(transform(tpt), transform(args)) case LambdaTypeTree(tparams, body) => - inContext(localCtx(tree)) { - cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) - } + cpy.LambdaTypeTree(tree)(transformSub(tparams), transform(body)) case TermLambdaTypeTree(params, body) => - inContext(localCtx(tree)) { - cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) - } + cpy.TermLambdaTypeTree(tree)(transformSub(params), transform(body)) case MatchTypeTree(bound, selector, cases) => cpy.MatchTypeTree(tree)(transform(bound), transform(selector), transformSub(cases)) case ByNameTypeTree(result) => @@ -1458,19 +1461,13 @@ object Trees { case EmptyValDef => tree case tree @ ValDef(name, tpt, _) => - inContext(localCtx(tree)) { - val tpt1 = transform(tpt) - val rhs1 = transform(tree.rhs) - cpy.ValDef(tree)(name, tpt1, rhs1) - } + val tpt1 = transform(tpt) + val rhs1 = transform(tree.rhs) + cpy.ValDef(tree)(name, tpt1, rhs1) case tree @ DefDef(name, paramss, tpt, _) => - inContext(localCtx(tree)) { - cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) - } + cpy.DefDef(tree)(name, transformParamss(paramss), transform(tpt), transform(tree.rhs)) case tree @ TypeDef(name, rhs) => - inContext(localCtx(tree)) { - cpy.TypeDef(tree)(name, transform(rhs)) - } + cpy.TypeDef(tree)(name, transform(rhs)) case tree @ Template(constr, parents, self, _) if tree.derived.isEmpty => cpy.Template(tree)(transformSub(constr), transform(tree.parents), Nil, transformSub(self), transformStats(tree.body, tree.symbol)) case Import(expr, selectors) => @@ -1478,10 +1475,7 @@ object Trees { case Export(expr, selectors) => cpy.Export(tree)(transform(expr), selectors) case PackageDef(pid, stats) => - val pid1 = transformSub(pid) - inContext(localCtx(tree)) { - cpy.PackageDef(tree)(pid1, transformStats(stats, ctx.owner)) - } + cpy.PackageDef(tree)(transformSub(pid), transformStats(stats, ctx.owner)) case Annotated(arg, annot) => cpy.Annotated(tree)(transform(arg), transform(annot)) case Thicket(trees) => diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index cf18f09b044c..3a88ba6af382 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -140,20 +140,21 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: case tree: CaseDef => instrumentCaseDef(tree) case tree: ValDef => - // only transform the rhs, in the local context - val rhs = transform(tree.rhs)(using localCtx(tree)) + // only transform the rhs + val rhs = transform(tree.rhs) cpy.ValDef(tree)(rhs=rhs) case tree: DefDef => // only transform the params (for the default values) and the rhs - val defCtx = localCtx(tree) - val paramss = transformParamss(tree.paramss)(using defCtx) - val rhs = transform(tree.rhs)(using defCtx) + // force instrumentation of literals and other small trees in the rhs, + // to ensure that the method call are recorded + val paramss = transformParamss(tree.paramss) + val rhs = transform(tree.rhs) cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs) case tree: PackageDef => // only transform the statements of the package - cpy.PackageDef(tree)(tree.pid, transform(tree.stats)(using localCtx(tree))) + cpy.PackageDef(tree)(tree.pid, transform(tree.stats)) case tree: Assign => // only transform the rhs cpy.Assign(tree)(tree.lhs, transform(tree.rhs)) From 3f71cd767be2797de40c60714f744c8c1b918df2 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Wed, 30 Mar 2022 14:09:02 +0200 Subject: [PATCH 062/121] Ensure that cases and DefDefs are always instrumented for coverage --- .../src/dotty/tools/dotc/ast/Desugar.scala | 3 +- .../dotty/tools/dotc/ast/MainProxies.scala | 6 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 2 +- .../dotty/tools/dotc/coverage/Coverage.scala | 9 +- .../tools/dotc/coverage/Serializer.scala | 3 +- .../dotc/transform/InstrumentCoverage.scala | 177 ++++++++---- docs/_docs/usage/coverage.md | 3 +- .../coverage/pos/Constructor.scoverage.check | 56 +++- .../pos/ContextFunctions.scoverage.check | 67 +++-- tests/coverage/pos/Enum.scoverage.check | 270 ++++++------------ tests/coverage/pos/Givens.scoverage.check | 84 +++++- tests/coverage/pos/Inlined.scoverage.check | 60 ++-- tests/coverage/pos/Literals.scala | 9 +- tests/coverage/pos/Literals.scoverage.check | 83 +++--- .../pos/MatchCaseClasses.scoverage.check | 129 ++++++++- .../coverage/pos/MatchNumbers.scoverage.check | 63 +++- tests/coverage/pos/Select.scoverage.check | 82 +++++- tests/coverage/pos/SimpleMethods.scala | 23 ++ .../pos/SimpleMethods.scoverage.check | 241 ++++++++++++++++ .../pos/StructuralTypes.scoverage.check | 73 ++--- .../coverage/pos/TypeLambdas.scoverage.check | 47 +-- .../run/currying/test.scoverage.check | 115 +++++++- .../run/inheritance/test.scoverage.check | 36 ++- .../run/interpolation/test.scoverage.check | 138 +++------ .../coverage/run/lifting/test.scoverage.check | 136 +++++---- tests/coverage/run/trait/test.scoverage.check | 48 +++- 26 files changed, 1295 insertions(+), 668 deletions(-) create mode 100644 tests/coverage/pos/SimpleMethods.scala create mode 100644 tests/coverage/pos/SimpleMethods.scoverage.check diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 0cb2ed4d7acc..117c5217f15e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1437,6 +1437,7 @@ object desugar { ValDef(param.name, param.tpt, selector(idx)) .withSpan(param.span) .withAttachment(UntupledParam, ()) + .withFlags(Synthetic) } Function(param :: Nil, Block(vdefs, body)) } @@ -1693,7 +1694,7 @@ object desugar { case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags) } RefinedTypeTree(polyFunctionTpt, List( - DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree) + DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree).withFlags(Synthetic) )) } else { diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index ea41984c5766..183854f3aede 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -105,12 +105,14 @@ object MainProxies { .filterNot(_.matches(defn.MainAnnot)) .map(annot => insertTypeSplices.transform(annot.tree)) val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) - .withFlags(JavaStatic) + .withFlags(JavaStatic | Synthetic) .withAnnotations(annots) val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) .withFlags(Final | Invisible) - if (!ctx.reporter.hasErrors) result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil + + if (!ctx.reporter.hasErrors) + result = mainCls.withSpan(mainAnnotSpan.toSynthetic) :: Nil } result } diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 5afbb7f416ed..c3e826578906 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -610,7 +610,7 @@ object Trees { override def toString = s"InlineMatch($selector, $cases)" } - /** case pat if guard => body; only appears as child of a Match */ + /** case pat if guard => body */ case class CaseDef[-T >: Untyped] private[ast] (pat: Tree[T], guard: Tree[T], body: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] = CaseDef[T] diff --git a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala index 6140c937171e..8ae249c1f5a3 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Coverage.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Coverage.scala @@ -23,12 +23,5 @@ case class Statement( symbolName: String, treeName: String, branch: Boolean, - var count: Int = 0, ignored: Boolean = false -): - /** Records that this statement has been invoked one more time. */ - def invoked(): Unit = - count += 1 - - def isInvoked: Boolean = - count > 0 +) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala index c4f633dcdfa2..4bd0d89a0b84 100644 --- a/compiler/src/dotty/tools/dotc/coverage/Serializer.scala +++ b/compiler/src/dotty/tools/dotc/coverage/Serializer.scala @@ -58,6 +58,7 @@ object Serializer: |""".stripMargin) def writeStatement(stmt: Statement, writer: Writer): Unit = + // Note: we write 0 for the count because we have not measured the actual coverage at this point writer.write(s"""${stmt.id} |${getRelativePath(stmt.location.sourcePath)} |${stmt.location.packageName} @@ -71,7 +72,7 @@ object Serializer: |${stmt.symbolName} |${stmt.treeName} |${stmt.branch} - |${stmt.count} + |0 |${stmt.ignored} |${stmt.desc} |\f diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 3a88ba6af382..442bf27d9f6b 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -60,27 +60,13 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */ private class CoverageTransformer extends Transformer: - private val IgnoreLiterals = new Property.Key[Boolean] - - private def ignoreLiteralsContext(using ctx: Context): Context = - ctx.fresh.setProperty(IgnoreLiterals, true) - override def transform(tree: Tree)(using ctx: Context): Tree = inContext(transformCtx(tree)) { // necessary to position inlined code properly tree match // simple cases - case tree: (Import | Export | This | Super | New) => tree + case tree: (Import | Export | Literal | This | Super | New) => tree case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ... - // Literals must be instrumented (at least) when returned by a def, - // otherwise `def d = "literal"` is not covered when called from a test. - // They can be left untouched when passed in a parameter of an Apply. - case tree: Literal => - if ctx.property(IgnoreLiterals).contains(true) then - tree - else - instrument(tree) - // branches case tree: If => cpy.If(tree)( @@ -92,32 +78,32 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: cpy.Try(tree)( expr = instrument(transform(tree.expr), branch = true), cases = instrumentCases(tree.cases), - finalizer = instrument(transform(tree.finalizer), true) + finalizer = instrument(transform(tree.finalizer), branch = true) ) // a.f(args) case tree @ Apply(fun: Select, args) => // don't transform the first Select, but do transform `a.b` in `a.b.f(args)` + val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) if canInstrumentApply(tree) then - val transformedFun = cpy.Select(fun)(transform(fun.qualifier), fun.name) if needsLift(tree) then val transformed = cpy.Apply(tree)(transformedFun, args) // args will be transformed in instrumentLifted - instrumentLifted(transformed)(using ignoreLiteralsContext) + instrumentLifted(transformed) else - val transformed = cpy.Apply(tree)(transformedFun, transform(args)(using ignoreLiteralsContext)) - instrument(transformed)(using ignoreLiteralsContext) + val transformed = transformApply(tree, transformedFun) + instrument(transformed) else - tree + transformApply(tree, transformedFun) // f(args) case tree: Apply => if canInstrumentApply(tree) then if needsLift(tree) then - instrumentLifted(tree)(using ignoreLiteralsContext) // see comment about Literals + instrumentLifted(tree) else - instrument(super.transform(tree)(using ignoreLiteralsContext)) + instrument(transformApply(tree)) else - tree + transformApply(tree) // (f(x))[args] case TypeApply(fun: Apply, args) => @@ -142,15 +128,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: case tree: ValDef => // only transform the rhs val rhs = transform(tree.rhs) - cpy.ValDef(tree)(rhs=rhs) + cpy.ValDef(tree)(rhs = rhs) case tree: DefDef => - // only transform the params (for the default values) and the rhs - // force instrumentation of literals and other small trees in the rhs, - // to ensure that the method call are recorded + // Only transform the params (for the default values) and the rhs. val paramss = transformParamss(tree.paramss) val rhs = transform(tree.rhs) - cpy.DefDef(tree)(tree.name, paramss, tree.tpt, rhs) + val finalRhs = + if canInstrumentDefDef(tree) then + // Ensure that the rhs is always instrumented, if possible + instrumentBody(tree, rhs) + else + rhs + cpy.DefDef(tree)(tree.name, paramss, tree.tpt, finalRhs) case tree: PackageDef => // only transform the statements of the package @@ -168,7 +158,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: /** Lifts and instruments an application. * Note that if only one arg needs to be lifted, we just lift everything. */ - def instrumentLifted(tree: Apply)(using Context) = + private def instrumentLifted(tree: Apply)(using Context) = // lifting val buffer = mutable.ListBuffer[Tree]() val liftedApply = LiftCoverage.liftForCoverage(buffer, tree) @@ -179,40 +169,93 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: Block( instrumentedArgs, instrumentedApply - ).withSpan(instrumentedApply.span) + ) - def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = - cases.map(instrumentCaseDef) + private inline def transformApply(tree: Apply)(using Context): Apply = + transformApply(tree, transform(tree.fun)) - def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = - cpy.CaseDef(tree)(tree.pat, transform(tree.guard), transform(tree.body)) + private inline def transformApply(tree: Apply, transformedFun: Tree)(using Context): Apply = + cpy.Apply(tree)(transformedFun, transform(tree.args)) - def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = + private inline def instrumentCases(cases: List[CaseDef])(using Context): List[CaseDef] = + cases.map(instrumentCaseDef) + + private def instrumentCaseDef(tree: CaseDef)(using Context): CaseDef = + val pat = tree.pat + val guard = tree.guard + val friendlyEnd = if guard.span.exists then guard.span.end else pat.span.end + val pos = tree.sourcePos.withSpan(tree.span.withEnd(friendlyEnd)) // user-friendly span + // ensure that the body is always instrumented by inserting a call to Invoker.invoked at its beginning + val instrumentedBody = instrument(transform(tree.body), pos, false) + cpy.CaseDef(tree)(tree.pat, transform(tree.guard), instrumentedBody) + + /** Records information about a new coverable statement. Generates a unique id for it. + * @return the statement's id + */ + private def recordStatement(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Int = + val id = statementId + statementId += 1 + val statement = new Statement( + source = ctx.source.file.name, + location = Location(tree), + id = id, + start = pos.start, + end = pos.end, + line = pos.line, + desc = tree.source.content.slice(pos.start, pos.end).mkString, + symbolName = tree.symbol.name.toSimpleName.toString, + treeName = tree.getClass.getSimpleName.nn, + branch + ) + coverage.addStatement(statement) + id + + private inline def syntheticSpan(pos: SourcePosition): Span = pos.span.toSynthetic + + /** Shortcut for instrument(tree, tree.sourcePos, branch) */ + private inline def instrument(tree: Tree, branch: Boolean = false)(using Context): Tree = instrument(tree, tree.sourcePos, branch) - def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using ctx: Context): Tree = + /** Instruments a statement, if it has a position. */ + private def instrument(tree: Tree, pos: SourcePosition, branch: Boolean)(using Context): Tree = if pos.exists && !pos.span.isZeroExtent then - statementId += 1 - val id = statementId - val statement = new Statement( - source = ctx.source.file.name, - location = Location(tree), - id = id, - start = pos.start, - end = pos.end, - line = pos.line, - desc = tree.source.content.slice(pos.start, pos.end).mkString, - symbolName = tree.symbol.name.toSimpleName.toString(), - treeName = tree.getClass.getSimpleName.nn, - branch - ) - coverage.addStatement(statement) - val span = Span(pos.start, pos.end) // synthetic span - Block(List(invokeCall(id, span)), tree).withSpan(span) + val statementId = recordStatement(tree, pos, branch) + insertInvokeCall(tree, pos, statementId) else tree - def invokeCall(id: Int, span: Span)(using Context): Tree = + /** Instruments the body of a DefDef. Handles corner cases. */ + private def instrumentBody(parent: DefDef, body: Tree)(using Context): Tree = + /* recurse on closures, so that we insert the call at the leaf: + + def g: (a: Ta) ?=> (b: Tb) = { + // nothing here <-- not here! + def $anonfun(using a: Ta) = + Invoked.invoked(id, DIR) <-- here + + closure($anonfun) + } + */ + body match + case b @ Block((meth: DefDef) :: Nil, closure: Closure) + if meth.symbol == closure.meth.symbol && defn.isContextFunctionType(body.tpe) => + val instr = cpy.DefDef(meth)(rhs = instrumentBody(parent, meth.rhs)) + cpy.Block(b)(instr :: Nil, closure) + case _ => + // compute user-friendly position to highlight more text in the coverage UI + val namePos = parent.namePos + val pos = namePos.withSpan(namePos.span.withStart(parent.span.start)) + // record info and insert call to Invoker.invoked + val statementId = recordStatement(parent, pos, false) + insertInvokeCall(body, pos, statementId) + + /** Returns the tree, prepended by a call to Invoker.invoker */ + private def insertInvokeCall(tree: Tree, pos: SourcePosition, statementId: Int)(using Context): Tree = + val callSpan = syntheticSpan(pos) + Block(invokeCall(statementId, callSpan) :: Nil, tree).withSpan(callSpan.union(tree.span)) + + /** Generates Invoked.invoked(id, DIR) */ + private def invokeCall(id: Int, span: Span)(using Context): Tree = val outputPath = ctx.settings.coverageOutputDir.value ref(defn.InvokedMethodRef).withSpan(span) .appliedToArgs( @@ -229,7 +272,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: * ``` * should not be changed to {val $x = f(); T($x)}(1) but to {val $x = f(); val $y = 1; T($x)($y)} */ - def needsLift(tree: Apply)(using Context): Boolean = + private def needsLift(tree: Apply)(using Context): Boolean = def isBooleanOperator(fun: Tree) = // We don't want to lift a || getB(), to avoid calling getB if a is true. // Same idea with a && getB(): if a is false, getB shouldn't be called. @@ -249,9 +292,17 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: nestedApplyNeedsLift || !isBooleanOperator(fun) && !tree.args.isEmpty && !tree.args.forall(LiftCoverage.noLift) - def canInstrumentApply(tree: Apply)(using Context): Boolean = - val tpe = tree.typeOpt - tpe match + /** Check if the body of a DefDef can be instrumented with instrumentBody. */ + private def canInstrumentDefDef(tree: DefDef)(using Context): Boolean = + // No need to force the instrumentation of synthetic definitions + // (it would work, but it looks better without). + !tree.symbol.isOneOf(Accessor | Synthetic | Artifact) && + !tree.rhs.isEmpty + + /** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */ + private def canInstrumentApply(tree: Apply)(using Context): Boolean = + !tree.symbol.isOneOf(Synthetic | Artifact) && // no need to instrument synthetic apply + (tree.typeOpt match case AppliedType(tycon: NamedType, _) => /* If the last expression in a block is a context function, we'll try to summon its arguments at the current point, even if the expected type @@ -267,11 +318,15 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: */ !tycon.name.isContextFunction case m: MethodType => - // def f(a: Ta)(b: Tb) - // f(a)(b) cannot be rewritten to {invoked();f(a)}(b) + /* def f(a: Ta)(b: Tb) + f(a)(b) + + Here, f(a)(b) cannot be rewritten to {invoked();f(a)}(b) + */ false case _ => true + ) object InstrumentCoverage: val name: String = "instrumentCoverage" diff --git a/docs/_docs/usage/coverage.md b/docs/_docs/usage/coverage.md index 838865fcd014..2e24b08ea29e 100644 --- a/docs/_docs/usage/coverage.md +++ b/docs/_docs/usage/coverage.md @@ -15,7 +15,8 @@ In Scala 2, all these steps were performed by external tools. In particular, ste In Scala 3, the compiler itself takes care of step 1. To use this feature, add the compile option `-coverage-out:DIR`, where `DIR` is the destination of the measurement files. -You can also set `-coverage-sourceroot:PATHS_ROOT` to customize how the path of your source files are resolved. +You can also set `-sourceroot:PATHS_ROOT` to customize how the path of your source files are resolved. +Note that `-sourceroot` also sets the root path of the SemanticDB files. ## How-to with sbt diff --git a/tests/coverage/pos/Constructor.scoverage.check b/tests/coverage/pos/Constructor.scoverage.check index 91a3331b002f..b6223734b1a8 100644 --- a/tests/coverage/pos/Constructor.scoverage.check +++ b/tests/coverage/pos/Constructor.scoverage.check @@ -18,6 +18,23 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ +0 +Constructor.scala +covtest +C +Class +covtest.C +f +28 +33 +3 +f +DefDef +false +0 +false +def f + 1 Constructor.scala covtest @@ -25,15 +42,15 @@ C Class covtest.C x -56 -57 +48 +53 4 - -Literal +x +DefDef false 0 false -1 +def x 2 Constructor.scala @@ -58,18 +75,35 @@ covtest O$ Object covtest.O$ +g +78 +83 +8 +g +DefDef +false +0 +false +def g + +4 +Constructor.scala +covtest +O$ +Object +covtest.O$ y -106 -107 +98 +103 9 - -Literal +y +DefDef false 0 false -1 +def y -4 +5 Constructor.scala covtest O$ diff --git a/tests/coverage/pos/ContextFunctions.scoverage.check b/tests/coverage/pos/ContextFunctions.scoverage.check index df4ae8c780f1..74a76df8bbf4 100644 --- a/tests/coverage/pos/ContextFunctions.scoverage.check +++ b/tests/coverage/pos/ContextFunctions.scoverage.check @@ -18,41 +18,41 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 ContextFunctions.scala covtest -Imperative +OnError Class -covtest.Imperative -$anonfun -178 -184 -8 - -Literal +covtest.OnError +onError +56 +67 +3 +onError +DefDef false 0 false -"name" +def onError -2 +1 ContextFunctions.scala covtest Imperative Class covtest.Imperative -readPerson -243 -247 -12 - -Literal +readName2 +121 +134 +7 +readName2 +DefDef false 0 false -null +def readName2 -3 +2 ContextFunctions.scala covtest Imperative @@ -69,7 +69,7 @@ false false readName2(using e)(using s) -4 +3 ContextFunctions.scala covtest Imperative @@ -86,7 +86,7 @@ false false OnError((e) => readName2(using e)(using s)) -5 +4 ContextFunctions.scala covtest Imperative @@ -103,7 +103,7 @@ false false readName2(using e)(using s) -6 +5 ContextFunctions.scala covtest Imperative @@ -120,7 +120,7 @@ false false readName2(using e)(using s) -7 +6 ContextFunctions.scala covtest Imperative @@ -137,7 +137,7 @@ false false OnError((e) => readName2(using e)(using s)) -8 +7 ContextFunctions.scala covtest Imperative @@ -154,7 +154,7 @@ false false OnError((e) => readName2(using e)(using s)) -9 +8 ContextFunctions.scala covtest Imperative @@ -171,3 +171,20 @@ false false OnError((e) => readName2(using e)(using s)).onError(None) +9 +ContextFunctions.scala +covtest +Imperative +Class +covtest.Imperative +readPerson +192 +206 +11 +readPerson +DefDef +false +0 +false +def readPerson + diff --git a/tests/coverage/pos/Enum.scoverage.check b/tests/coverage/pos/Enum.scoverage.check index 1e41a1732a6b..d76c749275e7 100644 --- a/tests/coverage/pos/Enum.scoverage.check +++ b/tests/coverage/pos/Enum.scoverage.check @@ -18,24 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 -Enum.scala -covtest -Planet -Class -covtest.Planet - -322 -333 -13 - -Literal -false 0 -false -6.67300E-11 - -2 Enum.scala covtest Planet @@ -52,7 +35,7 @@ false false G * mass -3 +1 Enum.scala covtest Planet @@ -69,7 +52,7 @@ false false G * mass -4 +2 Enum.scala covtest Planet @@ -86,7 +69,7 @@ false false G * mass -5 +3 Enum.scala covtest Planet @@ -103,7 +86,7 @@ false false radius * radius -6 +4 Enum.scala covtest Planet @@ -120,7 +103,24 @@ false false G * mass / (radius * radius -7 +5 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceGravity +338 +356 +14 +surfaceGravity +DefDef +false +0 +false +def surfaceGravity + +6 Enum.scala covtest Planet @@ -137,6 +137,23 @@ false false otherMass * surfaceGravity +7 +Enum.scala +covtest +Planet +Class +covtest.Planet +surfaceWeight +392 +409 +15 +surfaceWeight +DefDef +false +0 +false +def surfaceWeight + 8 Enum.scala covtest @@ -280,57 +297,6 @@ EnumTypes$ Object covtest.EnumTypes$ test -1004 -1036 -29 -apply -Apply -false -0 -false -ListEnum.Cons(3, ListEnum.Empty) - -17 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test -987 -1037 -29 -apply -Apply -false -0 -false -ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty)) - -18 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test -970 -1038 -29 -apply -Apply -false -0 -false -ListEnum.Cons(1, ListEnum.Cons(2, ListEnum.Cons(3, ListEnum.Empty))) - -19 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test 1051 1076 30 @@ -341,7 +307,7 @@ false false "Example 1: \n"+emptyList -20 +17 Enum.scala covtest EnumTypes$ @@ -358,58 +324,7 @@ false false println("Example 1: \n"+emptyList) -21 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test -1092 -1101 -31 -apply -Apply -false -0 -false -${list}\n - -22 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test -1092 -1101 -31 -invoked -Apply -false -0 -false -${list}\n - -23 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -test -1092 -1101 -31 -apply -Apply -false -0 -false -${list}\n - -24 +18 Enum.scala covtest EnumTypes$ @@ -426,7 +341,7 @@ false false s"${list}\n" -25 +19 Enum.scala covtest EnumTypes$ @@ -443,7 +358,7 @@ false false println(s"${list}\n") -26 +20 Enum.scala covtest EnumTypes$ @@ -460,7 +375,7 @@ false false earthWeight/Planet.Earth.surfaceGravity -27 +21 Enum.scala covtest EnumTypes$ @@ -477,58 +392,7 @@ false false Planet.values -28 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -$anonfun -1273 -1318 -36 -apply -Apply -false -0 -false -Your weight on $p is ${p.surfaceWeight(mass)} - -29 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -$anonfun -1273 -1318 -36 -invoked -Apply -false -0 -false -Your weight on $p is ${p.surfaceWeight(mass)} - -30 -Enum.scala -covtest -EnumTypes$ -Object -covtest.EnumTypes$ -$anonfun -1273 -1318 -36 -apply -Apply -false -0 -false -Your weight on $p is ${p.surfaceWeight(mass)} - -31 +22 Enum.scala covtest EnumTypes$ @@ -545,7 +409,7 @@ false false p.surfaceWeight(mass) -32 +23 Enum.scala covtest EnumTypes$ @@ -562,7 +426,7 @@ false false s"Your weight on $p is ${p.surfaceWeight(mass)}" -33 +24 Enum.scala covtest EnumTypes$ @@ -579,7 +443,7 @@ false false println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -34 +25 Enum.scala covtest EnumTypes$ @@ -597,7 +461,24 @@ false for p <- Planet.values do println(s"Your weight on $p is ${p.surfaceWeight(mass)}") -35 +26 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +calculateEarthWeightOnPlanets +1109 +1142 +33 +calculateEarthWeightOnPlanets +DefDef +false +0 +false +def calculateEarthWeightOnPlanets + +27 Enum.scala covtest EnumTypes$ @@ -614,7 +495,7 @@ false false println("Example 2:") -36 +28 Enum.scala covtest EnumTypes$ @@ -631,3 +512,20 @@ false false calculateEarthWeightOnPlanets(80) +29 +Enum.scala +covtest +EnumTypes$ +Object +covtest.EnumTypes$ +test +901 +909 +27 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/Givens.scoverage.check b/tests/coverage/pos/Givens.scoverage.check index 9cae23846460..389713521448 100644 --- a/tests/coverage/pos/Givens.scoverage.check +++ b/tests/coverage/pos/Givens.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 Givens.scala covtest Givens @@ -35,7 +35,7 @@ false false 3 == "3" -2 +1 Givens.scala covtest Givens @@ -52,7 +52,7 @@ false false println(3 == "3") -3 +2 Givens.scala covtest Givens @@ -69,7 +69,7 @@ false false 3 == 5.1 -4 +3 Givens.scala covtest Givens @@ -86,6 +86,23 @@ false false println(3 == 5.1) +4 +Givens.scala +covtest +Givens +Class +covtest.Givens +test +100 +108 +8 +test +DefDef +false +0 +false +def test + 5 Givens.scala covtest @@ -126,6 +143,23 @@ covtest Givens Class covtest.Givens +printContext +217 +233 +13 +printContext +DefDef +false +0 +false +def printContext + +8 +Givens.scala +covtest +Givens +Class +covtest.Givens getMessage 348 358 @@ -137,7 +171,24 @@ false false i.toString -8 +9 +Givens.scala +covtest +Givens +Class +covtest.Givens +getMessage +315 +329 +17 +getMessage +DefDef +false +0 +false +def getMessage + +10 Givens.scala covtest Givens @@ -154,7 +205,7 @@ false false Context(0) -9 +11 Givens.scala covtest Givens @@ -171,7 +222,7 @@ false false printContext("test")(using c) -10 +12 Givens.scala covtest Givens @@ -188,7 +239,7 @@ false false getMessage(123) -11 +13 Givens.scala covtest Givens @@ -205,3 +256,20 @@ false false printContext(getMessage(123)) +14 +Givens.scala +covtest +Givens +Class +covtest.Givens +test2 +362 +371 +19 +test2 +DefDef +false +0 +false +def test2 + diff --git a/tests/coverage/pos/Inlined.scoverage.check b/tests/coverage/pos/Inlined.scoverage.check index 016e3ef56ad5..f3cb3b5d9026 100644 --- a/tests/coverage/pos/Inlined.scoverage.check +++ b/tests/coverage/pos/Inlined.scoverage.check @@ -18,24 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 -Inlined.scala -covtest -Inlined$package$ -Object -covtest.Inlined$package$ -testInlined -122 -123 -4 - -Literal -false 0 -false -1 - -2 Inlined.scala covtest Inlined$package$ @@ -52,7 +35,7 @@ false false l == 1 -3 +1 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -69,7 +52,7 @@ false false scala.runtime.Scala3RunTime -4 +2 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -86,7 +69,7 @@ false false scala.runtime.Scala3RunTime.assertFailed() -5 +3 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -103,7 +86,7 @@ true false scala.runtime.Scala3RunTime.assertFailed() -6 +4 Inlined.scala covtest Inlined$package$ @@ -120,7 +103,7 @@ false false List(l) -7 +5 Inlined.scala covtest Inlined$package$ @@ -137,7 +120,7 @@ false false l == List(l).length -8 +6 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -154,7 +137,7 @@ false false scala.runtime.Scala3RunTime -9 +7 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -171,7 +154,7 @@ false false scala.runtime.Scala3RunTime.assertFailed() -10 +8 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -188,7 +171,7 @@ true false scala.runtime.Scala3RunTime.assertFailed() -11 +9 Inlined.scala covtest Inlined$package$ @@ -205,7 +188,7 @@ false false List(l) -12 +10 Inlined.scala covtest Inlined$package$ @@ -222,7 +205,7 @@ false false List(l).length == 1 -13 +11 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -239,7 +222,7 @@ false false scala.runtime.Scala3RunTime -14 +12 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -256,7 +239,7 @@ false false scala.runtime.Scala3RunTime.assertFailed() -15 +13 ../../../library/src/scala/runtime/stdLibPatches/Predef.scala covtest Inlined$package$ @@ -273,3 +256,20 @@ true false scala.runtime.Scala3RunTime.assertFailed() +14 +Inlined.scala +covtest +Inlined$package$ +Object +covtest.Inlined$package$ +testInlined +86 +101 +3 +testInlined +DefDef +false +0 +false +def testInlined + diff --git a/tests/coverage/pos/Literals.scala b/tests/coverage/pos/Literals.scala index 62297793bb70..4b61238b2015 100644 --- a/tests/coverage/pos/Literals.scala +++ b/tests/coverage/pos/Literals.scala @@ -1,9 +1,10 @@ +package covtest -def mustBeInstrumented = "literal" - -def thisOneToo = +def block = println("not this") // this literal should not be instrumented, only the println call - 12 // this literal must be instrumented + 12 + true + null def f(x: Int, y: Int, z: Int)(t: Int) = ??? diff --git a/tests/coverage/pos/Literals.scoverage.check b/tests/coverage/pos/Literals.scoverage.check index 25bd06e3e4ac..bda71a49f3b1 100644 --- a/tests/coverage/pos/Literals.scoverage.check +++ b/tests/coverage/pos/Literals.scoverage.check @@ -18,71 +18,88 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ +0 +Literals.scala +covtest +Literals$package$ +Object +covtest.Literals$package$ +block +31 +50 +3 +println +Apply +false +0 +false +println("not this") + 1 Literals.scala - +covtest Literals$package$ Object -.Literals$package$ -mustBeInstrumented +covtest.Literals$package$ +block +17 26 -35 -1 - -Literal +2 +block +DefDef false 0 false -"literal" +def block 2 Literals.scala - +covtest Literals$package$ Object -.Literals$package$ -thisOneToo -56 -75 -4 -println -Apply +covtest.Literals$package$ +f +137 +142 +8 +f +DefDef false 0 false -println("not this") +def f 3 Literals.scala - +covtest Literals$package$ Object -.Literals$package$ -thisOneToo -144 -146 -5 - -Literal +covtest.Literals$package$ +main +201 +212 +11 +f +Apply false 0 false -12 +f(0,1,2)(3) 4 Literals.scala - +covtest Literals$package$ Object -.Literals$package$ +covtest.Literals$package$ main -249 -260 +182 +190 10 -f -Apply +main +DefDef false 0 false -f(0,1,2)(3) +def main diff --git a/tests/coverage/pos/MatchCaseClasses.scoverage.check b/tests/coverage/pos/MatchCaseClasses.scoverage.check index 51a7e2f6ec42..0e469fc86b7c 100644 --- a/tests/coverage/pos/MatchCaseClasses.scoverage.check +++ b/tests/coverage/pos/MatchCaseClasses.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 MatchCaseClasses.scala covtest MatchCaseClasses$ @@ -35,6 +35,23 @@ false false println("a") +1 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +135 +147 +7 + +Block +false +0 +false +case Pat1(0) + 2 MatchCaseClasses.scala covtest @@ -59,6 +76,23 @@ MatchCaseClasses$ Object covtest.MatchCaseClasses$ f +168 +180 +8 + +Block +false +0 +false +case Pat1(_) + +4 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f 225 237 9 @@ -69,7 +103,24 @@ false false println("c") -4 +5 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +201 +221 +9 + +Block +false +0 +false +case p @ Pat2(1, -1) + +6 MatchCaseClasses.scala covtest MatchCaseClasses$ @@ -86,7 +137,7 @@ false false println(y) -5 +7 MatchCaseClasses.scala covtest MatchCaseClasses$ @@ -103,7 +154,24 @@ false false println("d") -6 +8 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +242 +265 +10 + +Block +false +0 +false +case Pat2(_, y: String) + +9 MatchCaseClasses.scala covtest MatchCaseClasses$ @@ -120,7 +188,24 @@ false false println("e") -7 +10 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +309 +321 +13 + +Block +false +0 +false +case p: Pat2 + +11 MatchCaseClasses.scala covtest MatchCaseClasses$ @@ -137,3 +222,37 @@ false false println("other") +12 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +342 +348 +14 + +Block +false +0 +false +case _ + +13 +MatchCaseClasses.scala +covtest +MatchCaseClasses$ +Object +covtest.MatchCaseClasses$ +f +101 +106 +6 +f +DefDef +false +0 +false +def f + diff --git a/tests/coverage/pos/MatchNumbers.scoverage.check b/tests/coverage/pos/MatchNumbers.scoverage.check index c7fa7c83f015..f16c11031784 100644 --- a/tests/coverage/pos/MatchNumbers.scoverage.check +++ b/tests/coverage/pos/MatchNumbers.scoverage.check @@ -18,6 +18,23 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ +0 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +106 +126 +6 + +Block +false +0 +false +case x: Int if x < 0 + 1 MatchNumbers.scala covtest @@ -42,15 +59,15 @@ MatchNumbers$ Object covtest.MatchNumbers$ f -130 -132 -6 +137 +148 +7 -Literal +Block false 0 false --1 +case x: Int 3 MatchNumbers.scala @@ -58,6 +75,40 @@ covtest MatchNumbers$ Object covtest.MatchNumbers$ +f +158 +170 +8 + +Block +false +0 +false +case y: Long + +4 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ +f +69 +74 +5 +f +DefDef +false +0 +false +def f + +5 +MatchNumbers.scala +covtest +MatchNumbers$ +Object +covtest.MatchNumbers$ 185 189 @@ -69,7 +120,7 @@ false false f(0) -4 +6 MatchNumbers.scala covtest MatchNumbers$ diff --git a/tests/coverage/pos/Select.scoverage.check b/tests/coverage/pos/Select.scoverage.check index 5419667a50ad..ef2831ee1013 100644 --- a/tests/coverage/pos/Select.scoverage.check +++ b/tests/coverage/pos/Select.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 Select.scala covtest A @@ -35,9 +35,43 @@ false false println("A") +1 +Select.scala +covtest +A +Class +covtest.A +print +68 +77 +6 +print +DefDef +false +0 +false +def print + 2 Select.scala covtest +A +Class +covtest.A +instance +97 +109 +7 +instance +DefDef +false +0 +false +def instance + +3 +Select.scala +covtest B Class covtest.B @@ -52,7 +86,7 @@ false false A -3 +4 Select.scala covtest B @@ -69,7 +103,7 @@ false false super.print() -4 +5 Select.scala covtest B @@ -86,7 +120,24 @@ false false println(this.instance) -5 +6 +Select.scala +covtest +B +Class +covtest.B +print +139 +157 +10 +print +DefDef +false +0 +false +override def print + +7 Select.scala covtest Select$package$ @@ -103,7 +154,7 @@ false false A() -6 +8 Select.scala covtest Select$package$ @@ -120,7 +171,7 @@ false false new A -7 +9 Select.scala covtest Select$package$ @@ -137,7 +188,7 @@ false false a.instance.print() -8 +10 Select.scala covtest Select$package$ @@ -154,3 +205,20 @@ false false a.print() +11 +Select.scala +covtest +Select$package$ +Object +covtest.Select$package$ +test +208 +216 +14 +test +DefDef +false +0 +false +def test + diff --git a/tests/coverage/pos/SimpleMethods.scala b/tests/coverage/pos/SimpleMethods.scala new file mode 100644 index 000000000000..510a86799b32 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scala @@ -0,0 +1,23 @@ +package covtest + +class C: + def a: C = this + def b: Unit = return + def c: Unit = () + def d: Int = 12 + def e: Null = null + + def block: Int = + "literal" + 0 + + def cond: Boolean = + if false then true + else false + + def new1: C = new {} + + def tryCatch: Unit = + try () + catch + case e: Exception => 1 diff --git a/tests/coverage/pos/SimpleMethods.scoverage.check b/tests/coverage/pos/SimpleMethods.scoverage.check new file mode 100644 index 000000000000..f2d9d61a1cd4 --- /dev/null +++ b/tests/coverage/pos/SimpleMethods.scoverage.check @@ -0,0 +1,241 @@ +# Coverage data, format version: 3.0 +# Statement data: +# - id +# - source path +# - package name +# - class name +# - class type (Class, Object or Trait) +# - full class name +# - method name +# - start offset +# - end offset +# - line number +# - symbol name +# - tree name +# - is branch +# - invocations count +# - is ignored +# - description (can be multi-line) +# ' ' sign +# ------------------------------------------ +0 +SimpleMethods.scala +covtest +C +Class +covtest.C +a +28 +33 +3 +a +DefDef +false +0 +false +def a + +1 +SimpleMethods.scala +covtest +C +Class +covtest.C +b +46 +51 +4 +b +DefDef +false +0 +false +def b + +2 +SimpleMethods.scala +covtest +C +Class +covtest.C +c +69 +74 +5 +c +DefDef +false +0 +false +def c + +3 +SimpleMethods.scala +covtest +C +Class +covtest.C +d +88 +93 +6 +d +DefDef +false +0 +false +def d + +4 +SimpleMethods.scala +covtest +C +Class +covtest.C +e +106 +111 +7 +e +DefDef +false +0 +false +def e + +5 +SimpleMethods.scala +covtest +C +Class +covtest.C +block +128 +137 +9 +block +DefDef +false +0 +false +def block + +6 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +206 +210 +14 + +Literal +true +0 +false +true + +7 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +220 +225 +15 + +Literal +true +0 +false +false + +8 +SimpleMethods.scala +covtest +C +Class +covtest.C +cond +168 +176 +13 +cond +DefDef +false +0 +false +def cond + +9 +SimpleMethods.scala +covtest +C +Class +covtest.C +new1 +229 +237 +17 +new1 +DefDef +false +0 +false +def new1 + +10 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +282 +284 +20 + +Literal +true +0 +false +() + +11 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +301 +318 +22 + +Block +false +0 +false +case e: Exception + +12 +SimpleMethods.scala +covtest +C +Class +covtest.C +tryCatch +253 +265 +19 +tryCatch +DefDef +false +0 +false +def tryCatch + diff --git a/tests/coverage/pos/StructuralTypes.scoverage.check b/tests/coverage/pos/StructuralTypes.scoverage.check index 77616e0b1134..43ddfd7715b8 100644 --- a/tests/coverage/pos/StructuralTypes.scoverage.check +++ b/tests/coverage/pos/StructuralTypes.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 StructuralTypes.scala covtest Record @@ -35,7 +35,7 @@ false false _._1 == name -2 +1 StructuralTypes.scala covtest Record @@ -52,24 +52,24 @@ false false elems.find(_._1 == name) -3 +2 StructuralTypes.scala covtest -StructuralTypes$ -Object -covtest.StructuralTypes$ -test -277 -283 -12 -ArrowAssoc -Apply +Record +Class +covtest.Record +selectDynamic +109 +126 +5 +selectDynamic +DefDef false 0 false -"name" +def selectDynamic -4 +3 StructuralTypes.scala covtest StructuralTypes$ @@ -86,24 +86,7 @@ false false "name" -> "Emma" -5 -StructuralTypes.scala -covtest -StructuralTypes$ -Object -covtest.StructuralTypes$ -test -295 -300 -12 -ArrowAssoc -Apply -false -0 -false -"age" - -6 +4 StructuralTypes.scala covtest StructuralTypes$ @@ -120,37 +103,37 @@ false false "age" -> 42 -7 +5 StructuralTypes.scala covtest StructuralTypes$ Object covtest.StructuralTypes$ test -270 -307 -12 -apply +333 +344 +13 +selectDynamic Apply false 0 false -Record("name" -> "Emma", "age" -> 42) +person.name -8 +6 StructuralTypes.scala covtest StructuralTypes$ Object covtest.StructuralTypes$ test -333 -344 -13 -selectDynamic -Apply +234 +242 +11 +test +DefDef false 0 false -person.name +def test diff --git a/tests/coverage/pos/TypeLambdas.scoverage.check b/tests/coverage/pos/TypeLambdas.scoverage.check index 9485c5170872..f97d5285cf06 100644 --- a/tests/coverage/pos/TypeLambdas.scoverage.check +++ b/tests/coverage/pos/TypeLambdas.scoverage.check @@ -18,24 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 -TypeLambdas.scala -covtest -TypeLambdas$ -Object -covtest.TypeLambdas$ -test -310 -311 -13 -ArrowAssoc -Apply -false 0 -false -1 - -2 TypeLambdas.scala covtest TypeLambdas$ @@ -52,7 +35,7 @@ false false 1 -> "1" -3 +1 TypeLambdas.scala covtest TypeLambdas$ @@ -69,7 +52,7 @@ false false Map(1 -> "1") -4 +2 TypeLambdas.scala covtest TypeLambdas$ @@ -86,37 +69,37 @@ false false println(m) -5 +3 TypeLambdas.scala covtest TypeLambdas$ Object covtest.TypeLambdas$ test -367 -377 -16 -apply +382 +396 +17 +println Apply false 0 false -("a", "b") +println(tuple) -6 +4 TypeLambdas.scala covtest TypeLambdas$ Object covtest.TypeLambdas$ test -382 -396 -17 -println -Apply +259 +267 +12 +test +DefDef false 0 false -println(tuple) +def test diff --git a/tests/coverage/run/currying/test.scoverage.check b/tests/coverage/run/currying/test.scoverage.check index 89ea9c4008c1..688c75b42933 100644 --- a/tests/coverage/run/currying/test.scoverage.check +++ b/tests/coverage/run/currying/test.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 currying/test.scala Test$ @@ -35,7 +35,7 @@ false false a+b -2 +1 currying/test.scala Test$ @@ -52,6 +52,23 @@ false false a+b+c +2 +currying/test.scala + +Test$ +Object +.Test$ +f1 +15 +21 +1 +f1 +DefDef +false +0 +false +def f1 + 3 currying/test.scala @@ -92,6 +109,23 @@ currying/test.scala Test$ Object .Test$ +f2 +56 +62 +2 +f2 +DefDef +false +0 +false +def f2 + +6 +currying/test.scala + +Test$ +Object +.Test$ $anonfun 166 169 @@ -103,7 +137,7 @@ false false a+b -6 +7 currying/test.scala Test$ @@ -120,7 +154,24 @@ false false a+b+c -7 +8 +currying/test.scala + +Test$ +Object +.Test$ +g1 +114 +120 +5 +g1 +DefDef +false +0 +false +def g1 + +9 currying/test.scala Test$ @@ -137,7 +188,7 @@ false false a+b -8 +10 currying/test.scala Test$ @@ -154,7 +205,24 @@ false false a+b+c -9 +11 +currying/test.scala + +Test$ +Object +.Test$ +g2 +175 +181 +8 +g2 +DefDef +false +0 +false +def g2 + +12 currying/test.scala Test$ @@ -171,7 +239,7 @@ false false f1(0)(1)(2) -10 +13 currying/test.scala Test$ @@ -188,7 +256,7 @@ false false println(f1(0)(1)(2)) -11 +14 currying/test.scala Test$ @@ -205,7 +273,7 @@ false false f2(0) -12 +15 currying/test.scala Test$ @@ -222,7 +290,7 @@ false false f2(0)(1) -13 +16 currying/test.scala Test$ @@ -239,7 +307,7 @@ false false f2(0)(1)(2) -14 +17 currying/test.scala Test$ @@ -256,7 +324,7 @@ false false println(f2(0)(1)(2)) -15 +18 currying/test.scala Test$ @@ -273,7 +341,7 @@ false false g1(using 0)(using 1)(using 2) -16 +19 currying/test.scala Test$ @@ -290,7 +358,7 @@ false false println(g1(using 0)(using 1)(using 2)) -17 +20 currying/test.scala Test$ @@ -307,7 +375,7 @@ false false g2(using 0)(using 1)(using 2) -18 +21 currying/test.scala Test$ @@ -324,3 +392,20 @@ false false println(g2(using 0)(using 1)(using 2)) +22 +currying/test.scala + +Test$ +Object +.Test$ +main +235 +243 +10 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/inheritance/test.scoverage.check b/tests/coverage/run/inheritance/test.scoverage.check index a714673ea3b5..5744f4b5eb3b 100644 --- a/tests/coverage/run/inheritance/test.scoverage.check +++ b/tests/coverage/run/inheritance/test.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 inheritance/test.scala B @@ -35,7 +35,7 @@ false false A(x, 0) -2 +1 inheritance/test.scala C1 @@ -52,7 +52,7 @@ false false println("block") -3 +2 inheritance/test.scala C1 @@ -69,7 +69,7 @@ false false B({println("block"); 1}) -4 +3 inheritance/test.scala C2 @@ -86,7 +86,7 @@ false false A(2,2) -5 +4 inheritance/test.scala C2 @@ -103,7 +103,7 @@ false false B(A(2,2).x) -6 +5 inheritance/test.scala test$package$ @@ -120,7 +120,7 @@ false false C1() -7 +6 inheritance/test.scala test$package$ @@ -137,7 +137,7 @@ false false println(C1().x) -8 +7 inheritance/test.scala test$package$ @@ -154,7 +154,7 @@ false false C2() -9 +8 inheritance/test.scala test$package$ @@ -171,3 +171,21 @@ false false println(C2().x) +9 +inheritance/test.scala + +test$package$ +Object +.test$package$ +Test +136 +150 +6 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/interpolation/test.scoverage.check b/tests/coverage/run/interpolation/test.scoverage.check index de100e7c116e..9c93d58f182c 100644 --- a/tests/coverage/run/interpolation/test.scoverage.check +++ b/tests/coverage/run/interpolation/test.scoverage.check @@ -18,58 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 -interpolation/test.scala - -Test$ -Object -.Test$ -simple -62 -77 -3 -apply -Apply -false 0 -false -$a, ${b.length} - -2 -interpolation/test.scala - -Test$ -Object -.Test$ -simple -62 -77 -3 -invoked -Apply -false -0 -false -$a, ${b.length} - -3 -interpolation/test.scala - -Test$ -Object -.Test$ -simple -62 -77 -3 -apply -Apply -false -0 -false -$a, ${b.length} - -4 interpolation/test.scala Test$ @@ -86,7 +35,7 @@ false false b.length -5 +1 interpolation/test.scala Test$ @@ -103,75 +52,41 @@ false false s"$a, ${b.length}" -6 -interpolation/test.scala - -Test$ -Object -.Test$ -main -147 -176 -6 -apply -Apply -false -0 -false -List("d", "o", "t", "t", "y") - -7 -interpolation/test.scala - -Test$ -Object -.Test$ -$anonfun -222 -228 -8 -apply -Apply -false -0 -false -$i: $s - -8 +2 interpolation/test.scala Test$ Object .Test$ -$anonfun -222 -228 -8 -invoked -Apply +simple +16 +26 +2 +simple +DefDef false 0 false -$i: $s +def simple -9 +3 interpolation/test.scala Test$ Object .Test$ -$anonfun -222 -228 -8 +main +147 +176 +6 apply Apply false 0 false -$i: $s +List("d", "o", "t", "t", "y") -10 +4 interpolation/test.scala Test$ @@ -188,7 +103,7 @@ false false s"$i: $s" -11 +5 interpolation/test.scala Test$ @@ -205,7 +120,7 @@ false false println(s"$i: $s") -12 +6 interpolation/test.scala Test$ @@ -222,3 +137,20 @@ false false xs.zipWithIndex.map((s, i) => println(s"$i: $s")) +7 +interpolation/test.scala + +Test$ +Object +.Test$ +main +82 +90 +5 +main +DefDef +false +0 +false +def main + diff --git a/tests/coverage/run/lifting/test.scoverage.check b/tests/coverage/run/lifting/test.scoverage.check index 701110a58615..7729e7515734 100644 --- a/tests/coverage/run/lifting/test.scoverage.check +++ b/tests/coverage/run/lifting/test.scoverage.check @@ -18,7 +18,7 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 lifting/test.scala Vals @@ -35,7 +35,7 @@ false false List(1) -2 +1 lifting/test.scala Vals @@ -52,7 +52,7 @@ false false List(1,2,3) -3 +2 lifting/test.scala Vals @@ -69,24 +69,7 @@ false false l :: List(1,2,3) -4 -lifting/test.scala - -A -Class -.A -msg -104 -112 -5 - -Literal -false -0 -false -"string" - -5 +3 lifting/test.scala A @@ -103,7 +86,7 @@ false false "string" + a -6 +4 lifting/test.scala A @@ -120,7 +103,7 @@ false false "string" + a + "." -7 +5 lifting/test.scala A @@ -137,7 +120,7 @@ false false "string" + a + "." + b -8 +6 lifting/test.scala A @@ -154,7 +137,7 @@ false false "string" + a + "." + b + "." -9 +7 lifting/test.scala A @@ -171,22 +154,56 @@ false false "string" + a + "." + b + "." + c -10 +8 +lifting/test.scala + +A +Class +.A +msg +70 +77 +5 +msg +DefDef +false +0 +false +def msg + +9 lifting/test.scala A Class .A integer -158 -159 +139 +150 6 - -Literal +integer +DefDef false 0 false +def integer + +10 +lifting/test.scala + +A +Class +.A +ex +162 +168 +7 +ex +DefDef +false 0 +false +def ex 11 lifting/test.scala @@ -211,35 +228,18 @@ lifting/test.scala test$package$ Object .test$package$ -Test -235 -238 -12 - -Literal -false -0 -false -123 - -13 -lifting/test.scala - -test$package$ -Object -.test$package$ f -251 -253 +241 +246 13 - -Literal +f +DefDef false 0 false --1 +def f -14 +13 lifting/test.scala test$package$ @@ -256,7 +256,7 @@ false false a.msg(i, 0, a.integer) -15 +14 lifting/test.scala test$package$ @@ -273,7 +273,7 @@ false false println(x) -16 +15 lifting/test.scala test$package$ @@ -290,7 +290,7 @@ false false a.ex.msg(i, 0, a.ex.integer) -17 +16 lifting/test.scala test$package$ @@ -307,7 +307,7 @@ false false println(x) -18 +17 lifting/test.scala test$package$ @@ -324,7 +324,7 @@ false false f() -19 +18 lifting/test.scala test$package$ @@ -341,7 +341,7 @@ false false a.msg(f(), 0, i) -20 +19 lifting/test.scala test$package$ @@ -358,3 +358,21 @@ false false println(x) +20 +lifting/test.scala + +test$package$ +Object +.test$package$ +Test +188 +202 +10 +Test +DefDef +false +0 +false +@main +def Test + diff --git a/tests/coverage/run/trait/test.scoverage.check b/tests/coverage/run/trait/test.scoverage.check index ac6000132788..dcc93e2a7809 100644 --- a/tests/coverage/run/trait/test.scoverage.check +++ b/tests/coverage/run/trait/test.scoverage.check @@ -18,24 +18,24 @@ # - description (can be multi-line) # ' ' sign # ------------------------------------------ -1 +0 trait/test.scala T1 Trait .T1 x -20 -21 +12 +17 1 - -Literal +x +DefDef false 0 false -0 +def x -2 +1 trait/test.scala Impl2 @@ -52,7 +52,7 @@ false false T2("test") -3 +2 trait/test.scala Impl3 @@ -69,7 +69,7 @@ false false Impl2() -4 +3 trait/test.scala Impl3 @@ -86,7 +86,7 @@ false false T2(Impl2().p) -5 +4 trait/test.scala test$package$ @@ -103,7 +103,7 @@ false false Impl1() -6 +5 trait/test.scala test$package$ @@ -120,7 +120,7 @@ false false println(Impl1().x) -7 +6 trait/test.scala test$package$ @@ -137,7 +137,7 @@ false false Impl2() -8 +7 trait/test.scala test$package$ @@ -154,7 +154,7 @@ false false println(Impl2().p) -9 +8 trait/test.scala test$package$ @@ -171,7 +171,7 @@ false false Impl3() -10 +9 trait/test.scala test$package$ @@ -188,3 +188,21 @@ false false println(Impl3().p) +10 +trait/test.scala + +test$package$ +Object +.test$package$ +Test +145 +159 +10 +Test +DefDef +false +0 +false +@main +def Test + From 2fc33a36ea19752920d17596b5a6194c815a1873 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Tue, 5 Apr 2022 18:03:30 +0200 Subject: [PATCH 063/121] Make testCompilation run coverage tests --- project/Build.scala | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index abe42e6a640f..f3e8957fb258 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -138,9 +138,6 @@ object Build { // Run tests with filter through vulpix test suite val testCompilation = inputKey[Unit]("runs integration test with the supplied filter") - // Run code coverage instrumentation tests - val testCoverage = inputKey[Unit]("runs code coverage instrumentation test") - // Used to compile files similar to ./bin/scalac script val scalac = inputKey[Unit]("run the compiler using the correct classpath, or the user supplied classpath") @@ -586,7 +583,8 @@ object Build { s""" |usage: testCompilation [--help] [--from-tasty] [--update-checkfiles] [] | - |By default runs tests in dotty.tools.dotc.*CompilationTests excluding tests tagged with dotty.SlowTests. + |By default runs tests in dotty.tools.dotc.*CompilationTests and dotty.tools.dotc.coverage.*, + |excluding tests tagged with dotty.SlowTests. | | --help show this message | --from-tasty runs tests in dotty.tools.dotc.FromTastyTests @@ -601,7 +599,7 @@ object Build { val updateCheckfile = args.contains("--update-checkfiles") val fromTasty = args.contains("--from-tasty") val args1 = if (updateCheckfile | fromTasty) args.filter(x => x != "--update-checkfiles" && x != "--from-tasty") else args - val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests" + val test = if (fromTasty) "dotty.tools.dotc.FromTastyTests" else "dotty.tools.dotc.*CompilationTests dotty.tools.dotc.coverage.*" val cmd = s" $test -- --exclude-categories=dotty.SlowTests" + (if (updateCheckfile) " -Ddotty.tests.updateCheckfiles=TRUE" else "") + (if (args1.nonEmpty) " -Ddotty.tests.filter=" + args1.mkString(" ") else "") @@ -609,22 +607,6 @@ object Build { } }.evaluated, - testCoverage := Def.inputTaskDyn { - val args = spaceDelimited("").parsed - if (args.contains("--help")) { - println("usage: testCoverage [--update-checkfiles] []") - (Test / testOnly).toTask(" not.a.test") - } else { - val updateCheckfile = args.contains("--update-checkfiles") - val otherArgs = args.filter(_ != "--update-checkfiles") - val test = "dotty.tools.dotc.coverage.CoverageTests" - val argUpdateCheckfile = if (updateCheckfile) "-Ddotty.tests.updateCheckfiles=TRUE" else "" - val argCustom = if (otherArgs.nonEmpty) otherArgs.mkString(" ") else "" - val cmd = s" $test -- $argUpdateCheckfile $argCustom" - (Test/testOnly).toTask(cmd) - } - }.evaluated, - Compile / mainClass := Some("dotty.tools.dotc.Main"), scala := { From 7072aa92c29bc3f1a0dd45970ca7f597216765fb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Apr 2022 13:21:42 +0200 Subject: [PATCH 064/121] Provide `Show[Throwable]` Fixes #14938 --- compiler/src/dotty/tools/dotc/printing/Formatting.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index 0cef7fd3e833..e3f81adeee8a 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -66,7 +66,7 @@ object Formatting { given Show[Boolean] = ShowAny given Show[String] = ShowAny given Show[Class[?]] = ShowAny - given Show[Exception] = ShowAny + given Show[Throwable] = ShowAny given Show[StringBuffer] = ShowAny given Show[CompilationUnit] = ShowAny given Show[Phases.Phase] = ShowAny From e852aa7882d93d9bafe3a6438506feb5a440bf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Loyck=20Andres?= Date: Mon, 4 Oct 2021 12:12:45 +0200 Subject: [PATCH 065/121] Add scala.annotation.MainAnnotation See `docs/_docs/reference/experimental/main-annotation.md` --- .../dotty/tools/dotc/ast/MainProxies.scala | 350 ++++++++++++++++-- .../dotty/tools/dotc/core/Definitions.scala | 8 + .../src/dotty/tools/dotc/core/StdNames.scala | 4 + .../src/dotty/tools/dotc/typer/Checking.scala | 7 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../reference/experimental/main-annotation.md | 91 +++++ docs/sidebar.yml | 1 + .../src/scala/annotation/MainAnnotation.scala | 119 ++++++ project/MiMaFilters.scala | 12 +- .../referenceReplacements/sidebar.yml | 1 + .../neg/main-annotation-mainannotation.scala | 3 + tests/run/main-annotation-example.check | 3 + tests/run/main-annotation-example.scala | 59 +++ .../main-annotation-homemade-annot-1.check | 4 + .../main-annotation-homemade-annot-1.scala | 45 +++ .../main-annotation-homemade-annot-2.check | 11 + .../main-annotation-homemade-annot-2.scala | 50 +++ .../main-annotation-homemade-annot-3.check | 1 + .../main-annotation-homemade-annot-3.scala | 24 ++ .../main-annotation-homemade-annot-4.check | 1 + .../main-annotation-homemade-annot-4.scala | 24 ++ .../main-annotation-homemade-annot-5.check | 2 + .../main-annotation-homemade-annot-5.scala | 26 ++ .../main-annotation-homemade-annot-6.check | 28 ++ .../main-annotation-homemade-annot-6.scala | 63 ++++ tests/run/main-annotation-newMain.scala | 307 +++++++++++++++ 26 files changed, 1217 insertions(+), 29 deletions(-) create mode 100644 docs/_docs/reference/experimental/main-annotation.md create mode 100644 library/src/scala/annotation/MainAnnotation.scala create mode 100644 tests/neg/main-annotation-mainannotation.scala create mode 100644 tests/run/main-annotation-example.check create mode 100644 tests/run/main-annotation-example.scala create mode 100644 tests/run/main-annotation-homemade-annot-1.check create mode 100644 tests/run/main-annotation-homemade-annot-1.scala create mode 100644 tests/run/main-annotation-homemade-annot-2.check create mode 100644 tests/run/main-annotation-homemade-annot-2.scala create mode 100644 tests/run/main-annotation-homemade-annot-3.check create mode 100644 tests/run/main-annotation-homemade-annot-3.scala create mode 100644 tests/run/main-annotation-homemade-annot-4.check create mode 100644 tests/run/main-annotation-homemade-annot-4.scala create mode 100644 tests/run/main-annotation-homemade-annot-5.check create mode 100644 tests/run/main-annotation-homemade-annot-5.scala create mode 100644 tests/run/main-annotation-homemade-annot-6.check create mode 100644 tests/run/main-annotation-homemade-annot-6.scala create mode 100644 tests/run/main-annotation-newMain.scala diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 183854f3aede..01ae50850b57 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -2,30 +2,40 @@ package dotty.tools.dotc package ast import core._ -import Symbols._, Types._, Contexts._, Flags._, Constants._ -import StdNames.nme - -/** Generate proxy classes for @main functions. - * A function like - * - * @main def f(x: S, ys: T*) = ... - * - * would be translated to something like - * - * import CommandLineParser._ - * class f { - * @static def main(args: Array[String]): Unit = - * try - * f( - * parseArgument[S](args, 0), - * parseRemainingArguments[T](args, 1): _* - * ) - * catch case err: ParseError => showError(err) - * } - */ +import Symbols._, Types._, Contexts._, Decorators._, util.Spans._, Flags._, Constants._ +import StdNames.{nme, tpnme} +import ast.Trees._ +import Names.Name +import Comments.Comment +import NameKinds.DefaultGetterName +import Annotations.Annotation + object MainProxies { - def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + /** Generate proxy classes for @main functions and @myMain functions where myMain <:< MainAnnotation */ + def proxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + mainAnnotationProxies(stats) ++ mainProxies(stats) + } + + /** Generate proxy classes for @main functions. + * A function like + * + * @main def f(x: S, ys: T*) = ... + * + * would be translated to something like + * + * import CommandLineParser._ + * class f { + * @static def main(args: Array[String]): Unit = + * try + * f( + * parseArgument[S](args, 0), + * parseRemainingArguments[T](args, 1): _* + * ) + * catch case err: ParseError => showError(err) + * } + */ + private def mainProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { import tpd._ def mainMethods(stats: List[Tree]): List[Symbol] = stats.flatMap { case stat: DefDef if stat.symbol.hasAnnotation(defn.MainAnnot) => @@ -39,7 +49,7 @@ object MainProxies { } import untpd._ - def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { + private def mainProxy(mainFun: Symbol)(using Context): List[TypeDef] = { val mainAnnotSpan = mainFun.getAnnotation(defn.MainAnnot).get.tree.span def pos = mainFun.sourcePos val argsRef = Ident(nme.args) @@ -116,4 +126,298 @@ object MainProxies { } result } + + private type DefaultValueSymbols = Map[Int, Symbol] + private type ParameterAnnotationss = Seq[Seq[Annotation]] + + /** + * Generate proxy classes for main functions. + * A function like + * + * /** + * * Lorem ipsum dolor sit amet + * * consectetur adipiscing elit. + * * + * * @param x my param x + * * @param ys all my params y + * */ + * @myMain(80) def f( + * @myMain.Alias("myX") x: S, + * ys: T* + * ) = ... + * + * would be translated to something like + * + * final class f { + * static def main(args: Array[String]): Unit = { + * val cmd = new myMain(80).command( + * info = new CommandInfo( + * name = "f", + * documentation = "Lorem ipsum dolor sit amet consectetur adipiscing elit.", + * parameters = Seq( + * new scala.annotation.MainAnnotation.ParameterInfo("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) + * new scala.annotation.MainAnnotation.ParameterInfo("ys", "T", false, false, "all my params y", Seq()) + * ) + * ) + * args = args + * ) + * + * val args0: () => S = cmd.argGetter[S](0, None) + * val args1: () => Seq[T] = cmd.varargGetter[T] + * + * cmd.run(() => f(args0(), args1()*)) + * } + * } + */ + private def mainAnnotationProxies(stats: List[tpd.Tree])(using Context): List[untpd.Tree] = { + import tpd._ + + /** + * Computes the symbols of the default values of the function. Since they cannot be inferred anymore at this + * point of the compilation, they must be explicitly passed by [[mainProxy]]. + */ + def defaultValueSymbols(scope: Tree, funSymbol: Symbol): DefaultValueSymbols = + scope match { + case TypeDef(_, template: Template) => + template.body.flatMap((_: Tree) match { + case dd: DefDef if dd.name.is(DefaultGetterName) && dd.name.firstPart == funSymbol.name => + val DefaultGetterName.NumberedInfo(index) = dd.name.info + List(index -> dd.symbol) + case _ => Nil + }).toMap + case _ => Map.empty + } + + /** Computes the list of main methods present in the code. */ + def mainMethods(scope: Tree, stats: List[Tree]): List[(Symbol, ParameterAnnotationss, DefaultValueSymbols, Option[Comment])] = stats.flatMap { + case stat: DefDef => + val sym = stat.symbol + sym.annotations.filter(_.matches(defn.MainAnnotationClass)) match { + case Nil => + Nil + case _ :: Nil => + val paramAnnotations = stat.paramss.flatMap(_.map( + valdef => valdef.symbol.annotations.filter(_.matches(defn.MainAnnotationParameterAnnotation)) + )) + (sym, paramAnnotations.toVector, defaultValueSymbols(scope, sym), stat.rawComment) :: Nil + case mainAnnot :: others => + report.error(s"method cannot have multiple main annotations", mainAnnot.tree) + Nil + } + case stat @ TypeDef(_, impl: Template) if stat.symbol.is(Module) => + mainMethods(stat, impl.body) + case _ => + Nil + } + + // Assuming that the top-level object was already generated, all main methods will have a scope + mainMethods(EmptyTree, stats).flatMap(mainAnnotationProxy) + } + + private def mainAnnotationProxy(mainFun: Symbol, paramAnnotations: ParameterAnnotationss, defaultValueSymbols: DefaultValueSymbols, docComment: Option[Comment])(using Context): Option[TypeDef] = { + val mainAnnot = mainFun.getAnnotation(defn.MainAnnotationClass).get + def pos = mainFun.sourcePos + + val documentation = new Documentation(docComment) + + /** () => value */ + def unitToValue(value: Tree): Tree = + val defDef = DefDef(nme.ANON_FUN, List(Nil), TypeTree(), value) + Block(defDef, Closure(Nil, Ident(nme.ANON_FUN), EmptyTree)) + + /** Generate a list of trees containing the ParamInfo instantiations. + * + * A ParamInfo has the following shape + * ``` + * new scala.annotation.MainAnnotation.ParameterInfo("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) + * ``` + */ + def parameterInfos(mt: MethodType): List[Tree] = + extension (tree: Tree) def withProperty(sym: Symbol, args: List[Tree]) = + Apply(Select(tree, sym.name), args) + + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val param = paramName.toString + val paramType0 = if formal.isRepeatedParam then formal.argTypes.head.dealias else formal.dealias + val paramType = paramType0.dealias + + val paramTypeStr = formal.dealias.typeSymbol.owner.showFullName + "." + paramType.show + val hasDefault = defaultValueSymbols.contains(idx) + val isRepeated = formal.isRepeatedParam + val paramDoc = documentation.argDocs.getOrElse(param, "") + val paramAnnots = + val annotationTrees = paramAnnotations(idx).map(instantiateAnnotation).toList + Apply(ref(defn.SeqModule.termRef), annotationTrees) + + val constructorArgs = List(param, paramTypeStr, hasDefault, isRepeated, paramDoc) + .map(value => Literal(Constant(value))) + + New(TypeTree(defn.MainAnnotationParameterInfo.typeRef), List(constructorArgs :+ paramAnnots)) + + end parameterInfos + + /** + * Creates a list of references and definitions of arguments. + * The goal is to create the + * `val args0: () => S = cmd.argGetter[S](0, None)` + * part of the code. + */ + def argValDefs(mt: MethodType): List[ValDef] = + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val argName = nme.args ++ idx.toString + val isRepeated = formal.isRepeatedParam + val formalType = if isRepeated then formal.argTypes.head else formal + val getterName = if isRepeated then nme.varargGetter else nme.argGetter + val defaultValueGetterOpt = defaultValueSymbols.get(idx) match + case None => ref(defn.NoneModule.termRef) + case Some(dvSym) => + val value = unitToValue(ref(dvSym.termRef)) + Apply(ref(defn.SomeClass.companionModule.termRef), value) + val argGetter0 = TypeApply(Select(Ident(nme.cmd), getterName), TypeTree(formalType) :: Nil) + val argGetter = + if isRepeated then argGetter0 + else Apply(argGetter0, List(Literal(Constant(idx)), defaultValueGetterOpt)) + + ValDef(argName, TypeTree(), argGetter) + end argValDefs + + + /** Create a list of argument references that will be passed as argument to the main method. + * `args0`, ...`argn*` + */ + def argRefs(mt: MethodType): List[Tree] = + for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield + val argRef = Apply(Ident(nme.args ++ idx.toString), Nil) + if formal.isRepeatedParam then repeated(argRef) else argRef + end argRefs + + + /** Turns an annotation (e.g. `@main(40)`) into an instance of the class (e.g. `new scala.main(40)`). */ + def instantiateAnnotation(annot: Annotation): Tree = + val argss = { + def recurse(t: tpd.Tree, acc: List[List[Tree]]): List[List[Tree]] = t match { + case Apply(t, args: List[tpd.Tree]) => recurse(t, extractArgs(args) :: acc) + case _ => acc + } + + def extractArgs(args: List[tpd.Tree]): List[Tree] = + args.flatMap { + case Typed(SeqLiteral(varargs, _), _) => varargs.map(arg => TypedSplice(arg)) + case arg: Select if arg.name.is(DefaultGetterName) => Nil // Ignore default values, they will be added later by the compiler + case arg => List(TypedSplice(arg)) + } + + recurse(annot.tree, Nil) + } + + New(TypeTree(annot.symbol.typeRef), argss) + end instantiateAnnotation + + def generateMainClass(mainCall: Tree, args: List[Tree], parameterInfos: List[Tree]): TypeDef = + val cmdInfo = + val nameTree = Literal(Constant(mainFun.showName)) + val docTree = Literal(Constant(documentation.mainDoc)) + val paramInfos = Apply(ref(defn.SeqModule.termRef), parameterInfos) + New(TypeTree(defn.MainAnnotationCommandInfo.typeRef), List(List(nameTree, docTree, paramInfos))) + + val cmd = ValDef( + nme.cmd, + TypeTree(), + Apply( + Select(instantiateAnnotation(mainAnnot), nme.command), + List(cmdInfo, Ident(nme.args)) + ) + ) + val run = Apply(Select(Ident(nme.cmd), nme.run), mainCall) + val body = Block(cmdInfo :: cmd :: args, run) + val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) + .withFlags(Param) + /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. + * The annotations will be retype-checked in another scope that may not have the same imports. + */ + def insertTypeSplices = new TreeMap { + override def transform(tree: Tree)(using Context): Tree = tree match + case tree: tpd.Ident @unchecked => TypedSplice(tree) + case tree => super.transform(tree) + } + val annots = mainFun.annotations + .filterNot(_.matches(defn.MainAnnotationClass)) + .map(annot => insertTypeSplices.transform(annot.tree)) + val mainMeth = DefDef(nme.main, (mainArg :: Nil) :: Nil, TypeTree(defn.UnitType), body) + .withFlags(JavaStatic) + .withAnnotations(annots) + val mainTempl = Template(emptyConstructor, Nil, Nil, EmptyValDef, mainMeth :: Nil) + val mainCls = TypeDef(mainFun.name.toTypeName, mainTempl) + .withFlags(Final | Invisible) + mainCls.withSpan(mainAnnot.tree.span.toSynthetic) + end generateMainClass + + if (!mainFun.owner.isStaticOwner) + report.error(s"main method is not statically accessible", pos) + None + else mainFun.info match { + case _: ExprType => + Some(generateMainClass(unitToValue(ref(mainFun.termRef)), Nil, Nil)) + case mt: MethodType => + if (mt.isImplicitMethod) + report.error(s"main method cannot have implicit parameters", pos) + None + else mt.resType match + case restpe: MethodType => + report.error(s"main method cannot be curried", pos) + None + case _ => + Some(generateMainClass(unitToValue(Apply(ref(mainFun.termRef), argRefs(mt))), argValDefs(mt), parameterInfos(mt))) + case _: PolyType => + report.error(s"main method cannot have type parameters", pos) + None + case _ => + report.error(s"main can only annotate a method", pos) + None + } + } + + /** A class responsible for extracting the docstrings of a method. */ + private class Documentation(docComment: Option[Comment]): + import util.CommentParsing._ + + /** The main part of the documentation. */ + lazy val mainDoc: String = _mainDoc + /** The parameters identified by @param. Maps from parameter name to its documentation. */ + lazy val argDocs: Map[String, String] = _argDocs + + private var _mainDoc: String = "" + private var _argDocs: Map[String, String] = Map() + + docComment match { + case Some(comment) => if comment.isDocComment then parseDocComment(comment.raw) else _mainDoc = comment.raw + case None => + } + + private def cleanComment(raw: String): String = + var lines: Seq[String] = raw.trim.nn.split('\n').nn.toSeq + lines = lines.map(l => l.substring(skipLineLead(l, -1), l.length).nn.trim.nn) + var s = lines.foldLeft("") { + case ("", s2) => s2 + case (s1, "") if s1.last == '\n' => s1 // Multiple newlines are kept as single newlines + case (s1, "") => s1 + '\n' + case (s1, s2) if s1.last == '\n' => s1 + s2 + case (s1, s2) => s1 + ' ' + s2 + } + s.replaceAll(raw"\[\[", "").nn.replaceAll(raw"\]\]", "").nn.trim.nn + + private def parseDocComment(raw: String): Unit = + // Positions of the sections (@) in the docstring + val tidx: List[(Int, Int)] = tagIndex(raw) + + // Parse main comment + var mainComment: String = raw.substring(skipLineLead(raw, 0), startTag(raw, tidx)).nn + _mainDoc = cleanComment(mainComment) + + // Parse arguments comments + val argsCommentsSpans: Map[String, (Int, Int)] = paramDocs(raw, "@param", tidx) + val argsCommentsTextSpans = argsCommentsSpans.view.mapValues(extractSectionText(raw, _)) + val argsCommentsTexts = argsCommentsTextSpans.mapValues({ case (beg, end) => raw.substring(beg, end).nn }) + _argDocs = argsCommentsTexts.mapValues(cleanComment(_)).toMap + end Documentation } diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5eb1ccb0f957..9e70102fa1dd 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -528,6 +528,8 @@ class Definitions { @tu lazy val Seq_lengthCompare: Symbol = SeqClass.requiredMethod(nme.lengthCompare, List(IntType)) @tu lazy val Seq_length : Symbol = SeqClass.requiredMethod(nme.length) @tu lazy val Seq_toSeq : Symbol = SeqClass.requiredMethod(nme.toSeq) + @tu lazy val SeqModule: Symbol = requiredModule("scala.collection.immutable.Seq") + @tu lazy val StringOps: Symbol = requiredClass("scala.collection.StringOps") @tu lazy val StringOps_format: Symbol = StringOps.requiredMethod(nme.format) @@ -853,6 +855,12 @@ class Definitions { @tu lazy val XMLTopScopeModule: Symbol = requiredModule("scala.xml.TopScope") + @tu lazy val MainAnnotationClass: ClassSymbol = requiredClass("scala.annotation.MainAnnotation") + @tu lazy val MainAnnotationCommandInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.CommandInfo") + @tu lazy val MainAnnotationParameterInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterInfo") + @tu lazy val MainAnnotationParameterAnnotation: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterAnnotation") + @tu lazy val MainAnnotationCommand: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Command") + @tu lazy val CommandLineParserModule: Symbol = requiredModule("scala.util.CommandLineParser") @tu lazy val CLP_ParseError: ClassSymbol = CommandLineParserModule.requiredClass("ParseError").typeRef.symbol.asClass @tu lazy val CLP_parseArgument: Symbol = CommandLineParserModule.requiredMethod("parseArgument") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 1bf91bf69abe..bb5efcc01a5e 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -397,6 +397,7 @@ object StdNames { val applyOrElse: N = "applyOrElse" val args : N = "args" val argv : N = "argv" + val argGetter : N = "argGetter" val arrayClass: N = "arrayClass" val arrayElementClass: N = "arrayElementClass" val arrayType: N = "arrayType" @@ -427,6 +428,8 @@ object StdNames { val classOf: N = "classOf" val classType: N = "classType" val clone_ : N = "clone" + val cmd: N = "cmd" + val command: N = "command" val common: N = "common" val compiletime : N = "compiletime" val conforms_ : N = "$conforms" @@ -613,6 +616,7 @@ object StdNames { val fromOrdinal: N = "fromOrdinal" val values: N = "values" val view_ : N = "view" + val varargGetter : N = "varargGetter" val wait_ : N = "wait" val wildcardType: N = "wildcardType" val withFilter: N = "withFilter" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index b7c65a30e7b4..1cce3fdea280 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1351,12 +1351,13 @@ trait Checking { def checkAnnotApplicable(annot: Tree, sym: Symbol)(using Context): Boolean = !ctx.reporter.reportsErrorsFor { val annotCls = Annotations.annotClass(annot) + val concreteAnnot = Annotations.ConcreteAnnotation(annot) val pos = annot.srcPos - if (annotCls == defn.MainAnnot) { + if (annotCls == defn.MainAnnot || concreteAnnot.matches(defn.MainAnnotationClass)) { if (!sym.isRealMethod) - report.error(em"@main annotation cannot be applied to $sym", pos) + report.error(em"main annotation cannot be applied to $sym", pos) if (!sym.owner.is(Module) || !sym.owner.isStatic) - report.error(em"$sym cannot be a @main method since it cannot be accessed statically", pos) + report.error(em"$sym cannot be a main method since it cannot be accessed statically", pos) } // TODO: Add more checks here } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index ac8d6152812e..d915a35b88b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2602,7 +2602,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer pkg.moduleClass.info.decls.lookup(topLevelClassName).ensureCompleted() var stats1 = typedStats(tree.stats, pkg.moduleClass)._1 if (!ctx.isAfterTyper) - stats1 = stats1 ++ typedBlockStats(MainProxies.mainProxies(stats1))._1 + stats1 = stats1 ++ typedBlockStats(MainProxies.proxies(stats1))._1 cpy.PackageDef(tree)(pid1, stats1).withType(pkg.termRef) } case _ => diff --git a/docs/_docs/reference/experimental/main-annotation.md b/docs/_docs/reference/experimental/main-annotation.md new file mode 100644 index 000000000000..c87d143ed15f --- /dev/null +++ b/docs/_docs/reference/experimental/main-annotation.md @@ -0,0 +1,91 @@ +--- +layout: doc-page +title: "MainAnnotation" +--- + +`MainAnnotation` provides a generic way to define main annotations such as `@main`. + +When a users annotates a method with an annotation that extends `MainAnnotation` a class with a `main` method will be generated. The main method will contain the code needed to parse the command line arguments and run the application. + +```scala +/** Sum all the numbers + * + * @param first Fist number to sum + * @param rest The rest of the numbers to sum + */ +@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum +``` + +```scala +object foo { + def main(args: Array[String]): Unit = { + + val cmd = new myMain().command( + info = new CommandInfo( + name = "sum", + documentation = "Sum all the numbers", + parameters = Seq( + new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()), + new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq()) + ) + ), + args = args + ) + val args0 = cmd.argGetter[Int](0, None) // using a parser of Int + val args1 = cmd.varargGetter[Int] // using a parser of Int + cmd.run(() => sum(args0(), args1()*)) + } +} +``` + +The implementation of the `main` method first instantiates the annotation and then creates a `Command`. +When creating the `Command`, the arguments can be checked and preprocessed. +Then it defines a series of argument getters calling `argGetter` for each parameter and `varargGetter` for the last one if it is a varargs. `argGetter` gets an optional lambda that computes the default argument. +Finally, the `run` method is called to run the application. It receives a by-name argument that contains the call the annotated method with the instantiations arguments (using the lambdas from `argGetter`/`varargGetter`). + + +Example of implementation of `myMain` that takes all arguments positionally. It used `util.CommandLineParser.FromString` and expects no default arguments. For simplicity, any errors in preprocessing or parsing results in crash. + +```scala +// Parser used to parse command line arguments +import scala.util.CommandLineParser.FromString[T] + +// Result type of the annotated method is Int +class myMain extends MainAnnotation: + import MainAnnotation.{ ParameterInfo, Command } + + /** A new command with arguments from `args` */ + def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] = + if args.contains("--help") then + println(info.documentation) + // TODO: Print documentation of the parameters + System.exit(0) + assert(info.parameters.forall(!_.hasDefault), "Default arguments are not supported") + val (plainArgs, varargs) = + if info.parameters.last.isVarargs then + val numPlainArgs = info.parameters.length - 1 + assert(numPlainArgs <= args.length, "Not enough arguments") + (args.take(numPlainArgs), args.drop(numPlainArgs)) + else + assert(info.parameters.length <= args.length, "Not enough arguments") + assert(info.parameters.length >= args.length, "Too many arguments") + (args, Array.empty[String]) + new MyCommand(plainArgs, varargs) + + @experimental + class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]: + + def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(plainArgs(idx)) + + def varargGetter[T](using parser: FromString[T]): () => Seq[T] = + () => varargs.map(arg => parser.fromString(arg)) + + def run(program: () => Int): Unit = + println("executing program") + val result = program() + println("result: " + result) + println("executed program") + end MyCommand +end myMain +``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 0f6ed6bf935d..7c68120bfbc2 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -147,6 +147,7 @@ subsection: - page: reference/experimental/named-typeargs-spec.md - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md + - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md - page: reference/experimental/tupled-function.md - page: reference/syntax.md diff --git a/library/src/scala/annotation/MainAnnotation.scala b/library/src/scala/annotation/MainAnnotation.scala new file mode 100644 index 000000000000..6e30ee6f69a3 --- /dev/null +++ b/library/src/scala/annotation/MainAnnotation.scala @@ -0,0 +1,119 @@ +package scala.annotation + +/** MainAnnotation provides the functionality for a compiler-generated main class. + * It links a compiler-generated main method (call it compiler-main) to a user + * written main method (user-main). + * The protocol of calls from compiler-main is as follows: + * + * - create a `command` with the command line arguments, + * - for each parameter of user-main, a call to `command.argGetter`, + * or `command.varargGetter` if is a final varargs parameter, + * - a call to `command.run` with the closure of user-main applied to all arguments. + * + * Example: + * ```scala + * /** Sum all the numbers + * * + * * @param first Fist number to sum + * * @param rest The rest of the numbers to sum + * */ + * @myMain def sum(first: Int, rest: Int*): Int = first + rest.sum + * ``` + * generates + * ```scala + * object foo { + * def main(args: Array[String]): Unit = { + * val cmd = new myMain().command( + * info = new CommandInfo( + * name = "foo.main", + * documentation = "Sum all the numbers", + * parameters = Seq( + * new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), + * new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") + * ) + * ) + * args = args + * ) + * val args0 = cmd.argGetter[Int](0, None) // using cmd.Parser[Int] + * val args1 = cmd.varargGetter[Int] // using cmd.Parser[Int] + * cmd.run(() => sum(args0(), args1()*)) + * } + * } + * ``` + * + */ +@experimental +trait MainAnnotation extends StaticAnnotation: + import MainAnnotation.{Command, CommandInfo} + + /** A new command with arguments from `args` + * + * @param info The information about the command (name, documentation and info about parameters) + * @param args The command line arguments + */ + def command(info: CommandInfo, args: Array[String]): Command[?, ?] + +end MainAnnotation + +@experimental +object MainAnnotation: + + /** A class representing a command to run + * + * @param Parser The class used for argument string parsing and arguments into a `T` + * @param Result The required result type of the main method. + * If this type is Any or Unit, any type will be accepted. + */ + trait Command[Parser[_], Result]: + + /** The getter for the `idx`th argument of type `T` + * + * @param idx The index of the argument + * @param defaultArgument Optional lambda to instantiate the default argument + */ + def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using Parser[T]): () => T + + /** The getter for a final varargs argument of type `T*` */ + def varargGetter[T](using Parser[T]): () => Seq[T] + + /** Run `program` if all arguments are valid if all arguments are valid + * + * @param program A function containing the call to the main method and instantiation of its arguments + */ + def run(program: () => Result): Unit + end Command + + /** Information about the main method + * + * @param name The name of the main method + * @param documentation The documentation of the main method without the `@param` documentation (see ParameterInfo.documentaion) + * @param parameters Information about the parameters of the main method + */ + final class CommandInfo( + val name: String, + val documentation: String, + val parameters: Seq[ParameterInfo], + ) + + /** Information about a parameter of a main method + * + * @param name The name of the parameter + * @param typeName The name of the parameter's type + * @param hasDefault If the parameter has a default argument + * @param isVarargs If the parameter is a varargs parameter (can only be true for the last parameter) + * @param documentation The documentation of the parameter (from `@param` documentation in the main method) + * @param annotations The annotations of the parameter that extend `ParameterAnnotation` + */ + final class ParameterInfo ( + val name: String, + val typeName: String, + val hasDefault: Boolean, + val isVarargs: Boolean, + val documentation: String, + val annotations: Seq[ParameterAnnotation], + ) + + /** Marker trait for annotations that will be included in the ParameterInfo annotations. */ + trait ParameterAnnotation extends StaticAnnotation + +end MainAnnotation diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 2c4fd4992432..8bd16f134f57 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,13 +3,21 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - - // Those are OK because user code is not allowed to inherit from Quotes: + // Experimental APIs that can be added in 3.2.0 or later + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), + + // Experimental `MainAnnotation` APIs. Can be added in 3.3.0 or later. + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Command"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$CommandInfo"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterInfo"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterAnnotation"), ) } diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index a8453449e73e..680b44d353d4 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -127,6 +127,7 @@ subsection: - page: reference/experimental/named-typeargs-spec.md - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md + - page: reference/experimental/main-annotation.md - page: reference/experimental/cc.md - page: reference/syntax.md - title: Language Versions diff --git a/tests/neg/main-annotation-mainannotation.scala b/tests/neg/main-annotation-mainannotation.scala new file mode 100644 index 000000000000..21e37d1779af --- /dev/null +++ b/tests/neg/main-annotation-mainannotation.scala @@ -0,0 +1,3 @@ +import scala.annotation.MainAnnotation + +@MainAnnotation def f(i: Int, n: Int) = () // error diff --git a/tests/run/main-annotation-example.check b/tests/run/main-annotation-example.check new file mode 100644 index 000000000000..97fcf11da08b --- /dev/null +++ b/tests/run/main-annotation-example.check @@ -0,0 +1,3 @@ +executing program +result: 28 +executed program diff --git a/tests/run/main-annotation-example.scala b/tests/run/main-annotation-example.scala new file mode 100644 index 000000000000..91036df44f57 --- /dev/null +++ b/tests/run/main-annotation-example.scala @@ -0,0 +1,59 @@ +import scala.annotation.* +import collection.mutable +import scala.util.CommandLineParser.FromString + +/** Sum all the numbers + * + * @param first Fist number to sum + * @param rest The rest of the numbers to sum + */ +@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("sum") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("23", "2", "3")) +end Test + +@experimental +class myMain extends MainAnnotation: + import MainAnnotation.{ Command, CommandInfo, ParameterInfo } + + /** A new command with arguments from `args` */ + def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] = + if args.contains("--help") then + println(info.documentation) + System.exit(0) + assert(info.parameters.forall(!_.hasDefault), "Default arguments are not supported") + val (plainArgs, varargs) = + if info.parameters.last.isVarargs then + val numPlainArgs = info.parameters.length - 1 + assert(numPlainArgs <= args.length, "Not enough arguments") + (args.take(numPlainArgs), args.drop(numPlainArgs)) + else + assert(info.parameters.length <= args.length, "Not enough arguments") + assert(info.parameters.length >= args.length, "Too many arguments") + (args, Array.empty[String]) + new MyCommand(plainArgs, varargs) + + @experimental + class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]: + + def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(plainArgs(idx)) + + def varargGetter[T](using parser: FromString[T]): () => Seq[T] = + () => varargs.map(arg => parser.fromString(arg)) + + def run(program: () => Int): Unit = + println("executing program") + val result = program() + println("result: " + result) + println("executed program") + end MyCommand +end myMain diff --git a/tests/run/main-annotation-homemade-annot-1.check b/tests/run/main-annotation-homemade-annot-1.check new file mode 100644 index 000000000000..4b7ff457bb11 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-1.check @@ -0,0 +1,4 @@ +42 +42 +1 +2 diff --git a/tests/run/main-annotation-homemade-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala new file mode 100644 index 000000000000..fabbc6348221 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-1.scala @@ -0,0 +1,45 @@ +import scala.concurrent._ +import scala.annotation.* +import scala.collection.mutable +import ExecutionContext.Implicits.global +import duration._ +import util.CommandLineParser.FromString + +@mainAwait def get(wait: Int): Future[Int] = Future{ + Thread.sleep(1000 * wait) + 42 +} + +@mainAwait def getMany(wait: Int*): Future[Int] = Future{ + Thread.sleep(1000 * wait.sum) + wait.length +} + +object Test: + def callMain(cls: String, args: Array[String]): Unit = + val clazz = Class.forName(cls) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + println(Await.result(get(1), Duration(2, SECONDS))) + callMain("get", Array("1")) + callMain("getMany", Array("1")) + callMain("getMany", Array("0", "1")) +end Test + +@experimental +class mainAwait(timeout: Int = 2) extends MainAnnotation: + import MainAnnotation.* + + // This is a toy example, it only works with positional args + def command(info: CommandInfo, args: Array[String]): Command[FromString, Future[Any]] = + new Command[FromString, Future[Any]]: + override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(args(idx)) + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = + () => for i <- ((info.parameters.length-1) until args.length) yield p.fromString(args(i)) + + override def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS))) +end mainAwait diff --git a/tests/run/main-annotation-homemade-annot-2.check b/tests/run/main-annotation-homemade-annot-2.check new file mode 100644 index 000000000000..f57ec79b8dbd --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-2.check @@ -0,0 +1,11 @@ +I was run! +A +I was run! +A +I was run! +A +Here are some colors: +Purple smart, Blue fast, White fashion, Yellow quiet, Orange honest, Pink loud +This will be printed, but nothing more. +This will be printed, but nothing more. +This will be printed, but nothing more. diff --git a/tests/run/main-annotation-homemade-annot-2.scala b/tests/run/main-annotation-homemade-annot-2.scala new file mode 100644 index 000000000000..e2eecfbd6fcc --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-2.scala @@ -0,0 +1,50 @@ +import scala.collection.mutable +import scala.annotation.* +import util.CommandLineParser.FromString + +@myMain()("A") +def foo1(): Unit = println("I was run!") + +@myMain(0)("This should not be printed") +def foo2() = throw new Exception("This should not be run") + +@myMain(1)("Purple smart", "Blue fast", "White fashion", "Yellow quiet", "Orange honest", "Pink loud") +def foo3() = println("Here are some colors:") + +@myMain()() +def foo4() = println("This will be printed, but nothing more.") + +object Test: + val allClazzes: Seq[Class[?]] = + LazyList.from(1).map(i => scala.util.Try(Class.forName("foo" + i.toString))).takeWhile(_.isSuccess).map(_.get) + + def callMains(): Unit = + for (clazz <- allClazzes) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) + + def main(args: Array[String]) = + callMains() +end Test + +// This is a toy example, it only works with positional args +@experimental +class myMain(runs: Int = 3)(after: String*) extends MainAnnotation: + import MainAnnotation.* + + def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = + new Command[FromString, Any]: + + override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(args(idx)) + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = + () => for i <- (info.parameters.length until args.length) yield p.fromString(args(i)) + + override def run(f: () => Any): Unit = + for (_ <- 1 to runs) + f() + if after.length > 0 then println(after.mkString(", ")) + end run + end command +end myMain diff --git a/tests/run/main-annotation-homemade-annot-3.check b/tests/run/main-annotation-homemade-annot-3.check new file mode 100644 index 000000000000..cd0875583aab --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-3.check @@ -0,0 +1 @@ +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala new file mode 100644 index 000000000000..640f6a934004 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-3.scala @@ -0,0 +1,24 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainNoArgs def foo() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + val clazz = Class.forName("foo") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainNoArgs extends MainAnnotation: + import MainAnnotation.* + + def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = + new Command[FromString, Any]: + override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + + override def run(program: () => Any): Unit = program() + end command diff --git a/tests/run/main-annotation-homemade-annot-4.check b/tests/run/main-annotation-homemade-annot-4.check new file mode 100644 index 000000000000..cd0875583aab --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-4.check @@ -0,0 +1 @@ +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-4.scala b/tests/run/main-annotation-homemade-annot-4.scala new file mode 100644 index 000000000000..602744398e74 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-4.scala @@ -0,0 +1,24 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainManyArgs(1, "B", 3) def foo() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + val clazz = Class.forName("foo") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation: + import MainAnnotation.* + + def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = + new Command[FromString, Any]: + override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T = ??? + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + + override def run(program: () => Any): Unit = program() + end command diff --git a/tests/run/main-annotation-homemade-annot-5.check b/tests/run/main-annotation-homemade-annot-5.check new file mode 100644 index 000000000000..7d60d6656c81 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-5.check @@ -0,0 +1,2 @@ +Hello world! +Hello world! diff --git a/tests/run/main-annotation-homemade-annot-5.scala b/tests/run/main-annotation-homemade-annot-5.scala new file mode 100644 index 000000000000..e529ac304efe --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-5.scala @@ -0,0 +1,26 @@ +import scala.annotation.* +import scala.util.CommandLineParser.FromString + +@mainManyArgs(Some(1)) def foo() = println("Hello world!") +@mainManyArgs(None) def bar() = println("Hello world!") + +object Test: + def main(args: Array[String]) = + for (methodName <- List("foo", "bar")) + val clazz = Class.forName(methodName) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class mainManyArgs(o: Option[Int]) extends MainAnnotation: + import MainAnnotation.* + + def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = + new Command[FromString, Any]: + override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + + override def run(program: () => Any): Unit = program() + end command diff --git a/tests/run/main-annotation-homemade-annot-6.check b/tests/run/main-annotation-homemade-annot-6.check new file mode 100644 index 000000000000..b9e33bf3e406 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-6.check @@ -0,0 +1,28 @@ +command( + Array(), + foo, + "Foo docs", + Seq( + ParameterInfo(name="i", typeName="scala.Int", hasDefault=false, isVarargs=false, documentation="", annotations=List()), + ParameterInfo(name="j", typeName="java.lang.String", hasDefault=true, isVarargs=false, documentation="", annotations=List()) + )* +) +argGetter(0, None) +argGetter(1, Some(2)) +run() +foo(42, abc) + +command( + Array(), + bar, + "Bar docs", + Seq( + ParameterInfo(name="i", typeName="scala.collection.immutable.List[Int]", hasDefault=false, isVarargs=false, documentation="the first parameter", annotations=List(MyParamAnnot(3))), + ParameterInfo(name="rest", typeName="scala.Int", hasDefault=false, isVarargs=true, documentation="", annotations=List()) + )* +) +argGetter(0, None) +varargGetter() +run() +bar(List(42), 42, 42) + diff --git a/tests/run/main-annotation-homemade-annot-6.scala b/tests/run/main-annotation-homemade-annot-6.scala new file mode 100644 index 000000000000..5d1c227d0c72 --- /dev/null +++ b/tests/run/main-annotation-homemade-annot-6.scala @@ -0,0 +1,63 @@ +import scala.annotation.* + +/** Foo docs */ +@myMain def foo(i: Int, j: String = "2") = println(s"foo($i, $j)") +/** Bar docs + * + * @param i the first parameter + */ +@myMain def bar(@MyParamAnnot(3) i: List[Int], rest: Int*) = println(s"bar($i, ${rest.mkString(", ")})") + +object Test: + def main(args: Array[String]) = + for (methodName <- List("foo", "bar")) + val clazz = Class.forName(methodName) + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, Array[String]()) +end Test + +@experimental +class myMain extends MainAnnotation: + import MainAnnotation.* + + def command(info: CommandInfo, args: Array[String]): Command[Make, Any] = + def paramInfoString(paramInfo: ParameterInfo) = + import paramInfo.* + s" ParameterInfo(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)" + println( + s"""command( + | ${args.mkString("Array(", ", ", ")")}, + | ${info.name}, + | "${info.documentation}", + | ${info.parameters.map(paramInfoString).mkString("Seq(\n", ",\n", "\n )*")} + |)""".stripMargin) + new Command[Make, Any]: + override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Make[T]): () => T = + println(s"argGetter($idx, ${defaultArgument.map(_())})") + () => p.make + + override def varargGetter[T](using p: Make[T]): () => Seq[T] = + println("varargGetter()") + () => Seq(p.make, p.make) + + override def run(f: () => Any): Unit = + println("run()") + f() + println() + end command + +@experimental +case class MyParamAnnot(n: Int) extends MainAnnotation.ParameterAnnotation + +trait Make[T]: + def make: T + +given Make[Int] with + def make: Int = 42 + + +given Make[String] with + def make: String = "abc" + +given [T: Make]: Make[List[T]] with + def make: List[T] = List(summon[Make[T]].make) diff --git a/tests/run/main-annotation-newMain.scala b/tests/run/main-annotation-newMain.scala new file mode 100644 index 000000000000..c2a538443b24 --- /dev/null +++ b/tests/run/main-annotation-newMain.scala @@ -0,0 +1,307 @@ +import scala.annotation.* +import collection.mutable +import scala.util.CommandLineParser.FromString + +@newMain def happyBirthday(age: Int, name: String, others: String*) = + val suffix = + age % 100 match + case 11 | 12 | 13 => "th" + case _ => + age % 10 match + case 1 => "st" + case 2 => "nd" + case 3 => "rd" + case _ => "th" + val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") + for other <- others do bldr.append(" and ").append(other) + println(bldr) + + +object Test: + def callMain(args: Array[String]): Unit = + val clazz = Class.forName("happyBirthday") + val method = clazz.getMethod("main", classOf[Array[String]]) + method.invoke(null, args) + + def main(args: Array[String]): Unit = + callMain(Array("23", "Lisa", "Peter")) +end Test + + + +@experimental +final class newMain extends MainAnnotation: + import newMain._ + import MainAnnotation._ + + def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = + new Command[FromString, Any]: + + private inline val argMarker = "--" + private inline val shortArgMarker = "-" + + /** + * The name of the special argument to display the method's help. + * If one of the method's parameters is called the same, will be ignored. + */ + private inline val helpArg = "help" + + /** + * The short name of the special argument to display the method's help. + * If one of the method's parameters uses the same short name, will be ignored. + */ + private inline val shortHelpArg = 'h' + private var shortHelpIsOverridden = false + + private inline val maxUsageLineLength = 120 + + /** A map from argument canonical name (the name of the parameter in the method definition) to parameter informations */ + private val nameToParameterInfo: Map[String, ParameterInfo] = info.parameters.map(infos => infos.name -> infos).toMap + + private val (positionalArgs, byNameArgs, invalidByNameArgs, helpIsOverridden) = { + val namesToCanonicalName: Map[String, String] = info.parameters.flatMap( + infos => + var names = getAlternativeNames(infos) + val canonicalName = infos.name + if nameIsValid(canonicalName) then names = canonicalName +: names + names.map(_ -> canonicalName) + ).toMap + val shortNamesToCanonicalName: Map[Char, String] = info.parameters.flatMap( + infos => + var names = getShortNames(infos) + val canonicalName = infos.name + if shortNameIsValid(canonicalName) then names = canonicalName(0) +: names + names.map(_ -> canonicalName) + ).toMap + + val helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) + shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) + + def getCanonicalArgName(arg: String): Option[String] = + if arg.startsWith(argMarker) && arg.length > argMarker.length then + namesToCanonicalName.get(arg.drop(argMarker.length)) + else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then + shortNamesToCanonicalName.get(arg(shortArgMarker.length)) + else + None + + def isArgName(arg: String): Boolean = + val isFullName = arg.startsWith(argMarker) + val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length)) + isFullName || isShortName + + def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = + remainingArgs match { + case Seq() => + (pa, bna, ia) + case argName +: argValue +: rest if isArgName(argName) => + getCanonicalArgName(argName) match { + case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) + case None => recurse(rest, pa, bna, ia :+ argName) + } + case arg +: rest => + recurse(rest, pa :+ arg, bna, ia) + } + + val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) + val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) + (pa, nameToArgValues, ia, helpIsOverridden) + } + + /** A buffer for all errors */ + private val errors = new mutable.ArrayBuffer[String] + + /** Issue an error, and return an uncallable getter */ + private def error(msg: String): () => Nothing = + errors += msg + () => throw new AssertionError("trying to get invalid argument") + + private inline def nameIsValid(name: String): Boolean = + name.length > 1 // TODO add more checks for illegal characters + + private inline def shortNameIsValid(name: String): Boolean = + name.length == 1 && shortNameIsValid(name(0)) + + private inline def shortNameIsValid(shortName: Char): Boolean = + ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') + + private def getNameWithMarker(name: String | Char): String = name match { + case c: Char => shortArgMarker + c + case s: String if shortNameIsValid(s) => shortArgMarker + s + case s => argMarker + s + } + + private def convert[T](argName: String, arg: String)(using p: FromString[T]): () => T = + p.fromStringOption(arg) match + case Some(t) => () => t + case None => error(s"invalid argument for $argName: $arg") + + private def usage(): Unit = + def argsUsage: Seq[String] = + for info <- info.parameters yield + val canonicalName = getNameWithMarker(info.name) + val shortNames = getShortNames(info).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(info).map(getNameWithMarker) + val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") + if info.isVarargs then s"[<${info.typeName}> [<${info.typeName}> [...]]]" + else if info.hasDefault then s"[$namesPrint <${info.typeName}>]" + else s"$namesPrint <${info.typeName}>" + end for + + def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { + def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = + (args, currentLine) match { + case (Nil, "") => acc + case (Nil, l) => (acc :+ l) + case (arg +: t, "") => recurse(t, arg, acc) + case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) + case (arg +: t, l) => recurse(t, arg, acc :+ l) + } + + recurse(argsUsage, "", Vector()).toList + } + + val usageBeginning = s"Usage: ${info.name} " + val argsOffset = usageBeginning.length + val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) + + println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) + end usage + + private def explain(): Unit = + inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + + def wrapLongLine(line: String, maxLength: Int): List[String] = { + def recurse(s: String, acc: Vector[String]): Seq[String] = + val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) + if ((s.length <= maxLength) || (lastSpace < 0)) + acc :+ s + else { + val (shortLine, rest) = s.splitAt(lastSpace) + recurse(rest.trim.nn, acc :+ shortLine) + } + + recurse(line, Vector()).toList + } + + if (info.documentation.nonEmpty) + println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) + if (nameToParameterInfo.nonEmpty) { + val argNameShift = 2 + val argDocShift = argNameShift + 2 + + println("Arguments:") + for info <- info.parameters do + val canonicalName = getNameWithMarker(info.name) + val shortNames = getShortNames(info).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(info).map(getNameWithMarker) + val otherNames = (alternativeNames ++: shortNames) match { + case Seq() => "" + case names => names.mkString("(", ", ", ") ") + } + val argDoc = StringBuilder(" " * argNameShift) + argDoc.append(s"$canonicalName $otherNames- ${info.typeName}") + + if info.isVarargs then argDoc.append(" (vararg)") + else if info.hasDefault then argDoc.append(" (optional)") + + val doc = info.documentation + if (doc.nonEmpty) { + val shiftedDoc = + doc.split("\n").nn + .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) + .mkString("\n") + argDoc.append("\n").append(shiftedDoc) + } + + println(argDoc) + end for + } + end explain + + private def getAliases(paramInfos: ParameterInfo): Seq[String] = + paramInfos.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) + + private def getAlternativeNames(paramInfos: ParameterInfo): Seq[String] = + getAliases(paramInfos).filter(nameIsValid(_)) + + private def getShortNames(paramInfos: ParameterInfo): Seq[Char] = + getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0)) + + private def getInvalidNames(paramInfos: ParameterInfo): Seq[String | Char] = + getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) + + override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T = + val name = info.parameters(idx).name + val parameterInfo = nameToParameterInfo(name) + // TODO: Decide which string is associated with this arg when constructing the command. + // Here we should only get the string for this argument, apply it to the parser and handle parsing errors. + // Should be able to get the argument from its index. + byNameArgs.get(name) match { + case Some(Nil) => + throw AssertionError(s"$name present in byNameArgs, but it has no argument value") + case Some(argValues) => + if argValues.length > 1 then + // Do not accept multiple values + // Remove this test to take last given argument + error(s"more than one value for $name: ${argValues.mkString(", ")}") + else + convert(name, argValues.last) + case None => + if positionalArgs.length > 0 then + convert(name, positionalArgs.dequeue) + else if optDefaultGetter.nonEmpty then + optDefaultGetter.get + else + error(s"missing argument for $name") + } + end argGetter + + override def varargGetter[T](using p: FromString[T]): () => Seq[T] = + val name = info.parameters.last.name + // TODO: Decide which strings are associated with the varargs when constructing the command. + // Here we should only get the strings for this argument, apply them to the parser and handle parsing errors. + // Should be able to get the argument from its index (last). + val byNameGetters = byNameArgs.getOrElse(name, Seq()).map(arg => convert(name, arg)) + val positionalGetters = positionalArgs.removeAll.map(arg => convert(name, arg)) + // First take arguments passed by name, then those passed by position + () => (byNameGetters ++ positionalGetters).map(_()) + + override def run(f: () => Any): Unit = + // Check aliases unicity + val nameAndCanonicalName = nameToParameterInfo.toList.flatMap { + case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName) + } + val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) + + for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 + do throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + + // Check aliases validity + val problematicNames = nameToParameterInfo.toList.flatMap((_, infos) => getInvalidNames(infos)) + if problematicNames.length > 0 then throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") + + // Handle unused and invalid args + for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") + for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") + + val displayHelp = + (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) + + if displayHelp then + usage() + println() + explain() + else if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + else + f() + end run + end command +end newMain + +object newMain: + @experimental + final class Alias(val aliases: String*) extends MainAnnotation.ParameterAnnotation +end newMain From 813e059afe04428089a5b6e79b7ec956c0478741 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 5 Apr 2022 15:48:15 +0200 Subject: [PATCH 066/121] Simplify `MainAnnotation` interface Remove the `Command` class and place the `argGetter`, `varargsGetter` and `run` methods directly in the `MainAnnotation` interface. Now `command` pre-processes the arguments which clearly states which strings will be used for each argument. This simplifies the implementation of the `MainAnnotation` methods. --- .../dotty/tools/dotc/ast/MainProxies.scala | 104 ++-- .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../reference/experimental/main-annotation.md | 92 ++-- .../src/scala/annotation/MainAnnotation.scala | 99 ++-- .../reference-expected-links.txt | 1 + tests/run/main-annotation-example.scala | 65 +-- .../main-annotation-homemade-annot-1.scala | 17 +- .../main-annotation-homemade-annot-2.scala | 25 +- .../main-annotation-homemade-annot-3.scala | 13 +- .../main-annotation-homemade-annot-4.scala | 14 +- .../main-annotation-homemade-annot-5.scala | 13 +- .../main-annotation-homemade-annot-6.check | 15 +- .../main-annotation-homemade-annot-6.scala | 37 +- tests/run/main-annotation-newMain.scala | 473 +++++++++--------- 15 files changed, 511 insertions(+), 462 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala index 01ae50850b57..5e969c0c38c9 100644 --- a/compiler/src/dotty/tools/dotc/ast/MainProxies.scala +++ b/compiler/src/dotty/tools/dotc/ast/MainProxies.scala @@ -143,6 +143,7 @@ object MainProxies { * */ * @myMain(80) def f( * @myMain.Alias("myX") x: S, + * y: S, * ys: T* * ) = ... * @@ -150,22 +151,23 @@ object MainProxies { * * final class f { * static def main(args: Array[String]): Unit = { - * val cmd = new myMain(80).command( - * info = new CommandInfo( - * name = "f", - * documentation = "Lorem ipsum dolor sit amet consectetur adipiscing elit.", - * parameters = Seq( - * new scala.annotation.MainAnnotation.ParameterInfo("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) - * new scala.annotation.MainAnnotation.ParameterInfo("ys", "T", false, false, "all my params y", Seq()) - * ) + * val annotation = new myMain(80) + * val info = new Info( + * name = "f", + * documentation = "Lorem ipsum dolor sit amet consectetur adipiscing elit.", + * parameters = Seq( + * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))), + * new scala.annotation.MainAnnotation.Parameter("y", "S", true, false, "", Seq()), + * new scala.annotation.MainAnnotation.Parameter("ys", "T", false, true, "all my params y", Seq()) * ) - * args = args - * ) - * - * val args0: () => S = cmd.argGetter[S](0, None) - * val args1: () => Seq[T] = cmd.varargGetter[T] - * - * cmd.run(() => f(args0(), args1()*)) + * ), + * val command = annotation.command(info, args) + * if command.isDefined then + * val cmd = command.get + * val args0: () => S = annotation.argGetter[S](info.parameters(0), cmd(0), None) + * val args1: () => S = annotation.argGetter[S](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) + * val args2: () => Seq[T] = annotation.varargGetter[T](info.parameters(2), cmd.drop(2)) + * annotation.run(() => f(args0(), args1(), args2()*)) * } * } */ @@ -229,7 +231,7 @@ object MainProxies { * * A ParamInfo has the following shape * ``` - * new scala.annotation.MainAnnotation.ParameterInfo("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) + * new scala.annotation.MainAnnotation.Parameter("x", "S", false, false, "my param x", Seq(new scala.main.Alias("myX"))) * ``` */ def parameterInfos(mt: MethodType): List[Tree] = @@ -252,33 +254,34 @@ object MainProxies { val constructorArgs = List(param, paramTypeStr, hasDefault, isRepeated, paramDoc) .map(value => Literal(Constant(value))) - New(TypeTree(defn.MainAnnotationParameterInfo.typeRef), List(constructorArgs :+ paramAnnots)) + New(TypeTree(defn.MainAnnotationParameter.typeRef), List(constructorArgs :+ paramAnnots)) end parameterInfos /** * Creates a list of references and definitions of arguments. * The goal is to create the - * `val args0: () => S = cmd.argGetter[S](0, None)` + * `val args0: () => S = annotation.argGetter[S](0, cmd(0), None)` * part of the code. */ def argValDefs(mt: MethodType): List[ValDef] = for ((formal, paramName), idx) <- mt.paramInfos.zip(mt.paramNames).zipWithIndex yield - val argName = nme.args ++ idx.toString - val isRepeated = formal.isRepeatedParam - val formalType = if isRepeated then formal.argTypes.head else formal - val getterName = if isRepeated then nme.varargGetter else nme.argGetter - val defaultValueGetterOpt = defaultValueSymbols.get(idx) match - case None => ref(defn.NoneModule.termRef) - case Some(dvSym) => - val value = unitToValue(ref(dvSym.termRef)) - Apply(ref(defn.SomeClass.companionModule.termRef), value) - val argGetter0 = TypeApply(Select(Ident(nme.cmd), getterName), TypeTree(formalType) :: Nil) - val argGetter = - if isRepeated then argGetter0 - else Apply(argGetter0, List(Literal(Constant(idx)), defaultValueGetterOpt)) - - ValDef(argName, TypeTree(), argGetter) + val argName = nme.args ++ idx.toString + val isRepeated = formal.isRepeatedParam + val formalType = if isRepeated then formal.argTypes.head else formal + val getterName = if isRepeated then nme.varargGetter else nme.argGetter + val defaultValueGetterOpt = defaultValueSymbols.get(idx) match + case None => ref(defn.NoneModule.termRef) + case Some(dvSym) => + val value = unitToValue(ref(dvSym.termRef)) + Apply(ref(defn.SomeClass.companionModule.termRef), value) + val argGetter0 = TypeApply(Select(Ident(nme.annotation), getterName), TypeTree(formalType) :: Nil) + val index = Literal(Constant(idx)) + val paramInfo = Apply(Select(Ident(nme.info), nme.parameters), index) + val argGetter = + if isRepeated then Apply(argGetter0, List(paramInfo, Apply(Select(Ident(nme.cmd), nme.drop), List(index)))) + else Apply(argGetter0, List(paramInfo, Apply(Ident(nme.cmd), List(index)), defaultValueGetterOpt)) + ValDef(argName, TypeTree(), argGetter) end argValDefs @@ -318,18 +321,39 @@ object MainProxies { val nameTree = Literal(Constant(mainFun.showName)) val docTree = Literal(Constant(documentation.mainDoc)) val paramInfos = Apply(ref(defn.SeqModule.termRef), parameterInfos) - New(TypeTree(defn.MainAnnotationCommandInfo.typeRef), List(List(nameTree, docTree, paramInfos))) + New(TypeTree(defn.MainAnnotationInfo.typeRef), List(List(nameTree, docTree, paramInfos))) - val cmd = ValDef( - nme.cmd, + val annotVal = ValDef( + nme.annotation, + TypeTree(), + instantiateAnnotation(mainAnnot) + ) + val infoVal = ValDef( + nme.info, + TypeTree(), + cmdInfo + ) + val command = ValDef( + nme.command, TypeTree(), Apply( - Select(instantiateAnnotation(mainAnnot), nme.command), - List(cmdInfo, Ident(nme.args)) + Select(Ident(nme.annotation), nme.command), + List(Ident(nme.info), Ident(nme.args)) ) ) - val run = Apply(Select(Ident(nme.cmd), nme.run), mainCall) - val body = Block(cmdInfo :: cmd :: args, run) + val argsVal = ValDef( + nme.cmd, + TypeTree(), + Select(Ident(nme.command), nme.get) + ) + val run = Apply(Select(Ident(nme.annotation), nme.run), mainCall) + val body0 = If( + Select(Ident(nme.command), nme.isDefined), + Block(argsVal :: args, run), + EmptyTree + ) + val body = Block(List(annotVal, infoVal, command), body0) // TODO add `if (cmd.nonEmpty)` + val mainArg = ValDef(nme.args, TypeTree(defn.ArrayType.appliedTo(defn.StringType)), EmptyTree) .withFlags(Param) /** Replace typed `Ident`s that have been typed with a TypeSplice with the reference to the symbol. diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 9e70102fa1dd..8aaaff52708d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -856,8 +856,8 @@ class Definitions { @tu lazy val XMLTopScopeModule: Symbol = requiredModule("scala.xml.TopScope") @tu lazy val MainAnnotationClass: ClassSymbol = requiredClass("scala.annotation.MainAnnotation") - @tu lazy val MainAnnotationCommandInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.CommandInfo") - @tu lazy val MainAnnotationParameterInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterInfo") + @tu lazy val MainAnnotationInfo: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Info") + @tu lazy val MainAnnotationParameter: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Parameter") @tu lazy val MainAnnotationParameterAnnotation: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.ParameterAnnotation") @tu lazy val MainAnnotationCommand: ClassSymbol = requiredClass("scala.annotation.MainAnnotation.Command") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index bb5efcc01a5e..dc9e48b65f47 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -543,6 +543,7 @@ object StdNames { val ordinalDollar: N = "$ordinal" val ordinalDollar_ : N = "_$ordinal" val origin: N = "origin" + val parameters: N = "parameters" val parts: N = "parts" val postfixOps: N = "postfixOps" val prefix : N = "prefix" diff --git a/docs/_docs/reference/experimental/main-annotation.md b/docs/_docs/reference/experimental/main-annotation.md index c87d143ed15f..d2172d97a284 100644 --- a/docs/_docs/reference/experimental/main-annotation.md +++ b/docs/_docs/reference/experimental/main-annotation.md @@ -13,33 +13,35 @@ When a users annotates a method with an annotation that extends `MainAnnotation` * @param first Fist number to sum * @param rest The rest of the numbers to sum */ -@myMain def sum(first: Int, rest: Int*): Int = first + rest.sum +@myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum ``` ```scala object foo { def main(args: Array[String]): Unit = { - - val cmd = new myMain().command( - info = new CommandInfo( - name = "sum", - documentation = "Sum all the numbers", - parameters = Seq( - new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()), - new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq()) - ) - ), - args = args + val mainAnnot = new myMain() + val info = new Info( + name = "foo.main", + documentation = "Sum all the numbers", + parameters = Seq( + new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum", Seq()), + new Parameter("second", "scala.Int", hasDefault=true, isVarargs=false, "", Seq()), + new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum", Seq()) + ) ) - val args0 = cmd.argGetter[Int](0, None) // using a parser of Int - val args1 = cmd.varargGetter[Int] // using a parser of Int - cmd.run(() => sum(args0(), args1()*)) + val mainArgsOpt = mainAnnot.command(info, args) + if mainArgsOpt.isDefined then + val mainArgs = mainArgsOpt.get + val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using a parser of Int + val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using a parser of Int + val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using a parser of Int + mainAnnot.run(() => sum(args0(), args1(), args2()*)) } } ``` -The implementation of the `main` method first instantiates the annotation and then creates a `Command`. -When creating the `Command`, the arguments can be checked and preprocessed. +The implementation of the `main` method first instantiates the annotation and then call `command`. +When calling the `command`, the arguments can be checked and preprocessed. Then it defines a series of argument getters calling `argGetter` for each parameter and `varargGetter` for the last one if it is a varargs. `argGetter` gets an optional lambda that computes the default argument. Finally, the `run` method is called to run the application. It receives a by-name argument that contains the call the annotated method with the instantiations arguments (using the lambdas from `argGetter`/`varargGetter`). @@ -50,42 +52,46 @@ Example of implementation of `myMain` that takes all arguments positionally. It // Parser used to parse command line arguments import scala.util.CommandLineParser.FromString[T] -// Result type of the annotated method is Int -class myMain extends MainAnnotation: - import MainAnnotation.{ ParameterInfo, Command } +// Result type of the annotated method is Int and arguments are parsed using FromString +@experimental class myMain extends MainAnnotation[FromString, Int]: + import MainAnnotation.{ Info, Parameter } - /** A new command with arguments from `args` */ - def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] = + def command(info: Info, args: Seq[String]): Option[Seq[String]] = if args.contains("--help") then println(info.documentation) - // TODO: Print documentation of the parameters - System.exit(0) - assert(info.parameters.forall(!_.hasDefault), "Default arguments are not supported") - val (plainArgs, varargs) = - if info.parameters.last.isVarargs then - val numPlainArgs = info.parameters.length - 1 - assert(numPlainArgs <= args.length, "Not enough arguments") - (args.take(numPlainArgs), args.drop(numPlainArgs)) + None // do not parse or run the program + else if info.parameters.exists(_.hasDefault) then + println("Default arguments are not supported") + None + else if info.hasVarargs then + val numPlainArgs = info.parameters.length - 1 + if numPlainArgs <= args.length then + println("Not enough arguments") + None + else + Some(args) + else + if info.parameters.length <= args.length then + println("Not enough arguments") + None + else if info.parameters.length >= args.length then + println("Too many arguments") + None else - assert(info.parameters.length <= args.length, "Not enough arguments") - assert(info.parameters.length >= args.length, "Too many arguments") - (args, Array.empty[String]) - new MyCommand(plainArgs, varargs) + Some(args) - @experimental - class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]: + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(arg) - def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = - () => parser.fromString(plainArgs(idx)) + def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = + () => args.map(arg => parser.fromString(arg)) - def varargGetter[T](using parser: FromString[T]): () => Seq[T] = - () => varargs.map(arg => parser.fromString(arg)) + def run(program: () => Int): Unit = + println("executing program") - def run(program: () => Int): Unit = - println("executing program") + try { val result = program() println("result: " + result) println("executed program") - end MyCommand end myMain ``` diff --git a/library/src/scala/annotation/MainAnnotation.scala b/library/src/scala/annotation/MainAnnotation.scala index 6e30ee6f69a3..9d2f5362ba15 100644 --- a/library/src/scala/annotation/MainAnnotation.scala +++ b/library/src/scala/annotation/MainAnnotation.scala @@ -17,83 +17,90 @@ package scala.annotation * * @param first Fist number to sum * * @param rest The rest of the numbers to sum * */ - * @myMain def sum(first: Int, rest: Int*): Int = first + rest.sum + * @myMain def sum(first: Int, second: Int = 0, rest: Int*): Int = first + second + rest.sum * ``` * generates * ```scala * object foo { * def main(args: Array[String]): Unit = { - * val cmd = new myMain().command( - * info = new CommandInfo( - * name = "foo.main", - * documentation = "Sum all the numbers", - * parameters = Seq( - * new ParameterInfo("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), - * new ParameterInfo("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") - * ) + * val mainAnnot = new myMain() + * val info = new Info( + * name = "foo.main", + * documentation = "Sum all the numbers", + * parameters = Seq( + * new Parameter("first", "scala.Int", hasDefault=false, isVarargs=false, "Fist number to sum"), + * new Parameter("rest", "scala.Int" , hasDefault=false, isVarargs=true, "The rest of the numbers to sum") * ) - * args = args * ) - * val args0 = cmd.argGetter[Int](0, None) // using cmd.Parser[Int] - * val args1 = cmd.varargGetter[Int] // using cmd.Parser[Int] - * cmd.run(() => sum(args0(), args1()*)) + * val mainArgsOpt = mainAnnot.command(info, args) + * if mainArgsOpt.isDefined then + * val mainArgs = mainArgsOpt.get + * val args0 = mainAnnot.argGetter[Int](info.parameters(0), mainArgs(0), None) // using parser Int + * val args1 = mainAnnot.argGetter[Int](info.parameters(1), mainArgs(1), Some(() => sum$default$1())) // using parser Int + * val args2 = mainAnnot.varargGetter[Int](info.parameters(2), mainArgs.drop(2)) // using parser Int + * mainAnnot.run(() => sum(args0(), args1(), args2()*)) * } * } * ``` * + * @param Parser The class used for argument string parsing and arguments into a `T` + * @param Result The required result type of the main method. + * If this type is Any or Unit, any type will be accepted. */ @experimental -trait MainAnnotation extends StaticAnnotation: - import MainAnnotation.{Command, CommandInfo} +trait MainAnnotation[Parser[_], Result] extends StaticAnnotation: + import MainAnnotation.{Info, Parameter} - /** A new command with arguments from `args` + /** Process the command arguments before parsing them. + * + * Return `Some` of the sequence of arguments that will be parsed to be passed to the main method. + * This sequence needs to have the same length as the number of parameters of the main method (i.e. `info.parameters.size`). + * If there is a varags parameter, then the sequence must be at least of length `info.parameters.size - 1`. + * + * Returns `None` if the arguments are invalid and parsing and run should be stopped. * * @param info The information about the command (name, documentation and info about parameters) * @param args The command line arguments */ - def command(info: CommandInfo, args: Array[String]): Command[?, ?] + def command(info: Info, args: Seq[String]): Option[Seq[String]] -end MainAnnotation + /** The getter for the `idx`th argument of type `T` + * + * @param idx The index of the argument + * @param defaultArgument Optional lambda to instantiate the default argument + */ + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using Parser[T]): () => T -@experimental -object MainAnnotation: + /** The getter for a final varargs argument of type `T*` */ + def varargGetter[T](param: Parameter, args: Seq[String])(using Parser[T]): () => Seq[T] - /** A class representing a command to run + /** Run `program` if all arguments are valid if all arguments are valid * - * @param Parser The class used for argument string parsing and arguments into a `T` - * @param Result The required result type of the main method. - * If this type is Any or Unit, any type will be accepted. + * @param program A function containing the call to the main method and instantiation of its arguments */ - trait Command[Parser[_], Result]: + def run(program: () => Result): Unit - /** The getter for the `idx`th argument of type `T` - * - * @param idx The index of the argument - * @param defaultArgument Optional lambda to instantiate the default argument - */ - def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using Parser[T]): () => T - - /** The getter for a final varargs argument of type `T*` */ - def varargGetter[T](using Parser[T]): () => Seq[T] +end MainAnnotation - /** Run `program` if all arguments are valid if all arguments are valid - * - * @param program A function containing the call to the main method and instantiation of its arguments - */ - def run(program: () => Result): Unit - end Command +@experimental +object MainAnnotation: /** Information about the main method * * @param name The name of the main method - * @param documentation The documentation of the main method without the `@param` documentation (see ParameterInfo.documentaion) + * @param documentation The documentation of the main method without the `@param` documentation (see Parameter.documentaion) * @param parameters Information about the parameters of the main method */ - final class CommandInfo( + final class Info( val name: String, val documentation: String, - val parameters: Seq[ParameterInfo], - ) + val parameters: Seq[Parameter], + ): + + /** If the method ends with a varargs parameter */ + def hasVarargs: Boolean = parameters.nonEmpty && parameters.last.isVarargs + + end Info /** Information about a parameter of a main method * @@ -104,7 +111,7 @@ object MainAnnotation: * @param documentation The documentation of the parameter (from `@param` documentation in the main method) * @param annotations The annotations of the parameter that extend `ParameterAnnotation` */ - final class ParameterInfo ( + final class Parameter( val name: String, val typeName: String, val hasDefault: Boolean, @@ -113,7 +120,7 @@ object MainAnnotation: val annotations: Seq[ParameterAnnotation], ) - /** Marker trait for annotations that will be included in the ParameterInfo annotations. */ + /** Marker trait for annotations that will be included in the Parameter annotations. */ trait ParameterAnnotation extends StaticAnnotation end MainAnnotation diff --git a/project/scripts/expected-links/reference-expected-links.txt b/project/scripts/expected-links/reference-expected-links.txt index 737267576c6e..f51727b7b432 100644 --- a/project/scripts/expected-links/reference-expected-links.txt +++ b/project/scripts/expected-links/reference-expected-links.txt @@ -68,6 +68,7 @@ ./experimental/erased-defs.html ./experimental/explicit-nulls.html ./experimental/index.html +./experimental/main-annotation.html ./experimental/named-typeargs-spec.html ./experimental/named-typeargs.html ./experimental/numeric-literals.html diff --git a/tests/run/main-annotation-example.scala b/tests/run/main-annotation-example.scala index 91036df44f57..954278d6b26f 100644 --- a/tests/run/main-annotation-example.scala +++ b/tests/run/main-annotation-example.scala @@ -21,39 +21,42 @@ object Test: end Test @experimental -class myMain extends MainAnnotation: - import MainAnnotation.{ Command, CommandInfo, ParameterInfo } +class myMain extends MainAnnotation[FromString, Int]: + import MainAnnotation.{ Info, Parameter } - /** A new command with arguments from `args` */ - def command(info: CommandInfo, args: Array[String]): Command[FromString, Int] = + def command(info: Info, args: Seq[String]): Option[Seq[String]] = if args.contains("--help") then println(info.documentation) - System.exit(0) - assert(info.parameters.forall(!_.hasDefault), "Default arguments are not supported") - val (plainArgs, varargs) = - if info.parameters.last.isVarargs then - val numPlainArgs = info.parameters.length - 1 - assert(numPlainArgs <= args.length, "Not enough arguments") - (args.take(numPlainArgs), args.drop(numPlainArgs)) + None // do not parse or run the program + else if info.parameters.exists(_.hasDefault) then + println("Default arguments are not supported") + None + else if info.hasVarargs then + val numPlainArgs = info.parameters.length - 1 + if numPlainArgs > args.length then + println("Not enough arguments") + None else - assert(info.parameters.length <= args.length, "Not enough arguments") - assert(info.parameters.length >= args.length, "Too many arguments") - (args, Array.empty[String]) - new MyCommand(plainArgs, varargs) - - @experimental - class MyCommand(plainArgs: Seq[String], varargs: Seq[String]) extends Command[FromString, Int]: - - def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = - () => parser.fromString(plainArgs(idx)) - - def varargGetter[T](using parser: FromString[T]): () => Seq[T] = - () => varargs.map(arg => parser.fromString(arg)) - - def run(program: () => Int): Unit = - println("executing program") - val result = program() - println("result: " + result) - println("executed program") - end MyCommand + Some(args) + else + if info.parameters.length > args.length then + println("Not enough arguments") + None + else if info.parameters.length < args.length then + println("Too many arguments") + None + else + Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using parser: FromString[T]): () => T = + () => parser.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using parser: FromString[T]): () => Seq[T] = + () => args.map(arg => parser.fromString(arg)) + + def run(program: () => Int): Unit = + println("executing program") + val result = program() + println("result: " + result) + println("executed program") end myMain diff --git a/tests/run/main-annotation-homemade-annot-1.scala b/tests/run/main-annotation-homemade-annot-1.scala index fabbc6348221..daf27b944d99 100644 --- a/tests/run/main-annotation-homemade-annot-1.scala +++ b/tests/run/main-annotation-homemade-annot-1.scala @@ -29,17 +29,18 @@ object Test: end Test @experimental -class mainAwait(timeout: Int = 2) extends MainAnnotation: +class mainAwait(timeout: Int = 2) extends MainAnnotation[FromString, Future[Any]]: import MainAnnotation.* // This is a toy example, it only works with positional args - def command(info: CommandInfo, args: Array[String]): Command[FromString, Future[Any]] = - new Command[FromString, Future[Any]]: - override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = - () => p.fromString(args(idx)) + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = - () => for i <- ((info.parameters.length-1) until args.length) yield p.fromString(args(i)) + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(arg) + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = + () => for arg <- args yield p.fromString(arg) + + def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS))) - override def run(f: () => Future[Any]): Unit = println(Await.result(f(), Duration(timeout, SECONDS))) end mainAwait diff --git a/tests/run/main-annotation-homemade-annot-2.scala b/tests/run/main-annotation-homemade-annot-2.scala index e2eecfbd6fcc..3cee9151282d 100644 --- a/tests/run/main-annotation-homemade-annot-2.scala +++ b/tests/run/main-annotation-homemade-annot-2.scala @@ -29,22 +29,21 @@ end Test // This is a toy example, it only works with positional args @experimental -class myMain(runs: Int = 3)(after: String*) extends MainAnnotation: +class myMain(runs: Int = 3)(after: String*) extends MainAnnotation[FromString, Any]: import MainAnnotation.* - def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = - new Command[FromString, Any]: + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = - () => p.fromString(args(idx)) + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = + () => p.fromString(arg) - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = - () => for i <- (info.parameters.length until args.length) yield p.fromString(args(i)) + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = + () => for arg <- args yield p.fromString(arg) + + def run(f: () => Any): Unit = + for (_ <- 1 to runs) + f() + if after.length > 0 then println(after.mkString(", ")) + end run - override def run(f: () => Any): Unit = - for (_ <- 1 to runs) - f() - if after.length > 0 then println(after.mkString(", ")) - end run - end command end myMain diff --git a/tests/run/main-annotation-homemade-annot-3.scala b/tests/run/main-annotation-homemade-annot-3.scala index 640f6a934004..3fc42abcce79 100644 --- a/tests/run/main-annotation-homemade-annot-3.scala +++ b/tests/run/main-annotation-homemade-annot-3.scala @@ -11,14 +11,13 @@ object Test: end Test @experimental -class mainNoArgs extends MainAnnotation: +class mainNoArgs extends MainAnnotation[FromString, Any]: import MainAnnotation.* - def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = - new Command[FromString, Any]: - override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - override def run(program: () => Any): Unit = program() - end command + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-4.scala b/tests/run/main-annotation-homemade-annot-4.scala index 602744398e74..0dbd006ee5b1 100644 --- a/tests/run/main-annotation-homemade-annot-4.scala +++ b/tests/run/main-annotation-homemade-annot-4.scala @@ -11,14 +11,14 @@ object Test: end Test @experimental -class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation: +class mainManyArgs(i1: Int, s2: String, i3: Int) extends MainAnnotation[FromString, Any]: import MainAnnotation.* - def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = - new Command[FromString, Any]: - override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T = ??? + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - override def run(program: () => Any): Unit = program() - end command + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-5.scala b/tests/run/main-annotation-homemade-annot-5.scala index e529ac304efe..d61cd55eb852 100644 --- a/tests/run/main-annotation-homemade-annot-5.scala +++ b/tests/run/main-annotation-homemade-annot-5.scala @@ -13,14 +13,13 @@ object Test: end Test @experimental -class mainManyArgs(o: Option[Int]) extends MainAnnotation: +class mainManyArgs(o: Option[Int]) extends MainAnnotation[FromString, Any]: import MainAnnotation.* - def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = - new Command[FromString, Any]: - override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? + def command(info: Info, args: Seq[String]): Option[Seq[String]] = Some(args) - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = ??? + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = ??? - override def run(program: () => Any): Unit = program() - end command + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = ??? + + def run(program: () => Any): Unit = program() diff --git a/tests/run/main-annotation-homemade-annot-6.check b/tests/run/main-annotation-homemade-annot-6.check index b9e33bf3e406..5cc6c07e1f56 100644 --- a/tests/run/main-annotation-homemade-annot-6.check +++ b/tests/run/main-annotation-homemade-annot-6.check @@ -1,27 +1,24 @@ command( - Array(), + Array(1, 2), foo, "Foo docs", Seq( - ParameterInfo(name="i", typeName="scala.Int", hasDefault=false, isVarargs=false, documentation="", annotations=List()), - ParameterInfo(name="j", typeName="java.lang.String", hasDefault=true, isVarargs=false, documentation="", annotations=List()) + Parameter(name="i", typeName="scala.Int", hasDefault=false, isVarargs=false, documentation="", annotations=List()), + Parameter(name="j", typeName="java.lang.String", hasDefault=true, isVarargs=false, documentation="", annotations=List()) )* ) -argGetter(0, None) -argGetter(1, Some(2)) run() foo(42, abc) command( - Array(), + Array(1, 2), bar, "Bar docs", Seq( - ParameterInfo(name="i", typeName="scala.collection.immutable.List[Int]", hasDefault=false, isVarargs=false, documentation="the first parameter", annotations=List(MyParamAnnot(3))), - ParameterInfo(name="rest", typeName="scala.Int", hasDefault=false, isVarargs=true, documentation="", annotations=List()) + Parameter(name="i", typeName="scala.collection.immutable.List[Int]", hasDefault=false, isVarargs=false, documentation="the first parameter", annotations=List(MyParamAnnot(3))), + Parameter(name="rest", typeName="scala.Int", hasDefault=false, isVarargs=true, documentation="", annotations=List()) )* ) -argGetter(0, None) varargGetter() run() bar(List(42), 42, 42) diff --git a/tests/run/main-annotation-homemade-annot-6.scala b/tests/run/main-annotation-homemade-annot-6.scala index 5d1c227d0c72..9ba0b31fc689 100644 --- a/tests/run/main-annotation-homemade-annot-6.scala +++ b/tests/run/main-annotation-homemade-annot-6.scala @@ -13,17 +13,17 @@ object Test: for (methodName <- List("foo", "bar")) val clazz = Class.forName(methodName) val method = clazz.getMethod("main", classOf[Array[String]]) - method.invoke(null, Array[String]()) + method.invoke(null, Array[String]("1", "2")) end Test @experimental -class myMain extends MainAnnotation: +class myMain extends MainAnnotation[Make, Any]: import MainAnnotation.* - def command(info: CommandInfo, args: Array[String]): Command[Make, Any] = - def paramInfoString(paramInfo: ParameterInfo) = + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + def paramInfoString(paramInfo: Parameter) = import paramInfo.* - s" ParameterInfo(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)" + s" Parameter(name=\"$name\", typeName=\"$typeName\", hasDefault=$hasDefault, isVarargs=$isVarargs, documentation=\"$documentation\", annotations=$annotations)" println( s"""command( | ${args.mkString("Array(", ", ", ")")}, @@ -31,20 +31,19 @@ class myMain extends MainAnnotation: | "${info.documentation}", | ${info.parameters.map(paramInfoString).mkString("Seq(\n", ",\n", "\n )*")} |)""".stripMargin) - new Command[Make, Any]: - override def argGetter[T](idx: Int, defaultArgument: Option[() => T])(using p: Make[T]): () => T = - println(s"argGetter($idx, ${defaultArgument.map(_())})") - () => p.make - - override def varargGetter[T](using p: Make[T]): () => Seq[T] = - println("varargGetter()") - () => Seq(p.make, p.make) - - override def run(f: () => Any): Unit = - println("run()") - f() - println() - end command + Some(args) + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: Make[T]): () => T = + () => p.make + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: Make[T]): () => Seq[T] = + println("varargGetter()") + () => Seq(p.make, p.make) + + def run(f: () => Any): Unit = + println("run()") + f() + println() @experimental case class MyParamAnnot(n: Int) extends MainAnnotation.ParameterAnnotation diff --git a/tests/run/main-annotation-newMain.scala b/tests/run/main-annotation-newMain.scala index c2a538443b24..9e85d5f948cc 100644 --- a/tests/run/main-annotation-newMain.scala +++ b/tests/run/main-annotation-newMain.scala @@ -30,275 +30,288 @@ end Test @experimental -final class newMain extends MainAnnotation: +final class newMain extends MainAnnotation[FromString, Any]: import newMain._ import MainAnnotation._ - def command(info: CommandInfo, args: Array[String]): Command[FromString, Any] = - new Command[FromString, Any]: - - private inline val argMarker = "--" - private inline val shortArgMarker = "-" - - /** - * The name of the special argument to display the method's help. - * If one of the method's parameters is called the same, will be ignored. - */ - private inline val helpArg = "help" - - /** - * The short name of the special argument to display the method's help. - * If one of the method's parameters uses the same short name, will be ignored. - */ - private inline val shortHelpArg = 'h' - private var shortHelpIsOverridden = false - - private inline val maxUsageLineLength = 120 - - /** A map from argument canonical name (the name of the parameter in the method definition) to parameter informations */ - private val nameToParameterInfo: Map[String, ParameterInfo] = info.parameters.map(infos => infos.name -> infos).toMap - - private val (positionalArgs, byNameArgs, invalidByNameArgs, helpIsOverridden) = { - val namesToCanonicalName: Map[String, String] = info.parameters.flatMap( - infos => - var names = getAlternativeNames(infos) - val canonicalName = infos.name - if nameIsValid(canonicalName) then names = canonicalName +: names - names.map(_ -> canonicalName) - ).toMap - val shortNamesToCanonicalName: Map[Char, String] = info.parameters.flatMap( - infos => - var names = getShortNames(infos) - val canonicalName = infos.name - if shortNameIsValid(canonicalName) then names = canonicalName(0) +: names - names.map(_ -> canonicalName) - ).toMap - - val helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) - shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) - - def getCanonicalArgName(arg: String): Option[String] = - if arg.startsWith(argMarker) && arg.length > argMarker.length then - namesToCanonicalName.get(arg.drop(argMarker.length)) - else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then - shortNamesToCanonicalName.get(arg(shortArgMarker.length)) - else - None - - def isArgName(arg: String): Boolean = - val isFullName = arg.startsWith(argMarker) - val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length)) - isFullName || isShortName - - def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = - remainingArgs match { - case Seq() => - (pa, bna, ia) - case argName +: argValue +: rest if isArgName(argName) => - getCanonicalArgName(argName) match { - case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) - case None => recurse(rest, pa, bna, ia :+ argName) - } - case arg +: rest => - recurse(rest, pa :+ arg, bna, ia) - } - - val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) - val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) - (pa, nameToArgValues, ia, helpIsOverridden) - } + private inline val argMarker = "--" + private inline val shortArgMarker = "-" - /** A buffer for all errors */ - private val errors = new mutable.ArrayBuffer[String] + /** The name of the special argument to display the method's help. + * If one of the method's parameters is called the same, will be ignored. + */ + private inline val helpArg = "help" - /** Issue an error, and return an uncallable getter */ - private def error(msg: String): () => Nothing = - errors += msg - () => throw new AssertionError("trying to get invalid argument") + /** The short name of the special argument to display the method's help. + * If one of the method's parameters uses the same short name, will be ignored. + */ + private inline val shortHelpArg = 'h' - private inline def nameIsValid(name: String): Boolean = - name.length > 1 // TODO add more checks for illegal characters + private inline val maxUsageLineLength = 120 - private inline def shortNameIsValid(name: String): Boolean = - name.length == 1 && shortNameIsValid(name(0)) + private var info: Info = _ // TODO remove this var - private inline def shortNameIsValid(shortName: Char): Boolean = - ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') - private def getNameWithMarker(name: String | Char): String = name match { - case c: Char => shortArgMarker + c - case s: String if shortNameIsValid(s) => shortArgMarker + s - case s => argMarker + s - } + /** A buffer for all errors */ + private val errors = new mutable.ArrayBuffer[String] - private def convert[T](argName: String, arg: String)(using p: FromString[T]): () => T = - p.fromStringOption(arg) match - case Some(t) => () => t - case None => error(s"invalid argument for $argName: $arg") - - private def usage(): Unit = - def argsUsage: Seq[String] = - for info <- info.parameters yield - val canonicalName = getNameWithMarker(info.name) - val shortNames = getShortNames(info).map(getNameWithMarker) - val alternativeNames = getAlternativeNames(info).map(getNameWithMarker) - val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") - if info.isVarargs then s"[<${info.typeName}> [<${info.typeName}> [...]]]" - else if info.hasDefault then s"[$namesPrint <${info.typeName}>]" - else s"$namesPrint <${info.typeName}>" - end for - - def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { - def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = - (args, currentLine) match { - case (Nil, "") => acc - case (Nil, l) => (acc :+ l) - case (arg +: t, "") => recurse(t, arg, acc) - case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) - case (arg +: t, l) => recurse(t, arg, acc :+ l) - } + /** Issue an error, and return an uncallable getter */ + private def error(msg: String): () => Nothing = + errors += msg + () => throw new AssertionError("trying to get invalid argument") - recurse(argsUsage, "", Vector()).toList - } + private def getAliases(param: Parameter): Seq[String] = + param.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) - val usageBeginning = s"Usage: ${info.name} " - val argsOffset = usageBeginning.length - val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) + private def getAlternativeNames(param: Parameter): Seq[String] = + getAliases(param).filter(nameIsValid(_)) - println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) - end usage + private def getShortNames(param: Parameter): Seq[Char] = + getAliases(param).filter(shortNameIsValid(_)).map(_(0)) - private def explain(): Unit = - inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + private inline def nameIsValid(name: String): Boolean = + name.length > 1 // TODO add more checks for illegal characters - def wrapLongLine(line: String, maxLength: Int): List[String] = { - def recurse(s: String, acc: Vector[String]): Seq[String] = - val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) - if ((s.length <= maxLength) || (lastSpace < 0)) - acc :+ s - else { - val (shortLine, rest) = s.splitAt(lastSpace) - recurse(rest.trim.nn, acc :+ shortLine) - } + private inline def shortNameIsValid(name: String): Boolean = + name.length == 1 && shortNameIsValidChar(name(0)) - recurse(line, Vector()).toList - } + private inline def shortNameIsValidChar(shortName: Char): Boolean = + ('A' <= shortName && shortName <= 'Z') || ('a' <= shortName && shortName <= 'z') - if (info.documentation.nonEmpty) - println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) - if (nameToParameterInfo.nonEmpty) { - val argNameShift = 2 - val argDocShift = argNameShift + 2 - - println("Arguments:") - for info <- info.parameters do - val canonicalName = getNameWithMarker(info.name) - val shortNames = getShortNames(info).map(getNameWithMarker) - val alternativeNames = getAlternativeNames(info).map(getNameWithMarker) - val otherNames = (alternativeNames ++: shortNames) match { - case Seq() => "" - case names => names.mkString("(", ", ", ") ") - } - val argDoc = StringBuilder(" " * argNameShift) - argDoc.append(s"$canonicalName $otherNames- ${info.typeName}") - - if info.isVarargs then argDoc.append(" (vararg)") - else if info.hasDefault then argDoc.append(" (optional)") - - val doc = info.documentation - if (doc.nonEmpty) { - val shiftedDoc = - doc.split("\n").nn - .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) - .mkString("\n") - argDoc.append("\n").append(shiftedDoc) - } + private def getNameWithMarker(name: String | Char): String = name match { + case c: Char => shortArgMarker + c + case s: String if shortNameIsValid(s) => shortArgMarker + s + case s => argMarker + s + } - println(argDoc) - end for - } - end explain + private def getInvalidNames(param: Parameter): Seq[String | Char] = + getAliases(param).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) - private def getAliases(paramInfos: ParameterInfo): Seq[String] = - paramInfos.annotations.collect{ case a: Alias => a }.flatMap(_.aliases) + def command(info: Info, args: Seq[String]): Option[Seq[String]] = + this.info = info - private def getAlternativeNames(paramInfos: ParameterInfo): Seq[String] = - getAliases(paramInfos).filter(nameIsValid(_)) + val namesToCanonicalName: Map[String, String] = info.parameters.flatMap( + infos => + val names = getAlternativeNames(infos) + val canonicalName = infos.name + if nameIsValid(canonicalName) then (canonicalName +: names).map(_ -> canonicalName) + else names.map(_ -> canonicalName) + ).toMap + val shortNamesToCanonicalName: Map[Char, String] = info.parameters.flatMap( + infos => + val names = getShortNames(infos) + val canonicalName = infos.name + if shortNameIsValid(canonicalName) then (canonicalName(0) +: names).map(_ -> canonicalName) + else names.map(_ -> canonicalName) + ).toMap - private def getShortNames(paramInfos: ParameterInfo): Seq[Char] = - getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0)) + val helpIsOverridden = namesToCanonicalName.exists((name, _) => name == helpArg) + val shortHelpIsOverridden = shortNamesToCanonicalName.exists((name, _) => name == shortHelpArg) - private def getInvalidNames(paramInfos: ParameterInfo): Seq[String | Char] = - getAliases(paramInfos).filter(name => !nameIsValid(name) && !shortNameIsValid(name)) + val (positionalArgs, byNameArgs, invalidByNameArgs) = { + def getCanonicalArgName(arg: String): Option[String] = + if arg.startsWith(argMarker) && arg.length > argMarker.length then + namesToCanonicalName.get(arg.drop(argMarker.length)) + else if arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 then + shortNamesToCanonicalName.get(arg(shortArgMarker.length)) + else + None + + def isArgName(arg: String): Boolean = + val isFullName = arg.startsWith(argMarker) + val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValidChar(arg(shortArgMarker.length)) + isFullName || isShortName + + def recurse(remainingArgs: Seq[String], pa: mutable.Queue[String], bna: Seq[(String, String)], ia: Seq[String]): (mutable.Queue[String], Seq[(String, String)], Seq[String]) = + remainingArgs match { + case Seq() => + (pa, bna, ia) + case argName +: argValue +: rest if isArgName(argName) => + getCanonicalArgName(argName) match { + case Some(canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue), ia) + case None => recurse(rest, pa, bna, ia :+ argName) + } + case arg +: rest => + recurse(rest, pa :+ arg, bna, ia) + } - override def argGetter[T](idx: Int, optDefaultGetter: Option[() => T])(using p: FromString[T]): () => T = - val name = info.parameters(idx).name - val parameterInfo = nameToParameterInfo(name) - // TODO: Decide which string is associated with this arg when constructing the command. - // Here we should only get the string for this argument, apply it to the parser and handle parsing errors. - // Should be able to get the argument from its index. - byNameArgs.get(name) match { + val (pa, bna, ia) = recurse(args.toSeq, mutable.Queue.empty, Vector(), Vector()) + val nameToArgValues: Map[String, Seq[String]] = if bna.isEmpty then Map.empty else bna.groupMapReduce(_._1)(p => List(p._2))(_ ++ _) + (pa, nameToArgValues, ia) + } + + val argStrings: Seq[Seq[String]] = + for paramInfo <- info.parameters yield { + if (paramInfo.isVarargs) { + val byNameGetters = byNameArgs.getOrElse(paramInfo.name, Seq()) + val positionalGetters = positionalArgs.removeAll() + // First take arguments passed by name, then those passed by position + byNameGetters ++ positionalGetters + } else { + byNameArgs.get(paramInfo.name) match case Some(Nil) => - throw AssertionError(s"$name present in byNameArgs, but it has no argument value") + throw AssertionError(s"${paramInfo.name} present in byNameArgs, but it has no argument value") case Some(argValues) => if argValues.length > 1 then // Do not accept multiple values // Remove this test to take last given argument - error(s"more than one value for $name: ${argValues.mkString(", ")}") + error(s"more than one value for ${paramInfo.name}: ${argValues.mkString(", ")}") + Nil else - convert(name, argValues.last) + List(argValues.last) case None => if positionalArgs.length > 0 then - convert(name, positionalArgs.dequeue) - else if optDefaultGetter.nonEmpty then - optDefaultGetter.get + List(positionalArgs.dequeue()) + else if paramInfo.hasDefault then + Nil else - error(s"missing argument for $name") + error(s"missing argument for ${paramInfo.name}") + Nil } - end argGetter - - override def varargGetter[T](using p: FromString[T]): () => Seq[T] = - val name = info.parameters.last.name - // TODO: Decide which strings are associated with the varargs when constructing the command. - // Here we should only get the strings for this argument, apply them to the parser and handle parsing errors. - // Should be able to get the argument from its index (last). - val byNameGetters = byNameArgs.getOrElse(name, Seq()).map(arg => convert(name, arg)) - val positionalGetters = positionalArgs.removeAll.map(arg => convert(name, arg)) - // First take arguments passed by name, then those passed by position - () => (byNameGetters ++ positionalGetters).map(_()) - - override def run(f: () => Any): Unit = - // Check aliases unicity - val nameAndCanonicalName = nameToParameterInfo.toList.flatMap { - case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName) + } + + // Check aliases unicity + val nameAndCanonicalName = info.parameters.flatMap { + case paramInfo => (paramInfo.name +: getAlternativeNames(paramInfo) ++: getShortNames(paramInfo)).map(_ -> paramInfo.name) + } + val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) + + for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 do + throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + + // Check aliases validity + val problematicNames = info.parameters.flatMap(getInvalidNames) + if problematicNames.length > 0 then + throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") + + // Handle unused and invalid args + for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") + for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") + + val displayHelp = + (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || + (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) + + if displayHelp then + usage() + println() + explain() + None + else if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + None + else + Some(argStrings.flatten) + end command + + private def usage(): Unit = + def argsUsage: Seq[String] = + for (infos <- info.parameters) + yield { + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]") + val shortTypeName = infos.typeName.split('.').last + if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]" + else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]" + else s"$namesPrint <$shortTypeName>" + } + + def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = { + def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] = + (args, currentLine) match { + case (Nil, "") => acc + case (Nil, l) => (acc :+ l) + case (arg +: t, "") => recurse(t, arg, acc) + case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc) + case (arg +: t, l) => recurse(t, arg, acc :+ l) } - val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2) - for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 - do throw IllegalArgumentException(s"$name is used for multiple parameters: ${canonicalNames.mkString(", ")}") + recurse(argsUsage, "", Vector()).toList + } - // Check aliases validity - val problematicNames = nameToParameterInfo.toList.flatMap((_, infos) => getInvalidNames(infos)) - if problematicNames.length > 0 then throw IllegalArgumentException(s"The following aliases are invalid: ${problematicNames.mkString(", ")}") + val usageBeginning = s"Usage: ${info.name} " + val argsOffset = usageBeginning.length + val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset) - // Handle unused and invalid args - for (remainingArg <- positionalArgs) error(s"unused argument: $remainingArg") - for (invalidArg <- invalidByNameArgs) error(s"unknown argument name: $invalidArg") + println(usageBeginning + usages.mkString("\n" + " " * argsOffset)) + end usage - val displayHelp = - (!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) || (!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg))) + private def explain(): Unit = + inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n") + + def wrapLongLine(line: String, maxLength: Int): List[String] = { + def recurse(s: String, acc: Vector[String]): Seq[String] = + val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength) + if ((s.length <= maxLength) || (lastSpace < 0)) + acc :+ s + else { + val (shortLine, rest) = s.splitAt(lastSpace) + recurse(rest.trim.nn, acc :+ shortLine) + } + + recurse(line, Vector()).toList + } + + if (info.documentation.nonEmpty) + println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n")) + if (info.parameters.nonEmpty) { + val argNameShift = 2 + val argDocShift = argNameShift + 2 + + println("Arguments:") + for infos <- info.parameters do + val canonicalName = getNameWithMarker(infos.name) + val shortNames = getShortNames(infos).map(getNameWithMarker) + val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker) + val otherNames = (alternativeNames ++: shortNames) match { + case Seq() => "" + case names => names.mkString("(", ", ", ") ") + } + val argDoc = StringBuilder(" " * argNameShift) + argDoc.append(s"$canonicalName $otherNames- ${infos.typeName.split('.').last}") + if infos.isVarargs then argDoc.append(" (vararg)") + else if infos.hasDefault then argDoc.append(" (optional)") + + if (infos.documentation.nonEmpty) { + val shiftedDoc = + infos.documentation.split("\n").nn + .map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift)) + .mkString("\n") + argDoc.append("\n").append(shiftedDoc) + } + + println(argDoc) + } + end explain + + private def convert[T](argName: String, arg: String, p: FromString[T]): () => T = + p.fromStringOption(arg) match + case Some(t) => () => t + case None => error(s"invalid argument for $argName: $arg") + + def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = { + if arg.nonEmpty then convert(param.name, arg, p) + else defaultArgument match + case Some(defaultGetter) => defaultGetter + case None => error(s"missing argument for ${param.name}") + } + + def varargGetter[T](param: Parameter, args: Seq[String])(using p: FromString[T]): () => Seq[T] = { + val getters = args.map(arg => convert(param.name, arg, p)) + () => getters.map(_()) + } + + def run(execProgram: () => Any): Unit = { + if errors.nonEmpty then + for msg <- errors do println(s"Error: $msg") + usage() + else + execProgram() + } - if displayHelp then - usage() - println() - explain() - else if errors.nonEmpty then - for msg <- errors do println(s"Error: $msg") - usage() - else - f() - end run - end command end newMain object newMain: From 70baa8bff072f510d3a222be104db18fb36c73a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 14 Apr 2022 11:45:24 +0200 Subject: [PATCH 067/121] Set reference version to 3.1.3-RC2 --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index f3e8957fb258..249c37efd98d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -57,7 +57,7 @@ object DottyJSPlugin extends AutoPlugin { object Build { import ScaladocConfigs._ - val referenceVersion = "3.1.2" + val referenceVersion = "3.1.3-RC2" val baseVersion = "3.2.0-RC1" From 8e880bd057585ef3f257d77fe0082ab7d1be673e Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 23 Dec 2021 12:56:18 +0100 Subject: [PATCH 068/121] Allow return in tailrec position --- .../src/dotty/tools/dotc/transform/TailRec.scala | 2 +- tests/run/tailrec-return.check | 2 ++ tests/run/tailrec-return.scala | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/run/tailrec-return.check create mode 100644 tests/run/tailrec-return.scala diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index 2aee452070f9..a44b445d7169 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -456,7 +456,7 @@ class TailRec extends MiniPhase { case Return(expr, from) => val fromSym = from.symbol - val inTailPosition = fromSym.is(Label) && tailPositionLabeledSyms.contains(fromSym) + val inTailPosition = !fromSym.is(Label) || tailPositionLabeledSyms.contains(fromSym) cpy.Return(tree)(transform(expr, inTailPosition), from) case _ => diff --git a/tests/run/tailrec-return.check b/tests/run/tailrec-return.check new file mode 100644 index 000000000000..6b81a566d1a7 --- /dev/null +++ b/tests/run/tailrec-return.check @@ -0,0 +1,2 @@ +6 +false diff --git a/tests/run/tailrec-return.scala b/tests/run/tailrec-return.scala new file mode 100644 index 000000000000..53b5ae73b82d --- /dev/null +++ b/tests/run/tailrec-return.scala @@ -0,0 +1,16 @@ +object Test: + + @annotation.tailrec + def sum(n: Int, acc: Int = 0): Int = + if n != 0 then return sum(n - 1, acc + n) + acc + + @annotation.tailrec + def isEven(n: Int): Boolean = + if n != 0 && n != 1 then return isEven(n - 2) + if n == 1 then return false + true + + def main(args: Array[String]): Unit = + println(sum(3)) + println(isEven(5)) From 675eaaf0dc39c9eb7bd337bc16f11c4485d5481b Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 29 Dec 2021 16:27:00 +0100 Subject: [PATCH 069/121] Visit all trees --- .../dotty/tools/dotc/transform/TailRec.scala | 16 +----- tests/run/tailrec-return.check | 5 ++ tests/run/tailrec-return.scala | 50 +++++++++++++++++++ 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TailRec.scala b/compiler/src/dotty/tools/dotc/transform/TailRec.scala index a44b445d7169..71b66c3d0da6 100644 --- a/compiler/src/dotty/tools/dotc/transform/TailRec.scala +++ b/compiler/src/dotty/tools/dotc/transform/TailRec.scala @@ -286,23 +286,11 @@ class TailRec extends MiniPhase { def yesTailTransform(tree: Tree)(using Context): Tree = transform(tree, tailPosition = true) - /** If not in tail position a tree traversal may not be needed. - * - * A recursive call may still be in tail position if within the return - * expression of a labeled block. - * A tree traversal may also be needed to report a failure to transform - * a recursive call of a @tailrec annotated method (i.e. `isMandatory`). - */ - private def isTraversalNeeded = - isMandatory || tailPositionLabeledSyms.size > 0 - def noTailTransform(tree: Tree)(using Context): Tree = - if (isTraversalNeeded) transform(tree, tailPosition = false) - else tree + transform(tree, tailPosition = false) def noTailTransforms[Tr <: Tree](trees: List[Tr])(using Context): List[Tr] = - if (isTraversalNeeded) trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] - else trees + trees.mapConserve(noTailTransform).asInstanceOf[List[Tr]] override def transform(tree: Tree)(using Context): Tree = { /* Rewrite an Apply to be considered for tail call transformation. */ diff --git a/tests/run/tailrec-return.check b/tests/run/tailrec-return.check index 6b81a566d1a7..361e76d8a285 100644 --- a/tests/run/tailrec-return.check +++ b/tests/run/tailrec-return.check @@ -1,2 +1,7 @@ 6 false +true +false +true +Ada Lovelace, Alan Turing +List(9, 10) diff --git a/tests/run/tailrec-return.scala b/tests/run/tailrec-return.scala index 53b5ae73b82d..aa760960403d 100644 --- a/tests/run/tailrec-return.scala +++ b/tests/run/tailrec-return.scala @@ -11,6 +11,56 @@ object Test: if n == 1 then return false true + @annotation.tailrec + def isEvenApply(n: Int): Boolean = + // Return inside an `Apply.fun` + ( + if n != 0 && n != 1 then return isEvenApply(n - 2) + else if n == 1 then return false + else (x: Boolean) => x + )(true) + + @annotation.tailrec + def isEvenWhile(n: Int): Boolean = + // Return inside a `WhileDo.cond` + while( + if n != 0 && n != 1 then return isEvenWhile(n - 2) + else if n == 1 then return false + else true + ) {} + true + + @annotation.tailrec + def isEvenReturn(n: Int): Boolean = + // Return inside a `Return` + return + if n != 0 && n != 1 then return isEvenReturn(n - 2) + else if n == 1 then return false + else true + + @annotation.tailrec + def names(l: List[(String, String) | Null], acc: List[String] = Nil): List[String] = + l match + case Nil => acc.reverse + case x :: xs => + if x == null then return names(xs, acc) + + val displayName = x._1 + " " + x._2 + names(xs, displayName :: acc) + + def nonTail(l: List[Int]): List[Int] = + l match + case Nil => Nil + case x :: xs => + // The call to nonTail should *not* be eliminated + (x + 1) :: nonTail(xs) + + def main(args: Array[String]): Unit = println(sum(3)) println(isEven(5)) + println(isEvenApply(6)) + println(isEvenWhile(7)) + println(isEvenReturn(8)) + println(names(List(("Ada", "Lovelace"), null, ("Alan", "Turing"))).mkString(", ")) + println(nonTail(List(8, 9))) From 0529bf8805f4d41c88349450050426f5bd855924 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Thu, 14 Apr 2022 16:43:03 +0200 Subject: [PATCH 070/121] Fix coverage tests on windows --- .../dotty/tools/dotc/coverage/CoverageTests.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 84e07003cf0d..945a0e65b9ec 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -14,6 +14,7 @@ import java.nio.file.{Files, FileSystems, Path, Paths, StandardCopyOption} import scala.jdk.CollectionConverters.* import scala.util.Properties.userDir import scala.language.unsafeNulls +import scala.collection.mutable.Buffer @Category(Array(classOf[BootstrappedOnlyTests])) class CoverageTests: @@ -31,6 +32,15 @@ class CoverageTests: checkCoverageIn(rootSrc.resolve("run"), true) def checkCoverageIn(dir: Path, run: Boolean)(using TestGroup): Unit = + /** Converts \ to / on windows, to make the tests pass without changing the serialization. */ + def fixWindowsPaths(lines: Buffer[String]): Buffer[String] = + val separator = java.io.File.separatorChar + if separator != '/' then + lines.map(_.replace("\\" + separator, "/")) // escape the separator + else + lines + end fixWindowsPaths + Files.walk(dir).filter(scalaFile.matches).forEach(p => { val path = p val fileName = path.getFileName.toString.stripSuffix(".scala") @@ -41,8 +51,8 @@ class CoverageTests: if updateCheckFiles then Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) else - val expected = Files.readAllLines(expectFile).asScala - val obtained = Files.readAllLines(targetFile).asScala + val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) + val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) if expected != obtained then for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do Console.err.println(s"wrong line ${i+1}:") From 80157b19a7953d18f93862fb2e25f6012c6b91d9 Mon Sep 17 00:00:00 2001 From: Vadim Chelyshov Date: Thu, 14 Apr 2022 17:54:04 +0300 Subject: [PATCH 071/121] Better error messages in case of `TooLarge*` exceptions in GenBCode Just a bit more informative messages for `MethodTooLargeException` and `ClassTooLargeException` instead of unhandled error with large stacktrace. --- .../dotty/tools/backend/jvm/GenBCode.scala | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index f00c54e584f9..65fe7971d458 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -33,6 +33,8 @@ import scala.tools.asm.tree._ import tpd._ import StdNames._ import dotty.tools.io._ +import scala.tools.asm.MethodTooLargeException +import scala.tools.asm.ClassTooLargeException class GenBCode extends Phase { @@ -512,7 +514,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim * (c) tear down (closing the classfile-writer and clearing maps) * */ - def run(t: Tree): Unit = { + def run(t: Tree)(using Context): Unit = { this.tree = t // val bcodeStart = Statistics.startTimer(BackendStats.bcodeTimer) @@ -558,18 +560,29 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim * (c) dequeue one at a time from queue-2, convert it to byte-array, place in queue-3 * (d) serialize to disk by draining queue-3. */ - private def buildAndSendToDisk(needsOutFolder: Boolean) = { - - feedPipeline1() - // val genStart = Statistics.startTimer(BackendStats.bcodeGenStat) - (new Worker1(needsOutFolder)).run() - // Statistics.stopTimer(BackendStats.bcodeGenStat, genStart) - - (new Worker2).run() - - // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) - drainQ3() - // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) + private def buildAndSendToDisk(needsOutFolder: Boolean)(using Context) = { + try + feedPipeline1() + // val genStart = Statistics.startTimer(BackendStats.bcodeGenStat) + (new Worker1(needsOutFolder)).run() + // Statistics.stopTimer(BackendStats.bcodeGenStat, genStart) + + (new Worker2).run() + + // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) + drainQ3() + // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) + catch + case e: MethodTooLargeException => + val method = + s"${e.getClassName.replaceAll("/", ".")}.${e.getMethodName}" + val msg = + s"Generated bytecode for method '$method' is too large. Size: ${e.getCodeSize} bytes. Limit is 64KB" + report.error(msg) + case e: ClassTooLargeException => + val msg = + s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64K" + report.error(msg) } From 19ae93ba82f4bf4758bab3c2a9f9721898be7c1b Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Apr 2022 19:42:48 +0200 Subject: [PATCH 072/121] Drop serializabilty tests in parallel collections CB --- community-build/community-projects/scala-parallel-collections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-parallel-collections b/community-build/community-projects/scala-parallel-collections index b26e74437f0c..c43c0a2523f6 160000 --- a/community-build/community-projects/scala-parallel-collections +++ b/community-build/community-projects/scala-parallel-collections @@ -1 +1 @@ -Subproject commit b26e74437f0cadc3c92e9378e69197f3494b1811 +Subproject commit c43c0a2523f62551f476f1b901168c6c876f1e4c From 077652ce304e0ceedc5a07b0d0ca6c92ba5f60a0 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Apr 2022 22:03:19 +0200 Subject: [PATCH 073/121] Fix correct test file in parallel collections CB --- community-build/community-projects/scala-parallel-collections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-parallel-collections b/community-build/community-projects/scala-parallel-collections index c43c0a2523f6..a6bd648bb188 160000 --- a/community-build/community-projects/scala-parallel-collections +++ b/community-build/community-projects/scala-parallel-collections @@ -1 +1 @@ -Subproject commit c43c0a2523f62551f476f1b901168c6c876f1e4c +Subproject commit a6bd648bb188a65ab36be07e956e52fe25f64d67 From f7f23b7439f841d87a393abcc3e275634f06a30f Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Fri, 15 Apr 2022 12:47:19 +0900 Subject: [PATCH 074/121] Update compiler message links. use docs.scala-lang.org --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 8 ++++---- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala | 2 +- tests/neg-custom-args/fatal-warnings/enum-variance.check | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8aaaff52708d..5db706197245 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -2003,7 +2003,7 @@ class Definitions { add(MatchableClass, """/** The base trait of types that can be safely pattern matched against. | * - | * See [[https://dotty.epfl.ch/docs/reference/other-new-features/matchable.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/other-new-features/matchable.html]]. | */ """.stripMargin) @@ -2112,21 +2112,21 @@ class Definitions { add(AnyKindClass, """/** The super-type of all types. | * - | * See [[https://dotty.epfl.ch/docs/reference/other-new-features/kind-polymorphism.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/other-new-features/kind-polymorphism.html]]. | */ """.stripMargin) add(andType, """/** The intersection of two types. | * - | * See [[https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/new-types/intersection-types.html]]. | */ """.stripMargin) add(orType, """/** The union of two types. | * - | * See [[https://dotty.epfl.ch/docs/reference/new-types/union-types.html]]. + | * See [[https://docs.scala-lang.org/scala3/reference/new-types/union-types.html]]. | */ """.stripMargin) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d915a35b88b4..21fc7d9f482a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3674,14 +3674,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree.symbol != defn.StringContext_f && tree.symbol != defn.StringContext_s) if (ctx.settings.XignoreScala2Macros.value) { - report.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html", tree.srcPos.startPos) + report.warning("Scala 2 macro cannot be used in Dotty, this call will crash at runtime. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html", tree.srcPos.startPos) Throw(New(defn.MatchErrorClass.typeRef, Literal(Constant(s"Reached unexpanded Scala 2 macro call to ${tree.symbol.showFullName} compiled with -Xignore-scala2-macros.")) :: Nil)) .withType(tree.tpe) .withSpan(tree.span) } else { report.error( - """Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html + """Scala 2 macro cannot be used in Dotty. See https://docs.scala-lang.org/scala3/reference/dropped-features/macros.html |To turn this error into a warning, pass -Xignore-scala2-macros to the compiler""".stripMargin, tree.srcPos.startPos) tree } diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 88212c3bb96c..ca114fe84ccf 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -171,7 +171,7 @@ class VarianceChecker(using Context) { val towner = tvar.owner if towner.isAllOf(EnumCase) && towner.isClass && tvar.is(Synthetic) then val example = - "See an example at http://dotty.epfl.ch/docs/reference/enums/adts.html#parameter-variance-of-enums" + "See an example at https://docs.scala-lang.org/scala3/reference/enums/adts.html#parameter-variance-of-enums" i"\n${hl("enum case")} ${towner.name} requires explicit declaration of $tvar to resolve this issue.\n$example" else "" diff --git a/tests/neg-custom-args/fatal-warnings/enum-variance.check b/tests/neg-custom-args/fatal-warnings/enum-variance.check index 22b5065f8ea5..b525d4d94ba6 100644 --- a/tests/neg-custom-args/fatal-warnings/enum-variance.check +++ b/tests/neg-custom-args/fatal-warnings/enum-variance.check @@ -1,9 +1,9 @@ -- Error: tests/neg-custom-args/fatal-warnings/enum-variance.scala:2:12 ------------------------------------------------ 2 | case Refl(f: T => T) // error: enum case Refl requires explicit declaration of type T | ^^^^^^^^^ - | contravariant type T occurs in covariant position in type T => T of value f - | enum case Refl requires explicit declaration of type T to resolve this issue. - | See an example at http://dotty.epfl.ch/docs/reference/enums/adts.html#parameter-variance-of-enums + | contravariant type T occurs in covariant position in type T => T of value f + | enum case Refl requires explicit declaration of type T to resolve this issue. + | See an example at https://docs.scala-lang.org/scala3/reference/enums/adts.html#parameter-variance-of-enums -- Error: tests/neg-custom-args/fatal-warnings/enum-variance.scala:5:16 ------------------------------------------------ 5 | case Refl[-T](f: T => T) extends ExplicitView[T] // error: contravariant type T occurs in covariant position | ^^^^^^^^^ From f9a573dfd8f43c0d7b900b7157c7aaa13c454039 Mon Sep 17 00:00:00 2001 From: Guillaume Raffin Date: Fri, 15 Apr 2022 11:18:46 +0200 Subject: [PATCH 075/121] Fix windows fix for coverage tests --- compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 945a0e65b9ec..6f978bc131d8 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -36,7 +36,7 @@ class CoverageTests: def fixWindowsPaths(lines: Buffer[String]): Buffer[String] = val separator = java.io.File.separatorChar if separator != '/' then - lines.map(_.replace("\\" + separator, "/")) // escape the separator + lines.map(_.replace(separator, '/')) else lines end fixWindowsPaths From 0ab1bb639312f4046151c5eb9a8a2686ed3bf7bd Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Apr 2022 11:21:04 +0200 Subject: [PATCH 076/121] Apply suggestions from code review --- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 15cfc656f222..1cff4dfe4b8b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1557,11 +1557,11 @@ object TreeUnpickler { class TreeWithoutOwner extends Exception /** An attachment key indicating that an old-style leading () in a constructor - * call that has otherwise only using clauses was suppressed. + * call that is followed by a using clause was suppressed. */ val SuppressedApplyToNone: Property.Key[Unit] = Property.Key() - /** An attachment key indicating that an trailing () in a constructor + /** An attachment key indicating that a trailing () in a constructor * call that has otherwise only using clauses was inserted. */ val InsertedApplyToNone: Property.Key[Unit] = Property.Key() From d82dfcc0f57b7693b185e68f1457f70cc4b25133 Mon Sep 17 00:00:00 2001 From: Tom Grigg Date: Mon, 4 Apr 2022 12:07:56 -0700 Subject: [PATCH 077/121] fix vulpix rewrite testing previously, the test run would complete with success despite the rewrite test itself failing. --- compiler/test/dotty/tools/vulpix/ParallelTesting.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala index cf051e5f01ad..5a57ede4ceb5 100644 --- a/compiler/test/dotty/tools/vulpix/ParallelTesting.scala +++ b/compiler/test/dotty/tools/vulpix/ParallelTesting.scala @@ -1064,7 +1064,13 @@ trait ParallelTesting extends RunnerOrchestration { self => target.copy(dir = copyToDir(outDir, dir)) } - new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + val test = new RewriteTest(copiedTargets, checkFileMap, times, threadLimit, shouldFail || shouldSuppressOutput).executeTestSuite() + + cleanup() + + if test.didFail then + fail("Rewrite test failed") + this } From b481090fb9ce833fffb8304b34dbc752f674bd40 Mon Sep 17 00:00:00 2001 From: Tom Grigg Date: Wed, 30 Mar 2022 18:29:01 -0700 Subject: [PATCH 078/121] fix rewrites for filtering for generators the test added here used to fail because the `case` was patched inside the parens, e.g. `for (case x: String) ...` which is not valid --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- compiler/test/dotty/tools/dotc/CompilationTests.scala | 1 + tests/rewrites/filtering-fors.check | 6 ++++++ tests/rewrites/filtering-fors.scala | 6 ++++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/rewrites/filtering-fors.check create mode 100644 tests/rewrites/filtering-fors.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6c1767f6ef83..c83bb569c7bb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1603,7 +1603,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration`) if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked") - else patch(Span(pat.span.start), "case ") + else patch(Span(tree.span.start), "case ") // skip exhaustivity check in later phase // TODO: move the check above to patternMatcher phase diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6b2af4c1ff85..5bafac7686e5 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -73,6 +73,7 @@ class CompilationTests { aggregateTests( compileFile("tests/rewrites/rewrites.scala", scala2CompatMode.and("-rewrite", "-indent")), compileFile("tests/rewrites/rewrites3x.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), + compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")), diff --git a/tests/rewrites/filtering-fors.check b/tests/rewrites/filtering-fors.check new file mode 100644 index 000000000000..f6cac26d8629 --- /dev/null +++ b/tests/rewrites/filtering-fors.check @@ -0,0 +1,6 @@ +val xs: List[Any] = ??? +val as = for case (x: String) <- xs yield x +val bs = + for + case (x: String) <- xs + yield x diff --git a/tests/rewrites/filtering-fors.scala b/tests/rewrites/filtering-fors.scala new file mode 100644 index 000000000000..e3b312186a14 --- /dev/null +++ b/tests/rewrites/filtering-fors.scala @@ -0,0 +1,6 @@ +val xs: List[Any] = ??? +val as = for (x: String) <- xs yield x +val bs = + for + (x: String) <- xs + yield x From c818e0a158a3ce97a444c983db78f5c094fe9294 Mon Sep 17 00:00:00 2001 From: Tom Grigg Date: Thu, 31 Mar 2022 11:36:27 -0700 Subject: [PATCH 079/121] fix rewrites for refutable pattern bindings needing `@unchecked` over compound expressions --- .../src/dotty/tools/dotc/typer/Typer.scala | 24 +++++++++++++++--- .../dotty/tools/dotc/CompilationTests.scala | 1 + .../rewrites/refutable-pattern-bindings.check | 25 +++++++++++++++++++ .../rewrites/refutable-pattern-bindings.scala | 25 +++++++++++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/rewrites/refutable-pattern-bindings.check create mode 100644 tests/rewrites/refutable-pattern-bindings.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c83bb569c7bb..9ac83c570f11 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1596,14 +1596,32 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typedMatchFinish(tree, sel1, selType, tree.cases, pt) } + /** Are some form of brackets necessary to annotate the tree `sel` as `@unchecked`? + * If so, return a Some(opening bracket, closing bracket), otherwise None. + */ + def uncheckedBrackets(sel: untpd.Tree): Option[(String, String)] = sel match + case _: untpd.If + | _: untpd.Match + | _: untpd.ForYield + | _: untpd.ParsedTry + | _: untpd.Try => Some("(", ")") + case _: untpd.Block => Some("{", "}") + case _ => None + result match { case result @ Match(sel, CaseDef(pat, _, _) :: _) => tree.selector.removeAttachment(desugar.CheckIrrefutable) match { case Some(checkMode) if !sel.tpe.hasAnnotation(defn.UncheckedAnnot) => val isPatDef = checkMode == desugar.MatchCheck.IrrefutablePatDef - if (!checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration`) - if (isPatDef) patch(Span(tree.selector.span.end), ": @unchecked") - else patch(Span(tree.span.start), "case ") + if !checkIrrefutable(sel, pat, isPatDef) && sourceVersion == `future-migration` then + if isPatDef then uncheckedBrackets(tree.selector) match + case None => + patch(Span(tree.selector.span.end), ": @unchecked") + case Some(bl, br) => + patch(Span(tree.selector.span.start), s"$bl") + patch(Span(tree.selector.span.end), s"$br: @unchecked") + else + patch(Span(tree.span.start), "case ") // skip exhaustivity check in later phase // TODO: move the check above to patternMatcher phase diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 5bafac7686e5..1fb14ec1d858 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -74,6 +74,7 @@ class CompilationTests { compileFile("tests/rewrites/rewrites.scala", scala2CompatMode.and("-rewrite", "-indent")), compileFile("tests/rewrites/rewrites3x.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/filtering-fors.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), + compileFile("tests/rewrites/refutable-pattern-bindings.scala", defaultOptions.and("-rewrite", "-source", "future-migration")), compileFile("tests/rewrites/i8982.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i9632.scala", defaultOptions.and("-indent", "-rewrite")), compileFile("tests/rewrites/i11895.scala", defaultOptions.and("-indent", "-rewrite")), diff --git a/tests/rewrites/refutable-pattern-bindings.check b/tests/rewrites/refutable-pattern-bindings.check new file mode 100644 index 000000000000..5acea5dc0ead --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings.check @@ -0,0 +1,25 @@ +val xs: List[Any] = ??? + +val hd :: tl = (xs match + case Nil => null :: xs + case _ => xs): @unchecked + +val h :: t = xs: @unchecked + +val a :: b = + (if xs.isEmpty then null :: xs + else xs): @unchecked + +val c :: d = + (try xs.head :: xs + catch case _: NoSuchElementException => null :: xs): @unchecked + +val e :: f = + {val zero = null :: Nil + if xs.isEmpty then zero + else xs}: @unchecked + +val j :: k = + (for + case (x: String) <- xs + yield x): @unchecked diff --git a/tests/rewrites/refutable-pattern-bindings.scala b/tests/rewrites/refutable-pattern-bindings.scala new file mode 100644 index 000000000000..e724c9564bd2 --- /dev/null +++ b/tests/rewrites/refutable-pattern-bindings.scala @@ -0,0 +1,25 @@ +val xs: List[Any] = ??? + +val hd :: tl = xs match + case Nil => null :: xs + case _ => xs + +val h :: t = xs + +val a :: b = + if xs.isEmpty then null :: xs + else xs + +val c :: d = + try xs.head :: xs + catch case _: NoSuchElementException => null :: xs + +val e :: f = + val zero = null :: Nil + if xs.isEmpty then zero + else xs + +val j :: k = + for + (x: String) <- xs + yield x From d2a5fc77765242d3887d1cb688612089836e2f05 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 10:29:43 +0200 Subject: [PATCH 080/121] Accept soft modifiers separated by blank lines This was handled inconsistently before. An identifier could be classified as a soft modifier even if followed by newlines but then the modifier parsing code would not skip the NEWLINES token. Now it does skip. --- .../dotty/tools/dotc/parsing/Parsers.scala | 2 +- tests/pos/i11712.scala | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i11712.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8fab2e4074b9..30a6d94726d9 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2873,7 +2873,7 @@ object Parsers { val isAccessMod = accessModifierTokens contains in.token val mods1 = addModifier(mods) loop(if (isAccessMod) accessQualifierOpt(mods1) else mods1) - else if (in.token == NEWLINE && (mods.hasFlags || mods.hasAnnotations)) { + else if (in.isNewLine && (mods.hasFlags || mods.hasAnnotations)) { in.nextToken() loop(mods) } diff --git a/tests/pos/i11712.scala b/tests/pos/i11712.scala new file mode 100644 index 000000000000..27fc1ef2771e --- /dev/null +++ b/tests/pos/i11712.scala @@ -0,0 +1,21 @@ +object Test: + + def transparent = println("transparent method called") + + transparent + println() + inline def f1 = 1 + + transparent + inline def f2 = 2 + + transparent + trait T1 + + transparent + + inline def f3 = 3 + + transparent + + trait T2 \ No newline at end of file From 97493c7c052a78ff8eeb905be7544e1f085f01bd Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 10:46:43 +0200 Subject: [PATCH 081/121] Revert "Highlight all EBNF snippets. Add missing semicolons." This reverts commit a60c4e714243b9361615facd62469c9a440d16ab. --- .../reference/changed-features/imports.md | 14 ++++----- .../changed-features/match-syntax.md | 8 ++--- .../changed-features/pattern-bindings.md | 4 +-- .../changed-features/structural-types-spec.md | 10 +++---- .../changed-features/vararg-splices.md | 6 ++-- .../reference/contextual/context-bounds.md | 6 ++-- .../contextual/context-functions-spec.md | 6 ++-- docs/_docs/reference/contextual/derivation.md | 10 +++---- .../reference/contextual/extension-methods.md | 18 +++++------ .../reference/contextual/given-imports.md | 16 +++++----- docs/_docs/reference/contextual/givens.md | 10 +++---- .../reference/contextual/using-clauses.md | 12 ++++---- docs/_docs/reference/enums/adts.md | 14 ++++----- .../experimental/named-typeargs-spec.md | 8 ++--- .../reference/metaprogramming/macros-spec.md | 30 +++++++++---------- .../reference/metaprogramming/simple-smp.md | 10 +++---- .../dependent-function-types-spec.md | 6 ++-- .../new-types/intersection-types-spec.md | 6 ++-- .../reference/new-types/type-lambdas-spec.md | 10 +++---- .../reference/other-new-features/export.md | 18 +++++------ .../indentation-experimental.md | 8 ++--- .../other-new-features/indentation.md | 22 +++++++------- .../other-new-features/opaques-details.md | 4 +-- 23 files changed, 128 insertions(+), 128 deletions(-) diff --git a/docs/_docs/reference/changed-features/imports.md b/docs/_docs/reference/changed-features/imports.md index df9657115822..7d364ce68b92 100644 --- a/docs/_docs/reference/changed-features/imports.md +++ b/docs/_docs/reference/changed-features/imports.md @@ -46,14 +46,14 @@ are offered under settings `-source 3.1-migration -rewrite`. ### Syntax -```ebnf -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; +``` +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` diff --git a/docs/_docs/reference/changed-features/match-syntax.md b/docs/_docs/reference/changed-features/match-syntax.md index bd39fc39b033..cff77262611b 100644 --- a/docs/_docs/reference/changed-features/match-syntax.md +++ b/docs/_docs/reference/changed-features/match-syntax.md @@ -47,10 +47,10 @@ The syntactical precedence of match expressions has been changed. The new syntax of match expressions is as follows. -```ebnf +``` InfixExpr ::= ... - | InfixExpr MatchClause ; + | InfixExpr MatchClause SimpleExpr ::= ... - | SimpleExpr ‘.’ MatchClause ; -MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ ; + | SimpleExpr ‘.’ MatchClause +MatchClause ::= ‘match’ ‘{’ CaseClauses ‘}’ ``` diff --git a/docs/_docs/reference/changed-features/pattern-bindings.md b/docs/_docs/reference/changed-features/pattern-bindings.md index 7d84f97cc4c3..2c8d1c10ceae 100644 --- a/docs/_docs/reference/changed-features/pattern-bindings.md +++ b/docs/_docs/reference/changed-features/pattern-bindings.md @@ -50,8 +50,8 @@ for case (x, y) <- elems yield (y, x) // returns List((2, 1), (4, 3)) ## Syntax Changes Generators in for expressions may be prefixed with `case`. -```ebnf -Generator ::= [‘case’] Pattern1 ‘<-’ Expr ; +``` +Generator ::= [‘case’] Pattern1 ‘<-’ Expr ``` ## Migration diff --git a/docs/_docs/reference/changed-features/structural-types-spec.md b/docs/_docs/reference/changed-features/structural-types-spec.md index 63cf258888fa..290189a67eda 100644 --- a/docs/_docs/reference/changed-features/structural-types-spec.md +++ b/docs/_docs/reference/changed-features/structural-types-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/changed-features/structura ## Syntax -```ebnf -SimpleType ::= ... | Refinement ; -Refinement ::= ‘{’ RefineStatSeq ‘}’ ; -RefineStatSeq ::= RefineStat {semi RefineStat} ; -RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl ; +``` +SimpleType ::= ... | Refinement +Refinement ::= ‘{’ RefineStatSeq ‘}’ +RefineStatSeq ::= RefineStat {semi RefineStat} +RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl ``` ## Implementation of Structural Types diff --git a/docs/_docs/reference/changed-features/vararg-splices.md b/docs/_docs/reference/changed-features/vararg-splices.md index d74962cec3ff..1d8b61408176 100644 --- a/docs/_docs/reference/changed-features/vararg-splices.md +++ b/docs/_docs/reference/changed-features/vararg-splices.md @@ -24,12 +24,12 @@ The old syntax for splice arguments will be phased out. ## Syntax -```ebnf +``` ArgumentPatterns ::= ‘(’ [Patterns] ‘)’ - | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’ ; + | ‘(’ [Patterns ‘,’] Pattern2 ‘*’ ‘)’ ParArgumentExprs ::= ‘(’ [‘using’] ExprsInParens ‘)’ - | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ; + | ‘(’ [ExprsInParens ‘,’] PostfixExpr ‘*’ ‘)’ ``` ## Compatibility considerations diff --git a/docs/_docs/reference/contextual/context-bounds.md b/docs/_docs/reference/contextual/context-bounds.md index 756f0c3a36bf..e336f00cc463 100644 --- a/docs/_docs/reference/contextual/context-bounds.md +++ b/docs/_docs/reference/contextual/context-bounds.md @@ -40,7 +40,7 @@ done automatically under `-rewrite`. ## Syntax -```ebnf -TypeParamBounds ::= [SubtypeBounds] {ContextBound} ; -ContextBound ::= ‘:’ Type ; +``` +TypeParamBounds ::= [SubtypeBounds] {ContextBound} +ContextBound ::= ‘:’ Type ``` diff --git a/docs/_docs/reference/contextual/context-functions-spec.md b/docs/_docs/reference/contextual/context-functions-spec.md index 1a5b43691b63..3c61e0e0c6ca 100644 --- a/docs/_docs/reference/contextual/context-functions-spec.md +++ b/docs/_docs/reference/contextual/context-functions-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/contextual/context-functio ## Syntax -```ebnf +``` Type ::= ... - | FunArgTypes ‘?=>’ Type ; + | FunArgTypes ‘?=>’ Type Expr ::= ... - | FunParams ‘?=>’ Expr ; + | FunParams ‘?=>’ Expr ``` Context function types associate to the right, e.g. diff --git a/docs/_docs/reference/contextual/derivation.md b/docs/_docs/reference/contextual/derivation.md index df1bcddcf80c..972ac945a22d 100644 --- a/docs/_docs/reference/contextual/derivation.md +++ b/docs/_docs/reference/contextual/derivation.md @@ -354,12 +354,12 @@ hand side of this definition in the same way as an instance defined in ADT compa ### Syntax -```ebnf -Template ::= InheritClauses [TemplateBody] ; -EnumDef ::= id ClassConstr InheritClauses EnumBody ; -InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ; +``` +Template ::= InheritClauses [TemplateBody] +EnumDef ::= id ClassConstr InheritClauses EnumBody +InheritClauses ::= [‘extends’ ConstrApps] [‘derives’ QualId {‘,’ QualId}] ConstrApps ::= ConstrApp {‘with’ ConstrApp} - | ConstrApp {‘,’ ConstrApp} ; + | ConstrApp {‘,’ ConstrApp} ``` **Note:** To align `extends` clauses and `derives` clauses, Scala 3 also allows multiple diff --git a/docs/_docs/reference/contextual/extension-methods.md b/docs/_docs/reference/contextual/extension-methods.md index 3d2dc4109ca5..c2a12081cf99 100644 --- a/docs/_docs/reference/contextual/extension-methods.md +++ b/docs/_docs/reference/contextual/extension-methods.md @@ -285,20 +285,20 @@ def position(s: String)(ch: Char, n: Int): Int = Here are the syntax changes for extension methods and collective extensions relative to the [current syntax](../syntax.md). -```ebnf -BlockStat ::= ... | Extension ; -TemplateStat ::= ... | Extension ; -TopStat ::= ... | Extension ; +``` +BlockStat ::= ... | Extension +TemplateStat ::= ... | Extension +TopStat ::= ... | Extension Extension ::= ‘extension’ [DefTypeParamClause] {UsingParamClause} - ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods ; -ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> ; -ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ; + ‘(’ DefParam ‘)’ {UsingParamClause} ExtMethods +ExtMethods ::= ExtMethod | [nl] <<< ExtMethod {semi ExtMethod} >>> +ExtMethod ::= {Annotation [nl]} {Modifier} ‘def’ DefDef ``` In the above the notation `<<< ts >>>` in the production rule `ExtMethods` is defined as follows : -```ebnf -<<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent ; +``` +<<< ts >>> ::= ‘{’ ts ‘}’ | indent ts outdent ``` `extension` is a soft keyword. It is recognized as a keyword only if it appears diff --git a/docs/_docs/reference/contextual/given-imports.md b/docs/_docs/reference/contextual/given-imports.md index cd49eda49d1b..3e3bc3d275cc 100644 --- a/docs/_docs/reference/contextual/given-imports.md +++ b/docs/_docs/reference/contextual/given-imports.md @@ -103,15 +103,15 @@ given instances once their user base has migrated. ### Syntax -```ebnf -Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; +``` +Import ::= ‘import’ ImportExpr {‘,’ ImportExpr} +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*' | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*' | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` diff --git a/docs/_docs/reference/contextual/givens.md b/docs/_docs/reference/contextual/givens.md index 9c440d56ca1e..1d0baae9e257 100644 --- a/docs/_docs/reference/contextual/givens.md +++ b/docs/_docs/reference/contextual/givens.md @@ -173,14 +173,14 @@ is created for each reference. Here is the syntax for given instances: -```ebnf +``` TmplDef ::= ... - | ‘given’ GivenDef ; + | ‘given’ GivenDef GivenDef ::= [GivenSig] StructuralInstance | [GivenSig] AnnotType ‘=’ Expr - | [GivenSig] AnnotType ; -GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ ; -StructuralInstance ::= ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody ; + | [GivenSig] AnnotType +GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘:’ +StructuralInstance ::= ConstrApp {‘with’ ConstrApp} ‘with’ TemplateBody ``` A given instance starts with the reserved word `given` and an optional _signature_. The signature diff --git a/docs/_docs/reference/contextual/using-clauses.md b/docs/_docs/reference/contextual/using-clauses.md index 31caf9dbd1d1..8c522d82c402 100644 --- a/docs/_docs/reference/contextual/using-clauses.md +++ b/docs/_docs/reference/contextual/using-clauses.md @@ -144,10 +144,10 @@ def summon[T](using x: T): x.type = x Here is the new syntax of parameters and arguments seen as a delta from the [standard context free syntax of Scala 3](../syntax.md). `using` is a soft keyword, recognized only at the start of a parameter or argument list. It can be used as a normal identifier everywhere else. -```ebnf -ClsParamClause ::= ... | UsingClsParamClause ; -DefParamClauses ::= ... | UsingParamClause ; -UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ ; -UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ ; -ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ ; +``` +ClsParamClause ::= ... | UsingClsParamClause +DefParamClauses ::= ... | UsingParamClause +UsingClsParamClause ::= ‘(’ ‘using’ (ClsParams | Types) ‘)’ +UsingParamClause ::= ‘(’ ‘using’ (DefParams | Types) ‘)’ +ParArgumentExprs ::= ... | ‘(’ ‘using’ ExprsInParens ‘)’ ``` diff --git a/docs/_docs/reference/enums/adts.md b/docs/_docs/reference/enums/adts.md index fa2aa2ebd113..6fc5be78d7b8 100644 --- a/docs/_docs/reference/enums/adts.md +++ b/docs/_docs/reference/enums/adts.md @@ -154,18 +154,18 @@ The changes are specified below as deltas with respect to the Scala syntax given 1. Enum definitions are defined as follows: - ```ebnf - TmplDef ::= `enum' EnumDef ; - EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody ; - EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ ; + ``` + TmplDef ::= `enum' EnumDef + EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody + EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ EnumStat ::= TemplateStat - | {Annotation [nl]} {Modifier} EnumCase ; + | {Annotation [nl]} {Modifier} EnumCase ``` 2. Cases of enums are defined as follows: - ```ebnf - EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) ; + ``` + EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids) ``` ### Reference diff --git a/docs/_docs/reference/experimental/named-typeargs-spec.md b/docs/_docs/reference/experimental/named-typeargs-spec.md index 9ec9fd200ed2..7bf5c2f43ccf 100644 --- a/docs/_docs/reference/experimental/named-typeargs-spec.md +++ b/docs/_docs/reference/experimental/named-typeargs-spec.md @@ -10,11 +10,11 @@ In this section we give more details about the [named type arguments](named-type The addition to the grammar is: -```ebnf +``` SimpleExpr1 ::= ... - | SimpleExpr (TypeArgs | NamedTypeArgs) ; -NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ ; -NamedTypeArg ::= id ‘=’ Type ; + | SimpleExpr (TypeArgs | NamedTypeArgs) +NamedTypeArgs ::= ‘[’ NamedTypeArg {‘,’ NamedTypeArg} ‘]’ +NamedTypeArg ::= id ‘=’ Type ``` Note in particular that named arguments cannot be passed to type constructors: diff --git a/docs/_docs/reference/metaprogramming/macros-spec.md b/docs/_docs/reference/metaprogramming/macros-spec.md index 91cd36173ede..d152377e6d62 100644 --- a/docs/_docs/reference/metaprogramming/macros-spec.md +++ b/docs/_docs/reference/metaprogramming/macros-spec.md @@ -10,13 +10,13 @@ movedTo: https://docs.scala-lang.org/scala3/reference/metaprogramming/macros-spe Compared to the [Scala 3 reference grammar](../syntax.md) there are the following syntax changes: -```ebnf +``` SimpleExpr ::= ... | ‘'’ ‘{’ Block ‘}’ | ‘'’ ‘[’ Type ‘]’ - | ‘$’ ‘{’ Block ‘}’ ; + | ‘$’ ‘{’ Block ‘}’ SimpleType ::= ... - | ‘$’ ‘{’ Block ‘}’ ; + | ‘$’ ‘{’ Block ‘}’ ``` In addition, an identifier `$x` starting with a `$` that appears inside a quoted expression or type is treated as a splice `${x}` and a quoted identifier @@ -57,36 +57,36 @@ extends simply-typed lambda calculus with quotes and splices. ### Syntax The syntax of terms, values, and types is given as follows: -```ebnf +``` Terms t ::= x variable (x: T) => t lambda t t application 't quote - $t splice ; + $t splice Values v ::= (x: T) => t lambda - 'u quote ; + 'u quote -Simple terms u ::= x | (x: T) => u | u u | 't ; +Simple terms u ::= x | (x: T) => u | u u | 't Types T ::= A base type T -> T function type - expr T quoted ; + expr T quoted ``` Typing rules are formulated using a stack of environments `Es`. Individual environments `E` consist as usual of variable bindings `x: T`. Environments can be combined using the two combinators `'` and `$`. -```ebnf +``` Environment E ::= () empty - E, x: T ; + E, x: T Env. stack Es ::= () empty E simple - Es * Es combined ; + Es * Es combined Separator * ::= ' - $ ; + $ ``` The two environment combinators are both associative with left and right identity `()`. @@ -108,9 +108,9 @@ rule says that splice and quotes cancel each other out. The third rule is a context rule; it says that reduction is allowed in the hole `[ ]` position of an evaluation context. Evaluation contexts `e` and splice evaluation context `e_s` are defined syntactically as follows: -```ebnf -Eval context e ::= [ ] | e t | v e | 'e_s[${e}] ; -Splice context e_s ::= [ ] | (x: T) => e_s | e_s t | u e_s ; +``` +Eval context e ::= [ ] | e t | v e | 'e_s[${e}] +Splice context e_s ::= [ ] | (x: T) => e_s | e_s t | u e_s ``` ### Typing rules diff --git a/docs/_docs/reference/metaprogramming/simple-smp.md b/docs/_docs/reference/metaprogramming/simple-smp.md index 89529bac1480..78277987c874 100644 --- a/docs/_docs/reference/metaprogramming/simple-smp.md +++ b/docs/_docs/reference/metaprogramming/simple-smp.md @@ -23,21 +23,21 @@ replace evaluation contexts with contextual typing rules. While this is more verbose, it makes it easier to set up the meta theory. ## Syntax -```ebnf +``` Terms t ::= x variable (x: T) => t lambda t t application ’t quote - ~t splice ; + ~t splice -Simple terms u ::= x | (x: T) => u | u u ; +Simple terms u ::= x | (x: T) => u | u u Values v ::= (x: T) => t lambda - ’u quoted value ; + ’u quoted value Types T ::= A base type T -> T function type - ’T quoted type ; + ’T quoted type ``` ## Operational semantics diff --git a/docs/_docs/reference/new-types/dependent-function-types-spec.md b/docs/_docs/reference/new-types/dependent-function-types-spec.md index 8fb0d03dc13c..c3915ba9b8ac 100644 --- a/docs/_docs/reference/new-types/dependent-function-types-spec.md +++ b/docs/_docs/reference/new-types/dependent-function-types-spec.md @@ -8,11 +8,11 @@ Initial implementation in [PR #3464](https://github.com/lampepfl/dotty/pull/3464 ## Syntax -```ebnf +``` FunArgTypes ::= InfixType | ‘(’ [ FunArgType {',' FunArgType } ] ‘)’ - | ‘(’ TypedFunParam {',' TypedFunParam } ‘)’ ; -TypedFunParam ::= id ‘:’ Type ; + | ‘(’ TypedFunParam {',' TypedFunParam } ‘)’ +TypedFunParam ::= id ‘:’ Type ``` Dependent function types associate to the right, e.g. diff --git a/docs/_docs/reference/new-types/intersection-types-spec.md b/docs/_docs/reference/new-types/intersection-types-spec.md index 6a6e579f4bae..8ed9305e1486 100644 --- a/docs/_docs/reference/new-types/intersection-types-spec.md +++ b/docs/_docs/reference/new-types/intersection-types-spec.md @@ -12,9 +12,9 @@ with the usual precedence and subject to usual resolving rules. Unless shadowed by another definition, it resolves to the type `scala.&`, which acts as a type alias to an internal representation of intersection types. -```ebnf -Type ::= ...| InfixType ; -InfixType ::= RefinedType {id [nl] RefinedType} ; +``` +Type ::= ...| InfixType +InfixType ::= RefinedType {id [nl] RefinedType} ``` ## Subtyping Rules diff --git a/docs/_docs/reference/new-types/type-lambdas-spec.md b/docs/_docs/reference/new-types/type-lambdas-spec.md index 5f52956a4d4e..5c791ba40272 100644 --- a/docs/_docs/reference/new-types/type-lambdas-spec.md +++ b/docs/_docs/reference/new-types/type-lambdas-spec.md @@ -6,11 +6,11 @@ movedTo: https://docs.scala-lang.org/scala3/reference/new-types/type-lambdas-spe ## Syntax -```ebnf -Type ::= ... | TypeParamClause ‘=>>’ Type ; -TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’ ; -TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds ; -TypeBounds ::= [‘>:’ Type] [‘<:’ Type] ; +``` +Type ::= ... | TypeParamClause ‘=>>’ Type +TypeParamClause ::= ‘[’ TypeParam {‘,’ TypeParam} ‘]’ +TypeParam ::= {Annotation} (id [HkTypeParamClause] | ‘_’) TypeBounds +TypeBounds ::= [‘>:’ Type] [‘<:’ Type] ``` ### Type Checking diff --git a/docs/_docs/reference/other-new-features/export.md b/docs/_docs/reference/other-new-features/export.md index 89e054605bb7..85f03de4104e 100644 --- a/docs/_docs/reference/other-new-features/export.md +++ b/docs/_docs/reference/other-new-features/export.md @@ -134,20 +134,20 @@ more flexible way. ## Syntax changes: -```ebnf +``` TemplateStat ::= ... - | Export ; + | Export TopStat ::= ... - | Export ; -Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} ; -ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ; + | Export +Export ::= ‘export’ ImportExpr {‘,’ ImportExpr} +ImportExpr ::= SimpleRef {‘.’ id} ‘.’ ImportSpec ImportSpec ::= NamedSelector | WildcardSelector - | ‘{’ ImportSelectors) ‘}’ ; -NamedSelector ::= id [‘as’ (id | ‘_’)] ; -WildCardSelector ::= ‘*’ | ‘given’ [InfixType] ; + | ‘{’ ImportSelectors) ‘}’ +NamedSelector ::= id [‘as’ (id | ‘_’)] +WildCardSelector ::= ‘*’ | ‘given’ [InfixType] ImportSelectors ::= NamedSelector [‘,’ ImportSelectors] - | WildCardSelector {‘,’ WildCardSelector} ; + | WildCardSelector {‘,’ WildCardSelector} ``` ## Elaboration of Export Clauses diff --git a/docs/_docs/reference/other-new-features/indentation-experimental.md b/docs/_docs/reference/other-new-features/indentation-experimental.md index 073c10e2f92c..cbdd41eeaf12 100644 --- a/docs/_docs/reference/other-new-features/indentation-experimental.md +++ b/docs/_docs/reference/other-new-features/indentation-experimental.md @@ -62,13 +62,13 @@ Braces can be omitted if the lambda starts with a parameter list and `=>` or `=> ## Syntax Changes -```ebnf +``` SimpleExpr ::= ... | SimpleExpr `:` IndentedArgument - | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument ; + | SimpleExpr FunParams (‘=>’ | ‘?=>’) IndentedArgument InfixExpr ::= ... - | InfixExpr id `:` IndentedArgument ; -IndentedArgument ::= indent (CaseClauses | Block) outdent ; + | InfixExpr id `:` IndentedArgument +IndentedArgument ::= indent (CaseClauses | Block) outdent ``` Note that a lambda argument must have the `=>` at the end of a line for braces diff --git a/docs/_docs/reference/other-new-features/indentation.md b/docs/_docs/reference/other-new-features/indentation.md index e886cb916757..46bc21e2597d 100644 --- a/docs/_docs/reference/other-new-features/indentation.md +++ b/docs/_docs/reference/other-new-features/indentation.md @@ -170,11 +170,11 @@ In each case, the `:` at the end of line can be replaced without change of meani The syntax changes allowing this are as follows: -```ebnf -Template ::= InheritClauses [colonEol] [TemplateBody] ; -EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody ; -Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ ; -SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] ; +``` +Template ::= InheritClauses [colonEol] [TemplateBody] +EnumDef ::= id ClassConstr InheritClauses [colonEol] EnumBody +Packaging ::= ‘package’ QualId [nl | colonEol] ‘{’ TopStatSeq ‘}’ +SimpleExpr ::= ‘new’ ConstrApp {‘with’ ConstrApp} [[colonEol] TemplateBody] ``` Here, `colonEol` stands for ": at end of line", as described above. @@ -372,13 +372,13 @@ If none of these criteria apply, it's often better to not use an end marker sinc #### Syntax -```ebnf -EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL ; +``` +EndMarker ::= ‘end’ EndMarkerTag -- when followed by EOL EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ | ‘try’ - | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ ; -BlockStat ::= ... | EndMarker ; -TemplateStat ::= ... | EndMarker ; -TopStat ::= ... | EndMarker ; + | ‘new’ | ‘this’ | ‘given’ | ‘extension’ | ‘val’ +BlockStat ::= ... | EndMarker +TemplateStat ::= ... | EndMarker +TopStat ::= ... | EndMarker ``` ### Example diff --git a/docs/_docs/reference/other-new-features/opaques-details.md b/docs/_docs/reference/other-new-features/opaques-details.md index 9508f517710c..83608ca78dd3 100644 --- a/docs/_docs/reference/other-new-features/opaques-details.md +++ b/docs/_docs/reference/other-new-features/opaques-details.md @@ -6,9 +6,9 @@ movedTo: https://docs.scala-lang.org/scala3/reference/other-new-features/opaques ## Syntax -```ebnf +``` Modifier ::= ... - | ‘opaque’ ; + | ‘opaque’ ``` `opaque` is a [soft modifier](../soft-modifier.md). It can still be used as a normal identifier when it is not in front of a definition keyword. From eb1e71f518ea35acffeef627a503de5c6bf697e7 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 11:13:09 +0200 Subject: [PATCH 082/121] Fix syntax in Expr.scala ``` class Expr private object ``` now means the `private` modifier is for the following `object`, not the (invisible) constructor of `Expr`. --- library/src/scala/quoted/Expr.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index bf7922239314..996fe3ff8da2 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -4,7 +4,7 @@ package scala.quoted * * `Expr` has extension methods that are defined in `scala.quoted.Quotes`. */ -abstract class Expr[+T] private[scala] +abstract class Expr[+T] private[scala] () /** Constructors for expressions */ object Expr { From 42e036374e8ca687a4619cbb8549a79fbc46a665 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 12:50:24 +0200 Subject: [PATCH 083/121] Treat all incomplete ifs as statements Previously only a one-armed if was treated as a statement where all parts were typed with Unit as expected type. We now extend that treatment also to multi-branch ifs that lack a final part. Fixes #14914 --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 ++++++++++--- tests/pos/i14914.scala | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tests/pos/i14914.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6c1767f6ef83..191d324d99d0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1128,15 +1128,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if tree.isInline then checkInInlineContext("inline if", tree.srcPos) val cond1 = typed(tree.cond, defn.BooleanType) + def isIncomplete(tree: untpd.If): Boolean = tree.elsep match + case EmptyTree => true + case elsep: untpd.If => isIncomplete(elsep) + case _ => false + + val branchPt = if isIncomplete(tree) then defn.UnitType else pt.dropIfProto + val result = if tree.elsep.isEmpty then - val thenp1 = typed(tree.thenp, defn.UnitType)(using cond1.nullableContextIf(true)) + val thenp1 = typed(tree.thenp, branchPt)(using cond1.nullableContextIf(true)) val elsep1 = tpd.unitLiteral.withSpan(tree.span.endPos) cpy.If(tree)(cond1, thenp1, elsep1).withType(defn.UnitType) else val thenp1 :: elsep1 :: Nil = harmonic(harmonize, pt) { - val thenp0 = typed(tree.thenp, pt.dropIfProto)(using cond1.nullableContextIf(true)) - val elsep0 = typed(tree.elsep, pt.dropIfProto)(using cond1.nullableContextIf(false)) + val thenp0 = typed(tree.thenp, branchPt)(using cond1.nullableContextIf(true)) + val elsep0 = typed(tree.elsep, branchPt)(using cond1.nullableContextIf(false)) thenp0 :: elsep0 :: Nil } assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1) diff --git a/tests/pos/i14914.scala b/tests/pos/i14914.scala new file mode 100644 index 000000000000..3208a5cf8a02 --- /dev/null +++ b/tests/pos/i14914.scala @@ -0,0 +1,8 @@ + +def Test(b: Boolean) = + val a = + if b then + 1 + else if !b then + 2 + val _: Unit = a From 2df52b954143e74f382276d9f9cca3102b4ceb29 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 13:15:34 +0200 Subject: [PATCH 084/121] Fix outer reference detection in class parents The case where a class parent was an AppliedType was missing. Hence, classes that referred to an outer this only via a parent were mis-categorized. --- .../src/dotty/tools/dotc/transform/ExplicitOuter.scala | 3 ++- tests/pos/i14932.scala | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i14932.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala index a7f8421f51d0..0a9798226d76 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala @@ -278,7 +278,8 @@ object ExplicitOuter { ) case _ => false } - def hasOuterPrefix(tp: Type) = tp match { + def hasOuterPrefix(tp: Type): Boolean = tp.stripped match { + case AppliedType(tycon, _) => hasOuterPrefix(tycon) case TypeRef(prefix, _) => isOuterRef(prefix) case _ => false } diff --git a/tests/pos/i14932.scala b/tests/pos/i14932.scala new file mode 100644 index 000000000000..8fff627fc3d7 --- /dev/null +++ b/tests/pos/i14932.scala @@ -0,0 +1,9 @@ +trait Core { + class Base[T]() +} + +class Module(val core: Core) { + object Indirection { + class Extension[T]() extends core.Base[T]() + } +} \ No newline at end of file From aae0719c1763ac46b1c4448b1bf58cdea8d9c5ca Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 13:22:39 +0200 Subject: [PATCH 085/121] Fix nonsensical code in SemanticDB --- .../dotc/semanticdb/internal/SemanticdbInputStream.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala b/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala index 99a4da63128a..8aed9e5b9771 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/internal/SemanticdbInputStream.scala @@ -143,8 +143,7 @@ class SemanticdbInputStream private (buffer: Array[Byte], input: InputStream) { throw new IllegalStateException( s"refillBuffer() called when $n bytes were already available in buffer") } - if (totalBytesRetired + bufferPos + n > currentLimit) false - else if (input != null) { + if totalBytesRetired + bufferPos + n <= currentLimit && input != null then val pos: Int = bufferPos if (pos > 0) { if (bufferSize > pos) { @@ -166,7 +165,6 @@ class SemanticdbInputStream private (buffer: Array[Byte], input: InputStream) { recomputeBufferSizeAfterLimit() return ((bufferSize >= n) || tryRefillBuffer(n)) } - } false } From 1cf9a8ae83e29d2ac8f1d11a08cebd85a6a321fe Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 14:37:05 +0200 Subject: [PATCH 086/121] Honor language imports mode when unpickling --- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 11 ++++++++++- tests/pos/i14947.scala | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i14947.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index dd9f234e0365..cfdc62567ebf 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1060,7 +1060,16 @@ class TreeUnpickler(reader: TastyReader, Nil def readIndexedStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = - until(end)(readIndexedStat(exprOwner)) + val buf = new mutable.ListBuffer[Tree] + var curCtx = ctx + while currentAddr.index < end.index do + val stat = readIndexedStat(exprOwner)(using curCtx) + buf += stat + stat match + case stat: Import => curCtx = ctx.importContext(stat, stat.symbol) + case _ => + assert(currentAddr.index == end.index) + buf.toList def readStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = { fork.indexStats(end) diff --git a/tests/pos/i14947.scala b/tests/pos/i14947.scala new file mode 100644 index 000000000000..eead3657d4bc --- /dev/null +++ b/tests/pos/i14947.scala @@ -0,0 +1,7 @@ +import scala.language.unsafeNulls +class C { + def g: String | Null = ??? + + def f = + if ??? then "" else g +} \ No newline at end of file From fb5f38098b540a721425d61ca9fbcebc885238b8 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 15:05:07 +0200 Subject: [PATCH 087/121] Adapt infos of export default getters to qualifier Fixes #14953 --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 ++- tests/pos/i14953.scala | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i14953.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aac0288aa771..2b85d442e959 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1185,7 +1185,8 @@ class Namer { typer: Typer => buf += ddef.withSpan(span) if hasDefaults then foreachDefaultGetterOf(sym.asTerm, - getter => addForwarder(getter.name.asTermName, getter, span)) + getter => addForwarder( + getter.name.asTermName, getter.asSeenFrom(path.tpe), span)) end addForwarder def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = diff --git a/tests/pos/i14953.scala b/tests/pos/i14953.scala new file mode 100644 index 000000000000..6448f74cd4ab --- /dev/null +++ b/tests/pos/i14953.scala @@ -0,0 +1,8 @@ +object Test{ + + val xs: Seq[String] = List("Apple", "Orange", "Pear") + export xs.* + +} + +val _ = Test.head From 92b1c5ae0fb80d0a65174ef6def041047ca9d015 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 18:45:33 +0200 Subject: [PATCH 088/121] Fix test --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 1 + tests/{pos => explicit-nulls/pos-special}/i14947.scala | 0 2 files changed, 1 insertion(+) rename tests/{pos => explicit-nulls/pos-special}/i14947.scala (100%) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6b2af4c1ff85..5ff62a0d97d1 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -249,6 +249,7 @@ class CompilationTests { compileFilesInDir("tests/explicit-nulls/pos-patmat", explicitNullsOptions and "-Xfatal-warnings"), compileFilesInDir("tests/explicit-nulls/unsafe-common", explicitNullsOptions and "-language:unsafeNulls"), compileFile("tests/explicit-nulls/pos-special/i14682.scala", explicitNullsOptions and "-Ysafe-init"), + compileFile("tests/explicit-nulls/pos-special/i14947.scala", explicitNullsOptions and "-Ytest-pickler" and "-Xprint-types"), ) }.checkCompile() diff --git a/tests/pos/i14947.scala b/tests/explicit-nulls/pos-special/i14947.scala similarity index 100% rename from tests/pos/i14947.scala rename to tests/explicit-nulls/pos-special/i14947.scala From 857ed965f9ad2734605f01ca17ab977a932fda3b Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 18 Apr 2022 21:45:05 +0200 Subject: [PATCH 089/121] Print double definitions signatures at Typer Fixes #14966 --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 10 ++++++---- tests/neg/i14966.check | 10 ++++++++++ tests/neg/i14966.scala | 2 ++ 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i14966.check create mode 100644 tests/neg/i14966.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 18e2d234452d..36246faaea22 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2174,10 +2174,12 @@ import transform.SymUtils._ else "Name clash between inherited members" - em"""$clashDescription: - |${previousDecl.showDcl} ${symLocation(previousDecl)} and - |${decl.showDcl} ${symLocation(decl)} - |""" + details + atPhase(typerPhase) { + em"""$clashDescription: + |${previousDecl.showDcl} ${symLocation(previousDecl)} and + |${decl.showDcl} ${symLocation(decl)} + |""" + } + details } def explain = "" } diff --git a/tests/neg/i14966.check b/tests/neg/i14966.check new file mode 100644 index 000000000000..2706d54d64c8 --- /dev/null +++ b/tests/neg/i14966.check @@ -0,0 +1,10 @@ +-- [E120] Naming Error: tests/neg/i14966.scala:2:11 -------------------------------------------------------------------- +2 | export s.* // error + | ^ + | Double definition: + | final def concat[B >: T](suffix: IterableOnce[B]): Set[B] in class B at line 2 and + | final def concat(that: IterableOnce[T]): Set[T] in class B at line 2 + | have the same type after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/i14966.scala b/tests/neg/i14966.scala new file mode 100644 index 000000000000..f8869e9bbac0 --- /dev/null +++ b/tests/neg/i14966.scala @@ -0,0 +1,2 @@ +class B[T](val s: Set[T]): + export s.* // error From b34964bba316f9b16f5b19e34d6d3ca0692bc752 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Apr 2022 09:30:11 +0200 Subject: [PATCH 090/121] Also propagate language imports into block expressions --- .../tools/dotc/core/tasty/TreeUnpickler.scala | 15 ++++++++------- tests/explicit-nulls/pos-special/i14947.scala | 9 ++++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cfdc62567ebf..8ba0d8a8a60b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1059,7 +1059,7 @@ class TreeUnpickler(reader: TastyReader, else Nil - def readIndexedStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = + def readIndexedStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = val buf = new mutable.ListBuffer[Tree] var curCtx = ctx while currentAddr.index < end.index do @@ -1069,13 +1069,15 @@ class TreeUnpickler(reader: TastyReader, case stat: Import => curCtx = ctx.importContext(stat, stat.symbol) case _ => assert(currentAddr.index == end.index) - buf.toList + k(buf.toList, curCtx) - def readStats(exprOwner: Symbol, end: Addr)(using Context): List[Tree] = { + def readStats[T](exprOwner: Symbol, end: Addr, k: (List[Tree], Context) => T = sameTrees)(using Context): T = { fork.indexStats(end) - readIndexedStats(exprOwner, end) + readIndexedStats(exprOwner, end, k) } + private def sameTrees(xs: List[Tree], ctx: Context) = xs + def readIndexedParams[T <: MemberDef](tag: Int)(using Context): List[T] = collectWhile(nextByte == tag) { readIndexedDef().asInstanceOf[T] } @@ -1183,9 +1185,8 @@ class TreeUnpickler(reader: TastyReader, case BLOCK => val exprReader = fork skipTree() - val stats = readStats(ctx.owner, end) - val expr = exprReader.readTerm() - Block(stats, expr) + readStats(ctx.owner, end, + (stats, ctx) => Block(stats, exprReader.readTerm()(using ctx))) case INLINED => val exprReader = fork skipTree() diff --git a/tests/explicit-nulls/pos-special/i14947.scala b/tests/explicit-nulls/pos-special/i14947.scala index eead3657d4bc..b8f013f325b1 100644 --- a/tests/explicit-nulls/pos-special/i14947.scala +++ b/tests/explicit-nulls/pos-special/i14947.scala @@ -1,7 +1,14 @@ +class B: + def g: String | Null = ??? + + def f = + import scala.language.unsafeNulls + if ??? then "" else g + import scala.language.unsafeNulls class C { def g: String | Null = ??? def f = if ??? then "" else g -} \ No newline at end of file +} From 3c6425b9e66f0409f2d41acf3852c8f1165c203e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 19 Apr 2022 09:35:48 +0200 Subject: [PATCH 091/121] Fix check file Is this a stability problem? It seems the CI visits Seq definitions in different order than local. --- tests/neg/i14966.check | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/neg/i14966.check b/tests/neg/i14966.check index 2706d54d64c8..6cc8132f17bb 100644 --- a/tests/neg/i14966.check +++ b/tests/neg/i14966.check @@ -2,8 +2,8 @@ 2 | export s.* // error | ^ | Double definition: - | final def concat[B >: T](suffix: IterableOnce[B]): Set[B] in class B at line 2 and - | final def concat(that: IterableOnce[T]): Set[T] in class B at line 2 + | final def ++[B >: T](suffix: IterableOnce[B]): Set[B] in class B at line 2 and + | final def ++(that: IterableOnce[T]): Set[T] in class B at line 2 | have the same type after erasure. | | Consider adding a @targetName annotation to one of the conflicting definitions From 4f9ef984ac3085c17a91c035d9bcc3346d064a97 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 13 Apr 2022 14:23:00 +0200 Subject: [PATCH 092/121] Add reflect AppliedType constructor Fixes #14740 --- .../src/scala/quoted/runtime/impl/QuotesImpl.scala | 2 ++ library/src/scala/quoted/Quotes.scala | 3 +++ project/MiMaFilters.scala | 1 + tests/pos-macros/i14740/Macro_1.scala | 11 +++++++++++ tests/pos-macros/i14740/Test_2.scala | 3 +++ 5 files changed, 20 insertions(+) create mode 100644 tests/pos-macros/i14740/Macro_1.scala create mode 100644 tests/pos-macros/i14740/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7641b9f0489b..37da7dafd25a 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1893,6 +1893,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end AppliedTypeTypeTest object AppliedType extends AppliedTypeModule: + def apply(tycon: TypeRepr, args: List[TypeRepr]): AppliedType = + Types.AppliedType(tycon, args) def unapply(x: AppliedType): (TypeRepr, List[TypeRepr]) = (AppliedTypeMethods.tycon(x), AppliedTypeMethods.args(x)) end AppliedType diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 862a9def8dcb..013a99559fbb 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -2812,6 +2812,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Methods of the module object `val AppliedType` */ trait AppliedTypeModule { this: AppliedType.type => + /** Applied the type constructor `T` to a list of type arguments `T_1,..,T_n` to create `T[T_1,..,T_n]` */ + @experimental + def apply(tycon: TypeRepr, args: List[TypeRepr]): AppliedType def unapply(x: AppliedType): (TypeRepr, List[TypeRepr]) } diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 8bd16f134f57..2d41aca4fed1 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -11,6 +11,7 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#AppliedTypeModule.apply"), // Experimental `MainAnnotation` APIs. Can be added in 3.3.0 or later. ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation"), diff --git a/tests/pos-macros/i14740/Macro_1.scala b/tests/pos-macros/i14740/Macro_1.scala new file mode 100644 index 000000000000..49b869e2b307 --- /dev/null +++ b/tests/pos-macros/i14740/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted.* + +type Foo[T] + +transparent inline def emptyList[T]: Any = ${ impl[T] } + +private def impl[T: Type](using Quotes): Expr[Any] = { + import quotes.reflect._ + val tpe = AppliedType(TypeRepr.of[Foo], List(TypeRepr.of[T])) // test AppliedType constructor + Typed('{???}.asTerm, Inferred(tpe)).asExpr // '{ ??? : Foo[T] } +} diff --git a/tests/pos-macros/i14740/Test_2.scala b/tests/pos-macros/i14740/Test_2.scala new file mode 100644 index 000000000000..a59d01526f4f --- /dev/null +++ b/tests/pos-macros/i14740/Test_2.scala @@ -0,0 +1,3 @@ +def test1: Foo[Any] = emptyList[Any] +def test2: Foo[Int] = emptyList[Int] +def test3: Foo[String] = emptyList[String] From 837be4593f4681fce9d0d08d2d17c79d65528fb4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 19 Apr 2022 13:34:46 +0200 Subject: [PATCH 093/121] Use filter in coverage tests Fixes #14968 --- .../tools/dotc/coverage/CoverageTests.scala | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 6f978bc131d8..15e6af6fe562 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -41,24 +41,24 @@ class CoverageTests: lines end fixWindowsPaths - Files.walk(dir).filter(scalaFile.matches).forEach(p => { - val path = p - val fileName = path.getFileName.toString.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(path, dir, run) - val targetFile = targetDir.resolve(s"scoverage.coverage") - val expectFile = p.resolveSibling(s"$fileName.scoverage.check") - - if updateCheckFiles then - Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) - else - val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) - val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) - if expected != obtained then - for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do - Console.err.println(s"wrong line ${i+1}:") - Console.err.println(s" expected: $exp") - Console.err.println(s" actual : $actual") - fail(s"$targetFile differs from expected $expectFile") + Files.walk(dir).filter(scalaFile.matches).forEach(path => { + if Properties.testsFilter.isEmpty || Properties.testsFilter.exists(path.toString.contains) then + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, dir, run) + val targetFile = targetDir.resolve(s"scoverage.coverage") + val expectFile = path.resolveSibling(s"$fileName.scoverage.check") + + if updateCheckFiles then + Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) + else + val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) + val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) + if expected != obtained then + for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do + Console.err.println(s"wrong line ${i+1}:") + Console.err.println(s" expected: $exp") + Console.err.println(s" actual : $actual") + fail(s"$targetFile differs from expected $expectFile") }) From 3f642da23bb075cbe73420b669ebe2e9ebe4b59f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 14 Apr 2022 17:35:33 +0200 Subject: [PATCH 094/121] Add a test to track @experimental definitions Label all definition and add coments on when they can be stabilized --- .../stdlibExperimentalDefinitions.scala | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala new file mode 100644 index 000000000000..7148a8c692ba --- /dev/null +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -0,0 +1,129 @@ +import scala.quoted.* +import scala.tasty.inspector.* + +val experimentalDefinitionInLibrary = Set( + + // README + // - Definitions should be grouped under a language feature or API + // - API definitions that must be stabilized at the same time should be added in the same line + // - Language definitions are assumed to be stabilized all at once unless stated otherwise + + + //// New feature: Safe Exceptions + // Can be stabilized when safe exeptions language feature is stabilized. + "scala.CanThrow", + "scala.unsafeExceptions", "scala.unsafeExceptions$", + "scala.runtime.$throws$package$.$throws", + + //// New feature: Tupled Functions + // Can be stabilized when language feature is stabilized. + // Needs user feedback. + // Needs generalization to polymorphic types (at least proof of concept that shows that that design is compatible). + "scala.runtime.TupledFunctions", + "scala.runtime.TupledFunctions$", + "scala.util.TupledFunction", + "scala.util.TupledFunction$", + + //// New feature: main annotation generalization + // Can be stabilized when language feature is stabilized. + // Needs user feedback. + // Should argGetter/varargGetter be simplified? + // Should we have better support for main annotation macros? + "scala.annotation.MainAnnotation", + "scala.annotation.MainAnnotation$", + + //// New APIs: compiletime.ops + // Can be stabilized in 3.3.0 or later. + // Needs user feedback + "scala.compiletime.ops.any$.IsConst", + "scala.compiletime.ops.any$.ToString", + "scala.compiletime.ops.double", "scala.compiletime.ops.double$", + "scala.compiletime.ops.float", + "scala.compiletime.ops.float$", + "scala.compiletime.ops.int$.NumberOfLeadingZeros", + "scala.compiletime.ops.int$.ToDouble", + "scala.compiletime.ops.int$.ToFloat", + "scala.compiletime.ops.int$.ToLong", + "scala.compiletime.ops.long", "scala.compiletime.ops.long$", + "scala.compiletime.ops.string$.Length", + "scala.compiletime.ops.string$.Matches", + "scala.compiletime.ops.string$.Substring", + + //// New APIs: Mirror + // Can be stabilized in 3.2.0 or later. + "scala.deriving.Mirror$.fromTuple", // Probably for 3.2.0 + "scala.deriving.Mirror$.fromProductTyped", // This API is a bit convoluted. We may need some more feedback before we can stabilize it. + + //// New APIs: Tuples + // Should be stabilized in 3.2.0. + "scala.Tuple.:*", "scala.Tuple$.Append", "scala.runtime.Tuples$.append", + "scala.NonEmptyTuple.init", "scala.Tuple$.Init", "scala.runtime.Tuples$.init", + "scala.Tuple$.Last", "scala.NonEmptyTuple.last", "scala.runtime.Tuples$.last", + + //// New APIs: Quotes + // Should be stabilized in 3.2.0. + "scala.quoted.Quotes.reflectModule.AppliedTypeModule.apply", + "scala.quoted.Quotes.reflectModule.SymbolMethods.asQuotes", + "scala.quoted.Quotes.reflectModule.SymbolMethods.termRef", + "scala.quoted.Quotes.reflectModule.SymbolMethods.typeRef", + "scala.quoted.Quotes.reflectModule.TypeReprMethods.substituteTypes", + "scala.quoted.Quotes.reflectModule.TypeReprMethods.typeArgs", + "scala.quoted.Quotes.reflectModule.TypeTreeModule.ref", + // Can be stabilized in 3.2.0 (unsure) or later + "scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings", + // Cant be stabilized yet. + // Need newClass variant that can add constructor parameters. + // Need experimental annotation macros to check that design works. + "scala.quoted.Quotes.reflectModule.ClassDefModule.apply", + "scala.quoted.Quotes.reflectModule.SymbolModule.newClass", +) + + +@main def Test = { + val inspector = new Inspector { + def inspect(using Quotes)(tastys: List[Tasty[quotes.type]]): Unit = { + import quotes.reflect.* + val experimentalAnnot = Symbol.requiredClass("scala.annotation.experimental") + object AccumulateExperimentalDefs extends TreeAccumulator[Set[Symbol]]: + def foldTree(expDefs: Set[Symbol], tree: Tree)(owner: Symbol): Set[Symbol] = + tree match + case tree: Definition if tree.symbol.hasAnnotation(experimentalAnnot) => foldOverTree(expDefs + tree.symbol, tree)(owner) + case _ => foldOverTree(expDefs, tree)(owner) + + val experimentalDefinitionsSyms = tastys.foldLeft(Set.empty[Symbol]) { (acc, tasty) => + AccumulateExperimentalDefs.foldTree(acc, tasty.ast)(Symbol.spliceOwner) + } + val experimentalDefinitions = experimentalDefinitionsSyms.map(_.fullName) + val missingFromList = experimentalDefinitions -- experimentalDefinitionInLibrary + val missingInLibrary = experimentalDefinitionInLibrary -- experimentalDefinitions + assert(missingFromList.isEmpty, + s"""Failed @experimental definitions check + | + |Found @experimental definition in library not listed: + |${missingFromList.toSeq.sorted.mkString("\n")} + | + |If added new experimental defintions to the library, add them to the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala + | + |Test only: sbt "scala3-bootstrapped/testCompilation tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala" + |""".stripMargin + ) + assert(missingInLibrary.isEmpty, + s"""Failed @experimental definitions check + | + |Listed @experimental definition was not found in the library + |${missingInLibrary.toSeq.sorted.mkString("\n")} + | + |If experimental definition was removed or stabilized, remove from the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala + | + |Test only: sbt "scala3-bootstrapped/testCompilation tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala" + |""".stripMargin + ) + } + } + + // Artefact of the current test infrastructure + // TODO improve infrastructure to avoid needing this code on each test + val libJarClasspath = dotty.tools.dotc.util.ClasspathFromClassloader(this.getClass.getClassLoader).split(java.io.File.pathSeparator).find(x => x.contains("scala3-library-bootstrapped") && x.endsWith(".jar")).get + + TastyInspector.inspectTastyFilesInJar(libJarClasspath)(inspector) +} From 318f610908fe0b5ace9e84c04de3a83ba414601e Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 19 Apr 2022 16:20:28 +0200 Subject: [PATCH 095/121] Update compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala Co-authored-by: Guillaume Raffin --- .../tools/dotc/coverage/CoverageTests.scala | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala index 15e6af6fe562..9edc3f5b1cf9 100644 --- a/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala +++ b/compiler/test/dotty/tools/dotc/coverage/CoverageTests.scala @@ -41,24 +41,26 @@ class CoverageTests: lines end fixWindowsPaths - Files.walk(dir).filter(scalaFile.matches).forEach(path => { - if Properties.testsFilter.isEmpty || Properties.testsFilter.exists(path.toString.contains) then - val fileName = path.getFileName.toString.stripSuffix(".scala") - val targetDir = computeCoverageInTmp(path, dir, run) - val targetFile = targetDir.resolve(s"scoverage.coverage") - val expectFile = path.resolveSibling(s"$fileName.scoverage.check") - - if updateCheckFiles then - Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) - else - val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) - val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) - if expected != obtained then - for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do - Console.err.println(s"wrong line ${i+1}:") - Console.err.println(s" expected: $exp") - Console.err.println(s" actual : $actual") - fail(s"$targetFile differs from expected $expectFile") + def runOnFile(p: Path): Boolean = + scalaFile.matches(p) && + (Properties.testsFilter.isEmpty || Properties.testsFilter.exists(p.toString.contains)) + + Files.walk(dir).filter(runOnFile).forEach(path => { + val fileName = path.getFileName.toString.stripSuffix(".scala") + val targetDir = computeCoverageInTmp(path, dir, run) + val targetFile = targetDir.resolve(s"scoverage.coverage") + val expectFile = path.resolveSibling(s"$fileName.scoverage.check") + if updateCheckFiles then + Files.copy(targetFile, expectFile, StandardCopyOption.REPLACE_EXISTING) + else + val expected = fixWindowsPaths(Files.readAllLines(expectFile).asScala) + val obtained = fixWindowsPaths(Files.readAllLines(targetFile).asScala) + if expected != obtained then + for ((exp, actual),i) <- expected.zip(obtained).filter(_ != _).zipWithIndex do + Console.err.println(s"wrong line ${i+1}:") + Console.err.println(s" expected: $exp") + Console.err.println(s" actual : $actual") + fail(s"$targetFile differs from expected $expectFile") }) From 8c021c30c3cbc60eb53cedd4923f506f09829eb0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 19 Apr 2022 17:33:29 +0200 Subject: [PATCH 096/121] Update compiler/src/dotty/tools/backend/jvm/GenBCode.scala --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 65fe7971d458..a10a146e798b 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -581,7 +581,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim report.error(msg) case e: ClassTooLargeException => val msg = - s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64K" + s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64KB" report.error(msg) } From a5e32da7b95dc7326cacb4d78261533651d9df07 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 19 May 2021 14:28:40 +0200 Subject: [PATCH 097/121] Re-architecture quote pickling Separate the logic that creates holes in quotes from the logic that pickles the quotes. Holes are created in the `Splicer` phase and the result of the transformation can be `-Ycheck`ed. Now, the `PickleQuotes` phase only needs to extract the contents of the holes, pickle the quote and put them into a call to `unpickleExprV2`/`unpickleTypeV2`. We add `unpickleExprV2` to support some optimization in the encoding of the pickled quote. Namely we removed an unnecessary lambda from the arguments of the hole passed into the contents of the hole. By not changing `unpickleExpr` the current compiler will be able to handle the old encoding in binaries compiled with older compilers. The `unpickleTypeV2` is just a version of `unpickleType` that does not take the `termHole` parameter which is always `null`. With `-Yscala-relese` 3.0 or 3.1, the compiler will generate calls to the old `unpickleExpr`/`unpickleType`. Fixes #8100 Fixes #12440 Fixes #13563 Fixes #14337 Fixes #14373 Closes #13732 --- .../dotty/tools/dotc/CompilationUnit.scala | 8 +- compiler/src/dotty/tools/dotc/Compiler.scala | 1 + .../dotty/tools/dotc/ast/TreeTypeMap.scala | 7 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 32 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 9 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 1 + .../dotty/tools/dotc/core/Definitions.scala | 36 + .../src/dotty/tools/dotc/core/Phases.scala | 8 +- .../tools/dotc/core/StagingContext.scala | 10 +- .../src/dotty/tools/dotc/core/StdNames.scala | 3 + .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 4 +- .../tools/dotc/printing/RefinedPrinter.scala | 8 +- .../tools/dotc/quoted/PickledQuotes.scala | 110 ++- .../dotty/tools/dotc/transform/Inlining.scala | 2 - .../tools/dotc/transform/PickleQuotes.scala | 741 +++++++----------- .../tools/dotc/transform/PostTyper.scala | 1 - .../tools/dotc/transform/ReifiedReflect.scala | 110 +++ .../dotty/tools/dotc/transform/Splicing.scala | 398 ++++++++++ .../dotty/tools/dotc/transform/Staging.scala | 2 +- .../tools/dotc/transform/TreeChecker.scala | 31 + .../src/dotty/tools/dotc/typer/Inliner.scala | 7 +- .../tools/dotc/typer/QuotesAndSplices.scala | 4 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 + .../src/dotty/tools/dotc/typer/Typer.scala | 1 + .../quoted/runtime/impl/QuotesImpl.scala | 12 +- .../scala/quoted/runtime/QuoteUnpickler.scala | 17 + project/MiMaFilters.scala | 8 +- .../hierarchical-mirrors-3.0}/app/Main.scala | 0 .../hierarchical-mirrors-3.0}/build.sbt | 0 .../hierarchical-mirrors-3.0}/lib/Top.scala | 0 .../project/DottyInjectedPlugin.scala | 0 .../hierarchical-mirrors-3.0}/test | 0 .../macros-backward-3.0/app/App.scala | 17 + .../macros-backward-3.0/build.sbt | 13 + .../macros-backward-3.0/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-backward-3.0/test | 1 + .../macros-backward-3.1/app/App.scala | 17 + .../macros-backward-3.1/build.sbt | 13 + .../macros-backward-3.1/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-backward-3.1/test | 1 + .../macros-forward-3.0/app/App.scala | 17 + .../macros-forward-3.0/build.sbt | 14 + .../macros-forward-3.0/lib/Macro.scala | 20 + .../project/DottyInjectedPlugin.scala | 11 + .../scala3-compat/macros-forward-3.0/test | 1 + .../scala/quoted/staging/QuoteCompiler.scala | 3 +- .../forwardCompat-3.0/Macro_1_r3.0.scala | 0 .../forwardCompat-3.0/Test_2_c3.0.2.scala} | 0 .../pos-macros/forwardCompat-3.0/why.md | 3 + tests/pos-macros/InlinedTypeOf.scala | 11 + .../backwardCompat-3.0/Macro_1_c3.0.0.scala | 10 + .../backwardCompat-3.0/Test_2.scala | 3 + .../backwardCompat-3.1/Macro_1_c3.1.0.scala | 10 + .../backwardCompat-3.1/Test_2.scala | 3 + tests/pos-macros/baseCompat/Macro_1.scala | 10 + tests/pos-macros/baseCompat/Test_2.scala | 2 + .../captured-quoted-def-1/Macro_1.scala | 6 + .../captured-quoted-def-1/Test_2.scala | 1 + .../captured-quoted-def-2/Macro_1.scala | 6 + .../captured-quoted-def-2/Test_2.scala | 1 + .../captured-quoted-def-3/Macro_1.scala | 6 + .../captured-quoted-def-3/Test_2.scala | 1 + .../captured-quoted-def-4/Macro_1.scala | 6 + .../captured-quoted-def-4/Test_2.scala | 1 + .../captured-quoted-def-5/Macro_1.scala | 6 + .../captured-quoted-def-5/Test_2.scala | 1 + .../captured-quoted-multy-stage/Macro_1.scala | 9 + .../captured-quoted-multy-stage/Test_2.scala | 1 + .../captured-quoted-type-1/Macro_1.scala | 10 + .../captured-quoted-type-1/Test_2.scala | 1 + .../captured-quoted-type-2/Macro_1.scala | 10 + .../captured-quoted-type-2/Test_2.scala | 1 + tests/pos-macros/i12440.scala | 21 + tests/pos-macros/i13563.scala | 3 + tests/pos-macros/i13732.scala | 6 + tests/pos-macros/i4774f.scala | 6 + tests/pos-macros/i8100.scala | 4 +- tests/pos-macros/typetags.scala | 4 + 81 files changed, 1415 insertions(+), 517 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/Splicing.scala rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/app/Main.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/build.sbt (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/lib/Top.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/project/DottyInjectedPlugin.scala (100%) rename sbt-test/{scala3-backcompat/hierarchical-mirrors => scala3-compat/hierarchical-mirrors-3.0}/test (100%) create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/build.sbt create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.0/test create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/build.sbt create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-backward-3.1/test create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/app/App.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/build.sbt create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala create mode 100644 sbt-test/scala3-compat/macros-forward-3.0/test rename tests/{ => disabled}/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala (100%) rename tests/{pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala => disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala} (100%) create mode 100644 tests/disabled/pos-macros/forwardCompat-3.0/why.md create mode 100644 tests/pos-macros/InlinedTypeOf.scala create mode 100644 tests/pos-macros/captured-quoted-def-1/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-1/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-2/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-2/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-3/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-3/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-4/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-4/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-def-5/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-def-5/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-multy-stage/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-type-1/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-type-1/Test_2.scala create mode 100644 tests/pos-macros/captured-quoted-type-2/Macro_1.scala create mode 100644 tests/pos-macros/captured-quoted-type-2/Test_2.scala create mode 100644 tests/pos-macros/i12440.scala create mode 100644 tests/pos-macros/i13563.scala create mode 100644 tests/pos-macros/i13732.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index df2b586ac6ae..c25d271f39a9 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -47,15 +47,10 @@ class CompilationUnit protected (val source: SourceFile) { var needsMirrorSupport: Boolean = false /** Will be set to `true` if contains `Quote`. - * The information is used in phase `Staging` in order to avoid traversing trees that need no transformations. + * The information is used in phase `Staging`/`Splicing`/`PickleQuotes` in order to avoid traversing trees that need no transformations. */ var needsStaging: Boolean = false - /** Will be set to `true` if contains `Quote` that needs to be pickled - * The information is used in phase `PickleQuotes` in order to avoid traversing trees that need no transformations. - */ - var needsQuotePickling: Boolean = false - var suspended: Boolean = false var suspendedAtInliningPhase: Boolean = false @@ -115,7 +110,6 @@ object CompilationUnit { val force = new Force force.traverse(unit1.tpdTree) unit1.needsStaging = force.containsQuote - unit1.needsQuotePickling = force.containsQuote unit1.needsInlining = force.containsInline } unit1 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 51891beef79f..d41c57ab116e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -54,6 +54,7 @@ class Compiler { List(new Inlining) :: // Inline and execute macros List(new PostInlining) :: // Add mirror support for inlined code List(new Staging) :: // Check staging levels and heal staged types + List(new Splicing) :: // Replace level 1 splices with holes List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures Nil diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index c393b257af11..2ccd646c3226 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -134,8 +134,11 @@ class TreeTypeMap( val bind1 = tmap.transformSub(bind) val expr1 = tmap.transform(expr) cpy.Labeled(labeled)(bind1, expr1) - case Hole(isTermHole, n, args) => - Hole(isTermHole, n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe)) + case tree @ Hole(_, _, args, content, tpt) => + val args1 = args.mapConserve(transform) + val content1 = transform(content) + val tpt1 = transform(tpt) + cpy.Hole(tree)(args = args1, content = content1, tpt = tpt1) case lit @ Literal(Constant(tpe: Type)) => cpy.Literal(lit)(Constant(mapType(tpe))) case tree1 => diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index c3e826578906..02ea87dca7d6 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -503,6 +503,11 @@ object Trees { def forwardTo: Tree[T] = fun } + object GenericApply: + def unapply[T >: Untyped](tree: Tree[T]): Option[(Tree[T], List[Tree[T]])] = tree match + case tree: GenericApply[T] => Some((tree.fun, tree.args)) + case _ => None + /** The kind of application */ enum ApplyKind: case Regular // r.f(x) @@ -525,8 +530,6 @@ object Trees { attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular) } - - /** fun[args] */ case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends GenericApply[T] { @@ -972,10 +975,16 @@ object Trees { def genericEmptyValDef[T >: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]] def genericEmptyTree[T >: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]] - /** Tree that replaces a splice in pickled quotes. - * It is only used when picking quotes (Will never be in a TASTy file). + /** Tree that replaces a level 1 splices in pickled (level 0) quotes. + * It is only used when picking quotes (will never be in a TASTy file). + * + * @param isTermHole If this hole is a term, otherwise it is a type hole. + * @param idx The index of the hole in it's enclosing level 0 quote. + * @param args The arguments of the splice to compute its content + * @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle. + * @param tpt Type of the hole */ - case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends Tree[T] { + case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] { type ThisTree[-T >: Untyped] <: Hole[T] override def isTerm: Boolean = isTermHole override def isType: Boolean = !isTermHole @@ -1331,6 +1340,10 @@ object Trees { case tree: Thicket if (trees eq tree.trees) => tree case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree))) } + def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match { + case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree + case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree))) + } // Copier methods with default arguments; these demand that the original tree // is of the same class as the copy. We only include trees with more than 2 elements here. @@ -1352,6 +1365,9 @@ object Trees { TypeDef(tree: Tree)(name, rhs) def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(using Context): Template = Template(tree: Tree)(constr, parents, derived, self, body) + def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole = + Hole(tree: Tree)(isTerm, idx, args, content, tpt) + } /** Hook to indicate that a transform of some subtree should be skipped */ @@ -1481,6 +1497,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) + case tree @ Hole(_, _, args, content, tpt) => + cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt)) case _ => transformMoreCases(tree) } @@ -1620,8 +1638,8 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case Hole(_, _, args) => - this(x, args) + case Hole(_, _, args, content, tpt) => + this(this(this(x, args), content), tpt) case _ => foldMoreCases(x, tree) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index fb8bd917ca20..2e87194ff207 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -377,6 +377,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { def Throw(expr: Tree)(using Context): Tree = ref(defn.throwMethod).appliedTo(expr) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = + ta.assignType(untpd.Hole(isTermHole, idx, args, content, tpt), tpt) + // ------ Making references ------------------------------------------------------ def prefixIsElidable(tp: NamedType)(using Context): Boolean = { @@ -1518,10 +1521,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * @param tpe the type of the elements of the resulting list. * */ - def mkList(trees: List[Tree], tpe: Tree)(using Context): Tree = + def mkList(trees: List[Tree], tpt: Tree)(using Context): Tree = ref(defn.ListModule).select(nme.apply) - .appliedToTypeTree(tpe) - .appliedToVarargs(trees, tpe) + .appliedToTypeTree(tpt) + .appliedToVarargs(trees, tpt) protected def FunProto(args: List[Tree], resType: Type)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index de95526c5991..365b969faffc 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -411,6 +411,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors) def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats) def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot) + def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt) // ------ Additional creation methods for untyped only ----------------- diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5db706197245..18fbf977bbc2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -791,10 +791,46 @@ class Definitions { @tu lazy val QuotedExprClass: ClassSymbol = requiredClass("scala.quoted.Expr") @tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes") + @tu lazy val Quotes_reflect: Symbol = QuotesClass.requiredValue("reflect") + @tu lazy val Quotes_reflect_asTerm: Symbol = Quotes_reflect.requiredMethod("asTerm") + @tu lazy val Quotes_reflect_Apply: Symbol = Quotes_reflect.requiredValue("Apply") + @tu lazy val Quotes_reflect_Apply_apply: Symbol = Quotes_reflect_Apply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TypeApply: Symbol = Quotes_reflect.requiredValue("TypeApply") + @tu lazy val Quotes_reflect_TypeApply_apply: Symbol = Quotes_reflect_TypeApply.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Assign: Symbol = Quotes_reflect.requiredValue("Assign") + @tu lazy val Quotes_reflect_Assign_apply: Symbol = Quotes_reflect_Assign.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Inferred: Symbol = Quotes_reflect.requiredValue("Inferred") + @tu lazy val Quotes_reflect_Inferred_apply: Symbol = Quotes_reflect_Inferred.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_Literal: Symbol = Quotes_reflect.requiredValue("Literal") + @tu lazy val Quotes_reflect_Literal_apply: Symbol = Quotes_reflect_Literal.requiredMethod(nme.apply) + @tu lazy val Quotes_reflect_TreeMethods: Symbol = Quotes_reflect.requiredMethod("TreeMethods") + @tu lazy val Quotes_reflect_TreeMethods_asExpr: Symbol = Quotes_reflect_TreeMethods.requiredMethod("asExpr") + @tu lazy val Quotes_reflect_TypeRepr: Symbol = Quotes_reflect.requiredValue("TypeRepr") + @tu lazy val Quotes_reflect_TypeRepr_of: Symbol = Quotes_reflect_TypeRepr.requiredMethod("of") + @tu lazy val Quotes_reflect_TypeRepr_typeConstructorOf: Symbol = Quotes_reflect_TypeRepr.requiredMethod("typeConstructorOf") + @tu lazy val Quotes_reflect_TypeReprMethods: Symbol = Quotes_reflect.requiredValue("TypeReprMethods") + @tu lazy val Quotes_reflect_TypeReprMethods_asType: Symbol = Quotes_reflect_TypeReprMethods.requiredMethod("asType") + @tu lazy val Quotes_reflect_TypeTreeType: Symbol = Quotes_reflect.requiredType("TypeTree") + @tu lazy val Quotes_reflect_TermType: Symbol = Quotes_reflect.requiredType("Term") + @tu lazy val Quotes_reflect_BooleanConstant: Symbol = Quotes_reflect.requiredValue("BooleanConstant") + @tu lazy val Quotes_reflect_ByteConstant: Symbol = Quotes_reflect.requiredValue("ByteConstant") + @tu lazy val Quotes_reflect_ShortConstant: Symbol = Quotes_reflect.requiredValue("ShortConstant") + @tu lazy val Quotes_reflect_IntConstant: Symbol = Quotes_reflect.requiredValue("IntConstant") + @tu lazy val Quotes_reflect_LongConstant: Symbol = Quotes_reflect.requiredValue("LongConstant") + @tu lazy val Quotes_reflect_FloatConstant: Symbol = Quotes_reflect.requiredValue("FloatConstant") + @tu lazy val Quotes_reflect_DoubleConstant: Symbol = Quotes_reflect.requiredValue("DoubleConstant") + @tu lazy val Quotes_reflect_CharConstant: Symbol = Quotes_reflect.requiredValue("CharConstant") + @tu lazy val Quotes_reflect_StringConstant: Symbol = Quotes_reflect.requiredValue("StringConstant") + @tu lazy val Quotes_reflect_UnitConstant: Symbol = Quotes_reflect.requiredValue("UnitConstant") + @tu lazy val Quotes_reflect_NullConstant: Symbol = Quotes_reflect.requiredValue("NullConstant") + @tu lazy val Quotes_reflect_ClassOfConstant: Symbol = Quotes_reflect.requiredValue("ClassOfConstant") + @tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler") @tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr") + @tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") + @tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2") @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") @tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch") diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index e43f617bf965..b72515b518a6 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -201,7 +201,7 @@ object Phases { private var mySbtExtractDependenciesPhase: Phase = _ private var myPicklerPhase: Phase = _ private var myInliningPhase: Phase = _ - private var myPickleQuotesPhase: Phase = _ + private var mySplicingPhase: Phase = _ private var myFirstTransformPhase: Phase = _ private var myCollectNullableFieldsPhase: Phase = _ private var myRefChecksPhase: Phase = _ @@ -224,7 +224,7 @@ object Phases { final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase final def picklerPhase: Phase = myPicklerPhase final def inliningPhase: Phase = myInliningPhase - final def pickleQuotesPhase: Phase = myPickleQuotesPhase + final def splicingPhase: Phase = mySplicingPhase final def firstTransformPhase: Phase = myFirstTransformPhase final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase final def refchecksPhase: Phase = myRefChecksPhase @@ -250,7 +250,7 @@ object Phases { mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies]) myPicklerPhase = phaseOfClass(classOf[Pickler]) myInliningPhase = phaseOfClass(classOf[Inlining]) - myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes]) + mySplicingPhase = phaseOfClass(classOf[Splicing]) myFirstTransformPhase = phaseOfClass(classOf[FirstTransform]) myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields]) myRefChecksPhase = phaseOfClass(classOf[RefChecks]) @@ -426,7 +426,7 @@ object Phases { def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase def picklerPhase(using Context): Phase = ctx.base.picklerPhase def inliningPhase(using Context): Phase = ctx.base.inliningPhase - def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase + def splicingPhase(using Context): Phase = ctx.base.splicingPhase def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase diff --git a/compiler/src/dotty/tools/dotc/core/StagingContext.scala b/compiler/src/dotty/tools/dotc/core/StagingContext.scala index 4a4f451df099..41e77655d5d6 100644 --- a/compiler/src/dotty/tools/dotc/core/StagingContext.scala +++ b/compiler/src/dotty/tools/dotc/core/StagingContext.scala @@ -8,11 +8,11 @@ import dotty.tools.dotc.transform.PCPCheckAndHeal object StagingContext { - /** A key to be used in a context property that tracks the quoteation level */ + /** A key to be used in a context property that tracks the quotation level */ private val QuotationLevel = new Property.Key[Int] - /** A key to be used in a context property that tracks the quoteation stack. - * Stack containing the Quotes references recieved by the surrounding quotes. + /** A key to be used in a context property that tracks the quotation stack. + * Stack containing the Quotes references received by the surrounding quotes. */ private val QuotesStack = new Property.Key[List[tpd.Tree]] @@ -26,7 +26,7 @@ object StagingContext { def quoteContext(using Context): Context = ctx.fresh.setProperty(QuotationLevel, level + 1) - /** Context with an incremented quotation level and pushes a refecence to a Quotes on the quote context stack */ + /** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */ def pushQuotes(qctxRef: tpd.Tree)(using Context): Context = val old = ctx.property(QuotesStack).getOrElse(List.empty) ctx.fresh.setProperty(QuotationLevel, level + 1) @@ -43,7 +43,7 @@ object StagingContext { ctx.property(TaggedTypes).get /** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty. - * The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice. + * The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice. */ def popQuotes()(using Context): (Option[tpd.Tree], Context) = val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index dc9e48b65f47..949a99c1e016 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -433,6 +433,7 @@ object StdNames { val common: N = "common" val compiletime : N = "compiletime" val conforms_ : N = "$conforms" + val contents: N = "contents" val copy: N = "copy" val currentMirror: N = "currentMirror" val create: N = "create" @@ -486,6 +487,7 @@ object StdNames { val hash_ : N = "hash" val head: N = "head" val higherKinds: N = "higherKinds" + val idx: N = "idx" val identity: N = "identity" val implicitConversions: N = "implicitConversions" val implicitly: N = "implicitly" @@ -553,6 +555,7 @@ object StdNames { val productElementName: N = "productElementName" val productIterator: N = "productIterator" val productPrefix: N = "productPrefix" + val quotes : N = "quotes" val raw_ : N = "raw" val refl: N = "refl" val reflect: N = "reflect" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index dd2cc46c406d..09aae581fcfc 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -637,11 +637,11 @@ class TreePickler(pickler: TastyPickler) { pickleTree(hi) pickleTree(alias) } - case Hole(_, idx, args) => + case Hole(_, idx, args, _, tpt) => writeByte(HOLE) withLength { writeNat(idx) - pickleType(tree.tpe, richTypes = true) + pickleType(tpt.tpe, richTypes = true) args.foreach(pickleTree) } } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index dd9f234e0365..c438f77bb84b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1313,7 +1313,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(true, idx, args).withType(tpe) + Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => readPathTerm() } @@ -1347,7 +1347,7 @@ class TreeUnpickler(reader: TastyReader, val idx = readNat() val tpe = readType() val args = until(end)(readTerm()) - Hole(false, idx, args).withType(tpe) + Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe) case _ => if (isTypeTreeTag(nextByte)) readTerm() else { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6d90b168a29f..9401efc58135 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -697,10 +697,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { "Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}" case MacroTree(call) => keywordStr("macro ") ~ toTextGlobal(call) - case Hole(isTermHole, idx, args) => - val (prefix, postfix) = if isTermHole then ("{{{ ", " }}}") else ("[[[ ", " ]]]") + case Hole(isTermHole, idx, args, content, tpt) => + val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]") val argsText = toTextGlobal(args, ", ") - prefix ~~ idx.toString ~~ "|" ~~ argsText ~~ postfix + val contentText = toTextGlobal(content) + val tptText = toTextGlobal(tpt) + prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix case _ => tree.fallbackToText(this) } diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 9de869c994e6..b3a4783a783c 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -47,34 +47,67 @@ object PickledQuotes { changeOwnerOfTree(tpe1.typeTree, ctx.owner) } + /** `typeHole`/`types` argument of `QuoteUnpickler.{unpickleExpr,unpickleExprV2,unpickleType,unpickleTypeV2}` */ + enum TypeHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[scala.quoted.Type[?]]) => scala.quoted.Type[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(types: Null | Seq[scala.quoted.Type[?]]) + + def isEmpty: Boolean = this match + case V1(evalHole) => evalHole == null + case V2(types) => types == null + + enum ExprHole: + /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. + * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 + * Note: For `unpickleType` it will always be `null`. + */ + case V1(evalHole: Null | ((Int, Seq[ExprHole.ArgV1], scala.quoted.Quotes) => scala.quoted.Expr[?])) + /** `termHole` argument of `QuoteUnpickler.unpickleExprV2` + * From code compiled with Scala 3.2.0+ + */ + case V2(evalHole: Null | ((Int, Seq[ExprHole.ArgV2], scala.quoted.Quotes) => scala.quoted.Expr[?])) + + object ExprHole: + type ArgV1 = scala.quoted.Type[?] | (Quotes ?=> scala.quoted.Expr[Any]) + type ArgV2 = scala.quoted.Type[?] | scala.quoted.Expr[Any] + /** Unpickle the tree contained in the TastyExpr */ - def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTerm(pickled: String | List[String], typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)) - val Inlined(call, Nil, expnasion) = unpickled + val Inlined(call, Nil, expansion) = unpickled val inlineCtx = inlineContext(call) - val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx) + val expansion1 = spliceTypes(expansion, typeHole)(using inlineCtx) val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx) cpy.Inlined(unpickled)(call, Nil, expansion2) } + /** Unpickle the tree contained in the TastyType */ - def unpickleTypeTree(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { + def unpickleTypeTree(pickled: String | List[String], typeHole: TypeHole)(using Context): Tree = { val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = true)) - spliceTypes(unpickled, typeHole, termHole) + spliceTypes(unpickled, typeHole) } /** Replace all term holes with the spliced terms */ - private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = { - val evaluateHoles = new TreeMap { + private def spliceTerms(tree: Tree, typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { + def evaluateHoles = new TreeMap { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Hole(isTerm, idx, args) => + case Hole(isTermHole, idx, args, _, _) => inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) { - val reifiedArgs = args.map { arg => - if (arg.isTerm) (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) - else new TypeImpl(arg, SpliceScope.getCurrent) - } - if isTerm then - val quotedExpr = termHole(idx, reifiedArgs, QuotesImpl()) + if isTermHole then + val quotedExpr = termHole match + case ExprHole.V1(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV1Args(args), QuotesImpl()) + case ExprHole.V2(evalHole) => + evalHole.nn.apply(idx, reifyExprHoleV2Args(args), QuotesImpl()) + val filled = PickledQuotes.quotedExprToTree(quotedExpr) // We need to make sure a hole is created with the source file of the surrounding context, even if @@ -82,9 +115,13 @@ object PickledQuotes { if filled.source == ctx.source then filled else filled.cloneIn(ctx.source).withSpan(tree.span) else + // For backwards compatibility with 3.0.x and 3.1.x + // In 3.2.0+ all these holes are handled by `spliceTypes` before we call `spliceTerms`. + // // Replaces type holes generated by PickleQuotes (non-spliced types). // These are types defined in a quote and used at the same level in a nested quote. - val quotedType = typeHole(idx, reifiedArgs) + val TypeHole.V1(evalHole) = typeHole + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) PickledQuotes.quotedTypeToTree(quotedType) } case tree => @@ -109,24 +146,34 @@ object PickledQuotes { } } } - val tree1 = evaluateHoles.transform(tree) + val tree1 = termHole match + case ExprHole.V2(null) => tree + case _ => evaluateHoles.transform(tree) quotePickling.println(i"**** evaluated quote\n$tree1") tree1 } /** Replace all type holes generated with the spliced types */ - private def spliceTypes(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Int], scala.quoted.Quotes) => Any)(using Context): Tree = { - tree match + private def spliceTypes(tree: Tree, typeHole: TypeHole)(using Context): Tree = { + if typeHole.isEmpty then tree + else tree match case Block(stat :: rest, expr1) if stat.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => val typeSpliceMap = (stat :: rest).iterator.map { case tdef: TypeDef => assert(tdef.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot)) - val tree = tdef.rhs match - case TypeBoundsTree(_, Hole(_, idx, args), _) => - val quotedType = typeHole(idx, args) - PickledQuotes.quotedTypeToTree(quotedType) - case TypeBoundsTree(_, tpt, _) => - tpt + val tree = typeHole match + case TypeHole.V1(evalHole) => + tdef.rhs match + case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) => + // To keep for backwards compatibility. In some older version holes where created in the bounds. + val quotedType = evalHole.nn.apply(idx, reifyTypeHoleArgs(args)) + PickledQuotes.quotedTypeToTree(quotedType) + case TypeBoundsTree(_, tpt, _) => + // To keep for backwards compatibility. In some older version we missed the creation of some holes. + tpt + case TypeHole.V2(types) => + val Hole(_, idx, _, _, _) = tdef.rhs + PickledQuotes.quotedTypeToTree(types.nn.apply(idx)) (tdef.symbol, tree.tpe) }.toMap class ReplaceSplicedTyped extends TypeMap() { @@ -148,6 +195,21 @@ object PickledQuotes { tree } + def reifyTypeHoleArgs(args: List[Tree])(using Context): List[scala.quoted.Type[?]] = + args.map(arg => new TypeImpl(arg, SpliceScope.getCurrent)) + + def reifyExprHoleV1Args(args: List[Tree])(using Context): List[ExprHole.ArgV1] = + args.map { arg => + if arg.isTerm then (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + + def reifyExprHoleV2Args(args: List[Tree])(using Context): List[ExprHole.ArgV2] = + args.map { arg => + if arg.isTerm then new ExprImpl(arg, SpliceScope.getCurrent) + else new TypeImpl(arg, SpliceScope.getCurrent) + } + // TASTY picklingtests/pos/quoteTest.scala /** Pickle tree into it's TASTY bytes s*/ diff --git a/compiler/src/dotty/tools/dotc/transform/Inlining.scala b/compiler/src/dotty/tools/dotc/transform/Inlining.scala index ca17cf607641..3c5da3ffadf3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Inlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/Inlining.scala @@ -69,8 +69,6 @@ class Inlining extends MacroTransform { if tree1.tpe.isError then tree1 else Inliner.inlineCall(tree1) case _: GenericApply if tree.symbol.isQuote => - if level == 0 then - ctx.compilationUnit.needsQuotePickling = true super.transform(tree)(using StagingContext.quoteContext) case _: GenericApply if tree.symbol.isExprSplice => super.transform(tree)(using StagingContext.spliceContext) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 82a187f57109..40e3526861d0 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -13,6 +13,7 @@ import ast.TreeTypeMap import SymUtils._ import NameKinds._ import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.config.ScalaRelease.* import scala.collection.mutable import dotty.tools.dotc.core.Annotations._ @@ -23,45 +24,52 @@ import dotty.tools.dotc.typer.Inliner import scala.annotation.constructorOnly - -/** Translates quoted terms and types to `unpickleExpr` or `unpickleType` method calls. +/** Translates quoted terms and types to `unpickleExprV2` or `unpickleType` method calls. * * Transforms top level quote * ``` * '{ ... - * val x1 = ??? - * val x2 = ??? + * @TypeSplice type X0 = {{ 0 | .. | contentsTpe0 | .. }} + * @TypeSplice type X2 = {{ 1 | .. | contentsTpe1 | .. }} + * val x1: U1 = ??? + * val x2: U2 = ??? + * ... + * {{{ 3 | x1 | contents0 | T0 }}} // hole * ... - * ${ ... '{ ... x1 ... x2 ...} ... } + * {{{ 4 | x2 | contents1 | T1 }}} // hole + * ... + * {{{ 5 | x1, x2 | contents2 | T2 }}} // hole * ... * } * ``` * to * ``` - * unpickleExpr( + * unpickleExprV2( * pickled = [[ // PICKLED TASTY - * ... + * @TypeSplice type X0 // with bounds that do not contain captured types + * @TypeSplice type X1 // with bounds that do not contain captured types * val x1 = ??? * val x2 = ??? * ... - * Hole( | x1, x2) + * {{{ 0 | x1 | | T0 }}} // hole + * ... + * {{{ 1 | x2 | | T1 }}} // hole + * ... + * {{{ 2 | x1, x2 | | T2 }}} // hole * ... * ]], * typeHole = (idx: Int, args: List[Any]) => idx match { - * case 0 => ... + * case 0 => contentsTpe0.apply(args(0).asInstanceOf[Type[?]]) // beta reduced + * case 1 => contentsTpe1.apply(args(0).asInstanceOf[Type[?]]) // beta reduced * }, - * termHole = (idx: Int, args: List[Any], qctx: Quotes) => idx match { - * case 0 => ... - * ... - * case => - * val x1$1 = args(0).asInstanceOf[Expr[T]] - * val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]] - * ... - * { ... '{ ... ${x1$1} ... ${x2$1} ...} ... } + * termHole = (idx: Int, args: List[Any], quotes: Quotes) => idx match { + * case 3 => content0.apply(args(0).asInstanceOf[Expr[U1]]).apply(quotes) // beta reduced + * case 4 => content1.apply(args(0).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced + * case 5 => content2.apply(args(0).asInstanceOf[Expr[U1]], args(1).asInstanceOf[Expr[U2]]).apply(quotes) // beta reduced * }, * ) * ``` - * and then performs the same transformation on `'{ ... ${x1$1} ... ${x2$1} ...}`. + * and then performs the same transformation on any quote contained in the `content`s. * */ class PickleQuotes extends MacroTransform { @@ -75,375 +83,80 @@ class PickleQuotes extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - tree match { + tree match case tree: RefTree if !Inliner.inInlineMethod => assert(!tree.symbol.isQuote) assert(!tree.symbol.isExprSplice) - case _ : TypeDef => + case _ : TypeDef if !Inliner.inInlineMethod => assert(!tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot), s"${tree.symbol} should have been removed by PickledQuotes because it has a @quoteTypeTag") case _ => - } override def run(using Context): Unit = - if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext) + if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext) protected def newTransformer(using Context): Transformer = new Transformer { override def transform(tree: tpd.Tree)(using Context): tpd.Tree = - new QuoteReifier(null, new mutable.HashMap[Symbol, Tree => Tree], new Embedded, ctx.owner)(ctx).transform(tree) + tree match + case Apply(Select(Apply(TypeApply(fn, List(tpt)), List(code)),nme.apply), List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + val (contents, codeWithHoles) = makeHoles(code) + val sourceRef = Inliner.inlineCallTrace(ctx.owner, tree.sourcePos) + val codeWithHoles2 = Inlined(sourceRef, Nil, codeWithHoles) + val pickled = PickleQuotes(quotes, codeWithHoles2, contents, tpt.tpe, false) + transform(pickled) // pickle quotes that are in the contents + case Apply(TypeApply(_, List(tpt)), List(quotes)) if tree.symbol == defn.QuotedTypeModule_of => + tpt match + case Select(t, _) if tpt.symbol == defn.QuotedType_splice => + // `Type.of[t.Underlying](quotes)` --> `t` + ref(t.symbol)(using ctx.withSource(tpt.source)).withSpan(tpt.span) + case _ => + val (contents, tptWithHoles) = makeHoles(tpt) + PickleQuotes(quotes, tptWithHoles, contents, tpt.tpe, true) + case tree: DefDef if tree.symbol.is(Macro) => + // Shrink size of the tree. The methods have already been inlined. + // TODO move to FirstTransform to trigger even without quotes + cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + case _: DefDef if tree.symbol.isInlineMethod => + tree + case _ => + super.transform(tree) } - /** The main transformer class - * @param outer the next outer reifier, null is this is the topmost transformer - * @param embedded a list of embedded quotes (if in a splice) or splices (if in a quote) - * @param owner the owner in the destination lifted lambda - * @param capturers register a reference defined in a quote but used in another quote nested in a splice. - * Returns a version of the reference that needs to be used in its place. - * '{ - * val x = ??? - * ${ ... '{ ... x ... } ... } - * } - * Eta expanding the `x` in `${ ... '{ ... x ... } ... }` will return a `${x$1}` for which the `x$1` - * be created by some outer reifier. - * This transformation is only applied to definitions at staging level 1. - * See `isCaptured`. - */ - private class QuoteReifier(outer: QuoteReifier | Null, capturers: mutable.HashMap[Symbol, Tree => Tree], - val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self => - - import StagingContext._ - - /** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */ - def nested(isQuote: Boolean)(using Context): QuoteReifier = { - val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new Embedded - new QuoteReifier(this, capturers, nestedEmbedded, ctx.owner)(ctx) - } - - /** Split `body` into a core and a list of embedded splices. - * Then if inside a splice, make a hole from these parts. - * If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or - * `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with - * core and splices as arguments. - */ - override protected def transformQuotation(body: Tree, quote: Apply)(using Context): Tree = { - val isType = quote.symbol eq defn.QuotedTypeModule_of - if (level > 0) { - val body1 = nested(isQuote = true).transform(body)(using quoteContext) - super.transformQuotation(body1, quote) - } - else { - val (body1, splices) = nested(isQuote = true).splitQuote(body)(using quoteContext) - if (level == 0) { - val body2 = - if (body1.isType) body1 - else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1) - pickledQuote(quote, body2, splices, body.tpe, isType).withSpan(quote.span) - } - else - body - } - } - - private def pickledQuote(quote: Apply, body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => qctx.reflect.TreeMethods.asExpr( - * qctx.reflect.Literal.apply(x$1.reflect.Constant..apply()) - * ).asInstanceOf[scala.quoted.Expr[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsLiteral(lit: Literal) = { - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkConst(ts: List[Tree]) = { - val reflect = ts.head.select("reflect".toTermName) - val typeName = body.tpe.typeSymbol.name - val literalValue = - if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil - else List(body) - val constant = reflect.select(s"${typeName}Constant".toTermName).select(nme.apply).appliedToTermArgs(literalValue) - val literal = reflect.select("Literal".toTermName).select(nme.apply).appliedTo(constant) - reflect.select("TreeMethods".toTermName).select("asExpr".toTermName).appliedTo(literal).asInstance(exprType) - } - Lambda(lambdaTpe, mkConst).withSpan(body.span) - } - - /** Encode quote using Reflection.Literal - * - * Generate the code - * ```scala - * qctx => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(qctx) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def liftedValue(lit: Literal, lifter: Symbol) = - val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, exprType) - def mkToExprCall(ts: List[Tree]) = - ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(ts.head) - Lambda(lambdaTpe, mkToExprCall).withSpan(body.span) - - def pickleAsValue(lit: Literal) = { - // TODO should all constants be pickled as Literals? - // Should examime the generated bytecode size to decide and performance - lit.const.tag match { - case Constants.NullTag => pickleAsLiteral(lit) - case Constants.UnitTag => pickleAsLiteral(lit) - case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) - case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) - case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) - case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) - case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) - case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) - case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) - case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) - case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) - } - } - - /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} - * - * Generate the code - * ```scala - * qctx => qctx.asInstanceOf[QuoteUnpickler].[]( - * , - * , - * , - * ) - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def pickleAsTasty() = { - val pickleQuote = PickledQuotes.pickleQuote(body) - val pickledQuoteStrings = pickleQuote match - case x :: Nil => Literal(Constant(x)) - case xs => liftList(xs.map(x => Literal(Constant(x))), defn.StringType) - - // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices - val (typeSplices, termSplices) = splices.zipWithIndex.partition { case (splice, _) => - splice.tpe match - case defn.FunctionOf(_, res, _, _) => res.typeSymbol == defn.QuotedTypeClass - } - - // This and all closures in typeSplices are removed by the BetaReduce phase - val typeHoles = - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), - defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), - args => { - val cases = typeSplices.map { case (splice, idx) => - CaseDef(Literal(Constant(idx)), EmptyTree, splice.select(nme.apply).appliedTo(args(1))) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - // This and all closures in termSplices are removed by the BetaReduce phase - val termHoles = - if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without splices as small as possible - else - Lambda( - MethodType( - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), - defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), - args => { - val cases = termSplices.map { case (splice, idx) => - val defn.FunctionOf(_, defn.FunctionOf(qctxType :: _, _, _, _), _, _) = splice.tpe - val rhs = splice.select(nme.apply).appliedTo(args(1)).select(nme.apply).appliedTo(args(2).asInstance(qctxType)) - CaseDef(Literal(Constant(idx)), EmptyTree, rhs) - } - Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - - val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass - val quotedType = quoteClass.typeRef.appliedTo(originalTp) - val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) - def callUnpickle(ts: List[Tree]) = { - val qctx = ts.head.asInstance(defn.QuoteUnpicklerClass.typeRef) - val unpickleMeth = if isType then defn.QuoteUnpickler_unpickleType else defn.QuoteUnpickler_unpickleExpr - qctx.select(unpickleMeth).appliedToType(originalTp).appliedTo(pickledQuoteStrings, typeHoles, termHoles) - } - Lambda(lambdaTpe, callUnpickle).withSpan(body.span) - } - - /** Encode quote using Reflection.TypeRepr.typeConstructorOf - * - * Generate the code - * ```scala - * qctx.reflect.TypeReprMethods.asType( - * qctx.reflect.TypeRepr.typeConstructorOf(classOf[]]) - * ).asInstanceOf[scala.quoted.Type[]] - * ``` - * this closure is always applied directly to the actual context and the BetaReduce phase removes it. - */ - def taggedType() = - val typeType = defn.QuotedTypeClass.typeRef.appliedTo(body.tpe) - val classTree = TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) - val reflect = quote.args.head.select("reflect".toTermName) - val typeRepr = reflect.select("TypeRepr".toTermName).select("typeConstructorOf".toTermName).appliedTo(classTree) - reflect.select("TypeReprMethods".toTermName).select("asType".toTermName).appliedTo(typeRepr).asInstance(typeType) - - if (isType) { - if (splices.isEmpty && body.symbol.isPrimitiveValueClass) taggedType() - else pickleAsTasty().select(nme.apply).appliedTo(quote.args.head) // TODO do not create lambda - } - else getLiteral(body) match { - case Some(lit) => pickleAsValue(lit) - case _ => pickleAsTasty() - } - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSplice(body: Tree, splice: Apply)(using Context): Tree = - if (level > 1) { - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Apply(splice)(splice.fun, body1 :: Nil) - } - else { - assert(level == 1, "unexpected top splice outside quote") - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - val hole = makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - // We do not place add the inline marker for trees that where lifted as they come from the same file as their - // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. - // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. - // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). - if (outer.nn.embedded.isLiftedSymbol(body.symbol)) hole - else Inlined(EmptyTree, Nil, hole).withSpan(splice.span) - } - - /** If inside a quote, split the body of the splice into a core and a list of embedded quotes - * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. - */ - protected def transformSpliceType(body: Tree, splice: Select)(using Context): Tree = - if level > 1 then - val body1 = nested(isQuote = false).transform(body)(using spliceContext) - cpy.Select(splice)(body1, splice.name) - else if level == 1 then - val (body1, quotes) = nested(isQuote = false).splitSplice(body)(using spliceContext) - val tpe = outer.nn.embedded.getHoleType(body, splice) - makeHole(splice.isTerm, body1, quotes, tpe).withSpan(splice.span) - else - splice - - /** Transforms the contents of a nested splice - * Assuming - * '{ - * val x = ??? - * val y = ??? - * ${ ... '{ ... x .. y ... } ... } - * } - * then the spliced subexpression - * { ... '{ ... x ... y ... } ... } - * will be transformed to - * (args: Seq[Any]) => { - * val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]] - * { ... '{ ... ${x$1} ... ${y$1} ... } ... } - * } - * - * See: `capture` - * - * At the same time register embedded trees `x` and `y` to place as arguments of the hole - * placed in the original code. - * '{ - * val x = ??? - * val y = ??? - * Hole(0 | x, y) - * } - */ - private def makeLambda(tree: Tree)(using Context): Tree = { - def body(arg: Tree)(using Context): Tree = { - var i = 0 - transformWithCapturer(tree)( - (captured: mutable.Map[Symbol, Tree]) => { - (tree: Tree) => { - def newCapture = { - val tpw = tree.tpe.widen match { - case tpw: MethodicType => tpw.toFunctionType(isJava = false) - case tpw => tpw - } - assert(tpw.isInstanceOf[ValueType]) - val argTpe = - if (tree.isType) defn.QuotedTypeClass.typeRef.appliedTo(tpw) - else defn.FunctionType(1, isContextual = true).appliedTo(defn.QuotesClass.typeRef, defn.QuotedExprClass.typeRef.appliedTo(tpw)) - val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).cast(argTpe) - val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) - i += 1 - embedded.addTree(tree, capturedArg.symbol) - captured.put(tree.symbol, capturedArg) - capturedArg - } - val refSym = captured.getOrElseUpdate(tree.symbol, newCapture).symbol - ref(refSym).withSpan(tree.span) + private def makeHoles(tree: tpd.Tree)(using Context): (List[Tree], tpd.Tree) = + + class HoleContentExtractor extends Transformer: + private val contents = List.newBuilder[Tree] + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree @ Hole(isTerm, _, _, content, _) => + if !content.isEmpty then + contents += content + val holeType = + if isTerm then getTermHoleType(tree.tpe) else getTypeHoleType(tree.tpe) + val hole = cpy.Hole(tree)(content = EmptyTree, TypeTree(holeType)) + if isTerm then Inlined(EmptyTree, Nil, hole).withSpan(tree.span) else hole + case tree: DefTree => + val newAnnotations = tree.symbol.annotations.mapconserve { annot => + annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol))) } - } - ) + tree.symbol.annotations = newAnnotations + super.transform(tree) + case _ => + super.transform(tree).withType(mapAnnots(tree.tpe)) + + private def mapAnnots = new TypeMap { // TODO factor out duplicated logic in Splicing + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } } - /* Lambdas are generated outside the quote that is being reified (i.e. in outer.owner). - * In case the case that level == -1 the code is not in a quote, it is in an inline method, - * hence we should take that as owner directly. - */ - val lambdaOwner = if (level == -1) ctx.owner else outer.nn.owner - - val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen) - val meth = newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe) - Closure(meth, tss => body(tss.head.head)(using ctx.withOwner(meth)).changeNonLocalOwners(meth)).withSpan(tree.span) - } - - private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(using Context): Tree = { - val captured = mutable.LinkedHashMap.empty[Symbol, Tree] - val captured2 = capturer(captured) - - outer.nn.localSymbols.foreach(sym => if (!sym.isInlineMethod) capturers.put(sym, captured2)) - - val tree2 = transform(tree) - capturers --= outer.nn.localSymbols - - val captures = captured.result().valuesIterator.toList - if (captures.isEmpty) tree2 - else Block(captures, tree2) - } - - /** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */ - private def isCaptured(sym: Symbol, level: Int)(using Context): Boolean = - level == 1 && levelOf(sym) == 1 && capturers.contains(sym) - - /** Transform `tree` and return the resulting tree and all `embedded` quotes - * or splices as a pair. - */ - private def splitQuote(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = stipTypeAnnotations(transform(tree)) - (tree1, embedded.getTrees) - } - - private def splitSplice(tree: Tree)(using Context): (Tree, List[Tree]) = { - val tree1 = makeLambda(tree) - (tree1, embedded.getTrees) - } - - private def stipTypeAnnotations(tree: Tree)(using Context): Tree = - new TreeTypeMap(typeMap = _.stripAnnots).apply(tree) - - /** Register `body` as an `embedded` quote or splice - * and return a hole with `splices` as arguments and the given type `tpe`. - */ - private def makeHole(isTermHole: Boolean, body: Tree, splices: List[Tree], tpe: Type)(using Context): Hole = { - val idx = embedded.addTree(body, NoSymbol) /** Remove references to local types that will not be defined in this quote */ - def getTypeHoleType(using Context) = new TypeMap() { + private def getTypeHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match case tp: TypeRef if tp.typeSymbol.isTypeSplice => apply(tp.dealias) @@ -457,71 +170,33 @@ class PickleQuotes extends MacroTransform { } /** Remove references to local types that will not be defined in this quote */ - def getTermHoleType(using Context) = new TypeMap() { + private def getTermHoleType(using Context) = new TypeMap() { override def apply(tp: Type): Type = tp match - case tp @ TypeRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TypeRef(NoPrefix, _) => // reference to term with a type defined in outer quote getTypeHoleType(tp) - case tp @ TermRef(NoPrefix, _) if capturers.contains(tp.symbol) => + case tp @ TermRef(NoPrefix, _) => // widen term refs to terms defined in outer quote apply(tp.widenTermRefExpr) case tp => mapOver(tp) } - val holeType = if isTermHole then getTermHoleType(tpe) else getTypeHoleType(tpe) + /** Get the contents of the transformed tree */ + def getContents() = + val res = contents.result + contents.clear() + res + end HoleContentExtractor - Hole(isTermHole, idx, splices).withType(holeType).asInstanceOf[Hole] - } + val holeMaker = new HoleContentExtractor + val newTree = holeMaker.transform(tree) + (holeMaker.getContents(), newTree) - override def transform(tree: Tree)(using Context): Tree = - if (tree.source != ctx.source && tree.source.exists) - transform(tree)(using ctx.withSource(tree.source)) - else reporting.trace(i"Reifier.transform $tree at $level", show = true) { - tree match { - case Apply(TypeApply(fn, (body: RefTree) :: Nil), _) - if fn.symbol == defn.QuotedTypeModule_of && isCaptured(body.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `X` with `x$1: Type[X$1]` - // in `Type.of[X]` to `Type.of[x$1.Underlying]` and go directly to `X$1` - capturers(body.symbol)(body) - case Apply(Select(Apply(TypeApply(fn,_), List(ref: RefTree)),nme.apply),List(quotes)) - if fn.symbol == defn.QuotedRuntime_exprQuote && isCaptured(ref.symbol, level + 1) => - // Optimization: avoid the full conversion when capturing `x` with `x$1: Expr[X]` - // in `'{x}` to `'{ ${x$1} }'` and go directly to `x$1` - capturers(ref.symbol)(ref).select(nme.apply).appliedTo(quotes) - case tree: RefTree if isCaptured(tree.symbol, level) => - val body = capturers(tree.symbol).apply(tree) - if (tree.isType) - transformSpliceType(body, body.select(tpnme.Underlying)) - else - val splice = ref(defn.QuotedRuntime_exprSplice).appliedToType(tree.tpe).appliedTo(body) - transformSplice(body, splice) - - case tree: DefDef if tree.symbol.is(Macro) && level == 0 => - // Shrink size of the tree. The methods have already been inlined. - // TODO move to FirstTransform to trigger even without quotes - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) - - case tree: DefTree if level >= 1 => - val newAnnotations = tree.symbol.annotations.mapconserve { annot => - val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) - if (annot.tree == newAnnotTree) annot - else ConcreteAnnotation(newAnnotTree) - } - tree.symbol.annotations = newAnnotations - super.transform(tree) - case _ => - super.transform(tree) - } - } - private def liftList(list: List[Tree], tpe: Type)(using Context): Tree = - list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => - acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) - } - } -} + end makeHoles +} object PickleQuotes { import tpd._ @@ -529,34 +204,210 @@ object PickleQuotes { val name: String = "pickleQuotes" val description: String = "turn quoted trees into explicit run-time data structures" - def getLiteral(tree: tpd.Tree): Option[Literal] = tree match { - case tree: Literal => Some(tree) - case Block(Nil, e) => getLiteral(e) - case Inlined(_, Nil, e) => getLiteral(e) - case _ => None - } + def apply(quotes: Tree, body: Tree, contents: List[Tree], originalTp: Type, isType: Boolean)(using Context) = { + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + object reflect extends ReifiedReflect { + val quotesTree = quotes + } - class Embedded(trees: mutable.ListBuffer[tpd.Tree] = mutable.ListBuffer.empty, map: mutable.Map[Symbol, tpd.Tree] = mutable.Map.empty) { - /** Adds the tree and returns it's index */ - def addTree(tree: tpd.Tree, liftedSym: Symbol): Int = { - trees += tree - if (liftedSym ne NoSymbol) - map.put(liftedSym, tree) - trees.length - 1 + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => quotes.reflect.TreeMethods.asExpr( + * quotes.reflect.Literal.apply(x$1.reflect.Constant..apply()) + * ).asInstanceOf[scala.quoted.Expr[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsLiteral(lit: Literal) = { + val typeName = body.tpe.typeSymbol.name + val literalValue = + if lit.const.tag == Constants.NullTag || lit.const.tag == Constants.UnitTag then Nil + else List(body) + val constModule = lit.const.tag match + case Constants.BooleanTag => defn. Quotes_reflect_BooleanConstant + case Constants.ByteTag => defn. Quotes_reflect_ByteConstant + case Constants.ShortTag => defn. Quotes_reflect_ShortConstant + case Constants.IntTag => defn. Quotes_reflect_IntConstant + case Constants.LongTag => defn. Quotes_reflect_LongConstant + case Constants.FloatTag => defn. Quotes_reflect_FloatConstant + case Constants.DoubleTag => defn. Quotes_reflect_DoubleConstant + case Constants.CharTag => defn. Quotes_reflect_CharConstant + case Constants.StringTag => defn. Quotes_reflect_StringConstant + case Constants.UnitTag => defn. Quotes_reflect_UnitConstant + case Constants.NullTag => defn. Quotes_reflect_NullConstant + case Constants.ClazzTag => defn. Quotes_reflect_ClassOfConstant + reflect.asExpr(body.tpe) { + reflect.Literal { + reflect.self + .select(constModule) + .select(nme.apply) + .appliedToTermArgs(literalValue) + } + } } - /** Type used for the hole that will replace this splice */ - def getHoleType(body: tpd.Tree, splice: tpd.Tree)(using Context): Type = - // For most expressions the splice.tpe but there are some types that are lost by lifting - // that can be recoverd from the original tree. Currently the cases are: - // * Method types: the splice represents a method reference - map.get(body.symbol).map(_.tpe.widen).getOrElse(splice.tpe) + /** Encode quote using Reflection.Literal + * + * Generate the code + * ```scala + * quotes => scala.quoted.ToExpr.{BooleanToExpr,ShortToExpr, ...}.apply()(quotes) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def liftedValue(lit: Literal, lifter: Symbol) = + val exprType = defn.QuotedExprClass.typeRef.appliedTo(body.tpe) + ref(lifter).appliedToType(originalTp).select(nme.apply).appliedTo(lit).appliedTo(quotes) + + def pickleAsValue(lit: Literal) = { + // TODO should all constants be pickled as Literals? + // Should examine the generated bytecode size to decide and performance + lit.const.tag match { + case Constants.NullTag => pickleAsLiteral(lit) + case Constants.UnitTag => pickleAsLiteral(lit) + case Constants.BooleanTag => liftedValue(lit, defn.ToExprModule_BooleanToExpr) + case Constants.ByteTag => liftedValue(lit, defn.ToExprModule_ByteToExpr) + case Constants.ShortTag => liftedValue(lit, defn.ToExprModule_ShortToExpr) + case Constants.IntTag => liftedValue(lit, defn.ToExprModule_IntToExpr) + case Constants.LongTag => liftedValue(lit, defn.ToExprModule_LongToExpr) + case Constants.FloatTag => liftedValue(lit, defn.ToExprModule_FloatToExpr) + case Constants.DoubleTag => liftedValue(lit, defn.ToExprModule_DoubleToExpr) + case Constants.CharTag => liftedValue(lit, defn.ToExprModule_CharToExpr) + case Constants.StringTag => liftedValue(lit, defn.ToExprModule_StringToExpr) + } + } - def isLiftedSymbol(sym: Symbol)(using Context): Boolean = map.contains(sym) + /** Encode quote using QuoteUnpickler.{unpickleExpr, unpickleType} + * + * Generate the code + * ```scala + * quotes => quotes.asInstanceOf[QuoteUnpickler].[]( + * , + * , + * , + * ) + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def pickleAsTasty() = { + + val unpickleV1 = ctx.scalaRelease <= Release3_1 + + val pickleQuote = PickledQuotes.pickleQuote(body) + val pickledQuoteStrings = pickleQuote match + case x :: Nil => Literal(Constant(x)) + case xs => tpd.mkList(xs.map(x => Literal(Constant(x))), TypeTree(defn.StringType)) + + // TODO split holes earlier into types and terms. This all holes in each category can have consecutive indices + val (typeSplices, termSplices) = contents.zipWithIndex.partition { + _._1.tpe.derivesFrom(defn.QuotedTypeClass) + } - /** Get the list of embedded trees */ - def getTrees: List[tpd.Tree] = trees.toList + // This and all closures in typeSplices are removed by the BetaReduce phase + val types = + if unpickleV1 then + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents).map(name => UniqueName.fresh(name).toTermName), + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), + defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), + args => { + val cases = typeSplices.map { case (splice, idx) => + CaseDef(Literal(Constant(idx)), EmptyTree, splice) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + } + ) + else // if unpickleV2 then + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) + + // This and all closures in termSplices are removed by the BetaReduce phase + val termHoles = + if termSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else + Lambda( + MethodType( + List(nme.idx, nme.contents, nme.quotes).map(name => UniqueName.fresh(name).toTermName), + List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType), defn.QuotesClass.typeRef), + defn.QuotedExprClass.typeRef.appliedTo(defn.AnyType)), + args => + val cases = termSplices.map { case (splice, idx) => + val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _, _), _, _) = splice.tpe + val rhs = { + val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => + val argi = args(1).select(nme.apply).appliedTo(Literal(Constant(i))) + if unpickleV1 && argType.derivesFrom(defn.QuotedExprClass) then + val argType1 = defn.FunctionType(1).appliedTo(defn.QuotesClass.typeRef, argType) + argi.asInstance(argType1).select(nme.apply).appliedTo(args(2)) + else + argi.asInstance(argType) + } + val Block(List(ddef: DefDef), _) = splice + // TODO: beta reduce inner closure? Or wait until BetaReduce phase? + BetaReduce(ddef, spliceArgs).select(nme.apply).appliedTo(args(2).asInstance(quotesType)) + } + CaseDef(Literal(Constant(idx)), EmptyTree, rhs) + } + cases match + case CaseDef(_, _, rhs) :: Nil => rhs + case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) + ) + + val quoteClass = if isType then defn.QuotedTypeClass else defn.QuotedExprClass + val quotedType = quoteClass.typeRef.appliedTo(originalTp) + val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) + val unpickleMeth = + if unpickleV1 then + if isType then defn.QuoteUnpickler_unpickleType + else defn.QuoteUnpickler_unpickleExpr + else // if unpickleV2 then + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 + val unpickleArgs = + if isType && !unpickleV1 then List(pickledQuoteStrings, types) + else List(pickledQuoteStrings, types, termHoles) + quotes + .asInstance(defn.QuoteUnpicklerClass.typeRef) + .select(unpickleMeth).appliedToType(originalTp) + .appliedToArgs(unpickleArgs).withSpan(body.span) + } + + /** Encode quote using Reflection.TypeRepr.typeConstructorOf + * + * Generate the code + * ```scala + * quotes.reflect.TypeReprMethods.asType( + * quotes.reflect.TypeRepr.typeConstructorOf(classOf[]]) + * ).asInstanceOf[scala.quoted.Type[]] + * ``` + * this closure is always applied directly to the actual context and the BetaReduce phase removes it. + */ + def taggedType() = + reflect.asType(body.tpe) { + reflect.TypeRepr_typeConstructorOf( + TypeApply(ref(defn.Predef_classOf.termRef), body :: Nil) + ) + } - override def toString: String = s"Embedded($trees, $map)" + def getLiteral(tree: tpd.Tree): Option[Literal] = tree match + case tree: Literal => Some(tree) + case Block(Nil, e) => getLiteral(e) + case Inlined(_, Nil, e) => getLiteral(e) + case _ => None + + if (isType) then + if contents.isEmpty && body.symbol.isPrimitiveValueClass then taggedType() + else pickleAsTasty() + else + getLiteral(body) match + case Some(lit) => pickleAsValue(lit) + case _ => pickleAsTasty() } + } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3c6217cf5171..1b3ff3f0dd7e 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -339,7 +339,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tree: TypeApply => if tree.symbol.isQuote then ctx.compilationUnit.needsStaging = true - ctx.compilationUnit.needsQuotePickling = true if tree.symbol.is(Inline) then ctx.compilationUnit.needsInlining = true val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala new file mode 100644 index 000000000000..0d25053a64cb --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/ReifiedReflect.scala @@ -0,0 +1,110 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import tpd._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner + +import scala.annotation.constructorOnly + +/** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ +trait ReifiedReflect: + + /** Stable reference to the instance of `scala.quoted.Quotes` */ + def quotesTree: Tree + + def self(using Context): Tree = + quotesTree.select(defn.Quotes_reflect) + + /** Create type for `quotes.reflect.Term` */ + def TermTpt(using Context) = + self.select(defn.Quotes_reflect_TermType) + + /** Create type for `quotes.reflect.TypeTree` */ + def TypeTreeTpt(using Context) = + self.select(defn.Quotes_reflect_TypeTreeType) + + /** Create tree for `quotes.reflect.Apply(, List(*))` */ + def Apply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TermTpt) + self.select(defn.Quotes_reflect_Apply) + .select(defn.Quotes_reflect_Apply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.TypeApply(, List(*))` */ + def TypeApply(fn: Tree, args: List[Tree])(using Context) = + val argTrees = tpd.mkList(args, TypeTreeTpt) + self.select(defn.Quotes_reflect_TypeApply) + .select(defn.Quotes_reflect_TypeApply_apply) + .appliedTo(fn, argTrees) + + /** Create tree for `quotes.reflect.Assing(, )` */ + def Assign(lhs: Tree, rhs: Tree)(using Context) = + self.select(defn.Quotes_reflect_Assign) + .select(defn.Quotes_reflect_Assign_apply) + .appliedTo(lhs, rhs) + + /** Create tree for `quotes.reflect.Inferred()` */ + def Inferred(typeTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_Inferred) + .select(defn.Quotes_reflect_Inferred_apply) + .appliedTo(typeTree) + + /** Create tree for `quotes.reflect.Literal()` */ + def Literal(constant: Tree)(using Context) = + self.select(defn.Quotes_reflect_Literal) + .select(defn.Quotes_reflect_Literal_apply) + .appliedTo(constant) + + /** Create tree for `quotes.reflect.TypeRepr.of(Type.of[](quotes))` */ + def TypeReprOf(tpe: Type)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_of) + .appliedToType(tpe) + .appliedTo( + ref(defn.QuotedTypeModule_of) + .appliedToType(tpe) + .appliedTo(quotesTree) + ) + + /** Create tree for `quotes.reflect.TypeRepr.typeConstructorOf()` */ + def TypeRepr_typeConstructorOf(classTree: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeRepr) + .select(defn.Quotes_reflect_TypeRepr_typeConstructorOf) + .appliedTo(classTree) + + /** Create tree for `quotes.reflect.asTerm()` */ + def asTerm(expr: Tree)(using Context) = + self.select(defn.Quotes_reflect_asTerm) + .appliedTo(expr) + + /** Create tree for `quotes.reflect.TypeReprMethods.asType()` */ + def asType(tpe: Type)(typeRepr: Tree)(using Context) = + self.select(defn.Quotes_reflect_TypeReprMethods) + .select(defn.Quotes_reflect_TypeReprMethods_asType) + .appliedTo(typeRepr) + .asInstance(defn.QuotedTypeClass.typeRef.appliedTo(tpe)) + + /** Create tree for `quotes.reflect.TreeMethods.asExpr().asInstanceOf[]` */ + def asExpr(tpe: Type)(term: Tree)(using Context) = + self.select(defn.Quotes_reflect_TreeMethods) + .select(defn.Quotes_reflect_TreeMethods_asExpr) + .appliedTo(term) + .asInstance(defn.QuotedExprClass.typeRef.appliedTo(tpe)) + +end ReifiedReflect diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala new file mode 100644 index 000000000000..7f99ad57b2d0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -0,0 +1,398 @@ +package dotty.tools.dotc +package transform + +import core._ +import Decorators._ +import Flags._ +import Types._ +import Contexts._ +import Symbols._ +import Constants._ +import ast.Trees._ +import ast.{TreeTypeMap, untpd} +import util.Spans._ +import SymUtils._ +import NameKinds._ +import dotty.tools.dotc.ast.tpd +import StagingContext._ + +import scala.collection.mutable +import dotty.tools.dotc.core.Annotations._ +import dotty.tools.dotc.core.Names._ +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.quoted._ +import dotty.tools.dotc.transform.TreeMapWithStages._ +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.config.ScalaRelease.* + +import scala.annotation.constructorOnly + +object Splicing: + val name: String = "splicing" + +/** Transforms level 1 splices into holes. To do so it transforms the contents of the splice into + * a lambda that receives all cross-quote references. + * + * Cross-quote reference is a reference to a definition that is not defined in the current quote. + * Those references appear in quotes that are nested in a splice. + * + * After this phase we have the invariant where all splices have the following shape + * ``` + * {{{ | | * | (*) => }}} + * ``` + * where `` does not contain any free references to quoted definitions and `*` + * contains the quotes with references to all cross-quote references. There are some special rules + * for references in the LHS of assignments and cross-quote method references. + * + * In the following code example `x1` and `x2` are cross-quote references. + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * ${ (q: Quotes) ?=> f('{ g(x1, x2) }) }: T3 + * } + * ``` + * + * This phase identifies cross-quote references such as `x1` and replaces it with an `${x1$}`. + * All cross-quote arguments are directly applied in the lambda. + * + * ``` + * '{ ... + * val x1: T1 = ??? + * val x2: T2 = ??? + * {{{ 0 | T3 | x1, x2 | + * (x1$: Expr[T1], x2$: Expr[T2]) => // body of this lambda does not contain references to x1 or x2 + * (q: Quotes) ?=> f('{ g(${x1$}, ${x2$}) }) + * + * }}} + * } + * ``` + * + * and then performs the same transformation on `'{ g(${x1$}, ${x2$}) }`. + * + */ +class Splicing extends MacroTransform: + import tpd._ + + override def phaseName: String = Splicing.name + + override def run(using Context): Unit = + if ctx.compilationUnit.needsStaging then + super.run(using freshStagingContext) + + protected def newTransformer(using Context): Transformer = Level0QuoteTransformer + + /** Transforms all quotes at level 0 using the `QuoteTransformer` */ + private object Level0QuoteTransformer extends Transformer: + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + assert(level == 0) + tree match + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + QuoteTransformer().transform(tree) + case TypeApply(_, _) if tree.symbol == defn.QuotedTypeModule_of => + QuoteTransformer().transform(tree) + case tree: DefDef if tree.symbol.is(Inline) => + // Quotes in inlined methods are only pickled after they are inlined. + tree + case _ => + super.transform(tree) + end Level0QuoteTransformer + + + /** Transforms all direct splices in the current quote and replace them with holes. */ + private class QuoteTransformer() extends Transformer: + /** Set of definitions in the current quote */ + private val quotedDefs = mutable.Set.empty[Symbol] + + /** Number of holes created in this quote. Used for indexing holes. */ + private var numHoles = 0 + + /** Mapping from the term symbol of a `Type[T]` to it's hole. Used to deduplicate type holes. */ + private val typeHoles = mutable.Map.empty[Symbol, Hole] + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case Apply(fn, List(splicedCode)) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + if level > 1 then + val splicedCode1 = super.transform(splicedCode)(using spliceContext) + cpy.Apply(tree)(fn, List(splicedCode1)) + else + val holeIdx = numHoles + numHoles += 1 + val splicer = SpliceTransformer(ctx.owner, quotedDefs.contains) + val newSplicedCode1 = splicer.transformSplice(splicedCode, tree.tpe, holeIdx)(using spliceContext) + val newSplicedCode2 = Level0QuoteTransformer.transform(newSplicedCode1)(using spliceContext) + newSplicedCode2 + case tree: TypeDef if tree.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + val tp @ TypeRef(qual: TermRef, _) = tree.rhs.tpe.hiBound + quotedDefs += tree.symbol + val hole = typeHoles.get(qual.symbol) match + case Some (hole) => cpy.Hole(hole)(content = EmptyTree) + case None => + val holeIdx = numHoles + numHoles += 1 + val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) + typeHoles.put(qual.symbol, hole) + hole + val rhs = + if ctx.scalaRelease <= Release3_1 then + val secondHoleIdx = numHoles + numHoles += 1 + TypeBoundsTree(hole, cpy.Hole(hole)(idx = secondHoleIdx)) + else hole + cpy.TypeDef(tree)(rhs = rhs) + case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) + if fn.symbol == defn.QuotedRuntime_exprQuote => + super.transform(tree)(using quoteContext) + case _: Template => + for sym <- tree.symbol.owner.info.decls do + quotedDefs += sym + super.transform(tree) + case tree: DefTree => + quotedDefs += tree.symbol + transformAnnotations(tree) + super.transform(tree) + case _: TypeTree => + super.transform(tree).withType(transformAnnotTrees(tree.tpe)) + case _ => + super.transform(tree) + + private def transformAnnotations(tree: DefTree)(using Context): Unit = + tree.symbol.annotations = tree.symbol.annotations.mapconserve { annot => + val newAnnotTree = transform(annot.tree)(using ctx.withOwner(tree.symbol)) + if (annot.tree == newAnnotTree) annot + else ConcreteAnnotation(newAnnotTree) + } + + /** Transform trees within annotations */ + private def transformAnnotTrees(using Context) = new TypeMap { + override def apply(tp: Type): Type = { + tp match + case tp @ AnnotatedType(underlying, annot) => + val underlying1 = this(underlying) + derivedAnnotatedType(tp, underlying1, annot.derivedAnnotation(transform(annot.tree))) + case _ => mapOver(tp) + } + } + + end QuoteTransformer + + /** Transforms a splice at level 1 into a hole + * + * Finds all terms and types that are defined in the current quote and used within this splice. + * The resulting hole will contain all of these terms and types as arguments. + * Note that these captured variables are stage correct. + * + * For a `x` of type `T1` and a type `X` defined in the current quote + * ```scala + * ${ (using Quotes) ?=> {... x ... X ...} }: T2 + * ``` + * is transformed into + * ```scala + * {{{ | T2 | x, X | (x$1: Expr[T1], X$1: Type[X]) => (using Quotes) ?=> {... ${x$1} ... X$1.Underlying ...} }}} + * ``` + */ + private class SpliceTransformer(spliceOwner: Symbol, isCaptured: Symbol => Boolean) extends Transformer: + private var refBindingMap = mutable.Map.empty[Symbol, (Tree, Symbol)] + /** Reference to the `Quotes` instance of the current level 1 splice */ + private var quotes: Tree | Null = null // TODO: add to the context + private var healedTypes: PCPCheckAndHeal.QuoteTypeTags | Null = null // TODO: add to the context + + def transformSplice(tree: tpd.Tree, tpe: Type, holeIdx: Int)(using Context): tpd.Tree = + assert(level == 0) + val newTree = transform(tree) + val (refs, bindings) = refBindingMap.values.toList.unzip + val bindingsTypes = bindings.map(_.termRef.widenTermRefExpr) + val methType = MethodType(bindingsTypes, newTree.tpe) + val meth = newSymbol(spliceOwner, nme.ANON_FUN, Synthetic | Method, methType) + val ddef = DefDef(meth, List(bindings), newTree.tpe, newTree.changeOwner(ctx.owner, meth)) + val fnType = defn.FunctionType(bindings.size, isContextual = false).appliedTo(bindingsTypes :+ newTree.tpe) + val closure = Block(ddef :: Nil, Closure(Nil, ref(meth), TypeTree(fnType))) + tpd.Hole(true, holeIdx, refs, closure, TypeTree(tpe)) + + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = + tree match + case tree: RefTree => + if tree.isTerm then + if isCaptured(tree.symbol) then + val tpe = tree.tpe.widenTermRefExpr match { + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + } + spliced(tpe)(capturedTerm(tree)) + else super.transform(tree) + else // tree.isType then + if containsCapturedType(tree.tpe) then + if level >= 1 then getTagRefFor(tree) + else + // Dealias references to captured types + TypeTree(tree.tpe.dealias) + else super.transform(tree) + case tree: TypeTree => + if containsCapturedType(tree.tpe) && level >= 1 then getTagRefFor(tree) + else tree + case tree @ Assign(lhs: RefTree, rhs) => + if isCaptured(lhs.symbol) then transformSplicedAssign(tree) + else super.transform(tree) + case Apply(fn, args) if fn.symbol == defn.QuotedRuntime_exprNestedSplice => + val newArgs = args.mapConserve(arg => transform(arg)(using spliceContext)) + cpy.Apply(tree)(fn, newArgs) + case Apply(sel @ Select(app @ Apply(fn, args),nme.apply), quotesArgs) + if fn.symbol == defn.QuotedRuntime_exprQuote => + args match + case List(tree: RefTree) if isCaptured(tree.symbol) => + capturedTerm(tree) + case _ => + val newArgs = withCurrentQuote(quotesArgs.head) { + if level > 1 then args.mapConserve(arg => transform(arg)(using quoteContext)) + else args.mapConserve(arg => transformLevel0QuoteContent(arg)(using quoteContext)) + } + cpy.Apply(tree)(cpy.Select(sel)(cpy.Apply(app)(fn, newArgs), nme.apply), quotesArgs) + case Apply(TypeApply(_, List(tpt)), List(quotes)) + if tree.symbol == defn.QuotedTypeModule_of && containsCapturedType(tpt.tpe) => + ref(capturedType(tpt))(using ctx.withSource(tree.source)).withSpan(tree.span) + case CapturedApplication(fn, argss) => + transformCapturedApplication(tree, fn, argss) + case _ => + super.transform(tree) + + private def transformLevel0QuoteContent(tree: Tree)(using Context): Tree = + // transform and collect new healed types + val old = healedTypes + healedTypes = new PCPCheckAndHeal.QuoteTypeTags(tree.span) + val tree1 = transform(tree) + val newHealedTypes = healedTypes.nn.getTypeTags + healedTypes = old + // add new healed types to the current, merge with existing healed types if necessary + if newHealedTypes.isEmpty then tree1 + else tree1 match + case Block(stats @ (x :: _), expr) if x.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => + Block(newHealedTypes ::: stats, expr) + case _ => + Block(newHealedTypes, tree1) + + class ArgsClause(val args: List[Tree]): + def isTerm: Boolean = args.isEmpty || args.head.isTerm + + private object CapturedApplication { + + /** Matches and application `f(...)` (possibly with several argument clauses) where `f` is captured */ + def unapply(tree: Tree)(using Context): Option[(RefTree, List[ArgsClause])] = tree match + case GenericApply(fn: RefTree, args) if isCaptured(fn.symbol) => + Some((fn, ArgsClause(args) :: Nil)) + case GenericApply(CapturedApplication(fn, argss), args) => + Some((fn, argss :+ ArgsClause(args))) + case _ => + None + } + + private def containsCapturedType(tpe: Type)(using Context): Boolean = + tpe.existsPart(t => isCaptured(t.typeSymbol) || isCaptured(t.termSymbol), StopAt.Static) + + /** Transform an assignment `x = e` with a captured `x` to + * `${ Assign(x$1.asTerm, '{e}.asTerm).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `x` as a captured variable in the hole and creates an `x$1` `Expr` reference to it. + */ + private def transformSplicedAssign(tree: Assign)(using Context): Tree = + spliced(tree.tpe) { + reflect.asExpr(tree.tpe)( + reflect.Assign( + reflect.asTerm(capturedTerm(tree.lhs)), + reflect.asTerm(quoted(tree.rhs)) + ) + ) + } + + /** Transform an application `f(a1, a2, ...)` with a captured `f` to + * `${ Apply(f$1.asTerm, List('{a1$}.asTerm, '{a2$}.asTerm, ...)).asExpr.asInstanceOf[Expr[T]] }` + * + * Registers `f` as a captured variable in the hole and creates an `f$1` `Expr` reference to it. + * + * It also handles cases with multiple argument clauses using nested `Apply`/`TypeApply`. + */ + private def transformCapturedApplication(tree: Tree, fn: RefTree, argss: List[ArgsClause])(using Context): Tree = + spliced(tree.tpe) { + def TermList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.asTerm(quoted(transform(arg)(using spliceContext)))) + def TypeTreeList(args: List[Tree]): List[Tree] = + args.map(arg => reflect.Inferred(reflect.TypeReprOf(transform(arg)(using spliceContext).tpe))) + reflect.asExpr(tree.tpe) { + argss.foldLeft[Tree](reflect.asTerm(capturedTerm(fn, defn.AnyType))) { (acc, clause) => + if clause.isTerm then reflect.Apply(acc, TermList(clause.args)) + else reflect.TypeApply(acc, TypeTreeList(clause.args)) + } + } + } + + private def capturedTerm(tree: Tree)(using Context): Tree = + val tpe = tree.tpe.widenTermRefExpr match + case tpw: MethodicType => tpw.toFunctionType(isJava = false) + case tpw => tpw + capturedTerm(tree, tpe) + + private def capturedTerm(tree: Tree, tpe: Type)(using Context): Tree = + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(tree.symbol.name.toTermName).toTermName, + Param, + defn.QuotedExprClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (tree, newBinding))._2 + ref(bindingSym) + + private def capturedType(tree: Tree)(using Context): Symbol = + val tpe = tree.tpe.widenTermRefExpr + def newBinding = newSymbol( + spliceOwner, + UniqueName.fresh(nme.Type).toTermName, + Param, + defn.QuotedTypeClass.typeRef.appliedTo(tpe), + ) + val bindingSym = refBindingMap.getOrElseUpdate(tree.symbol, (TypeTree(tree.tpe), newBinding))._2 + bindingSym + + private def getTagRefFor(tree: Tree)(using Context): Tree = + val capturedTypeSym = capturedType(tree) + TypeTree(healedTypes.nn.getTagRef(capturedTypeSym.termRef)) + + private def withCurrentQuote[T](newQuotes: Tree)(body: => T)(using Context): T = + if level == 0 then + val savedQuotes = quotes + quotes = newQuotes + try body + finally quotes = savedQuotes + else body + + private def spliced(tpe: Type)(body: Context ?=> Tree)(using Context): Tree = + val exprTpe = defn.QuotedExprClass.typeRef.appliedTo(tpe) + val closure = + val methTpe = ContextualMethodType(List(defn.QuotesClass.typeRef), exprTpe) + val meth = newSymbol(ctx.owner, nme.ANON_FUN, Synthetic | Method, methTpe) + Closure(meth, argss => { + withCurrentQuote(argss.head.head) { + body(using ctx.withOwner(meth)).changeOwner(ctx.owner, meth) + } + }) + ref(defn.QuotedRuntime_exprNestedSplice) + .appliedToType(tpe) + .appliedTo(Literal(Constant(null))) // Dropped when creating the Hole that contains it + .appliedTo(closure) + + private def quoted(expr: Tree)(using Context): Tree = + val tpe = expr.tpe.widenTermRefExpr + ref(defn.QuotedRuntime_exprQuote) + .appliedToType(tpe) + .appliedTo(expr) + .select(nme.apply) + .appliedTo(quotes.nn) + + /** Helper methods to construct trees calling methods in `Quotes.reflect` based on the current `quotes` tree */ + private object reflect extends ReifiedReflect { + def quotesTree = quotes.nn + } + + end SpliceTransformer + +end Splicing diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index a327cff83950..1de050a9a6c1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -31,7 +31,7 @@ class Staging extends MacroTransform { override def allowsImplicitSearch: Boolean = true override def checkPostCondition(tree: Tree)(using Context): Unit = - if (ctx.phase <= pickleQuotesPhase) { + if (ctx.phase <= splicingPhase) { // Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald tree match { case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass => diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index bd3f4f44984b..1d3c4cd644f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -573,6 +573,37 @@ class TreeChecker extends Phase with SymTransformer { else super.typedPackageDef(tree) + override def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = { + val tree1 @ Hole(isTermHole, _, args, content, tpt) = super.typedHole(tree, pt) + + // Check result type of the hole + if isTermHole then assert(tpt.typeOpt <:< pt) + else assert(tpt.typeOpt =:= pt) + + // Check that the types of the args conform to the types of the contents of the hole + val argQuotedTypes = args.map { arg => + if arg.isTerm then + val tpe = arg.typeOpt.widenTermRefExpr match + case _: MethodicType => + // Special erasure for captured function references + // See `SpliceTransformer.transformCapturedApplication` + defn.AnyType + case tpe => tpe + defn.QuotedExprClass.typeRef.appliedTo(tpe) + else defn.QuotedTypeClass.typeRef.appliedTo(arg.typeOpt) + } + val expectedResultType = + if isTermHole then defn.QuotedExprClass.typeRef.appliedTo(tpt.typeOpt) + else defn.QuotedTypeClass.typeRef.appliedTo(tpt.typeOpt) + val contextualResult = + defn.FunctionOf(List(defn.QuotesClass.typeRef), expectedResultType, isContextual = true) + val expectedContentType = + defn.FunctionOf(argQuotedTypes, contextualResult) + assert(content.typeOpt =:= expectedContentType) + + tree1 + } + override def ensureNoLocalRefs(tree: Tree, pt: Type, localSyms: => List[Symbol])(using Context): Tree = tree diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 67bc68c4a495..d097ed8dcc4e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1629,12 +1629,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { case res => specializeEq(inlineIfNeeded(res)) } - if res.symbol == defn.QuotedRuntime_exprQuote then - ctx.compilationUnit.needsQuotePickling = true res override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + val tree1 = inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + if tree1.symbol.isQuote then + ctx.compilationUnit.needsStaging = true + tree1 override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = val tree1 = diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index f8422ed20e21..4a9102df211d 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -187,6 +187,10 @@ trait QuotesAndSplices { using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx))) pat.select(tpnme.Underlying) + def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree = + val tpt = typedType(tree.tpt) + assignType(tree, tpt) + private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit = if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline))) report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index e8924b418c3f..80068d8632e6 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -536,6 +536,10 @@ trait TypeAssigner { def assignType(tree: untpd.PackageDef, pid: Tree)(using Context): PackageDef = tree.withType(pid.symbol.termRef) + + def assignType(tree: untpd.Hole, tpt: Tree)(using Context): Hole = + tree.withType(tpt.tpe) + } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4e03f19d42f2..0f0e0a2a5744 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2901,6 +2901,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case tree: untpd.Splice => typedSplice(tree, pt) case tree: untpd.TypSplice => typedTypSplice(tree, pt) case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here + case tree: untpd.Hole => typedHole(tree, pt) case _ => typedUnadapted(desugar(tree), pt, locked) } diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 37da7dafd25a..da560ab6e240 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -3037,11 +3037,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler end reflect def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Expr[T] = - val tree = PickledQuotes.unpickleTerm(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V1(typeHole), PickledQuotes.ExprHole.V1(termHole)) + new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] + + def unpickleExprV2[T](pickled: String | List[String], types: Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] = + val tree = PickledQuotes.unpickleTerm(pickled, PickledQuotes.TypeHole.V2(types), PickledQuotes.ExprHole.V2(termHole)) new ExprImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Expr[T]] def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?]): scala.quoted.Type[T] = - val tree = PickledQuotes.unpickleTypeTree(pickled, typeHole, termHole) + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V1(typeHole)) + new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] + + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Seq[Type[?]]): scala.quoted.Type[T] = + val tree = PickledQuotes.unpickleTypeTree(pickled, PickledQuotes.TypeHole.V2(types)) new TypeImpl(tree, SpliceScope.getCurrent).asInstanceOf[scala.quoted.Type[T]] object ExprMatch extends ExprMatchModule: diff --git a/library/src/scala/quoted/runtime/QuoteUnpickler.scala b/library/src/scala/quoted/runtime/QuoteUnpickler.scala index 2d8ef54eb9e6..63e62658cbb4 100644 --- a/library/src/scala/quoted/runtime/QuoteUnpickler.scala +++ b/library/src/scala/quoted/runtime/QuoteUnpickler.scala @@ -7,11 +7,28 @@ trait QuoteUnpickler: /** Unpickle `repr` which represents a pickled `Expr` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleExpr[T](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Expr` tree, + * replacing splice nodes with `holes`. + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleExprV2[T](pickled: String | List[String], types: Null | Seq[Type[?]], termHole: Null | ((Int, Seq[Type[?] | Expr[Any]], Quotes) => Expr[?])): scala.quoted.Expr[T] + /** Unpickle `repr` which represents a pickled `Type` tree, * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.0.x and 3.1.x */ def unpickleType[T <: AnyKind](pickled: String | List[String], typeHole: (Int, Seq[Any]) => Type[?], termHole: (Int, Seq[Any], Quotes) => Expr[?]): scala.quoted.Type[T] + /** Unpickle `repr` which represents a pickled `Type` tree, + * replacing splice nodes with `holes` + * + * Generated for code compiled with Scala 3.2.0+ + */ + def unpickleTypeV2[T <: AnyKind](pickled: String | List[String], types: Null | Seq[Type[?]]): scala.quoted.Type[T] diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 2d41aca4fed1..6c68ce216685 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,7 +3,13 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - // Experimental APIs that can be added in 3.2.0 or later + // APIs that must be added in 3.2.0 + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), + + // Experimental APIs that can be added in 3.2.0 ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/app/Main.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/app/Main.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/build.sbt rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/build.sbt diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/lib/Top.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/lib/Top.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/project/DottyInjectedPlugin.scala rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/project/DottyInjectedPlugin.scala diff --git a/sbt-test/scala3-backcompat/hierarchical-mirrors/test b/sbt-test/scala3-compat/hierarchical-mirrors-3.0/test similarity index 100% rename from sbt-test/scala3-backcompat/hierarchical-mirrors/test rename to sbt-test/scala3-compat/hierarchical-mirrors-3.0/test diff --git a/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/sbt-test/scala3-compat/macros-backward-3.0/build.sbt b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt new file mode 100644 index 000000000000..6e5bd200ecb1 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.0/test b/sbt-test/scala3-compat/macros-backward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.0/test @@ -0,0 +1 @@ +> app/compile diff --git a/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/sbt-test/scala3-compat/macros-backward-3.1/build.sbt b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt new file mode 100644 index 000000000000..024e9be97524 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/build.sbt @@ -0,0 +1,13 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := "3.1.1", + scalacOptions ++= checkOptions, + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalacOptions ++= checkOptions, + ) diff --git a/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-backward-3.1/test b/sbt-test/scala3-compat/macros-backward-3.1/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-backward-3.1/test @@ -0,0 +1 @@ +> app/compile diff --git a/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala new file mode 100644 index 000000000000..ce7bf1b45b13 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala @@ -0,0 +1,17 @@ +package app + +import lib.* + +def powerTest(x: Double): Unit = + power(x, 0) + power(x, 1) + power(x, 5) + power(x, 10) + +def letTest: Unit = + let(0) { _ + 1 } + let(0) { _.toString } + let((4, 'a')) { _.swap } + let(new Foo) { _.hashCode } + +class Foo diff --git a/sbt-test/scala3-compat/macros-forward-3.0/build.sbt b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt new file mode 100644 index 000000000000..974fd6cff6cc --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt @@ -0,0 +1,14 @@ +lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") + +lazy val lib = project.in(file("lib")) + .settings( + scalacOptions ++= Seq("-scala-output-version", "3.0") ++ checkOptions + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalaVersion := "3.0.2", + scalacOptions ++= checkOptions, + dependencyOverrides += scalaOrganization.value %% "scala3-library" % scalaVersion.value, + ) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala new file mode 100644 index 000000000000..12e69a6ce4fd --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala @@ -0,0 +1,20 @@ +package lib + +import scala.quoted.* + +inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } + +private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = + unrolledPowerCode(x, n.valueOrError) + +private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = + if n == 0 then '{ 1.0 } // tests simple quotes without splices + else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices + else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture + + +inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } + +private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = + // tests use of Type + '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-test/scala3-compat/macros-forward-3.0/test b/sbt-test/scala3-compat/macros-forward-3.0/test new file mode 100644 index 000000000000..19aca297fdcf --- /dev/null +++ b/sbt-test/scala3-compat/macros-forward-3.0/test @@ -0,0 +1 @@ +> app/compile diff --git a/staging/src/scala/quoted/staging/QuoteCompiler.scala b/staging/src/scala/quoted/staging/QuoteCompiler.scala index 7b1899821f96..eee2dacdc5f5 100644 --- a/staging/src/scala/quoted/staging/QuoteCompiler.scala +++ b/staging/src/scala/quoted/staging/QuoteCompiler.scala @@ -16,7 +16,7 @@ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Types.ExprType import dotty.tools.dotc.quoted.PickledQuotes import dotty.tools.dotc.transform.Splicer.checkEscapedVariables -import dotty.tools.dotc.transform.{Inlining, Staging, PickleQuotes} +import dotty.tools.dotc.transform.{Inlining, Staging, Splicing, PickleQuotes} import dotty.tools.dotc.util.Spans.Span import dotty.tools.dotc.util.{SourceFile, NoSourcePosition} import dotty.tools.io.{Path, VirtualFile} @@ -41,6 +41,7 @@ private class QuoteCompiler extends Compiler: override protected def picklerPhases: List[List[Phase]] = List(new Inlining) :: List(new Staging) :: + List(new Splicing) :: List(new PickleQuotes) :: Nil diff --git a/tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala similarity index 100% rename from tests/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala rename to tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala diff --git a/tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala similarity index 100% rename from tests/pos-macros/forwardCompat-3.0/Test_2_c3.0.0.scala rename to tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/why.md b/tests/disabled/pos-macros/forwardCompat-3.0/why.md new file mode 100644 index 000000000000..efb05ec1e0f4 --- /dev/null +++ b/tests/disabled/pos-macros/forwardCompat-3.0/why.md @@ -0,0 +1,3 @@ +Fails `testCompilation` as if the release flag was not set. But it was and the compile used it. + +Manual tests show that this does work. diff --git a/tests/pos-macros/InlinedTypeOf.scala b/tests/pos-macros/InlinedTypeOf.scala new file mode 100644 index 000000000000..cd708eb02322 --- /dev/null +++ b/tests/pos-macros/InlinedTypeOf.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +class Sm[T](t: T) + +object Foo { + + inline def foo[T] = { compiletime.summonInline[Type[T]]; ??? } + + def toexpr[T: Type](using Quotes) = foo[Sm[T]] + +} diff --git a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala index fb06e93f91c0..868dae764e23 100644 --- a/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala +++ b/tests/pos-macros/backwardCompat-3.0/Macro_1_c3.0.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.0 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.0/Test_2.scala b/tests/pos-macros/backwardCompat-3.0/Test_2.scala index 8c0a8004b9cf..d88e018068c8 100644 --- a/tests/pos-macros/backwardCompat-3.0/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.0/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.0 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala index fb06e93f91c0..9bce5bb93e46 100644 --- a/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala +++ b/tests/pos-macros/backwardCompat-3.1/Macro_1_c3.1.0.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + // Does not compile on 3.1 + // inline def poly: Int = ${ polyCode } + // private def polyCode(using Quotes): Expr[Int] = + // def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + // '{ + // def f[T](x: T): T = ${ bar('x) } + // f[Int](1) + // } diff --git a/tests/pos-macros/backwardCompat-3.1/Test_2.scala b/tests/pos-macros/backwardCompat-3.1/Test_2.scala index 8c0a8004b9cf..b7613398981f 100644 --- a/tests/pos-macros/backwardCompat-3.1/Test_2.scala +++ b/tests/pos-macros/backwardCompat-3.1/Test_2.scala @@ -13,3 +13,6 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +// Does not compile on 3.1 +// def polyTest: Unit = poly diff --git a/tests/pos-macros/baseCompat/Macro_1.scala b/tests/pos-macros/baseCompat/Macro_1.scala index fb06e93f91c0..9dcf082ba023 100644 --- a/tests/pos-macros/baseCompat/Macro_1.scala +++ b/tests/pos-macros/baseCompat/Macro_1.scala @@ -18,3 +18,13 @@ object Macros: private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = // tests use of Type '{ val y: T = $x; $body(y): U } + + + inline def poly: Int = ${ polyCode } + + private def polyCode(using Quotes): Expr[Int] = + def bar[T: Type](x: Expr[T])(using Quotes): Expr[T] = x + '{ + def f[T](x: T): T = ${ bar('x) } + f[Int](1) + } diff --git a/tests/pos-macros/baseCompat/Test_2.scala b/tests/pos-macros/baseCompat/Test_2.scala index 8c0a8004b9cf..5bfed5960b3e 100644 --- a/tests/pos-macros/baseCompat/Test_2.scala +++ b/tests/pos-macros/baseCompat/Test_2.scala @@ -13,3 +13,5 @@ def letTest: Unit = let(new Foo) { _.hashCode } class Foo + +def polyTest: Unit = poly diff --git a/tests/pos-macros/captured-quoted-def-1/Macro_1.scala b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala new file mode 100644 index 000000000000..fadde58dd168 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y: Int = 1; ${ identity('y) } } diff --git a/tests/pos-macros/captured-quoted-def-1/Test_2.scala b/tests/pos-macros/captured-quoted-def-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-2/Macro_1.scala b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala new file mode 100644 index 000000000000..3d090311bca1 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(): Int = 1; ${ identity('{ y() }) } } diff --git a/tests/pos-macros/captured-quoted-def-2/Test_2.scala b/tests/pos-macros/captured-quoted-def-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-3/Macro_1.scala b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala new file mode 100644 index 000000000000..c3ccfdda56a4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int): Int = 1; ${ identity('{ y(1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-3/Test_2.scala b/tests/pos-macros/captured-quoted-def-3/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-3/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-4/Macro_1.scala b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala new file mode 100644 index 000000000000..e88084be84b3 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y(i: Int)(j: Int, k: Int): Int = 1; ${ identity('{ y(1)(2, 3) }) } } diff --git a/tests/pos-macros/captured-quoted-def-4/Test_2.scala b/tests/pos-macros/captured-quoted-def-4/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-4/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-def-5/Macro_1.scala b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala new file mode 100644 index 000000000000..b047ec26b621 --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Macro_1.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +inline def myMacro: Int = ${ myMacroExpr } + +private def myMacroExpr(using Quotes): Expr[Int] = + '{ def y[T](i: T): T = i; ${ identity('{ y[Int](1) }) } } diff --git a/tests/pos-macros/captured-quoted-def-5/Test_2.scala b/tests/pos-macros/captured-quoted-def-5/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-def-5/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala new file mode 100644 index 000000000000..533125bdef90 --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Macro_1.scala @@ -0,0 +1,9 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr('{1}) } + +def myMacroExpr(x: Expr[Int])(using Quotes): Expr[Any] = + '{ + def f(using q1: Quotes) = '{ 1 + ${Expr($x)} } + () + } diff --git a/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-multy-stage/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-1/Macro_1.scala b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala new file mode 100644 index 000000000000..fdea3ecf4f7f --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr(using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: Z = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-1/Test_2.scala b/tests/pos-macros/captured-quoted-type-1/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-1/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/captured-quoted-type-2/Macro_1.scala b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala new file mode 100644 index 000000000000..b9cf18de7ac4 --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Macro_1.scala @@ -0,0 +1,10 @@ +import scala.quoted.* + +inline def myMacro: Any = ${ myMacroExpr } + +def myMacroExpr[T: Type](using Quotes): Expr[Any] = + '{ + def f[Z] = + ${ identity('{ val y: (T, Z) = ??? }) } + 42 + } diff --git a/tests/pos-macros/captured-quoted-type-2/Test_2.scala b/tests/pos-macros/captured-quoted-type-2/Test_2.scala new file mode 100644 index 000000000000..76a9e17659db --- /dev/null +++ b/tests/pos-macros/captured-quoted-type-2/Test_2.scala @@ -0,0 +1 @@ +def test = myMacro diff --git a/tests/pos-macros/i12440.scala b/tests/pos-macros/i12440.scala new file mode 100644 index 000000000000..4b4c56fef568 --- /dev/null +++ b/tests/pos-macros/i12440.scala @@ -0,0 +1,21 @@ +import scala.quoted.* + +trait Mirror: + type ElemTypes <: Tuple + +class Eq: + + def test1(using Quotes): Unit = '{ + val m: Mirror = ??? + ${ summonType[m.ElemTypes]; ??? } + ${ summonType[List[m.ElemTypes]]; ??? } + } + + def test2(using Quotes): Unit = '{ + val m: Mirror = ??? + type ET = m.ElemTypes + ${ summonType[ET]; ??? } + ${ summonType[List[ET]]; ??? } + } + + def summonType[X](using Type[X]) = ??? diff --git a/tests/pos-macros/i13563.scala b/tests/pos-macros/i13563.scala new file mode 100644 index 000000000000..91a36f447d4a --- /dev/null +++ b/tests/pos-macros/i13563.scala @@ -0,0 +1,3 @@ +import scala.quoted.* +def foo(using Quotes): Unit = + '{ def bar[T](): Unit = ${ summon[Type[T]]; ??? }; () } diff --git a/tests/pos-macros/i13732.scala b/tests/pos-macros/i13732.scala new file mode 100644 index 000000000000..88d987817ca7 --- /dev/null +++ b/tests/pos-macros/i13732.scala @@ -0,0 +1,6 @@ +import scala.quoted.* + +def generateImpl(using q: Quotes): Expr[Unit] = + '{ def runEffect[T]: T = ${ runEffectImpl[T] } } + +inline def runEffectImpl[T: Type]: Expr[T] = ??? diff --git a/tests/pos-macros/i4774f.scala b/tests/pos-macros/i4774f.scala index 59d21eb2d2d4..336f20e19280 100644 --- a/tests/pos-macros/i4774f.scala +++ b/tests/pos-macros/i4774f.scala @@ -7,4 +7,10 @@ object Test { def loop2[T](x: Expr[T])(implicit t: Type[T], qctx: Quotes): Expr[T] = '{ def y(): T = $x; ${ loop2('{y()}) } } + + def loop3[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int): T = $x; ${ loop2('{y(1)}) } } + + def loop4[T](x: Expr[T])(using Type[T], Quotes): Expr[T] = + '{ def y(i: Int)(j: Int): T = $x; ${ loop2('{y(1)(2)}) } } } diff --git a/tests/pos-macros/i8100.scala b/tests/pos-macros/i8100.scala index 0cf80fe920ee..1a6b5f3d5ba6 100644 --- a/tests/pos-macros/i8100.scala +++ b/tests/pos-macros/i8100.scala @@ -14,8 +14,8 @@ def f[T: Type](using Quotes) = ${ g[m.E](using Type.of[ME]) } ${ g[ME](using Type.of[m.E]) } ${ g[m.E](using Type.of[m.E]) } - // ${ g[ME] } // FIXME: issue seems to be in PickleQuotes - // ${ g[m.E] } // FIXME: issue seems to be in PickleQuotes + ${ g[ME] } + ${ g[m.E] } } def g[T](using Type[T]) = ??? diff --git a/tests/pos-macros/typetags.scala b/tests/pos-macros/typetags.scala index 3a6e60c2e91c..2cf285888272 100644 --- a/tests/pos-macros/typetags.scala +++ b/tests/pos-macros/typetags.scala @@ -7,5 +7,9 @@ object Test { implicitly[Type[List[Int]]] implicitly[Type[T]] implicitly[Type[List[T]]] + Type.of[Int] + Type.of[List[Int]] + Type.of[T] + Type.of[List[T]] } } From ca01392cd6a46ab55c84f7cb5a1067814b950a8f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 19 Apr 2022 17:07:57 +0200 Subject: [PATCH 098/121] Remove support -scala-output-version with quotes --- .../dotty/tools/dotc/core/Definitions.scala | 2 - .../tools/dotc/quoted/PickledQuotes.scala | 4 +- .../tools/dotc/transform/PickleQuotes.scala | 42 +++---------------- .../dotty/tools/dotc/transform/Splicing.scala | 8 +--- .../macros-forward-3.0/app/App.scala | 17 -------- .../macros-forward-3.0/build.sbt | 14 ------- .../macros-forward-3.0/lib/Macro.scala | 20 --------- .../project/DottyInjectedPlugin.scala | 11 ----- .../scala3-compat/macros-forward-3.0/test | 1 - .../forwardCompat-3.0/Macro_1_r3.0.scala | 20 --------- .../forwardCompat-3.0/Test_2_c3.0.2.scala | 15 ------- .../pos-macros/forwardCompat-3.0/why.md | 3 -- .../forwardCompat-3.1/Macro_1_r3.1.scala | 20 --------- .../forwardCompat-3.1/Test_2_c3.1.0.scala | 15 ------- .../pos-macros/forwardCompat-3.1/why.md | 1 - 15 files changed, 9 insertions(+), 184 deletions(-) delete mode 100644 sbt-test/scala3-compat/macros-forward-3.0/app/App.scala delete mode 100644 sbt-test/scala3-compat/macros-forward-3.0/build.sbt delete mode 100644 sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala delete mode 100644 sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala delete mode 100644 sbt-test/scala3-compat/macros-forward-3.0/test delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.0/why.md delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala delete mode 100644 tests/disabled/pos-macros/forwardCompat-3.1/why.md diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 18fbf977bbc2..e09b7ca98955 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -827,9 +827,7 @@ class Definitions { @tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler") - @tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr") @tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2") - @tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType") @tu lazy val QuoteUnpickler_unpickleTypeV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleTypeV2") @tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching") diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index b3a4783a783c..2e0454c3a7aa 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -50,7 +50,7 @@ object PickledQuotes { /** `typeHole`/`types` argument of `QuoteUnpickler.{unpickleExpr,unpickleExprV2,unpickleType,unpickleTypeV2}` */ enum TypeHole: /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. - * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 + * From code compiled with Scala 3.0.x and 3.1.x. * Note: For `unpickleType` it will always be `null`. */ case V1(evalHole: Null | ((Int, Seq[scala.quoted.Type[?]]) => scala.quoted.Type[?])) @@ -65,7 +65,7 @@ object PickledQuotes { enum ExprHole: /** `termHole` argument of `QuoteUnpickler.{unpickleExpr, unpickleType}`. - * From code compiled with Scala 3.0.x and 3.1.x or with -scala-output-version 3.0 and 3.1 + * From code compiled with Scala 3.0.x and 3.1.x. * Note: For `unpickleType` it will always be `null`. */ case V1(evalHole: Null | ((Int, Seq[ExprHole.ArgV1], scala.quoted.Quotes) => scala.quoted.Expr[?])) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 40e3526861d0..a12ef6d50bed 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -291,9 +291,6 @@ object PickleQuotes { * this closure is always applied directly to the actual context and the BetaReduce phase removes it. */ def pickleAsTasty() = { - - val unpickleV1 = ctx.scalaRelease <= Release3_1 - val pickleQuote = PickledQuotes.pickleQuote(body) val pickledQuoteStrings = pickleQuote match case x :: Nil => Literal(Constant(x)) @@ -306,26 +303,8 @@ object PickleQuotes { // This and all closures in typeSplices are removed by the BetaReduce phase val types = - if unpickleV1 then - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible - else - Lambda( - MethodType( - List(nme.idx, nme.contents).map(name => UniqueName.fresh(name).toTermName), - List(defn.IntType, defn.SeqType.appliedTo(defn.AnyType)), - defn.QuotedTypeClass.typeRef.appliedTo(WildcardType)), - args => { - val cases = typeSplices.map { case (splice, idx) => - CaseDef(Literal(Constant(idx)), EmptyTree, splice) - } - cases match - case CaseDef(_, _, rhs) :: Nil => rhs - case _ => Match(args(0).annotated(New(ref(defn.UncheckedAnnot.typeRef))), cases) - } - ) - else // if unpickleV2 then - if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible - else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) + if typeSplices.isEmpty then Literal(Constant(null)) // keep pickled quote without contents as small as possible + else SeqLiteral(typeSplices.map(_._1), TypeTree(defn.QuotedTypeClass.typeRef.appliedTo(WildcardType))) // This and all closures in termSplices are removed by the BetaReduce phase val termHoles = @@ -341,12 +320,7 @@ object PickleQuotes { val defn.FunctionOf(argTypes, defn.FunctionOf(quotesType :: _, _, _, _), _, _) = splice.tpe val rhs = { val spliceArgs = argTypes.zipWithIndex.map { (argType, i) => - val argi = args(1).select(nme.apply).appliedTo(Literal(Constant(i))) - if unpickleV1 && argType.derivesFrom(defn.QuotedExprClass) then - val argType1 = defn.FunctionType(1).appliedTo(defn.QuotesClass.typeRef, argType) - argi.asInstance(argType1).select(nme.apply).appliedTo(args(2)) - else - argi.asInstance(argType) + args(1).select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argType) } val Block(List(ddef: DefDef), _) = splice // TODO: beta reduce inner closure? Or wait until BetaReduce phase? @@ -363,14 +337,10 @@ object PickleQuotes { val quotedType = quoteClass.typeRef.appliedTo(originalTp) val lambdaTpe = MethodType(defn.QuotesClass.typeRef :: Nil, quotedType) val unpickleMeth = - if unpickleV1 then - if isType then defn.QuoteUnpickler_unpickleType - else defn.QuoteUnpickler_unpickleExpr - else // if unpickleV2 then - if isType then defn.QuoteUnpickler_unpickleTypeV2 - else defn.QuoteUnpickler_unpickleExprV2 + if isType then defn.QuoteUnpickler_unpickleTypeV2 + else defn.QuoteUnpickler_unpickleExprV2 val unpickleArgs = - if isType && !unpickleV1 then List(pickledQuoteStrings, types) + if isType then List(pickledQuoteStrings, types) else List(pickledQuoteStrings, types, termHoles) quotes .asInstance(defn.QuoteUnpicklerClass.typeRef) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicing.scala b/compiler/src/dotty/tools/dotc/transform/Splicing.scala index 7f99ad57b2d0..fe0c233173b7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicing.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicing.scala @@ -135,13 +135,7 @@ class Splicing extends MacroTransform: val hole = tpd.Hole(false, holeIdx, Nil, ref(qual), TypeTree(tp)) typeHoles.put(qual.symbol, hole) hole - val rhs = - if ctx.scalaRelease <= Release3_1 then - val secondHoleIdx = numHoles - numHoles += 1 - TypeBoundsTree(hole, cpy.Hole(hole)(idx = secondHoleIdx)) - else hole - cpy.TypeDef(tree)(rhs = rhs) + cpy.TypeDef(tree)(rhs = hole) case Apply(Select(Apply(TypeApply(fn,_), List(code)),nme.apply),List(quotes)) if fn.symbol == defn.QuotedRuntime_exprQuote => super.transform(tree)(using quoteContext) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala b/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala deleted file mode 100644 index ce7bf1b45b13..000000000000 --- a/sbt-test/scala3-compat/macros-forward-3.0/app/App.scala +++ /dev/null @@ -1,17 +0,0 @@ -package app - -import lib.* - -def powerTest(x: Double): Unit = - power(x, 0) - power(x, 1) - power(x, 5) - power(x, 10) - -def letTest: Unit = - let(0) { _ + 1 } - let(0) { _.toString } - let((4, 'a')) { _.swap } - let(new Foo) { _.hashCode } - -class Foo diff --git a/sbt-test/scala3-compat/macros-forward-3.0/build.sbt b/sbt-test/scala3-compat/macros-forward-3.0/build.sbt deleted file mode 100644 index 974fd6cff6cc..000000000000 --- a/sbt-test/scala3-compat/macros-forward-3.0/build.sbt +++ /dev/null @@ -1,14 +0,0 @@ -lazy val checkOptions = Seq("-Xcheck-macros", "-Ycheck:all", "-Yno-double-bindings") - -lazy val lib = project.in(file("lib")) - .settings( - scalacOptions ++= Seq("-scala-output-version", "3.0") ++ checkOptions - ) - -lazy val app = project.in(file("app")) - .dependsOn(lib) - .settings( - scalaVersion := "3.0.2", - scalacOptions ++= checkOptions, - dependencyOverrides += scalaOrganization.value %% "scala3-library" % scalaVersion.value, - ) diff --git a/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala b/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala deleted file mode 100644 index 12e69a6ce4fd..000000000000 --- a/sbt-test/scala3-compat/macros-forward-3.0/lib/Macro.scala +++ /dev/null @@ -1,20 +0,0 @@ -package lib - -import scala.quoted.* - -inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - -private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - -private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - -inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - -private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala b/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala deleted file mode 100644 index fb946c4b8c61..000000000000 --- a/sbt-test/scala3-compat/macros-forward-3.0/project/DottyInjectedPlugin.scala +++ /dev/null @@ -1,11 +0,0 @@ -import sbt._ -import Keys._ - -object DottyInjectedPlugin extends AutoPlugin { - override def requires = plugins.JvmPlugin - override def trigger = allRequirements - - override val projectSettings = Seq( - scalaVersion := sys.props("plugin.scalaVersion") - ) -} diff --git a/sbt-test/scala3-compat/macros-forward-3.0/test b/sbt-test/scala3-compat/macros-forward-3.0/test deleted file mode 100644 index 19aca297fdcf..000000000000 --- a/sbt-test/scala3-compat/macros-forward-3.0/test +++ /dev/null @@ -1 +0,0 @@ -> app/compile diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.0/Macro_1_r3.0.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala b/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala deleted file mode 100644 index 8c0a8004b9cf..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.0/Test_2_c3.0.2.scala +++ /dev/null @@ -1,15 +0,0 @@ -import Macros.* - -def powerTest(x: Double): Unit = - power(x, 0) - power(x, 1) - power(x, 5) - power(x, 10) - -def letTest: Unit = - let(0) { _ + 1 } - let(0) { _.toString } - let((4, 'a')) { _.swap } - let(new Foo) { _.hashCode } - -class Foo diff --git a/tests/disabled/pos-macros/forwardCompat-3.0/why.md b/tests/disabled/pos-macros/forwardCompat-3.0/why.md deleted file mode 100644 index efb05ec1e0f4..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.0/why.md +++ /dev/null @@ -1,3 +0,0 @@ -Fails `testCompilation` as if the release flag was not set. But it was and the compile used it. - -Manual tests show that this does work. diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala deleted file mode 100644 index fb06e93f91c0..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Macro_1_r3.1.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.quoted.* - -object Macros: - - inline def power(x: Double, inline n: Int) = ${ powerCode('x, 'n) } - - private def powerCode(x: Expr[Double], n: Expr[Int])(using Quotes): Expr[Double] = - unrolledPowerCode(x, n.valueOrError) - - private def unrolledPowerCode(x: Expr[Double], n: Int)(using Quotes): Expr[Double] = - if n == 0 then '{ 1.0 } // tests simple quotes without splices - else if n % 2 == 1 then '{ $x * ${ unrolledPowerCode(x, n - 1) } } // tests simple splices - else '{ val y = $x * $x; ${ unrolledPowerCode('y, n / 2) } } // tests splice with term capture - - - inline def let[T, U](x: T)(inline body: T => U): U = ${ letCode('x, 'body) } - - private def letCode[T: Type, U: Type](x: Expr[T], body: Expr[T => U])(using Quotes): Expr[U] = - // tests use of Type - '{ val y: T = $x; $body(y): U } diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala b/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala deleted file mode 100644 index 8c0a8004b9cf..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/Test_2_c3.1.0.scala +++ /dev/null @@ -1,15 +0,0 @@ -import Macros.* - -def powerTest(x: Double): Unit = - power(x, 0) - power(x, 1) - power(x, 5) - power(x, 10) - -def letTest: Unit = - let(0) { _ + 1 } - let(0) { _.toString } - let((4, 'a')) { _.swap } - let(new Foo) { _.hashCode } - -class Foo diff --git a/tests/disabled/pos-macros/forwardCompat-3.1/why.md b/tests/disabled/pos-macros/forwardCompat-3.1/why.md deleted file mode 100644 index f281f9c08662..000000000000 --- a/tests/disabled/pos-macros/forwardCompat-3.1/why.md +++ /dev/null @@ -1 +0,0 @@ -Disabled until https://github.com/lampepfl/dotty/issues/14306 is fixed From 8f5ee23c4debdd502ba25a6d9c760c46cb49490f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 20 Apr 2022 08:48:52 +0100 Subject: [PATCH 099/121] Expand on TreeNodeChecker's docs --- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index be0a8420cdfd..b89af15488f2 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -648,7 +648,11 @@ object TreeChecker { } }.apply(tp0) - /** Check that the tree only contains legal children trees */ + /** Run some additional checks on the nodes of the trees. Specifically: + * + * - TypeTree can only appear in TypeApply args, New, Typed tpt, Closure + * tpt, SeqLiteral elemtpt, ValDef tpt, DefDef tpt, and TypeDef rhs. + */ object TreeNodeChecker extends untpd.TreeTraverser: import untpd._ def traverse(tree: Tree)(using Context) = tree match From 1ff3dbf2ad084c42ad0f6eea42f616177ed844e1 Mon Sep 17 00:00:00 2001 From: Vadim Chelyshov Date: Wed, 20 Apr 2022 12:22:59 +0300 Subject: [PATCH 100/121] fix: correction to ClassTooLargeException message --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index a10a146e798b..8a525b2f8091 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -581,7 +581,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim report.error(msg) case e: ClassTooLargeException => val msg = - s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64KB" + s"Class '${e.getClassName.replaceAll("/", ".")}' is too large. Constant pool size: ${e.getConstantPoolCount}. Limit is 64K entries" report.error(msg) } From 5d90b1acb96a814e245d7373763d00ab660f1cc6 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 20 Apr 2022 11:27:19 +0100 Subject: [PATCH 101/121] Upgrade MiMa, configure @experimental & curtail filters --- project/Build.scala | 2 ++ project/MiMaFilters.scala | 20 +++++--------------- project/plugins.sbt | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 249c37efd98d..bc8a6157803d 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -439,6 +439,8 @@ object Build { case CompatMode.BinaryCompatible => "backward" case CompatMode.SourceAndBinaryCompatible => "both" }), + + mimaExcludeAnnotations += "scala.annotation.experimental", ) /** Projects -------------------------------------------------------------- */ diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 6c68ce216685..b3e4aabb6376 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,28 +3,18 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( + ProblemFilters.exclude[Problem]("scala.runtime.*"), // KEEP: scala.runtime isn't public API + // APIs that must be added in 3.2.0 ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleTypeV2"), - // Experimental APIs that can be added in 3.2.0 - ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.asQuotes"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ClassDefModule.apply"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolModule.newClass"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.termRef"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeTreeModule.ref"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#AppliedTypeModule.apply"), - // Experimental `MainAnnotation` APIs. Can be added in 3.3.0 or later. - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation"), - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$"), - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Command"), - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$CommandInfo"), - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterInfo"), + // MiMa bug: classes nested in an experimental object should be ignored + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Info"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$Parameter"), ProblemFilters.exclude[MissingClassProblem]("scala.annotation.MainAnnotation$ParameterAnnotation"), ) } diff --git a/project/plugins.sbt b/project/plugins.sbt index 9d743cec8252..f0b13c6cb7c4 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -14,4 +14,4 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.3.2") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.9.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.9.0") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.0") From 1e18c01e9e9e52989d46fd37f1819330110d4859 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 20 Apr 2022 13:12:53 +0200 Subject: [PATCH 102/121] deprecate 3.1-migration, err on import --- compiler/src/dotty/tools/dotc/config/SourceVersion.scala | 9 ++++++++- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 9 +++++++++ library/src/scala/runtime/stdLibPatches/language.scala | 3 +++ tests/neg/i12457.scala | 2 +- tests/neg/source-import-3-1-migration.check | 4 ++++ tests/neg/source-import-3-1-migration.scala | 1 + tests/pos/source-import-3-1-migration.scala | 1 - 7 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 tests/neg/source-import-3-1-migration.check create mode 100644 tests/neg/source-import-3-1-migration.scala delete mode 100644 tests/pos/source-import-3-1-migration.scala diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index 9361c7e6774d..4c61ddd036ae 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -17,5 +17,12 @@ enum SourceVersion: object SourceVersion extends Property.Key[SourceVersion]: - val allSourceVersionNames = values.toList.map(_.toString.toTermName) + /** language versions that may appear in a language import, are deprecated, but not removed from the standard library. */ + val illegalSourceVersionNames = List("3.1-migration").map(_.toTermName) + + /** language versions that the compiler recognises. */ + val validSourceVersionNames = values.toList.map(_.toString.toTermName) + + /** All source versions that can be recognised from a language import. e.g. `import language.3.1` */ + val allSourceVersionNames = validSourceVersionNames ::: illegalSourceVersionNames end SourceVersion diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8fab2e4074b9..0f243fdf7088 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3150,6 +3150,15 @@ object Parsers { syntaxError(i"source version import is only allowed at the toplevel", id.span) else if ctx.compilationUnit.sourceVersion.isDefined then syntaxError(i"duplicate source version import", id.span) + else if illegalSourceVersionNames.contains(imported) then + val candidate = + val nonMigration = imported.toString.replace("-migration", "") + validSourceVersionNames.find(_.show == nonMigration) + val baseMsg = i"`$imported` is not a valid source version" + val msg = candidate match + case Some(member) => i"$baseMsg, did you mean language.`$member`?" + case _ => baseMsg + syntaxError(msg, id.span) else ctx.compilationUnit.sourceVersion = Some(SourceVersion.valueOf(imported.toString)) case None => diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 7c8c116a106e..683155fb0e48 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -149,10 +149,13 @@ object language: object `3.0` /** Set source version to 3.1-migration. + * + * This is a no-op, and should not be used. A syntax error will be reported upon import. * * @see [[https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html]] */ @compileTimeOnly("`3.1-migration` can only be used at compile time in import statements") + @deprecated("`3.1-migration` is not valid, use `3.1` instead", since = "3.2") object `3.1-migration` /** Set source version to 3.1 diff --git a/tests/neg/i12457.scala b/tests/neg/i12457.scala index 8ebc061d6d59..050597fb341d 100644 --- a/tests/neg/i12457.scala +++ b/tests/neg/i12457.scala @@ -1,3 +1,3 @@ -import language.`3.1-migration` +import language.`3.1` trait X [ X <: Z , Z >: X [ R ] ] // error diff --git a/tests/neg/source-import-3-1-migration.check b/tests/neg/source-import-3-1-migration.check new file mode 100644 index 000000000000..d88e72a6871e --- /dev/null +++ b/tests/neg/source-import-3-1-migration.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/source-import-3-1-migration.scala:1:16 ------------------------------------------------------------- +1 |import language.`3.1-migration` // error + | ^^^^^^^^^^^^^^^ + | `3.1-migration` is not a valid source version, did you mean language.`3.1`? diff --git a/tests/neg/source-import-3-1-migration.scala b/tests/neg/source-import-3-1-migration.scala new file mode 100644 index 000000000000..0e84eaea26ea --- /dev/null +++ b/tests/neg/source-import-3-1-migration.scala @@ -0,0 +1 @@ +import language.`3.1-migration` // error diff --git a/tests/pos/source-import-3-1-migration.scala b/tests/pos/source-import-3-1-migration.scala deleted file mode 100644 index 6e6f9f905b99..000000000000 --- a/tests/pos/source-import-3-1-migration.scala +++ /dev/null @@ -1 +0,0 @@ -import language.`3.1-migration` From b829b72ba4044ad2d99035e789f9a32606e2a572 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 20 Apr 2022 16:29:53 +0200 Subject: [PATCH 103/121] Cover holes in any part of the prefix --- .../src/scala/quoted/runtime/impl/QuoteMatcher.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 0d4a6a463274..654fa94693c6 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -244,9 +244,12 @@ object QuoteMatcher { case Select(qual2, _) if symbolMatch(scrutinee, pattern) => ref match case Select(qual1, _) => qual1 =?= qual2 - case ref: Ident if qual2.symbol == defn.QuotedRuntimePatterns_patternHole => - tpd.desugarIdentPrefix(ref) =?= qual2 - case ref: Ident => matched + case ref: Ident => + if qual2.existsSubTree(_.symbol == defn.QuotedRuntimePatterns_patternHole) then + // Prefix has a hole, so we need to match the prefix to extract the value of the hole + tpd.desugarIdentPrefix(ref) =?= qual2 + else + matched /* Match reference */ case _: Ident if symbolMatch(scrutinee, pattern) => matched From bfa51c7b9d0b8f7a493af246a0c095a8143ddb79 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Wed, 20 Apr 2022 16:05:43 +0100 Subject: [PATCH 104/121] Drop scala.runtime glob filter See PR comments: we don't want to break things by dropping APIs used by compiled code. --- project/MiMaFilters.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index b3e4aabb6376..ef450919d806 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -3,8 +3,6 @@ import com.typesafe.tools.mima.core._ object MiMaFilters { val Library: Seq[ProblemFilter] = Seq( - ProblemFilters.exclude[Problem]("scala.runtime.*"), // KEEP: scala.runtime isn't public API - // APIs that must be added in 3.2.0 ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.runtime.QuoteUnpickler.unpickleExprV2"), From 0f207e56c4bfb7c0bba06d75e34df2abe7b19687 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 20 Apr 2022 17:45:26 +0200 Subject: [PATCH 105/121] fix #13346: add regression test --- tests/pos/i13346.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/pos/i13346.scala diff --git a/tests/pos/i13346.scala b/tests/pos/i13346.scala new file mode 100644 index 000000000000..25702cfe3082 --- /dev/null +++ b/tests/pos/i13346.scala @@ -0,0 +1,19 @@ +object Outer_Typed { + object Inner { + class ClassUntyped(x: Int) + class ClassTyped[T](x: T) // notice type parameter + + // ok + val _ = new ClassUntyped(42) + val _ = new ClassTyped("42") + val _ = ClassUntyped(42) + val _ = ClassTyped("42") + } + + export Inner._ + + val _ = new ClassUntyped(42) + val _ = new ClassTyped("42") + val _ = ClassUntyped(42) + val _ = ClassTyped("42") // error: Not found: ClassTyped +} From 8d1c43578b95e4e608eedd33ec221f140dfd936c Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Apr 2022 18:41:52 +0200 Subject: [PATCH 106/121] Avoid export clashes Avoid export clashes in most cases by picking the alternative which would be preferred by overloading resolution. If none is, issue a more intelligible error message than before. --- .../src/dotty/tools/dotc/typer/Namer.scala | 59 ++++++++++++++++++- tests/neg/i14966.check | 20 +++---- tests/neg/i14966.scala | 13 +++- tests/pos/i14966.scala | 13 ++++ tests/pos/i14966a.scala | 2 + 5 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 tests/pos/i14966.scala create mode 100644 tests/pos/i14966a.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index aac0288aa771..8a147d388ad0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -24,6 +24,7 @@ import Inferencing._ import transform.ValueClasses._ import transform.TypeUtils._ import transform.SymUtils._ +import TypeErasure.erasure import reporting._ import config.Feature.sourceVersion import config.SourceVersion._ @@ -1237,8 +1238,64 @@ class Namer { typer: Typer => addForwarders(sels1, sel.name :: seen) case _ => + /** Avoid a clash of export forwarder `forwarder` with other forwarders in `forwarders`. + * @return If `forwarder` clashes, a new leading forwarder and trailing forwarders list + * that avoids the clash according to the scheme described in `avoidClashes`. + * If there's no clash, the inputs as they are in a pair. + */ + def avoidClashWith(forwarder: tpd.DefDef, forwarders: List[tpd.MemberDef]): (tpd.DefDef, List[tpd.MemberDef]) = + def clashes(fwd1: Symbol, fwd2: Symbol) = + fwd1.targetName == fwd2.targetName + && erasure(fwd1.info).signature == erasure(fwd2.info).signature + + forwarders match + case forwarders @ ((forwarder1: tpd.DefDef) :: forwarders1) + if forwarder.name == forwarder1.name => + if clashes(forwarder.symbol, forwarder1.symbol) then + val alt1 = tpd.methPart(forwarder.rhs).tpe + val alt2 = tpd.methPart(forwarder1.rhs).tpe + val cmp = alt1 match + case alt1: TermRef => alt2 match + case alt2: TermRef => compare(alt1, alt2) + case _ => 0 + case _ => 0 + if cmp == 0 then + report.error( + ex"""Clashing exports: The exported + | ${forwarder.rhs.symbol}: ${alt1.widen} + |and ${forwarder1.rhs.symbol}: ${alt2.widen} + |have the same signature after erasure and overloading resolution could not disambiguate.""", + exp.srcPos) + avoidClashWith(if cmp < 0 then forwarder else forwarder1, forwarders1) + else + val (forwarder2, forwarders2) = avoidClashWith(forwarder, forwarders1) + (forwarder2, forwarders.derivedCons(forwarder1, forwarders2)) + case _ => + (forwarder, forwarders) + end avoidClashWith + + /** Avoid clashes of any two export forwarders in `forwarders`. + * A clash is if two forwarders f1 and f2 have the same name and signatures after erasure. + * We try to avoid a clash by dropping one of f1 and f2, keeping the one whose right hand + * side reference would be preferred by overloading resolution. + * If neither of f1 or f2 is preferred over the other, report an error. + * + * The idea is that this simulates the hypothetical case where export forwarders + * are not generated and we treat an export instead more like an import where we + * expand the use site reference. Test cases in {neg,pos}/i14699.scala. + * + * @pre Forwarders with the same name are consecutive in `forwarders`. + */ + def avoidClashes(forwarders: List[tpd.MemberDef]): List[tpd.MemberDef] = forwarders match + case forwarders @ (forwarder :: forwarders1) => + val (forwarder2, forwarders2) = forwarder match + case forwarder: tpd.DefDef => avoidClashWith(forwarder, forwarders1) + case _ => (forwarder, forwarders1) + forwarders.derivedCons(forwarder2, avoidClashes(forwarders2)) + case Nil => forwarders + addForwarders(selectors, Nil) - val forwarders = buf.toList + val forwarders = avoidClashes(buf.toList) exp.pushAttachment(ExportForwarders, forwarders) forwarders end exportForwarders diff --git a/tests/neg/i14966.check b/tests/neg/i14966.check index 6cc8132f17bb..c5227b352b1b 100644 --- a/tests/neg/i14966.check +++ b/tests/neg/i14966.check @@ -1,10 +1,10 @@ --- [E120] Naming Error: tests/neg/i14966.scala:2:11 -------------------------------------------------------------------- -2 | export s.* // error - | ^ - | Double definition: - | final def ++[B >: T](suffix: IterableOnce[B]): Set[B] in class B at line 2 and - | final def ++(that: IterableOnce[T]): Set[T] in class B at line 2 - | have the same type after erasure. - | - | Consider adding a @targetName annotation to one of the conflicting definitions - | for disambiguation. +-- Error: tests/neg/i14966.scala:10:9 ---------------------------------------------------------------------------------- +10 | export s.* // error + | ^^^^^^^^^^ + | Clashing exports: The exported + | method f: (x: List[Int]): Int + | and method f²: (x: List[String]): Int + | have the same signature after erasure and overloading resolution could not disambiguate. + | + | where: f is a method in trait S + | f² is a method in trait I diff --git a/tests/neg/i14966.scala b/tests/neg/i14966.scala index f8869e9bbac0..38c6d99247ea 100644 --- a/tests/neg/i14966.scala +++ b/tests/neg/i14966.scala @@ -1,2 +1,11 @@ -class B[T](val s: Set[T]): - export s.* // error +trait I[A]: + def f(x: List[String]): A + +trait S: + def f(x: List[Int]): Int + +trait T[A] extends I[A], S + +class Test(s: T[Int]): + export s.* // error + diff --git a/tests/pos/i14966.scala b/tests/pos/i14966.scala new file mode 100644 index 000000000000..9ed0e6ce3720 --- /dev/null +++ b/tests/pos/i14966.scala @@ -0,0 +1,13 @@ +trait I[+A] extends IOps[A, I[A]] + +trait S[A] extends I[A], SOps[A, S[A]] + +trait IOps[+A, +C <: I[A]]: + def concat[B >: A](other: IterableOnce[B]): C + +trait SOps[A, +C <: S[A]] extends IOps[A, C]: + def concat(other: IterableOnce[A]): C + +class Test(s: S[Int]): + export s.* + diff --git a/tests/pos/i14966a.scala b/tests/pos/i14966a.scala new file mode 100644 index 000000000000..ad426fc721b2 --- /dev/null +++ b/tests/pos/i14966a.scala @@ -0,0 +1,2 @@ +class B[T](val s: Set[T]): + export s.* From fed27643972efeeee6b83ffb8c11305fcedb501e Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 20 Apr 2022 19:31:46 +0200 Subject: [PATCH 107/121] Print erased type in double definition message Print erased type if the message says "have the same type after erasure" --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 3 ++- tests/neg/i14966a.check | 10 ++++++++++ tests/neg/i14966a.scala | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i14966a.check create mode 100644 tests/neg/i14966a.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 36246faaea22..13bf2cf7f580 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2128,6 +2128,7 @@ import transform.SymUtils._ class DoubleDefinition(decl: Symbol, previousDecl: Symbol, base: Symbol)(using Context) extends NamingMsg(DoubleDefinitionID) { def msg = { def nameAnd = if (decl.name != previousDecl.name) " name and" else "" + def erasedType = if ctx.erasedTypes then i" ${decl.info}" else "" def details(using Context): String = if (decl.isRealMethod && previousDecl.isRealMethod) { import Signature.MatchDegree._ @@ -2155,7 +2156,7 @@ import transform.SymUtils._ |Consider adding a @targetName annotation to one of the conflicting definitions |for disambiguation.""" else "" - i"have the same$nameAnd type after erasure.$hint" + i"have the same$nameAnd type$erasedType after erasure.$hint" } } else "" diff --git a/tests/neg/i14966a.check b/tests/neg/i14966a.check new file mode 100644 index 000000000000..777d1ec74955 --- /dev/null +++ b/tests/neg/i14966a.check @@ -0,0 +1,10 @@ +-- [E120] Naming Error: tests/neg/i14966a.scala:3:6 -------------------------------------------------------------------- +3 | def f(x: List[Int]): String = ??? // error + | ^ + | Double definition: + | def f[X <: String](x: List[X]): String in class Test at line 2 and + | def f(x: List[Int]): String in class Test at line 3 + | have the same type (x: scala.collection.immutable.List): String after erasure. + | + | Consider adding a @targetName annotation to one of the conflicting definitions + | for disambiguation. diff --git a/tests/neg/i14966a.scala b/tests/neg/i14966a.scala new file mode 100644 index 000000000000..f2643798f1e5 --- /dev/null +++ b/tests/neg/i14966a.scala @@ -0,0 +1,4 @@ +class Test: + def f[X <: String](x: List[X]): String = ??? + def f(x: List[Int]): String = ??? // error + From be9c8becec97d1406bc74ba7bea439fde9c12611 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 20 Apr 2022 20:31:32 +0200 Subject: [PATCH 108/121] More aggressive reduction of type selection (fixes parboiled2) Previously, when reducing `a.T` we checked if the type of `a` was a subtype of `RefinedType(.., T, TypeAlias(...))`, now we extend this check to handle refinements where the `info` is a `TypeBounds` where both bounds are equal. This solves two big issues at once: - We can restore tests/pos/13491.scala to its original form from before #13780. The check for abstract types introduced by #13780 for soundness reasons is no longer hit because the type selection is reduced before we get to that point. This is important because parboiled2 relies on this and is therefore currently broken on 3.1.3-RC1 and main (https://github.com/sirthias/parboiled2/issues/365). - This fixes #14903 (slow compilation issue affecting parboiled2) without caching skolems (as in the alternative fix #14909). Again, this is due to the type containing skolem being reducible to a simpler type and therefore cacheable. --- .../src/dotty/tools/dotc/core/Types.scala | 9 ++-- tests/pos/13491.scala | 2 +- tests/pos/i14903a.scala | 22 ++++++++ tests/pos/i14903b.scala | 54 +++++++++++++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/pos/i14903a.scala create mode 100644 tests/pos/i14903b.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ce7e93b51989..1b1fb41e3756 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1548,9 +1548,10 @@ object Types { @tailrec def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => pre.refinedInfo match { - case TypeAlias(alias) => - if (pre.refinedName ne name) loop(pre.parent) else alias - case _ => loop(pre.parent) + case TypeBounds(lo, hi) if lo eq hi => + if (pre.refinedName ne name) loop(pre.parent) else lo + case _ => + loop(pre.parent) } case pre: RecType => val candidate = pre.parent.lookupRefined(name) @@ -4640,7 +4641,7 @@ object Types { myRepr.nn } - override def toString: String = s"Skolem($hashCode)" + override def toString: String = s"SkolemType($hashCode)" } /** A skolem type used to wrap the type of the qualifier of a selection. diff --git a/tests/pos/13491.scala b/tests/pos/13491.scala index b2c7941e15a0..d764494c6048 100644 --- a/tests/pos/13491.scala +++ b/tests/pos/13491.scala @@ -87,7 +87,7 @@ object Rule { def rule[I <: HList, O <: HList](r: Rule[I, O]): Rule[I, O] = ??? - implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T] { type Out = Out0 }): RuleN[Out0] = ??? + implicit def valueMap[T, Out0 <: HList](m: Map[String, T])(implicit h: HListable[T]): RuleN[h.Out] = ??? } object Test { diff --git a/tests/pos/i14903a.scala b/tests/pos/i14903a.scala new file mode 100644 index 000000000000..9e4e28320987 --- /dev/null +++ b/tests/pos/i14903a.scala @@ -0,0 +1,22 @@ +trait Wrapper[T] { + type Out + } + + type Func[T] = + T match { + case String => Long + case Long => Int + case Int => Float + case Float => Double + case Double => Unit + case Unit => String + } + + implicit def infer[A]: Wrapper[One[A]] { type Out = Func[A] } = ??? + + trait One[A] { + def use(implicit w: Wrapper[One[A]]): One[w.Out] + } + + val x: One[Long] = null + val _ = x.use.use.use.use.use.use.use diff --git a/tests/pos/i14903b.scala b/tests/pos/i14903b.scala new file mode 100644 index 000000000000..cd61a12e858c --- /dev/null +++ b/tests/pos/i14903b.scala @@ -0,0 +1,54 @@ +import annotation.unchecked.uncheckedVariance + +sealed trait HList +sealed trait HNil extends HList +case object HNil extends HNil +case class ::[+H, +T <: HList](head: H, tail: T) extends HList + +type Concat[X <: HList, Y <: HList] <: HList = X match + case HNil => Y + case h :: t => h :: Concat[t, Y] + +/** + * Decompose L into Prefix ++ Suffix if possible +*/ +type StripSuffix[L <: HList, Suffix <: HList] <: Option[HList] = L match + case Suffix => Some[HNil] + case h :: t => StripSuffix[t, Suffix] match + case Some[x] => Some[h :: x] + case _ => None.type + case _ => None.type + +/** + * type-level implementation of this logic: + * Out = + * R if T has a tail of type L + * (L dropRight T) ++ R if L has a tail of type T +*/ +sealed trait TailSwitch[L <: HList, T <: HList, R <: HList]: + type Out <: HList + +object TailSwitch: + type TS[L <: HList, T <: HList, R <: HList] <: HList = + StripSuffix[T, L] match + case Some[_] => R + case _ => StripSuffix[L, T] match + case Some[x] => Concat[x, R] + + implicit def tailSwitch[L <: HList, T <: HList, R <: HList]: (TailSwitch[L, T, R] { + type Out = TS[L, T, R] + }) = new TailSwitch[L, T, R] { type Out = TS[L, T, R] } + +/** + * Rule popping I from stack and pushing back O +*/ +sealed class Rule[-I <: HList, +O <: HList]: + def ~[I2 <: HList, O2 <: HList](that: Rule[I2, O2])(implicit + i: TailSwitch[I2, O @uncheckedVariance, I @uncheckedVariance], + o: TailSwitch[O @uncheckedVariance, I2, O2] + ): Rule[i.Out, o.Out] = ??? + +object Test: + def dot = new Rule[HNil, HNil] {} + def num = new Rule[HNil, Byte :: HNil] {} + def pattern = num ~ dot ~ num ~ dot ~ num ~ dot ~ num // error From e0cce9a951c77a84e97d654fae326d7474410b62 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 20 Apr 2022 22:33:17 +0200 Subject: [PATCH 109/121] lookupRefined: check for AliasingBounds instead of TypeBounds with equal bounds It turns out that the problematic cases fixed in the previous commit only occurs because we create type aliases with info of type MatchAlias instead of TypeAlias if their rhs is an applied match type (Namer#TypeDefCompleter#typeSig calls `toBounds` on the rhs which does `if (self.isMatch) MatchAlias(self)`). I'm not sure if there is a good reason for that (and if so, do we need to be careful to avoid loops when dealiasing MatchAlias?) or if we should change the logic in Namer to return a TypeAlias instead. --- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1b1fb41e3756..9ab49d2f2062 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1548,8 +1548,8 @@ object Types { @tailrec def loop(pre: Type): Type = pre.stripTypeVar match { case pre: RefinedType => pre.refinedInfo match { - case TypeBounds(lo, hi) if lo eq hi => - if (pre.refinedName ne name) loop(pre.parent) else lo + case tp: AliasingBounds => + if (pre.refinedName ne name) loop(pre.parent) else tp.alias case _ => loop(pre.parent) } From 57bc4c6b69712e181445c815b24b1dc3f8349d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 21 Apr 2022 10:47:52 +0200 Subject: [PATCH 110/121] Revert "-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")" This reverts commit e39b618f9a9a1c4f306b1abfbfdb740758f5b37c. --- .github/workflows/ci.yaml | 7 + .../communitybuild/CommunityBuildRunner.scala | 5 +- .../src/scala/dotty/communitybuild/Main.scala | 2 +- .../scala/dotty/communitybuild/projects.scala | 230 +++++++++--------- .../communitybuild/CommunityBuildTest.scala | 14 -- .../sbtplugin/CommunityBuildPlugin.scala | 29 +-- 6 files changed, 134 insertions(+), 153 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 91add81f44ae..2ad5f18b1787 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -363,6 +363,7 @@ jobs: git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestC" +<<<<<<< HEAD - name: Show dependency tracking file if: ${{ always() }} run: cat community-build/dotty-community-build-deps || true @@ -410,6 +411,8 @@ jobs: git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestForwardCompat" +======= +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) test_sbt: runs-on: [self-hosted, Linux] container: @@ -509,7 +512,11 @@ jobs: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache +<<<<<<< HEAD needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, community_build_forward_compat, test_sbt, test_java8] +======= + needs: [test_non_bootstrapped, test, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'lampepfl/dotty'" env: NIGHTLYBUILD: yes diff --git a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala index d60def1a28f1..6a0c54c4b00b 100644 --- a/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala +++ b/community-build/src/scala/dotty/communitybuild/CommunityBuildRunner.scala @@ -17,10 +17,10 @@ object CommunityBuildRunner: * for more infrastructural details. */ extension (self: CommunityProject) def run()(using suite: CommunityBuildRunner): Unit = - if self.requiresExperimental && !self.compilerSupportExperimental then + if self.requiresExperimental && !compilerSupportExperimental then log(s"Skipping ${self.project} - it needs experimental features unsupported in this build.") return - self.dependencies().foreach(_.publish()) + self.dependencies.foreach(_.publish()) self.testOnlyDependencies().foreach(_.publish()) suite.runProject(self) @@ -45,7 +45,6 @@ trait CommunityBuildRunner: val project = projectDef.project val command = projectDef.binaryName val arguments = projectDef.buildCommands - val compilerVersion = projectDef.compilerVersion @annotation.tailrec def execTimes(task: () => Int, timesToRerun: Int): Boolean = diff --git a/community-build/src/scala/dotty/communitybuild/Main.scala b/community-build/src/scala/dotty/communitybuild/Main.scala index 7c3a39261eb0..852cee46af22 100644 --- a/community-build/src/scala/dotty/communitybuild/Main.scala +++ b/community-build/src/scala/dotty/communitybuild/Main.scala @@ -57,7 +57,7 @@ object Main: val (toRun, ignored) = allProjects.partition( p => p.docCommand != null - && (!p.requiresExperimental || p.compilerSupportExperimental) + && (!p.requiresExperimental || compilerSupportExperimental) ) val paths = toRun.map { project => diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index c5e7997d0b21..74510f3a3c5b 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -6,10 +6,13 @@ import java.nio.charset.StandardCharsets.UTF_8 lazy val communitybuildDir: Path = Paths.get(sys.props("user.dir")) -lazy val testedCompilerVersion: String = +lazy val compilerVersion: String = val file = communitybuildDir.resolve("scala3-bootstrapped.version") new String(Files.readAllBytes(file), UTF_8) +lazy val compilerSupportExperimental: Boolean = + compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") + lazy val sbtPluginFilePath: String = // Workaround for https://github.com/sbt/sbt/issues/4395 new File(sys.props("user.home") + "/.sbt/1.0/plugins").mkdirs() @@ -36,21 +39,17 @@ sealed trait CommunityProject: val testCommand: String val publishCommand: String val docCommand: String - val dependencies: () => List[CommunityProject] + val dependencies: List[CommunityProject] val testOnlyDependencies: () => List[CommunityProject] val binaryName: String val runCommandsArgs: List[String] = Nil val requiresExperimental: Boolean val environment: Map[String, String] = Map.empty - val compilerVersion: String final val projectDir = communitybuildDir.resolve("community-projects").resolve(project) - final val compilerSupportExperimental: Boolean = - compilerVersion.contains("SNAPSHOT") || compilerVersion.contains("NIGHTLY") - final def publishDependencies(): Unit = - dependencies().foreach(_.publish()) + dependencies.foreach(_.publish()) /** Publish this project to the local Maven repository */ final def publish(): Unit = @@ -88,11 +87,10 @@ end CommunityProject final case class MillCommunityProject( project: String, baseCommand: String, - dependencies: () => List[CommunityProject] = () => Nil, + dependencies: List[CommunityProject] = Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, ignoreDocs: Boolean = false, requiresExperimental: Boolean = false, - compilerVersion: String = testedCompilerVersion ) extends CommunityProject: override val binaryName: String = "./mill" override val testCommand = s"$baseCommand.test" @@ -107,14 +105,12 @@ final case class SbtCommunityProject( project: String, sbtTestCommand: String, extraSbtArgs: List[String] = Nil, - dependencies: () => List[CommunityProject] = () => Nil, + dependencies: List[CommunityProject] = Nil, testOnlyDependencies: () => List[CommunityProject] = () => Nil, sbtPublishCommand: String = null, sbtDocCommand: String = null, scalacOptions: List[String] = SbtCommunityProject.scalacOptions, requiresExperimental: Boolean = false, - compilerVersion: String = testedCompilerVersion, - isForwardCompatProject: Boolean = false ) extends CommunityProject: override val binaryName: String = "sbt" @@ -123,7 +119,6 @@ final case class SbtCommunityProject( private val baseCommand = "clean; set Global/logLevel := Level.Error; set Global/updateOptions ~= (_.withLatestSnapshots(false)); " - ++ (if isForwardCompatProject then "set Global / isForwardCompatProject := true; " else "") ++ (if scalacOptions.isEmpty then "" else s"""set Global/scalacOptions ++= $scalacOptionsString;""") ++ s"++$compilerVersion!; " @@ -151,6 +146,7 @@ final case class SbtCommunityProject( s"--addPluginSbtFile=$sbtPluginFilePath" ) +<<<<<<< HEAD def forwardCompat: SbtCommunityProject = this.copy( project = project + "-forward-compat", @@ -164,6 +160,8 @@ final case class SbtCommunityProject( scalacOptions = scalacOptions ++ Seq("-scala-output-version", release) ) +======= +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) object SbtCommunityProject: def scalacOptions = List( "-Xcheck-macros", @@ -184,89 +182,89 @@ object projects: lazy val utest = MillCommunityProject( project = "utest", - baseCommand = s"utest.jvm[$testedCompilerVersion]", + baseCommand = s"utest.jvm[$compilerVersion]", ignoreDocs = true ) lazy val sourcecode = MillCommunityProject( project = "sourcecode", - baseCommand = s"sourcecode.jvm[$testedCompilerVersion]", + baseCommand = s"sourcecode.jvm[$compilerVersion]", ignoreDocs = true ) lazy val oslib = MillCommunityProject( project = "os-lib", - baseCommand = s"os.jvm[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode) + baseCommand = s"os.jvm[$compilerVersion]", + dependencies = List(utest, sourcecode) ) lazy val oslibWatch = MillCommunityProject( project = "os-lib", - baseCommand = s"os.watch[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode), + baseCommand = s"os.watch[$compilerVersion]", + dependencies = List(utest, sourcecode), ignoreDocs = true ) lazy val ujson = MillCommunityProject( project = "upickle", - baseCommand = s"ujson.jvm[$testedCompilerVersion]", - dependencies = () => List(geny) + baseCommand = s"ujson.jvm[$compilerVersion]", + dependencies = List(geny) ) lazy val upickle = MillCommunityProject( project = "upickle", - baseCommand = s"upickle.jvm[$testedCompilerVersion]", - dependencies = () => List(geny, utest) + baseCommand = s"upickle.jvm[$compilerVersion]", + dependencies = List(geny, utest) ) lazy val upickleCore = MillCommunityProject( project = "upickle", - baseCommand = s"core.jvm[$testedCompilerVersion]", - dependencies = () => List(geny, utest) + baseCommand = s"core.jvm[$compilerVersion]", + dependencies = List(geny, utest) ) lazy val upickleImplicits = MillCommunityProject( project = "upickle", - baseCommand = s"implicits.jvm[$testedCompilerVersion]", - dependencies = () => List(upickleCore, ujson) + baseCommand = s"implicits.jvm[$compilerVersion]", + dependencies = List(upickleCore, ujson) ) lazy val upack = MillCommunityProject( project = "upickle", - baseCommand = s"upack.jvm[$testedCompilerVersion]", - dependencies = () => List(ujson, upickleCore) + baseCommand = s"upack.jvm[$compilerVersion]", + dependencies = List(ujson, upickleCore) ) lazy val geny = MillCommunityProject( project = "geny", - baseCommand = s"geny.jvm[$testedCompilerVersion]", - dependencies = () => List(utest) + baseCommand = s"geny.jvm[$compilerVersion]", + dependencies = List(utest) ) lazy val fansi = MillCommunityProject( project = "fansi", - baseCommand = s"fansi.jvm[$testedCompilerVersion]", - dependencies = () => List(utest, sourcecode), + baseCommand = s"fansi.jvm[$compilerVersion]", + dependencies = List(utest, sourcecode), ignoreDocs = true ) lazy val pprint = MillCommunityProject( project = "PPrint", - baseCommand = s"pprint.jvm[$testedCompilerVersion]", - dependencies = () => List(fansi), + baseCommand = s"pprint.jvm[$compilerVersion]", + dependencies = List(fansi), ignoreDocs = true ) lazy val requests = MillCommunityProject( project = "requests-scala", - baseCommand = s"requests[$testedCompilerVersion]", - dependencies = () => List(geny, utest, ujson, upickleCore) + baseCommand = s"requests[$compilerVersion]", + dependencies = List(geny, utest, ujson, upickleCore) ) lazy val cask = MillCommunityProject( project = "cask", - baseCommand = s"cask[$testedCompilerVersion]", - dependencies = () => List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) + baseCommand = s"cask[$compilerVersion]", + dependencies = List(utest, geny, sourcecode, pprint, upickle, upickleImplicits, upack, requests) ) lazy val scas = MillCommunityProject( @@ -288,8 +286,6 @@ object projects: sbtDocCommand = forceDoc("jvm") ) - lazy val scalacheckForwardCompat = scalacheck.forwardCompat.withScalaRelease("3.0") - lazy val scalatest: SbtCommunityProject = SbtCommunityProject( project = "scalatest", sbtTestCommand = @@ -310,7 +306,7 @@ object projects: // org.scalatest.Outcome // Problem parsing scalatest.dotty/target/scala-3.0.0-M2/src_managed/main/org/scalatest/concurrent/ConductorFixture.scala:[602..624..3843], documentation may not be generated. // dotty.tools.dotc.core.MissingType: - dependencies = () => List(scalaXml), + dependencies = List(scalaXml), testOnlyDependencies = () => List(scalatestplusJunit, scalatestplusTestNG) ) @@ -319,21 +315,21 @@ object projects: sbtTestCommand = "scalatestPlusScalaCheckJVM/test", sbtPublishCommand = "scalatestPlusScalaCheckJVM/publishLocal", sbtDocCommand = "scalatestPlusScalaCheckJVM/doc", - dependencies = () => List(scalatest, scalacheck) + dependencies = List(scalatest, scalacheck) ) lazy val scalatestplusJunit = SbtCommunityProject( project = "scalatestplus-junit", sbtTestCommand = "scalatestplus-junit/test", sbtPublishCommand = "scalatestplus-junit/publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val scalatestplusTestNG = SbtCommunityProject( project = "scalatestplus-testng", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val scalaXml = SbtCommunityProject( @@ -369,7 +365,7 @@ object projects: project = "minitest", sbtTestCommand = "test", sbtDocCommand = aggregateDoc("lawsJVM")("minitestJVM"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val fastparse = SbtCommunityProject( @@ -428,15 +424,19 @@ object projects: project = "sconfig", sbtTestCommand = "sconfigJVM/test", sbtDocCommand = "sconfigJVM/doc", - dependencies = () => List(scalaCollectionCompat) + dependencies = List(scalaCollectionCompat) ) lazy val zio = SbtCommunityProject( project = "zio", sbtTestCommand = "testJVMDotty", sbtDocCommand = forceDoc("coreJVM"), +<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), dependencies = () => List(izumiReflect) +======= + dependencies = List(izumiReflect) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val munit = SbtCommunityProject( @@ -444,17 +444,15 @@ object projects: sbtTestCommand = "testsJVM/test;testsJS/test;", sbtPublishCommand = "munitJVM/publishLocal; munitJS/publishLocal; munitScalacheckJVM/publishLocal; munitScalacheckJS/publishLocal; junit/publishLocal", sbtDocCommand = "junit/doc; munitJVM/doc", - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) - lazy val munitForwardCompat = munit.forwardCompat.withScalaRelease("3.0") - lazy val scodecBits = SbtCommunityProject( project = "scodec-bits", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", sbtDocCommand = "coreJVM/doc", - dependencies = () => List(munit), + dependencies = List(munit), ) lazy val scodec = SbtCommunityProject( @@ -462,8 +460,12 @@ object projects: sbtTestCommand = "unitTests/test", // Adds package sbtDocCommand = "coreJVM/doc", +<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), dependencies = () => List(munit, scodecBits), +======= + dependencies = List(munit, scodecBits), +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val scalaParserCombinators = SbtCommunityProject( @@ -496,7 +498,7 @@ object projects: // [error] class scalaz.iteratee.Iteratee cannot be unpickled because no class file was found sbtDocCommand = forceDoc("effectJVM"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val endpoints4s = SbtCommunityProject( @@ -510,16 +512,19 @@ object projects: sbtTestCommand = "ciJVM", sbtPublishCommand = "publishLocal", sbtDocCommand = ";coreJVM/doc ;lawsJVM/doc ;kernelJVM/doc", - dependencies = () => List(cats, coop, disciplineSpecs2, scalacheck) + dependencies = List(cats, coop, disciplineSpecs2, scalacheck) ) +<<<<<<< HEAD lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.copy(compilerVersion = "3.0.2") +======= +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) lazy val scalaParallelCollections = SbtCommunityProject( project = "scala-parallel-collections", sbtTestCommand = "test", sbtDocCommand = forceDoc("core"), - dependencies = () => List(scalacheck) + dependencies = List(scalacheck) ) lazy val scalaCollectionCompat = SbtCommunityProject( @@ -531,8 +536,8 @@ object projects: lazy val scalaJava8Compat = SbtCommunityProject( project = "scala-java8-compat", // the fnGen subproject must be built with 2.12.x - sbtTestCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; test", - sbtPublishCommand = s"++2.12.14; ++$testedCompilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", + sbtTestCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; test", + sbtPublishCommand = s"++2.12.14; ++$compilerVersion; set fnGen/dependencyOverrides := Nil; publishLocal", scalacOptions = Nil // avoid passing Scala 3 options to Scala 2.12 in fnGen subproject ) @@ -547,82 +552,77 @@ object projects: project = "discipline", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "set every credentials := Nil;coreJVM/publishLocal;coreJS/publishLocal", +<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), dependencies = () => List(scalacheck) +======= + dependencies = List(scalacheck) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) - lazy val disciplineForwardCompat = discipline.forwardCompat.withScalaRelease("3.0") - lazy val disciplineMunit = SbtCommunityProject( project = "discipline-munit", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(discipline, munit) + dependencies = List(discipline, munit) ) - lazy val disciplineMunitForwardCompat = disciplineMunit.forwardCompat.withScalaRelease("3.0") - lazy val disciplineSpecs2 = SbtCommunityProject( project = "discipline-specs2", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(discipline), + dependencies = List(discipline), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") ) - lazy val disciplineSpecs2ForwardCompat = disciplineSpecs2.forwardCompat.withScalaRelease("3.0") - lazy val simulacrumScalafixAnnotations = SbtCommunityProject( project = "simulacrum-scalafix", sbtTestCommand = "annotation/test:compile;annotationJS/test:compile", sbtPublishCommand = "annotation/publishLocal;annotationJS/publishLocal", ) - lazy val simulacrumScalafixAnnotationsForwardCompat = simulacrumScalafixAnnotations.forwardCompat.withScalaRelease("3.0") - lazy val cats = SbtCommunityProject( project = "cats", sbtTestCommand = "set Global/scalaJSStage := FastOptStage;buildJVM;validateAllJS", sbtPublishCommand = "catsJVM/publishLocal;catsJS/publishLocal", - dependencies = () => List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), + dependencies = List(discipline, disciplineMunit, scalacheck, simulacrumScalafixAnnotations), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init") // disable -Ysafe-init, due to -Xfatal-warning ) - lazy val catsForwardCompat = cats.forwardCompat.withScalaRelease("3.0") - lazy val catsMtl = SbtCommunityProject( project = "cats-mtl", sbtTestCommand = "testsJVM/test;testsJS/test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal;lawsJVM/publishLocal;lawsJS/publishLocal", - dependencies = () => List(cats, disciplineMunit) + dependencies = List(cats, disciplineMunit) ) +<<<<<<< HEAD lazy val catsMtlForwardCompat = catsMtl.forwardCompat.copy(compilerVersion = "3.0.2") +======= +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) lazy val coop = SbtCommunityProject( project = "coop", sbtTestCommand = "test", sbtPublishCommand = "coreJVM/publishLocal;coreJS/publishLocal", - dependencies = () => List(cats, catsMtl) + dependencies = List(cats, catsMtl) ) - lazy val coopForwardCompat = coop.forwardCompat.withScalaRelease("3.0") - // 'Sciss/Lucre' with its dependencies: lazy val scissEqual = SbtCommunityProject( project = "Equal", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissFingerTree = SbtCommunityProject( project = "FingerTree", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissLog = SbtCommunityProject( @@ -635,57 +635,62 @@ object projects: project = "Model", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissNumbers = SbtCommunityProject( project = "Numbers", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissSerial = SbtCommunityProject( project = "Serial", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatest), + dependencies = List(scalatest), ) lazy val scissAsyncFile = SbtCommunityProject( project = "AsyncFile", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scissLog, scalatest), + dependencies = List(scissLog, scalatest), ) lazy val scissSpan = SbtCommunityProject( project = "Span", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scissSerial, scalatest), + dependencies = List(scissSerial, scalatest), ) lazy val scalaSTM = SbtCommunityProject( project = "scala-stm", sbtTestCommand = "rootJVM/test", sbtPublishCommand = "rootJVM/publishLocal", - dependencies = () => List(scalatestplusJunit), + dependencies = List(scalatestplusJunit), ) lazy val scissLucre = SbtCommunityProject( project = "Lucre", sbtTestCommand = "adjunctJVM/test;baseJVM/test;confluentJVM/test;coreJVM/test;dataJVM/test;exprJVM/test;geomJVM/test;lucre-bdb/test;testsJVM/test", extraSbtArgs = List("-Dde.sciss.lucre.ShortTests=true"), +<<<<<<< HEAD sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", dependencies = () => List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), +======= + sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;expr0JVM/publishLocal;expr1JVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", + dependencies = List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val izumiReflect = SbtCommunityProject( project = "izumi-reflect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val perspective = SbtCommunityProject( @@ -693,11 +698,12 @@ object projects: // No library with easy typeclasses to verify data against exist for Dotty, so no tests yet // Until then I guess this mainly serves to check that it still compiles at all sbtTestCommand = "dottyPerspectiveExamples/compile", - dependencies = () => List(cats) + dependencies = List(cats) ) lazy val akka = SbtCommunityProject( project = "akka", +<<<<<<< HEAD extraSbtArgs = List(s"-Dakka.build.scalaVersion=$testedCompilerVersion"), sbtTestCommand = List( "set every targetSystemJdk := true", @@ -709,12 +715,17 @@ object projects: ).mkString("; "), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), dependencies = () => List(scalatest, scalatestplusJunit, scalatestplusScalacheck) +======= + extraSbtArgs = List(s"-Dakka.build.scalaVersion=$compilerVersion"), + sbtTestCommand = "set every targetSystemJdk := true; akka-actor-tests/Test/compile", + dependencies = List(scalatest, scalatestplusJunit, scalatestplusScalacheck) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val monocle = SbtCommunityProject( project = "Monocle", sbtTestCommand = "coreJVM/test; macrosJVM/test; testJVM/test", - dependencies = () => List(cats, munit, discipline, disciplineMunit) + dependencies = List(cats, munit, discipline, disciplineMunit) ) lazy val protoquill = SbtCommunityProject( @@ -722,7 +733,11 @@ object projects: extraSbtArgs = List("-Dcommunity=true", "-DcommunityRemote=true", "-Dquill.macro.stdout=true"), sbtTestCommand = "runCommunityBuild", sbtPublishCommand = "publishLocal", +<<<<<<< HEAD dependencies = () => List(scalatest), +======= + dependencies = List(), // TODO add scalatest and pprint (see protoquill/build.sbt) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) scalacOptions = List("-language:implicitConversions"), // disabled -Ysafe-init, due to bug in macro ) @@ -730,65 +745,73 @@ object projects: project = "onnx-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val playJson = SbtCommunityProject( project = "play-json", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalatest, scalatestplusScalacheck), + dependencies = List(scalatest, scalatestplusScalacheck), ) lazy val munitCatsEffect = SbtCommunityProject( project = "munit-cats-effect", sbtTestCommand = "ce3JVM/test; ce3JS/test", sbtPublishCommand = "ce3JVM/publishLocal; ce3JS/publishLocal", - dependencies = () => List(munit, catsEffect3) + dependencies = List(munit, catsEffect3) ) lazy val scalacheckEffect = SbtCommunityProject( project = "scalacheck-effect", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(cats, catsEffect3, munit, scalacheck) + dependencies = List(cats, catsEffect3, munit, scalacheck) ) lazy val fs2 = SbtCommunityProject( project = "fs2", sbtTestCommand = "coreJVM/test; coreJS/test", // io/test requires JDK9+ sbtPublishCommand = "coreJVM/publishLocal; coreJS/publishLocal", +<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), dependencies = () => List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) +======= + dependencies = List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val libretto = SbtCommunityProject( project = "libretto", sbtTestCommand = "core/test; examples/compile", sbtPublishCommand = "core/publishLocal; examples/publishLocal", - dependencies = () => List(scalatest) + dependencies = List(scalatest) ) lazy val jacksonModuleScala = SbtCommunityProject( project = "jackson-module-scala", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", - dependencies = () => List(scalaJava8Compat, scalatest) + dependencies = List(scalaJava8Compat, scalatest) ) lazy val specs2 = SbtCommunityProject( project = "specs2", sbtTestCommand = "core/testOnly -- exclude ci", sbtPublishCommand = "core/publishLocal", - dependencies = () => List() + dependencies = List() ) lazy val spire = SbtCommunityProject( project = "spire", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", +<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), dependencies = () => List(cats, disciplineMunit) +======= + dependencies = List(cats, disciplineMunit) +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val http4s = SbtCommunityProject( @@ -801,19 +824,6 @@ object projects: end projects -lazy val forwardCompatMapping = Map[CommunityProject, CommunityProject]( - projects.scalacheck -> projects.scalacheckForwardCompat, - projects.munit -> projects.munitForwardCompat, - projects.discipline -> projects.disciplineForwardCompat, - projects.disciplineMunit -> projects.disciplineMunitForwardCompat, - projects.disciplineSpecs2 -> projects.disciplineSpecs2ForwardCompat, - projects.simulacrumScalafixAnnotations -> projects.simulacrumScalafixAnnotationsForwardCompat, - projects.cats -> projects.catsForwardCompat, - projects.catsMtl -> projects.catsMtlForwardCompat, - projects.coop -> projects.coopForwardCompat, - projects.catsEffect3 -> projects.catsEffect3ForwardCompat, -) - def allProjects = List( projects.utest, projects.sourcecode, @@ -832,7 +842,6 @@ def allProjects = List( projects.scas, projects.intent, projects.scalacheck, - projects.scalacheckForwardCompat, projects.scalatest, projects.scalatestplusScalacheck, projects.scalatestplusJunit, @@ -849,7 +858,6 @@ def allProjects = List( projects.sconfig, projects.zio, projects.munit, - projects.munitForwardCompat, projects.scodecBits, projects.scodec, projects.scalaParserCombinators, @@ -857,23 +865,16 @@ def allProjects = List( projects.scalaz, projects.endpoints4s, projects.catsEffect3, - projects.catsEffect3ForwardCompat, projects.scalaParallelCollections, projects.scalaCollectionCompat, projects.scalaJava8Compat, projects.verify, projects.discipline, - projects.disciplineForwardCompat, projects.disciplineMunit, - projects.disciplineMunitForwardCompat, projects.disciplineSpecs2, - projects.disciplineSpecs2ForwardCompat, projects.simulacrumScalafixAnnotations, - projects.simulacrumScalafixAnnotationsForwardCompat, projects.cats, - projects.catsForwardCompat, projects.catsMtl, - projects.catsMtlForwardCompat, projects.coop, projects.scissEqual, projects.scissFingerTree, @@ -899,10 +900,13 @@ def allProjects = List( projects.libretto, projects.jacksonModuleScala, projects.specs2, +<<<<<<< HEAD projects.coop, projects.coopForwardCompat, projects.spire, projects.http4s +======= +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val projectMap = allProjects.groupBy(_.project) diff --git a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala index 5be7f936ab66..657d49db3172 100644 --- a/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala +++ b/community-build/test/scala/dotty/communitybuild/CommunityBuildTest.scala @@ -100,17 +100,3 @@ class CommunityBuildTestC: @Test def verify = projects.verify.run() @Test def xmlInterpolator = projects.xmlInterpolator.run() end CommunityBuildTestC - -@Category(Array(classOf[TestCategory])) -class CommunityBuildTestForwardCompat: - @Test def catsEffect3ForwardCompat = projects.catsEffect3ForwardCompat.run() - @Test def catsForwardCompat = projects.catsForwardCompat.run() - @Test def catsMtlForwardCompat = projects.catsMtlForwardCompat.run() - @Test def coopForwardCompat = projects.coopForwardCompat.run() - @Test def disciplineForwardCompat = projects.disciplineForwardCompat.run() - @Test def disciplineMunitForwardCompat = projects.disciplineMunitForwardCompat.run() - @Test def disciplineSpecs2ForwardCompat = projects.disciplineSpecs2ForwardCompat.run() - @Test def munitForwardCompat = projects.munitForwardCompat.run() - @Test def scalacheckForwardCompat = projects.scalacheckForwardCompat.run() - @Test def simulacrumScalafixAnnotationsForwardCompat = projects.simulacrumScalafixAnnotationsForwardCompat.run() -end CommunityBuildTestForwardCompat diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index d6cebbfe3cac..fd9a6f48b9a8 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -15,16 +15,6 @@ object CommunityBuildPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin override def trigger = allRequirements - object autoImport { - val isForwardCompatProject = settingKey[Boolean]("Is it a project used for testing forward binary compatibility?") - } - - import autoImport._ - - override val globalSettings: Seq[Setting[_]] = Seq( - isForwardCompatProject := false - ) - override val projectSettings: Seq[Setting[_]] = Seq( publishLocal := Def.taskDyn { val pubLocalResult = publishLocal.value @@ -33,22 +23,12 @@ object CommunityBuildPlugin extends AutoPlugin { CommunityBuildDependencies.publish(projectID.value) pubLocalResult } - }.value, - projectID := { - val id = projectID.value - if (isForwardCompatProject.value) { - val revision = if (id.revision.endsWith("-SNAPSHOT")) - id.revision.replace("-SNAPSHOT", "-forward-compat-SNAPSHOT") - else - id.revision + "-forward-compat" - id.withRevision(revision) - } else - id - } + }.value ) override val buildSettings: Seq[Setting[_]] = Seq( dependencyOverrides ++= { +<<<<<<< HEAD if (scalaVersion.value.startsWith("3.")) { val forwardCompatFilter: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) val stdlibOverrides = Seq( @@ -57,6 +37,11 @@ object CommunityBuildPlugin extends AutoPlugin { ) CommunityBuildDependencies.allOverrides(sLog.value).filter(forwardCompatFilter) ++ stdlibOverrides } else Nil +======= + if (scalaVersion.value.startsWith("3.")) + CommunityBuildDependencies.allOverrides(sLog.value) + else Nil +>>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) } ) } From 35d9a7635280e815d9002f0c8762ac25e72e3a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 21 Apr 2022 10:49:29 +0200 Subject: [PATCH 111/121] Remove no longer needed submodules --- .gitmodules | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/.gitmodules b/.gitmodules index 971eb25a9466..aadf222714ad 100644 --- a/.gitmodules +++ b/.gitmodules @@ -216,37 +216,6 @@ [submodule "community-build/community-projects/spire"] path = community-build/community-projects/spire url = https://github.com/dotty-staging/spire.git -[submodule "community-build/community-projects/munit-forward-compat"] - path = community-build/community-projects/munit-forward-compat - url = https://github.com/dotty-staging/munit.git -[submodule "community-build/community-projects/discipline-forward-compat"] - path = community-build/community-projects/discipline-forward-compat - url = https://github.com/dotty-staging/discipline.git -[submodule "community-build/community-projects/discipline-munit-forward-compat"] - path = community-build/community-projects/discipline-munit-forward-compat - url = https://github.com/dotty-staging/discipline-munit.git -[submodule "community-build/community-projects/discipline-specs2-forward-compat"] - path = community-build/community-projects/discipline-specs2-forward-compat - url = https://github.com/dotty-staging/discipline-specs2.git -[submodule "community-build/community-projects/simulacrum-scalafix-forward-compat"] - path = community-build/community-projects/simulacrum-scalafix-forward-compat - url = https://github.com/dotty-staging/simulacrum-scalafix.git -[submodule "community-build/community-projects/cats-forward-compat"] - path = community-build/community-projects/cats-forward-compat - url = https://github.com/dotty-staging/cats.git -[submodule "community-build/community-projects/cats-mtl-forward-compat"] - path = community-build/community-projects/cats-mtl-forward-compat - url = https://github.com/dotty-staging/cats-mtl.git -[submodule "community-build/community-projects/coop-forward-compat"] - path = community-build/community-projects/coop-forward-compat - url = https://github.com/dotty-staging/coop.git -[submodule "community-build/community-projects/cats-effect-3-forward-compat"] - path = community-build/community-projects/cats-effect-3-forward-compat - url = https://github.com/dotty-staging/cats-effect.git - branch = series/3.x -[submodule "community-build/community-projects/scalacheck-forward-compat"] - path = community-build/community-projects/scalacheck-forward-compat - url = https://github.com/dotty-staging/scalacheck [submodule "community-build/community-projects/http4s"] path = community-build/community-projects/http4s url = https://github.com/dotty-staging/http4s.git From 806386ecd42858c564e7a646c4fa9b842ab27d2d Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 21 Apr 2022 12:36:12 +0200 Subject: [PATCH 112/121] Pick correct forwarder to avoid clashes We should pick the one which is better, but we mistakenly chose the worst one instead, since I misinterpreted the ordering. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 8a147d388ad0..ac85798cb128 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1266,7 +1266,7 @@ class Namer { typer: Typer => |and ${forwarder1.rhs.symbol}: ${alt2.widen} |have the same signature after erasure and overloading resolution could not disambiguate.""", exp.srcPos) - avoidClashWith(if cmp < 0 then forwarder else forwarder1, forwarders1) + avoidClashWith(if cmp < 0 then forwarder1 else forwarder, forwarders1) else val (forwarder2, forwarders2) = avoidClashWith(forwarder, forwarders1) (forwarder2, forwarders.derivedCons(forwarder1, forwarders2)) From 944bf01885aac9eecd57ebd5d39a56395edc8ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 21 Apr 2022 11:10:18 +0200 Subject: [PATCH 113/121] Remove leftover merge conflicts --- .github/workflows/ci.yaml | 52 +------------ .../scala/dotty/communitybuild/projects.scala | 73 +------------------ .../sbtplugin/CommunityBuildPlugin.scala | 11 --- 3 files changed, 5 insertions(+), 131 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ad5f18b1787..08e28c0c4a2b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -363,56 +363,10 @@ jobs: git submodule update --init --recursive --jobs 7 ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestC" -<<<<<<< HEAD - name: Show dependency tracking file if: ${{ always() }} run: cat community-build/dotty-community-build-deps || true - community_build_forward_compat: - runs-on: [self-hosted, Linux] - container: - image: lampepfl/dotty:2021-03-22 - options: --cpu-shares 4096 - volumes: - - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - - ${{ github.workspace }}/../../cache/general:/root/.cache - if: "github.event_name == 'schedule' && github.repository == 'lampepfl/dotty' - || ( - github.event_name == 'pull_request' - && !contains(github.event.pull_request.body, '[skip ci]') - && !contains(github.event.pull_request.body, '[skip community_build]') - && contains(github.event.pull_request.body, '[test_forward_compat]') - ) - || ( - github.event_name == 'workflow_dispatch' - && github.repository == 'lampepfl/dotty' - )" - - steps: - - name: Reset existing repo - run: git -c "http.https://github.com/.extraheader=" fetch --recurse-submodules=no "https://github.com/lampepfl/dotty" && git reset --hard FETCH_HEAD || true - - - name: Checkout cleanup script - uses: actions/checkout@v2 - - - name: Cleanup - run: .github/workflows/cleanup.sh - - - name: Git Checkout - uses: actions/checkout@v2 - - - name: Add SBT proxy repositories - run: cp -vf .github/workflows/repositories /root/.sbt/ ; true - - - name: Test - run: | - git submodule sync - git submodule update --init --recursive --jobs 7 - ./project/scripts/sbt "community-build/testOnly dotty.communitybuild.CommunityBuildTestForwardCompat" - -======= ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) test_sbt: runs-on: [self-hosted, Linux] container: @@ -512,11 +466,7 @@ jobs: - ${{ github.workspace }}/../../cache/sbt:/root/.sbt - ${{ github.workspace }}/../../cache/ivy:/root/.ivy2/cache - ${{ github.workspace }}/../../cache/general:/root/.cache -<<<<<<< HEAD - needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, community_build_forward_compat, test_sbt, test_java8] -======= - needs: [test_non_bootstrapped, test, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) + needs: [test_non_bootstrapped, test, mima, community_build_a, community_build_b, community_build_c, test_sbt, test_java8] if: "(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository == 'lampepfl/dotty'" env: NIGHTLYBUILD: yes diff --git a/community-build/src/scala/dotty/communitybuild/projects.scala b/community-build/src/scala/dotty/communitybuild/projects.scala index 74510f3a3c5b..3a0ec6c280b7 100644 --- a/community-build/src/scala/dotty/communitybuild/projects.scala +++ b/community-build/src/scala/dotty/communitybuild/projects.scala @@ -146,22 +146,6 @@ final case class SbtCommunityProject( s"--addPluginSbtFile=$sbtPluginFilePath" ) -<<<<<<< HEAD - def forwardCompat: SbtCommunityProject = - this.copy( - project = project + "-forward-compat", - dependencies = () => dependencies().map(forwardCompatMapping), - testOnlyDependencies = () => testOnlyDependencies().map(forwardCompatMapping), - isForwardCompatProject = true - ) - - def withScalaRelease(release: String): SbtCommunityProject = - this.copy( - scalacOptions = scalacOptions ++ Seq("-scala-output-version", release) - ) - -======= ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) object SbtCommunityProject: def scalacOptions = List( "-Xcheck-macros", @@ -431,12 +415,8 @@ object projects: project = "zio", sbtTestCommand = "testJVMDotty", sbtDocCommand = forceDoc("coreJVM"), -<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), - dependencies = () => List(izumiReflect) -======= - dependencies = List(izumiReflect) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) + dependencies =List(izumiReflect) ) lazy val munit = SbtCommunityProject( @@ -460,12 +440,8 @@ object projects: sbtTestCommand = "unitTests/test", // Adds package sbtDocCommand = "coreJVM/doc", -<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(munit, scodecBits), -======= dependencies = List(munit, scodecBits), ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val scalaParserCombinators = SbtCommunityProject( @@ -515,11 +491,6 @@ object projects: dependencies = List(cats, coop, disciplineSpecs2, scalacheck) ) -<<<<<<< HEAD - lazy val catsEffect3ForwardCompat = catsEffect3.forwardCompat.copy(compilerVersion = "3.0.2") - -======= ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) lazy val scalaParallelCollections = SbtCommunityProject( project = "scala-parallel-collections", sbtTestCommand = "test", @@ -552,12 +523,8 @@ object projects: project = "discipline", sbtTestCommand = "coreJVM/test;coreJS/test", sbtPublishCommand = "set every credentials := Nil;coreJVM/publishLocal;coreJS/publishLocal", -<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(scalacheck) -======= dependencies = List(scalacheck) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val disciplineMunit = SbtCommunityProject( @@ -597,11 +564,6 @@ object projects: dependencies = List(cats, disciplineMunit) ) -<<<<<<< HEAD - lazy val catsMtlForwardCompat = catsMtl.forwardCompat.copy(compilerVersion = "3.0.2") - -======= ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) lazy val coop = SbtCommunityProject( project = "coop", sbtTestCommand = "test", @@ -677,13 +639,8 @@ object projects: project = "Lucre", sbtTestCommand = "adjunctJVM/test;baseJVM/test;confluentJVM/test;coreJVM/test;dataJVM/test;exprJVM/test;geomJVM/test;lucre-bdb/test;testsJVM/test", extraSbtArgs = List("-Dde.sciss.lucre.ShortTests=true"), -<<<<<<< HEAD sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", - dependencies = () => List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), -======= - sbtPublishCommand = "adjunctJVM/publishLocal;baseJVM/publishLocal;confluentJVM/publishLocal;coreJVM/publishLocal;dataJVM/publishLocal;expr0JVM/publishLocal;expr1JVM/publishLocal;exprJVM/publishLocal;geomJVM/publishLocal;lucre-bdb/publishLocal", dependencies = List(scalaSTM, scissAsyncFile, scissEqual, scissFingerTree, scissLog, scissModel, scissNumbers, scissSerial, scissSpan, scalatest), ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val izumiReflect = SbtCommunityProject( @@ -703,8 +660,7 @@ object projects: lazy val akka = SbtCommunityProject( project = "akka", -<<<<<<< HEAD - extraSbtArgs = List(s"-Dakka.build.scalaVersion=$testedCompilerVersion"), + extraSbtArgs = List(s"-Dakka.build.scalaVersion=$compilerVersion"), sbtTestCommand = List( "set every targetSystemJdk := true", // selectively disable -Xfatal-warnings due to deprecations @@ -714,12 +670,7 @@ object projects: "akka-actor-tests/Test/compile", ).mkString("; "), scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(scalatest, scalatestplusJunit, scalatestplusScalacheck) -======= - extraSbtArgs = List(s"-Dakka.build.scalaVersion=$compilerVersion"), - sbtTestCommand = "set every targetSystemJdk := true; akka-actor-tests/Test/compile", dependencies = List(scalatest, scalatestplusJunit, scalatestplusScalacheck) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val monocle = SbtCommunityProject( @@ -733,11 +684,7 @@ object projects: extraSbtArgs = List("-Dcommunity=true", "-DcommunityRemote=true", "-Dquill.macro.stdout=true"), sbtTestCommand = "runCommunityBuild", sbtPublishCommand = "publishLocal", -<<<<<<< HEAD - dependencies = () => List(scalatest), -======= - dependencies = List(), // TODO add scalatest and pprint (see protoquill/build.sbt) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) + dependencies = List(scalatest), scalacOptions = List("-language:implicitConversions"), // disabled -Ysafe-init, due to bug in macro ) @@ -773,12 +720,8 @@ object projects: project = "fs2", sbtTestCommand = "coreJVM/test; coreJS/test", // io/test requires JDK9+ sbtPublishCommand = "coreJVM/publishLocal; coreJS/publishLocal", -<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) -======= dependencies = List(cats, catsEffect3, munitCatsEffect, scalacheckEffect, scodecBits) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val libretto = SbtCommunityProject( @@ -806,12 +749,8 @@ object projects: project = "spire", sbtTestCommand = "test", sbtPublishCommand = "publishLocal", -<<<<<<< HEAD scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Xcheck-macros"), - dependencies = () => List(cats, disciplineMunit) -======= dependencies = List(cats, disciplineMunit) ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val http4s = SbtCommunityProject( @@ -819,7 +758,7 @@ object projects: sbtTestCommand = "tests/test; server/test; client/test; ember-core/test; ember-server/test; ember-client/test; circe/test", sbtPublishCommand = "publishLocal", scalacOptions = SbtCommunityProject.scalacOptions.filter(_ != "-Ysafe-init"), - dependencies = () => List(cats, catsEffect3, fs2, disciplineMunit, scalacheckEffect) + dependencies = List(cats, catsEffect3, fs2, disciplineMunit, scalacheckEffect) ) end projects @@ -900,13 +839,9 @@ def allProjects = List( projects.libretto, projects.jacksonModuleScala, projects.specs2, -<<<<<<< HEAD projects.coop, - projects.coopForwardCompat, projects.spire, projects.http4s -======= ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) ) lazy val projectMap = allProjects.groupBy(_.project) diff --git a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala index fd9a6f48b9a8..0b580e1cb77a 100644 --- a/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala +++ b/sbt-community-build/src/dotty/communitybuild/sbtplugin/CommunityBuildPlugin.scala @@ -28,20 +28,9 @@ object CommunityBuildPlugin extends AutoPlugin { override val buildSettings: Seq[Setting[_]] = Seq( dependencyOverrides ++= { -<<<<<<< HEAD - if (scalaVersion.value.startsWith("3.")) { - val forwardCompatFilter: ModuleID => Boolean = if (isForwardCompatProject.value) (_.revision.contains("-forward-compat")) else (!_.revision.contains("-forward-compat")) - val stdlibOverrides = Seq( - scalaOrganization.value %% "scala3-library" % scalaVersion.value, - scalaOrganization.value %% "scala3-library_sjs1" % scalaVersion.value - ) - CommunityBuildDependencies.allOverrides(sLog.value).filter(forwardCompatFilter) ++ stdlibOverrides - } else Nil -======= if (scalaVersion.value.startsWith("3.")) CommunityBuildDependencies.allOverrides(sLog.value) else Nil ->>>>>>> parent of e39b618f9a (-Yscala-release support: extend community build with basic forward-compat tests (compiling selected projects with "-Yscala-release 3.0")) } ) } From 272621bfc023d3a81ddd6321c6ce7696e5ddb051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 21 Apr 2022 13:56:50 +0200 Subject: [PATCH 114/121] Remove submodules. This time for good --- community-build/community-projects/cats-effect-3-forward-compat | 1 - community-build/community-projects/cats-forward-compat | 1 - community-build/community-projects/cats-mtl-forward-compat | 1 - community-build/community-projects/coop-forward-compat | 1 - community-build/community-projects/discipline-forward-compat | 1 - .../community-projects/discipline-munit-forward-compat | 1 - .../community-projects/discipline-specs2-forward-compat | 1 - community-build/community-projects/munit-forward-compat | 1 - community-build/community-projects/scalacheck-forward-compat | 1 - .../community-projects/simulacrum-scalafix-forward-compat | 1 - 10 files changed, 10 deletions(-) delete mode 160000 community-build/community-projects/cats-effect-3-forward-compat delete mode 160000 community-build/community-projects/cats-forward-compat delete mode 160000 community-build/community-projects/cats-mtl-forward-compat delete mode 160000 community-build/community-projects/coop-forward-compat delete mode 160000 community-build/community-projects/discipline-forward-compat delete mode 160000 community-build/community-projects/discipline-munit-forward-compat delete mode 160000 community-build/community-projects/discipline-specs2-forward-compat delete mode 160000 community-build/community-projects/munit-forward-compat delete mode 160000 community-build/community-projects/scalacheck-forward-compat delete mode 160000 community-build/community-projects/simulacrum-scalafix-forward-compat diff --git a/community-build/community-projects/cats-effect-3-forward-compat b/community-build/community-projects/cats-effect-3-forward-compat deleted file mode 160000 index 2510979ee03d..000000000000 --- a/community-build/community-projects/cats-effect-3-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2510979ee03d10e60011812698567885547d85d7 diff --git a/community-build/community-projects/cats-forward-compat b/community-build/community-projects/cats-forward-compat deleted file mode 160000 index 6bbbc1e3477b..000000000000 --- a/community-build/community-projects/cats-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6bbbc1e3477b7da6b58a97844f773c3c445b4e5e diff --git a/community-build/community-projects/cats-mtl-forward-compat b/community-build/community-projects/cats-mtl-forward-compat deleted file mode 160000 index 149f002c8774..000000000000 --- a/community-build/community-projects/cats-mtl-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 149f002c8774b61df87cb846455d94ae858b3b54 diff --git a/community-build/community-projects/coop-forward-compat b/community-build/community-projects/coop-forward-compat deleted file mode 160000 index 8700dde73ef1..000000000000 --- a/community-build/community-projects/coop-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8700dde73ef16a6135014840c4e33361dbd015d7 diff --git a/community-build/community-projects/discipline-forward-compat b/community-build/community-projects/discipline-forward-compat deleted file mode 160000 index afd001326789..000000000000 --- a/community-build/community-projects/discipline-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit afd00132678985341db210b56f3b2ead1a8405c2 diff --git a/community-build/community-projects/discipline-munit-forward-compat b/community-build/community-projects/discipline-munit-forward-compat deleted file mode 160000 index 38ea89226b8d..000000000000 --- a/community-build/community-projects/discipline-munit-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 38ea89226b8ddedc891b160f75d57ae5177f19a1 diff --git a/community-build/community-projects/discipline-specs2-forward-compat b/community-build/community-projects/discipline-specs2-forward-compat deleted file mode 160000 index e689c3e809a8..000000000000 --- a/community-build/community-projects/discipline-specs2-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e689c3e809a89a03cdbbb3a1771e33148715f6c7 diff --git a/community-build/community-projects/munit-forward-compat b/community-build/community-projects/munit-forward-compat deleted file mode 160000 index 92f3ad9e8261..000000000000 --- a/community-build/community-projects/munit-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 92f3ad9e8261b4c142a551baaf61ef5fed84d36a diff --git a/community-build/community-projects/scalacheck-forward-compat b/community-build/community-projects/scalacheck-forward-compat deleted file mode 160000 index 976db31cd549..000000000000 --- a/community-build/community-projects/scalacheck-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 976db31cd549328167a90ecc6f5f31efa83cd845 diff --git a/community-build/community-projects/simulacrum-scalafix-forward-compat b/community-build/community-projects/simulacrum-scalafix-forward-compat deleted file mode 160000 index 2515271c46ad..000000000000 --- a/community-build/community-projects/simulacrum-scalafix-forward-compat +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2515271c46ad46512a43d20e1e8ae0793433cf0b From 6442498b1351b1e15c87e0353a5c4cc0cc948102 Mon Sep 17 00:00:00 2001 From: Rikito Taniguchi Date: Thu, 14 Apr 2022 18:35:47 +0900 Subject: [PATCH 115/121] refactor: TopLevel implicit is now allowed It seems like TopLevelCantBeImplicit is no longer the case as of https://github.com/lampepfl/dotty/pull/5754 And it is actually confirmed in https://github.com/lampepfl/dotty/blob/93fc41fcb624df73cc12d52b79d518a30a778a7c/tests/run/toplevel-implicits/a.b.scala#L19-L21 This commit replace the unnecessary check in from Checking.scala to assertion and deleted the `ErrorMessage` definition for `TopLevelCantBeImplicit`. I'm leaving the `TopLevelCantBeImplicitID` in `ErrorMessageID.scala` so we don't screw up the error number. --- compiler/src/dotty/tools/dotc/reporting/messages.scala | 7 ------- compiler/src/dotty/tools/dotc/typer/Checking.scala | 3 +-- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 18e2d234452d..c1545dadf46e 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -1510,13 +1510,6 @@ import transform.SymUtils._ def explain = "" } - class TopLevelCantBeImplicit(sym: Symbol)( - implicit ctx: Context) - extends SyntaxMsg(TopLevelCantBeImplicitID) { - def msg = em"""${hl("implicit")} modifier cannot be used for top-level definitions""" - def explain = "" - } - class TypesAndTraitsCantBeImplicit()(using Context) extends SyntaxMsg(TypesAndTraitsCantBeImplicitID) { def msg = em"""${hl("implicit")} modifier cannot be used for types or traits""" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 1cce3fdea280..1e597e42d2a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -473,8 +473,7 @@ object Checking { if sym.isInlineMethod && !sym.is(Deferred) && sym.allOverriddenSymbols.nonEmpty then checkInlineOverrideParameters(sym) if (sym.is(Implicit)) { - if (sym.owner.is(Package)) - fail(TopLevelCantBeImplicit(sym)) + assert(!sym.owner.is(Package), s"top-level implicit $sym should be wrapped by a package after typer") if sym.isType && (!sym.isClass || sym.is(Trait)) then fail(TypesAndTraitsCantBeImplicit()) } From 1ca0b92cac23dba6afbc0dcb173eeedc9e93368a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 22 Apr 2022 10:50:49 +0200 Subject: [PATCH 116/121] Fix typos --- .../tasty-inspector/stdlibExperimentalDefinitions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 7148a8c692ba..63b95c280b61 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -10,7 +10,7 @@ val experimentalDefinitionInLibrary = Set( //// New feature: Safe Exceptions - // Can be stabilized when safe exeptions language feature is stabilized. + // Can be stabilized when safe exceptions language feature is stabilized. "scala.CanThrow", "scala.unsafeExceptions", "scala.unsafeExceptions$", "scala.runtime.$throws$package$.$throws", @@ -102,7 +102,7 @@ val experimentalDefinitionInLibrary = Set( |Found @experimental definition in library not listed: |${missingFromList.toSeq.sorted.mkString("\n")} | - |If added new experimental defintions to the library, add them to the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala + |If added new experimental definitions to the library, add them to the list in tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala | |Test only: sbt "scala3-bootstrapped/testCompilation tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala" |""".stripMargin From 4fdf17da729963452fa9f88a2ae89480755f4eec Mon Sep 17 00:00:00 2001 From: johannes karoff Date: Sun, 24 Apr 2022 00:01:26 +0200 Subject: [PATCH 117/121] Fix typo in newClass docs --- library/src/scala/quoted/Quotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 013a99559fbb..08898b4f7a1c 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -3610,7 +3610,7 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * def decls(cls: Symbol): List[Symbol] = * List(Symbol.newMethod(cls, "foo", MethodType(Nil)(_ => Nil, _ => TypeRepr.of[Unit]))) * - * val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfInfo = None) + * val cls = Symbol.newClass(Symbol.spliceOwner, name, parents = parents.map(_.tpe), decls, selfType = None) * val fooSym = cls.declaredMethod("foo").head * * val fooDef = DefDef(fooSym, argss => Some('{println(s"Calling foo")}.asTerm)) From 9b6a0afd1d862f410007b3e1fe5c4d23c60cbbbb Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 24 Apr 2022 19:57:29 +0200 Subject: [PATCH 118/121] Build.scala: Fix warning due to postfix usage --- project/Build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Build.scala b/project/Build.scala index c614c59357ce..1d0055bac866 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1410,7 +1410,7 @@ object Build { val outputDir = languageReferenceConfig.value.get[OutputDir].get.value val expectedLinksFile = (file("project") / "scripts" / "expected-links" / "reference-expected-links.txt").toString import _root_.scala.sys.process._ - s"$script $outputDir $expectedLinksFile" ! + s"$script $outputDir $expectedLinksFile".! } } From d3ce1295e829df5bc122b517b64cd4da920f1216 Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 10 Mar 2022 09:20:43 +0100 Subject: [PATCH 119/121] Add file path escaping to Scaladoc As Jekyll (and in extension GitHub pages) makes starting a file/folder name with a couple characters illegal, an additional check is added for those and problematic paths are reported. To avoid always having "_docs" if not using -Yapi_subdirectory in url by default in the static site, we also replace that with "docs". Some tests concerning links were adjusted to accomodate the changes. Example testcase for illegal jekyll chars in Scaladoc was also added. --- .../src/{_docs => docs}/tests/Adoc.scala | 2 +- .../src/example/JekyllIncompat.scala | 5 +++++ .../src/dotty/tools/scaladoc/DocContext.scala | 17 +++++++++++++++++ .../src/dotty/tools/scaladoc/Scaladoc.scala | 1 + .../tools/scaladoc/renderers/Locations.scala | 1 + .../tools/scaladoc/renderers/Renderer.scala | 2 +- .../tools/scaladoc/site/StaticSiteContext.scala | 11 ++++++++--- .../src/dotty/tools/scaladoc/util/check.scala | 14 ++++++++++++++ .../dotty/tools/scaladoc/ReportingTest.scala | 4 ++-- .../tools/scaladoc/site/NavigationTest.scala | 2 +- .../scaladoc/site/SiteGeneratationTest.scala | 14 +++++++------- 11 files changed, 58 insertions(+), 15 deletions(-) rename scaladoc-testcases/src/{_docs => docs}/tests/Adoc.scala (59%) create mode 100644 scaladoc-testcases/src/example/JekyllIncompat.scala create mode 100644 scaladoc/src/dotty/tools/scaladoc/util/check.scala diff --git a/scaladoc-testcases/src/_docs/tests/Adoc.scala b/scaladoc-testcases/src/docs/tests/Adoc.scala similarity index 59% rename from scaladoc-testcases/src/_docs/tests/Adoc.scala rename to scaladoc-testcases/src/docs/tests/Adoc.scala index 04b2c3918023..af93f9eddfa0 100644 --- a/scaladoc-testcases/src/_docs/tests/Adoc.scala +++ b/scaladoc-testcases/src/docs/tests/Adoc.scala @@ -1,4 +1,4 @@ -package _docs.tests +package docs.tests class Adoc: def foo = 123 diff --git a/scaladoc-testcases/src/example/JekyllIncompat.scala b/scaladoc-testcases/src/example/JekyllIncompat.scala new file mode 100644 index 000000000000..65c735804d57 --- /dev/null +++ b/scaladoc-testcases/src/example/JekyllIncompat.scala @@ -0,0 +1,5 @@ +package example + +package jekyllIncompat: + object #~ + object ~>~ diff --git a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala index 64c072aa3de3..366b5b8c162c 100644 --- a/scaladoc/src/dotty/tools/scaladoc/DocContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/DocContext.scala @@ -17,6 +17,8 @@ import java.io.PrintStream import scala.io.Codec import java.net.URL import scala.util.Try +import scala.collection.mutable +import dotty.tools.scaladoc.util.Check.checkJekyllIncompatPath type CompilerContext = dotty.tools.dotc.core.Contexts.Context @@ -90,3 +92,18 @@ case class DocContext(args: Scaladoc.Args, compilerContext: CompilerContext): val externalDocumentationLinks = args.externalMappings + + + // We collect and report any generated files incompatible with Jekyll + private lazy val jekyllIncompatLinks = mutable.HashSet[String]() + + def checkPathCompat(path: Seq[String]): Unit = + if checkJekyllIncompatPath(path) then jekyllIncompatLinks.add(path.mkString("/")) + + def reportPathCompatIssues(): Unit = + if !jekyllIncompatLinks.isEmpty then + report.warning( + s"""Following generated file paths might not be compatible with Jekyll: + |${jekyllIncompatLinks.mkString("\n")} + |If using GitHub Pages consider adding a \".nojekyll\" file. + """.stripMargin)(using compilerContext) diff --git a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala index 0b746f19035b..9d263441ace1 100644 --- a/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala +++ b/scaladoc/src/dotty/tools/scaladoc/Scaladoc.scala @@ -229,5 +229,6 @@ object Scaladoc: given docContext: DocContext = new DocContext(args, ctx) val module = ScalaModuleProvider.mkModule() new dotty.tools.scaladoc.renderers.HtmlRenderer(module.rootPackage, module.members).render() + docContext.reportPathCompatIssues() report.inform("generation completed successfully") docContext diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala index 9490693fb759..12615671e538 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Locations.scala @@ -47,6 +47,7 @@ trait Locations(using ctx: DocContext): case "" :: tail => "_empty_" :: tail case other => other if ctx.args.apiSubdirectory then "api" :: fqn else fqn + ctx.checkPathCompat(path) cache.put(dri, path) path case cached => cached diff --git a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala index 6d177e113c24..4ce81450afbb 100644 --- a/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala +++ b/scaladoc/src/dotty/tools/scaladoc/renderers/Renderer.scala @@ -120,7 +120,7 @@ abstract class Renderer(rootPackage: Member, val members: Map[DRI, Member], prot val all = navigablePage +: redirectPages // We need to check for conflicts only if we have top-level member called docs val hasPotentialConflict = - rootPackage.members.exists(m => m.name.startsWith("_docs")) + rootPackage.members.exists(m => m.name.startsWith("docs")) if hasPotentialConflict then def walk(page: Page): Unit = diff --git a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala index 48c2acb745af..51ee9e30e933 100644 --- a/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala +++ b/scaladoc/src/dotty/tools/scaladoc/site/StaticSiteContext.scala @@ -23,7 +23,12 @@ class StaticSiteContext( val docsPath = root.toPath.resolve("_docs") val blogPath = root.toPath.resolve("_blog") - val relativizeFrom = if args.apiSubdirectory then docsPath else root.toPath + def relativize(path: Path): Path = + if args.apiSubdirectory then + docsPath.relativize(path) + else + val relativised = docsPath.relativize(path) + Paths.get("docs").resolve(relativised) val siteExtensions = Set(".html", ".md") @@ -47,7 +52,7 @@ class StaticSiteContext( val redirectFrom = loadedTemplate.templateFile.settings.getOrElse("page", Map.empty).asInstanceOf[Map[String, Object]].get("redirectFrom") def redirectToTemplate(redirectFrom: String) = val path = if redirectFrom.startsWith("/") - then relativizeFrom.resolve(redirectFrom.drop(1)) + then docsPath.resolve(redirectFrom.drop(1)) else loadedTemplate.file.toPath.resolveSibling(redirectFrom) val driFrom = driFor(path) val driTo = driFor(loadedTemplate.file.toPath) @@ -93,7 +98,7 @@ class StaticSiteContext( } def driFor(dest: Path): DRI = - val rawFilePath = relativizeFrom.relativize(dest) + val rawFilePath = relativize(dest) val pageName = dest.getFileName.toString val dotIndex = pageName.lastIndexOf('.') diff --git a/scaladoc/src/dotty/tools/scaladoc/util/check.scala b/scaladoc/src/dotty/tools/scaladoc/util/check.scala new file mode 100644 index 000000000000..cbbeaded312e --- /dev/null +++ b/scaladoc/src/dotty/tools/scaladoc/util/check.scala @@ -0,0 +1,14 @@ +package dotty.tools.scaladoc.util + +object Check: + /** + * Jekyll (also used by GitHub Pages) by default makes a couple characters + * illegal to use in file name beginnings. + */ + def checkJekyllIncompatPath(path: Seq[String]): Boolean = + path.find( filename => + filename.matches("^~.*") + || filename.matches("^\\_.*") + || filename.matches("^\\..*") + || filename.matches("^\\#.*") + ).isDefined \ No newline at end of file diff --git a/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala b/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala index e64eac41ac66..fc215b1de229 100644 --- a/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/ReportingTest.scala @@ -76,11 +76,11 @@ class ReportingTest: val docsRoot = testDocPath.resolve("conflicts-pages").toString checkReportedDiagnostics(_.copy( docsRoot = Some(docsRoot), - tastyFiles = tastyFiles("tests", rootPck = "_docs") + tastyFiles = tastyFiles("tests", rootPck = "docs") )){ diag => assertNoWarning(diag) val Seq(msg) = diag.errorMsgs.map(_.toLowerCase) - Seq("conflict","api", "static", "page", "_docs/tests/adoc.html") + Seq("conflict","api", "static", "page", "docs/tests/adoc.html") .foreach( word => Assert.assertTrue(s"Error message: $msg should contains $word", msg.contains(word)) ) diff --git a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala index 39bd7acf84f6..0a2d1891a3cc 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/NavigationTest.scala @@ -44,5 +44,5 @@ class NavigationTest extends BaseHtmlTest: )), )) - testNavMenu("_docs/Adoc.html", topLevelNav) + testNavMenu("docs/Adoc.html", topLevelNav) } diff --git a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala index 1b3c53aba3c0..c46875c7d0cd 100644 --- a/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala +++ b/scaladoc/test/dotty/tools/scaladoc/site/SiteGeneratationTest.scala @@ -32,13 +32,13 @@ class SiteGeneratationTest extends BaseHtmlTest: } def testDocPages()(using ProjectContext) = - checkFile("_docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) - checkFile("_docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) - checkFile("_docs/dir/nested.html")( + checkFile("docs/Adoc.html")(title = "Adoc", header = "Header in Adoc", parents = Seq(projectName)) + checkFile("docs/dir/index.html")(title = "A directory", header = "A directory", parents = Seq(projectName)) + checkFile("docs/dir/nested.html")( title = "Nested in a directory", header = "Nested in a directory", parents = Seq(projectName, "A directory")) def testDocIndexPage()(using ProjectContext) = - checkFile("_docs/index.html")(title = projectName, header = s"$projectName in header") + checkFile("docs/index.html")(title = projectName, header = s"$projectName in header") def testApiPages( mainTitle: String = "API", @@ -66,13 +66,13 @@ class SiteGeneratationTest extends BaseHtmlTest: testDocIndexPage() testApiPages() - withHtmlFile("_docs/Adoc.html"){ content => + withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "../tests/site/SomeClass.html") } withHtmlFile("tests/site/SomeClass.html"){ content => content.assertAttr(".breadcrumbs a","href", - "../../_docs/index.html", "../../index.html", "../site.html", "SomeClass.html" + "../../docs/index.html", "../../index.html", "../site.html", "SomeClass.html" ) } } @@ -98,7 +98,7 @@ class SiteGeneratationTest extends BaseHtmlTest: @Test def staticLinking() = withGeneratedSite(testDocPath.resolve("static-links")){ - withHtmlFile("_docs/Adoc.html"){ content => + withHtmlFile("docs/Adoc.html"){ content => content.assertAttr("p a","href", "dir/html.html", "dir/name...with..dots..html", From a01d23f744c62e158fbccd36d7cc92ac826f21c0 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Thu, 21 Apr 2022 10:18:06 +0100 Subject: [PATCH 120/121] Add some overview notes on the GADT implementation Authored by Aleksander Boruch-Gruszecki. The intent here is to start to have some more detailed information on the implementation, even in an initially rough form that can be refined over time. --- docs/_docs/internals/gadts.md | 205 ++++++++++++++++++++++++++++++++++ docs/sidebar.yml | 1 + 2 files changed, 206 insertions(+) create mode 100644 docs/_docs/internals/gadts.md diff --git a/docs/_docs/internals/gadts.md b/docs/_docs/internals/gadts.md new file mode 100644 index 000000000000..777b9dd32e39 --- /dev/null +++ b/docs/_docs/internals/gadts.md @@ -0,0 +1,205 @@ +# GADTs - Broad overview + +There are multiple levels to the implementation. They deal with slightly different problems. The most important levels are the following ones: + +1. Figuring out which relationships are necessary (PatternTypeConstrainer) +2. Breaking down the relationships (TypeComparer) +3. Recording and bookkeeping of relationships (GadtConstraint) +4. Looking up the information (TypeComparer, GadtConstraint) + +Out of the levels above, 4. is by far the simplest one. + +There are also other parts to supporting GADTs. Roughly in order of importance, they are: + +1. At specific points, abstract types are added to GadtConstraint. For instance, when entering a method, we add all type parameters to GadtConstraint. +2. Casts need to be inserted when GADTs were used for type comparison. + 1. `TypeComparer` keeps track of whether a GADT constraint was used in a mutable variable `usedGadt`. +3. GADT constraints need to be stored in attachments to be used in PostTyper. + 1. Attachment key is named `inferredGadtConstraints`. +4. When we select members on a type that may have GADT constraints, we perform special "healing" by approximating the type using those constraints. We cannot take the constraints into account because member lookup is cached, and GADT constraints are only valid for specific scopes. + +# Useful widgets + +## Expr + +This is the classical GADT example: + +```scala +enum Expr[T] { + case IntLit(value: Int) extends Expr[Int] + case BoolLit(value: Boolean) extends Expr[Boolean] + case IfExpr( + cond: Expr[Boolean], + when: Expr[T], + otherwise: Expr[T], + ) +} +``` + +## EQ + +The following enum will result in an equality constraint between `S` and `T` if we match on it: + +```scala +enum EQ[S, T] { + case Refl[U]() extends EQ[U, U] +} +``` + +## SUB + +The following enum will result in a subtyping constraint `S <: T` if we match on it: + +```scala +enum SUB[-S, +T] { + case Refl[U]() extends SUB[U, U] +} +``` + +# Details of above + +## What abstract types can have GADT constraints + +Right now, we record GADT constraints for: + +- function/method type parameters +- class type parameters + +There is a branch on the way which will also record them for type members (so path-dependent types) and singleton types. It has a paper associated: "Implementing path-depepdent GADTs for Scala 3". + +## What are necessary relationships? Any examples? + +### Covariance means no constraint is necessary + +Standard (non-case) classes allow "strange" inheritance which means that we cannot infer any information from covariant type parameters. + +```scala +class Expr[+A] +class IntList extends Expr[List[Int]] + +def foo[T](e: Expr[List[T]]): T = + e match { + case _ : IntList => + // e : Expr[List[Int]] + // T <: Int + 0 + } + +class Weird(list: List[String]) extends IntList with Expr[Nothing] +``` + +Case classes have a special check which disallows inheritance like `Weird`. This means we can infer extra information from them. + +## Breaking down the constraints + +```scala +class Expr[A] +class IntList extends Expr[List[Int]] + +def foo[T](e: Expr[List[T]]): T = + e match { + case _ : IntList => + // Level 1: + // We start with e : Expr[List[T]] + // We check that e : IntList <: Expr[List[Int] + // Expr is invariant, + // so we have List[Int] <: List[T] , List[T] <: List[Int] + // Level 2: + // We compare List[Int] <: List[T] + // We record Int <: T + // We compare List[T] <: List[Int] + // We record T <: Int + 0 + } +``` + +## Relation betweeen GadtConstraint and OrderingConstraint + +### Internal and external types + +GadtConstraint uses OrderingConstraint as the datastructure to record information about GADT constraints. + +OrderingConstraint only supports working with TypeParamRefs. + +GadtConstraint wants to record information about things other than TypeParamRefs. + +To solve this, GadtConstraint internally creates TypeParamRefs which it adds to the OrderingConstraint it keeps internally. Whenever a GADT constraint is added, we "internalize" the type by replacing all the external types with the internal TypeParamRefs. Whenever we take bound information out of the GADT constraint, we need to "externalize" the types by replacing the internal TypeParamRefs with their external versions. To implement this, GadtConstraint keeps a bidirectional mapping between the external types and the internal TypeParamRefs. + +The TypeParamRefs and TypeVars registered in one constraint cannot ever be present in types mentioned in the other type constraint. The internal TypeParamRefs and TypeVars cannot ever leak out of the GadtConstraint. We cannot ever record a bound in GadtConstraint which mentions TypeParamRefs used for type inference. (That part is ensured by the way TypeComparer is organised – we will always try to record bounds in the "normal" constraint before recording a GADT bound.) + +# Other details + +## TypeComparer approximations + +TypeComparer sometimes approximates the types it compares. Let's see an example based on these definitions: + +```scala +class Expr[T] +class IntList extends Expr[Int] +``` + +when comparing if `IntList <: Expr[Int]`, `TypeComparer` will approximate `IntList` to `Expr[Int]`. Then it will compare `Expr[Int] <: Expr[Int]` with appropriate variables set. + +The variables which TypeComparer sets are `approxState` and `frozenGadt`. + +## Necessary/sufficient either + +TypeComparer sometimes needs to approximate some constraints, specifically when dealing with intersection and union types. The way this approximation works changes if we're currently inferring GADT constraints. This is hopefully documented well in TypeComparer in doc comments for `necessaryEither` and `sufficientEither`. + +## Types bound in patterns + +```scala +(list : List[Int]) match { + case lst : List[a] => + // a is a new type bound in the pattern. + // We do not record any information about a. + // We should know that a <: Int. + // (Or it's fine to just have a =:= Int.) + // We would not have this issue if we used a custom unapply. + // Type case patterns create a fresh symbol even if they shouldn't. + // (See indexPattern in Typer.) +} +``` + +## Internal structure of OrderingConstraint + +Imagine we have two type parameters in scope, `A` and `B`. + +We could record the following in `ctx.gadt`: + +```text +A <: Int +B <: A +``` + +Then, we expect that calling ``ctx.gadt.bounds(`B`)`` will return `` `<: Int` ``. + +In order to handle this, `GadtConstraint` relies on `OrderingConstraint`. Internally, it will represent the above constraints as follows: + +```text +A <: Int +B <: Int +B <: A +``` + +The first two constraints are "entries" – they are easy to look up whenever we ask for bounds of `A` or `B`. The third constraint is an ordering – it helps with correctly propagating the bounds we record. + +# Possible broad improvements + +## Allow OrderingConstraint to record bounds for things other than TypeParamRefs + +This would mean we no longer need to keep the bidirectional mapping in GadtConstraint. + +## Not mixing OrderingConstraint and ConstraintHandling in GadtConstraint + +GadtConstraint right now mixes OrderingConstraint and ConstraintHandling. The first one is supposed to be the immutable constraint datastructure. The second one implements mutable functionality around a variable containing the immutable datastructure. + +GadtConstraint mixes them both. Things would be better organised if GadtConstraint was split like the normal constraint. + +## Creating a separate TypeComparer for breaking down types into GADT constraints + +TypeComparer is biased towards one specific way of approximating constraints. When we infer types, it's ok to be "optimistic". When inferring GADT constraints, we should be as pessimistic as possible, in order to only infer constraints which are necessary. + +TypeComparer was originally written to support type inference and GADTs were only added later on. This means that the "default" way TypeComparer approximates wasn't even noticeable before, but when inferring GADT constraints could result in inferring unsound information. + +We could potentially fix this by creating a special TypeComparer which would *only* "break down" subtype relationships to record GADT constraints. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 7c68120bfbc2..95786121e8f4 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -193,5 +193,6 @@ subsection: - page: internals/type-system.md - page: internals/dotty-internals-1-notes.md - page: internals/debug-macros.md + - page: internals/gadts.md - page: release-notes-0.1.2.md hidden: true From 70e8f4dca042911abf94ba0eb48141b40d9d1313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Zyba=C5=82a?= Date: Tue, 26 Apr 2022 15:50:14 +0200 Subject: [PATCH 121/121] Change language reference version to 3.1.2 --- project/Build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/Build.scala b/project/Build.scala index 1d0055bac866..e925b8c84324 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1397,6 +1397,7 @@ object Build { .add(OutputDir("scaladoc/output/reference")) .add(SiteRoot(s"${temp.getAbsolutePath}/docs")) .add(ProjectName("Scala 3 Reference")) + .add(ProjectVersion("3.1.2")) // TODO: Change that later to the current version tag. (This must happen on first forward this branch to stable release tag) .remove[VersionsDictionaryUrl] .add(SourceLinks(List( dottySrcLink(referenceVersion, temp.getAbsolutePath + "=")