Skip to content

Fix #7793: Handle context parameters in resolveOverloaded #8406

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 3 commits into from
Mar 3, 2020
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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ object desugar {
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
val app = Apply(nu, vparams.map(refOfDef))
vparams match {
case vparam :: _ if vparam.mods.is(Given) => app.setGivenApply()
case vparam :: _ if vparam.mods.is(Given) => app.setUsingApply()
case _ => app
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,8 @@ object Trees {
extends GenericApply[T] {
type ThisTree[-T >: Untyped] = Apply[T]

def isGivenApply = hasAttachment(untpd.ApplyGiven)
def setGivenApply() = { putAttachment(untpd.ApplyGiven, ()); this }
def isUsingApply = hasAttachment(untpd.ApplyGiven)
def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this }
}

/** fun[args] */
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3311,7 +3311,6 @@ object Types {
companion.eq(ContextualMethodType) ||
companion.eq(ErasedContextualMethodType)


def computeSignature(implicit ctx: Context): Signature = {
val params = if (isErasedMethod) Nil else paramInfos
resultSignature.prependTermParams(params, isJavaMethod)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ object Parsers {

def mkApply(fn: Tree, args: (List[Tree], Boolean)): Tree =
val res = Apply(fn, args._1)
if args._2 then res.setGivenApply()
if args._2 then res.setUsingApply()
res

val argumentExpr: () => Tree = () => exprInParens() match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
else
toTextLocal(fun)
~ "("
~ Str("using ").provided(app.isGivenApply && !homogenizedView)
~ Str("using ").provided(app.isUsingApply && !homogenizedView)
~ toTextGlobal(args, ", ")
~ ")"
case tree: TypeApply =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ object Erasure {
val Apply(fun, args) = tree
if (fun.symbol == defn.cbnArg)
typedUnadapted(args.head, pt)
else typedExpr(fun, FunProto(args, pt)(this, isGivenApply = false)) match {
else typedExpr(fun, FunProto(args, pt)(this, isUsingApply = false)) match {
case fun1: Apply => // arguments passed in prototype were already passed
fun1
case fun1 =>
Expand Down
75 changes: 43 additions & 32 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -833,7 +833,7 @@ trait Applications extends Compatibility {
def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {

def realApply(implicit ctx: Context): Tree = {
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isGivenApply)(argCtx(tree))
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isUsingApply)(argCtx(tree))
record("typedApply")
val fun1 = typedFunPart(tree.fun, originalProto)

Expand Down Expand Up @@ -1299,6 +1299,16 @@ trait Applications extends Compatibility {
}
}

/** Drop any implicit parameter section */
def stripImplicit(tp: Type)(using Context): Type = tp match {
case mt: MethodType if mt.isImplicitMethod =>
stripImplicit(resultTypeApprox(mt))
case pt: PolyType =>
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
case _ =>
tp
}

/** Compare owner inheritance level.
* @param sym1 The first owner
* @param sym2 The second owner
Expand Down Expand Up @@ -1466,16 +1476,6 @@ trait Applications extends Compatibility {
else tp
}

/** Drop any implicit parameter section */
def stripImplicit(tp: Type): Type = tp match {
case mt: MethodType if mt.isImplicitMethod =>
stripImplicit(resultTypeApprox(mt))
case pt: PolyType =>
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, stripImplicit(pt.resultType))
case _ =>
tp
}

def compareWithTypes(tp1: Type, tp2: Type) = {
val ownerScore = compareOwner(alt1.symbol.maybeOwner, alt2.symbol.maybeOwner)
def winsType1 = isAsSpecific(alt1, tp1, alt2, tp2)
Expand Down Expand Up @@ -1707,6 +1707,22 @@ trait Applications extends Compatibility {
case _ => arg
end normArg

/** Resolve overloading by mapping to a different problem where each alternative's
* type is mapped with `f`, alternatives with non-existing types are dropped, and the
* expected type is `pt`. Map the results back to the original alternatives.
*/
def resolveMapped(alts: List[TermRef], f: TermRef => Type, pt: Type): List[TermRef] =
val reverseMapping = alts.flatMap { alt =>
val t = f(alt)
if t.exists then
Some((TermRef(NoPrefix, alt.symbol.asTerm.copy(info = t)), alt))
else
None
}
val mapped = reverseMapping.map(_._1)
overload.println(i"resolve mapped: $mapped")
resolveOverloaded(mapped, pt, targs).map(reverseMapping.toMap)

val candidates = pt match {
case pt @ FunProto(args, resultType) =>
val numArgs = args.length
Expand Down Expand Up @@ -1747,6 +1763,15 @@ trait Applications extends Compatibility {
alts2
}

if pt.isUsingApply then
val alts0 = alts.filterConserve { alt =>
val mt = alt.widen.stripPoly
mt.isImplicitMethod || mt.isContextualMethod
}
if alts0 ne alts then return resolveOverloaded(alts0, pt, targs)
else if alts.exists(_.widen.stripPoly.isContextualMethod) then
return resolveMapped(alts, alt => stripImplicit(alt.widen), pt)

val alts1 = narrowBySize(alts)
//ctx.log(i"narrowed by size: ${alts1.map(_.symbol.showDcl)}%, %")
if isDetermined(alts1) then alts1
Expand Down Expand Up @@ -1783,32 +1808,20 @@ trait Applications extends Compatibility {
else compat
}

/** For each candidate `C`, a proxy termref paired with `C`.
* The proxy termref has as symbol a copy of the original candidate symbol,
* with an info that strips the first value parameter list away.
* @param argTypes The types of the arguments of the FunProto `pt`.
/** The type of alternative `alt` after instantiating its first parameter
* clause with `argTypes`.
*/
def advanceCandidates(argTypes: List[Type]): List[(TermRef, TermRef)] = {
def strippedType(tp: Type): Type = tp match {
def skipParamClause(argTypes: List[Type])(alt: TermRef): Type =
def skip(tp: Type): Type = tp match {
case tp: PolyType =>
val rt = strippedType(tp.resultType)
val rt = skip(tp.resultType)
if (rt.exists) tp.derivedLambdaType(resType = rt) else rt
case tp: MethodType =>
tp.instantiate(argTypes)
case _ =>
NoType
}
def cloneCandidate(cand: TermRef): List[(TermRef, TermRef)] = {
val strippedInfo = strippedType(cand.widen)
if (strippedInfo.exists) {
val sym = cand.symbol.asTerm.copy(info = strippedInfo)
(TermRef(NoPrefix, sym), cand) :: Nil
}
else Nil
}
overload.println(i"look at more params: ${candidates.head.symbol}: ${candidates.map(_.widen)}%, % with $pt, [$targs%, %]")
candidates.flatMap(cloneCandidate)
}
skip(alt.widen)

def resultIsMethod(tp: Type): Boolean = tp.widen.stripPoly match
case tp: MethodType => tp.resultType.isInstanceOf[MethodType]
Expand All @@ -1821,9 +1834,7 @@ trait Applications extends Compatibility {
deepPt match
case pt @ FunProto(_, resType: FunProto) =>
// try to narrow further with snd argument list
val advanced = advanceCandidates(pt.typedArgs().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
resolveMapped(candidates, skipParamClause(pt.typedArgs().tpes), resType)
case _ =>
// prefer alternatives that need no eta expansion
val noCurried = alts.filter(!resultIsMethod(_))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ object EtaExpansion extends LiftImpure {
if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam)
ids = ids.init :+ repeated(ids.last)
val app = Apply(lifted, ids)
if (mt.isContextualMethod) app.setGivenApply()
if (mt.isContextualMethod) app.setUsingApply()
val body = if (isLastApplication) app else PostfixOp(app, Ident(nme.WILDCARD))
val fn =
if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given))
Expand Down
14 changes: 7 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ object ProtoTypes {

trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto
trait FunOrPolyProto extends ProtoType { // common trait of PolyProto and FunProto
def isGivenApply: Boolean = false
def isUsingApply: Boolean = false
}

class FunProtoState {
Expand All @@ -251,7 +251,7 @@ object ProtoTypes {
* [](args): resultType
*/
case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer,
override val isGivenApply: Boolean, state: FunProtoState = new FunProtoState)(implicit val ctx: Context)
override val isUsingApply: Boolean, state: FunProtoState = new FunProtoState)(implicit val ctx: Context)
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
override def resultType(implicit ctx: Context): Type = resType

Expand All @@ -265,7 +265,7 @@ object ProtoTypes {

def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
else new FunProto(args, resultType)(typer, isGivenApply)
else new FunProto(args, resultType)(typer, isUsingApply)

/** @return True if all arguments have types.
*/
Expand Down Expand Up @@ -355,7 +355,7 @@ object ProtoTypes {
case pt: FunProto =>
pt
case _ =>
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, isGivenApply)
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, isUsingApply)
tupled
}

Expand Down Expand Up @@ -390,14 +390,14 @@ object ProtoTypes {

override def withContext(newCtx: Context): ProtoType =
if (newCtx `eq` ctx) this
else new FunProto(args, resType)(typer, isGivenApply, state)(newCtx)
else new FunProto(args, resType)(typer, isUsingApply, state)(newCtx)
}

/** A prototype for expressions that appear in function position
*
* [](args): resultType, where args are known to be typed
*/
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isGivenApply: Boolean)(implicit ctx: Context) extends FunProto(args, resultType)(typer, isGivenApply)(ctx) {
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isUsingApply: Boolean)(implicit ctx: Context) extends FunProto(args, resultType)(typer, isUsingApply)(ctx) {
override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(implicit ctx: Context): List[tpd.Tree] = args
override def withContext(ctx: Context): FunProtoTyped = this
}
Expand Down Expand Up @@ -444,7 +444,7 @@ object ProtoTypes {
}

class UnapplyFunProto(argType: Type, typer: Typer)(implicit ctx: Context) extends FunProto(
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, isGivenApply = false)
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, isUsingApply = false)

/** A prototype for expressions [] that are type-parameterized:
*
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ class Typer extends Namer
case _ =>
given nestedCtx as Context = ctx.fresh.setNewTyperState()
val protoArgs = args map (_ withType WildcardType)
val callProto = FunProto(protoArgs, WildcardType)(this, app.isGivenApply)
val callProto = FunProto(protoArgs, WildcardType)(this, app.isUsingApply)
val expr1 = typedExpr(expr, callProto)
if nestedCtx.reporter.hasErrors then NoType
else
Expand Down Expand Up @@ -2626,7 +2626,7 @@ class Typer extends Namer
errorTree(tree, NoMatchingOverload(altDenots, pt)(err))
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil
pt match {
case pt: FunOrPolyProto if !pt.isGivenApply =>
case pt: FunOrPolyProto if !pt.isUsingApply =>
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(noMatches)
case _ =>
Expand Down Expand Up @@ -2777,7 +2777,7 @@ class Typer extends Namer
}
tryEither {
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
if (wtp.isContextualMethod) app.setGivenApply()
if (wtp.isContextualMethod) app.setUsingApply()
typr.println(i"try with default implicit args $app")
typed(app, pt, locked)
} { (_, _) =>
Expand All @@ -2794,7 +2794,7 @@ class Typer extends Namer
}
}
pt.revealIgnored match {
case pt: FunProto if pt.isGivenApply =>
case pt: FunProto if pt.isUsingApply =>
// We can end up here if extension methods are called with explicit given arguments.
// See for instance #7119.
tree
Expand Down Expand Up @@ -3216,8 +3216,8 @@ class Typer extends Namer
* Overridden in `ReTyper`, where all applications are treated the same
*/
protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(implicit ctx: Context): Boolean =
methType.isContextualMethod == pt.isGivenApply ||
methType.isImplicitMethod && pt.isGivenApply // for a transition allow `with` arguments for regular implicit parameters
methType.isContextualMethod == pt.isUsingApply ||
methType.isImplicitMethod && pt.isUsingApply // 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
Expand Down
9 changes: 9 additions & 0 deletions tests/pos/i7793.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
trait Foo:
def g(f: Int => Int): Int = 1
def g(using String)(f: Int => String): String = "2"

@main def Test =
val m: Foo = ???
given String = "foo"
m.g(x => "2")
m.g(using summon[String])(x => "2")