From 2fa2db784075dfb58cf507c45a948819ade8a6d4 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 10 Mar 2013 10:00:54 -0700 Subject: SI-7228, bug in weak subtyping. Another in the category of bugs which involve narrowing, widening, mediuming, dealiasing, weakening, normalizing, denormalizing, supernormalizing, subnormalizing, and double-bounded supersubnormalizing. This is probably not the ideal fix, but it is an improvement. --- .../scala/tools/nsc/typechecker/Implicits.scala | 23 +++---- .../scala/tools/nsc/typechecker/Typers.scala | 2 +- src/reflect/scala/reflect/internal/Types.scala | 4 +- .../scala/reflect/internal/tpe/TypeComparers.scala | 6 +- test/files/pos/t7228.scala | 75 ++++++++++++++++++++++ 5 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 test/files/pos/t7228.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 2331f82a58..29d4c8423b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -268,7 +268,7 @@ trait Implicits { */ object Function1 { val Sym = FunctionClass(1) - def unapply(tp: Type) = tp match { + def unapply(tp: Type) = tp baseType Sym match { case TypeRef(_, Sym, arg1 :: arg2 :: _) => Some((arg1, arg2)) case _ => None } @@ -431,10 +431,8 @@ trait Implicits { val start = if (Statistics.canEnable) Statistics.startTimer(matchesPtNanos) else null val result = normSubType(tp, pt) || isView && { pt match { - case TypeRef(_, Function1.Sym, arg1 :: arg2 :: Nil) => - matchesPtView(tp, arg1, arg2, undet) - case _ => - false + case Function1(arg1, arg2) => matchesPtView(tp, arg1, arg2, undet) + case _ => false } } if (Statistics.canEnable) Statistics.stopTimer(matchesPtNanos, start) @@ -576,20 +574,19 @@ trait Implicits { def fail(reason: String): SearchResult = failure(itree, reason) try { - val itree1 = - if (isView) { - val arg1 :: arg2 :: _ = pt.typeArgs + val itree1 = pt match { + case Function1(arg1, arg2) if isView => typed1( atPos(itree.pos)(Apply(itree, List(Ident("") setType approximate(arg1)))), EXPRmode, approximate(arg2) ) - } - else - typed1(itree, EXPRmode, wildPt) - - if (context.hasErrors) + case _ => typed1(itree, EXPRmode, wildPt) + } + if (context.hasErrors) { + log("implicit adapt failed: " + context.errBuffer.head.errMsg) return fail(context.errBuffer.head.errMsg) + } if (Statistics.canEnable) Statistics.incCounter(typedImplicits) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index eaf57cd39c..a110d6d15d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1133,7 +1133,7 @@ trait Typers extends Adaptations with Tags { return typedPos(tree.pos, mode, pt) { Block(List(tree), Literal(Constant())) } - } else if (isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt)) { + } else if (isNumericValueClass(sym) && isNumericSubType(tree.tpe.dealiasWiden, pt)) { if (settings.warnNumericWiden.value) context.unit.warning(tree.pos, "implicit numeric widening") return typedPos(tree.pos, mode, pt) { diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index a6c5367425..b59732e595 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1217,7 +1217,7 @@ trait Types protected def rewrap(newtp: Type): Type = NotNullType(newtp) override def isNotNull: Boolean = true override def notNull = this - override def deconst: Type = underlying //todo: needed? + override def deconst: Type = underlying.deconst //todo: needed? override def safeToString: String = underlying.toString + " with NotNull" override def kind = "NotNullType" } @@ -1989,7 +1989,7 @@ trait Types assert(underlying.typeSymbol != UnitClass) override def isTrivial: Boolean = true override def isNotNull = value.value != null - override def deconst: Type = underlying + override def deconst: Type = underlying.deconst override def safeToString: String = underlying.toString + "(" + value.escapedStringValue + ")" override def kind = "ConstantType" diff --git a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala index 82321f61c2..2d499cf299 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala @@ -583,7 +583,7 @@ trait TypeComparers { def isWeakSubType(tp1: Type, tp2: Type) = - tp1.deconst.normalize match { + tp1.widen.normalize match { case TypeRef(_, sym1, _) if isNumericValueClass(sym1) => tp2.deconst.normalize match { case TypeRef(_, sym2, _) if isNumericValueClass(sym2) => @@ -609,8 +609,8 @@ trait TypeComparers { * (Even if the calls are to typeSymbolDirect.) */ def isNumericSubType(tp1: Type, tp2: Type): Boolean = ( - isNumericValueType(tp1) - && isNumericValueType(tp2) + isNumericValueType(tp1.dealiasWiden) + && isNumericValueType(tp2.dealias) && isNumericSubClass(tp1.typeSymbol, tp2.typeSymbol) ) diff --git a/test/files/pos/t7228.scala b/test/files/pos/t7228.scala new file mode 100644 index 0000000000..5d936f6529 --- /dev/null +++ b/test/files/pos/t7228.scala @@ -0,0 +1,75 @@ +object AdaptWithWeaklyConformantType { + implicit class D(d: Double) { def double = d*2 } + + val x1: Int = 1 + var x2: Int = 2 + val x3 = 3 + var x4 = 4 + final val x5 = 5 + final var x6 = 6 + + def f1 = x1.double + def f2 = x2.double + def f3 = x3.double + def f4 = x4.double + def f5 = x5.double + def f6 = x6.double +} + +object AdaptAliasWithWeaklyConformantType { + implicit class D(d: Double) { def double = d*2 } + type T = Int + + val x1: T = 1 + var x2: T = 2 + val x3 = (3: T) + var x4 = (4: T) + final val x5 = (5: T) + final var x6 = (6: T) + + def f1 = x1.double + def f2 = x2.double + def f3 = x3.double + def f4 = x4.double + def f5 = x5.double + def f6 = x6.double +} + +object AdaptToAliasWithWeaklyConformantType { + type U = Double + implicit class D(d: U) { def double = d*2 } + + val x1: Int = 1 + var x2: Int = 2 + val x3 = (3: Int) + var x4 = (4: Int) + final val x5 = (5: Int) + final var x6 = (6: Int) + + def f1 = x1.double + def f2 = x2.double + def f3 = x3.double + def f4 = x4.double + def f5 = x5.double + def f6 = x6.double +} + +object AdaptAliasToAliasWithWeaklyConformantType { + type U = Double + type T = Int + implicit class D(d: U) { def double = d*2 } + + val x1: T = 1 + var x2: T = 2 + val x3 = (3: T) + var x4 = (4: T) + final val x5 = (5: T) + final var x6 = (6: T) + + def f1 = x1.double + def f2 = x2.double + def f3 = x3.double + def f4 = x4.double + def f5 = x5.double + def f6 = x6.double +} -- cgit v1.2.3 From cb02c96bed1454e1c0702c529366f3c40d6bffd9 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 10 Mar 2013 10:00:54 -0700 Subject: Simplified the widening logic. Should speak for itself. Whenever someone changed @switch from an error to a warning, it broke all the tests which depended on the error. I added -Xfatal-warnings to a couple which needed it. And one of those tests was then failing, as it must now since we couldn't get away with what was being attempted, so I moved it to pending. --- .../scala/tools/nsc/typechecker/Namers.scala | 30 ++++++++++------------ test/files/pos/no-widen-locals.scala | 19 -------------- test/files/pos/switch-small.flags | 1 + test/pending/pos/no-widen-locals.flags | 1 + test/pending/pos/no-widen-locals.scala | 19 ++++++++++++++ 5 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 test/files/pos/no-widen-locals.scala create mode 100644 test/files/pos/switch-small.flags create mode 100644 test/pending/pos/no-widen-locals.flags create mode 100644 test/pending/pos/no-widen-locals.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 007c7c6a83..d5da4967be 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -805,23 +805,19 @@ trait Namers extends MethodSynthesis { case _ => false } - - val tpe1 = dropIllegalStarTypes(tpe.deconst) - val tpe2 = tpe1.widen - - // This infers Foo.type instead of "object Foo" - // See Infer#adjustTypeArgs for the polymorphic case. - if (tpe.typeSymbolDirect.isModuleClass) tpe1 - else if (sym.isVariable || sym.isMethod && !sym.hasAccessorFlag) - if (tpe2 <:< pt) tpe2 else tpe1 - else if (isHidden(tpe)) tpe2 - // In an attempt to make pattern matches involving method local vals - // compilable into switches, for a time I had a more generous condition: - // `if (sym.isFinal || sym.isLocal) tpe else tpe1` - // This led to issues with expressions like classOf[List[_]] which apparently - // depend on being deconst-ed here, so this is again the original: - else if (!sym.isFinal) tpe1 - else tpe + val shouldWiden = ( + !tpe.typeSymbolDirect.isModuleClass // Infer Foo.type instead of "object Foo" + && (tpe.widen <:< pt) // Don't widen our way out of conforming to pt + && ( sym.isVariable + || sym.isMethod && !sym.hasAccessorFlag + || isHidden(tpe) + ) + ) + dropIllegalStarTypes( + if (shouldWiden) tpe.widen + else if (sym.isFinal) tpe // "final val" allowed to retain constant type + else tpe.deconst + ) } /** Computes the type of the body in a ValDef or DefDef, and * assigns the type to the tpt's node. Returns the type. diff --git a/test/files/pos/no-widen-locals.scala b/test/files/pos/no-widen-locals.scala deleted file mode 100644 index 013e63f0a2..0000000000 --- a/test/files/pos/no-widen-locals.scala +++ /dev/null @@ -1,19 +0,0 @@ -// Worked from r23262 until that was reverted somewhere -// around r25016. -import annotation.switch - -object Test { - def f(x: Int) = { - val X1 = 5 - val X2 = 10 - val X3 = 15 - val X4 = 20 - - (x: @switch) match { - case X1 => 1 - case X2 => 2 - case X3 => 3 - case X4 => 4 - } - } -} diff --git a/test/files/pos/switch-small.flags b/test/files/pos/switch-small.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/files/pos/switch-small.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/pending/pos/no-widen-locals.flags b/test/pending/pos/no-widen-locals.flags new file mode 100644 index 0000000000..85d8eb2ba2 --- /dev/null +++ b/test/pending/pos/no-widen-locals.flags @@ -0,0 +1 @@ +-Xfatal-warnings diff --git a/test/pending/pos/no-widen-locals.scala b/test/pending/pos/no-widen-locals.scala new file mode 100644 index 0000000000..013e63f0a2 --- /dev/null +++ b/test/pending/pos/no-widen-locals.scala @@ -0,0 +1,19 @@ +// Worked from r23262 until that was reverted somewhere +// around r25016. +import annotation.switch + +object Test { + def f(x: Int) = { + val X1 = 5 + val X2 = 10 + val X3 = 15 + val X4 = 20 + + (x: @switch) match { + case X1 => 1 + case X2 => 2 + case X3 => 3 + case X4 => 4 + } + } +} -- cgit v1.2.3 From 9c5ea96b1c0fa45037a96e530b6ae71687a292d1 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 10 Mar 2013 10:11:28 -0700 Subject: Moved some numeric subtyping logic closer to center. Fixed bug in numeric widening related to continuations, which enabled simplifying isNumericSubType. --- .../scala/tools/nsc/typechecker/Typers.scala | 9 ++++--- .../scala/reflect/internal/Definitions.scala | 2 +- .../scala/reflect/internal/tpe/TypeComparers.scala | 31 +++++++++++++--------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a110d6d15d..c19d6b7a56 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1124,16 +1124,19 @@ trait Typers extends Adaptations with Tags { else { if (mode.inExprModeButNot(FUNmode)) { pt.dealias match { - case TypeRef(_, sym, _) => + // The <: Any requirement inhibits attempts to adapt continuation types + // to non-continuation types. + case TypeRef(_, sym, _) if tree.tpe <:< AnyClass.tpe => // note: was if (pt.typeSymbol == UnitClass) but this leads to a potentially // infinite expansion if pt is constant type () - if (sym == UnitClass && tree.tpe <:< AnyClass.tpe) { // (12) + if (sym == UnitClass) { // (12) if (settings.warnValueDiscard.value) context.unit.warning(tree.pos, "discarded non-Unit value") return typedPos(tree.pos, mode, pt) { Block(List(tree), Literal(Constant())) } - } else if (isNumericValueClass(sym) && isNumericSubType(tree.tpe.dealiasWiden, pt)) { + } + else if (isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt)) { if (settings.warnNumericWiden.value) context.unit.warning(tree.pos, "implicit numeric widening") return typedPos(tree.pos, mode, pt) { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index fe5a5c81e2..bfba81c654 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1131,7 +1131,7 @@ trait Definitions extends api.StandardDefinitions { /** Is type's symbol a numeric value class? */ def isNumericValueType(tp: Type): Boolean = tp match { case TypeRef(_, sym, _) => isNumericValueClass(sym) - case _ => false + case _ => false } // todo: reconcile with javaSignature!!! diff --git a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala index 2d499cf299..a03ab1610e 100644 --- a/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala +++ b/src/reflect/scala/reflect/internal/tpe/TypeComparers.scala @@ -5,6 +5,7 @@ package tpe import scala.collection.{ mutable } import Flags._ import util.Statistics +import scala.annotation.tailrec trait TypeComparers { self: SymbolTable => @@ -583,9 +584,9 @@ trait TypeComparers { def isWeakSubType(tp1: Type, tp2: Type) = - tp1.widen.normalize match { + tp1.dealiasWiden match { case TypeRef(_, sym1, _) if isNumericValueClass(sym1) => - tp2.deconst.normalize match { + tp2.deconst.dealias match { case TypeRef(_, sym2, _) if isNumericValueClass(sym2) => isNumericSubClass(sym1, sym2) case tv2 @ TypeVar(_, _) => @@ -594,7 +595,7 @@ trait TypeComparers { isSubType(tp1, tp2) } case tv1 @ TypeVar(_, _) => - tp2.deconst.normalize match { + tp2.deconst.dealias match { case TypeRef(_, sym2, _) if isNumericValueClass(sym2) => tv1.registerBound(tp2, isLowerBound = false, isNumericBound = true) case _ => @@ -604,14 +605,18 @@ trait TypeComparers { isSubType(tp1, tp2) } - /** The isNumericValueType tests appear redundant, but without them - * test/continuations-neg/function3.scala goes into an infinite loop. - * (Even if the calls are to typeSymbolDirect.) - */ - def isNumericSubType(tp1: Type, tp2: Type): Boolean = ( - isNumericValueType(tp1.dealiasWiden) - && isNumericValueType(tp2.dealias) - && isNumericSubClass(tp1.typeSymbol, tp2.typeSymbol) - ) - + def isNumericSubType(tp1: Type, tp2: Type) = ( + isNumericSubClass(primitiveBaseClass(tp1.dealiasWiden), primitiveBaseClass(tp2.dealias)) + ) + + /** If the given type has a primitive class among its base classes, + * the symbol of that class. Otherwise, NoSymbol. + */ + private def primitiveBaseClass(tp: Type): Symbol = { + @tailrec def loop(bases: List[Symbol]): Symbol = bases match { + case Nil => NoSymbol + case x :: xs => if (isPrimitiveValueClass(x)) x else loop(xs) + } + loop(tp.baseClasses) + } } -- cgit v1.2.3 From 6ef63e49f8d762ac02367225ee737ea93f52a738 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Wed, 13 Mar 2013 06:18:53 -0700 Subject: Fix it-never-happened performance regression. Diligent reviewer observed that a hot spot was possibly being made hotter. Reviewer's suggested remedy was a spectacular bust, but studious observation revealed the news lash that expensive methods are expensive and we should avoid calling them if we can. Put short-circuit test back in front of unapply call. Now the time spent in unapply is within a few percent. --- src/compiler/scala/tools/nsc/typechecker/Implicits.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 29d4c8423b..5b11adf127 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -573,15 +573,16 @@ trait Implicits { ) def fail(reason: String): SearchResult = failure(itree, reason) + def fallback = typed1(itree, EXPRmode, wildPt) try { - val itree1 = pt match { - case Function1(arg1, arg2) if isView => + val itree1 = if (!isView) fallback else pt match { + case Function1(arg1, arg2) => typed1( atPos(itree.pos)(Apply(itree, List(Ident("") setType approximate(arg1)))), EXPRmode, approximate(arg2) ) - case _ => typed1(itree, EXPRmode, wildPt) + case _ => fallback } if (context.hasErrors) { log("implicit adapt failed: " + context.errBuffer.head.errMsg) -- cgit v1.2.3