From 728225fda4d9d304fa93fddbb1fb959f219ef41e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 Feb 2019 12:59:00 +0100 Subject: [PATCH 1/2] Fix #5976: Don't assume `T <:< =>T`. It's incorrect to assume `T <:< => T` since, taken as parameters, the erasure of the two types is different: `|T|` vs `Function1`. To compenate, we need to `widenExpr` in various places. --- .../dotty/tools/dotc/core/Denotations.scala | 2 +- .../dotty/tools/dotc/core/TypeComparer.scala | 5 +++-- .../dotty/tools/dotc/typer/Applications.scala | 4 ++-- .../dotty/tools/dotc/typer/Implicits.scala | 4 ++-- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 1 + .../dotty/tools/dotc/typer/TypeAssigner.scala | 4 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 20 +++++++++---------- tests/neg/i5976.scala | 7 +++++++ tests/run/i5976.scala | 10 ++++++++++ 10 files changed, 40 insertions(+), 19 deletions(-) create mode 100644 tests/neg/i5976.scala create mode 100644 tests/run/i5976.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 9b9969f1f5c8..c042957433e8 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -647,7 +647,7 @@ object Denotations { } case _ => - try tp1 & tp2 + try tp1.widenExpr & tp2.widenExpr catch { case ex: Throwable => println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}") diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 71e032ffb7eb..388c137a737f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -621,7 +621,8 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { // ()T <:< => T, since everything one can do with a => T one can // also do with a ()T by automatic () insertion. case tp1 @ MethodType(Nil) => isSubType(tp1.resultType, restpe2) - case _ => isSubType(tp1.widenExpr, restpe2) + case tp1 @ ExprType(restpe1) => isSubType(restpe1, restpe2) + case _ => fourthTry } compareExpr case tp2 @ TypeBounds(lo2, hi2) => @@ -1237,7 +1238,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { } def qualifies(m: SingleDenotation) = - isSubType(m.info, rinfo2) || matchAbstractTypeMember(m.info) + isSubType(m.info.widenExpr, rinfo2.widenExpr) || matchAbstractTypeMember(m.info) tp1.member(name) match { // inlined hasAltWith for performance case mbr: SingleDenotation => qualifies(mbr) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 4900a99cda64..7a590b65d205 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -119,7 +119,7 @@ object Applications { def lengthCompareTp = MethodType(List(defn.IntType), defn.IntType) def applyTp(elemTp: Type) = MethodType(List(defn.IntType), elemTp) def dropTp(elemTp: Type) = MethodType(List(defn.IntType), defn.SeqType.appliedTo(elemTp)) - def toSeqTp(elemTp: Type) = ExprType(defn.SeqType.appliedTo(elemTp)) + def toSeqTp(elemTp: Type) = defn.SeqType.appliedTo(elemTp) // the result type of `def apply(i: Int): T` val elemTp = getTp.member(nme.apply).suchThat(_.info <:< applyTp(WildcardType)).info.resultType @@ -603,7 +603,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * argument trees. */ class ApplicableToTreesDirectly(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context) extends ApplicableToTrees(methRef, targs, args, resultType)(ctx) { - override def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) <:< formal + override def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) <:< formal.widenExpr } /** Subclass of Application for applicability tests with value argument types. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index a5aca1b408d8..783bd007a58d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -98,7 +98,7 @@ object Implicits { else if (mt.paramInfos.lengthCompare(1) == 0 && { var formal = widenSingleton(mt.paramInfos.head) if (approx) formal = wildApprox(formal) - ctx.test(implicit ctx => argType relaxed_<:< formal) + ctx.test(implicit ctx => argType relaxed_<:< formal.widenExpr) }) Candidate.Conversion else @@ -1039,7 +1039,7 @@ trait Implicits { self: Typer => val locked = ctx.typerState.ownedVars val adapted = if (argument.isEmpty) - adapt(generated, pt, locked) + adapt(generated, pt.widenExpr, locked) else { val untpdGenerated = untpd.TypedSplice(generated) def tryConversion(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index dc5401f85852..6744647d97f1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1215,7 +1215,7 @@ class Namer { typer: Typer => var rhsCtx = ctx.addMode(Mode.InferringReturnType) if (sym.isInlineMethod) rhsCtx = rhsCtx.addMode(Mode.InlineableBody) - def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe + def rhsType = typedAheadExpr(mdef.rhs, (inherited orElse rhsProto).widenExpr)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. val deskolemize = new ApproximatingTypeMap { diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 560cb400eb1c..e06585629ef4 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -31,6 +31,7 @@ object ProtoTypes { * 2. `pt` is by name parameter type, and `tp` is compatible with its underlying type * 3. there is an implicit conversion from `tp` to `pt`. * 4. `tp` is a numeric subtype of `pt` (this case applies even if implicit conversions are disabled) + * If `pt` is a by-name type, we compare against the underlying type instead. */ def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean = (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index f171440b73bd..222db7012d53 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -54,7 +54,9 @@ trait TypeAssigner { required = EmptyFlagConjunction, excluded = Private) .suchThat(decl.matches(_)) val inheritedInfo = inherited.info - if (inheritedInfo.exists && decl.info <:< inheritedInfo && !(inheritedInfo <:< decl.info)) { + if (inheritedInfo.exists && + decl.info.widenExpr <:< inheritedInfo.widenExpr && + !(inheritedInfo.widenExpr <:< decl.info.widenExpr)) { val r = RefinedType(parent, decl.name, decl.info) typr.println(i"add ref $parent $decl --> " + r) r diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d3664fab5270..c5c98812bf53 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -734,7 +734,7 @@ class Typer extends Namer fullyDefinedType(tree.tpe, "block", tree.span) var avoidingType = avoid(tree.tpe, localSyms) val ptDefined = isFullyDefined(pt, ForceDegree.none) - if (ptDefined && !(avoidingType <:< pt)) avoidingType = pt + if (ptDefined && !(avoidingType.widenExpr <:< pt)) avoidingType = pt val tree1 = ascribeType(tree, avoidingType) assert(ptDefined || noLeaks(tree1) || tree1.tpe.isErroneous, // `ptDefined` needed because of special case of anonymous classes @@ -954,7 +954,7 @@ class Typer extends Namer (defn.isProductSubType(formal) || formal.derivesFrom(defn.PairClass)) && productSelectorTypes(formal, tree.sourcePos).corresponds(params) { (argType, param) => - param.tpt.isEmpty || argType <:< typedAheadType(param.tpt).tpe + param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe } } @@ -1482,7 +1482,7 @@ class Typer extends Namer val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match { case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe - case rhs => typedExpr(rhs, tpt1.tpe) + case rhs => typedExpr(rhs, tpt1.tpe.widenExpr) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) @@ -1549,7 +1549,7 @@ class Typer extends Namer } } if (sym.isInlineMethod) rhsCtx = rhsCtx.addMode(Mode.InlineableBody) - val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) + val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx) if (sym.isInlineMethod) PrepareInlineable.registerInlineInfo(sym, ddef.rhs, _ => rhs1) @@ -2310,7 +2310,7 @@ class Typer extends Namer } private def adapt1(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = { - assert(pt.exists) + assert(pt.exists && !pt.isInstanceOf[ExprType]) def methodStr = err.refStr(methPart(tree).tpe) def readapt(tree: Tree)(implicit ctx: Context) = adapt(tree, pt, locked) @@ -2904,11 +2904,11 @@ class Typer extends Namer methType.isImplicit && pt.isContextual // for a transition allow `with` arguments for regular implicit parameters /** Check that `tree == x: pt` is typeable. Used when checking a pattern - * against a selector of type `pt`. This implementation accounts for - * user-defined definitions of `==`. - * - * Overwritten to no-op in ReTyper. - */ + * against a selector of type `pt`. This implementation accounts for + * user-defined definitions of `==`. + * + * Overwritten to no-op in ReTyper. + */ protected def checkEqualityEvidence(tree: tpd.Tree, pt: Type)(implicit ctx: Context) : Unit = { tree match { case _: RefTree | _: Literal diff --git a/tests/neg/i5976.scala b/tests/neg/i5976.scala new file mode 100644 index 000000000000..8a9c29b85ae1 --- /dev/null +++ b/tests/neg/i5976.scala @@ -0,0 +1,7 @@ +object Test { + def f(i: => Int) = i + i + val res = List(42).map(f) // error + + val g: (=> Int) => Int = f + val h: Int => Int = g // error +} \ No newline at end of file diff --git a/tests/run/i5976.scala b/tests/run/i5976.scala new file mode 100644 index 000000000000..6a46aa6d41b4 --- /dev/null +++ b/tests/run/i5976.scala @@ -0,0 +1,10 @@ +object Test extends App { + def f(i: => Int) = i + i + implicit def ups(f: ((=>Int) => Int)): (Int => Int) = x => f(x) + val res = List(42).map(f) + + val g: (=> Int) => Int = f + val h: Int => Int = g + + assert(res == List(84)) +} \ No newline at end of file From 5de8dae1b2a5e167f79ed434c23fe821a1f85cf1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 24 Feb 2019 13:30:09 +0100 Subject: [PATCH 2/2] Don't merge `T` and `=> T` in lubs and glbs Align lub/glb with the changes to subtyping. --- .../dotty/tools/dotc/core/Denotations.scala | 35 +++++++++++++------ .../dotty/tools/dotc/core/TypeComparer.scala | 9 +++-- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index c042957433e8..ccff9fd1c3c2 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -518,8 +518,8 @@ object Denotations { val info1 = denot1.info val info2 = denot2.info val sameSym = sym1 eq sym2 - if (sameSym && (info1 frozen_<:< info2)) denot2 - else if (sameSym && (info2 frozen_<:< info1)) denot1 + if (sameSym && (info1.widenExpr frozen_<:< info2.widenExpr)) denot2 + else if (sameSym && (info2.widenExpr frozen_<:< info1.widenExpr)) denot1 else { val jointSym = if (sameSym) sym1 @@ -586,9 +586,11 @@ object Denotations { (for ((name1, name2, idx) <- (tp1.paramNames, tp2.paramNames, tp1.paramNames.indices).zipped) yield if (name1 == name2) name1 else tp1.companion.syntheticParamName(idx)).toList - /** Normally, `tp1 & tp2`. Special cases for matching methods and classes, with - * the possibility of raising a merge error. - */ + /** Normally, `tp1 & tp2`. + * Special cases for matching methods and classes, with + * the possibility of raising a merge error. + * Special handling of ExprTypes, where mixed intersections widen the ExprType away. + */ def infoMeet(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol, safeIntersection: Boolean)(implicit ctx: Context): Type = { if (tp1 eq tp2) tp1 else tp1 match { @@ -645,9 +647,13 @@ object Denotations { case _ => mergeConflict(sym1, sym2, tp1, tp2) } - + case ExprType(rtp1) => + tp2 match { + case ExprType(rtp2) => ExprType(rtp1 & rtp2) + case _ => rtp1 & tp2 + } case _ => - try tp1.widenExpr & tp2.widenExpr + try tp1 & tp2.widenExpr catch { case ex: Throwable => println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}") @@ -656,9 +662,11 @@ object Denotations { } } - /** Normally, `tp1 | tp2`. Special cases for matching methods and classes, with - * the possibility of raising a merge error. - */ + /** Normally, `tp1 | tp2`. + * Special cases for matching methods and classes, with + * the possibility of raising a merge error. + * Special handling of ExprTypes, where mixed unions widen the ExprType away. + */ def infoJoin(tp1: Type, tp2: Type, sym1: Symbol, sym2: Symbol)(implicit ctx: Context): Type = tp1 match { case tp1: TypeBounds => tp2 match { @@ -697,8 +705,13 @@ object Denotations { case _ => mergeConflict(sym1, sym2, tp1, tp2) } + case ExprType(rtp1) => + tp2 match { + case ExprType(rtp2) => ExprType(rtp1 | rtp2) + case _ => rtp1 | tp2 + } case _ => - tp1 | tp2 + tp1 | tp2.widenExpr } /** A non-overloaded denotation */ diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 388c137a737f..514c20b688dd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1757,7 +1757,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { case ExprType(rt2) => ExprType(rt1 & rt2) case _ => - rt1 & tp2 + NoType } case tp1: TypeVar if tp1.isInstantiated => tp1.underlying & tp2 @@ -1777,7 +1777,12 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { */ private def distributeOr(tp1: Type, tp2: Type): Type = tp1 match { case ExprType(rt1) => - ExprType(rt1 | tp2.widenExpr) + tp2 match { + case ExprType(rt2) => + ExprType(rt1 | rt2) + case _ => + NoType + } case tp1: TypeVar if tp1.isInstantiated => tp1.underlying | tp2 case tp1: AnnotatedType if !tp1.isRefining =>