From 461c477105961bfae90c31933beffba2c6bb270f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 23:12:28 +0100 Subject: [PATCH 1/3] Fix #4098: Check that refinements have good bounds So far, we did the check only for member symbols. We have to do the same thing for type refinements that do not refer to a member. --- .../tools/dotc/core/CheckRealizable.scala | 32 +++++++++++++++---- tests/neg/i4098.scala | 16 ++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/neg/i4098.scala diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 05925421bdb0..07513adaaf4c 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -30,8 +30,8 @@ object CheckRealizable { class NotFinal(sym: Symbol)(implicit ctx: Context) extends Realizability(i" refers to nonfinal $sym") - class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) - extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") + class HasProblemBounds(name: Name, info: Type)(implicit ctx: Context) + extends Realizability(i" has a member $name with possibly conflicting bounds ${info.bounds.lo} <: ... <: ${info.bounds.hi}") class HasProblemBaseArg(typ: Type, argBounds: TypeBounds)(implicit ctx: Context) extends Realizability(i" has a base type $typ with possibly conflicting parameter bounds ${argBounds.lo} <: ... <: ${argBounds.hi}") @@ -96,6 +96,14 @@ class CheckRealizable(implicit ctx: Context) { else boundsRealizability(tp).andAlso(memberRealizability(tp)) } + private def refinedNames(tp: Type): Set[Name] = tp.dealias match { + case tp: RefinedType => refinedNames(tp.parent) + tp.refinedName + case tp: AndType => refinedNames(tp.tp1) ++ refinedNames(tp.tp2) + case tp: OrType => refinedNames(tp.tp1) ++ refinedNames(tp.tp2) + case tp: TypeProxy => refinedNames(tp.underlying) + case _ => Set.empty + } + /** `Realizable` if `tp` has good bounds, a `HasProblem...` instance * pointing to a bad bounds member otherwise. "Has good bounds" means: * @@ -107,12 +115,21 @@ class CheckRealizable(implicit ctx: Context) { * also lead to base types with bad bounds). */ private def boundsRealizability(tp: Type) = { - val mbrProblems = + + val memberProblems = for { mbr <- tp.nonClassTypeMembers if !(mbr.info.loBound <:< mbr.info.hiBound) } - yield new HasProblemBounds(mbr) + yield new HasProblemBounds(mbr.name, mbr.info) + + val refinementProblems = + for { + name <- refinedNames(tp) + mbr <- tp.member(name).alternatives + if !(mbr.info.loBound <:< mbr.info.hiBound) + } + yield new HasProblemBounds(name, mbr.info) def baseTypeProblems(base: Type) = base match { case AndType(base1, base2) => @@ -126,12 +143,13 @@ class CheckRealizable(implicit ctx: Context) { val baseProblems = tp.baseClasses.map(_.baseTypeOf(tp)).flatMap(baseTypeProblems) - (((Realizable: Realizability) - /: mbrProblems)(_ andAlso _) + ((((Realizable: Realizability) + /: memberProblems)(_ andAlso _) + /: refinementProblems)(_ andAlso _) /: baseProblems)(_ andAlso _) } - /** `Realizable` if all of `tp`'s non-struct fields have realizable types, + /** `Realizable` if all of `tp`'s non-strict fields have realizable types, * a `HasProblemField` instance pointing to a bad field otherwise. */ private def memberRealizability(tp: Type) = { diff --git a/tests/neg/i4098.scala b/tests/neg/i4098.scala new file mode 100644 index 000000000000..0d2be82b17ae --- /dev/null +++ b/tests/neg/i4098.scala @@ -0,0 +1,16 @@ +object App { + import scala.reflect.Selectable.reflectiveSelectable + + def coerce[U, V](u: U): V = { + type X = { type R >: U } + type Y = { type R = V } + type Z = X & Y + val u1: Z#R = u // error: not a legal path + u1 + } + + def main(args: Array[String]): Unit = { + val x: Int = coerce[String, Int]("a") + println(x + 1) + } +} From c65e9548e5a6fd6da10288cc9844cc9e8c93aeb6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 15 Mar 2018 23:17:19 +0100 Subject: [PATCH 2/3] Change position of dynamic selections When checking whether non-member value refinements could cause unsoundness problems, I noted that the end position of a dynamic select got lost; the dynamic selection would have the same position as its qualifier. I.e. if `z.x` is a dynamic selection, the indicated position would be just `z`. Fixed now. The correct position can be seen when inspecting the error message of i4098.scala. Ideally, this would be a scripting test. I let someone else add one. --- .../src/dotty/tools/dotc/typer/Dynamic.scala | 37 ++++++++++--------- tests/neg/i4098a.scala | 16 ++++++++ 2 files changed, 35 insertions(+), 18 deletions(-) create mode 100644 tests/neg/i4098a.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala index f108c15b2d8d..e14b7f122b6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Dynamic.scala +++ b/compiler/src/dotty/tools/dotc/typer/Dynamic.scala @@ -11,6 +11,7 @@ import dotty.tools.dotc.core.Names.{Name, TermName} import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.Decorators._ +import util.Positions._ import core.Symbols._ import core.Definitions import Inferencing._ @@ -49,7 +50,7 @@ trait Dynamic { self: Typer with Applications => * foo.bar[T0, ...](x = bazX, y = bazY, baz, ...) ~~> foo.applyDynamicNamed[T0, ...]("bar")(("x", bazX), ("y", bazY), ("", baz), ...) */ def typedDynamicApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { - def typedDynamicApply(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = { + def typedDynamicApply(qual: untpd.Tree, name: Name, selPos: Position, targs: List[untpd.Tree]): Tree = { def isNamedArg(arg: untpd.Tree): Boolean = arg match { case NamedArg(_, _) => true; case _ => false } val args = tree.args val dynName = if (args.exists(isNamedArg)) nme.applyDynamicNamed else nme.applyDynamic @@ -62,19 +63,19 @@ trait Dynamic { self: Typer with Applications => case arg => namedArgTuple("", arg) } val args1 = if (dynName == nme.applyDynamic) args else namedArgs - typedApply(untpd.Apply(coreDynamic(qual, dynName, name, targs), args1), pt) + typedApply(untpd.Apply(coreDynamic(qual, dynName, name, selPos, targs), args1), pt) } } tree.fun match { - case Select(qual, name) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, Nil) - case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => - typedDynamicApply(qual, name, targs) + case sel @ Select(qual, name) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, sel.pos, Nil) + case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicApply(qual, name, sel.pos, targs) case TypeApply(fun, targs) => - typedDynamicApply(fun, nme.apply, targs) + typedDynamicApply(fun, nme.apply, fun.pos, targs) case fun => - typedDynamicApply(fun, nme.apply, Nil) + typedDynamicApply(fun, nme.apply, fun.pos, Nil) } } @@ -86,26 +87,26 @@ trait Dynamic { self: Typer with Applications => * through an existing transformation of in typedAssign [foo.bar(baz) = quux ~~> foo.bar.update(baz, quux)]. */ def typedDynamicSelect(tree: untpd.Select, targs: List[Tree], pt: Type)(implicit ctx: Context): Tree = - typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, targs), pt) + typedApply(coreDynamic(tree.qualifier, nme.selectDynamic, tree.name, tree.pos, targs), pt) /** Translate selection that does not typecheck according to the normal rules into a updateDynamic. * foo.bar = baz ~~> foo.updateDynamic(bar)(baz) */ def typedDynamicAssign(tree: untpd.Assign, pt: Type)(implicit ctx: Context): Tree = { - def typedDynamicAssign(qual: untpd.Tree, name: Name, targs: List[untpd.Tree]): Tree = - typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, targs), tree.rhs), pt) + def typedDynamicAssign(qual: untpd.Tree, name: Name, selPos: Position, targs: List[untpd.Tree]): Tree = + typedApply(untpd.Apply(coreDynamic(qual, nme.updateDynamic, name, selPos, targs), tree.rhs), pt) tree.lhs match { - case Select(qual, name) if !isDynamicMethod(name) => - typedDynamicAssign(qual, name, Nil) - case TypeApply(Select(qual, name), targs) if !isDynamicMethod(name) => - typedDynamicAssign(qual, name, targs) + case sel @ Select(qual, name) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, sel.pos, Nil) + case TypeApply(sel @ Select(qual, name), targs) if !isDynamicMethod(name) => + typedDynamicAssign(qual, name, sel.pos, targs) case _ => errorTree(tree, ReassignmentToVal(tree.lhs.symbol.name)) } } - private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = { - val select = untpd.Select(qual, dynName) + private def coreDynamic(qual: untpd.Tree, dynName: Name, name: Name, selPos: Position, targs: List[untpd.Tree])(implicit ctx: Context): untpd.Apply = { + val select = untpd.Select(qual, dynName).withPos(selPos) val selectWithTypes = if (targs.isEmpty) select else untpd.TypeApply(select, targs) @@ -134,7 +135,7 @@ trait Dynamic { self: Typer with Applications => def structuralCall(selectorName: TermName, formals: List[Tree]) = { val selectable = adapt(qual, defn.SelectableType) val scall = untpd.Apply( - untpd.TypedSplice(selectable.select(selectorName)), + untpd.TypedSplice(selectable.select(selectorName)).withPos(tree.pos), (Literal(Constant(name.toString)) :: formals).map(untpd.TypedSplice(_))) typed(scall) } diff --git a/tests/neg/i4098a.scala b/tests/neg/i4098a.scala new file mode 100644 index 000000000000..71da92830a84 --- /dev/null +++ b/tests/neg/i4098a.scala @@ -0,0 +1,16 @@ +object App { + import scala.reflect.Selectable.reflectiveSelectable + + def coerce[U, V](u: U): V = { + type X = { val x: { type R >: U } } + type Y = { val x: { type R = V } } + lazy val z: X & Y = z + val u1: z.x.R = u // error: Object { type R >: U | V <: V } is not stable (with arrows under z.x) + u1 + } + + def main(args: Array[String]): Unit = { + val x: Int = coerce[String, Int]("a") + println(x + 1) + } +} From 3f3f1ba4e0a9c60bda4cbb41722af44c57a6edb0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 19 Mar 2018 18:10:58 +0100 Subject: [PATCH 3/3] Restrict refinement bounds checking to type refinements Analogously to what we do for members that have symbols. --- compiler/src/dotty/tools/dotc/core/CheckRealizable.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 07513adaaf4c..bac81942f8e3 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -126,6 +126,7 @@ class CheckRealizable(implicit ctx: Context) { val refinementProblems = for { name <- refinedNames(tp) + if (name.isTypeName) mbr <- tp.member(name).alternatives if !(mbr.info.loBound <:< mbr.info.hiBound) }