diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index bef2a0e50c2b..c23fbb4f4ad8 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -104,6 +104,6 @@ object Mode { /** Read comments from definitions when unpickling from TASTY */ val ReadComments: Mode = newMode(22, "ReadComments") - /** Suppress insertion of apply or implicit conversion on qualifier */ - val FixedQualifier: Mode = newMode(23, "FixedQualifier") + /** We are synthesizing the receiver of an extension method */ + val SynthesizeExtMethodReceiver: Mode = newMode(23, "SynthesizeExtMethodReceiver") } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 240d2988e473..5d2367a633b5 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1227,7 +1227,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { /** Defer constraining type variables when compared against prototypes */ def isMatchedByProto(proto: ProtoType, tp: Type): Boolean = tp.stripTypeVar match { case tp: TypeParamRef if constraint contains tp => true - case _ => proto.isMatchedBy(tp) + case _ => proto.isMatchedBy(tp, keepConstraint = true) } /** Narrow gadt.bounds for the type parameter referenced by `tr` to include diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e246bc74f358..3e7f7669b3a2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1665,7 +1665,7 @@ object Types { /** A trait for proto-types, used as expected types in typer */ trait ProtoType extends Type { - def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean + def isMatchedBy(tp: Type, keepConstraint: Boolean = false)(implicit ctx: Context): Boolean def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T def map(tm: TypeMap)(implicit ctx: Context): ProtoType diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index e45ab1c4a486..c8a13900b801 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -524,7 +524,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case result: AmbiguousImplicits => "Ambiguous Implicit: " ~ toText(result.alt1.ref) ~ " and " ~ toText(result.alt2.ref) case _ => - "?Unknown Implicit Result?" + result.getClass + "Search Failure: " ~ toText(result.tree) } } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 6cbc0526b9d6..5961935e02b0 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -300,7 +300,7 @@ object messages { val explanation: String = "" } - case class NotAMember(site: Type, name: Name, selected: String)(implicit ctx: Context) + case class NotAMember(site: Type, name: Name, selected: String, addendum: String = "")(implicit ctx: Context) extends Message(NotAMemberID) { val kind: String = "Member Not Found" @@ -360,7 +360,7 @@ object messages { ) } - ex"$selected $name is not a member of ${site.widen}$closeMember" + ex"$selected $name is not a member of ${site.widen}$closeMember$addendum" } val explanation: String = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0a3af9b047f0..92e5dd820b0e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -277,10 +277,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => private[this] var _ok = true def ok: Boolean = _ok - def ok_=(x: Boolean): Unit = { - assert(x || ctx.reporter.errorsReported || !ctx.typerState.isCommittable) // !!! DEBUG - _ok = x - } + def ok_=(x: Boolean): Unit = _ok = x /** The function's type after widening and instantiating polytypes * with TypeParamRefs in constraint set @@ -802,7 +799,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * part. Return an optional value to indicate success. */ def tryWithImplicitOnQualifier(fun1: Tree, proto: FunProto)(implicit ctx: Context): Option[Tree] = - if (ctx.mode.is(Mode.FixedQualifier)) None + if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) + // Suppress insertion of apply or implicit conversion on extension method receiver + None else tryInsertImplicitOnQualifier(fun1, proto, ctx.typerState.ownedVars) flatMap { fun2 => tryEither { @@ -1118,8 +1117,11 @@ trait Applications extends Compatibility { self: Typer with Dynamic => /** Is given method reference applicable to type arguments `targs` and argument trees `args`? * @param resultType The expected result type of the application */ - def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - ctx.test(implicit ctx => new ApplicableToTrees(methRef, targs, args, resultType).success) + def isApplicable(methRef: TermRef, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { + def isApp(implicit ctx: Context): Boolean = + new ApplicableToTrees(methRef, targs, args, resultType).success + if (keepConstraint) isApp else ctx.test(implicit ctx => isApp) + } /** Is given method reference applicable to type arguments `targs` and argument trees `args` without inferring views? * @param resultType The expected result type of the application @@ -1137,8 +1139,8 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * possibly after inserting an `apply`? * @param resultType The expected result type of the application */ - def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type)(implicit ctx: Context): Boolean = - onMethod(tp, isApplicable(_, targs, args, resultType)) + def isApplicable(tp: Type, targs: List[Type], args: List[Tree], resultType: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = + onMethod(tp, isApplicable(_, targs, args, resultType, keepConstraint)) /** Is given type applicable to argument types `args`, possibly after inserting an `apply`? * @param resultType The expected result type of the application @@ -1491,7 +1493,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => ) if (alts2.isEmpty && !ctx.isAfterTyper) alts.filter(alt => - isApplicable(alt, targs, args, resultType) + isApplicable(alt, targs, args, resultType, keepConstraint = false) ) else alts2 @@ -1511,14 +1513,14 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } case pt @ PolyProto(targs1, pt1) if targs.isEmpty => - val alts1 = alts filter pt.isMatchedBy + val alts1 = alts.filter(pt.isMatchedBy(_)) resolveOverloaded(alts1, pt1, targs1.tpes) case defn.FunctionOf(args, resultType, _, _) => narrowByTypes(alts, args, resultType) case pt => - val compat = alts.filter(normalizedCompatible(_, pt)) + val compat = alts.filter(normalizedCompatible(_, pt, keepConstraint = false)) if (compat.isEmpty) /* * the case should not be moved to the enclosing match @@ -1691,7 +1693,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } val app = typed(untpd.Apply(core, untpd.TypedSplice(receiver) :: Nil), pt1, ctx.typerState.ownedVars)( - ctx.addMode(Mode.FixedQualifier)) + ctx.addMode(Mode.SynthesizeExtMethodReceiver)) if (!app.symbol.is(Extension)) ctx.error(em"not an extension method: $methodRef", receiver.sourcePos) app diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index df053e84d91e..ffd36af6feeb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -93,8 +93,9 @@ object Implicits { def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = { def methodCandidateKind(mt: MethodType, approx: Boolean) = - if (!mt.isImplicitMethod && - mt.paramInfos.lengthCompare(1) == 0 && { + if (mt.isImplicitMethod) + viewCandidateKind(normalize(mt, pt), argType, resType) + else if (mt.paramInfos.lengthCompare(1) == 0 && { var formal = widenSingleton(mt.paramInfos.head) if (approx) formal = wildApprox(formal) ctx.test(implicit ctx => argType relaxed_<:< formal) @@ -1274,9 +1275,13 @@ trait Implicits { self: Typer => case reason => if (contextual) bestImplicit(contextual = false).recoverWith { - failure2 => reason match { - case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure - case _ => failure2 + failure2 => failure2.reason match { + case _: AmbiguousImplicits => failure2 + case _ => + reason match { + case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure + case _ => List(failure, failure2).maxBy(_.tree.treeSize) + } } } else failure @@ -1635,4 +1640,6 @@ final class TermRefSet(implicit ctx: Context) { foreach(tr => buffer += tr) buffer.toList } + + override def toString = toList.toString } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 27f677324e7e..474da45bb114 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -35,12 +35,28 @@ object ProtoTypes { def isCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean = (tp.widenExpr relaxed_<:< pt.widenExpr) || viewExists(tp, pt) - /** Test compatibility after normalization in a fresh typerstate. */ - def normalizedCompatible(tp: Type, pt: Type)(implicit ctx: Context): Boolean = - ctx.test { implicit ctx => + /** Test compatibility after normalization. + * Do this in a fresh typerstate unless `keepConstraint` is true. + */ + def normalizedCompatible(tp: Type, pt: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { + def testCompat(implicit ctx: Context): Boolean = { val normTp = normalize(tp, pt) isCompatible(normTp, pt) || pt.isRef(defn.UnitClass) && normTp.isParameterless } + if (keepConstraint) + tp.widenSingleton match { + case poly: PolyType => + // We can't keep the constraint in this case, since we have to add type parameters + // to it, but there's no place to associate them with type variables. + // So we'd get a "inconsistent: no typevars were added to committable constraint" + // assertion failure in `constrained`. To do better, we'd have to change the + // constraint handling architecture so that some type parameters are committable + // and others are not. But that's a whole different ballgame. + normalizedCompatible(tp, pt, keepConstraint = false) + case _ => testCompat + } + else ctx.test(implicit ctx => testCompat) + } private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { case _: OrType => true @@ -89,7 +105,7 @@ object ProtoTypes { /** A trait for prototypes that match all types */ trait MatchAlways extends ProtoType { - def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = true + def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = true def map(tm: TypeMap)(implicit ctx: Context): ProtoType = this def fold[T](x: T, ta: TypeAccumulator[T])(implicit ctx: Context): T = x override def toString: String = getClass.toString @@ -131,13 +147,13 @@ object ProtoTypes { case _ => false } - override def isMatchedBy(tp1: Type)(implicit ctx: Context): Boolean = { + override def isMatchedBy(tp1: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { name == nme.WILDCARD || hasUnknownMembers(tp1) || { val mbr = if (privateOK) tp1.member(name) else tp1.nonPrivateMember(name) def qualifies(m: SingleDenotation) = memberProto.isRef(defn.UnitClass) || - tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto) + tp1.isValueType && compat.normalizedCompatible(NamedType(tp1, name, m), memberProto, keepConstraint) // Note: can't use `m.info` here because if `m` is a method, `m.info` // loses knowledge about `m`'s default arguments. mbr match { // hasAltWith inlined for performance @@ -234,8 +250,13 @@ object ProtoTypes { extends UncachedGroundType with ApplyingProto with FunOrPolyProto { override def resultType(implicit ctx: Context): Type = resType - def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = - typer.isApplicable(tp, Nil, unforcedTypedArgs, resultType) + def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { + val args = unforcedTypedArgs + def isPoly(tree: Tree) = tree.tpe.widenSingleton.isInstanceOf[PolyType] + // See remark in normalizedCompatible for why we can't keep the constraint + // if one of the arguments has a PolyType. + typer.isApplicable(tp, Nil, args, resultType, keepConstraint && !args.exists(isPoly)) + } def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto = if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this @@ -292,11 +313,13 @@ object ProtoTypes { * with unknown parameter types - this will then cause a * "missing parameter type" error */ - private def typedArgs(force: Boolean): List[Tree] = { - if (state.typedArgs.size != args.length) - state.typedArgs = args.mapconserve(cacheTypedArg(_, typer.typed(_), force)) - state.typedArgs - } + private def typedArgs(force: Boolean): List[Tree] = + if (state.typedArgs.size == args.length) state.typedArgs + else { + val args1 = args.mapconserve(cacheTypedArg(_, typer.typed(_), force)) + if (force || !args1.contains(WildcardType)) state.typedArgs = args1 + args1 + } def typedArgs: List[Tree] = typedArgs(force = true) def unforcedTypedArgs: List[Tree] = typedArgs(force = false) @@ -379,7 +402,7 @@ object ProtoTypes { override def resultType(implicit ctx: Context): Type = resType - def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = + def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = ctx.typer.isApplicable(tp, argType :: Nil, resultType) || { resType match { case SelectionProto(name: TermName, mbrType, _, _) => @@ -422,7 +445,7 @@ object ProtoTypes { override def resultType(implicit ctx: Context): Type = resType - override def isMatchedBy(tp: Type)(implicit ctx: Context): Boolean = { + override def isMatchedBy(tp: Type, keepConstraint: Boolean)(implicit ctx: Context): Boolean = { def isInstantiatable(tp: Type) = tp.widen match { case tp: PolyType => tp.paramNames.length == targs.length case _ => false diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index d66fe8eeb16d..c04066ad24b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -234,38 +234,46 @@ trait TypeAssigner { test(tpe, true) } - /** The type of a selection with `name` of a tree with type `site`. - */ - def selectionType(site: Type, name: Name, pos: SourcePosition)(implicit ctx: Context): Type = { - val mbr = site.member(name) + /** The type of the selection `tree`, where `qual1` is the typed qualifier part. */ + def selectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = { + var qualType = qual1.tpe.widenIfUnstable + if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR) + // constructors are selected on typeconstructor, type arguments are passed afterwards + qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos) + else if (!qualType.isInstanceOf[TermType]) + qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos) + val name = tree.name + val mbr = qualType.member(name) if (reallyExists(mbr)) - site.select(name, mbr) - else if (site.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) + qualType.select(name, mbr) + else if (qualType.derivesFrom(defn.DynamicClass) && !Dynamic.isDynamicMethod(name)) TryDynamicCallType - else if (site.isErroneous || name.toTermName == nme.ERROR) + else if (qualType.isErroneous || name.toTermName == nme.ERROR) UnspecifiedErrorType + else if (name == nme.CONSTRUCTOR) + errorType(ex"$qualType does not have a constructor", tree.sourcePos) else { - def kind = if (name.isTypeName) "type" else "value" - def addendum = - if (site.derivesFrom(defn.DynamicClass)) "\npossible cause: maybe a wrong Dynamic method signature?" - else "" - errorType( - if (name == nme.CONSTRUCTOR) ex"$site does not have a constructor" - else NotAMember(site, name, kind), - pos) + val kind = if (name.isTypeName) "type" else "value" + val addendum = + if (qualType.derivesFrom(defn.DynamicClass)) + "\npossible cause: maybe a wrong Dynamic method signature?" + else qual1.getAttachment(Typer.HiddenSearchFailure) match { + case Some(failure) if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] => + i""". + |An extension method was tried, but could not be fully constructed: + | + | ${failure.tree.show.replace("\n", "\n ")}""" + case _ => "" + } + errorType(NotAMember(qualType, name, kind, addendum), tree.sourcePos) } } - /** The selection type, which is additionally checked for accessibility. + /** The type of the selection in `tree`, where `qual1` is the typed qualifier part. + * The selection type is additionally checked for accessibility. */ def accessibleSelectionType(tree: untpd.RefTree, qual1: Tree)(implicit ctx: Context): Type = { - var qualType = qual1.tpe.widenIfUnstable - if (!qualType.hasSimpleKind && tree.name != nme.CONSTRUCTOR) - // constructors are selected on typeconstructor, type arguments are passed afterwards - qualType = errorType(em"$qualType takes type parameters", qual1.sourcePos) - else if (!qualType.isInstanceOf[TermType]) - qualType = errorType(em"$qualType is illegal as a selection prefix", qual1.sourcePos) - val ownType = selectionType(qualType, tree.name, tree.sourcePos) + val ownType = selectionType(tree, qual1) if (tree.getAttachment(desugar.SuppressAccessCheck).isDefined) ownType else ensureAccessible(ownType, qual1.isInstanceOf[Super], tree.sourcePos) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c792f939aaf4..c3b75ee6500f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -40,7 +40,6 @@ import transform.SymUtils._ import transform.TypeUtils._ import reporting.trace - object Typer { /** The precedence of bindings which determines which of several bindings will be @@ -75,6 +74,11 @@ object Typer { * the `()` was dropped by the Typer. */ private val DroppedEmptyArgs = new Property.Key[Unit] + + /** An attachment that indicates a failed conversion or extension method + * search was tried on a tree. This will in some cases be reported in error messages + */ + private[typer] val HiddenSearchFailure = new Property.Key[SearchFailure] } class Typer extends Namer @@ -2207,7 +2211,9 @@ class Typer extends Namer def tryImplicit(fallBack: => Tree) = tryInsertImplicitOnQualifier(tree, pt.withContext(ctx), locked).getOrElse(fallBack) - if (ctx.mode.is(Mode.FixedQualifier)) tree + if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) + // Suppress insertion of apply or implicit conversion on extension method receiver + tree else pt match { case pt @ FunProto(Nil, _) if tree.symbol.allOverriddenSymbols.exists(_.info.isNullaryMethod) && @@ -2391,6 +2397,13 @@ class Typer extends Namer } } + /** Reveal ignored parts of prototype when synthesizing the receiver + * of an extension method. This is necessary for pos/i5773a.scala + */ + def revealProtoOfExtMethod(tp: Type)(implicit ctx: Context): Type = + if (ctx.mode.is(Mode.SynthesizeExtMethodReceiver)) tp.deepenProto + else tp + def adaptNoArgsImplicitMethod(wtp: MethodType): Tree = { assert(wtp.isImplicitMethod) val tvarsToInstantiate = tvarsInParams(tree, locked).distinct @@ -2400,13 +2413,16 @@ class Typer extends Namer def dummyArg(tp: Type) = untpd.Ident(nme.???).withTypeUnchecked(tp) def addImplicitArgs(implicit ctx: Context) = { - def implicitArgs(formals: List[Type], argIndex: Int): List[Tree] = formals match { + def implicitArgs(formals: List[Type], argIndex: Int, pt: Type): List[Tree] = formals match { case Nil => Nil case formal :: formals1 => val arg = inferImplicitArg(formal, tree.span.endPos) arg.tpe match { - case failed: SearchFailureType - if !failed.isInstanceOf[AmbiguousImplicits] && !tree.symbol.hasDefaultParams => + case failed: AmbiguousImplicits => + val pt1 = pt.deepenProto + if ((pt1 `ne` pt) && resultMatches(wtp, pt1)) implicitArgs(formals, argIndex, pt1) + else arg :: implicitArgs(formals1, argIndex + 1, pt1) + case failed: SearchFailureType if !tree.symbol.hasDefaultParams => // no need to search further, the adapt fails in any case // the reason why we continue inferring arguments in case of an AmbiguousImplicits // is that we need to know whether there are further errors. @@ -2420,10 +2436,10 @@ class Typer extends Namer if (wtp.isParamDependent && arg.tpe.exists) formals1.mapconserve(f1 => safeSubstParam(f1, wtp.paramRefs(argIndex), arg.tpe)) else formals1 - arg :: implicitArgs(formals2, argIndex + 1) + arg :: implicitArgs(formals2, argIndex + 1, pt) } } - val args = implicitArgs(wtp.paramInfos, 0) + val args = implicitArgs(wtp.paramInfos, 0, pt) def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => @@ -2599,10 +2615,12 @@ class Typer extends Namer case _ => tp } + def resultMatches(wtp: Type, pt: Type) = + constrainResult(tree.symbol, wtp, revealProtoOfExtMethod(followAlias(pt))) + def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) def functionExpected = defn.isFunctionType(ptNorm) - def resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) def needsEta = pt match { case _: SingletonType => false case IgnoredProto(_: FunOrPolyProto) => false @@ -2613,8 +2631,9 @@ class Typer extends Namer case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) case wtp: MethodType if wtp.isImplicitMethod && - ({ resMatch = resultMatch; resMatch } || !functionExpected) => - if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) + ({ resMatch = resultMatches(wtp, pt); resMatch } || !functionExpected) => + if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) + adaptNoArgsImplicitMethod(wtp) else { // Don't proceed with implicit search if result type cannot match - the search // will likely be under-constrained, which means that an unbounded number of alternatives @@ -2724,11 +2743,14 @@ class Typer extends Namer checkImplicitConversionUseOK(inferred.symbol, tree.posd) readapt(inferred)(ctx.retractMode(Mode.ImplicitsEnabled)) case failure: SearchFailure => - if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) + if (pt.isInstanceOf[ProtoType] && !failure.isAmbiguous) { // don't report the failure but return the tree unchanged. This // will cause a failure at the next level out, which usually gives - // a better error message. + // a better error message. To compensate, store the encountered failure + // as an attachment, so that it can be reported later as an addendum. + tree.putAttachment(HiddenSearchFailure, failure) tree + } else recover(failure.reason) } else recover(NoMatchingImplicits) diff --git a/tests/neg/i5773.scala b/tests/neg/i5773.scala new file mode 100644 index 000000000000..6e98d5e0c93e --- /dev/null +++ b/tests/neg/i5773.scala @@ -0,0 +1,32 @@ +trait Semigroup[T] { + def (lhs: T) append (rhs: T): T + def (lhs: Int) appendS (rhs: T): T = ??? +} + +object Semigroup { + implicit object stringAppend extends Semigroup[String] { + override def (lhs: String) append (rhs: String): String = lhs + rhs + } + + implicit def sumSemigroup[N](implicit N: Numeric[N]): Semigroup[N] = new { + override def (lhs: N) append (rhs: N): N = N.plus(lhs, rhs) + def (lhs: Int) appendS (rhs: N): N = ??? // N.plus(lhs, rhs) + } +} + + +object Main { + import Semigroup.sumSemigroup // this is not sufficient + def f1 = { + println(1 appendS 2) // error This should give the following error message: +/* +21 | println(1 appendS 2) + | ^^^^^^^^^ + |value appendS is not a member of Int. + |An extension method was tried, but could not be fully constructed: + | + | Semigroup.sumSemigroup[Any](/* ambiguous */implicitly[Numeric[Any]]).appendS() +one error found +*/ + } +} \ No newline at end of file diff --git a/tests/pos/i5773.scala b/tests/pos/i5773.scala new file mode 100644 index 000000000000..e799691a249d --- /dev/null +++ b/tests/pos/i5773.scala @@ -0,0 +1,36 @@ +trait Semigroup[T] { + def (lhs: T) append (rhs: T): T +} + +object Semigroup { + implicit object stringAppend extends Semigroup[String] { + override def (lhs: String) append (rhs: String): String = lhs + rhs + } + + implicit def sumSemigroup[N](implicit N: Numeric[N]): Semigroup[N] = new { + override def (lhs: N) append (rhs: N): N = ??? // N.plus(lhs, rhs) + } + + implicit class SumSemiGroupDeco[N](implicit N: Numeric[N]) extends Semigroup[N] { + override def (lhs: N) append (rhs: N): N = ??? // N.plus(lhs, rhs) + } +} + +object Main { + import Semigroup.sumSemigroup // this is not sufficient + def f1 = { + import Semigroup.stringAppend // necessary to make the extension method visible + println("Hi" append " mum") + println(1 append 2) + } + + def f2 = { + implicit val intSumAppend: Semigroup[Int] = sumSemigroup[Int] + println(3 append 4) + } + + def f3 = { + import Semigroup.SumSemiGroupDeco + sumSemigroup.append(1)(2) + } +} \ No newline at end of file diff --git a/tests/pos/i5773a.scala b/tests/pos/i5773a.scala new file mode 100644 index 000000000000..2071402609c3 --- /dev/null +++ b/tests/pos/i5773a.scala @@ -0,0 +1,17 @@ +trait Semigroup[T] { + def (x: T) combine (y: T): T +} +object Test { + implicit val IntSemigroup: Semigroup[Int] = new { + def (x: Int) combine (y: Int): Int = x + y + } + implicit def OptionSemigroup[T: Semigroup]: Semigroup[Option[T]] = new { + def (x: Option[T]) combine (y: Option[T]): Option[T] = for { + x0 <- x + y0 <- y + } yield x0.combine(y0) + } + 1.combine(2) + Some(1).combine(Some(2)) + Option(1) combine Option(2) +}