diff options
author | Adriaan Moors <adriaan@lightbend.com> | 2016-07-27 11:18:08 -0700 |
---|---|---|
committer | Adriaan Moors <adriaan@lightbend.com> | 2016-08-12 16:39:31 -0700 |
commit | 228d1b1ceb7643df1313672cd620cddb1a429029 (patch) | |
tree | 4fb946f9fe8a03cf759f194b94e17c8544bbd4fb /src | |
parent | 618d42c747955a43557655bdc0c4281fec5a7923 (diff) | |
download | scala-228d1b1ceb7643df1313672cd620cddb1a429029.tar.gz scala-228d1b1ceb7643df1313672cd620cddb1a429029.tar.bz2 scala-228d1b1ceb7643df1313672cd620cddb1a429029.zip |
Propagate overloaded function type to expected arg type
Infer missing parameter types for function literals passed
to higher-order overloaded methods by deriving the
expected argument type from the function types in the
overloaded method type's argument types.
This eases the pain caused by methods becoming overloaded
because SAM types and function types are compatible,
which used to disable parameter type inference because
for overload resolution arguments are typed without
expected type, while typedFunction needs the expected
type to infer missing parameter types for function literals.
It also aligns us with dotty. The special case for
function literals seems reasonable, as it has precedent,
and it just enables the special case in typing function
literals (derive the param types from the expected type).
Since this does change type inference, you can opt out
using the Scala 2.11 source level.
Fix scala/scala-dev#157
Diffstat (limited to 'src')
4 files changed, 78 insertions, 28 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 7112edd75d..0071d66eb9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -731,7 +731,7 @@ trait Infer extends Checkable { // If args eq the incoming arg types, fail; otherwise recurse with these args. def tryWithArgs(args: List[Type]) = ( (args ne argtpes0) - && isApplicable(undetparams, mt, args, pt) + && isApplicableToMethod(undetparams, mt, args, pt) // used to be isApplicable(undetparams, mt, args, pt), knowing mt: MethodType ) def tryInstantiating(args: List[Type]) = falseIfNoInstance { val restpe = mt resultType args diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index d412b5ef33..8ce8dde9ab 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3271,40 +3271,74 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } val fun = preSelectOverloaded(fun0) + val argslen = args.length fun.tpe match { case OverloadedType(pre, alts) => def handleOverloaded = { val undetparams = context.undetparams + + def funArgTypes(tps: List[Type]) = tps.map { tp => + val relTp = tp.asSeenFrom(pre, fun.symbol.owner) + val argTps = functionOrSamArgTypes(relTp) + //println(s"funArgTypes $argTps from $relTp") + argTps.map(approximateAbstracts) + } + + def functionProto(argTps: List[Type]): Type = + try functionType(funArgTypes(argTps).transpose.map(lub), WildcardType) + catch { case _: IllegalArgumentException => WildcardType } + + // To propagate as much information as possible to typedFunction, which uses the expected type to + // infer missing parameter types for Function trees that we're typing as arguments here, + // we expand the parameter types for all alternatives to the expected argument length, + // then transpose to get a list of alternative argument types (push down the overloading to the arguments). + // Thus, for each `arg` in `args`, the corresponding `argPts` in `altArgPts` is a list of expected types + // for `arg`. Depending on which overload is picked, only one of those expected types must be met, but + // we're in the process of figuring that out, so we'll approximate below by normalizing them to function types + // and lubbing the argument types (we treat SAM and FunctionN types equally, but non-function arguments + // do not receive special treatment: they are typed under WildcardType.) + val altArgPts = + if (settings.isScala212 && args.exists(treeInfo.isFunctionMissingParamType)) + try alts.map(alt => formalTypes(alt.info.paramTypes, argslen)).transpose // do least amount of work up front + catch { case _: IllegalArgumentException => args.map(_ => Nil) } // fail safe in case formalTypes fails to align to argslen + else args.map(_ => Nil) // will type under argPt == WildcardType + val (args1, argTpes) = context.savingUndeterminedTypeParams() { val amode = forArgMode(fun, mode) - def typedArg0(tree: Tree) = typedArg(tree, amode, BYVALmode, WildcardType) - args.map { - case arg @ AssignOrNamedArg(Ident(name), rhs) => - // named args: only type the righthand sides ("unknown identifier" errors otherwise) - // the assign is untyped; that's ok because we call doTypedApply - val typedRhs = typedArg0(rhs) - val argWithTypedRhs = treeCopy.AssignOrNamedArg(arg, arg.lhs, typedRhs) - - // TODO: SI-8197/SI-4592: check whether this named argument could be interpreted as an assign + + map2(args, altArgPts) { (arg, argPts) => + def typedArg0(tree: Tree) = { + // if we have an overloaded HOF such as `(f: Int => Int)Int <and> (f: Char => Char)Char`, + // and we're typing a function like `x => x` for the argument, try to collapse + // the overloaded type into a single function type from which `typedFunction` + // can derive the argument type for `x` in the function literal above + val argPt = + if (argPts.nonEmpty && treeInfo.isFunctionMissingParamType(tree)) functionProto(argPts) + else WildcardType + + val argTyped = typedArg(tree, amode, BYVALmode, argPt) + (argTyped, argTyped.tpe.deconst) + } + + arg match { + // SI-8197/SI-4592 call for checking whether this named argument could be interpreted as an assign // infer.checkNames must not use UnitType: it may not be a valid assignment, or the setter may return another type from Unit - // - // var typedAsAssign = true - // val argTyped = silent(_.typedArg(argWithTypedRhs, amode, BYVALmode, WildcardType)) orElse { errors => - // typedAsAssign = false - // argWithTypedRhs - // } - // - // TODO: add an assignmentType field to NamedType, equal to: - // assignmentType = if (typedAsAssign) argTyped.tpe else NoType - - (argWithTypedRhs, NamedType(name, typedRhs.tpe.deconst)) - case arg @ treeInfo.WildcardStarArg(repeated) => - val arg1 = typedArg0(arg) - (arg1, RepeatedType(arg1.tpe.deconst)) - case arg => - val arg1 = typedArg0(arg) - (arg1, arg1.tpe.deconst) + // TODO: just make it an error to refer to a non-existent named arg, as it's far more likely to be + // a typo than an assignment passed as an argument + case AssignOrNamedArg(lhs@Ident(name), rhs) => + // named args: only type the righthand sides ("unknown identifier" errors otherwise) + // the assign is untyped; that's ok because we call doTypedApply + typedArg0(rhs) match { + case (rhsTyped, tp) => (treeCopy.AssignOrNamedArg(arg, lhs, rhsTyped), NamedType(name, tp)) + } + case treeInfo.WildcardStarArg(_) => + typedArg0(arg) match { + case (argTyped, tp) => (argTyped, RepeatedType(tp)) + } + case _ => + typedArg0(arg) + } }.unzip } if (context.reporter.hasErrors) @@ -3335,7 +3369,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case mt @ MethodType(params, _) => val paramTypes = mt.paramTypes // repeat vararg as often as needed, remove by-name - val argslen = args.length val formals = formalTypes(paramTypes, argslen) /* Try packing all arguments into a Tuple and apply `fun` diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index eca1bbea5a..0e63e81ae1 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -687,6 +687,17 @@ trait Definitions extends api.StandardDefinitions { } } + // the argument types expected by the function described by `tp` (a FunctionN or SAM type), + // or `Nil` if `tp` does not represent a function type or SAM (or if it happens to be Function0...) + def functionOrSamArgTypes(tp: Type): List[Type] = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.init + else samOf(tp) match { + case samSym if samSym.exists => tp.memberInfo(samSym).paramTypes + case _ => Nil + } + } + // the SAM's parameters and the Function's formals must have the same length // (varargs etc don't come into play, as we're comparing signatures, not checking an application) def samMatchesFunctionBasedOnArity(sam: Symbol, formals: List[Any]): Boolean = diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 86b70643c9..b9f3e987ee 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -263,6 +263,12 @@ abstract class TreeInfo { true } + def isFunctionMissingParamType(tree: Tree): Boolean = tree match { + case Function(vparams, _) => vparams.exists(_.tpt.isEmpty) + case _ => false + } + + /** Is symbol potentially a getter of a variable? */ def mayBeVarGetter(sym: Symbol): Boolean = sym.info match { |