Skip to content

Avoid forcing lambda parameter types in overloading resolution #6132

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 8 commits into from
Mar 27, 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
17 changes: 6 additions & 11 deletions compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,16 @@ object messages {
else
""

val inferred =
if (pt == WildcardType) ""
else i"\nWhat I could infer was: $pt"

i"""Missing parameter type
|
|The argument types of an anonymous function must be fully known. (SLS 8.5)
|Expected type: $pt
|Missing type for parameter ${param.name}$ofFun"""
|I could not infer the type of the parameter ${param.name}$ofFun.$inferred"""
}

val explanation: String =
hl"""|Anonymous functions must define a type. For example, if you define a function like this one:
|
|${"val f = { case x: Int => x + 1 }"}
|
|Make sure you give it a type of what you expect to match and help the type inference system:
|
|${"val f: Any => Int = { case x: Int => x + 1 }"} """
val explanation: String = ""
}

case class WildcardOnTypeArgumentNotAllowedOnNew()(implicit ctx: Context)
Expand Down
72 changes: 35 additions & 37 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case funRef: TermRef =>
val app =
if (proto.allArgTypesAreCurrent())
new ApplyToTyped(tree, fun1, funRef, proto.typedArgs, pt)
new ApplyToTyped(tree, fun1, funRef, proto.unforcedTypedArgs, pt)
else
new ApplyToUntyped(tree, fun1, funRef, proto, pt)(argCtx(tree))
convertNewGenericArray(app.result)
Expand All @@ -857,7 +857,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
}

fun1.tpe match {
case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs).withType(err)
case err: ErrorType => cpy.Apply(tree)(fun1, proto.unforcedTypedArgs).withType(err)
case TryDynamicCallType => typedDynamicApply(tree, pt)
case _ =>
if (originalProto.isDropped) fun1
Expand Down Expand Up @@ -1604,7 +1604,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
if (isDetermined(alts2)) alts2
else {
pretypeArgs(alts2, pt)
narrowByTrees(alts2, pt.typedArgs, resultType)
narrowByTrees(alts2, pt.unforcedTypedArgs, resultType)
}
}

Expand Down Expand Up @@ -1665,7 +1665,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
else pt match {
case pt @ FunProto(_, resType: FunProto) =>
// try to narrow further with snd argument list
val advanced = advanceCandidates(pt.typedArgs.tpes)
val advanced = advanceCandidates(pt.unforcedTypedArgs.tpes)
resolveOverloaded(advanced.map(_._1), resType, Nil) // resolve with candidates where first params are stripped
.map(advanced.toMap) // map surviving result(s) back to original candidates
case _ =>
Expand Down Expand Up @@ -1697,40 +1697,38 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
private def pretypeArgs(alts: List[TermRef], pt: FunProto)(implicit ctx: Context): Unit = {
def recur(altFormals: List[List[Type]], args: List[untpd.Tree]): Unit = args match {
case arg :: args1 if !altFormals.exists(_.isEmpty) =>
def isUnknownParamType(t: untpd.Tree) = t match {
case ValDef(_, tpt, _) => tpt.isEmpty
case _ => false
}
val fn = untpd.functionWithUnknownParamType(arg)
if (fn.isDefined) {
def isUniform[T](xs: List[T])(p: (T, T) => Boolean) = xs.forall(p(_, xs.head))
val formalsForArg: List[Type] = altFormals.map(_.head)
def argTypesOfFormal(formal: Type): List[Type] =
formal match {
case defn.FunctionOf(args, result, isImplicit, isErased) => args
case defn.PartialFunctionOf(arg, result) => arg :: Nil
case _ => Nil
untpd.functionWithUnknownParamType(arg) match {
case Some(fn) =>
def isUniform[T](xs: List[T])(p: (T, T) => Boolean) = xs.forall(p(_, xs.head))
val formalsForArg: List[Type] = altFormals.map(_.head)
def argTypesOfFormal(formal: Type): List[Type] =
formal match {
case defn.FunctionOf(args, result, isImplicit, isErased) => args
case defn.PartialFunctionOf(arg, result) => arg :: Nil
case _ => Nil
}
val formalParamTypessForArg: List[List[Type]] =
formalsForArg.map(argTypesOfFormal)
if (formalParamTypessForArg.forall(_.nonEmpty) &&
isUniform(formalParamTypessForArg)((x, y) => x.length == y.length)) {
val commonParamTypes = formalParamTypessForArg.transpose.map(ps =>
// Given definitions above, for i = 1,...,m,
// ps(i) = List(p_i_1, ..., p_i_n) -- i.e. a column
// If all p_i_k's are the same, assume the type as formal parameter
// type of the i'th parameter of the closure.
if (isUniform(ps)(_ frozen_=:= _)) ps.head
else WildcardType)
def isPartial = // we should generate a partial function for the arg
fn.isInstanceOf[untpd.Match] &&
formalsForArg.exists(_.isRef(defn.PartialFunctionClass))
val commonFormal =
if (isPartial) defn.PartialFunctionOf(commonParamTypes.head, WildcardType)
else defn.FunctionOf(commonParamTypes, WildcardType)
overload.println(i"pretype arg $arg with expected type $commonFormal")
if (commonParamTypes.forall(isFullyDefined(_, ForceDegree.noBottom)))
pt.typedArg(arg, commonFormal)(ctx.addMode(Mode.ImplicitsEnabled))
}
val formalParamTypessForArg: List[List[Type]] =
formalsForArg.map(argTypesOfFormal)
if (formalParamTypessForArg.forall(_.nonEmpty) &&
isUniform(formalParamTypessForArg)((x, y) => x.length == y.length)) {
val commonParamTypes = formalParamTypessForArg.transpose.map(ps =>
// Given definitions above, for i = 1,...,m,
// ps(i) = List(p_i_1, ..., p_i_n) -- i.e. a column
// If all p_i_k's are the same, assume the type as formal parameter
// type of the i'th parameter of the closure.
if (isUniform(ps)(_ frozen_=:= _)) ps.head
else WildcardType)
def isPartial = // we should generate a partial function for the arg
fn.get.isInstanceOf[untpd.Match] &&
formalsForArg.exists(_.isRef(defn.PartialFunctionClass))
val commonFormal =
if (isPartial) defn.PartialFunctionOf(commonParamTypes.head, WildcardType)
else defn.FunctionOf(commonParamTypes, WildcardType)
overload.println(i"pretype arg $arg with expected type $commonFormal")
pt.typedArg(arg, commonFormal)(ctx.addMode(Mode.ImplicitsEnabled))
}
case None =>
}
recur(altFormals.map(_.tail), args1)
case _ =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ object ErrorReporting {
case _: WildcardType | _: IgnoredProto => ""
case tp => em" and expected result type $tp"
}
em"arguments (${tp.typedArgs.tpes}%, %)$result"
em"arguments (${tp.unforcedTypedArgs.tpes}%, %)$result"
case _ =>
em"expected type $tp"
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ class Namer { typer: Typer =>
}

/** Typecheck `tree` during completion using `typed`, and remember result in TypedAhead map */
def typedAheadImpl(tree: Tree, typed: untpd.Tree => tpd.Tree)(implicit ctx: Context): tpd.Tree = {
def typedAhead(tree: Tree, typed: untpd.Tree => tpd.Tree)(implicit ctx: Context): tpd.Tree = {
val xtree = expanded(tree)
xtree.getAttachment(TypedAhead) match {
case Some(ttree) => ttree
Expand All @@ -1090,10 +1090,10 @@ class Namer { typer: Typer =>
}

def typedAheadType(tree: Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree =
typedAheadImpl(tree, typer.typed(_, pt)(ctx retractMode Mode.PatternOrTypeBits addMode Mode.Type))
typedAhead(tree, typer.typed(_, pt)(ctx retractMode Mode.PatternOrTypeBits addMode Mode.Type))

def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree =
typedAheadImpl(tree, typer.typed(_, pt)(ctx retractMode Mode.PatternOrTypeBits))
typedAhead(tree, typer.typed(_, pt)(ctx retractMode Mode.PatternOrTypeBits))

def typedAheadAnnotation(tree: Tree)(implicit ctx: Context): tpd.Tree =
typedAheadExpr(tree, defn.AnnotationType)
Expand Down
72 changes: 27 additions & 45 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,6 @@ object ProtoTypes {
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.Empty

/** A map recording the typer states and constraints in which arguments stored in myTypedArg were typed */
var evalState: SimpleIdentityMap[untpd.Tree, (TyperState, Constraint)] = SimpleIdentityMap.Empty

/** The tupled version of this prototype, if it has been computed */
var tupled: Type = NoType

Expand Down Expand Up @@ -265,66 +262,51 @@ object ProtoTypes {

override def notApplied: Type = WildcardType

/** Forget the types of any arguments that have been typed producing a constraint
* - that is in a typer state that is not yet committed into the one of the current context `ctx`,
* - or that has been retracted from its typestate because oif a failed operation.
* This is necessary to avoid "orphan" TypeParamRefs that are referred to from
* type variables in the typed arguments, but that are not registered in the
* current constraint. Test cases are pos/t1756.scala and pos/i3538.scala.
* @return True if all arguments have types (in particular, no types were forgotten).
/** @return True if all arguments have types.
*/
def allArgTypesAreCurrent()(implicit ctx: Context): Boolean = {
state.evalState foreachBinding { (arg, tstateConstr) =>
if ((tstateConstr._1.uncommittedAncestor.constraint `ne` ctx.typerState.constraint) ||
tstateConstr._2.isRetracted) {
typr.println(i"need to invalidate $arg / ${state.typedArg(arg)}, ${tstateConstr._2}, current = ${ctx.typerState.constraint}")
state.typedArg = state.typedArg.remove(arg)
state.evalState = state.evalState.remove(arg)
}
}
def allArgTypesAreCurrent()(implicit ctx: Context): Boolean =
state.typedArg.size == args.length

private def isUndefined(tp: Type): Boolean = tp match {
case _: WildcardType => true
case defn.FunctionOf(args, result, _, _) => args.exists(isUndefined) || isUndefined(result)
case _ => false
}

private def cacheTypedArg(arg: untpd.Tree, typerFn: untpd.Tree => Tree, force: Boolean)(implicit ctx: Context): Tree = {
var targ = state.typedArg(arg)
if (targ == null) {
if (!force && untpd.functionWithUnknownParamType(arg).isDefined)
// If force = false, assume ? rather than reporting an error.
// That way we don't cause a "missing parameter" error in `typerFn(arg)`
targ = arg.withType(WildcardType)
else {
targ = typerFn(arg)
if (!ctx.reporter.hasUnreportedErrors) {
// FIXME: This can swallow warnings by updating the typerstate from a nested
// context that gets discarded later. But we do have to update the
// typerstate if there are no errors. If we also omitted the next two lines
// when warning were emitted, `pos/t1756.scala` would fail when run with -feature.
// It would produce an orphan type parameter for CI when pickling.
state.typedArg = state.typedArg.updated(arg, targ)
state.evalState = state.evalState.updated(arg, (ctx.typerState, ctx.typerState.constraint))
}
untpd.functionWithUnknownParamType(arg) match {
case Some(untpd.Function(args, _)) if !force =>
// If force = false, assume what we know about the parameter types rather than reporting an error.
// That way we don't cause a "missing parameter" error in `typerFn(arg)`
val paramTypes = args map {
case ValDef(_, tpt, _) if !tpt.isEmpty => typer.typedType(tpt).typeOpt
case _ => WildcardType
}
targ = arg.withType(defn.FunctionOf(paramTypes, WildcardType))
case Some(_) if !force =>
targ = arg.withType(WildcardType)
case _ =>
targ = typerFn(arg)
if (!ctx.reporter.hasUnreportedErrors)
state.typedArg = state.typedArg.updated(arg, targ)
}
}
targ
}

/** The typed arguments. This takes any arguments already typed using
* `typedArg` into account.
* @param force if true try to typecheck arguments even if they are functions
* with unknown parameter types - this will then cause a
* "missing parameter type" error
*/
private def typedArgs(force: Boolean): List[Tree] =
def unforcedTypedArgs: List[Tree] =
if (state.typedArgs.size == args.length) state.typedArgs
else {
val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force))
if (force || !args1.contains(WildcardType)) state.typedArgs = args1
val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force = false))
if (!args1.exists(arg => isUndefined(arg.tpe))) state.typedArgs = args1
args1
}

def typedArgs: List[Tree] = typedArgs(force = true)
def unforcedTypedArgs: List[Tree] = typedArgs(force = false)

/** Type single argument and remember the unadapted result in `myTypedArg`.
* used to avoid repeated typings of trees when backtracking.
*/
Expand Down Expand Up @@ -376,7 +358,7 @@ object ProtoTypes {
derivedFunProto(args, tm(resultType), typer)

def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T =
ta(ta.foldOver(x, typedArgs.tpes), resultType)
ta(ta.foldOver(x, unforcedTypedArgs.tpes), resultType)

override def deepenProto(implicit ctx: Context): FunProto = derivedFunProto(args, resultType.deepenProto, typer)

Expand All @@ -390,7 +372,7 @@ object ProtoTypes {
* [](args): resultType, where args are known to be typed
*/
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isContextual: Boolean)(implicit ctx: Context) extends FunProto(args, resultType)(typer, isContextual)(ctx) {
override def typedArgs: List[tpd.Tree] = args
override def unforcedTypedArgs: List[tpd.Tree] = args
override def withContext(ctx: Context): FunProtoTyped = this
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ class Typer extends Namer
templ1 = cpy.Template(templ)(parents = untpd.TypeTree(pt) :: Nil)
templ1.parents foreach {
case parent: RefTree =>
typedAheadImpl(parent, tree => inferTypeParams(typedType(tree), pt))
typedAhead(parent, tree => inferTypeParams(typedType(tree), pt))
case _ =>
}
val x = tpnme.ANON_CLASS
Expand Down Expand Up @@ -940,7 +940,7 @@ class Typer extends Namer
}
case _ =>
}
errorType(AnonymousFunctionMissingParamType(param, params, tree, pt), param.sourcePos)
errorType(AnonymousFunctionMissingParamType(param, params, tree, formal), param.sourcePos)
}

def protoFormal(i: Int): Type =
Expand Down
6 changes: 2 additions & 4 deletions compiler/test-resources/repl/errorThenValid
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
scala> val xs = scala.collection.mutable.ListBuffer[Int]
1 | val xs = scala.collection.mutable.ListBuffer[Int]
| ^
|Missing parameter type
| Missing parameter type
|
|The argument types of an anonymous function must be fully known. (SLS 8.5)
|Expected type: ?
|Missing type for parameter elems
| I could not infer the type of the parameter elems.
scala> val xs = scala.collection.mutable.ListBuffer[Int]()
val xs: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
Loading