Skip to content

Fix #5976: Don't assume T <:< (=>T). #5981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 24 additions & 11 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 & tp2
try tp1 & tp2.widenExpr
catch {
case ex: Throwable =>
println(i"error for meet: $tp1 &&& $tp2, ${tp1.getClass}, ${tp2.getClass}")
Expand All @@ -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 {
Expand Down Expand Up @@ -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 */
Expand Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1756,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
Expand All @@ -1776,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 =>
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have some guarantee that the result of argType cannot be an ExprType here ? Even if we do, it seems more regular to widen both sides.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case we dropped in TypeComparer is where the RHS is an ExprType. => T was never a subtype of T.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but if both argType and formal are => T, then argOK used to return true but will return false now that we widen formal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I believe argTypes are never ExprTypes. They might be TermRefs with an underlying ExprType. But we skip the ExprType when we do a widen on these. lub also does widen, not widenExpr. So I think we are good.

}

/** Subclass of Application for applicability tests with value argument types. */
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here.

})
Candidate.Conversion
else
Expand Down Expand Up @@ -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) =
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 10 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions tests/neg/i5976.scala
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions tests/run/i5976.scala
Original file line number Diff line number Diff line change
@@ -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))
}