From 4525e3392b42b47147479087d961d328c3b717bb Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 4 Feb 2014 22:29:52 -0800 Subject: SI-6169 Refine java wildcard bounds using corresponding tparam Also fixes part of SI-8197. Necessary complement to SI-1786 (#2518), because we now infer tighter bounds for RHSs to conform to. When opening an existential, Java puts constraints in the typing environment that are derived from the bounds on the type parameters of the existentially quantified type, so let's do the same for existentials over java-defined classes in skolemizeExistential... Example from test case: ``` public class Exist { // java helpfully re-interprets Exist as Exist public Exist foo() { throw new RuntimeException(); } } ``` In Scala syntax, given a java-defined `class C[T <: String]`, the existential type `C[_]` is improved to `C[_ <: String]` before skolemization, which models what Java does (track the bounds as type constraints in the typing environment) (Also tried doing this once during class file parsing or when creating the existential type, but that causes cyclic errors because it happens too early.) --- src/reflect/scala/reflect/internal/Types.scala | 45 ++++++++++++++++++++++++-- test/files/pos/t6169/Exist.java | 4 +++ test/files/pos/t6169/ExistF.java | 4 +++ test/files/pos/t6169/ExistIndir.java | 4 +++ test/files/pos/t6169/OP.java | 1 + test/files/pos/t6169/Skin.java | 1 + test/files/pos/t6169/Skinnable.java | 3 ++ test/files/pos/t6169/skinnable.scala | 14 ++++++++ test/files/pos/t6169/t6169.scala | 7 ++++ 9 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 test/files/pos/t6169/Exist.java create mode 100644 test/files/pos/t6169/ExistF.java create mode 100644 test/files/pos/t6169/ExistIndir.java create mode 100644 test/files/pos/t6169/OP.java create mode 100644 test/files/pos/t6169/Skin.java create mode 100644 test/files/pos/t6169/Skinnable.java create mode 100644 test/files/pos/t6169/skinnable.scala create mode 100644 test/files/pos/t6169/t6169.scala diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 2acf901d0e..a49ecea57f 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -899,7 +899,7 @@ trait Types -1 } - /** If this is a poly- or methodtype, a copy with cloned type / value parameters + /** If this is a ExistentialType, PolyType or MethodType, a copy with cloned type / value parameters * owned by `owner`. Identity for all other types. */ def cloneInfo(owner: Symbol) = this @@ -2656,8 +2656,49 @@ trait Types override def baseTypeSeq = underlying.baseTypeSeq map maybeRewrap override def isHigherKinded = false - override def skolemizeExistential(owner: Symbol, origin: AnyRef) = + /** [SI-6169, SI-8197 -- companion to SI-1786] + * + * Approximation to improve the bounds of a Java-defined existential type, + * based on the bounds of the type parameters of the quantified type + * In Scala syntax, given a java-defined class C[T <: String], the existential type C[_] + * is improved to C[_ <: String] before skolemization, which captures (get it?) what Java does: + * enter the type paramers' bounds into the context when checking subtyping/type equality of existential types + * + * (Also tried doing this once during class file parsing or when creating the existential type, + * but that causes cyclic errors because it happens too early.) + */ + private def sharpenQuantifierBounds(): Unit = { + if (underlying.typeSymbol.isJavaDefined && quantified == underlying.typeArgs.map(_.typeSymbol)) { + val tpars = underlying.typeSymbol.typeParams + debuglog(s"sharpen bounds: $this | ${underlying.typeArgs.map(_.typeSymbol)} <-- ${tpars.map(_.info)}") + + foreach2(quantified, tpars) { (quant, tparam) => + // TODO: check `tparam.info.substSym(tpars, quantified) <:< quant.info` instead (for some weird reason not working for test/t6169/ExistF) + // for now, crude approximation for the common case + if (quant.info.bounds.isEmptyBounds && !tparam.info.bounds.isEmptyBounds) { + // avoid creating cycles [pos/t2940] that consist of an existential quantifier's + // bounded by an existential type that unhygienically has that quantifier as its own quantifier + // (TODO: clone latter existential with fresh quantifiers -- not covering this case for now) + if ((existentialsInType(tparam.info) intersect quantified).isEmpty) + quant setInfo tparam.info.substSym(tpars, quantified) + } + } + } + + _sharpenQuantifierBounds = false + } + private[this] var _sharpenQuantifierBounds = true + + override def skolemizeExistential(owner: Symbol, origin: AnyRef) = { + // do this here because it's quite close to what Java does: + // when checking subtyping/type equality, enter constraints + // derived from the existentially quantified type into the typing environment + // (aka \Gamma, which tracks types for variables and constraints/kinds for types) + // as a nice bonus, delaying this until we need it avoids cyclic errors + if (_sharpenQuantifierBounds) sharpenQuantifierBounds + deriveType(quantified, tparam => (owner orElse tparam.owner).newExistentialSkolem(tparam, origin))(underlying) + } private def wildcardArgsString(qset: Set[Symbol], args: List[Type]): List[String] = args map { case TypeRef(_, sym, _) if (qset contains sym) => diff --git a/test/files/pos/t6169/Exist.java b/test/files/pos/t6169/Exist.java new file mode 100644 index 0000000000..dfc6b36b33 --- /dev/null +++ b/test/files/pos/t6169/Exist.java @@ -0,0 +1,4 @@ +public class Exist { + // java helpfully re-interprets Exist as Exist + public Exist foo() { throw new RuntimeException(); } +} \ No newline at end of file diff --git a/test/files/pos/t6169/ExistF.java b/test/files/pos/t6169/ExistF.java new file mode 100644 index 0000000000..70fabd74cf --- /dev/null +++ b/test/files/pos/t6169/ExistF.java @@ -0,0 +1,4 @@ +public class ExistF> { + // java helpfully re-interprets ExistF as ExistF> + public ExistF foo() { throw new RuntimeException(); } +} \ No newline at end of file diff --git a/test/files/pos/t6169/ExistIndir.java b/test/files/pos/t6169/ExistIndir.java new file mode 100644 index 0000000000..e66d1698c4 --- /dev/null +++ b/test/files/pos/t6169/ExistIndir.java @@ -0,0 +1,4 @@ +public class ExistIndir { + // java helpfully re-interprets ExistIndir as ExistIndir + public ExistIndir foo() { throw new RuntimeException(); } +} diff --git a/test/files/pos/t6169/OP.java b/test/files/pos/t6169/OP.java new file mode 100644 index 0000000000..15e4c5640f --- /dev/null +++ b/test/files/pos/t6169/OP.java @@ -0,0 +1 @@ +public abstract class OP { } diff --git a/test/files/pos/t6169/Skin.java b/test/files/pos/t6169/Skin.java new file mode 100644 index 0000000000..780de1ee09 --- /dev/null +++ b/test/files/pos/t6169/Skin.java @@ -0,0 +1 @@ +public interface Skin { } diff --git a/test/files/pos/t6169/Skinnable.java b/test/files/pos/t6169/Skinnable.java new file mode 100644 index 0000000000..f91eaa30d8 --- /dev/null +++ b/test/files/pos/t6169/Skinnable.java @@ -0,0 +1,3 @@ +public interface Skinnable { + OP> skinProperty(); +} diff --git a/test/files/pos/t6169/skinnable.scala b/test/files/pos/t6169/skinnable.scala new file mode 100644 index 0000000000..3ba2734526 --- /dev/null +++ b/test/files/pos/t6169/skinnable.scala @@ -0,0 +1,14 @@ +object ObjectProperty { + implicit def jfxObjectProperty2sfx[T](p: OP[T]) = new ObjectProperty[T](p) +} + +class ObjectProperty[T](val delegate: OP[T]) + +trait TestWildcardBoundInference { + def delegate: Skinnable + def skin: ObjectProperty[Skin[_ /* inferred: <: Skinnable */]] = ObjectProperty.jfxObjectProperty2sfx(delegate.skinProperty) + skin: ObjectProperty[Skin[_ <: Skinnable]] + + def skinCheckInference = delegate.skinProperty + skinCheckInference: ObjectProperty[Skin[_ <: Skinnable]] +} \ No newline at end of file diff --git a/test/files/pos/t6169/t6169.scala b/test/files/pos/t6169/t6169.scala new file mode 100644 index 0000000000..37f42619ca --- /dev/null +++ b/test/files/pos/t6169/t6169.scala @@ -0,0 +1,7 @@ +class Test { + class MyExist extends ExistF[MyExist] + // SI-8197, SI-6169: java infers the bounds of existentials, so we have to as well now that SI-1786 is fixed... + def stringy: Exist[_ <: String] = (new Exist[String]).foo + def fbounded: (ExistF[t] forSome {type t <: ExistF[t] }) = (new MyExist).foo + def indir: ExistIndir[_ <: String, _ <: String] = (new ExistIndir[String, String]).foo +} \ No newline at end of file -- cgit v1.2.3 From 715a39bdf6a4632ec09b74777975ee90068b4466 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 5 Feb 2014 12:34:10 -0800 Subject: SI-6169 more accurate check for raw java type encoded as existential --- src/reflect/scala/reflect/internal/Types.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index a49ecea57f..02f51c2562 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -2668,7 +2668,13 @@ trait Types * but that causes cyclic errors because it happens too early.) */ private def sharpenQuantifierBounds(): Unit = { - if (underlying.typeSymbol.isJavaDefined && quantified == underlying.typeArgs.map(_.typeSymbol)) { + /* Check that we're looking at rawToExistential's handiwork + * (`existentialAbstraction(eparams, typeRef(apply(pre), sym, eparams map (_.tpe)))`). + * We can't do this sharpening there because we'll run into cycles. + */ + def rawToExistentialCreatedMe = (quantified corresponds underlying.typeArgs){ (q, a) => q.tpe =:= a } + + if (underlying.typeSymbol.isJavaDefined && rawToExistentialCreatedMe) { val tpars = underlying.typeSymbol.typeParams debuglog(s"sharpen bounds: $this | ${underlying.typeArgs.map(_.typeSymbol)} <-- ${tpars.map(_.info)}") -- cgit v1.2.3 From 38162b564b8e0b9b500fdc0443cb9e1a423a3ba3 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 5 Feb 2014 12:45:29 -0800 Subject: SI-6169 initialize before .typeParams -- just in case --- src/reflect/scala/reflect/internal/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 02f51c2562..cdae3eb584 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -2675,7 +2675,7 @@ trait Types def rawToExistentialCreatedMe = (quantified corresponds underlying.typeArgs){ (q, a) => q.tpe =:= a } if (underlying.typeSymbol.isJavaDefined && rawToExistentialCreatedMe) { - val tpars = underlying.typeSymbol.typeParams + val tpars = underlying.typeSymbol.initialize.typeParams // TODO: is initialize needed? debuglog(s"sharpen bounds: $this | ${underlying.typeArgs.map(_.typeSymbol)} <-- ${tpars.map(_.info)}") foreach2(quantified, tpars) { (quant, tparam) => -- cgit v1.2.3