aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--compiler/src/dotty/tools/dotc/ast/untpd.scala38
-rw-r--r--compiler/src/dotty/tools/dotc/core/Decorators.scala2
-rw-r--r--compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala2
-rw-r--r--compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala112
-rw-r--r--compiler/src/dotty/tools/dotc/transform/SyntheticMethods.scala5
-rw-r--r--compiler/src/dotty/tools/dotc/typer/Namer.scala21
-rw-r--r--compiler/src/dotty/tools/dotc/util/DiffUtil.scala19
-rw-r--r--compiler/test/dotty/tools/dotc/CompilationTests.scala2
-rw-r--r--compiler/test/dotty/tools/dotc/ParallelSummaryReport.java12
-rw-r--r--compiler/test/dotty/tools/dotc/ParallelTesting.scala70
-rw-r--r--compiler/test/dotty/tools/dotc/reporting/TestReporter.scala66
-rw-r--r--doc-tool/resources/css/dottydoc.css20
-rw-r--r--docs/docs/contributing/testing.md89
-rw-r--r--docs/docs/contributing/workflow.md16
-rw-r--r--docs/docs/internals/higher-kinded-v2.md9
-rw-r--r--docs/sidebar.yml2
-rw-r--r--tests/neg/i2151.scala6
-rw-r--r--tests/run/caseClassHash.check8
-rw-r--r--tests/run/t4400.scala35
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)
+ }
+
+}
+