diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index df7c9e087423..d6384294dffe 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9d7fe3e4486e..be42b5e12b2d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 => + 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) diff --git a/tests/pos/i3252.scala b/tests/pos/i3252.scala new file mode 100644 index 000000000000..570e43cd358d --- /dev/null +++ b/tests/pos/i3252.scala @@ -0,0 +1,3 @@ +object Test { + val i: Long = List(1, 2, 3).sum +}