Skip to content

Fix type inference for HLists and HMaps #2045

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 16 commits into from
Mar 9, 2017
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
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ object Config {
/** If this flag is set, take the fast path when comparing same-named type-aliases and types */
final val fastPathForRefinedSubtype = true

/** If this flag is set, and we compute `T1 { X = S1 }` & `T2 { X = S2 }` as a new
* upper bound of a constrained parameter, try to align the refinements by computing
* `S1 =:= S2` (which might instantiate type parameters).
* This rule is contentious because it cuts the constraint set.
*
* For more info, see the comment in `TypeComparer#distributeAnd`.
*/
final val alignArgsInAnd = true

/** If this flag is set, higher-kinded applications are checked for validity
*/
final val checkHKApplications = false
Expand Down
6 changes: 0 additions & 6 deletions compiler/src/dotty/tools/dotc/core/Constraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,6 @@ abstract class Constraint extends Showable {
*/
def replace(param: PolyParam, tp: Type)(implicit ctx: Context): This

/** Narrow one of the bounds of type parameter `param`
* If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
* that `param >: bound`.
*/
def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This

/** Is entry associated with `pt` removable? This is the case if
* all type parameters of the entry are associated with type variables
* which have their `inst` fields set.
Expand Down
24 changes: 23 additions & 1 deletion compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ trait ConstraintHandling {
try op finally alwaysFluid = saved
}

/** If set, align arguments `S1`, `S2`when taking the glb
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
* Aligning means computing `S1 =:= S2` which may change the current constraint.
* See note in TypeComparer#distributeAnd.
*/
protected var homogenizeArgs = false

/** We are currently comparing polytypes. Used as a flag for
* optimization: when `false`, no need to do an expensive `pruneLambdaParams`
*/
Expand All @@ -64,14 +71,29 @@ trait ConstraintHandling {
}
if (Config.checkConstraintsSeparated)
assert(!occursIn(bound), s"$param occurs in $bound")
val c1 = constraint.narrowBound(param, bound, isUpper)
val newBound = narrowedBound(param, bound, isUpper)
val c1 = constraint.updateEntry(param, newBound)
(c1 eq constraint) || {
constraint = c1
val TypeBounds(lo, hi) = constraint.entry(param)
isSubType(lo, hi)
}
}

/** Narrow one of the bounds of type parameter `param`
* If `isUpper` is true, ensure that `param <: `bound`, otherwise ensure
* that `param >: bound`.
*/
def narrowedBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): TypeBounds = {
val oldBounds @ TypeBounds(lo, hi) = constraint.nonParamBounds(param)
val saved = homogenizeArgs
homogenizeArgs = Config.alignArgsInAnd
try
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
else oldBounds.derivedTypeBounds(lo | bound, hi)
finally homogenizeArgs = saved
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this state be stored in Context instead?

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 think this would make the logic more complicated.

}

protected def addUpperBound(param: PolyParam, bound: Type): Boolean = {
def description = i"constraint $param <: $bound to\n$constraint"
if (bound.isRef(defn.NothingClass) && ctx.typerState.isGlobalCommittable) {
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ class Definitions {
enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
List(AnyClass.typeRef), EmptyScope)
def SingletonType = SingletonClass.typeRef

lazy val SeqType: TypeRef = ctx.requiredClassRef("scala.collection.Seq")
def SeqClass(implicit ctx: Context) = SeqType.symbol.asClass
Expand Down
8 changes: 0 additions & 8 deletions compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
updateEntry(p1, p1Bounds).replace(p2, p1)
}

def narrowBound(param: PolyParam, bound: Type, isUpper: Boolean)(implicit ctx: Context): This = {
val oldBounds @ TypeBounds(lo, hi) = nonParamBounds(param)
val newBounds =
if (isUpper) oldBounds.derivedTypeBounds(lo, hi & bound)
else oldBounds.derivedTypeBounds(lo | bound, hi)
updateEntry(param, newBounds)
}

// ---------- Removals ------------------------------------------------------------

/** A new constraint which is derived from this constraint by removing
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ object StdNames {
val EMPTY: N = ""
val EMPTY_PACKAGE: N = Names.EMPTY_PACKAGE.toString
val EVIDENCE_PARAM_PREFIX: N = "evidence$"
val DEP_PARAM_PREFIX = "<param>"
val EXCEPTION_RESULT_PREFIX: N = "exceptionResult"
val EXPAND_SEPARATOR: N = "$$"
val IMPL_CLASS_SUFFIX: N = "$class"
Expand Down
33 changes: 19 additions & 14 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1312,23 +1312,28 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
case tp1: RefinedType =>
tp2 match {
case tp2: RefinedType if tp1.refinedName == tp2.refinedName =>
// Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }`, if `S1 =:= S2`
// (possibly by instantiating type parameters), rewrite to `T1 & T2 { X = S1 }`.
// Otherwise rewrite to `T1 & T2 { X B }` where `B` is the conjunction of
// the bounds of `X` in `T1` and `T2`.
// The first rule above is contentious because it cuts the constraint set.
// But without it we would replace the two aliases by
// `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird and is probably
// not what's intended.
// Given two refinements `T1 { X = S1 }` and `T2 { X = S2 }` rewrite to
// `T1 & T2 { X B }` where `B` is the conjunction of the bounds of `X` in `T1` and `T2`.
//
// However, if `homogenizeArgs` is set, and both aliases `X = Si` are
// nonvariant, and `S1 =:= S2` (possibly by instantiating type parameters),
// rewrite instead to `T1 & T2 { X = S1 }`. This rule is contentious because
// it cuts the constraint set. On the other hand, without it we would replace
// the two aliases by `T { X >: S1 | S2 <: S1 & S2 }`, which looks weird
// and is probably not what's intended.
val rinfo1 = tp1.refinedInfo
val rinfo2 = tp2.refinedInfo
val parent = tp1.parent & tp2.parent
val rinfo =
if (rinfo1.isAlias && rinfo2.isAlias && isSameType(rinfo1, rinfo2))
rinfo1
else
rinfo1 & rinfo2
tp1.derivedRefinedType(parent, tp1.refinedName, rinfo)

def isNonvariantAlias(tp: Type) = tp match {
case tp: TypeAlias => tp.variance == 0
case _ => false
}
if (homogenizeArgs &&
isNonvariantAlias(rinfo1) && isNonvariantAlias(rinfo2))
isSameType(rinfo1, rinfo2) // establish new constraint

tp1.derivedRefinedType(parent, tp1.refinedName, rinfo1 & rinfo2)
case _ =>
NoType
}
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,10 @@ object Types {
}
else newLikeThis(prefix)
}
else newLikeThis(prefix)
else prefix match {
case _: WildcardType => WildcardType
case _ => newLikeThis(prefix)
}

/** Create a NamedType of the same kind as this type, but with a new prefix.
*/
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package printing
import core._
import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, Denotations._
import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation
import TypeApplications.AppliedType
import StdNames.{nme, tpnme}
import ast.Trees._, ast._
import typer.Implicits._
Expand Down Expand Up @@ -119,10 +120,11 @@ class PlainPrinter(_ctx: Context) extends Printer {
}

/** The longest sequence of refinement types, starting at given type
* and following parents.
* and following parents, but stopping at applied types.
*/
private def refinementChain(tp: Type): List[Type] =
tp :: (tp match {
case AppliedType(_, _) => Nil
case tp: RefinedType => refinementChain(tp.parent.stripTypeVar)
case _ => Nil
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,7 @@ object messages {
|
|You may want to create an anonymous class extending ${cls.name} with
| ${s"class ${cls.name} { }"}
|
|
|or add a companion object with
| ${s"object ${cls.name} extends ${cls.name}"}
|
Expand Down
25 changes: 11 additions & 14 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,15 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
protected def init() = methType match {
case methType: MethodType =>
// apply the result type constraint, unless method type is dependent
if (!methType.isDependent) {
val savedConstraint = ctx.typerState.constraint
if (!constrainResult(methType.resultType, resultType))
if (ctx.typerState.isCommittable)
// defer the problem until after the application;
// it might be healed by an implicit conversion
assert(ctx.typerState.constraint eq savedConstraint)
else
fail(err.typeMismatchMsg(methType.resultType, resultType))
}
val resultApprox = resultTypeApprox(methType)
val savedConstraint = ctx.typerState.constraint
if (!constrainResult(resultApprox, resultType))
if (ctx.typerState.isCommittable)
// defer the problem until after the application;
// it might be healed by an implicit conversion
assert(ctx.typerState.constraint eq savedConstraint)
else
fail(err.typeMismatchMsg(methType.resultType, resultType))
// match all arguments with corresponding formal parameters
matchArgs(orderedArgs, methType.paramTypes, 0)
case _ =>
Expand Down Expand Up @@ -1100,10 +1099,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>

/** Drop any implicit parameter section */
def stripImplicit(tp: Type): Type = tp match {
case mt: ImplicitMethodType if !mt.isDependent =>
mt.resultType
// todo: make sure implicit method types are not dependent?
// but check test case in /tests/pos/depmet_implicit_chaining_zw.scala
case mt: ImplicitMethodType =>
resultTypeApprox(mt)
case pt: PolyType =>
pt.derivedPolyType(pt.paramNames, pt.paramBounds, stripImplicit(pt.resultType))
case _ =>
Expand Down
47 changes: 29 additions & 18 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,14 @@ object ProtoTypes {
case pt: FunProto =>
mt match {
case mt: MethodType =>
mt.isDependent || constrainResult(mt.resultType, pt.resultType)
constrainResult(resultTypeApprox(mt), pt.resultType)
case _ =>
true
}
case _: ValueTypeOrProto if !disregardProto(pt) =>
mt match {
case mt: MethodType =>
mt.isDependent || isCompatible(normalize(mt, pt), pt)
case _ =>
isCompatible(mt, pt)
}
case _: WildcardType =>
isCompatible(mt, pt)
isCompatible(normalize(mt, pt), pt)
case pt: WildcardType if pt.optBounds.exists =>
isCompatible(normalize(mt, pt), pt)
case _ =>
true
}
Expand Down Expand Up @@ -394,6 +389,26 @@ object ProtoTypes {
/** Same as `constrained(pt, EmptyTree)`, but returns just the created polytype */
def constrained(pt: PolyType)(implicit ctx: Context): PolyType = constrained(pt, EmptyTree)._1

/** Create a new polyparam that represents a dependent method parameter singleton */
def newDepPolyParam(tp: Type)(implicit ctx: Context): PolyParam = {
val poly = PolyType(ctx.freshName(nme.DEP_PARAM_PREFIX).toTypeName :: Nil, 0 :: Nil)(
pt => TypeBounds.upper(AndType(tp, defn.SingletonType)) :: Nil,
pt => defn.AnyType)
ctx.typeComparer.addToConstraint(poly, Nil)
PolyParam(poly, 0)
}

/** The result type of `mt`, where all references to parameters of `mt` are
* replaced by either wildcards (if typevarsMissContext) or polyparams.
*/
def resultTypeApprox(mt: MethodType)(implicit ctx: Context): Type =
if (mt.isDependent) {
def replacement(tp: Type) =
if (ctx.mode.is(Mode.TypevarsMissContext)) WildcardType else newDepPolyParam(tp)
mt.resultType.substParams(mt, mt.paramTypes.map(replacement))
}
else mt.resultType

/** The normalized form of a type
* - unwraps polymorphic types, tracking their parameters in the current constraint
* - skips implicit parameters; if result type depends on implicit parameter,
Expand All @@ -413,22 +428,18 @@ object ProtoTypes {
tp.widenSingleton match {
case poly: PolyType => normalize(constrained(poly).resultType, pt)
case mt: MethodType =>
if (mt.isImplicit)
if (mt.isDependent)
mt.resultType.substParams(mt, mt.paramTypes.map(Function.const(WildcardType)))
else mt.resultType
else
if (mt.isDependent) tp
else {
val rt = normalize(mt.resultType, pt)
if (mt.isImplicit) resultTypeApprox(mt)
else if (mt.isDependent) tp
else {
val rt = normalize(mt.resultType, pt)
pt match {
case pt: IgnoredProto => mt
case pt: ApplyingProto => mt.derivedMethodType(mt.paramNames, mt.paramTypes, rt)
case _ =>
val ft = defn.FunctionOf(mt.paramTypes, rt)
if (mt.paramTypes.nonEmpty || ft <:< pt) ft else rt
}
}
}
case et: ExprType => et.resultType
case _ => tp
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Disabled because we now get an unsafe instantiation error
object Sessions {
trait Session {
type Dual <: Session
Expand Down Expand Up @@ -40,5 +41,5 @@ object Sessions {
In{z: Int => System.out.println(z)
Stop()}}))

def myRun = addServer run addClient
def myRun = addServer run addClient // error: unsafe instantiation
}
5 changes: 5 additions & 0 deletions tests/pending/pos/depmet_implicit_oopsla_session_2.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Fails on line 70 with: no implicit argument of type Sessions.Session[
// | Sessions.In[Int, Sessions.In[Int, Sessions.Out[Int, Sessions.Stop]]]^
// |]#HasDual[Sessions.Out[Int, Sessions.Out[Int, Sessions.In[Int, Sessions.Stop]]]^
// | ] found for parameter evidence$1 of method runSession in object Sessions
// This could be related to existential types (the # essentially boils down to one).
object Sessions {
def ?[T <: AnyRef](implicit w: T): w.type = w

Expand Down
23 changes: 23 additions & 0 deletions tests/pos/t5070.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,26 @@ class Test {

implicitly[a.T](b(a)) // works
}


class ImplicitVsTypeAliasTezt {

class Monad[m[_]] {
type For[a] = _For[m, a]
implicit def toFor[a](m: m[a]): For[a] = throw new Error("todo") // lookup fails
// implicit def toFor[a](m: m[a]): _For[m, a] = throw new Error("todo") // fine.
}

trait _For[m[_], a] {
def map[b](p: a => b): m[b]
}

def useMonad[m[_], a](m: m[a])(implicit i: Monad[m]) = {
import i._

// value map is not a member of type parameter m[a]
for {
x <- m
} yield x.toString
}
}
19 changes: 19 additions & 0 deletions tests/pos/t5643.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
object TupledEvidenceTest {

abstract class TupledEvidence[M[_], T0] { type T = T0 }

implicit def witnessTuple2[M[_], T1, T2](implicit ev1: M[T1], ev2: M[T2]):
TupledEvidence[M, (T1, T2)] { type T = (T1, T2) } = sys.error("")

class GetResult[T]

implicit val getString: GetResult[String] = new GetResult[String]

implicit def getTuple[T](implicit w: TupledEvidence[GetResult, T]): GetResult[w.T] = sys.error("")

def f[T : GetResult] = ""

f[(String,String)](getTuple[(String, String)])

f[(String,String)]
}
8 changes: 8 additions & 0 deletions tests/run/HLists-nonvariant.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
1
A
true
true
HCons(1,HCons(A,HCons(true,HNil)))
1
A
true
Loading