summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/Infer.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/typechecker/Infer.scala')
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Infer.scala216
1 files changed, 177 insertions, 39 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index 9bec522e0c..7a340176d9 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -37,15 +37,20 @@ trait Infer {
/** The formal parameter types corresponding to <code>formals</code>.
* If <code>formals</code> has a repeated last parameter, a list of
* (nargs - params.length + 1) copies of its type is returned.
+ * By-name types are replaced with their underlying type.
*
* @param formals ...
* @param nargs ...
*/
- def formalTypes(formals: List[Type], nargs: Int): List[Type] = {
- val formals1 = formals map {
+ def formalTypes(formals: List[Type], nargs: Int): List[Type] =
+ formalTypes(formals, nargs, true)
+
+ /** This variant allows keeping ByName parameters. Useed in NamesDefaults. */
+ def formalTypes(formals: List[Type], nargs: Int, removeByName: Boolean): List[Type] = {
+ val formals1 = if (removeByName) formals map {
case TypeRef(_, sym, List(arg)) if (sym == ByNameParamClass) => arg
case formal => formal
- }
+ } else formals
if (isVarArgs(formals1)) {
val ft = formals1.last.normalize.typeArgs.head
formals1.init ::: (for (i <- List.range(formals1.length - 1, nargs)) yield ft)
@@ -173,9 +178,9 @@ trait Infer {
* Implicit parameters are skipped.
*/
def normalize(tp: Type): Type = skipImplicit(tp) match {
- case MethodType(formals, restpe) if (!restpe.isDependent) =>
+ case MethodType(params, restpe) if (!restpe.isDependent) =>
if (util.Statistics.enabled) normM += 1
- functionType(formals, normalize(restpe))
+ functionType(params map (_.tpe), normalize(restpe))
case PolyType(List(), restpe) =>
if (util.Statistics.enabled) normP += 1
normalize(restpe)
@@ -392,7 +397,8 @@ trait Infer {
isPlausiblyCompatible(mt.resultType, pt)
case ExistentialType(tparams, qtpe) =>
isPlausiblyCompatible(qtpe, pt)
- case MethodType(formals, _) =>
+ case MethodType(params, _) =>
+ val formals = tp.paramTypes
pt.normalize match {
case TypeRef(pre, sym, args) =>
!sym.isClass || {
@@ -458,7 +464,7 @@ trait Infer {
// See test pos/jesper.scala
val varianceType = restpe match {
case mt: ImplicitMethodType if isFullyDefined(pt) =>
- MethodType(mt.paramTypes, AnyClass.tpe)
+ MethodType(mt.params, AnyClass.tpe)
case _ =>
restpe
}
@@ -543,6 +549,8 @@ trait Infer {
* Undetermined type arguments are represented by `definitions.NothingClass.tpe'.
* No check that inferred parameters conform to their bounds is made here.
*
+ * bq: was private, but need it for unapply checking
+ *
* @param tparams the type parameters of the method
* @param formals the value parameter types of the method
* @param restp the result type of the method
@@ -556,7 +564,6 @@ trait Infer {
* @return ...
* @throws NoInstance
*/
- // bq: was private, but need it for unapply checking
def methTypeArgs(tparams: List[Symbol], formals: List[Type], restpe: Type,
argtpes: List[Type], pt: Type,
uninstantiated: ListBuffer[Symbol]): List[Type] = {
@@ -612,14 +619,60 @@ trait Infer {
case _ =>
formalTypes(tp.paramTypes, n).length == n
}
+ /**
+ * Verifies whether the named application is valid. The logic is very
+ * similar to the one in NamesDefaults.removeNames.
+ *
+ * @return a triple (argtpes1, argPos, namesOk) where
+ * - argtpes1 the argument types in named application (assignments to
+ * non-parameter names are treated as assignments, i.e. type Unit)
+ * - argPos a Function1[Int, Int] mapping arguments from their current
+ * to the corresponding position in params
+ * - namesOK is false when there's an invalid use of named arguments
+ */
+ private def checkNames(argtpes: List[Type], params: List[Symbol]) = {
+ val argPos = (new Array[Int](argtpes.length)) map (x => -1)
+ var positionalAllowed = true
+ var namesOK = true
+ var index = 0
+ val argtpes1 = argtpes map {
+ case NamedType(name, tp) => // a named argument
+ var res = tp
+ val pos = params.findIndexOf(p => p.name == name && !p.hasFlag(SYNTHETIC))
+ if (pos == -1) {
+ if (positionalAllowed) { // treat assignment as positional argument
+ argPos(index) = index
+ res = UnitClass.tpe
+ } else // unknown parameter name
+ namesOK = false
+ } else if (argPos.contains(pos)) { // parameter specified twice
+ namesOK = false
+ } else {
+ positionalAllowed = false
+ argPos(index) = pos
+ }
+ index += 1
+ res
+ case tp => // a positional argument
+ argPos(index) = index
+ if (!positionalAllowed)
+ namesOK = false // positional after named
+ index += 1
+ tp
+ }
+ (argtpes1, argPos, namesOK)
+ }
/** Is there an instantiation of free type variables <code>undetparams</code>
* such that function type <code>ftpe</code> is applicable to
* <code>argtpes</code> and its result conform to <code>pt</code>?
*
* @param undetparams ...
- * @param ftpe ...
- * @param argtpes ...
+ * @param ftpe the type of the function (often a MethodType)
+ * @param argtpes the argument types; a NamedType(name, tp) for named
+ * arguments. For each NamedType, if `name' does not exist in `ftpe', that
+ * type is set to `Unit', i.e. the corresponding argument is treated as
+ * an assignment expression (@see checkNames).
* @param pt ...
* @return ...
*/
@@ -630,24 +683,75 @@ trait Infer {
alts exists (alt => isApplicable(undetparams, pre.memberType(alt), argtpes0, pt))
case ExistentialType(tparams, qtpe) =>
isApplicable(undetparams, qtpe, argtpes0, pt)
- case MethodType(formals0, _) =>
- val formals = formalTypes(formals0, argtpes0.length)
- val argtpes = actualTypes(argtpes0, formals.length)
- val restpe = ftpe.resultType(argtpes)
- if (undetparams.isEmpty) {
- (formals.length == argtpes.length &&
- isCompatible(argtpes, formals) &&
- isWeaklyCompatible(restpe, pt))
+ case MethodType(params, _) =>
+ // repeat varargs as needed, remove ByName
+ val formals = formalTypes(params map (_.tpe), argtpes0.length)
+
+ def tryTupleApply: Boolean = {
+ // if 1 formal, 1 argtpe (a tuple), otherwise unmodified argtpes0
+ val tupleArgTpe = actualTypes(argtpes0 map {
+ // no assignment is treated as named argument here
+ case NamedType(name, tp) => UnitClass.tpe
+ case tp => tp
+ }, formals.length)
+
+ argtpes0.length != tupleArgTpe.length &&
+ isApplicable(undetparams, ftpe, tupleArgTpe, pt)
+ }
+ def typesCompatible(argtpes: List[Type]) = {
+ val restpe = ftpe.resultType(argtpes)
+ if (undetparams.isEmpty) {
+ (isCompatible(argtpes, formals) &&
+ isWeaklyCompatible(restpe, pt))
+ } else {
+ try {
+ val uninstantiated = new ListBuffer[Symbol]
+ val targs = methTypeArgs(undetparams, formals, restpe, argtpes, pt, uninstantiated)
+ (exprTypeArgs(uninstantiated.toList, restpe.instantiateTypeParams(undetparams, targs), pt) ne null) &&
+ isWithinBounds(NoPrefix, NoSymbol, undetparams, targs)
+ } catch {
+ case ex: NoInstance => false
+ }
+ }
+ }
+
+ // very similar logic to doTypedApply in typechecker
+ if (argtpes0.length > formals.length) tryTupleApply
+ else if (argtpes0.length == formals.length) {
+ if (!argtpes0.exists(_.isInstanceOf[NamedType])) {
+ // fast track if no named arguments are used
+ typesCompatible(argtpes0)
+ } else {
+ // named arguments are used
+ val (argtpes1, argPos, namesOK) = checkNames(argtpes0, params)
+ if (!namesOK) false
+ // when using named application, the vararg param has to be specified exactly once
+ else if (!isIdentity(argPos) && (formals.length != params.length)) false
+ else {
+ // nb. arguments and names are OK, check if types are compatible
+ typesCompatible(reorderArgs(argtpes1, argPos))
+ }
+ }
} else {
- try {
- val uninstantiated = new ListBuffer[Symbol]
- val targs = methTypeArgs(undetparams, formals, restpe, argtpes, pt, uninstantiated)
- (exprTypeArgs(uninstantiated.toList, restpe.instantiateTypeParams(undetparams, targs), pt) ne null) &&
- isWithinBounds(NoPrefix, NoSymbol, undetparams, targs)
- } catch {
- case ex: NoInstance => false
+ // not enough arguments, check if applicable using defaults
+ val namedArgtpes = argtpes0.dropWhile {
+ case NamedType(name, _) => params.forall(_.name != name)
+ case _ => true
+ }
+ val namedParams = params.drop(argtpes0.length - namedArgtpes.length)
+ val missingParams = namedParams.filter(p => namedArgtpes.forall {
+ case NamedType(name, _) => name != p.name
+ case _ => true
+ })
+ if (missingParams.exists(!_.hasFlag(DEFAULTPARAM))) tryTupleApply
+ else {
+ val argtpes1 = argtpes0 ::: missingParams.map {
+ p => NamedType(p.name, p.tpe) // add defaults as named arguments
+ }
+ isApplicable(undetparams, ftpe, argtpes1, pt)
}
}
+
case PolyType(tparams, restpe) =>
val tparams1 = cloneSymbols(tparams)
isApplicable(tparams1 ::: undetparams, restpe.substSym(tparams, tparams1), argtpes0, pt)
@@ -657,7 +761,8 @@ trait Infer {
false
}
- private[typechecker] def isApplicableSafe(undetparams: List[Symbol], ftpe: Type, argtpes0: List[Type], pt: Type): Boolean = {
+ private[typechecker] def isApplicableSafe(undetparams: List[Symbol], ftpe: Type,
+ argtpes0: List[Type], pt: Type): Boolean = {
val reportAmbiguousErrors = context.reportAmbiguousErrors
context.reportAmbiguousErrors = false
try {
@@ -683,10 +788,10 @@ trait Infer {
alts exists (alt => isAsSpecific(pre.memberType(alt), ftpe2))
case et: ExistentialType =>
et.withTypeVars(isAsSpecific(_, ftpe2)) // !!! why isStrictly?
- case MethodType(formals @ (x :: xs), _) =>
- isApplicable(List(), ftpe2, formals, WildcardType)
- case PolyType(_, MethodType(formals @ (x :: xs), _)) =>
- isApplicable(List(), ftpe2, formals, WildcardType)
+ case MethodType(params @ (x :: xs), _) =>
+ isApplicable(List(), ftpe2, params map (_.tpe), WildcardType)
+ case PolyType(_, MethodType(params @ (x :: xs), _)) =>
+ isApplicable(List(), ftpe2, params map (_.tpe), WildcardType)
case ErrorType =>
true
case _ =>
@@ -1014,7 +1119,7 @@ trait Infer {
*/
def inferMethodInstance(fn: Tree, undetparams: List[Symbol],
args: List[Tree], pt0: Type): List[Symbol] = fn.tpe match {
- case MethodType(formals0, _) =>
+ case MethodType(params0, _) =>
if (inferInfo)
println("infer method instance "+fn+"\n"+
" undetparams = "+undetparams+"\n"+
@@ -1022,7 +1127,7 @@ trait Infer {
" pt = "+pt0)
try {
val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0
- val formals = formalTypes(formals0, args.length)
+ val formals = formalTypes(params0 map (_.tpe), args.length)
val argtpes = actualTypes(args map (_.tpe.deconst), formals.length)
val restpe = fn.tpe.resultType(argtpes)
val uninstantiated = new ListBuffer[Symbol]
@@ -1393,24 +1498,57 @@ trait Infer {
/** Assign <code>tree</code> the type of an alternative which is applicable
* to <code>argtpes</code>, and whose result type is compatible with `pt'.
- * If several applicable alternatives exist, take the
- * most specialized one.
+ * If several applicable alternatives exist, drop the alternatives which use
+ * default arguments, then select the most specialized one.
* If no applicable alternative exists, and pt != WildcardType, try again
* with pt = WildcardType.
* Otherwise, if there is no best alternative, error.
+ *
+ * @param argtpes contains the argument types. If an argument is named, as
+ * "a = 3", the corresponding type is `NamedType("a", Int)'. If the name
+ * of some NamedType does not exist in an alternative's parameter names,
+ * the type is replaces by `Unit', i.e. the argument is treated as an
+ * assignment expression.
*/
- def inferMethodAlternative(tree: Tree, undetparams: List[Symbol], argtpes: List[Type], pt0: Type): Unit = tree.tpe match {
+ def inferMethodAlternative(tree: Tree, undetparams: List[Symbol],
+ argtpes: List[Type], pt0: Type): Unit = tree.tpe match {
case OverloadedType(pre, alts) =>
val pt = if (pt0.typeSymbol == UnitClass) WildcardType else pt0
tryTwice {
- if (settings.debug.value) log("infer method alt " + tree.symbol + " with alternatives " + (alts map pre.memberType) + ", argtpes = " + argtpes + ", pt = " + pt)
- val applicable = alts filter (alt => isApplicable(undetparams, followApply(pre.memberType(alt)), argtpes, pt))
+ if (settings.debug.value)
+ log("infer method alt "+ tree.symbol +" with alternatives "+
+ (alts map pre.memberType) +", argtpes = "+ argtpes +", pt = "+ pt)
+
+ val allApplicable = alts filter (alt =>
+ isApplicable(undetparams, followApply(pre.memberType(alt)), argtpes, pt))
+
+ // if there are multiple, drop those that use a default
+ // (keep those that use vararg / tupling conversion)
+ val applicable =
+ if (allApplicable.length <= 1) allApplicable
+ else allApplicable filter (alt => {
+ val mtypes = followApply(alt.tpe) match {
+ case OverloadedType(_, alts) =>
+ // for functional values, the `apply' method might be overloaded
+ alts map (_.tpe)
+ case t => List(t)
+ }
+ mtypes.exists(t => t.paramTypes.length < argtpes.length || // tupling (*)
+ hasExactlyNumParams(t, argtpes.length)) // same nb or vararg
+ // (*) more arguments than parameters, but still applicable: tuplig conversion works.
+ // todo: should not return "false" when paramTypes = (Unit) no argument is given
+ // (tupling would work)
+ })
+
+
def improves(sym1: Symbol, sym2: Symbol) =
sym2 == NoSymbol || sym2.isError ||
- isStrictlyMoreSpecific(followApply(pre.memberType(sym1)), followApply(pre.memberType(sym2)), sym1, sym2)
+ isStrictlyMoreSpecific(followApply(pre.memberType(sym1)),
+ followApply(pre.memberType(sym2)), sym1, sym2)
+
val best = ((NoSymbol: Symbol) /: applicable) ((best, alt) =>
if (improves(alt, best)) alt else best)
- val competing = applicable dropWhile (alt => best == alt || improves(best, alt))
+ val competing = applicable.dropWhile(alt => best == alt || improves(best, alt))
if (best == NoSymbol) {
if (pt == WildcardType) {
errorTree(tree, applyErrorMsg(tree, " cannot be applied to ", argtpes, pt))