Skip to content

Fix #3252: Change logic in adaptNoArgs #3254

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
Oct 5, 2017
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
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,6 @@ class TyperState(previous: TyperState /* | Null */) extends DotClass with Showab

override def toText(printer: Printer): Text = constraint.toText(printer)

def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr
def hashesStr: String =
if (previous == null) "" else hashCode.toString + " -> " + previous.hashesStr
}
329 changes: 173 additions & 156 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1994,169 +1994,186 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
}
}

def adaptNoArgs(wtp: Type): Tree = wtp match {
case wtp: ExprType =>
adaptInterpolated(tree.withType(wtp.resultType), pt)
case wtp: ImplicitMethodType if constrainResult(wtp, followAlias(pt)) =>
val tvarsToInstantiate = tvarsInParams(tree)
wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate))
val constr = ctx.typerState.constraint
def addImplicitArgs(implicit ctx: Context) = {
val errors = new mutable.ListBuffer[() => String]
def implicitArgError(msg: => String) = {
errors += (() => msg)
EmptyTree
}
def issueErrors() = {
for (err <- errors) ctx.error(err(), tree.pos.endPos)
tree.withType(wtp.resultType)
}
val args = (wtp.paramNames, wtp.paramInfos).zipped map { (pname, formal) =>
def implicitArgError(msg: String => String) =
errors += (() => msg(em"parameter $pname of $methodStr"))
if (errors.nonEmpty) EmptyTree
else inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
}
if (errors.nonEmpty) {
// If there are several arguments, some arguments might already
// have influenced the context, binding variables, but later ones
// might fail. In that case the constraint needs to be reset.
ctx.typerState.constraint = constr

// If method has default params, fall back to regular application
// where all inferred implicits are passed as named args.
if (tree.symbol.hasDefaultParams) {
val namedArgs = (wtp.paramNames, args).zipped.flatMap { (pname, arg) =>
arg match {
case EmptyTree => Nil
case _ => untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
}
}
tryEither { implicit ctx =>
typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt)
} { (_, _) =>
issueErrors()
}
} else issueErrors()
}
else adapt(tpd.Apply(tree, args), pt)
def adaptNoArgsImplicitMethod(wtp: ImplicitMethodType): Tree = {
val tvarsToInstantiate = tvarsInParams(tree)
wtp.paramInfos.foreach(instantiateSelected(_, tvarsToInstantiate))
val constr = ctx.typerState.constraint
def addImplicitArgs(implicit ctx: Context) = {
val errors = new mutable.ListBuffer[() => String]
def implicitArgError(msg: => String) = {
errors += (() => msg)
EmptyTree
}
addImplicitArgs(argCtx(tree))
case wtp: MethodType if !pt.isInstanceOf[SingletonType] =>
// Follow proxies and approximate type paramrefs by their upper bound
// in the current constraint in order to figure out robustly
// whether an expected type is some sort of function type.
def underlyingApplied(tp: Type): Type = tp.stripTypeVar match {
case tp: RefinedType => tp
case tp: AppliedType => tp
case tp: TypeParamRef => underlyingApplied(ctx.typeComparer.bounds(tp).hi)
case tp: TypeProxy => underlyingApplied(tp.superType)
case _ => tp
def issueErrors() = {
for (err <- errors) ctx.error(err(), tree.pos.endPos)
tree.withType(wtp.resultType)
}
val ptNorm = underlyingApplied(pt)
val arity =
if (defn.isFunctionType(ptNorm))
if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none))
// if method type is fully defined, but expected type is not,
// prioritize method parameter types as parameter types of the eta-expanded closure
0
else defn.functionArity(ptNorm)
else {
val nparams = wtp.paramInfos.length
if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams
else -1 // no eta expansion in this case
}

/** A synthetic apply should be eta-expanded if it is the apply of an implicit function
* class, and the expected type is a function type. This rule is needed so we can pass
* an implicit function to a regular function type. So the following is OK
*
* val f: implicit A => B = ???
* val g: A => B = f
*
* and the last line expands to
*
* val g: A => B = (x$0: A) => f.apply(x$0)
*
* One could be tempted not to eta expand the rhs, but that would violate the invariant
* that expressions of implicit function types are always implicit closures, which is
* exploited by ShortcutImplicits.
*
* On the other hand, the following would give an error if there is no implicit
* instance of A available.
*
* val x: AnyRef = f
*
* That's intentional, we want to fail here, otherwise some unsuccesful implicit searches
* would go undetected.
*
* Examples for these cases are found in run/implicitFuns.scala and neg/i2006.scala.
*/
def isExpandableApply =
defn.isImplicitFunctionClass(tree.symbol.maybeOwner) && defn.isFunctionType(ptNorm)

/** Is reference to this symbol `f` automatically expanded to `f()`? */
def isAutoApplied(sym: Symbol): Boolean = {
sym.isConstructor ||
sym.matchNullaryLoosely ||
ctx.testScala2Mode(em"${sym.showLocated} requires () argument", tree.pos,
patch(tree.pos.endPos, "()"))
val args = (wtp.paramNames, wtp.paramInfos).zipped map { (pname, formal) =>
def implicitArgError(msg: String => String) =
errors += (() => msg(em"parameter $pname of $methodStr"))
if (errors.nonEmpty) EmptyTree
else inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
}
if (errors.nonEmpty) {
// If there are several arguments, some arguments might already
// have influenced the context, binding variables, but later ones
// might fail. In that case the constraint needs to be reset.
ctx.typerState.constraint = constr

// If method has default params, fall back to regular application
// where all inferred implicits are passed as named args.
if (tree.symbol.hasDefaultParams) {
val namedArgs = (wtp.paramNames, args).zipped.flatMap { (pname, arg) =>
arg match {
case EmptyTree => Nil
case _ => untpd.NamedArg(pname, untpd.TypedSplice(arg)) :: Nil
}
}
tryEither { implicit ctx =>
typed(untpd.Apply(untpd.TypedSplice(tree), namedArgs), pt)
} { (_, _) =>
issueErrors()
}
} else issueErrors()
}
else adapt(tpd.Apply(tree, args), pt)
}
addImplicitArgs(argCtx(tree))
}

/** A synthetic apply should be eta-expanded if it is the apply of an implicit function
* class, and the expected type is a function type. This rule is needed so we can pass
* an implicit function to a regular function type. So the following is OK
*
* val f: implicit A => B = ???
* val g: A => B = f
*
* and the last line expands to
*
* val g: A => B = (x$0: A) => f.apply(x$0)
*
* One could be tempted not to eta expand the rhs, but that would violate the invariant
* that expressions of implicit function types are always implicit closures, which is
* exploited by ShortcutImplicits.
*
* On the other hand, the following would give an error if there is no implicit
* instance of A available.
*
* val x: AnyRef = f
*
* That's intentional, we want to fail here, otherwise some unsuccesful implicit searches
* would go undetected.
*
* Examples for these cases are found in run/implicitFuns.scala and neg/i2006.scala.
*/
def adaptNoArgsUnappliedMethod(wtp: MethodType, functionExpected: Boolean, arity: Int): Tree = {
def isExpandableApply =
defn.isImplicitFunctionClass(tree.symbol.maybeOwner) && functionExpected

/** Is reference to this symbol `f` automatically expanded to `f()`? */
def isAutoApplied(sym: Symbol): Boolean = {
sym.isConstructor ||
sym.matchNullaryLoosely ||
ctx.testScala2Mode(em"${sym.showLocated} requires () argument", tree.pos,
patch(tree.pos.endPos, "()"))
}

// Reasons NOT to eta expand:
// - we reference a constructor
// - we are in a pattern
// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
if (arity >= 0 &&
!tree.symbol.isConstructor &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !isExpandableApply))
typed(etaExpand(tree, wtp, arity), pt)
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
adaptInterpolated(tpd.Apply(tree, Nil), pt)
else if (wtp.isImplicit)
err.typeMismatch(tree, pt)
else
missingArgs(wtp)
}

// Reasons NOT to eta expand:
// - we reference a constructor
// - we are in a pattern
// - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that)
if (arity >= 0 &&
!tree.symbol.isConstructor &&
!ctx.mode.is(Mode.Pattern) &&
!(isSyntheticApply(tree) && !isExpandableApply))
typed(etaExpand(tree, wtp, arity), pt)
else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol))
adaptInterpolated(tpd.Apply(tree, Nil), pt)
else if (wtp.isImplicit)
err.typeMismatch(tree, pt)
def adaptNoArgsOther(wtp: Type) = {
ctx.typeComparer.GADTused = false
if (defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) &&
!untpd.isImplicitClosure(tree) &&
!isApplyProto(pt) &&
!ctx.isAfterTyper) {
typr.println(i"insert apply on implicit $tree")
typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
}
else if (ctx.mode is Mode.Pattern) {
checkEqualityEvidence(tree, pt)
tree
}
else if (tree.tpe <:< pt) {
if (pt.hasAnnotation(defn.InlineParamAnnot))
checkInlineConformant(tree, "argument to inline parameter")
if (Inliner.hasBodyToInline(tree.symbol) &&
!ctx.owner.ownersIterator.exists(_.isInlineMethod) &&
!ctx.settings.YnoInline.value &&
!ctx.isAfterTyper &&
!ctx.reporter.hasErrors)
adapt(Inliner.inlineCall(tree, pt), pt)
else if (ctx.typeComparer.GADTused && pt.isValueType)
// Insert an explicit cast, so that -Ycheck in later phases succeeds.
// I suspect, but am not 100% sure that this might affect inferred types,
// if the expected type is a supertype of the GADT bound. It would be good to come
// up with a test case for this.
tree.asInstance(pt)
else
missingArgs(wtp)
case _ =>
ctx.typeComparer.GADTused = false
if (defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) &&
!untpd.isImplicitClosure(tree) &&
!isApplyProto(pt) &&
!ctx.isAfterTyper) {
typr.println(i"insert apply on implicit $tree")
typed(untpd.Select(untpd.TypedSplice(tree), nme.apply), pt)
}
else if (ctx.mode is Mode.Pattern) {
checkEqualityEvidence(tree, pt)
tree
}
else if (tree.tpe <:< pt) {
if (pt.hasAnnotation(defn.InlineParamAnnot))
checkInlineConformant(tree, "argument to inline parameter")
if (Inliner.hasBodyToInline(tree.symbol) &&
!ctx.owner.ownersIterator.exists(_.isInlineMethod) &&
!ctx.settings.YnoInline.value &&
!ctx.isAfterTyper &&
!ctx.reporter.hasErrors)
adapt(Inliner.inlineCall(tree, pt), pt)
else if (ctx.typeComparer.GADTused && pt.isValueType)
// Insert an explicit cast, so that -Ycheck in later phases succeeds.
// I suspect, but am not 100% sure that this might affect inferred types,
// if the expected type is a supertype of the GADT bound. It would be good to come
// up with a test case for this.
tree.asInstance(pt)
else
tree
}
else wtp match {
case wtp: MethodType => missingArgs(wtp)
case _ =>
typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt")
//typr.println(TypeComparer.explained(implicit ctx => tree.tpe <:< pt))
adaptToSubType(wtp)
}
}
else wtp match {
case wtp: MethodType => missingArgs(wtp)
case _ =>
typr.println(i"adapt to subtype ${tree.tpe} !<:< $pt")
//typr.println(TypeComparer.explained(implicit ctx => tree.tpe <:< pt))
adaptToSubType(wtp)
}
}

// Follow proxies and approximate type paramrefs by their upper bound
// in the current constraint in order to figure out robustly
// whether an expected type is some sort of function type.
def underlyingApplied(tp: Type): Type = tp.stripTypeVar match {
case tp: RefinedType => tp
case tp: AppliedType => tp
case tp: TypeParamRef => underlyingApplied(ctx.typeComparer.bounds(tp).hi)
case tp: TypeProxy => underlyingApplied(tp.superType)
case _ => tp
}

def adaptNoArgs(wtp: Type): Tree = {
val ptNorm = underlyingApplied(pt)
val functionExpected = defn.isFunctionType(ptNorm)
wtp match {
case wtp: ExprType =>
adaptInterpolated(tree.withType(wtp.resultType), pt)
case wtp: ImplicitMethodType
if constrainResult(wtp, followAlias(pt)) || !functionExpected =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order of the checks matter here? I see that constrainResult generates new constraints if it returns true.
If the order does not matter, then it should be less expensive to first check !functionExpected.

Copy link
Contributor Author

@odersky odersky Oct 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation. The order does matter, actually. constrainResult strenghtens the constraint if it succeeds so we want to always run it.

adaptNoArgsImplicitMethod(wtp)
case wtp: MethodType if !pt.isInstanceOf[SingletonType] =>
val arity =
if (functionExpected)
if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none))
// if method type is fully defined, but expected type is not,
// prioritize method parameter types as parameter types of the eta-expanded closure
0
else defn.functionArity(ptNorm)
else {
val nparams = wtp.paramInfos.length
if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams
else -1 // no eta expansion in this case
}
adaptNoArgsUnappliedMethod(wtp, functionExpected, arity)
case _ =>
adaptNoArgsOther(wtp)
}
}

/** Adapt an expression of constant type to a different constant type `tpe`. */
def adaptConstant(tree: Tree, tpe: ConstantType): Tree = {
def lit = Literal(tpe.value).withPos(tree.pos)
Expand Down
3 changes: 3 additions & 0 deletions tests/pos/i3252.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Test {
val i: Long = List(1, 2, 3).sum
}