Skip to content

Commit dc715ce

Browse files
authored
Merge pull request scala#6871 from adriaanm/overload_proto
More precise prototype for args of overloaded method
2 parents c6fc4e7 + 622f2cf commit dc715ce

File tree

16 files changed

+552
-231
lines changed

16 files changed

+552
-231
lines changed

spec/06-expressions.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,11 +1432,27 @@ to expressions $(e_1 , \ldots , e_n)$ of types $(\mathit{shape}(e_1) , \ldots ,
14321432
If there is precisely one alternative in $\mathscr{B}$, that alternative is chosen.
14331433

14341434
Otherwise, let $S_1 , \ldots , S_m$ be the list of types obtained by typing each argument as follows.
1435-
An argument `$e_i$` of the shape `($p_1$: $T_1 , \ldots , p_n$: $T_n$) => $b$` where one of the `$T_i$` is missing,
1436-
i.e., a function literal with a missing parameter type, is typed with an expected function type that
1437-
propagates the least upper bound of the fully defined types of the corresponding parameters of
1438-
the ([SAM-converted](#sam-conversion)) function types specified by the `$i$`th argument type found in each alternative.
1439-
All other arguments are typed with an undefined expected type.
1435+
1436+
Normally, an argument is typed without an expected type, except when trying to propagate more type
1437+
information to aid inference of higher-order function parameter types, as explained next. The intuition is
1438+
that all arguments must be of a function-like type (`PartialFunction`, `FunctionN` or some equivalent [SAM type](#sam-conversion)),
1439+
which in turn must define the same set of higher-order argument types, so that they can safely be used as
1440+
the expected type of a given argument of the overloaded method, without unduly ruling out any alternatives.
1441+
The intent is not to steer overloading resolution, but to preserve enough type information to steer type
1442+
inference of the arguments (a function literal or eta-expanded method) to this overloaded method.
1443+
1444+
Note that the expected type drives eta-expansion (not performed unless a function-like type is expected),
1445+
as well as inference of omitted parameter types of function literals.
1446+
1447+
More precisely, an argument `$e_i$` is typed with an expected type that is derived from the `$i$`th argument
1448+
type found in each alternative (call these `$T_{ij}$` for alternative `$j$` and argument position `$i$`) when
1449+
all `$T_{ij}$` are function types `$(A_{1j},..., A_{nj}) => ?$` (or the equivalent `PartialFunction`, or SAM)
1450+
of some arity `$n$`, and their argument types `$A_{kj}$` are identical across all overloads `$j$` for a
1451+
given `$k$`. Then, the expected type for `$e_i$` is derived as follows:
1452+
- we use `$PartialFunction[A_{1j},..., A_{nj}, ?]$` if for some overload `$j$`, `$T_{ij}$`'s type symbol is `PartialFunction`;
1453+
- else, if for some `$j$`, `$T_{ij}$` is `FunctionN`, the expected type is `$FunctionN[A_{1j},..., A_{nj}, ?]$`;
1454+
- else, if for all `$j$`, `$T_{ij}$` is a SAM type of the same class, defining argument types `$A_{1j},..., A_{nj}$`
1455+
(and a potentially varying result type), the expected type encodes these argument types and the SAM class.
14401456

14411457
For every member $m$ in $\mathscr{B}$ one determines whether it is applicable
14421458
to expressions ($e_1 , \ldots , e_m$) of types $S_1, \ldots , S_m$.

src/compiler/scala/tools/nsc/typechecker/Implicits.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ trait Implicits {
329329
result
330330
}
331331

332+
// TODO: use ProtoType for HasMember/HasMethodMatching
333+
332334
/** An extractor for types of the form ? { name: ? }
333335
*/
334336
object HasMember {
@@ -634,7 +636,7 @@ trait Implicits {
634636
private def matchesPtView(tp: Type, ptarg: Type, ptres: Type, undet: List[Symbol]): Boolean = tp match {
635637
case MethodType(p :: _, restpe) if p.isImplicit => matchesPtView(restpe, ptarg, ptres, undet)
636638
case MethodType(p :: Nil, restpe) => matchesArgRes(p.tpe, restpe, ptarg, ptres, undet)
637-
case ExistentialType(_, qtpe) => matchesPtView(normalize(qtpe), ptarg, ptres, undet)
639+
case ExistentialType(_, qtpe) => matchesPtView(methodToExpressionTp(qtpe), ptarg, ptres, undet)
638640
case Function1(arg1, res1) => matchesArgRes(arg1, res1, ptarg, ptres, undet)
639641
case _ => false
640642
}
@@ -733,7 +735,7 @@ trait Implicits {
733735
}
734736
case NullaryMethodType(restpe) => loop(restpe, pt)
735737
case PolyType(_, restpe) => loop(restpe, pt)
736-
case ExistentialType(_, qtpe) => if (fast) loop(qtpe, pt) else normalize(tp) <:< pt // is !fast case needed??
738+
case ExistentialType(_, qtpe) => if (fast) loop(qtpe, pt) else methodToExpressionTp(tp) <:< pt // is !fast case needed??
737739
case _ => if (fast) isPlausiblySubType(tp, pt) else tp <:< pt
738740
}
739741
loop(tp0, pt0)

src/compiler/scala/tools/nsc/typechecker/Infer.scala

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,42 @@ trait Infer extends Checkable {
5050
formals1
5151
}
5252

53+
// @requires sam == samOf(samTp)
54+
def instantiateSamFromFunction(funTp: Type, samTp: Type, sam: Symbol) = {
55+
val samClassSym = samTp.typeSymbol
56+
57+
// the unknowns
58+
val tparams = samClassSym.typeParams
59+
60+
if (tparams.isEmpty) samTp
61+
else {
62+
// ... as typevars
63+
val tvars = tparams map freshVar
64+
65+
// we're trying to fully define the type arguments for this type constructor
66+
val samTyCon = samClassSym.typeConstructor
67+
68+
val ptVars = appliedType(samTyCon, tvars)
69+
70+
// carry over info from pt
71+
ptVars <:< samTp
72+
73+
val samInfoWithTVars = ptVars.memberInfo(sam)
74+
75+
// use function type subtyping, not method type subtyping (the latter is invariant in argument types)
76+
funTp <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType)
77+
78+
val variances = tparams map varianceInType(sam.info)
79+
80+
// solve constraints tracked by tvars
81+
val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil))
82+
83+
debuglog(s"sam infer: $samTp --> ${appliedType(samTyCon, targs)} by ${funTp} <:< $samInfoWithTVars --> $targs for $tparams")
84+
85+
appliedType(samTyCon, targs)
86+
}
87+
}
88+
5389
/** Sorts the alternatives according to the given comparison function.
5490
* Returns a list containing the best alternative as well as any which
5591
* the best fails to improve upon.
@@ -103,9 +139,9 @@ trait Infer extends Checkable {
103139
finally excludedVars -= tv
104140
}
105141
def apply(tp: Type): Type = tp match {
106-
case WildcardType | BoundedWildcardType(_) | NoType => throw new NoInstance("undetermined type")
107-
case tv: TypeVar if !tv.untouchable => applyTypeVar(tv)
108-
case _ => mapOver(tp)
142+
case _: ProtoType | NoType => throw new NoInstance("undetermined type")
143+
case tv: TypeVar if !tv.untouchable => applyTypeVar(tv)
144+
case _ => mapOver(tp)
109145
}
110146
}
111147

@@ -115,13 +151,13 @@ trait Infer extends Checkable {
115151
/** Is type fully defined, i.e. no embedded anytypes or wildcards in it?
116152
*/
117153
private[typechecker] def isFullyDefined(tp: Type): Boolean = tp match {
118-
case WildcardType | BoundedWildcardType(_) | NoType => false
119-
case NoPrefix | ThisType(_) | ConstantType(_) => true
120-
case TypeRef(pre, _, args) => isFullyDefined(pre) && (args forall isFullyDefined)
121-
case SingleType(pre, _) => isFullyDefined(pre)
122-
case RefinedType(ts, _) => ts forall isFullyDefined
123-
case TypeVar(_, constr) if constr.inst == NoType => false
124-
case _ => falseIfNoInstance({ instantiate(tp) ; true })
154+
case _: ProtoType | NoType => false
155+
case NoPrefix | ThisType(_) | ConstantType(_) => true
156+
case TypeRef(pre, _, args) => isFullyDefined(pre) && (args forall isFullyDefined)
157+
case SingleType(pre, _) => isFullyDefined(pre)
158+
case RefinedType(ts, _) => ts forall isFullyDefined
159+
case TypeVar(_, constr) if constr.inst == NoType => false
160+
case _ => falseIfNoInstance { instantiate(tp); true }
125161
}
126162

127163
/** Solve constraint collected in types `tvars`.
@@ -151,25 +187,7 @@ trait Infer extends Checkable {
151187
case _ => tp
152188
}
153189

154-
/** Automatically perform the following conversions on expression types:
155-
* A method type becomes the corresponding function type.
156-
* A nullary method type becomes its result type.
157-
* Implicit parameters are skipped.
158-
* This method seems to be performance critical.
159-
*/
160-
def normalize(tp: Type): Type = tp match {
161-
case PolyType(_, restpe) =>
162-
logResult(sm"""|Normalizing PolyType in infer:
163-
| was: $restpe
164-
| now""")(normalize(restpe))
165-
case mt @ MethodType(_, restpe) if mt.isImplicit => normalize(restpe)
166-
case mt @ MethodType(_, restpe) if !mt.isDependentMethodType =>
167-
if (phase.erasedTypes) FunctionClass(mt.params.length).tpe
168-
else functionType(mt.paramTypes, normalize(restpe))
169-
case NullaryMethodType(restpe) => normalize(restpe)
170-
case ExistentialType(tparams, qtpe) => newExistentialType(tparams, normalize(qtpe))
171-
case _ => tp // @MAT aliases already handled by subtyping
172-
}
190+
173191

174192
private lazy val stdErrorClass = rootMirror.RootClass.newErrorClass(tpnme.ERROR)
175193
private lazy val stdErrorValue = stdErrorClass.newErrorValue(nme.ERROR)
@@ -297,11 +315,11 @@ trait Infer extends Checkable {
297315
&& isCompatible(tp, dropByName(pt))
298316
)
299317
def isCompatibleSam(tp: Type, pt: Type): Boolean = (definitions.isFunctionType(tp) || tp.isInstanceOf[MethodType] || tp.isInstanceOf[PolyType]) && {
300-
val samFun = typer.samToFunctionType(pt)
318+
val samFun = samToFunctionType(pt)
301319
(samFun ne NoType) && isCompatible(tp, samFun)
302320
}
303321

304-
val tp1 = normalize(tp)
322+
val tp1 = methodToExpressionTp(tp)
305323

306324
( (tp1 weak_<:< pt)
307325
|| isCoercible(tp1, pt)
@@ -344,9 +362,8 @@ trait Infer extends Checkable {
344362
tparam.tpe
345363
}
346364
val tp1 = tp map {
347-
case WildcardType => addTypeParam(TypeBounds.empty)
348-
case BoundedWildcardType(bounds) => addTypeParam(bounds)
349-
case t => t
365+
case pt: ProtoType => addTypeParam(pt.toBounds)
366+
case t => t
350367
}
351368
if (tp eq tp1) tp
352369
else existentialAbstraction(tparams.reverse, tp1)
@@ -359,25 +376,27 @@ trait Infer extends Checkable {
359376
* conforms to `pt`, return null.
360377
*/
361378
private def exprTypeArgs(tvars: List[TypeVar], tparams: List[Symbol], restpe: Type, pt: Type, useWeaklyCompatible: Boolean): List[Type] = {
362-
def restpeInst = restpe.instantiateTypeParams(tparams, tvars)
363-
def conforms = if (useWeaklyCompatible) isWeaklyCompatible(restpeInst, pt) else isCompatible(restpeInst, pt)
364-
// If the restpe is an implicit method, and the expected type is fully defined
365-
// optimize type variables wrt to the implicit formals only; ignore the result type.
366-
// See test pos/jesper.scala
367-
def variance = restpe match {
368-
case mt: MethodType if mt.isImplicit && isFullyDefined(pt) => MethodType(mt.params, AnyTpe)
369-
case _ => restpe
370-
}
371-
def solve() = solvedTypes(tvars, tparams, tparams map varianceInType(variance), upper = false, lubDepth(restpe :: pt :: Nil))
379+
val resTpVars = restpe.instantiateTypeParams(tparams, tvars)
372380

373-
if (conforms) {
381+
if (if (useWeaklyCompatible) isWeaklyCompatible(resTpVars, pt) else isCompatible(resTpVars, pt)) {
374382
// If conforms has just solved a tvar as a singleton type against pt, then we need to
375383
// prevent it from being widened later by adjustTypeArgs
376384
tvars.foreach(_.constr.stopWideningIfPrecluded)
377-
try solve() catch { case _: NoInstance => null }
385+
386+
// If the restpe is an implicit method, and the expected type is fully defined
387+
// optimize type variables wrt to the implicit formals only; ignore the result type.
388+
// See test pos/jesper.scala
389+
val variance = restpe match {
390+
case mt: MethodType if mt.isImplicit && isFullyDefined(pt) => MethodType(mt.params, AnyTpe)
391+
case _ => restpe
392+
}
393+
394+
try solvedTypes(tvars, tparams, tparams map varianceInType(variance), upper = false, lubDepth(restpe :: pt :: Nil))
395+
catch { case _: NoInstance => null }
378396
} else
379397
null
380398
}
399+
381400
/** Overload which allocates fresh type vars.
382401
* The other one exists because apparently inferExprInstance needs access to the typevars
383402
* after the call, and it's wasteful to return a tuple and throw it away almost every time.

0 commit comments

Comments
 (0)