Skip to content

Commit 8e5c9c4

Browse files
authored
Merge pull request #2045 from dotty-staging/fix-hlist-hmap
Fix type inference for HLists and HMaps
2 parents 6abaa10 + c7f1f35 commit 8e5c9c4

23 files changed

+430
-64
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ object Config {
7575
/** If this flag is set, take the fast path when comparing same-named type-aliases and types */
7676
final val fastPathForRefinedSubtype = true
7777

78+
/** If this flag is set, and we compute `T1 { X = S1 }` & `T2 { X = S2 }` as a new
79+
* upper bound of a constrained parameter, try to align the refinements by computing
80+
* `S1 =:= S2` (which might instantiate type parameters).
81+
* This rule is contentious because it cuts the constraint set.
82+
*
83+
* For more info, see the comment in `TypeComparer#distributeAnd`.
84+
*/
85+
final val alignArgsInAnd = true
86+
7887
/** If this flag is set, higher-kinded applications are checked for validity
7988
*/
8089
final val checkHKApplications = false

compiler/src/dotty/tools/dotc/core/Constraint.scala

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,6 @@ abstract class Constraint extends Showable {
111111
*/
112112
def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This
113113

114-
/** Narrow one of the bounds of type parameter `param`
115-
* If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
116-
* that `param >: bound`.
117-
*/
118-
def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This
119-
120114
/** Is entry associated with `pt` removable? This is the case if
121115
* all type parameters of the entry are associated with type variables
122116
* which have their `inst` fields set.

compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ trait ConstraintHandling {
4444
try op finally alwaysFluid = saved
4545
}
4646

47+
/** If set, align arguments `S1`, `S2`when taking the glb
48+
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
49+
* Aligning means computing `S1 =:= S2` which may change the current constraint.
50+
* See note in TypeComparer#distributeAnd.
51+
*/
52+
protected var homogenizeArgs = false
53+
4754
/** We are currently comparing polytypes. Used as a flag for
4855
* optimization: when `false`, no need to do an expensive `pruneLambdaParams`
4956
*/
@@ -64,14 +71,29 @@ trait ConstraintHandling {
6471
}
6572
if (Config.checkConstraintsSeparated)
6673
assert(!occursIn(bound), s"$param occurs in $bound")
67-
val c1 = constraint.narrowBound(param, bound, isUpper)
74+
val newBound = narrowedBound(param, bound, isUpper)
75+
val c1 = constraint.updateEntry(param, newBound)
6876
(c1 eq constraint) || {
6977
constraint = c1
7078
val TypeBounds(lo, hi) = constraint.entry(param)
7179
isSubType(lo, hi)
7280
}
7381
}
7482

83+
/** Narrow one of the bounds of type parameter `param`
84+
* If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
85+
* that `param >: bound`.
86+
*/
87+
def narrowedBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): TypeBounds = {
88+
val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param)
89+
val saved = homogenizeArgs
90+
homogenizeArgs = Config.alignArgsInAnd
91+
try
92+
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
93+
else oldBounds.derivedTypeBounds(lo | bound, hi)
94+
finally homogenizeArgs = saved
95+
}
96+
7597
protected def addUpperBound(param: PolyParam, bound: Type): Boolean = {
7698
def description = i"constraint $param <: $bound to\n$constraint"
7799
if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) {

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ class Definitions {
355355
enterCompleteClassSymbol(
356356
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
357357
List(AnyClass.typeRef), EmptyScope)
358+
def SingletonType = SingletonClass.typeRef
358359

359360
lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq")
360361
def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -354,14 +354,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
354354
updateEntry(p1, p1Bounds).replace(p2, p1)
355355
}
356356

357-
def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = {
358-
val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param)
359-
val newBounds =
360-
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
361-
else oldBounds.derivedTypeBounds(lo | bound, hi)
362-
updateEntry(param, newBounds)
363-
}
364-
365357
// ---------- Removals ------------------------------------------------------------
366358

367359
/** A new constraint which is derived from this constraint by removing

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ object StdNames {
9797
val EMPTY: N = ""
9898
val EMPTY_PACKAGE: N = Names.EMPTY_PACKAGE.toString
9999
val EVIDENCE_PARAM_PREFIX: N = "evidence$"
100+
val DEP_PARAM_PREFIX = "<param>"
100101
val EXCEPTION_RESULT_PREFIX: N = "exceptionResult"
101102
val EXPAND_SEPARATOR: N = "$$"
102103
val IMPL_CLASS_SUFFIX: N = "$class"

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,23 +1300,28 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
13001300
case tp1: RefinedType =>
13011301
tp2 match {
13021302
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
1303-
// Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }`, if `S1 =:= S2`
1304-
// (possibly by instantiating type parameters), rewrite to `T1 & T2 { X = S1 }`.
1305-
// Otherwise rewrite to `T1 & T2 { X B }` where `B` is the conjunction of
1306-
// the bounds of `X` in `T1` and `T2`.
1307-
// The first rule above is contentious because it cuts the constraint set.
1308-
// But without it we would replace the two aliases by
1309-
// `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird and is probably
1310-
// not what's intended.
1303+
// Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }` rewrite to
1304+
// `T1 & T2 { X B }` where `B` is the conjunction of the bounds of `X` in `T1` and `T2`.
1305+
//
1306+
// However, if `homogenizeArgs` is set, and both aliases `X = Si` are
1307+
// nonvariant, and `S1 =:= S2` (possibly by instantiating type parameters),
1308+
// rewrite instead to `T1 & T2 { X = S1 }`. This rule is contentious because
1309+
// it cuts the constraint set. On the other hand, without it we would replace
1310+
// the two aliases by `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird
1311+
// and is probably not what's intended.
13111312
val rinfo1 = tp1.refinedInfo
13121313
val rinfo2 = tp2.refinedInfo
13131314
val parent = tp1.parent & tp2.parent
1314-
val rinfo =
1315-
if (rinfo1.isAlias && rinfo2.isAlias && isSameType(rinfo1, rinfo2))
1316-
rinfo1
1317-
else
1318-
rinfo1 & rinfo2
1319-
tp1.derivedRefinedType(parent, tp1.refinedName, rinfo)
1315+
1316+
def isNonvariantAlias(tp: Type) = tp match {
1317+
case tp: TypeAlias => tp.variance == 0
1318+
case _ => false
1319+
}
1320+
if (homogenizeArgs &&
1321+
isNonvariantAlias(rinfo1) && isNonvariantAlias(rinfo2))
1322+
isSameType(rinfo1, rinfo2) // establish new constraint
1323+
1324+
tp1.derivedRefinedType(parent, tp1.refinedName, rinfo1 & rinfo2)
13201325
case _ =>
13211326
NoType
13221327
}

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1681,7 +1681,10 @@ object Types {
16811681
}
16821682
else newLikeThis(prefix)
16831683
}
1684-
else newLikeThis(prefix)
1684+
else prefix match {
1685+
case _: WildcardType => WildcardType
1686+
case _ => newLikeThis(prefix)
1687+
}
16851688

16861689
/** Create a NamedType of the same kind as this type, but with a new prefix.
16871690
*/

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package printing
44
import core._
55
import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, Denotations._
66
import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation
7+
import TypeApplications.AppliedType
78
import StdNames.{nme, tpnme}
89
import ast.Trees._, ast._
910
import typer.Implicits._
@@ -119,10 +120,11 @@ class PlainPrinter(_ctx: Context) extends Printer {
119120
}
120121

121122
/** The longest sequence of refinement types, starting at given type
122-
* and following parents.
123+
* and following parents, but stopping at applied types.
123124
*/
124125
private def refinementChain(tp: Type): List[Type] =
125126
tp :: (tp match {
127+
case AppliedType(_, _) => Nil
126128
case tp: RefinedType => refinementChain(tp.parent.stripTypeVar)
127129
case _ => Nil
128130
})

compiler/src/dotty/tools/dotc/typer/Applications.scala

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -213,16 +213,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
213213
protected def init() = methType match {
214214
case methType: MethodType =>
215215
// apply the result type constraint, unless method type is dependent
216-
if (!methType.isDependent) {
217-
val savedConstraint = ctx.typerState.constraint
218-
if (!constrainResult(methType.resultType, resultType))
219-
if (ctx.typerState.isCommittable)
220-
// defer the problem until after the application;
221-
// it might be healed by an implicit conversion
222-
assert(ctx.typerState.constraint eq savedConstraint)
223-
else
224-
fail(err.typeMismatchMsg(methType.resultType, resultType))
225-
}
216+
val resultApprox = resultTypeApprox(methType)
217+
val savedConstraint = ctx.typerState.constraint
218+
if (!constrainResult(resultApprox, resultType))
219+
if (ctx.typerState.isCommittable)
220+
// defer the problem until after the application;
221+
// it might be healed by an implicit conversion
222+
assert(ctx.typerState.constraint eq savedConstraint)
223+
else
224+
fail(err.typeMismatchMsg(methType.resultType, resultType))
226225
// match all arguments with corresponding formal parameters
227226
matchArgs(orderedArgs, methType.paramTypes, 0)
228227
case _ =>
@@ -1100,10 +1099,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
11001099

11011100
/** Drop any implicit parameter section */
11021101
def stripImplicit(tp: Type): Type = tp match {
1103-
case mt: ImplicitMethodType if !mt.isDependent =>
1104-
mt.resultType
1105-
// todo: make sure implicit method types are not dependent?
1106-
// but check test case in /tests/pos/depmet_implicit_chaining_zw.scala
1102+
case mt: ImplicitMethodType =>
1103+
resultTypeApprox(mt)
11071104
case pt: PolyType =>
11081105
pt.derivedPolyType(pt.paramNames, pt.paramBounds, stripImplicit(pt.resultType))
11091106
case _ =>

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,19 +57,14 @@ object ProtoTypes {
5757
case pt: FunProto =>
5858
mt match {
5959
case mt: MethodType =>
60-
mt.isDependent || constrainResult(mt.resultType, pt.resultType)
60+
constrainResult(resultTypeApprox(mt), pt.resultType)
6161
case _ =>
6262
true
6363
}
6464
case _: ValueTypeOrProto if !disregardProto(pt) =>
65-
mt match {
66-
case mt: MethodType =>
67-
mt.isDependent || isCompatible(normalize(mt, pt), pt)
68-
case _ =>
69-
isCompatible(mt, pt)
70-
}
71-
case _: WildcardType =>
72-
isCompatible(mt, pt)
65+
isCompatible(normalize(mt, pt), pt)
66+
case pt: WildcardType if pt.optBounds.exists =>
67+
isCompatible(normalize(mt, pt), pt)
7368
case _ =>
7469
true
7570
}
@@ -394,6 +389,26 @@ object ProtoTypes {
394389
/** Same as `constrained(pt, EmptyTree)`, but returns just the created polytype */
395390
def constrained(pt: PolyType)(implicit ctx: Context): PolyType = constrained(pt, EmptyTree)._1
396391

392+
/** Create a new polyparam that represents a dependent method parameter singleton */
393+
def newDepPolyParam(tp: Type)(implicit ctx: Context): PolyParam = {
394+
val poly = PolyType(ctx.freshName(nme.DEP_PARAM_PREFIX).toTypeName :: Nil, 0 :: Nil)(
395+
pt => TypeBounds.upper(AndType(tp, defn.SingletonType)) :: Nil,
396+
pt => defn.AnyType)
397+
ctx.typeComparer.addToConstraint(poly, Nil)
398+
PolyParam(poly, 0)
399+
}
400+
401+
/** The result type of `mt`, where all references to parameters of `mt` are
402+
* replaced by either wildcards (if typevarsMissContext) or polyparams.
403+
*/
404+
def resultTypeApprox(mt: MethodType)(implicit ctx: Context): Type =
405+
if (mt.isDependent) {
406+
def replacement(tp: Type) =
407+
if (ctx.mode.is(Mode.TypevarsMissContext)) WildcardType else newDepPolyParam(tp)
408+
mt.resultType.substParams(mt, mt.paramTypes.map(replacement))
409+
}
410+
else mt.resultType
411+
397412
/** The normalized form of a type
398413
* - unwraps polymorphic types, tracking their parameters in the current constraint
399414
* - skips implicit parameters; if result type depends on implicit parameter,
@@ -413,22 +428,18 @@ object ProtoTypes {
413428
tp.widenSingleton match {
414429
case poly: PolyType => normalize(constrained(poly).resultType, pt)
415430
case mt: MethodType =>
416-
if (mt.isImplicit)
417-
if (mt.isDependent)
418-
mt.resultType.substParams(mt, mt.paramTypes.map(Function.const(WildcardType)))
419-
else mt.resultType
420-
else
421-
if (mt.isDependent) tp
422-
else {
423-
val rt = normalize(mt.resultType, pt)
431+
if (mt.isImplicit) resultTypeApprox(mt)
432+
else if (mt.isDependent) tp
433+
else {
434+
val rt = normalize(mt.resultType, pt)
424435
pt match {
425436
case pt: IgnoredProto => mt
426437
case pt: ApplyingProto => mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt)
427438
case _ =>
428439
val ft = defn.FunctionOf(mt.paramTypes, rt)
429440
if (mt.paramTypes.nonEmpty || ft <:< pt) ft else rt
430-
}
431441
}
442+
}
432443
case et: ExprType => et.resultType
433444
case _ => tp
434445
}

tests/pending/pos/depmet_implicit_oopsla_session_simpler.scala renamed to tests/disabled/pos/depmet_implicit_oopsla_session_simpler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Disabled because we now get an unsafe instantiation error
12
object Sessions {
23
trait Session {
34
type Dual <: Session
@@ -40,5 +41,5 @@ object Sessions {
4041
In{z: Int => System.out.println(z)
4142
Stop()}}))
4243

43-
def myRun = addServer run addClient
44+
def myRun = addServer run addClient // error: unsafe instantiation
4445
}

tests/pending/pos/depmet_implicit_oopsla_session_2.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Fails on line 70 with: no implicit argument of type Sessions.Session[
2+
// | Sessions.In[Int, Sessions.In[Int, Sessions.Out[Int, Sessions.Stop]]]^
3+
// |]#HasDual[Sessions.Out[Int, Sessions.Out[Int, Sessions.In[Int, Sessions.Stop]]]^
4+
// | ] found for parameter evidence$1 of method runSession in object Sessions
5+
// This could be related to existential types (the # essentially boils down to one).
16
object Sessions {
27
def ?[T <: AnyRef](implicit w: T): w.type = w
38

tests/pos/t5070.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,26 @@ class Test {
1313

1414
implicitly[a.T](b(a)) // works
1515
}
16+
17+
18+
class ImplicitVsTypeAliasTezt {
19+
20+
class Monad[m[_]] {
21+
type For[a] = _For[m, a]
22+
implicit def toFor[a](m: m[a]): For[a] = throw new Error("todo") // lookup fails
23+
// implicit def toFor[a](m: m[a]): _For[m, a] = throw new Error("todo") // fine.
24+
}
25+
26+
trait _For[m[_], a] {
27+
def map[b](p: a => b): m[b]
28+
}
29+
30+
def useMonad[m[_], a](m: m[a])(implicit i: Monad[m]) = {
31+
import i._
32+
33+
// value map is not a member of type parameter m[a]
34+
for {
35+
x <- m
36+
} yield x.toString
37+
}
38+
}

tests/pos/t5643.scala

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
object TupledEvidenceTest {
2+
3+
abstract class TupledEvidence[M[_], T0] { type T = T0 }
4+
5+
implicit def witnessTuple2[M[_], T1, T2](implicit ev1: M[T1], ev2: M[T2]):
6+
TupledEvidence[M, (T1, T2)] { type T = (T1, T2) } = sys.error("")
7+
8+
class GetResult[T]
9+
10+
implicit val getString: GetResult[String] = new GetResult[String]
11+
12+
implicit def getTuple[T](implicit w: TupledEvidence[GetResult, T]): GetResult[w.T] = sys.error("")
13+
14+
def f[T : GetResult] = ""
15+
16+
f[(String,String)](getTuple[(String, String)])
17+
18+
f[(String,String)]
19+
}

tests/run/HLists-nonvariant.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
1
2+
A
3+
true
4+
true
5+
HCons(1,HCons(A,HCons(true,HNil)))
6+
1
7+
A
8+
true

0 commit comments

Comments
 (0)