From 3c8545b8293b903ffe9178060ad1b630f19762f1 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 1 May 2018 23:38:49 +0200 Subject: [PATCH 01/20] Prevent position errors on Ident(nme.ERROR) ``` Exception in thread "main" java.lang.AssertionError: assertion failed: position error: position not set for Ident() # 24 at dotty.DottyPredef$.assertFail(DottyPredef.scala:36) at dotty.tools.dotc.ast.Positioned.check$1(Positioned.scala:178) at dotty.tools.dotc.ast.Positioned.check$5$$anonfun$4(Positioned.scala:203) ``` --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 12 ++++++++++-- tests/neg/parser-stability-23.scala | 3 +++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tests/neg/parser-stability-23.scala diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index edaf6850d8cf..5a8cfd3a4bea 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -518,8 +518,16 @@ object Parsers { } /** Accept identifier and return Ident with its name as a term name. */ - def termIdent(): Ident = atPos(in.offset) { - makeIdent(in.token, ident()) + def termIdent(): Ident = { + val start = in.offset + val id = makeIdent(in.token, ident()) + if (id.name != nme.ERROR) { + atPos(start)(id) + } else { + // error identifiers don't consume any characters, so atPos(start)(id) wouldn't set a position. + // Some testcases would then fail in Positioned.checkPos. Set a position anyway! + atPos(start, start, in.lastOffset)(id) + } } /** Accept identifier and return Ident with its name as a type name. */ diff --git a/tests/neg/parser-stability-23.scala b/tests/neg/parser-stability-23.scala new file mode 100644 index 000000000000..b8db4d937f25 --- /dev/null +++ b/tests/neg/parser-stability-23.scala @@ -0,0 +1,3 @@ +object i0 { + import Ordering.{ implicitly => } (true: Boolean) match { case _: i1 => true } // error // error +} From ad04880cd3f1af0e7e147e7fc31499c26ee7207f Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 May 2018 18:10:31 +0200 Subject: [PATCH 02/20] Revert parts of "Prevent position errors on Ident(nme.ERROR)" This reverts commit 0f7b1b0bba2f644141796be5217c3019ee643b2f but keeps the testcase. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5a8cfd3a4bea..edaf6850d8cf 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -518,16 +518,8 @@ object Parsers { } /** Accept identifier and return Ident with its name as a term name. */ - def termIdent(): Ident = { - val start = in.offset - val id = makeIdent(in.token, ident()) - if (id.name != nme.ERROR) { - atPos(start)(id) - } else { - // error identifiers don't consume any characters, so atPos(start)(id) wouldn't set a position. - // Some testcases would then fail in Positioned.checkPos. Set a position anyway! - atPos(start, start, in.lastOffset)(id) - } + def termIdent(): Ident = atPos(in.offset) { + makeIdent(in.token, ident()) } /** Accept identifier and return Ident with its name as a type name. */ From 83610bb6fe4bd12e15b1462dd819bdb6bb079c4c Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 May 2018 18:06:37 +0200 Subject: [PATCH 03/20] Alternative fix --- .../src/dotty/tools/dotc/parsing/Parsers.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index edaf6850d8cf..4afea33c5798 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1975,11 +1975,24 @@ object Parsers { /** ImportSelector ::= id [`=>' id | `=>' `_'] */ def importSelector(): Tree = { + val start = in.offset val from = termIdentOrWildcard() if (from.name != nme.WILDCARD && in.token == ARROW) atPos(startOffset(from), in.skipToken()) { - Thicket(from, termIdentOrWildcard()) + val start2 = in.offset + val to = termIdentOrWildcard() + val toWithPos = + if (to.name == nme.ERROR) + // error identifiers don't consume any characters, so atPos(start)(id) wouldn't set a position. + // Some testcases would then fail in Positioned.checkPos. Set a position anyway! + atPos(start2, start2, in.lastOffset)(to) + else + to + Thicket(from, toWithPos) } + else if (from.name == nme.ERROR) { + atPos(start, start, in.lastOffset)(from) + } else from } From 78afe8a3e81eb45da95c8164945c5d26e8237f86 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 2 May 2018 18:13:47 +0200 Subject: [PATCH 04/20] Simplify fix --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 4afea33c5798..18e10817477f 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1975,24 +1975,20 @@ object Parsers { /** ImportSelector ::= id [`=>' id | `=>' `_'] */ def importSelector(): Tree = { - val start = in.offset val from = termIdentOrWildcard() if (from.name != nme.WILDCARD && in.token == ARROW) atPos(startOffset(from), in.skipToken()) { - val start2 = in.offset + val start = in.offset val to = termIdentOrWildcard() val toWithPos = if (to.name == nme.ERROR) // error identifiers don't consume any characters, so atPos(start)(id) wouldn't set a position. // Some testcases would then fail in Positioned.checkPos. Set a position anyway! - atPos(start2, start2, in.lastOffset)(to) + atPos(start, start, in.lastOffset)(to) else to Thicket(from, toWithPos) } - else if (from.name == nme.ERROR) { - atPos(start, start, in.lastOffset)(from) - } else from } From 3418413aa3f65e2bc803f9ee8a7d49a1a9642226 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Apr 2018 19:28:56 +0200 Subject: [PATCH 05/20] Detect and flag deep findMember recursions Deep findMember recursions can happen as a recult of illegal cyclic structures. I don't think there's a complete way to avoid these structures before the cyclic search can happen. So we now detect it by imposing a recursion limit (configurable, by default 100). --- compiler/src/dotty/tools/dotc/config/Config.scala | 5 ----- .../src/dotty/tools/dotc/config/ScalaSettings.scala | 3 ++- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 6 ------ compiler/src/dotty/tools/dotc/core/Types.scala | 12 +++++++----- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 8abbb81bae85..ea96d9dcd5d9 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -184,11 +184,6 @@ object Config { */ final val LogPendingFindMemberThreshold = 9 - /** Maximal number of outstanding recursive calls to findMember before backing out - * when findMemberLimit is set. - */ - final val PendingFindMemberLimit = LogPendingFindMemberThreshold * 4 - /** When in IDE, turn StaleSymbol errors into warnings instead of crashing */ final val ignoreStaleInIDE = true } diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8b39883d51c0..096dcf5c296c 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -63,7 +63,7 @@ class ScalaSettings extends Settings.SettingGroup { val Xhelp = BooleanSetting("-X", "Print a synopsis of advanced options.") val XnoForwarders = BooleanSetting("-Xno-forwarders", "Do not generate static forwarders in mirror classes.") val XminImplicitSearchDepth = IntSetting("-Xmin-implicit-search-depth", "Set number of levels of implicit searches undertaken before checking for divergence.", 5) - val xmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32) + val XmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32) val XmaxClassfileName = IntSetting("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, 72 to 255) val Xmigration = VersionSetting("-Xmigration", "Warn about constructs whose behavior may have changed since version.") val Xprint = PhasesSetting("-Xprint", "Print out program after") @@ -74,6 +74,7 @@ class ScalaSettings extends Settings.SettingGroup { val XmainClass = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d )", "") val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.") val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximal number of columns per line for REPL output", 390) + val XmaxMemberRecursions = IntSetting("-Xmax-member-recursions", "Maximal number of recursive calls to find-member", 100) val XfatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") val XverifySignatures = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.") diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 1ff312dae475..05fd66b54282 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -326,10 +326,4 @@ trait TypeOps { this: Context => // TODO: Make standalone object. object TypeOps { @sharable var track = false // !!!DEBUG - - /** When a property with this key is set in a context, it limits the number - * of recursive member searches. If the limit is reached, findMember returns - * NoDenotation. - */ - val findMemberLimit = new Property.Key[Unit] } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 9e08d013b1b8..3e08f888d5aa 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -652,17 +652,15 @@ object Types { val recCount = ctx.findMemberCount if (recCount >= Config.LogPendingFindMemberThreshold) { - if (ctx.property(TypeOps.findMemberLimit).isDefined && - ctx.findMemberCount > Config.PendingFindMemberLimit) - return NoDenotation + if (ctx.findMemberCount > ctx.settings.XmaxMemberRecursions.value) + throw new CyclicFindMember(pre, name) ctx.pendingMemberSearches = name :: ctx.pendingMemberSearches } ctx.findMemberCount = recCount + 1 - //assert(ctx.findMemberCount < 20) try go(this) catch { case ex: Throwable => - core.println(i"findMember exception for $this member $name, pre = $pre") + core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") throw ex // DEBUG } finally { @@ -4564,6 +4562,10 @@ object Types { if (ctx.debug) printStackTrace() } + class CyclicFindMember(pre: Type, name: Name)(implicit ctx: Context) extends TypeError( + i"""member search with prefix $pre too deep. + |searches, from inner to outer: .${ctx.pendingMemberSearches}% .%""") + private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { case pre: ThisType if pre.cls.givenSelfType.exists => i"\nor the self type of $pre might not contain all transitive dependencies" diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 49a507e0994c..ae4eb968d21d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -257,13 +257,13 @@ object Inliner { * and body that replace it. */ def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - if (enclosingInlineds.length < ctx.settings.xmaxInlines.value) { + if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors if (ctx.reporter.hasErrors) tree else new Inliner(tree, body).inlined(pt) } else errorTree( tree, - i"""|Maximal number of successive inlines (${ctx.settings.xmaxInlines.value}) exceeded, + i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, |Maybe this is caused by a recursive inline method? |You can use -Xmax:inlines to change the limit.""" ) From 29d39a17e9cf248952dacfe4843cae94363e7a58 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 13:24:57 +0200 Subject: [PATCH 06/20] Add test for cyclic references involving members of inherited and intersected types Test every template and every type intersection for cycles in its members. The tests are done as early possible without causing spurious cycles, which for intersections means PostTyper, unfortunately. Still missing: Do the test also for members reachable in common value definitions. --- .../src/dotty/tools/dotc/core/Types.scala | 9 +++- .../tools/dotc/transform/PostTyper.scala | 7 ++- .../src/dotty/tools/dotc/typer/Checking.scala | 35 ++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 + tests/run/returning.scala | 53 +++++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/run/returning.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 3e08f888d5aa..7f202a36d2a5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4562,8 +4562,15 @@ object Types { if (ctx.debug) printStackTrace() } + def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match { + case pre: TermRef => i"${pre.termSymbol.name}." + case pre: TypeRef => i"${pre.typeSymbol.name}#" + case pre: TypeProxy => showPrefixSafely(pre.underlying) + case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." + } + class CyclicFindMember(pre: Type, name: Name)(implicit ctx: Context) extends TypeError( - i"""member search with prefix $pre too deep. + i"""member search for ${showPrefixSafely(pre)}$name too deep. |searches, from inner to outer: .${ctx.pendingMemberSearches}% .%""") private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3416da942a88..2945e4ad92cf 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -6,7 +6,7 @@ import scala.collection.mutable import core._ import typer.Checking import Types._, Contexts._, Names._, Flags._, DenotTransformers._ -import SymDenotations._, StdNames._, Annotations._, Trees._ +import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ import reporting.diagnostic.messages.{ImportRenamedTwice, NotAMember, SuperCallsNotAllowedInline} @@ -280,6 +280,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case tpe => tpe } ) + case tree: AndTypeTree => + // Ideally, this should be done by Typer, but we run into cyclic references + // when trying to typecheck self types which are intersections. + Checking.checkNonCyclicInherited(tree.tpe, tree.left.tpe :: tree.right.tpe :: Nil, EmptyScope, tree.pos) + super.transform(tree) case Import(expr, selectors) => val exprTpe = expr.tpe val seen = mutable.Set.empty[Name] diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 26ecee23b338..fc9607aefcc7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -13,6 +13,7 @@ import Symbols._ import Trees._ import TreeInfo._ import ProtoTypes._ +import Scopes._ import CheckRealizable._ import ErrorReporting.errorTree @@ -320,6 +321,36 @@ object Checking { checkTree((), refinement) } + /** Check type members inherited from different `parents` of `joint` type for cycles, + * unless a type with the same name aleadry appears in `decls`. + * @return true iff no cycles were detected + */ + def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = { + def qualifies(sym: Symbol) = sym.name.isTypeName && !sym.is(Private) + val abstractTypeNames = + for (parent <- parents; mbr <- parent.abstractTypeMembers if qualifies(mbr.symbol)) + yield mbr.name.asTypeName + + for (name <- abstractTypeNames) + try { + val mbr = joint.member(name) + mbr.info match { + case bounds: TypeBounds => + val res = checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError + if (res) + println(i"cyclic ${mbr.symbol}, $bounds -> $res") + res + case _ => + false + } + } + catch { + case ex: CyclicFindMember => + ctx.error(em"cyclic reference involving type $name", pos) + true + } + } + /** Check that symbol's definition is well-formed. */ def checkWellFormed(sym: Symbol)(implicit ctx: Context): Unit = { def fail(msg: Message) = ctx.error(msg, sym.pos) @@ -519,6 +550,9 @@ trait Checking { def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = Checking.checkNonCyclic(sym, info, reportErrors) + def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = + Checking.checkNonCyclicInherited(joint, parents, decls, pos) + /** Check that Java statics and packages can only be used in selections. */ def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = { @@ -905,6 +939,7 @@ trait ReChecking extends Checking { trait NoChecking extends ReChecking { import tpd._ override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info + override def checkNonCyclicInherited(joint: Type, parents: List[Type], decls: Scope, pos: Position)(implicit ctx: Context): Unit = () override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkClassType(tp: Type, pos: Position, traitReq: Boolean, stablePrefixReq: Boolean)(implicit ctx: Context): Type = tp diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 24ed19112fb6..733b34d181e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1544,6 +1544,8 @@ class Typer extends Namer cls, isRequired, cdef.pos) } + checkNonCyclicInherited(cls.thisType, cls.classParents, cls.info.decls, cdef.pos) + // check value class constraints checkDerivedValueClass(cls, body1) diff --git a/tests/run/returning.scala b/tests/run/returning.scala new file mode 100644 index 000000000000..f0af05e2ba14 --- /dev/null +++ b/tests/run/returning.scala @@ -0,0 +1,53 @@ +package scala.util.control { + +object NonLocalReturns { + + class ReturnThrowable[T] extends ControlThrowable { + private var myResult: T = _ + def throwReturn(result: T): Nothing = { + myResult = result + throw this + } + def result: T = myResult + } + + def throwReturn[T](result: T)(implicit returner: ReturnThrowable[T]): Nothing = + returner.throwReturn(result) + + def returning[T](op: implicit ReturnThrowable[T] => T): T = { + val returner = new ReturnThrowable[T] + try op(returner) + catch { + case ex: ReturnThrowable[_] => + if (ex `eq` returner) ex.result.asInstanceOf[T] else throw ex + } + } +} +} + +object Test extends App { + + import scala.util.control.NonLocalReturns._ + import scala.collection.mutable.ListBuffer + + def has(xs: List[Int], elem: Int) = + returning { + for (x <- xs) + if (x == elem) throwReturn(true) + false + } + + def takeUntil(xs: List[Int], elem: Int) = + returning { + var buf = new ListBuffer[Int] + for (x <- xs) + yield { + if (x == elem) throwReturn(buf.toList) + buf += x + x + } + } + + assert(has(List(1, 2, 3), 2)) + assert(takeUntil(List(1, 2, 3), 3) == List(1, 2)) +} \ No newline at end of file From fb8ad7da4a9644066040245caaf0d6dde2f0b48c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 13:36:00 +0200 Subject: [PATCH 07/20] Add test --- tests/neg/i4368.scala | 150 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 tests/neg/i4368.scala diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala new file mode 100644 index 000000000000..34a245c3df67 --- /dev/null +++ b/tests/neg/i4368.scala @@ -0,0 +1,150 @@ +object Test1 { + trait X { + type A = B + type B + } + trait Y { + type A + type B = A + } + trait Z extends X with Y // error: cyclic +} + +object Test2 { + trait W { + type A + type B + } + trait X { z: W => + type A = z.B + type B + } + trait Y { z: W => + type A + type B = z.A + } + trait Z extends X with Y // error: cyclic +} + +object Test3 { + trait W { + type A + type B + } + trait X { z: W => + type A = z.B + type B + } + trait Y { z: W => + type A + type B = z.A + } + + object App { + type Z = X with Y + val z: Z = z + val a: z.A = a // error: too deep + } +} + +object Test4 { + trait X[F[_]] { + protected type A = F[B] + protected type B + } + trait Y[F[_]] { + protected type A + protected type B = F[A] + } + + trait Fix[F[_]] extends X[F] with Y[F] { + type Result = A // error: too deep + } +} + +object Test5 { + trait X { + type A = B + type B + } + trait Y { + type A + type B = A + } + + object App { + type Z = X & Y + val z: Z = z + val a: z.A = a // error: too deep + } +} + +object Test6 { + trait W { type T <: W; val t: T } + trait X { + type A = b.T + val a : A = b.t + type B <: W + val b : B + } + trait Y { + type A <: W + val a : A + type B = a.T + val b = a.t + } + trait Z extends X with Y // error: cyclic +} + +object Test7 { + class Fix[F[_]] { + class Foo { type R >: F[T] <: F[T] } + type T = F[Foo#R] + } + + object App { + type Nat = Fix[Option]#T // error: too deep + } +} +/* +object Test8 { + + class A { + type T = B#U // error: cyclic + } + + class B { + type U = A#T + } +} +*/ +object Test9 { + trait W { + type A + } + trait X extends W { + type A = B + type B + } + trait Y extends W { + type A + type B = A + } + + trait Foo[X <: W, Y <: W] { + type Z = X & Y + val z: Z + val a: z.A + } + + trait Boo { + val f: Foo[X, Y] + } + + trait Baz extends Boo { + val a = f.a // error: member search too deep + // this should be a cyclic error, but it goes undetected + // scalac reports a volatility error, but the dotty equivalent (checkRealizable) + // is checked too late. + } +} \ No newline at end of file From 3899e42040c899201279b261e3096d02f100ca1a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 13:37:39 +0200 Subject: [PATCH 08/20] Detect more cyclic references Detect more cyclic references be generalizing the condition which prefixes are interesting. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- tests/neg/i4368.scala | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index fc9607aefcc7..e637f3228021 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -221,7 +221,11 @@ object Checking { // global symbols when doing the cyclicity check. def isInteresting(prefix: Type): Boolean = prefix.stripTypeVar match { case NoPrefix => true - case prefix: ThisType => sym.owner.isClass && prefix.cls.isContainedIn(sym.owner) + case prefix: ThisType => + sym.owner.isClass && ( + prefix.cls.isContainedIn(sym.owner) // sym reachable through outer references + || sym.owner.isContainedIn(prefix.cls) // sym reachable through member references + ) case prefix: NamedType => (!sym.is(Private) && prefix.derivesFrom(sym.owner)) || (!prefix.symbol.isStaticOwner && isInteresting(prefix.prefix)) @@ -231,6 +235,7 @@ object Checking { case _: RefinedOrRecType | _: AppliedType => true case _ => false } + if (isInteresting(pre)) { val pre1 = this(pre, false, false) if (locked.contains(tp) || tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter]) diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala index 34a245c3df67..ce1bc25fdeca 100644 --- a/tests/neg/i4368.scala +++ b/tests/neg/i4368.scala @@ -98,12 +98,12 @@ object Test6 { object Test7 { class Fix[F[_]] { - class Foo { type R >: F[T] <: F[T] } + class Foo { type R >: F[T] <: F[T] } // error: cyclic type T = F[Foo#R] } object App { - type Nat = Fix[Option]#T // error: too deep + type Nat = Fix[Option]#T } } /* From 7f49faedb903500eb1c453cd83aa5a8c84e46cfe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 16:53:20 +0200 Subject: [PATCH 09/20] Rearrange TypeError exceptions - Move them all to TypeErrors.scala - Systematically use `toMessage` for reporting - Catch stack overflows instead of counting recursions --- .../tools/dotc/config/ScalaSettings.scala | 1 - .../src/dotty/tools/dotc/core/Types.scala | 61 +------------------ .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 5 +- .../dotc/transform/OverridingPairs.scala | 5 +- .../src/dotty/tools/dotc/typer/Checking.scala | 11 ++-- .../tools/dotc/typer/ErrorReporting.scala | 20 ------ .../dotty/tools/dotc/typer/RefChecks.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 3 +- tests/neg/i4368.scala | 2 +- 9 files changed, 14 insertions(+), 96 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 096dcf5c296c..d06794fcdf46 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -74,7 +74,6 @@ class ScalaSettings extends Settings.SettingGroup { val XmainClass = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d )", "") val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.") val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximal number of columns per line for REPL output", 390) - val XmaxMemberRecursions = IntSetting("-Xmax-member-recursions", "Maximal number of recursive calls to find-member", 100) val XfatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") val XverifySignatures = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7f202a36d2a5..1590a695dd06 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -20,7 +20,6 @@ import util.Positions.{Position, NoPosition} import util.Stats._ import util.{DotClass, SimpleIdentitySet} import reporting.diagnostic.Message -import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ import ast.TreeTypeMap import printing.Texts._ @@ -32,10 +31,9 @@ import Uniques._ import collection.{mutable, Seq} import config.Config import annotation.tailrec -import Flags.FlagSet import language.implicitConversions import scala.util.hashing.{ MurmurHash3 => hashing } -import config.Printers.{core, typr, cyclicErrors} +import config.Printers.{core, typr} import java.lang.ref.WeakReference object Types { @@ -651,17 +649,14 @@ object Types { } val recCount = ctx.findMemberCount - if (recCount >= Config.LogPendingFindMemberThreshold) { - if (ctx.findMemberCount > ctx.settings.XmaxMemberRecursions.value) - throw new CyclicFindMember(pre, name) + if (recCount >= Config.LogPendingFindMemberThreshold) ctx.pendingMemberSearches = name :: ctx.pendingMemberSearches - } ctx.findMemberCount = recCount + 1 try go(this) catch { case ex: Throwable => core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") - throw ex // DEBUG + throw new RecursionOverflow("find-member", pre, name, ex) } finally { if (recCount >= Config.LogPendingFindMemberThreshold) @@ -4548,56 +4543,6 @@ object Types { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true } - // ----- Exceptions ------------------------------------------------------------- - - class TypeError(msg: String) extends Exception(msg) - - class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) - extends TypeError( - s"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}") - - class MissingType(pre: Type, name: Name)(implicit ctx: Context) extends TypeError( - i"""cannot resolve reference to type $pre.$name - |the classfile defining the type might be missing from the classpath${otherReason(pre)}""") { - if (ctx.debug) printStackTrace() - } - - def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match { - case pre: TermRef => i"${pre.termSymbol.name}." - case pre: TypeRef => i"${pre.typeSymbol.name}#" - case pre: TypeProxy => showPrefixSafely(pre.underlying) - case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." - } - - class CyclicFindMember(pre: Type, name: Name)(implicit ctx: Context) extends TypeError( - i"""member search for ${showPrefixSafely(pre)}$name too deep. - |searches, from inner to outer: .${ctx.pendingMemberSearches}% .%""") - - private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { - case pre: ThisType if pre.cls.givenSelfType.exists => - i"\nor the self type of $pre might not contain all transitive dependencies" - case _ => "" - } - - class CyclicReference private (val denot: SymDenotation) - extends TypeError(s"cyclic reference involving $denot") { - def toMessage(implicit ctx: Context) = CyclicReferenceInvolving(denot) - } - - object CyclicReference { - def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { - val ex = new CyclicReference(denot) - if (!(ctx.mode is Mode.CheckCyclic)) { - cyclicErrors.println(ex.getMessage) - for (elem <- ex.getStackTrace take 200) - cyclicErrors.println(elem.toString) - } - ex - } - } - - class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg) - // ----- Debug --------------------------------------------------------- @sharable var debugTrace = false diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 3bcf99e8b3cc..e26fe556510b 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -14,7 +14,6 @@ import Symbols._ import NameOps._ import NameKinds.DefaultGetterName import typer.Inliner -import typer.ErrorReporting.cyclicErrorMsg import transform.ValueClasses import transform.SymUtils._ import dotty.tools.io.File @@ -243,10 +242,10 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder val ancestorTypes0 = try linearizedAncestorTypes(cinfo) catch { - case ex: CyclicReference => + case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(cyclicErrorMsg(ex), csym.pos) + ctx.error(ex.toMessage, csym.pos) defn.ObjectType :: Nil } if (ValueClasses.isDerivedValueClass(csym)) { diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 57858600dad6..2f166891f353 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -6,7 +6,6 @@ import Flags._, Symbols._, Contexts._, Types._, Scopes._, Decorators._ import util.HashSet import collection.mutable import collection.immutable.BitSet -import typer.ErrorReporting.cyclicErrorMsg import scala.annotation.tailrec /** A module that can produce a kind of iterator (`Cursor`), @@ -133,10 +132,10 @@ object OverridingPairs { } } catch { - case ex: CyclicReference => + case ex: TypeError => // See neg/i1750a for an example where a cyclic error can arise. // The root cause in this example is an illegal "override" of an inner trait - ctx.error(cyclicErrorMsg(ex), base.pos) + ctx.error(ex.toMessage, base.pos) } } else { curEntry = curEntry.prev diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e637f3228021..9f3e875dbd86 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -341,18 +341,15 @@ object Checking { val mbr = joint.member(name) mbr.info match { case bounds: TypeBounds => - val res = checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError - if (res) - println(i"cyclic ${mbr.symbol}, $bounds -> $res") - res + !checkNonCyclic(mbr.symbol, bounds, reportErrors = true).isError case _ => - false + true } } catch { - case ex: CyclicFindMember => + case ex: RecursionOverflow => ctx.error(em"cyclic reference involving type $name", pos) - true + false } } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 6e03384e31e1..79799c9013a1 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -23,26 +23,6 @@ object ErrorReporting { ErrorType(msg) } - def cyclicErrorMsg(ex: CyclicReference)(implicit ctx: Context) = { - val cycleSym = ex.denot.symbol - def errorMsg(msg: Message, cx: Context): Message = - if (cx.mode is Mode.InferringReturnType) { - cx.tree match { - case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => - OverloadedOrRecursiveMethodNeedsResultType(tree.name) - case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => - RecursiveValueNeedsResultType(tree.name) - case _ => - errorMsg(msg, cx.outer) - } - } else msg - - if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) - CyclicReferenceInvolvingImplicit(cycleSym) - else - errorMsg(ex.toMessage, ctx) - } - def wrongNumberOfTypeArgs(fntpe: Type, expectedArgs: List[ParamInfo], actual: List[untpd.Tree], pos: Position)(implicit ctx: Context) = errorType(WrongNumberOfTypeArgs(fntpe, expectedArgs, actual)(ctx), pos) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 628c069d99d5..af7dbdaed390 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -1614,7 +1614,7 @@ class RefChecks extends MiniPhase { thisPhase => } catch { case ex: TypeError => if (settings.debug) ex.printStackTrace() - unit.error(tree.pos, ex.getMessage()) + unit.error(tree.pos, ex.toMssage) tree } finally { localTyper = savedLocalTyper diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 733b34d181e7..657439aa7a72 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1857,8 +1857,7 @@ class Typer extends Namer assertPositioned(tree) try adapt(typedUnadapted(tree, pt, locked), pt, locked) catch { - case ex: CyclicReference => errorTree(tree, cyclicErrorMsg(ex)) - case ex: TypeError => errorTree(tree, ex.getMessage) + case ex: TypeError => errorTree(tree, ex.toMessage) } } diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala index ce1bc25fdeca..5ffe099811a1 100644 --- a/tests/neg/i4368.scala +++ b/tests/neg/i4368.scala @@ -110,7 +110,7 @@ object Test7 { object Test8 { class A { - type T = B#U // error: cyclic + type T = B#U } class B { From ebe1d1cac50d61788ab07e7ac3a7a4d34994907f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 19:00:00 +0200 Subject: [PATCH 10/20] Further refactoring of stackoverflow handling This now handles all errors from #4368 to #4372 and also #318. --- .../tools/dotc/core/TypeApplications.scala | 5 +- .../dotty/tools/dotc/core/TypeComparer.scala | 9 +- .../dotty/tools/dotc/core/TypeErrors.scala | 123 ++++++++++++++++++ .../src/dotty/tools/dotc/core/Types.scala | 10 +- .../tools/dotc/typer/VarianceChecker.scala | 34 ++--- .../dotty/tools/dotc/CompilationTests.scala | 1 + tests/neg-custom-args/i4372.scala | 3 + tests/neg/i4368.scala | 29 ++++- 8 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/core/TypeErrors.scala create mode 100644 tests/neg-custom-args/i4372.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 53b5bede376b..1971adf70875 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -168,7 +168,7 @@ class TypeApplications(val self: Type) extends AnyVal { * any type parameter that is-rebound by the refinement. */ final def typeParams(implicit ctx: Context): List[TypeParamInfo] = /*>|>*/ track("typeParams") /*<|<*/ { - self match { + try self match { case self: TypeRef => val tsym = self.symbol if (tsym.isClass) tsym.typeParams @@ -193,6 +193,9 @@ class TypeApplications(val self: Type) extends AnyVal { case _ => Nil } + catch { + case ex: Throwable => handleRecursive("type parameters of", self.show, ex) + } } /** If `self` is a higher-kinded type, its type parameters, otherwise Nil */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index d2e122b7bfe8..52c39ab473cc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -121,7 +121,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = { val saved = approx this.approx = a - try recur(tp1, tp2) finally this.approx = saved + try recur(tp1, tp2) + catch { + case ex: Throwable => handleRecursive("subtype", i"$tp1 <:< $tp2", ex, weight = 2) + } + finally this.approx = saved } protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox) @@ -161,7 +165,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { try { pendingSubTypes += p firstTry - } finally { + } + finally { pendingSubTypes -= p } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala new file mode 100644 index 000000000000..e15688dea314 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -0,0 +1,123 @@ +package dotty.tools +package dotc +package core + +import util.common._ +import Types._ +import Symbols._ +import Flags._ +import Names._ +import Contexts._ +import SymDenotations._ +import Denotations._ +import Decorators._ +import reporting.diagnostic.Message +import reporting.diagnostic.messages._ +import ast.untpd +import config.Printers.cyclicErrors + +class TypeError(msg: String) extends Exception(msg) { + def this() = this("") + def toMessage(implicit ctx: Context): Message = getMessage +} + +class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError { + override def toMessage(implicit ctx: Context): Message = + i"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}" +} + +class MissingType(pre: Type, name: Name) extends TypeError { + private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { + case pre: ThisType if pre.cls.givenSelfType.exists => + i"\nor the self type of $pre might not contain all transitive dependencies" + case _ => "" + } + + override def toMessage(implicit ctx: Context): Message = { + if (ctx.debug) printStackTrace() + i"""cannot resolve reference to type $pre.$name + |the classfile defining the type might be missing from the classpath${otherReason(pre)}""" + } +} + +class RecursionOverflow(val op: String, details: => String, previous: Throwable, val weight: Int) extends TypeError { + + def explanation = s"$op $details" + + private def recursions: List[RecursionOverflow] = { + val nested = previous match { + case previous: RecursionOverflow => previous.recursions + case _ => Nil + } + this :: nested + } + + def opsString(rs: List[RecursionOverflow])(implicit ctx: Context): String = { + val maxShown = 20 + if (rs.lengthCompare(maxShown) > 0) + i"""${opsString(rs.take(maxShown / 2))} + | ... + |${opsString(rs.takeRight(maxShown / 2))}""" + else + (rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "") + } + + override def toMessage(implicit ctx: Context): Message = { + val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse + s"""Recursion limit exceeded. + |Maybe there is an illegal cyclic reference? + |A recurring operation is (inner to outer): + |${opsString(mostCommon)}""".stripMargin + } + + override def fillInStackTrace(): Throwable = this + override def getStackTrace() = previous.getStackTrace() +} + +object handleRecursive { + def apply(op: String, details: => String, exc: Throwable, weight: Int = 1): Nothing = exc match { + case _: RecursionOverflow => + throw new RecursionOverflow(op, details, exc, weight) + case _ => + var e = exc + while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause + if (e != null) throw new RecursionOverflow(op, details, e, weight) + else throw exc + } +} + +class CyclicReference private (val denot: SymDenotation) extends TypeError { + override def toMessage(implicit ctx: Context) = { + val cycleSym = denot.symbol + def errorMsg(msg: Message, cx: Context): Message = + if (cx.mode is Mode.InferringReturnType) { + cx.tree match { + case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => + OverloadedOrRecursiveMethodNeedsResultType(tree.name) + case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => + RecursiveValueNeedsResultType(tree.name) + case _ => + errorMsg(msg, cx.outer) + } + } else msg + + if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) + CyclicReferenceInvolvingImplicit(cycleSym) + else + CyclicReferenceInvolving(denot) + } +} + +object CyclicReference { + def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { + val ex = new CyclicReference(denot) + if (!(ctx.mode is Mode.CheckCyclic)) { + cyclicErrors.println(ex.getMessage) + for (elem <- ex.getStackTrace take 200) + cyclicErrors.println(elem.toString) + } + ex + } +} + +class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg) \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 1590a695dd06..2e701b1c5ac9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -656,7 +656,15 @@ object Types { catch { case ex: Throwable => core.println(s"findMember exception for $this member $name, pre = $pre, recCount = $recCount") - throw new RecursionOverflow("find-member", pre, name, ex) + + def showPrefixSafely(pre: Type)(implicit ctx: Context): String = pre.stripTypeVar match { + case pre: TermRef => i"${pre.termSymbol.name}." + case pre: TypeRef => i"${pre.typeSymbol.name}#" + case pre: TypeProxy => showPrefixSafely(pre.underlying) + case _ => if (pre.typeSymbol.exists) i"${pre.typeSymbol.name}#" else "." + } + + handleRecursive("find-member", i"${showPrefixSafely(pre)}$name", ex) } finally { if (recCount >= Config.LogPendingFindMemberThreshold) diff --git a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala index 0ba36c2ac306..ee98321dcc78 100644 --- a/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala +++ b/compiler/src/dotty/tools/dotc/typer/VarianceChecker.scala @@ -82,21 +82,25 @@ class VarianceChecker()(implicit ctx: Context) { * same is true of the parameters (ValDefs). */ def apply(status: Option[VarianceError], tp: Type): Option[VarianceError] = trace(s"variance checking $tp of $base at $variance", variances) { - if (status.isDefined) status - else tp match { - case tp: TypeRef => - val sym = tp.symbol - if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) - else if (sym.isAliasType) this(status, sym.info.bounds.hi) - else foldOver(status, tp) - case tp: MethodOrPoly => - this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes. - case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => - status - //case tp: ClassInfo => - // ??? not clear what to do here yet. presumably, it's all checked at local typedefs - case _ => - foldOver(status, tp) + try + if (status.isDefined) status + else tp match { + case tp: TypeRef => + val sym = tp.symbol + if (sym.variance != 0 && base.isContainedIn(sym.owner)) checkVarianceOfSymbol(sym) + else if (sym.isAliasType) this(status, sym.info.bounds.hi) + else foldOver(status, tp) + case tp: MethodOrPoly => + this(status, tp.resultType) // params will be checked in their TypeDef or ValDef nodes. + case AnnotatedType(_, annot) if annot.symbol == defn.UncheckedVarianceAnnot => + status + //case tp: ClassInfo => + // ??? not clear what to do here yet. presumably, it's all checked at local typedefs + case _ => + foldOver(status, tp) + } + catch { + case ex: Throwable => handleRecursive("variance check of", tp.show, ex) } } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index aaa8ac216b5b..03906caae63d 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -187,6 +187,7 @@ class CompilationTests extends ParallelTesting { compileFile("tests/neg-custom-args/noimports.scala", defaultOptions.and("-Yno-imports")) + compileFile("tests/neg-custom-args/noimports2.scala", defaultOptions.and("-Yno-imports")) + compileFile("tests/neg-custom-args/i3882.scala", allowDeepSubtypes) + + compileFile("tests/neg-custom-args/i4372.scala", allowDeepSubtypes) + compileFile("tests/neg-custom-args/i1754.scala", allowDeepSubtypes) + compileFilesInDir("tests/neg-custom-args/isInstanceOf", allowDeepSubtypes and "-Xfatal-warnings") + compileFile("tests/neg-custom-args/i3627.scala", allowDeepSubtypes) diff --git a/tests/neg-custom-args/i4372.scala b/tests/neg-custom-args/i4372.scala new file mode 100644 index 000000000000..ebe4b1192d6b --- /dev/null +++ b/tests/neg-custom-args/i4372.scala @@ -0,0 +1,3 @@ +object i4372 { + class X[A >: X[_ <: X[_]] <: X[A]] +} diff --git a/tests/neg/i4368.scala b/tests/neg/i4368.scala index 5ffe099811a1..c5c0f5223d95 100644 --- a/tests/neg/i4368.scala +++ b/tests/neg/i4368.scala @@ -106,18 +106,16 @@ object Test7 { type Nat = Fix[Option]#T } } -/* object Test8 { class A { - type T = B#U + type T = B#U // error: cyclic } class B { type U = A#T } } -*/ object Test9 { trait W { type A @@ -147,4 +145,29 @@ object Test9 { // scalac reports a volatility error, but the dotty equivalent (checkRealizable) // is checked too late. } +} +object i4369 { + trait X { self => + type R <: Z + type Z >: X { type R = self.R; type Z = self.R } + } + class Foo extends X { type R = Foo; type Z = Foo } // error: too deep +} +object i4370 { + class Foo { type R = A } // error: cyclic + type A = List[Foo#R] +} +object i4371 { + class Foo { type A = Boo#B } // error: cyclic + class Boo { type B = Foo#A } +} +object i318 { + trait Y { + type A <: { type T >: B } + type B >: { type T >: A } + } + + val y: Y = ??? + val a: y.A = ??? // error: too deep + val b: y.B = a // error: too deep } \ No newline at end of file From 9bb6cfea27bffc2d32682d78a5f5335edb90e535 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 19:42:16 +0200 Subject: [PATCH 11/20] Fix CyclicError reporting --- .../dotty/tools/dotc/core/TypeErrors.scala | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index e15688dea314..ebb3c7da9687 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -86,25 +86,27 @@ object handleRecursive { } } -class CyclicReference private (val denot: SymDenotation) extends TypeError { +class CyclicReference private (val denot: SymDenotation)(implicit val ctx: Context) extends TypeError { + + def errorMsg(cx: Context): Message = + if (cx.mode is Mode.InferringReturnType) { + cx.tree match { + case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => + OverloadedOrRecursiveMethodNeedsResultType(tree.name) + case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => + RecursiveValueNeedsResultType(tree.name) + case _ => + errorMsg(cx.outer) + } + } + else CyclicReferenceInvolving(denot) + override def toMessage(implicit ctx: Context) = { val cycleSym = denot.symbol - def errorMsg(msg: Message, cx: Context): Message = - if (cx.mode is Mode.InferringReturnType) { - cx.tree match { - case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => - OverloadedOrRecursiveMethodNeedsResultType(tree.name) - case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => - RecursiveValueNeedsResultType(tree.name) - case _ => - errorMsg(msg, cx.outer) - } - } else msg - if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) CyclicReferenceInvolvingImplicit(cycleSym) else - CyclicReferenceInvolving(denot) + errorMsg(this.ctx) } } From e85e5165ef5db2ffdb2c3c491560a810045ff78c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 20:58:51 +0200 Subject: [PATCH 12/20] Fix context for cyclic error message --- compiler/src/dotty/tools/dotc/core/TypeErrors.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index ebb3c7da9687..edb8e374a597 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -86,7 +86,7 @@ object handleRecursive { } } -class CyclicReference private (val denot: SymDenotation)(implicit val ctx: Context) extends TypeError { +class CyclicReference private (val denot: SymDenotation) extends TypeError { def errorMsg(cx: Context): Message = if (cx.mode is Mode.InferringReturnType) { @@ -106,7 +106,7 @@ class CyclicReference private (val denot: SymDenotation)(implicit val ctx: Conte if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) CyclicReferenceInvolvingImplicit(cycleSym) else - errorMsg(this.ctx) + errorMsg(ctx) } } From 4bef48f7623e1bd0a422445f4461e5b371a78eba Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 21:12:28 +0200 Subject: [PATCH 13/20] Fix^2 context in CyclicReference toMessage --- .../dotty/tools/dotc/core/TypeErrors.scala | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index edb8e374a597..9b6cf53eea7b 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -88,20 +88,21 @@ object handleRecursive { class CyclicReference private (val denot: SymDenotation) extends TypeError { - def errorMsg(cx: Context): Message = - if (cx.mode is Mode.InferringReturnType) { - cx.tree match { - case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => - OverloadedOrRecursiveMethodNeedsResultType(tree.name) - case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => - RecursiveValueNeedsResultType(tree.name) - case _ => - errorMsg(cx.outer) + override def toMessage(implicit ctx: Context) = { + + def errorMsg(cx: Context): Message = + if (cx.mode is Mode.InferringReturnType) { + cx.tree match { + case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => + OverloadedOrRecursiveMethodNeedsResultType(tree.name) + case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => + RecursiveValueNeedsResultType(tree.name) + case _ => + errorMsg(cx.outer) + } } - } - else CyclicReferenceInvolving(denot) + else CyclicReferenceInvolving(denot) - override def toMessage(implicit ctx: Context) = { val cycleSym = denot.symbol if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) CyclicReferenceInvolvingImplicit(cycleSym) From 4828fe5a9174bc130b81aad1c6808f7f30e9d206 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Apr 2018 23:28:05 +0200 Subject: [PATCH 14/20] Fix error annotation in test --- tests/neg-custom-args/i4372.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg-custom-args/i4372.scala b/tests/neg-custom-args/i4372.scala index ebe4b1192d6b..e6e75061bb02 100644 --- a/tests/neg-custom-args/i4372.scala +++ b/tests/neg-custom-args/i4372.scala @@ -1,3 +1,3 @@ object i4372 { - class X[A >: X[_ <: X[_]] <: X[A]] + class X[A >: X[_ <: X[_]] <: X[A]] // error: too deep } From d977e786f31f1e5a15ad7bb833f4a7986b4d6df1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Apr 2018 10:20:38 +0200 Subject: [PATCH 15/20] MergeError refactorings --- .../dotty/tools/dotc/core/Denotations.scala | 44 +++++-------------- .../dotty/tools/dotc/core/TypeErrors.scala | 22 +++++++++- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 0443f346476e..54993ce45cf6 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -332,21 +332,8 @@ object Denotations { } /** Handle merge conflict by throwing a `MergeError` exception */ - private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type = { - def showSymbol(sym: Symbol): String = if (sym.exists) sym.showLocated else "[unknown]" - def showType(tp: Type) = tp match { - case ClassInfo(_, cls, _, _, _) => cls.showLocated - case bounds: TypeBounds => i"type bounds $bounds" - case _ => tp.show - } - val msg = - s"""cannot merge - | ${showSymbol(this.symbol)} of type ${showType(tp1)} and - | ${showSymbol(that.symbol)} of type ${showType(tp2)} - """ - if (true) throw new MergeError(msg, tp1, tp2) - else throw new Error(msg) // flip condition for debugging - } + private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type = + throw new MergeError(this.symbol, that.symbol, tp1, tp2, NoPrefix) /** Merge parameter names of lambda types. If names in corresponding positions match, keep them, * otherwise generate new synthetic names. @@ -537,8 +524,7 @@ object Denotations { else if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode) info1 // follow Scala2 linearization - // compare with way merge is performed in SymDenotation#computeMembersNamed - else - throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2) + else throw new MergeError(ex.sym1, ex.sym2, ex.tp1, ex.tp2, pre) } new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor) } @@ -1136,21 +1122,15 @@ object Denotations { def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = { val sym1 = denot1.symbol val sym2 = denot2.symbol - def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre" - val msg = - if (denot1.isTerm) - i"""cannot merge - | $sym1: ${sym1.info} and - | $sym2: ${sym2.info}; - |they are both defined in ${sym1.owner} but have matching signatures - | ${denot1.info} and - | ${denot2.info}$fromWhere""" - else - i"""cannot merge - | $sym1 ${denot1.info} - | $sym2 ${denot2.info} - |they are conflicting definitions$fromWhere""" - throw new MergeError(msg, denot2.info, denot2.info) + if (denot1.isTerm) + throw new MergeError(sym1, sym2, sym1.info, sym2.info, pre) { + override def addendum(implicit ctx: Context) = + i""" + |they are both defined in ${sym1.owner} but have matching signatures + | ${denot1.info} and + | ${denot2.info}${super.addendum}""" + } + else throw new MergeError(sym1, sym2, denot1.info, denot2.info, pre) } // --- Overloaded denotations and predenotations ------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index 9b6cf53eea7b..e9fbe16cb1a8 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -123,4 +123,24 @@ object CyclicReference { } } -class MergeError(msg: String, val tp1: Type, val tp2: Type) extends TypeError(msg) \ No newline at end of file +class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Type, prefix: Type) extends TypeError { + + private def showSymbol(sym: Symbol)(implicit ctx: Context): String = + if (sym.exists) sym.showLocated else "[unknown]" + + private def showType(tp: Type)(implicit ctx: Context) = tp match { + case ClassInfo(_, cls, _, _, _) => cls.showLocated + case _ => tp.show + } + + protected def addendum(implicit ctx: Context) = + if (prefix `eq` NoPrefix) "" else i"\nas members of type $prefix" + + override def toMessage(implicit ctx: Context): Message = { + if (ctx.debug) printStackTrace() + i"""cannot merge + | ${showSymbol(sym1)} of type ${showType(tp1)} and + | ${showSymbol(sym2)} of type ${showType(tp2)}$addendum + """ + } +} From 7d3927bebe49ee47c5b2cfcb0ab53812a9867c0a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Apr 2018 10:24:06 +0200 Subject: [PATCH 16/20] Add `append` method to Message Not needed here, but I missed the functionality elsewhere. --- .../src/dotty/tools/dotc/reporting/diagnostic/Message.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala index 09d7ae9751be..74b4e1fe0194 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/Message.scala @@ -65,6 +65,12 @@ abstract class Message(val errorId: ErrorMessageID) { self => val kind = self.kind val explanation = self.explanation } + + def append(suffix: => String): Message = new Message(errorId) { + val msg = self.msg ++ suffix + val kind = self.kind + val explanation = self.explanation + } } /** An extended message keeps the contained message from being evaluated, while From c3b9ff766abdd9b7b1f0e6e30dcb4e240b7963d7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Apr 2018 10:59:53 +0200 Subject: [PATCH 17/20] Also catch TypeErrors in all transforms Once we are at it, let's try to bullet-proof everything. --- .../dotty/tools/dotc/core/TypeErrors.scala | 1 + .../tools/dotc/transform/MacroTransform.scala | 33 +++++++++++-------- .../tools/dotc/transform/MegaPhase.scala | 8 ++++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala index e9fbe16cb1a8..3028ebc04a2d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErrors.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErrors.scala @@ -66,6 +66,7 @@ class RecursionOverflow(val op: String, details: => String, previous: Throwable, val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse s"""Recursion limit exceeded. |Maybe there is an illegal cyclic reference? + |If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. |A recurring operation is (inner to outer): |${opsString(mostCommon)}""".stripMargin } diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 9d84a2adb66c..5c3dea9cb79e 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -46,22 +46,27 @@ abstract class MacroTransform extends Phase { flatten(trees.mapconserve(transformStat(_))) } - override def transform(tree: Tree)(implicit ctx: Context): Tree = { - tree match { - case EmptyValDef => + override def transform(tree: Tree)(implicit ctx: Context): Tree = + try + tree match { + case EmptyValDef => + tree + case _: PackageDef | _: MemberDef => + super.transform(tree)(localCtx(tree)) + case impl @ Template(constr, parents, self, _) => + cpy.Template(tree)( + transformSub(constr), + transform(parents)(ctx.superCallContext), + transformSelf(self), + transformStats(impl.body, tree.symbol)) + case _ => + super.transform(tree) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) tree - case _: PackageDef | _: MemberDef => - super.transform(tree)(localCtx(tree)) - case impl @ Template(constr, parents, self, _) => - cpy.Template(tree)( - transformSub(constr), - transform(parents)(ctx.superCallContext), - transformSelf(self), - transformStats(impl.body, tree.symbol)) - case _ => - super.transform(tree) } - } def transformSelf(vd: ValDef)(implicit ctx: Context) = cpy.ValDef(vd)(tpt = transform(vd.tpt)) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 868c48b0c0ae..dac467a20e86 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -193,7 +193,13 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: Alternative => goAlternative(tree, start) case tree => goOther(tree, start) } - if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) + try + if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) + tree + } } /** Transform full tree using all phases in this group that have idxInGroup >= start */ From ec460a1b8e36f938c367ecf7061ec08a9e1ebf13 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Apr 2018 12:31:53 +0200 Subject: [PATCH 18/20] rearrange try-catch in MegaPhase Move it from `transformNode` to `goNamed` and `goUnnamed`. This might be faster since that way `transformNode` should be an inline candidate since it is short. --- .../tools/dotc/transform/MegaPhase.scala | 94 ++++++++++--------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index dac467a20e86..8e1981c85e07 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -157,49 +157,57 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { /** Transform node using all phases in this group that have idxInGroup >= start */ def transformNode(tree: Tree, start: Int)(implicit ctx: Context): Tree = { - def goNamed(tree: Tree, start: Int) = tree match { - case tree: Ident => goIdent(tree, start) - case tree: Select => goSelect(tree, start) - case tree: ValDef => goValDef(tree, start) - case tree: DefDef => goDefDef(tree, start) - case tree: TypeDef => goTypeDef(tree, start) - case tree: Bind => goBind(tree, start) - case _ => goOther(tree, start) - } - def goUnnamed(tree: Tree, start: Int) = tree match { - case tree: Apply => goApply(tree, start) - case tree: TypeTree => goTypeTree(tree, start) - case tree: Thicket => - cpy.Thicket(tree)(tree.trees.mapConserve(transformNode(_, start))) - case tree: This => goThis(tree, start) - case tree: Literal => goLiteral(tree, start) - case tree: Block => goBlock(tree, start) - case tree: TypeApply => goTypeApply(tree, start) - case tree: If => goIf(tree, start) - case tree: New => goNew(tree, start) - case tree: Typed => goTyped(tree, start) - case tree: CaseDef => goCaseDef(tree, start) - case tree: Closure => goClosure(tree, start) - case tree: Assign => goAssign(tree, start) - case tree: SeqLiteral => goSeqLiteral(tree, start) - case tree: Super => goSuper(tree, start) - case tree: Template => goTemplate(tree, start) - case tree: Match => goMatch(tree, start) - case tree: UnApply => goUnApply(tree, start) - case tree: PackageDef => goPackageDef(tree, start) - case tree: Try => goTry(tree, start) - case tree: Inlined => goInlined(tree, start) - case tree: Return => goReturn(tree, start) - case tree: Alternative => goAlternative(tree, start) - case tree => goOther(tree, start) - } - try - if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) - catch { - case ex: TypeError => - ctx.error(ex.toMessage, tree.pos) - tree - } + def goNamed(tree: Tree, start: Int) = + try + tree match { + case tree: Ident => goIdent(tree, start) + case tree: Select => goSelect(tree, start) + case tree: ValDef => goValDef(tree, start) + case tree: DefDef => goDefDef(tree, start) + case tree: TypeDef => goTypeDef(tree, start) + case tree: Bind => goBind(tree, start) + case _ => goOther(tree, start) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) + tree + } + def goUnnamed(tree: Tree, start: Int) = + try + tree match { + case tree: Apply => goApply(tree, start) + case tree: TypeTree => goTypeTree(tree, start) + case tree: Thicket => + cpy.Thicket(tree)(tree.trees.mapConserve(transformNode(_, start))) + case tree: This => goThis(tree, start) + case tree: Literal => goLiteral(tree, start) + case tree: Block => goBlock(tree, start) + case tree: TypeApply => goTypeApply(tree, start) + case tree: If => goIf(tree, start) + case tree: New => goNew(tree, start) + case tree: Typed => goTyped(tree, start) + case tree: CaseDef => goCaseDef(tree, start) + case tree: Closure => goClosure(tree, start) + case tree: Assign => goAssign(tree, start) + case tree: SeqLiteral => goSeqLiteral(tree, start) + case tree: Super => goSuper(tree, start) + case tree: Template => goTemplate(tree, start) + case tree: Match => goMatch(tree, start) + case tree: UnApply => goUnApply(tree, start) + case tree: PackageDef => goPackageDef(tree, start) + case tree: Try => goTry(tree, start) + case tree: Inlined => goInlined(tree, start) + case tree: Return => goReturn(tree, start) + case tree: Alternative => goAlternative(tree, start) + case tree => goOther(tree, start) + } + catch { + case ex: TypeError => + ctx.error(ex.toMessage, tree.pos) + tree + } + if (tree.isInstanceOf[NameTree]) goNamed(tree, start) else goUnnamed(tree, start) } /** Transform full tree using all phases in this group that have idxInGroup >= start */ From ae297aea4e92804ae613ceb1a1b96da28f125f6b Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 8 May 2018 11:08:38 +0200 Subject: [PATCH 19/20] Band-aid fix for ClassCastException on malformed programs --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- tests/neg/parser-stability-25.scala | 11 +++++++++++ tests/neg/parser-stability-26.scala | 2 ++ tests/pos/singleton-fun-types.scala | 4 ++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/neg/parser-stability-25.scala create mode 100644 tests/neg/parser-stability-26.scala create mode 100644 tests/pos/singleton-fun-types.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 24ed19112fb6..d7ca7a6b8b3b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -801,7 +801,8 @@ class Typer extends Namer } def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { - val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree + val untpd.Function(params0, body) = tree + val params: List[untpd.ValDef] = params0 collect { case t : untpd.ValDef => t } pt match { case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => diff --git a/tests/neg/parser-stability-25.scala b/tests/neg/parser-stability-25.scala new file mode 100644 index 000000000000..987ae79eb167 --- /dev/null +++ b/tests/neg/parser-stability-25.scala @@ -0,0 +1,11 @@ +class A extends (Int => i1) // error +class B extends (Int => this) // error +trait C { + val bar: Int => this // error +} + +// Test that function types ending in SIP-23 singleton types are understood correctly. + +class D extends (Int => 1) { + def apply(x: Int) = 2 // error +} diff --git a/tests/neg/parser-stability-26.scala b/tests/neg/parser-stability-26.scala new file mode 100644 index 000000000000..dd2c00d5c5b9 --- /dev/null +++ b/tests/neg/parser-stability-26.scala @@ -0,0 +1,2 @@ +// Test that function types ending in SIP-23 singleton types are understood correctly. +class E extends (Int => 1) // error diff --git a/tests/pos/singleton-fun-types.scala b/tests/pos/singleton-fun-types.scala new file mode 100644 index 000000000000..dd2f8525c9c4 --- /dev/null +++ b/tests/pos/singleton-fun-types.scala @@ -0,0 +1,4 @@ +trait C extends (Int => 1) +class D extends (Int => 1) { + def apply(x: Int) = 1 +} From 357b20109f876819dc0a3a21528f86a9531375dd Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 8 May 2018 14:10:24 +0200 Subject: [PATCH 20/20] Much better, still band-aidy, fix This patch addressed the problem at a better point I think: to typecheck ```scala class Foo extends someTree ``` we need to detect correctly if `someTree` should be typechecked as a term or a type; `Int => 1` should be typechecked as a type, even though it looks like a term according to `isTerm`. Really, we shouldn't use isType at all, because the user might write a type in place of a term or viceversa. I think we only want to know this is a constructor call or a type; and maybe we should just let the parser tell us which, since it knows. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d7ca7a6b8b3b..a2b53e89bdf5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -801,8 +801,7 @@ class Typer extends Namer } def typedFunctionValue(tree: untpd.Function, pt: Type)(implicit ctx: Context) = { - val untpd.Function(params0, body) = tree - val params: List[untpd.ValDef] = params0 collect { case t : untpd.ValDef => t } + val untpd.Function(params: List[untpd.ValDef] @unchecked, body) = tree pt match { case pt: TypeVar if untpd.isFunctionWithUnknownParamType(tree) => @@ -1482,7 +1481,13 @@ class Typer extends Namer val seenParents = mutable.Set[Symbol]() def typedParent(tree: untpd.Tree): Tree = { - var result = if (tree.isType) typedType(tree)(superCtx) else typedExpr(tree)(superCtx) + @tailrec + def isTreeType(t: untpd.Tree): Boolean = t match { + case _: untpd.Function => true + case untpd.Parens(t1) => isTreeType(t1) + case _ => tree.isType + } + var result = if (isTreeType(tree)) typedType(tree)(superCtx) else typedExpr(tree)(superCtx) val psym = result.tpe.typeSymbol if (seenParents.contains(psym)) ctx.error(i"$psym is extended twice", tree.pos) seenParents += psym