diff options
20 files changed, 328 insertions, 207 deletions
diff --git a/.gitignore b/.gitignore index 9842e0c6b..170cf4823 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ scala-scala out/ build/ !out/.keep +testlogs/ # Ignore build-file .packages diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index c253af852..9b55720b8 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -391,80 +391,80 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def ModuleDef(tree: Tree)(name: TermName, impl: Template) = tree match { case tree: ModuleDef if (name eq tree.name) && (impl eq tree.impl) => tree - case _ => untpd.ModuleDef(name, impl).withPos(tree.pos) + case _ => finalize(tree, untpd.ModuleDef(name, impl)) } def ParsedTry(tree: Tree)(expr: Tree, handler: Tree, finalizer: Tree) = tree match { case tree: ParsedTry if (expr eq tree.expr) && (handler eq tree.handler) && (finalizer eq tree.finalizer) => tree - case _ => untpd.ParsedTry(expr, handler, finalizer).withPos(tree.pos) + case _ => finalize(tree, untpd.ParsedTry(expr, handler, finalizer)) } def SymbolLit(tree: Tree)(str: String) = tree match { case tree: SymbolLit if str == tree.str => tree - case _ => untpd.SymbolLit(str).withPos(tree.pos) + case _ => finalize(tree, untpd.SymbolLit(str)) } def InterpolatedString(tree: Tree)(id: TermName, segments: List[Tree]) = tree match { case tree: InterpolatedString if (id eq tree.id) && (segments eq tree.segments) => tree - case _ => untpd.InterpolatedString(id, segments).withPos(tree.pos) + case _ => finalize(tree, untpd.InterpolatedString(id, segments)) } def Function(tree: Tree)(args: List[Tree], body: Tree) = tree match { case tree: Function if (args eq tree.args) && (body eq tree.body) => tree - case _ => untpd.Function(args, body).withPos(tree.pos) + case _ => finalize(tree, untpd.Function(args, body)) } def InfixOp(tree: Tree)(left: Tree, op: Ident, right: Tree) = tree match { case tree: InfixOp if (left eq tree.left) && (op eq tree.op) && (right eq tree.right) => tree - case _ => untpd.InfixOp(left, op, right).withPos(tree.pos) + case _ => finalize(tree, untpd.InfixOp(left, op, right)) } def PostfixOp(tree: Tree)(od: Tree, op: Ident) = tree match { case tree: PostfixOp if (od eq tree.od) && (op eq tree.op) => tree - case _ => untpd.PostfixOp(od, op).withPos(tree.pos) + case _ => finalize(tree, untpd.PostfixOp(od, op)) } def PrefixOp(tree: Tree)(op: Ident, od: Tree) = tree match { case tree: PrefixOp if (op eq tree.op) && (od eq tree.od) => tree - case _ => untpd.PrefixOp(op, od).withPos(tree.pos) + case _ => finalize(tree, untpd.PrefixOp(op, od)) } def Parens(tree: Tree)(t: Tree) = tree match { case tree: Parens if t eq tree.t => tree - case _ => untpd.Parens(t).withPos(tree.pos) + case _ => finalize(tree, untpd.Parens(t)) } def Tuple(tree: Tree)(trees: List[Tree]) = tree match { case tree: Tuple if trees eq tree.trees => tree - case _ => untpd.Tuple(trees).withPos(tree.pos) + case _ => finalize(tree, untpd.Tuple(trees)) } def Throw(tree: Tree)(expr: Tree) = tree match { case tree: Throw if expr eq tree.expr => tree - case _ => untpd.Throw(expr).withPos(tree.pos) + case _ => finalize(tree, untpd.Throw(expr)) } def WhileDo(tree: Tree)(cond: Tree, body: Tree) = tree match { case tree: WhileDo if (cond eq tree.cond) && (body eq tree.body) => tree - case _ => untpd.WhileDo(cond, body).withPos(tree.pos) + case _ => finalize(tree, untpd.WhileDo(cond, body)) } def DoWhile(tree: Tree)(body: Tree, cond: Tree) = tree match { case tree: DoWhile if (body eq tree.body) && (cond eq tree.cond) => tree - case _ => untpd.DoWhile(body, cond).withPos(tree.pos) + case _ => finalize(tree, untpd.DoWhile(body, cond)) } def ForYield(tree: Tree)(enums: List[Tree], expr: Tree) = tree match { case tree: ForYield if (enums eq tree.enums) && (expr eq tree.expr) => tree - case _ => untpd.ForYield(enums, expr).withPos(tree.pos) + case _ => finalize(tree, untpd.ForYield(enums, expr)) } def ForDo(tree: Tree)(enums: List[Tree], body: Tree) = tree match { case tree: ForDo if (enums eq tree.enums) && (body eq tree.body) => tree - case _ => untpd.ForDo(enums, body).withPos(tree.pos) + case _ => finalize(tree, untpd.ForDo(enums, body)) } def GenFrom(tree: Tree)(pat: Tree, expr: Tree) = tree match { case tree: GenFrom if (pat eq tree.pat) && (expr eq tree.expr) => tree - case _ => untpd.GenFrom(pat, expr).withPos(tree.pos) + case _ => finalize(tree, untpd.GenFrom(pat, expr)) } def GenAlias(tree: Tree)(pat: Tree, expr: Tree) = tree match { case tree: GenAlias if (pat eq tree.pat) && (expr eq tree.expr) => tree - case _ => untpd.GenAlias(pat, expr).withPos(tree.pos) + case _ => finalize(tree, untpd.GenAlias(pat, expr)) } def ContextBounds(tree: Tree)(bounds: TypeBoundsTree, cxBounds: List[Tree]) = tree match { case tree: ContextBounds if (bounds eq tree.bounds) && (cxBounds eq tree.cxBounds) => tree - case _ => untpd.ContextBounds(bounds, cxBounds).withPos(tree.pos) + case _ => finalize(tree, untpd.ContextBounds(bounds, cxBounds)) } def PatDef(tree: Tree)(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree) = tree match { case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree - case _ => untpd.PatDef(mods, pats, tpt, rhs).withPos(tree.pos) + case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)) } } diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index f8267072e..0e8ae196a 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -103,7 +103,7 @@ object Decorators { * as long as `xs`. */ def zipWithConserve[U](ys: List[U])(f: (T, U) => T): List[T] = - if (xs.isEmpty) xs + if (xs.isEmpty || ys.isEmpty) Nil else { val x1 = f(xs.head, ys.head) val xs1 = xs.tail.zipWithConserve(ys.tail)(f) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index 17eb8d39b..91e65ab66 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -21,7 +21,7 @@ trait MessageRendering { * @return string stripped of ANSI escape codes */ def stripColor(str: String): String = - str.replaceAll("\u001B\\[[;\\d]*m", "") + str.replaceAll("\u001b\\[.*?m", "") /** When inlining a method call, if there's an error we'd like to get the * outer context and the `pos` at which the call was inlined. diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index b0ae36612..197d18374 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -670,59 +670,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { override def toString = "X" + ((extractor, nextBinder.name)) } - /** - * An optimized version of ExtractorTreeMaker for Products. - * For now, this is hard-coded to case classes, and we simply extract the case class fields. - * - * The values for the subpatterns, as specified by the case class fields at the time of extraction, - * are stored in local variables that re-use the symbols in `subPatBinders`. - * This makes extractor patterns more debuggable (SI-5739) as well as - * avoiding mutation after the pattern has been matched (SI-5158, SI-6070) - * - * TODO: make this user-definable as follows - * When a companion object defines a method `def unapply_1(x: T): U_1`, but no `def unapply` or `def unapplySeq`, - * the extractor is considered to match any non-null value of type T - * the pattern is expected to have as many sub-patterns as there are `def unapply_I(x: T): U_I` methods, - * and the type of the I'th sub-pattern is `U_I`. - * The same exception for Seq patterns applies: if the last extractor is of type `Seq[U_N]`, - * the pattern must have at least N arguments (exactly N if the last argument is annotated with `: _*`). - * The arguments starting at N (and beyond) are taken from the sequence returned by apply_N, - * and it is checked that the sequence has enough elements to provide values for all expected sub-patterns. - * - * For a case class C, the implementation is assumed to be `def unapply_I(x: C) = x._I`, - * and the extractor call is inlined under that assumption. - */ - case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])( - val subPatBinders: List[Symbol], - val subPatRefs: List[Tree], - val mutableBinders: List[Symbol], - binderKnownNonNull: Boolean, - val ignoredSubPatBinders: Set[Symbol] - ) extends FunTreeMaker with PreserveSubPatBinders { - - val nextBinder = prevBinder // just passing through - - // mutable binders must be stored to avoid unsoundness or seeing mutation of fields after matching (SI-5158, SI-6070) - def extraStoredBinders: Set[Symbol] = mutableBinders.toSet - - def chainBefore(next: Tree)(casegen: Casegen): Tree = { - val nullCheck: Tree = ref(prevBinder).select(defn.Object_ne).appliedTo(Literal(Constant(null))) - - val cond: Option[Tree] = - if (binderKnownNonNull) extraCond - else extraCond.map(nullCheck.select(defn.Boolean_&&).appliedTo).orElse(Some(nullCheck)) - - cond match { - case Some(cond: Tree) => - casegen.ifThenElseZero(cond, bindSubPats(next)) - case _ => - bindSubPats(next) - } - } - - override def toString = "P" + ((prevBinder.name, extraCond getOrElse "", introducedRebindings)) - } - object IrrefutableExtractorTreeMaker { // will an extractor with unapply method of methodtype `tp` always succeed? // note: this assumes the other side-conditions implied by the extractor are met @@ -1392,15 +1339,11 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { // TODO: check unargs == args def apply(tree: Tree, binder: Symbol): ExtractorCall = { tree match { + case Typed(unapply, _) => apply(unapply, binder) case UnApply(unfun, implicits, args) => val castedBinder = ref(binder).ensureConforms(tree.tpe) val synth = if (implicits.isEmpty) unfun.appliedTo(castedBinder) else unfun.appliedTo(castedBinder).appliedToArgs(implicits) - new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) // extractor - case Typed(unapply@ UnApply(unfun, implicits, args), tpt) => - val castedBinder = ref(binder).ensureConforms(unapply.tpe) - val synth = /*Typed(*/ if (implicits.isEmpty) unfun.appliedTo(castedBinder) else unfun.appliedTo(castedBinder).appliedToArgs(implicits) //, tpt) - new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) // extractor - case Apply(fun, args) => new ExtractorCallProd(alignPatterns(tree, tree.tpe), fun, args, fun.tpe) // case class + new ExtractorCallRegular(alignPatterns(tree, synth.tpe), synth, args, synth.tpe) } } } @@ -1544,34 +1487,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { else Some(expectedLength) } - // TODO: to be called when there's a def unapplyProd(x: T): U - // U must have N members _1,..., _N -- the _i are type checked, call their type Ti, - // for now only used for case classes -- pretending there's an unapplyProd that's the identity (and don't call it) - class ExtractorCallProd(aligner: PatternAligned, val fun: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { - /** Create the TreeMaker that embodies this extractor call - * - * `binder` has been casted to `paramType` if necessary - * `binderKnownNonNull` indicates whether the cast implies `binder` cannot be null - * when `binderKnownNonNull` is `true`, `ProductExtractorTreeMaker` does not do a (redundant) null check on binder - */ - def treeMaker(binder: Symbol, binderKnownNonNull: Boolean, pos: Position, binderTypeTested: Type): TreeMaker = { - val paramAccessors = binder.caseAccessors - // binders corresponding to mutable fields should be stored (SI-5158, SI-6070) - // make an exception for classes under the scala package as they should be well-behaved, - // to optimize matching on List - val mutableBinders = ( - if (//!binder.info.typeSymbol.hasTransOwner(ScalaPackageClass) // TODO: DDD ??? - // && - (paramAccessors exists (_.hasAltWith(x => x.symbol is Flags.Mutable)))) - subPatBinders.zipWithIndex.collect{ case (binder, idx) if paramAccessors(idx).hasAltWith(x => x.symbol is Flags.Mutable) => binder } - else Nil - ) - - // checks binder ne null before chaining to the next extractor - ProductExtractorTreeMaker(binder, lengthGuard(binder))(subPatBinders, subPatRefs(binder), mutableBinders, binderKnownNonNull, ignoredSubPatBinders) - } - } - class ExtractorCallRegular(aligner: PatternAligned, extractorCallIncludingDummy: Tree, val args: List[Tree], val resultType: Type) extends ExtractorCall(aligner) { /** Create the TreeMaker that embodies this extractor call @@ -1893,11 +1808,6 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { } object alignPatterns extends ScalacPatternExpander { - /** Converts a T => (A, B, C) extractor to a T => ((A, B, CC)) extractor. - */ - def tupleExtractor(extractor: Extractor): Extractor = - extractor.copy(fixed = defn.tupleType(extractor.fixed) :: Nil) - private def validateAligned(tree: Tree, aligned: Aligned): Aligned = { import aligned._ @@ -1939,29 +1849,13 @@ class PatternMatcher extends MiniPhaseTransform with DenotTransformer { } val patterns = newPatterns(args) val isSeq = sel.symbol.name == nme.unapplySeq - val isUnapply = sel.symbol.name == nme.unapply val extractor = sel.symbol.name match { case nme.unapply => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = false) case nme.unapplySeq => unapplyMethodTypes(tree, /*fn*/sel, args, resultType, isSeq = true) case _ => applyMethodTypes(/*fn*/sel.tpe) } - /** Rather than let the error that is SI-6675 pollute the entire matching - * process, we will tuple the extractor before creation Aligned so that - * it contains known good values. - */ - def prodArity = extractor.prodArity - def acceptMessage = if (extractor.isErroneous) "" else s" to hold ${extractor.offeringString}" - val requiresTupling = isUnapply && patterns.totalArity == 1 && prodArity > 1 - - //if (requiresTupling && effectivePatternArity(args) == 1) - // currentUnit.deprecationWarning(sel.pos, s"${sel.symbol.owner} expects $prodArity patterns$acceptMessage but crushing into $prodArity-tuple to fit single pattern (SI-6675)") - - val normalizedExtractor = - if (requiresTupling) - tupleExtractor(extractor) - else extractor - validateAligned(fn, Aligned(patterns, normalizedExtractor)) + validateAligned(fn, Aligned(patterns, extractor)) } def apply(tree: Tree, resultType: Type): Aligned = tree match { diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala index 9dfd92fe9..13af7e112 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala @@ -144,12 +144,13 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { /** The class * + * package p * case class C(x: T, y: T) * * gets the hashCode method: * * def hashCode: Int = { - * <synthetic> var acc: Int = 0xcafebabe; + * <synthetic> var acc: Int = "p.C".hashCode // constant folded * acc = Statics.mix(acc, x); * acc = Statics.mix(acc, Statics.this.anyHash(y)); * Statics.finalizeHash(acc, 2) @@ -157,7 +158,7 @@ class SyntheticMethods(thisTransformer: DenotTransformer) { */ def caseHashCodeBody(implicit ctx: Context): Tree = { val acc = ctx.newSymbol(ctx.owner, "acc".toTermName, Mutable | Synthetic, defn.IntType, coord = ctx.owner.pos) - val accDef = ValDef(acc, Literal(Constant(0xcafebabe))) + val accDef = ValDef(acc, Literal(Constant(clazz.fullName.toString.hashCode))) val mixes = for (accessor <- accessors.toList) yield Assign(ref(acc), ref(defn.staticsMethod("mix")).appliedTo(ref(acc), hashImpl(accessor))) val finish = ref(defn.staticsMethod("finalizeHash")).appliedTo(ref(acc), Literal(Constant(accessors.size))) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 4077d8d65..94506f318 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -606,11 +606,11 @@ class Namer { typer: Typer => } } - // If a top-level object has no companion class in the current run, we - // enter a dummy companion class symbol (`denot.isAbsent` returns true) in - // scope. This ensures that we never use a companion from a previous run - // or from the classpath. See tests/pos/false-companion for an - // example where this matters. + // If a top-level object or class has no companion in the current run, we + // enter a dummy companion (`denot.isAbsent` returns true) in scope. This + // ensures that we never use a companion from a previous run or from the + // classpath. See tests/pos/false-companion for an example where this + // matters. if (ctx.owner.is(PackageClass)) { for (cdef @ TypeDef(moduleName, _) <- moduleDef.values) { val moduleSym = ctx.effectiveScope.lookup(moduleName.encode) @@ -623,6 +623,17 @@ class Namer { typer: Typer => } } } + for (cdef @ TypeDef(className, _) <- classDef.values) { + val classSym = ctx.effectiveScope.lookup(className.encode) + if (classSym.isDefinedInCurrentRun) { + val moduleName = className.toTermName + val moduleSym = ctx.effectiveScope.lookup(moduleName.encode) + if (!moduleSym.isDefinedInCurrentRun) { + val absentModuleSymbol = ctx.newModuleSymbol(ctx.owner, moduleName, EmptyFlags, EmptyFlags, (_, _) => NoType) + enterSymbol(absentModuleSymbol) + } + } + } } } diff --git a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala index b55aee719..6f7df13a6 100644 --- a/compiler/src/dotty/tools/dotc/util/DiffUtil.scala +++ b/compiler/src/dotty/tools/dotc/util/DiffUtil.scala @@ -58,8 +58,25 @@ object DiffUtil { (fnd, exp, totalChange.toDouble / (expected.length + found.length)) } - def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { + def mkColoredLineDiff(expected: String, actual: String): String = { + val tokens = splitTokens(expected, Nil).toArray + val lastTokens = splitTokens(actual, Nil).toArray + + val diff = hirschberg(lastTokens, tokens) + " |SOF\n" + diff.collect { + case Unmodified(str) => + " |" + str + case Inserted(str) => + ADDITION_COLOR + "e |" + str + ANSI_DEFAULT + case Modified(old, str) => + DELETION_COLOR + "a |" + old + "\ne |" + ADDITION_COLOR + str + ANSI_DEFAULT + case Deleted(str) => + DELETION_COLOR + "\na |" + str + ANSI_DEFAULT + }.mkString + "\n |EOF" + } + + def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = { val tokens = splitTokens(code, Nil).toArray val lastTokens = splitTokens(lastCode, Nil).toArray diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 788e30aa3..742b93fae 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -12,7 +12,7 @@ import scala.util.matching.Regex class CompilationTests extends ParallelSummaryReport with ParallelTesting { import CompilationTests._ - def isInteractive: Boolean = !sys.env.contains("DRONE") + def isInteractive: Boolean = ParallelSummaryReport.isInteractive def testFilter: Option[Regex] = sys.props.get("dotty.partest.filter").map(r => new Regex(r)) diff --git a/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java b/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java index 9214e7d25..5608b3656 100644 --- a/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java +++ b/compiler/test/dotty/tools/dotc/ParallelSummaryReport.java @@ -11,7 +11,9 @@ import dotty.tools.dotc.reporting.TestReporter$; * this class */ public class ParallelSummaryReport { - private static TestReporter rep = TestReporter.reporter(-1); + public final static boolean isInteractive = !System.getenv().containsKey("DRONE"); + + private static TestReporter rep = TestReporter.reporter(System.out, -1); private static ArrayDeque<String> failedTests = new ArrayDeque<>(); private static ArrayDeque<String> reproduceInstructions = new ArrayDeque<>(); private static int passed; @@ -34,7 +36,7 @@ public class ParallelSummaryReport { } @BeforeClass public final static void setup() { - rep = TestReporter.reporter(-1); + rep = TestReporter.reporter(System.out, -1); failedTests = new ArrayDeque<>(); reproduceInstructions = new ArrayDeque<>(); } @@ -54,7 +56,8 @@ public class ParallelSummaryReport { .map(x -> " " + x) .forEach(rep::echo); - rep.flushToStdErr(); + // If we're compiling locally, we don't need reproduce instructions + if (isInteractive) rep.flushToStdErr(); rep.echo(""); @@ -62,6 +65,9 @@ public class ParallelSummaryReport { .stream() .forEach(rep::echo); + // If we're on the CI, we want everything + if (!isInteractive) rep.flushToStdErr(); + if (failed > 0) rep.flushToFile(); } } diff --git a/compiler/test/dotty/tools/dotc/ParallelTesting.scala b/compiler/test/dotty/tools/dotc/ParallelTesting.scala index a81eb4d3a..2b20887e3 100644 --- a/compiler/test/dotty/tools/dotc/ParallelTesting.scala +++ b/compiler/test/dotty/tools/dotc/ParallelTesting.scala @@ -51,6 +51,16 @@ trait ParallelTesting { self => def outDir: JFile def flags: Array[String] + + def title: String = self match { + case self: JointCompilationSource => + if (self.files.length > 1) name + else self.files.head.getPath + + case self: SeparateCompilationSource => + self.dir.getPath + } + /** Adds the flags specified in `newFlags0` if they do not already exist */ def withFlags(newFlags0: String*) = { val newFlags = newFlags0.toArray @@ -69,7 +79,11 @@ trait ParallelTesting { self => val maxLen = 80 var lineLen = 0 - sb.append(s"\n\nTest compiled with $errors error(s) and $warnings warning(s), the test can be reproduced by running:") + sb.append( + s"""| + |Test '$title' compiled with $errors error(s) and $warnings warning(s), + |the test can be reproduced by running:""".stripMargin + ) sb.append("\n\n./bin/dotc ") flags.foreach { arg => if (lineLen > maxLen) { @@ -160,6 +174,8 @@ trait ParallelTesting { self => * according to the implementing class "neg", "run" or "pos". */ private abstract class Test(testSources: List[TestSource], times: Int, threadLimit: Option[Int], suppressAllOutput: Boolean) { + protected final val realStdout = System.out + protected final val realStderr = System.err /** Actual compilation run logic, the test behaviour is defined here */ protected def compilationRunnable(testSource: TestSource): Runnable @@ -178,10 +194,10 @@ trait ParallelTesting { self => val sourceCount = filteredSources.length private[this] var _errorCount = 0 - def errorCount: Int = synchronized { _errorCount } + def errorCount: Int = _errorCount private[this] var _testSourcesCompiled = 0 - private def testSourcesCompiled : Int = synchronized { _testSourcesCompiled } + private def testSourcesCompiled: Int = _testSourcesCompiled /** Complete the current compilation with the amount of errors encountered */ protected final def registerCompilation(errors: Int) = synchronized { @@ -214,7 +230,7 @@ trait ParallelTesting { self => /** Prints to `System.err` if we're not suppressing all output */ protected def echo(msg: String): Unit = - if (!suppressAllOutput) System.err.println(msg) + if (!suppressAllOutput) realStderr.println(msg) /** A single `Runnable` that prints a progress bar for the curent `Test` */ private def createProgressMonitor: Runnable = new Runnable { @@ -224,17 +240,19 @@ trait ParallelTesting { self => while (tCompiled < sourceCount) { val timestamp = (System.currentTimeMillis - start) / 1000 val progress = (tCompiled.toDouble / sourceCount * 40).toInt - print( + + realStdout.print( "[" + ("=" * (math.max(progress - 1, 0))) + (if (progress > 0) ">" else "") + (" " * (39 - progress)) + s"] compiling ($tCompiled/$sourceCount, ${timestamp}s)\r" ) + Thread.sleep(100) tCompiled = testSourcesCompiled } // println, otherwise no newline and cursor at start of line - println( + realStdout.println( s"[=======================================] compiled ($sourceCount/$sourceCount, " + s"${(System.currentTimeMillis - start) / 1000}s) " ) @@ -245,7 +263,10 @@ trait ParallelTesting { self => * if it did, the test should automatically fail. */ protected def tryCompile(testSource: TestSource)(op: => Unit): Unit = - try op catch { + try { + if (!isInteractive) realStdout.println(s"Testing ${testSource.title}") + op + } catch { case NonFatal(e) => { // if an exception is thrown during compilation, the complete test // run should fail @@ -295,8 +316,10 @@ trait ParallelTesting { self => Runtime.getRuntime.exec(fullArgs).waitFor() == 0 } else true - val reporter = TestReporter.parallelReporter(this, logLevel = - if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) + val reporter = + TestReporter.reporter(realStdout, logLevel = + if (suppressErrors || suppressAllOutput) ERROR + 1 else ERROR) + val driver = if (times == 1) new Driver { def newCompiler(implicit ctx: Context) = new Compiler } else new Driver { @@ -339,8 +362,12 @@ trait ParallelTesting { self => } pool.shutdown() - if (!pool.awaitTermination(10, TimeUnit.MINUTES)) + if (!pool.awaitTermination(20, TimeUnit.MINUTES)) { + pool.shutdownNow() + System.setOut(realStdout) + System.setErr(realStderr) throw new TimeoutException("Compiling targets timed out") + } if (didFail) { reportFailed() @@ -403,8 +430,6 @@ trait ParallelTesting { self => import java.net.{ URL, URLClassLoader } val printStream = new ByteArrayOutputStream - val oldOut = System.out - val oldErr = System.err try { // Do classloading magic and running here: @@ -412,7 +437,7 @@ trait ParallelTesting { self => val cls = ucl.loadClass("Test") val meth = cls.getMethod("main", classOf[Array[String]]) - self.synchronized { + synchronized { try { val ps = new PrintStream(printStream) System.setOut(ps) @@ -422,9 +447,13 @@ trait ParallelTesting { self => meth.invoke(null, Array("jvm")) // partest passes at least "jvm" as an arg } } - } finally { - System.setOut(oldOut) - System.setErr(oldErr) + System.setOut(realStdout) + System.setErr(realStderr) + } catch { + case t: Throwable => + System.setOut(realStdout) + System.setErr(realStderr) + throw t } } } @@ -447,6 +476,7 @@ trait ParallelTesting { self => private def verifyOutput(checkFile: JFile, dir: JFile, testSource: TestSource, warnings: Int) = { val outputLines = runMain(dir, testSource) val checkLines = Source.fromFile(checkFile).getLines.toArray + val sourceTitle = testSource.title def linesMatch = outputLines @@ -456,9 +486,13 @@ trait ParallelTesting { self => if (outputLines.length != checkLines.length || !linesMatch) { // Print diff to files and summary: val diff = outputLines.zip(checkLines).map { case (act, exp) => - DiffUtil.mkColoredCodeDiff(exp, act, true) + DiffUtil.mkColoredLineDiff(exp, act) }.mkString("\n") - val msg = s"\nOutput from run test '$checkFile' did not match expected, output:\n$diff\n" + + val msg = + s"""|Output from '$sourceTitle' did not match check file. + |Diff ('e' is expected, 'a' is actual): + |""".stripMargin + diff + "\n" echo(msg) addFailureInstruction(msg) diff --git a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala index 521cf9576..5641240a7 100644 --- a/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala +++ b/compiler/test/dotty/tools/dotc/reporting/TestReporter.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package reporting -import java.io.{ PrintWriter, File => JFile, FileOutputStream } +import java.io.{ PrintStream, PrintWriter, File => JFile, FileOutputStream } import java.text.SimpleDateFormat import java.util.Date @@ -25,10 +25,16 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M protected final val _messageBuf = mutable.ArrayBuffer.empty[String] final def flushToFile(): Unit = - _messageBuf.iterator.foreach(filePrintln) + _messageBuf + .iterator + .map(_.replaceAll("\u001b\\[.*?m", "")) + .foreach(filePrintln) final def flushToStdErr(): Unit = - _messageBuf.iterator.foreach(System.err.println) + _messageBuf + .iterator + .map(_.replaceAll("\u001b\\[.*?m", "")) + .foreach(System.err.println) final def inlineInfo(pos: SourcePosition): String = if (pos.exists) { @@ -75,10 +81,11 @@ extends Reporter with UniqueMessagePositions with HideNonSensicalMessages with M } object TestReporter { - private[this] val logWriter = { + private[this] lazy val logWriter = { val df = new SimpleDateFormat("yyyy-MM-dd-HH:mm") val timestamp = df.format(new Date) - new PrintWriter(new FileOutputStream(new JFile(s"../tests-$timestamp.log"), true)) + new JFile("../testlogs").mkdirs() + new PrintWriter(new FileOutputStream(new JFile(s"../testlogs/tests-$timestamp.log"), true)) } def writeToLog(str: String) = { @@ -86,38 +93,25 @@ object TestReporter { logWriter.flush() } - def parallelReporter(lock: AnyRef, logLevel: Int): TestReporter = new TestReporter( - new PrintWriter(Console.err, true), - str => lock.synchronized { - logWriter.println(str) - logWriter.flush() - }, - logLevel - ) - - def reporter(logLevel: Int): TestReporter = new TestReporter( - new PrintWriter(Console.err, true), - writeToLog, - logLevel - ) - - def simplifiedReporter(writer: PrintWriter): TestReporter = new TestReporter( - writer, - writeToLog, - WARNING - ) { - /** Prints the message with the given position indication in a simplified manner */ - override def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = { - val msg = s"${m.pos.line + 1}: " + m.contained.kind + extra - val extraInfo = inlineInfo(m.pos) - - writer.println(msg) - _messageBuf.append(msg) - - if (extraInfo.nonEmpty) { - writer.println(extraInfo) - _messageBuf.append(extraInfo) + def reporter(ps: PrintStream, logLevel: Int): TestReporter = + new TestReporter(new PrintWriter(ps, true), writeToLog, logLevel) + + def simplifiedReporter(writer: PrintWriter): TestReporter = { + val rep = new TestReporter(writer, writeToLog, WARNING) { + /** Prints the message with the given position indication in a simplified manner */ + override def printMessageAndPos(m: MessageContainer, extra: String)(implicit ctx: Context): Unit = { + val msg = s"${m.pos.line + 1}: " + m.contained.kind + extra + val extraInfo = inlineInfo(m.pos) + + writer.println(msg) + _messageBuf.append(msg) + + if (extraInfo.nonEmpty) { + writer.println(extraInfo) + _messageBuf.append(extraInfo) + } } } + rep } } diff --git a/doc-tool/resources/css/dottydoc.css b/doc-tool/resources/css/dottydoc.css index 0b833830c..a89e23375 100644 --- a/doc-tool/resources/css/dottydoc.css +++ b/doc-tool/resources/css/dottydoc.css @@ -334,3 +334,23 @@ blockquote { color: #777; border-left: 0.25em solid #ddd; } + +aside { + padding: 15px; + margin: 10px 0; +} + +aside.warning { + border-left: 3px solid #d62c2c; + background-color: #ffe4e4; +} + +aside.notice { + border-left: 3px solid #4c97e4; + background-color: #e4ebff; +} + +aside.success { + border-left: 3px solid #36bf1d; + background-color: #ebfddd; +} diff --git a/docs/docs/contributing/testing.md b/docs/docs/contributing/testing.md new file mode 100644 index 000000000..07aab1918 --- /dev/null +++ b/docs/docs/contributing/testing.md @@ -0,0 +1,89 @@ +--- +layout: doc-page +title: Testing in Dotty +--- + +<aside class="warning"> +This page should be updated as soon as scala-partest is removed +</aside> + +Running all tests in Dotty is as simple as: + +```bash +$ sbt test +``` + +There are currently several forms of tests in Dotty. These can be split into +two categories: + +## Unit tests +These tests can be found in `<sub-project>/test` and are used to check +functionality of specific parts of the codebase in isolation e.g: parsing, +scanning and message errors. + +Running a single unit test class from sbt is as simple as: + +```bash +> testOnly absolute.path.to.TestClass +``` + +You can further restrict the executed tests to a subset of `TestClass` methods +as follows: + +```bash +> testOnly absolute.path.to.TestClass -- *methodName +``` + +## Integration tests +These tests are Scala source files expected to compile with Dotty (pos tests), +along with their expected output (run tests) or errors (neg tests). + +All of these tests are contained in the `./tests/*` directories. + +## scala-partest +Historically these tests needed a structure which was generated by running the +unit tests, and then that structure was in turn used by +[scala-partest](http://github.com/scala/scala-partest) to run compilation tests +in parallel. + +This test suite can still be used (and is currently a part of the CI to check +that it has the same outcome as the new test suite). It is invoked from sbt by +running one of the following commands: + +```bash +> partest-only-no-bootstrap +> partest-only +> partest +``` + +- `partest-only-no-bootstrap` will only run the integration tests +- `partest-only` will bootstrap the compiler and run the integration tests +- `partest` will bootstrap the compiler, run the unit tests and then the + integration tests + +## dotty parallel test suite +The new test suite will soon become the standard integration test runner. It +has several advantages over the old implementation: + +- integrates with JUnit, without the need for setup +- reuses the same VM for compilation +- allows filtering of tests +- runs much faster (almost 2x) + +Currently to run these tests you need to invoke from sbt: + +```bash +> testOnly dotty.tools.dotc.CompilationTests +``` + +This might be aliased in the future. It is also possible to run tests filtered +by using: + +```bash +> filterTest .*i2147.scala +``` + +This will run both the test `./tests/pos/i2147.scala` and +`./tests/partest-test/i2147.scala` since both of these match the given regular +expression. This also means that you could run `filterTest .*` to run all +integration tests. diff --git a/docs/docs/contributing/workflow.md b/docs/docs/contributing/workflow.md index 6e7f5b9a0..3c654e8f6 100644 --- a/docs/docs/contributing/workflow.md +++ b/docs/docs/contributing/workflow.md @@ -57,11 +57,21 @@ $ sbt To test a specific test tests/x/y.scala (for example tests/pos/t210.scala): ```bash -> partest-only-no-bootstrap --show-diff --verbose tests/partest-generated/x/y.scala +> filterTest .*pos/t210.scala ``` -Currently this will re-run some unit tests and do some preprocessing because of -the way partest has been set up. +The filterTest task takes a regular expression as its argument. For example, +you could run a negative and a positive test with: + +```bash +> filterTest (.*pos/t697.scala)|(.*neg/i2101.scala) +``` + +or if they have the same name, the equivalent can be achieved with: + +```bash +> filterTest .*/i2101.scala +``` ## Inspecting Trees with Type Stealer ## diff --git a/docs/docs/internals/higher-kinded-v2.md b/docs/docs/internals/higher-kinded-v2.md index 4676d3ebd..3c857d4d5 100644 --- a/docs/docs/internals/higher-kinded-v2.md +++ b/docs/docs/internals/higher-kinded-v2.md @@ -3,10 +3,11 @@ layout: doc-page title: "Higher-Kinded Types in Dotty" --- -**This page is out of date and preserved for posterity. Please see [Implementing -Higher-Kinded Types in -Dotty](http://guillaume.martres.me/publications/dotty-hk.pdf) for a more up to -date version** +<aside class="warning"> + This page is out of date and preserved for posterity. Please see + <a href="http://guillaume.martres.me/publications/dotty-hk.pdf"> + Implementing Higher-Kinded Types in Dotty</a> for a more up to date version +</aside> Higher-Kinded Types in Dotty V2 =============================== diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 7ffa1f5b7..4065cff20 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -23,6 +23,8 @@ sidebar: url: docs/contributing/intellij-idea.html - title: Workflow url: docs/contributing/workflow.html + - title: Testing + url: docs/contributing/testing.html - title: Internals subsection: - title: Backend diff --git a/tests/neg/i2151.scala b/tests/neg/i2151.scala new file mode 100644 index 000000000..1ae034c02 --- /dev/null +++ b/tests/neg/i2151.scala @@ -0,0 +1,6 @@ +trait Test { + type Nil = [K] => K + type StrangeCons[H, Tail <: [H, A] => H] = Tail[H, H] + + def list: StrangeCons[Int, Nil] // error +} diff --git a/tests/run/caseClassHash.check b/tests/run/caseClassHash.check index b5a6f08e9..332fd477d 100644 --- a/tests/run/caseClassHash.check +++ b/tests/run/caseClassHash.check @@ -1,9 +1,9 @@ Foo(true,-1,-1,d,-5,-10,500.0,500.0,List(),5.0) Foo(true,-1,-1,d,-5,-10,500.0,500.0,List(),5) -1383698062 -1383698062 +205963949 +205963949 true -## method 1: 1383698062 -## method 2: 1383698062 +## method 1: 205963949 +## method 2: 205963949 Murmur 1: 1383698062 Murmur 2: 1383698062 diff --git a/tests/run/t4400.scala b/tests/run/t4400.scala new file mode 100644 index 000000000..04ae6722e --- /dev/null +++ b/tests/run/t4400.scala @@ -0,0 +1,35 @@ +final class Outer { + + + + sealed trait Inner + + + + final case class Inner1(foo: Int) extends Inner + + + + val inner: Outer#Inner = Inner1(0) + + + + def bar = inner match { + + case Inner1(i) => i + + } + +} + + + +object Test { + + def main(args: Array[String]): Unit = { + val s = (new Outer).bar + assert(s == 0) + } + +} + |