diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index bcd80977efd5..8ea53faf7c4e 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -62,7 +62,6 @@ class ScalaSettings extends Settings.SettingGroup { */ val Xhelp: Setting[Boolean] = BooleanSetting("-X", "Print a synopsis of advanced options.") val XnoForwarders: Setting[Boolean] = BooleanSetting("-Xno-forwarders", "Do not generate static forwarders in mirror classes.") - val XminImplicitSearchDepth: Setting[Int] = IntSetting("-Xmin-implicit-search-depth", "Set number of levels of implicit searches undertaken before checking for divergence.", 5) val XmaxInlines: Setting[Int] = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32) val XmaxClassfileName: Setting[Int] = IntSetting("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, 72 to 255) val Xmigration: Setting[ScalaVersion] = VersionSetting("-Xmigration", "Warn about constructs whose behavior may have changed since version.") diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 7ea68b0ae816..cf584f445689 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,7 @@ import Uniques._ import ast.Trees._ import ast.untpd import util.{FreshNameCreator, NoSource, SimpleIdentityMap, SourceFile} -import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, TypeAssigner, Typer} +import typer.{Implicits, ImportInfo, Inliner, NamerContextOps, SearchHistory, SearchRoot, TypeAssigner, Typer} import Implicits.ContextualImplicits import config.Settings._ import config.Config @@ -555,7 +555,7 @@ object Contexts { moreProperties = Map.empty store = initialStore.updated(settingsStateLoc, settingsGroup.defaultState) typeComparer = new TypeComparer(this) - searchHistory = new SearchHistory(0, Map()) + searchHistory = new SearchRoot gadt = EmptyGADTMap } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 588d2abe1743..a0f7ff9111f4 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -264,9 +264,8 @@ trait Symbols { this: Context => def newDefaultConstructor(cls: ClassSymbol): TermSymbol = newConstructor(cls, EmptyFlags, Nil, Nil) - /** Create a synthetic lazy implicit value */ - def newLazyImplicit(info: Type, coord: Coord): TermSymbol = - newSymbol(owner, LazyImplicitName.fresh(), Lazy, info, coord = coord) + def newLazyImplicit(info: Type, coord: Coord = NoCoord): TermSymbol = + newSymbol(owner, LazyImplicitName.fresh(), EmptyFlags, info, coord = coord) /** Create a symbol representing a selftype declaration for class `cls`. */ def newSelfSym(cls: ClassSymbol, name: TermName = nme.WILDCARD, selfInfo: Type = NoType): TermSymbol = diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 576e69e790cc..d8d5d17182e5 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1463,6 +1463,14 @@ object Types { case _ => this } + /** The set of distinct symbols referred to by this type, after all aliases are expanded */ + def coveringSet(implicit ctx: Context): Set[Symbol] = + (new CoveringSetAccumulator).apply(Set.empty[Symbol], this) + + /** The number of applications and refinements in this type, after all aliases are expanded */ + def typeSize(implicit ctx: Context): Int = + (new TypeSizeAccumulator).apply(0, this) + /** Convert to text */ def toText(printer: Printer): Text = printer.toText(this) @@ -4902,6 +4910,39 @@ object Types { } } + class TypeSizeAccumulator(implicit ctx: Context) extends TypeAccumulator[Int] { + def apply(n: Int, tp: Type): Int = tp match { + case tp: AppliedType => + foldOver(n + 1, tp) + case tp: RefinedType => + foldOver(n + 1, tp) + case tp: TypeRef if tp.info.isTypeAlias => + apply(n, tp.superType) + case _ => + foldOver(n, tp) + } + } + + class CoveringSetAccumulator(implicit ctx: Context) extends TypeAccumulator[Set[Symbol]] { + def apply(cs: Set[Symbol], tp: Type): Set[Symbol] = { + val sym = tp.typeSymbol + tp match { + case tp if tp.isTopType || tp.isBottomType => + cs + case tp: AppliedType => + foldOver(cs + sym, tp) + case tp: RefinedType => + foldOver(cs + sym, tp) + case tp: TypeRef if tp.info.isTypeAlias => + apply(cs, tp.superType) + case tp: TypeBounds => + foldOver(cs, tp) + case other => + foldOver(cs + sym, tp) + } + } + } + // ----- Name Filters -------------------------------------------------- /** A name filter selects or discards a member name of a type `pre`. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ee31378b971d..118aba4ea0e6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -3,7 +3,7 @@ package dotc package typer import core._ -import ast.{Trees, untpd, tpd} +import ast.{Trees, TreeTypeMap, untpd, tpd} import util.Positions._ import util.Stats.{track, record, monitored} import printing.{Showable, Printer} @@ -33,6 +33,7 @@ import config.Config import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.trace +import annotation.tailrec import scala.annotation.internal.sharable @@ -40,11 +41,6 @@ import scala.annotation.internal.sharable object Implicits { import tpd._ - /** A reference to an implicit value to be made visible on the next nested call to - * inferImplicitArg with a by-name expected type. - */ - val DelayedImplicit: Property.Key[TermRef] = new Property.Key - /** An implicit definition `implicitRef` that is visible under a different name, `alias`. * Gets generated if an implicit ref is imported via a renaming import. */ @@ -654,45 +650,13 @@ trait Implicits { self: Typer => assumedCanEqual(tp1, tp2) || !hasEq(tp1) && !hasEq(tp2) } - /** The context to be used when resolving a by-name implicit argument. - * This makes any implicit stored under `DelayedImplicit` visible and - * stores in turn the given `lazyImplicit` as new `DelayedImplicit`. - */ - def lazyImplicitCtx(lazyImplicit: Symbol): Context = { - val lctx = ctx.fresh - for (delayedRef <- ctx.property(DelayedImplicit)) - lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) - lctx.setProperty(DelayedImplicit, lazyImplicit.termRef) - } - - /** formalValue: The value type for which an implicit is searched - * lazyImplicit: An implicit symbol to install for nested by-name resolutions - * argCtx : The context to be used for searching the implicit argument - */ - val (formalValue, lazyImplicit, argCtx) = formal match { - case ExprType(fv) => - val lazyImplicit = ctx.newLazyImplicit(fv, pos) - (fv, lazyImplicit, lazyImplicitCtx(lazyImplicit)) - case _ => (formal, NoSymbol, ctx) - } - - inferImplicit(formalValue, EmptyTree, pos)(argCtx) match { - case SearchSuccess(arg, _, _) => - def refersToLazyImplicit = arg.existsSubTree { - case id: Ident => id.symbol == lazyImplicit - case _ => false - } - if (lazyImplicit.exists && refersToLazyImplicit) - Block( - ValDef(lazyImplicit.asTerm, arg.changeOwner(ctx.owner, lazyImplicit)).withPos(pos) :: Nil, - ref(lazyImplicit)) - else - arg + inferImplicit(formal, EmptyTree, pos)(ctx) match { + case SearchSuccess(arg, _, _) => arg case fail @ SearchFailure(failed) => def trySpecialCase(cls: ClassSymbol, handler: Type => Tree, ifNot: => Tree) = { - val base = formalValue.baseType(cls) - if (base <:< formalValue) { - // With the subtype test we enforce that the searched type `formalValue` is of the right form + val base = formal.baseType(cls) + if (base <:< formal) { + // With the subtype test we enforce that the searched type `formal` is of the right form handler(base).orElse(ifNot) } else ifNot @@ -847,15 +811,13 @@ trait Implicits { self: Typer => * @param argument If an implicit conversion is searched, the argument to which * it should be applied, EmptyTree otherwise. * @param pos The position where errors should be reported. - * !!! todo: catch potential cycles */ def inferImplicit(pt: Type, argument: Tree, pos: Position)(implicit ctx: Context): SearchResult = track("inferImplicit") { assert(ctx.phase.allowsImplicitSearch, if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" else i"type error: ${argument.tpe} does not conform to $pt${err.whyNoMatchStr(argument.tpe, pt)}") trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { - assert(!pt.isInstanceOf[ExprType]) - val result = + val result0 = try { new ImplicitSearch(pt, argument, pos).bestImplicit(contextual = true) } catch { @@ -864,30 +826,33 @@ trait Implicits { self: Typer => throw ce } - result match { - case result: SearchSuccess => - result.tstate.commit() - implicits.println(i"success: $result") - implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}") - result - case result: SearchFailure if result.isAmbiguous => - val deepPt = pt.deepenProto - if (deepPt ne pt) inferImplicit(deepPt, argument, pos) - else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) { - inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match { - case altResult: SearchSuccess => - ctx.migrationWarning( - s"According to new implicit resolution rules, this will be ambiguous:\n${result.reason.explanation}", - pos) - altResult - case _ => - result + val result = + result0 match { + case result: SearchSuccess => + result.tstate.commit() + implicits.println(i"success: $result") + implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}") + result + case result: SearchFailure if result.isAmbiguous => + val deepPt = pt.deepenProto + if (deepPt ne pt) inferImplicit(deepPt, argument, pos) + else if (ctx.scala2Mode && !ctx.mode.is(Mode.OldOverloadingResolution)) { + inferImplicit(pt, argument, pos)(ctx.addMode(Mode.OldOverloadingResolution)) match { + case altResult: SearchSuccess => + ctx.migrationWarning( + s"According to new implicit resolution rules, this will be ambiguous:\n${result.reason.explanation}", + pos) + altResult + case _ => + result + } } - } - else result - case _ => - result - } + else result + case _ => + result0 + } + // If we are at the outermost implicit search then emit the implicit dictionary, if any. + ctx.searchHistory.emitDictionary(pos, result) } } @@ -974,20 +939,25 @@ trait Implicits { self: Typer => }} /** Try to type-check implicit reference, after checking that this is not - * a diverging search + * a diverging search */ def tryImplicit(cand: Candidate, contextual: Boolean): SearchResult = { - val history = ctx.searchHistory nest wildProto - if (history eq ctx.searchHistory) - SearchFailure(new DivergingImplicit(cand.ref, pt, argument)) - else - typedImplicit(cand, contextual)(nestedContext().setNewTyperState().setSearchHistory(history)) + if (ctx.searchHistory.checkDivergence(cand, pt)) + SearchFailure(new DivergingImplicit(cand.ref, pt.widenExpr, argument)) + else { + val history = ctx.searchHistory.nest(cand, pt) + val result = typedImplicit(cand, contextual)(nestedContext().setNewTyperState().setSearchHistory(history)) + result match { + case res: SearchSuccess => + ctx.searchHistory.defineBynameImplicit(pt.widenExpr, res) + case _ => + result + } + } } /** Search a list of eligible implicit references */ def searchImplicits(eligible: List[Candidate], contextual: Boolean): SearchResult = { - val constr = ctx.typerState.constraint - /** Compare previous success with reference and level to determine which one would be chosen, if * an implicit starting with the reference was found. */ @@ -995,22 +965,6 @@ trait Implicits { self: Typer => if (prev.ref eq ref) 0 else nestedContext().test(implicit ctx => compare(prev.ref, ref, prev.level, level)) - /* Seems we don't need this anymore. - def numericValueTieBreak(alt1: SearchSuccess, alt2: SearchSuccess) = { - def isNumeric(tp: Type) = tp.typeSymbol.isNumericValueClass - def isProperSubType(tp1: Type, tp2: Type) = - tp1.isValueSubType(tp2) && !tp2.isValueSubType(tp1) - val rpt = pt.resultType - val rt1 = alt1.ref.widen.resultType - val rt2 = alt2.ref.widen.resultType - if (isNumeric(rpt) && isNumeric(rt1) && isNumeric(rt2)) - if (isProperSubType(rt1, rt2)) alt2 - else if (isProperSubType(rt2, rt1)) alt1 - else NoMatchingImplicitsFailure - else NoMatchingImplicitsFailure - } - */ - /** If `alt1` is also a search success, try to disambiguate as follows: * - If alt2 is preferred over alt1, pick alt2, otherwise return an * ambiguous implicits error. @@ -1098,7 +1052,7 @@ trait Implicits { self: Typer => case _: SearchSuccess => NoMatchingImplicitsFailure } - else result + else result def warnAmbiguousNegation(ambi: AmbiguousImplicits) = ctx.migrationWarning( @@ -1154,26 +1108,39 @@ trait Implicits { self: Typer => /** Find a unique best implicit reference */ def bestImplicit(contextual: Boolean): SearchResult = { - val eligible = - if (contextual) ctx.implicits.eligible(wildProto) - else implicitScope(wildProto).eligible - searchImplicits(eligible, contextual) match { - case result: SearchSuccess => - if (contextual && ctx.mode.is(Mode.InlineableBody)) - PrepareInlineable.markContextualImplicit(result.tree) - result - case failure: SearchFailure => - failure.reason match { - case _: AmbiguousImplicits => failure - case reason => - if (contextual) - bestImplicit(contextual = false).recoverWith { - failure2 => reason match { - case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure - case _ => failure2 - } - } - else failure + // Before searching for contextual or implicit scope candidates we first check if + // there is an under construction or already constructed term with which we can tie + // the knot. + // + // Since any suitable term found is defined as part of this search it will always be + // effectively in a more inner context than any other definition provided by + // explicit definitions. Consequently these terms have the highest priority and no + // other candidates need to be considered. + ctx.searchHistory.recursiveRef(pt) match { + case ref: TermRef => + SearchSuccess(tpd.ref(ref).withPos(pos.startPos), ref, 0)(ctx.typerState) + case _ => + val eligible = + if (contextual) ctx.implicits.eligible(wildProto) + else implicitScope(wildProto).eligible + searchImplicits(eligible, contextual) match { + case result: SearchSuccess => + if (contextual && ctx.mode.is(Mode.InlineableBody)) + PrepareInlineable.markContextualImplicit(result.tree) + result + case failure: SearchFailure => + failure.reason match { + case _: AmbiguousImplicits => failure + case reason => + if (contextual) + bestImplicit(contextual = false).recoverWith { + failure2 => reason match { + case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure + case _ => failure2 + } + } + else failure + } } } } @@ -1191,67 +1158,312 @@ trait Implicits { self: Typer => } } -/** Records the history of currently open implicit searches - * @param searchDepth The number of open searches. - * @param seen A map that records for each class symbol of a type - * that's currently searched for the complexity of the - * type that is searched for (wrt `typeSize`). The map - * is populated only once `searchDepth` is greater than - * the threshold given in the `XminImplicitSearchDepth` setting. +/** + * Records the history of currently open implicit searches. + * + * A search history maintains a list of open implicit searches (`open`) a shortcut flag + * indicating whether any of these are by name (`byname`) and a reference to the root + * search history (`root`) which in turn maintains a possibly empty dictionary of + * recursive implicit terms constructed during this search. + * + * A search history provides operations to create a nested search history, check for + * divergence, enter by name references and definitions in the implicit dictionary, lookup + * recursive references and emit a complete implicit dictionary when the outermost search + * is complete. */ -class SearchHistory(val searchDepth: Int, val seen: Map[ClassSymbol, Int]) { - - /** The number of applications and refinements in this type, after all aliases are expanded */ - private def typeSize(tp: Type)(implicit ctx: Context): Int = { - val accu = new TypeAccumulator[Int] { - def apply(n: Int, tp: Type): Int = tp match { - case tp: AppliedType => - foldOver(n + 1, tp) - case tp: RefinedType => - foldOver(n + 1, tp) - case tp: TypeRef if tp.info.isTypeAlias => - apply(n, tp.superType) - case _ => - foldOver(n, tp) +abstract class SearchHistory { outer => + val root: SearchRoot + val open: List[(Candidate, Type)] + /** Does this search history contain any by name implicit arguments. */ + val byname: Boolean + + /** + * Create the state for a nested implicit search. + * @param cand The candidate implicit to be explored. + * @param pt The target type for the above candidate. + * @result The nested history. + */ + def nest(cand: Candidate, pt: Type)(implicit ctx: Context): SearchHistory = { + new SearchHistory { + val root = outer.root + val open = (cand, pt) :: outer.open + val byname = outer.byname || isByname(pt) + } + } + + def isByname(tp: Type): Boolean = tp.isInstanceOf[ExprType] + + /** + * Check if the supplied candidate implicit and target type indicate a diverging + * implicit search. + * + * @param cand The candidate implicit to be explored. + * @param pt The target type for the above candidate. + * @result True if this candidate/pt are divergent, false otherwise. + */ + def checkDivergence(cand: Candidate, pt: Type)(implicit ctx: Context): Boolean = { + // For full details of the algorithm see the SIP: + // https://docs.scala-lang.org/sips/byname-implicits.html + + val widePt = pt.widenExpr + lazy val ptCoveringSet = widePt.coveringSet + lazy val ptSize = widePt.typeSize + lazy val wildPt = wildApprox(widePt) + + // Unless we are able to tie a recursive knot, we report divergence if there is an + // open implicit using the same candidate implicit definition which has a type which + // is larger (see `typeSize`) and is constructed using the same set of types and type + // constructors (see `coveringSet`). + // + // We are able to tie a recursive knot if there is compatible term already under + // construction which is separated from this context by at least one by name argument + // as we ascend the chain of open implicits to the outermost search context. + + @tailrec + def loop(ois: List[(Candidate, Type)], belowByname: Boolean): Boolean = + ois match { + case Nil => false + case (hd@(cand1, tp)) :: tl => + if (cand1.ref == cand.ref) { + val wideTp = tp.widenExpr + lazy val wildTp = wildApprox(wideTp) + if (belowByname && (wildTp <:< wildPt)) false + else if ((wideTp.typeSize < ptSize && wideTp.coveringSet == ptCoveringSet) || (wildTp == wildPt)) true + else loop(tl, isByname(tp) || belowByname) + } + else loop(tl, isByname(tp) || belowByname) + } + + loop(open, isByname(pt)) + } + + /** + * Return the reference, if any, to a term under construction or already constructed in + * the current search history corresponding to the supplied target type. + * + * A term is eligible if its type is a subtype of the target type and either it has + * already been constructed and is present in the current implicit dictionary, or it is + * currently under construction and is separated from the current search context by at + * least one by name argument position. + * + * Note that because any suitable term found is defined as part of this search it will + * always be effectively in a more inner context than any other definition provided by + * explicit definitions. Consequently these terms have the highest priority and no other + * candidates need to be considered. + * + * @param pt The target type being searched for. + * @result The corresponding dictionary reference if any, NoType otherwise. + */ + def recursiveRef(pt: Type)(implicit ctx: Context): Type = { + val widePt = pt.widenExpr + + refBynameImplicit(widePt).orElse { + val bynamePt = isByname(pt) + if (!byname && !bynamePt) NoType // No recursion unless at least one open implicit is by name ... + else { + // We are able to tie a recursive knot if there is compatible term already under + // construction which is separated from this context by at least one by name + // argument as we ascend the chain of open implicits to the outermost search + // context. + @tailrec + def loop(ois: List[(Candidate, Type)], belowByname: Boolean): Type = { + ois match { + case (hd@(cand, tp)) :: tl if (belowByname || isByname(tp)) && tp.widenExpr <:< widePt => tp + case (_, tp) :: tl => loop(tl, belowByname || isByname(tp)) + case _ => NoType + } + } + + loop(open, bynamePt) match { + case NoType => NoType + case tp => ctx.searchHistory.linkBynameImplicit(tp.widenExpr) + } } } - accu.apply(0, tp) } - /** Check for possible divergence. If one is detected return the current search history - * (this will be used as a criterion to abandon the implicit search in rankImplicits). - * If no divergence is detected, produce a new search history nested in the current one - * which records that we are now also looking for type `proto`. + // The following are delegated to the root of this search history. + def linkBynameImplicit(tpe: Type)(implicit ctx: Context): TermRef = root.linkBynameImplicit(tpe) + def refBynameImplicit(tpe: Type)(implicit ctx: Context): Type = root.refBynameImplicit(tpe) + def defineBynameImplicit(tpe: Type, result: SearchSuccess)(implicit ctx: Context): SearchResult = root.defineBynameImplicit(tpe, result) + + // This is NOOP unless at the root of this search history. + def emitDictionary(pos: Position, result: SearchResult)(implicit ctx: Context): SearchResult = result + + override def toString: String = s"SearchHistory(open = $open, byname = $byname)" +} + +/** + * The the state corresponding to the outermost context of an implicit searcch. + */ +final class SearchRoot extends SearchHistory { + val root = this + val open = Nil + val byname = false + + /** The dictionary of recursive implicit types and corresponding terms for this search. */ + var implicitDictionary0: mutable.Map[Type, (TermRef, tpd.Tree)] = null + def implicitDictionary = { + if (implicitDictionary0 == null) + implicitDictionary0 = mutable.Map.empty[Type, (TermRef, tpd.Tree)] + implicitDictionary0 + } + + /** + * Link a reference to an under-construction implicit for the provided type to its + * defining occurrence via the implicit dictionary, creating a dictionary entry for this + * type if one does not yet exist. + * + * @param tpe The type to link. + * @result The TermRef of the corresponding dictionary entry. + */ + override def linkBynameImplicit(tpe: Type)(implicit ctx: Context): TermRef = { + implicitDictionary.get(tpe) match { + case Some((ref, _)) => ref + case None => + val lazyImplicit = ctx.newLazyImplicit(tpe) + val ref = lazyImplicit.termRef + implicitDictionary.put(tpe, (ref, tpd.EmptyTree)) + ref + } + } + + /** + * Look up an implicit dictionary entry by type. + * + * If present yield the TermRef corresponding to the eventual dictionary entry, + * otherwise NoType. * - * As long as `searchDepth` is lower than the `XminImplicitSearchDepth` value - * in settings, a new history is always produced, so the implicit search is always - * undertaken. If `searchDepth` matches or exceeds the `XminImplicitSearchDepth` value, - * we test that the new search is for a class that is either not yet in the set of - * `seen` classes, or the complexity of the type `proto` being searched for is strictly - * lower than the complexity of the type that was previously encountered and that had - * the same class symbol as `proto`. A possible divergence is detected if that test fails. + * @param tpe The type to look up. + * @result The corresponding TermRef, or NoType if none. */ - def nest(proto: Type)(implicit ctx: Context): SearchHistory = { - if (searchDepth < ctx.settings.XminImplicitSearchDepth.value) - new SearchHistory(searchDepth + 1, seen) + override def refBynameImplicit(tpe: Type)(implicit ctx: Context): Type = { + implicitDictionary.get(tpe).map(_._1).getOrElse(NoType) + } + + /** + * Define a pending dictionary entry if any. + * + * If the provided type corresponds to an under-construction by name implicit, then use + * the tree contained in the provided SearchSuccess as its definition, returning an + * updated result referring to dictionary entry. Otherwise return the SearchSuccess + * unchanged. + * + * @param tpe The type for which the entry is to be defined + * @param result The SearchSuccess corresponding to tpe + * @result A SearchResult referring to the newly created dictionary entry if tpe + * is an under-construction by name implicit, the provided result otherwise. + */ + override def defineBynameImplicit(tpe: Type, result: SearchSuccess)(implicit ctx: Context): SearchResult = { + implicitDictionary.get(tpe) match { + case Some((ref, _)) => + implicitDictionary.put(tpe, (ref, result.tree)) + SearchSuccess(tpd.ref(ref).withPos(result.tree.pos), result.ref, result.level)(result.tstate) + case None => result + } + } + + /** + * Emit the implicit dictionary at the completion of an implicit search. + * + * @param pos The position at which the search is elaborated. + * @param result The result of the search prior to substitution of recursive references. + * @result The elaborated result, comprising the implicit dictionary and a result tree + * substituted with references into the dictionary. + */ + override def emitDictionary(pos: Position, result: SearchResult)(implicit ctx: Context): SearchResult = { + if (implicitDictionary == null || implicitDictionary.isEmpty) result else { - val size = typeSize(proto) - def updateMap(csyms: List[ClassSymbol], seen: Map[ClassSymbol, Int]): SearchHistory = csyms match { - case csym :: csyms1 => - seen get csym match { - // proto complexity is >= than the last time it was seen → diverge - case Some(prevSize) if size >= prevSize => this - case _ => updateMap(csyms1, seen.updated(csym, size)) + result match { + case failure: SearchFailure => failure + case success @ SearchSuccess(tree, _, _) => + import tpd._ + + // We might have accumulated dictionary entries for by name implicit arguments + // which are not in fact used recursively either directly in the outermost result + // term, or indirectly via other dictionary entries. We prune these out, recursively + // eliminating entries until all remaining entries are at least transtively referred + // to in the outermost result term. + @tailrec + def prune(trees: List[Tree], pending: List[(TermRef, Tree)], acc: List[(TermRef, Tree)]): List[(TermRef, Tree)] = pending match { + case Nil => acc + case ps => + val (in, out) = ps.partition { + case (vref, rhs) => + trees.exists(_.existsSubTree { + case id: Ident => id.symbol == vref.symbol + case _ => false + }) + } + if (in.isEmpty) acc + else prune(in.map(_._2) ++ trees, out, in ++ acc) + } + + val pruned = prune(List(tree), implicitDictionary.map(_._2).toList, Nil) + implicitDictionary0 = null + if (pruned.isEmpty) result + else { + // If there are any dictionary entries remaining after pruning, construct a dictionary + // class of the form, + // + // class { + // val $_lazy_implicit_$0 = ... + // ... + // val $_lazy_implicit_$n = ... + // } + // + // Where the RHSs of the $_lazy_implicit_$n are the terms used to populate the dictionary + // via defineByNameImplicit. + // + // The returned search result is then of the form, + // + // { + // class { ... } + // val $_lazy_implicit_$nn = new + // result.tree // with dictionary references substituted in + // } + + val parents = List(defn.ObjectType, defn.SerializableType) + val classSym = ctx.newNormalizedClassSymbol(ctx.owner, LazyImplicitName.fresh().toTypeName, Synthetic | Final, parents, coord = pos) + val vsyms = pruned.map(_._1.symbol) + val nsyms = vsyms.map(vsym => ctx.newSymbol(classSym, vsym.name, EmptyFlags, vsym.info, coord = pos).entered) + val vsymMap = (vsyms zip nsyms).toMap + + val rhss = pruned.map(_._2) + // Substitute dictionary references into dictionary entry RHSs + val rhsMap = new TreeTypeMap(treeMap = { + case id: Ident if vsymMap.contains(id.symbol) => + tpd.ref(vsymMap(id.symbol)) + case tree => tree + }) + val nrhss = rhss.map(rhsMap(_)) + + val vdefs = (nsyms zip nrhss) map { + case (nsym, nrhs) => ValDef(nsym.asTerm, nrhs) + } + + val constr = ctx.newConstructor(classSym, Synthetic, Nil, Nil).entered + val classDef = ClassDef(classSym, DefDef(constr), vdefs) + + val valSym = ctx.newLazyImplicit(classSym.typeRef, pos) + val inst = ValDef(valSym, New(classSym.typeRef, Nil)) + + // Substitute dictionary references into outermost result term. + val resMap = new TreeTypeMap(treeMap = { + case id: Ident if vsymMap.contains(id.symbol) => + Select(tpd.ref(valSym), id.name) + case tree => tree + }) + + val res = resMap(tree) + + val blk = Block(classDef :: inst :: Nil, res) + + success.copy(tree = blk)(success.tstate) } - case _ => - new SearchHistory(searchDepth + 1, seen) } - if (proto.classSymbols.isEmpty) this - else updateMap(proto.classSymbols, seen) } } - - override def toString: String = s"SearchHistory(depth = $searchDepth, seen = $seen)" } /** A set of term references where equality is =:= */ diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 0f6f6201e1e7..46b2fb96025c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -51,7 +51,7 @@ object ProtoTypes { */ def constrainResult(mt: Type, pt: Type)(implicit ctx: Context): Boolean = { val savedConstraint = ctx.typerState.constraint - val res = pt match { + val res = pt.widenExpr match { case pt: FunProto => mt match { case mt: MethodType => constrainResult(resultTypeApprox(mt), pt.resultType) diff --git a/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala b/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala new file mode 100644 index 000000000000..eaf133bfe0ad --- /dev/null +++ b/compiler/test/dotty/tools/dotc/typer/DivergenceChecker.scala @@ -0,0 +1,73 @@ +package dotty.tools.dotc.typer + +import dotty.tools.DottyTest +import dotty.tools.dotc.ast.Trees._ +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Symbols._ +import dotty.tools.dotc.core.Types._ + +import org.junit.Test +import org.junit.Assert.{ assertFalse, assertTrue, fail } + +class DivergenceCheckerTests extends DottyTest { + @Test + def testCoveringSet: Unit = { + val source = """ + |class A + |class B extends A + """.stripMargin + + val types = List( + "A", + "B", + "List[_]", + "List[Int]", + "List[AnyRef]", + "List[String]", + "List[List[(A, B)]]", + "List[List[(A, B) { type Baz = Int }]] { type Foo = A }" + ) + + val elements = List("A", "B", "Nothing", "Object", "String", "Tuple2[_, _]", "Int", "List[_]") + + checkTypes(source, List(types, elements)) { + case (List(tpes, elements0), context) => + implicit val ctx = context + + val List(a, b, n, o, s, t2, i, l) = elements0.map(_.typeConstructor) + val expectedCoveringSets = List( + Set(a), + Set(b), + Set(l), + Set(l, i), + Set(l, o), + Set(l, s), + Set(l, t2, a, b), + Set(l, t2, a, b, i) + ).map(_.map(_.dealias.typeSymbol)) + + val expectedSizes = List( + 0, + 0, + 1, + 1, + 1, + 1, + 3, + 5 + ) + + (tpes, expectedSizes, expectedCoveringSets).zipped.foreach { + case (tpe, expectedSize, expectedCoveringSet) => + val size = tpe.typeSize + val cs = tpe.coveringSet + + assertTrue(size == expectedSize) + assertTrue(cs == expectedCoveringSet) + } + + case _ => fail + } + } +} diff --git a/tests/neg/byname-implicits-11.scala b/tests/neg/byname-implicits-11.scala new file mode 100644 index 000000000000..aa24cbc80098 --- /dev/null +++ b/tests/neg/byname-implicits-11.scala @@ -0,0 +1,9 @@ +trait Foo[T] + +object Foo { + implicit def foo[T](implicit fooFoo: => Foo[Foo[T]]): Foo[T] = ??? +} + +object Test { + implicitly[Foo[Int]] // error +} diff --git a/tests/neg/byname-implicits-16.scala b/tests/neg/byname-implicits-16.scala new file mode 100644 index 000000000000..b61ccec8f7eb --- /dev/null +++ b/tests/neg/byname-implicits-16.scala @@ -0,0 +1,16 @@ +object Test { + class Z + class O[T] + class E[T] + + class Expand[T, U] + object Expand { + implicit def expando[T]: Expand[O[T], E[O[T]]] = ??? + implicit def expande[T]: Expand[E[T], O[E[T]]] = ??? + } + + implicit def mkN[T, U](implicit e: => Expand[O[T], U], u: => U): O[T] = ??? + implicit def mkN[T, U](implicit e: => Expand[E[T], U], u: => U): E[T] = ??? + + implicitly[O[Z]] // error +} diff --git a/tests/neg/byname-implicits-18.scala b/tests/neg/byname-implicits-18.scala new file mode 100644 index 000000000000..2b667424a1a5 --- /dev/null +++ b/tests/neg/byname-implicits-18.scala @@ -0,0 +1,53 @@ +/* + * Demo of using by name implicits to resolve (hidden) divergence issues when + * traversing recursive generic structures. + * + * See http://stackoverflow.com/questions/25923974 + */ +sealed trait HList +object HList { + implicit class Syntax[L <: HList](l: L) { + def ::[U](u: U): U :: L = new ::(u, l) + } +} + +sealed trait HNil extends HList +object HNil extends HNil +case class ::[+H, +T <: HList](head : H, tail : T) extends HList + +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +object Generic { + type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } +} + +object Test extends App { + case class Bootstrap[+A](head: A, tail: Option[Bootstrap[(A, A)]]) + object Bootstrap { + type BootstrapRepr[+A] = A :: Option[Bootstrap[(A, A)]] :: HNil + implicit def bootstrapGen[A]: Generic.Aux[Bootstrap[A], BootstrapRepr[A]] = + new Generic[Bootstrap[A]] { + type Repr = BootstrapRepr[A] + def to(t: Bootstrap[A]): Repr = t.head :: t.tail :: HNil + def from(r: Repr): Bootstrap[A] = Bootstrap(r.head, r.tail.head) + } + } + + class Tc[A] + object Tc { + implicit val tcInt: Tc[Int] = new Tc + implicit def tcOpt[A: Tc]: Tc[Option[A]] = new Tc + implicit def tcTuple[A: Tc, B: Tc]: Tc[(A, B)] = new Tc + implicit val tcHNil: Tc[HNil] = new Tc + implicit def tcHCons[H: Tc, T <: HList: Tc]: Tc[H :: T] = new Tc + implicit def tcGen[A, R <: HList]( + implicit gen: Generic.Aux[A, R], tcR: => Tc[R] + ): Tc[A] = new Tc + } + + implicitly[Tc[Bootstrap[Int]]] // error +} diff --git a/tests/neg/byname-implicits-21.scala b/tests/neg/byname-implicits-21.scala new file mode 100644 index 000000000000..d01ae30fbf9f --- /dev/null +++ b/tests/neg/byname-implicits-21.scala @@ -0,0 +1,21 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait Foo[T] + object Foo { + implicit def fooOption[T](implicit fooT: Foo[T]): Foo[Option[T]] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: => Foo[R]): Foo[T] = ??? + } + + trait A[T] + object A { + implicit def genA[T]: Generic[A[T]] { type Repr = Option[A[A[T]]] } = ??? + } + + implicitly[Foo[A[Unit]]] // error +} diff --git a/tests/neg/byname-implicits-26.scala b/tests/neg/byname-implicits-26.scala new file mode 100644 index 000000000000..4f5a5f2b25d7 --- /dev/null +++ b/tests/neg/byname-implicits-26.scala @@ -0,0 +1,35 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait GNil + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: Foo[R]): Foo[T] = ??? + } + + case class A(b: B, c: C, i: Int) + object A { + implicit val genA: Generic[A] { type Repr = (B, (C, (Int, Unit))) } = ??? + } + + case class B(c0: C, c1: C, c2: C, i: Int) + object B { + implicit val genB: Generic[B] { type Repr = (C, (C, (C, (Int, Unit)))) } = ??? + } + + case class C(b: A, i: Int) + object C { + implicit val genC: Generic[C] { type Repr = (A, (Int, Unit)) } = ??? + } + + implicitly[Foo[A]] // error +} diff --git a/tests/pos/byname-implicits-1.scala b/tests/pos/byname-implicits-1.scala new file mode 100644 index 000000000000..e56101d6c107 --- /dev/null +++ b/tests/pos/byname-implicits-1.scala @@ -0,0 +1,12 @@ +trait Foo +object Foo { + implicit def foo(implicit rec: => Foo): Foo = ??? + + //implicit def foo(implicit rec: => Int): Foo = ??? +} + +object Test { + //implicit def i: Int = 23 + + implicitly[Foo] +} diff --git a/tests/pos/byname-implicits-10.scala b/tests/pos/byname-implicits-10.scala new file mode 100644 index 000000000000..6686e751c4d8 --- /dev/null +++ b/tests/pos/byname-implicits-10.scala @@ -0,0 +1,11 @@ +trait Foo[T] + +object Foo { + implicit def pair[T, U](implicit fooT: => Foo[(T, U)], fooU: => Foo[(U, T)]): Foo[(T, U)] = new Foo[(T, U)] {} + implicit def int: Foo[Int] = new Foo[Int] {} + implicit def string: Foo[String] = new Foo[String] {} +} + +object Test { + implicitly[Foo[(Int, String)]] +} diff --git a/tests/pos/byname-implicits-12.scala b/tests/pos/byname-implicits-12.scala new file mode 100644 index 000000000000..57134e09d7e7 --- /dev/null +++ b/tests/pos/byname-implicits-12.scala @@ -0,0 +1,15 @@ +trait Foo[T] +object Foo { + implicit def unit: Foo[Unit] = ??? + implicit def int: Foo[Int] = ??? + implicit def pair[T, U](implicit ft: Foo[T], fu: Foo[U]): Foo[(T, U)] = ??? +} + +class Bar +object Bar { + implicit def bar(implicit f: => Foo[(Int, (Int, Unit))]): Foo[Bar] = ??? +} + +object Test { + implicitly[Foo[(Bar, Unit)]] +} diff --git a/tests/pos/byname-implicits-13.scala b/tests/pos/byname-implicits-13.scala new file mode 100644 index 000000000000..4ecc495d7a07 --- /dev/null +++ b/tests/pos/byname-implicits-13.scala @@ -0,0 +1,192 @@ +// deriving/src/main/scala/by-name-implicit-test.scala.scala +sealed trait AABB +case class AA(a: String) extends AABB +case class BB(a: String) extends AABB +case class DAABB(d: Double, aabb: AABB) +case class IDAABBS(i: Int, daabb: DAABB, s: String) + +case class Dog(age: Long) +case class Cat(name: String, friend: Either[Cat, Dog]) + +// Definitions from Shapeless --------------------------------------------------------------------- + +sealed trait HList extends Product with Serializable +final case class ::[+H, +T <: HList](head: H, tail: T) extends HList +sealed trait HNil extends HList +final case object HNil extends HNil + +sealed trait Coproduct extends Product with Serializable +sealed trait :+:[+H, +T <: Coproduct] extends Coproduct +final case class Inl[+H, +T <: Coproduct](head: H) extends :+:[H, T] +final case class Inr[+H, +T <: Coproduct](tail: T) extends :+:[H, T] +sealed trait CNil extends Coproduct + +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +// Manual Generic macro expansions ---------------------------------------------------------------- + +object GenericInstances { + implicit val genAABB: Generic[AABB] { type Repr = AA :+: BB :+: CNil } = + new Generic[AABB] { + type Repr = AA :+: BB :+: CNil + def to(t: AABB): Repr = t match { + case x: AA => Inl(x) + case x: BB => Inr(Inl(x)) + } + def from(r: Repr): AABB = r match { + case Inl(x) => x + case Inr(Inl(x)) => x + case _ => ??? + } + } + + implicit val genAA: Generic[AA] { type Repr = String :: HNil } = + new Generic[AA] { + type Repr = String :: HNil + def to(t: AA): Repr = t match { case AA(x) => ::(x, HNil) } + def from(r: Repr): AA = r match { case ::(x, HNil) => AA(x) } + } + + implicit val genBB: Generic[BB] { type Repr = String :: HNil } = + new Generic[BB] { + type Repr = String :: HNil + def to(t: BB): Repr = t match { case BB(x) => ::(x, HNil) } + def from(r: Repr): BB = r match { case ::(x, HNil) => BB(x) } + } + + implicit val genDAABB: Generic[DAABB] { type Repr = Double :: AABB :: HNil } = + new Generic[DAABB] { + type Repr = Double :: AABB :: HNil + def to(t: DAABB): Repr = t match { case DAABB(x, y) => ::(x, ::(y, HNil)) } + def from(r: Repr): DAABB = r match { case ::(x, ::(y, HNil)) => DAABB(x, y) } + } + + implicit val genIDAABBS: Generic[IDAABBS] { type Repr = Int :: DAABB :: String :: HNil } = + new Generic[IDAABBS] { + type Repr = Int :: DAABB :: String :: HNil + def to(t: IDAABBS): Repr = t match { case IDAABBS(x, y, z) => ::(x, ::(y, ::(z, HNil))) } + def from(r: Repr): IDAABBS = r match { case ::(x, ::(y, ::(z, HNil))) => IDAABBS(x, y, z) } + } + + implicit val genDog: Generic[Dog] { type Repr = Long :: HNil } = + new Generic[Dog] { + type Repr = Long :: HNil + def to(t: Dog): Repr = t match { case Dog(x) => ::(x, HNil) } + def from(r: Repr): Dog = r match { case ::(x, HNil) => Dog(x) } + } + + implicit val genCat: Generic[Cat] { type Repr = String :: Either[Cat, Dog] :: HNil } = + new Generic[Cat] { + type Repr = String :: Either[Cat, Dog] :: HNil + def to(t: Cat): Repr = t match { case Cat(x, y) => ::(x, ::(y, HNil)) } + def from(r: Repr): Cat = r match { case ::(x, ::(y, HNil)) => Cat(x, y) } + } + + implicit def genEither[A, B]: Generic[Either[A, B]] { type Repr = Left[A, B] :+: Right[A, B] :+: CNil } = + new Generic[Either[A, B]] { + type Repr = Left[A, B] :+: Right[A, B] :+: CNil + def to(t: Either[A, B]): Repr = t match { + case (x: Left[A, B] @unchecked) => Inl(x) + case (x: Right[A, B] @unchecked) => Inr(Inl(x)) + } + def from(r: Repr): Either[A, B] = r match { + case Inl(x) => x + case Inr(Inl(x)) => x + case _ => ??? + } + } + + implicit def genLeft[A, B]: Generic[Left[A, B]] { type Repr = A :: HNil } = + new Generic[Left[A, B]] { + type Repr = A :: HNil + def to(t: Left[A, B]): Repr = t match { case Left(x) => ::(x, HNil) } + def from(r: Repr): Left[A, B] = r match { case ::(x, HNil) => Left(x) } + } + + implicit def genRight[A, B]: Generic[Right[A, B]] { type Repr = B :: HNil } = + new Generic[Right[A, B]] { + type Repr = B :: HNil + def to(t: Right[A, B]): Repr = t match { case Right(x) => ::(x, HNil) } + def from(r: Repr): Right[A, B] = r match { case ::(x, HNil) => Right(x) } + } +} + +// First example from https://github.com/milessabin/shapeless-type-class-derivation-2015-demo +object equal { + trait Eq[T] { + def eqv(x: T, y: T): Boolean + } + + object Eq { + implicit val eqInt: Eq[Int] = + new Eq[Int] { + def eqv(x: Int, y: Int): Boolean = x == y + } + + implicit val eqString: Eq[String] = + new Eq[String] { + def eqv(x: String, y: String): Boolean = x == y + } + + implicit def eqGeneric[T, R] + (implicit + gen: Generic[T] { type Repr = R }, + eqRepr: => Eq[R] + ): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = + eqRepr.eqv(gen.to(x), gen.to(y)) + } + + implicit val eqHNil: Eq[HNil] = new Eq[HNil] { + def eqv(x: HNil, y: HNil): Boolean = true + } + + implicit def eqHCons[H, T <: HList] + (implicit + eqH: Eq[H], + eqT: Eq[T] + ): Eq[H :: T] = + new Eq[H :: T] { + def eqv(x: H :: T, y: H :: T): Boolean = + eqH.eqv(x.head, y.head) && eqT.eqv(x.tail, y.tail) + } + + implicit val eqCNil: Eq[CNil] = new Eq[CNil] { + def eqv(x: CNil, y: CNil): Boolean = true + } + + implicit def eqCNCons[H, T <: Coproduct] + (implicit + eqH: Eq[H], + eqT: Eq[T] + ): Eq[H :+: T] = + new Eq[H :+: T] { + def eqv(x: H :+: T, y: H :+: T): Boolean = + (x, y) match { + case (Inl(xh), Inl(yh)) => eqH.eqv(xh, yh) + case (Inr(xt), Inr(yt)) => eqT.eqv(xt, yt) + case _ => false + } + } + } + + implicit class EqOps[T](x: T)(implicit eqT: Eq[T]) { + def ===(y: T): Boolean = eqT.eqv(x, y) + } + + import GenericInstances._ + + implicit val EqLongInstance: Eq[Long] = new Eq[Long] { def eqv(x: Long, y: Long): Boolean = x == y } + implicit val EqDoubleInstance: Eq[Double] = new Eq[Double] { def eqv(x: Double, y: Double): Boolean = x == y } + implicit val EqIntInstance: Eq[Int] = new Eq[Int] { def eqv(x: Int, y: Int): Boolean = x == y } + implicit val EqStringInstance: Eq[String] = new Eq[String] { def eqv(x: String, y: String): Boolean = x == y } + + implicitly[Eq[Dog]] + implicitly[Eq[Cat]] + implicitly[Eq[IDAABBS]] +} diff --git a/tests/pos/byname-implicits-14.scala b/tests/pos/byname-implicits-14.scala new file mode 100644 index 000000000000..122903d7b695 --- /dev/null +++ b/tests/pos/byname-implicits-14.scala @@ -0,0 +1,192 @@ +// deriving/src/main/scala/by-name-implicit-test.scala.scala +sealed trait AABB +case class AA(a: String) extends AABB +case class BB(a: String) extends AABB +case class DAABB(d: Double, aabb: AABB) +case class IDAABBS(i: Int, daabb: DAABB, s: String) + +case class Dog(age: Long) +case class Cat(name: String, friend: Either[Cat, Dog]) + +// Definitions from Shapeless --------------------------------------------------------------------- + +sealed trait HList extends Product with Serializable +final case class ::[+H, +T <: HList](head: H, tail: T) extends HList +sealed trait HNil extends HList +final case object HNil extends HNil + +sealed trait Coproduct extends Product with Serializable +sealed trait :+:[+H, +T <: Coproduct] extends Coproduct +final case class Inl[+H, +T <: Coproduct](head: H) extends :+:[H, T] +final case class Inr[+H, +T <: Coproduct](tail: T) extends :+:[H, T] +sealed trait CNil extends Coproduct + +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +// Manual Generic macro expansions ---------------------------------------------------------------- + +object GenericInstances { + implicit val genAABB: Generic[AABB] { type Repr = AA :+: BB :+: CNil } = + new Generic[AABB] { + type Repr = AA :+: BB :+: CNil + def to(t: AABB): Repr = t match { + case x: AA => Inl(x) + case x: BB => Inr(Inl(x)) + } + def from(r: Repr): AABB = r match { + case Inl(x) => x + case Inr(Inl(x)) => x + case _ => ??? + } + } + + implicit val genAA: Generic[AA] { type Repr = String :: HNil } = + new Generic[AA] { + type Repr = String :: HNil + def to(t: AA): Repr = t match { case AA(x) => ::(x, HNil) } + def from(r: Repr): AA = r match { case ::(x, HNil) => AA(x) } + } + + implicit val genBB: Generic[BB] { type Repr = String :: HNil } = + new Generic[BB] { + type Repr = String :: HNil + def to(t: BB): Repr = t match { case BB(x) => ::(x, HNil) } + def from(r: Repr): BB = r match { case ::(x, HNil) => BB(x) } + } + + implicit val genDAABB: Generic[DAABB] { type Repr = Double :: AABB :: HNil } = + new Generic[DAABB] { + type Repr = Double :: AABB :: HNil + def to(t: DAABB): Repr = t match { case DAABB(x, y) => ::(x, ::(y, HNil)) } + def from(r: Repr): DAABB = r match { case ::(x, ::(y, HNil)) => DAABB(x, y) } + } + + implicit val genIDAABBS: Generic[IDAABBS] { type Repr = Int :: DAABB :: String :: HNil } = + new Generic[IDAABBS] { + type Repr = Int :: DAABB :: String :: HNil + def to(t: IDAABBS): Repr = t match { case IDAABBS(x, y, z) => ::(x, ::(y, ::(z, HNil))) } + def from(r: Repr): IDAABBS = r match { case ::(x, ::(y, ::(z, HNil))) => IDAABBS(x, y, z) } + } + + implicit val genDog: Generic[Dog] { type Repr = Long :: HNil } = + new Generic[Dog] { + type Repr = Long :: HNil + def to(t: Dog): Repr = t match { case Dog(x) => ::(x, HNil) } + def from(r: Repr): Dog = r match { case ::(x, HNil) => Dog(x) } + } + + implicit val genCat: Generic[Cat] { type Repr = String :: Either[Cat, Dog] :: HNil } = + new Generic[Cat] { + type Repr = String :: Either[Cat, Dog] :: HNil + def to(t: Cat): Repr = t match { case Cat(x, y) => ::(x, ::(y, HNil)) } + def from(r: Repr): Cat = r match { case ::(x, ::(y, HNil)) => Cat(x, y) } + } + + implicit def genEither[A, B]: Generic[Either[A, B]] { type Repr = Left[A, B] :+: Right[A, B] :+: CNil } = + new Generic[Either[A, B]] { + type Repr = Left[A, B] :+: Right[A, B] :+: CNil + def to(t: Either[A, B]): Repr = t match { + case (x: Left[A, B] @unchecked) => Inl(x) + case (x: Right[A, B] @unchecked) => Inr(Inl(x)) + } + def from(r: Repr): Either[A, B] = r match { + case Inl(x) => x + case Inr(Inl(x)) => x + case _ => ??? + } + } + + implicit def genLeft[A, B]: Generic[Left[A, B]] { type Repr = A :: HNil } = + new Generic[Left[A, B]] { + type Repr = A :: HNil + def to(t: Left[A, B]): Repr = t match { case Left(x) => ::(x, HNil) } + def from(r: Repr): Left[A, B] = r match { case ::(x, HNil) => Left(x) } + } + + implicit def genRight[A, B]: Generic[Right[A, B]] { type Repr = B :: HNil } = + new Generic[Right[A, B]] { + type Repr = B :: HNil + def to(t: Right[A, B]): Repr = t match { case Right(x) => ::(x, HNil) } + def from(r: Repr): Right[A, B] = r match { case ::(x, HNil) => Right(x) } + } +} + +// First example from https://github.com/milessabin/shapeless-type-class-derivation-2015-demo +object equal { + trait Eq[T] { + def eqv(x: T, y: T): Boolean + } + + object Eq { + implicit val eqInt: Eq[Int] = + new Eq[Int] { + def eqv(x: Int, y: Int): Boolean = x == y + } + + implicit val eqString: Eq[String] = + new Eq[String] { + def eqv(x: String, y: String): Boolean = x == y + } + + implicit def eqGeneric[T, R] + (implicit + gen: Generic[T] { type Repr = R }, + eqRepr: => Eq[R] + ): Eq[T] = + new Eq[T] { + def eqv(x: T, y: T): Boolean = + eqRepr.eqv(gen.to(x), gen.to(y)) + } + + implicit val eqHNil: Eq[HNil] = new Eq[HNil] { + def eqv(x: HNil, y: HNil): Boolean = true + } + + implicit def eqHCons[H, T <: HList] + (implicit + eqH: => Eq[H], + eqT: => Eq[T] + ): Eq[H :: T] = + new Eq[H :: T] { + def eqv(x: H :: T, y: H :: T): Boolean = + eqH.eqv(x.head, y.head) && eqT.eqv(x.tail, y.tail) + } + + implicit val eqCNil: Eq[CNil] = new Eq[CNil] { + def eqv(x: CNil, y: CNil): Boolean = true + } + + implicit def eqCNCons[H, T <: Coproduct] + (implicit + eqH: => Eq[H], + eqT: => Eq[T] + ): Eq[H :+: T] = + new Eq[H :+: T] { + def eqv(x: H :+: T, y: H :+: T): Boolean = + (x, y) match { + case (Inl(xh), Inl(yh)) => eqH.eqv(xh, yh) + case (Inr(xt), Inr(yt)) => eqT.eqv(xt, yt) + case _ => false + } + } + } + + implicit class EqOps[T](x: T)(implicit eqT: Eq[T]) { + def ===(y: T): Boolean = eqT.eqv(x, y) + } + + import GenericInstances._ + + implicit val EqLongInstance: Eq[Long] = new Eq[Long] { def eqv(x: Long, y: Long): Boolean = x == y } + implicit val EqDoubleInstance: Eq[Double] = new Eq[Double] { def eqv(x: Double, y: Double): Boolean = x == y } + implicit val EqIntInstance: Eq[Int] = new Eq[Int] { def eqv(x: Int, y: Int): Boolean = x == y } + implicit val EqStringInstance: Eq[String] = new Eq[String] { def eqv(x: String, y: String): Boolean = x == y } + + implicitly[Eq[Dog]] + implicitly[Eq[Cat]] + implicitly[Eq[IDAABBS]] +} diff --git a/tests/pos/byname-implicits-15.scala b/tests/pos/byname-implicits-15.scala new file mode 100644 index 000000000000..1818d3794759 --- /dev/null +++ b/tests/pos/byname-implicits-15.scala @@ -0,0 +1,27 @@ +object Test { + trait Generic[T] { + type Repr + } + + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + implicit def genTuple3[T, U, V]: Aux[(T, U, V), (T, (U, (V, Unit)))] = ??? + implicit def genTuple5[T, U, V, W, X]: Aux[(T, U, V, W, X), (T, (U, (V, (W, (X, Unit)))))] = ??? + } + + trait Show[T] + object Show { + implicit val showUnit: Show[Unit] = ??? + implicit val showInt: Show[Int] = ??? + implicit def showPair[T, U](implicit st: Show[T], su: Show[U]): Show[(T, U)] = ??? + implicit def showGen[T, R](implicit gen: Generic.Aux[T, R], sr: => Show[R]): Show[T] = ??? + } + + type I5 = (Int, Int, Int, Int, Int) + + // Demonstrates that the bynamity of sr suppresses the false positive divergence test + // which would otherwise see 5 nested pairs dominating 3 nested pairs. + implicitly[Show[(I5, I5, I5)]] + implicitly[Show[(Int, I5, Int)]] + implicitly[Show[(I5, (I5, I5, I5), Int)]] +} diff --git a/tests/pos/byname-implicits-19.scala b/tests/pos/byname-implicits-19.scala new file mode 100644 index 000000000000..65c0634935b2 --- /dev/null +++ b/tests/pos/byname-implicits-19.scala @@ -0,0 +1,66 @@ +/* +object Test { + class A + class B + class C + class D + + /* + { + implicit def parentA(implicit arg: => B): A = ??? + implicit def parentB(implicit arg: C): B = ??? + implicit def parentC(implicit arg: D): C = ??? + implicit def parentD(implicit arg: A): D = ??? + + implicitly[A] + } + */ + + /* + { + implicit def parentA(implicit arg: B): A = ??? + implicit def parentB(implicit arg: => C): B = ??? + implicit def parentC(implicit arg: D): C = ??? + implicit def parentD(implicit arg: A): D = ??? + + implicitly[A] + } + */ + + { + implicit def parentA(implicit arg: B): A = ??? + implicit def parentB(implicit arg: C): B = ??? + implicit def parentC(implicit arg: => D): C = ??? + implicit def parentD(implicit arg: A): D = ??? + + implicitly[A] + } + + /* + { + implicit def parentA(implicit arg: B): A = ??? + implicit def parentB(implicit arg: C): B = ??? + implicit def parentC(implicit arg: D): C = ??? + implicit def parentD(implicit arg: => A): D = ??? + + implicitly[A] + } + */ +} +*/ + +object Test { + class A + + { + implicit def parentA(implicit arg: => A): A = ??? + + implicitly[A] + } + + { + implicit def parentA(implicit arg: => A): A = ??? + + implicitly[A] + } +} diff --git a/tests/pos/byname-implicits-2.scala b/tests/pos/byname-implicits-2.scala new file mode 100644 index 000000000000..cd752c301b08 --- /dev/null +++ b/tests/pos/byname-implicits-2.scala @@ -0,0 +1,25 @@ +trait Foo[T] +object Foo { + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[H, T](implicit h: Foo[H], t: Foo[T]): Foo[(H, T)] = ??? +} + +trait Bar +object Bar { + implicit def fooBar(implicit repr: => Foo[(Int, (Int, Int))]): Foo[Bar] = ??? +} + +trait Baz +object Baz { + implicit def fooBaz(implicit i: Foo[Int], rec: => Foo[Baz]): Foo[Baz] = ??? +} + +object Test { + implicitly[Foo[Int]] + implicitly[Foo[(Int, Int)]] + implicitly[Foo[(Int, (Int, Int))]] + implicitly[Foo[(Int, (Int, (Int, Int)))]] + implicitly[Foo[Bar]] + implicitly[Foo[(Int, Bar)]] + implicitly[Foo[Baz]] +} diff --git a/tests/pos/byname-implicits-20.scala b/tests/pos/byname-implicits-20.scala new file mode 100644 index 000000000000..7b725d71e50f --- /dev/null +++ b/tests/pos/byname-implicits-20.scala @@ -0,0 +1,28 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: => Foo[R]): Foo[T] = ??? + } + + trait A + object A { + implicit val genA: Generic[A] { type Repr = (B, Unit) } = ??? + } + + trait B + object B { + implicit val genB: Generic[B] { type Repr = (Int, (Int, Unit)) } = ??? + } + + implicitly[Foo[A]] +} diff --git a/tests/pos/byname-implicits-22.scala b/tests/pos/byname-implicits-22.scala new file mode 100644 index 000000000000..4d833a2a961b --- /dev/null +++ b/tests/pos/byname-implicits-22.scala @@ -0,0 +1,44 @@ +object repro { + import scala.reflect.ClassTag + + trait +[L, R] + + case class Atomic[V](val name: String) + object Atomic { + def apply[V](implicit vtt: ClassTag[V]): Atomic[V] = Atomic[V](vtt.toString) + } + + case class Assign[V, X](val name: String) + object Assign { + def apply[V, X](implicit vtt: ClassTag[V]): Assign[V, X] = Assign[V, X](vtt.toString) + } + + trait AsString[X] { + def str: String + } + object AsString { + implicit def atomic[V](implicit a: Atomic[V]): AsString[V] = + new AsString[V] { val str = a.name } + implicit def assign[V, X](implicit a: Assign[V, X], asx: AsString[X]): AsString[V] = + new AsString[V] { val str = asx.str } + implicit def plus[L, R](implicit asl: AsString[L], asr: AsString[R]): AsString[+[L, R]] = + new AsString[+[L, R]] { val str = s"(${asl.str}) + (${asr.str})" } + } + + trait X + implicit val declareX: Atomic[X] = Atomic[X] + trait Y + implicit val declareY: Atomic[Y] = Atomic[Y] + trait Z + implicit val declareZ: Atomic[Z] = Atomic[Z] + + trait Q + implicit val declareQ: Assign[Q, (X + Y) + Z] = Assign[Q, (X + Y) + Z] + trait R + implicit val declareR: Assign[R, Q + Z] = Assign[R, Q + Z] + + implicitly[AsString[X]] + implicitly[AsString[X + Y]] + implicitly[AsString[Q]] + implicitly[AsString[R]] +} diff --git a/tests/pos/byname-implicits-23.scala b/tests/pos/byname-implicits-23.scala new file mode 100644 index 000000000000..19b6746732d2 --- /dev/null +++ b/tests/pos/byname-implicits-23.scala @@ -0,0 +1,30 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait GNil + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: Foo[R]): Foo[T] = ??? + } + + trait A + object A { + implicit val genA: Generic[A] { type Repr = (B, (Int, Unit)) } = ??? + } + + trait B + object B { + implicit val genB: Generic[B] { type Repr = (Int, (Int, (Int, Unit))) } = ??? + } + + implicitly[Foo[A]] +} diff --git a/tests/pos/byname-implicits-24.scala b/tests/pos/byname-implicits-24.scala new file mode 100644 index 000000000000..70b7f57873f9 --- /dev/null +++ b/tests/pos/byname-implicits-24.scala @@ -0,0 +1,30 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait GNil + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: Foo[R]): Foo[T] = ??? + } + + trait A + object A { + implicit val genA: Generic[A] { type Repr = (B, (Unit, Unit)) } = ??? + } + + trait B + object B { + implicit val genB: Generic[B] { type Repr = (Unit, (Unit, (Unit, Unit))) } = ??? + } + + implicitly[Foo[A]] +} diff --git a/tests/pos/byname-implicits-25.scala b/tests/pos/byname-implicits-25.scala new file mode 100644 index 000000000000..9b8658654811 --- /dev/null +++ b/tests/pos/byname-implicits-25.scala @@ -0,0 +1,35 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait GNil + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: Foo[R]): Foo[T] = ??? + } + + case class A(b: B, c: C, i: Int) + object A { + implicit val genA: Generic[A] { type Repr = (B, (C, (Int, Unit))) } = ??? + } + + case class B(c0: C, c1: C, c2: C, i: Int) + object B { + implicit val genB: Generic[B] { type Repr = (C, (C, (C, (Int, Unit)))) } = ??? + } + + case class C(i0: Int, i1: Int, i2: Int, i3: Int, i4: Int) + object C { + implicit val genC: Generic[C] { type Repr = (Int, (Int, (Int, (Int, (Int, Unit))))) } = ??? + } + + implicitly[Foo[A]] +} diff --git a/tests/pos/byname-implicits-27.scala b/tests/pos/byname-implicits-27.scala new file mode 100644 index 000000000000..6b7a0a258f16 --- /dev/null +++ b/tests/pos/byname-implicits-27.scala @@ -0,0 +1,37 @@ +object Test { + trait Generic[T] { + type Repr + } + object Generic { + type Aux[T, R] = Generic[T] { type Repr = R } + } + + trait GNil + + trait Foo[T] + object Foo { + implicit val fooUnit: Foo[Unit] = ??? + implicit val fooInt: Foo[Int] = ??? + implicit val fooString: Foo[String] = ??? + implicit val fooBoolean: Foo[Boolean] = ??? + implicit def fooPair[T, U](implicit fooT: Foo[T], fooU: Foo[U]): Foo[(T, U)] = ??? + implicit def fooGen[T, R](implicit gen: Generic.Aux[T, R], fr: Foo[R]): Foo[T] = ??? + } + + case class A(b: B, i: Int) + object A { + implicit val genA: Generic[A] { type Repr = (B, (Int, Unit)) } = ??? + } + + case class B(c: C, i: Int, b: Boolean) + object B { + implicit val genB: Generic[B] { type Repr = (C, (Int, (Boolean, Unit))) } = ??? + } + + case class C(i: Int, s: String, b: Boolean) + object C { + implicit val genC: Generic[C] { type Repr = (Int, (String, (Boolean, Unit))) } = ??? + } + + implicitly[Foo[A]] +} diff --git a/tests/pos/byname-implicits-29.scala b/tests/pos/byname-implicits-29.scala new file mode 100644 index 000000000000..e635506f1067 --- /dev/null +++ b/tests/pos/byname-implicits-29.scala @@ -0,0 +1,8 @@ +object Test { + class Loop[T, U] + object Loop { + implicit def mkLoop[T, U](implicit tu: => Loop[T, U], ut: => Loop[U, T]): Loop[T, U] = ??? + } + + implicitly[Loop[Int, String]] +} diff --git a/tests/pos/byname-implicits-3.scala b/tests/pos/byname-implicits-3.scala new file mode 100644 index 000000000000..d34e559c5907 --- /dev/null +++ b/tests/pos/byname-implicits-3.scala @@ -0,0 +1,11 @@ +trait Foo[T] + +object Foo { + implicit val int: Foo[Int] = ??? + implicit val bool: Foo[Boolean] = ??? + implicit def pair[T, U](implicit ftu0: => Foo[(T, U)], ftu1: => Foo[(T, U)]): Foo[(T, U)] = ??? +} + +object Test { + implicitly[Foo[(Int, Boolean)]] +} diff --git a/tests/pos/byname-implicits-7.scala b/tests/pos/byname-implicits-7.scala new file mode 100644 index 000000000000..83c335798dcf --- /dev/null +++ b/tests/pos/byname-implicits-7.scala @@ -0,0 +1,17 @@ +trait Foo { + type Out + def out: Out +} + +object Foo { + type Aux[Out0] = Foo { type Out = Out0 } + + implicit val fooInt: Aux[Int] = new Foo { type Out = Int ; def out = 23 } +} + +object Test { + def bar[T](t: T)(implicit foo: => Foo.Aux[T]): T = foo.out + + val i = bar(13) + i: Int +} diff --git a/tests/pos/byname-implicits-8.scala b/tests/pos/byname-implicits-8.scala new file mode 100644 index 000000000000..bcf4d8de4198 --- /dev/null +++ b/tests/pos/byname-implicits-8.scala @@ -0,0 +1,31 @@ +// shapeless's Lazy implemented in terms of byname implicits +trait Lazy[T] { + lazy val value: T +} + +object Lazy { + implicit def apply[T](implicit t: => T): Lazy[T] = + new Lazy[T] { + lazy val value = t + } + + def unapply[T](lt: Lazy[T]): Option[T] = Some(lt.value) +} + +trait Foo { + type Out + def out: Out +} + +object Foo { + type Aux[Out0] = Foo { type Out = Out0 } + + implicit val fooInt: Aux[Int] = new Foo { type Out = Int ; def out = 23 } +} + +object Test { + def bar[T](t: T)(implicit foo: Lazy[Foo.Aux[T]]): T = foo.value.out + + val i = bar(13) + i: Int +} diff --git a/tests/pos/byname-implicits-9.scala b/tests/pos/byname-implicits-9.scala new file mode 100644 index 000000000000..217852cd4085 --- /dev/null +++ b/tests/pos/byname-implicits-9.scala @@ -0,0 +1,73 @@ +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +object Generic { + type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } +} + +object ListInstances { + type LRepr[T] = Either[::[T], Either[Nil.type, Unit]] + type CRepr[T] = (T, (List[T], Unit)) + type NRepr = Unit + + implicit def genList[T]: Generic.Aux[List[T], LRepr[T]] = new Generic[List[T]] { + type Repr = LRepr[T] + def to(t: List[T]): Repr = t match { + case hd :: tl => Left(::(hd, tl)) + case n@Nil => Right(Left(Nil)) + } + def from(r: Repr): List[T] = (r: @unchecked) match { + case Left(c) => c + case Right(Left(n)) => n + } + } + + implicit def genCons[T]: Generic.Aux[::[T], CRepr[T]] = new Generic[::[T]] { + type Repr = CRepr[T] + def to(t: ::[T]): Repr = (t.head, (t.tail, ())) + def from(r: Repr): ::[T] = ::(r._1, r._2._1) + } + + implicit def genNil: Generic.Aux[Nil.type, NRepr] = new Generic[Nil.type] { + type Repr = NRepr + def to(t: Nil.type): Repr = () + def from(r: Repr): Nil.type = Nil + } +} + +trait Show[T] { + def show(t: T): String +} + +object Show { + implicit def showUnit: Show[Unit] = new Show[Unit] { + def show(u: Unit): String = "()" + } + + implicit def showInt: Show[Int] = new Show[Int] { + def show(i: Int): String = i.toString + } + + implicit def showPair[T, U](implicit st: Show[T], su: Show[U]): Show[(T, U)] = new Show[(T, U)] { + def show(t: (T, U)): String = s"(${st.show(t._1)}, ${su.show(t._2)}" + } + + implicit def showEither[T, U](implicit st: Show[T], su: Show[U]): Show[Either[T, U]] = new Show[Either[T, U]] { + def show(t: Either[T, U]): String = t match { + case Left(t) => s"Left(${st.show(t)})" + case Right(u) => s"Right(${su.show(u)})" + } + } + + implicit def showGen[T, R](implicit gen: Generic.Aux[T, R], sr: => Show[R]): Show[T] = new Show[T] { + def show(t: T) = sr.show(gen.to(t)) + } +} + +object Test { + import ListInstances._ + implicitly[Show[List[Int]]] +} diff --git a/tests/neg/implicitDivergenc.scala b/tests/pos/implicitDivergenc.scala similarity index 76% rename from tests/neg/implicitDivergenc.scala rename to tests/pos/implicitDivergenc.scala index 34828e9b1795..c8e34ee97c49 100644 --- a/tests/neg/implicitDivergenc.scala +++ b/tests/pos/implicitDivergenc.scala @@ -9,5 +9,5 @@ object Test { implicitly[C[C[C[String]]]] implicitly[C[C[C[C[String]]]]] implicitly[C[C[C[C[C[String]]]]]] - implicitly[C[C[C[C[C[C[String]]]]]]] // error: no implicit argument found (because of divergence) -} \ No newline at end of file + implicitly[C[C[C[C[C[C[String]]]]]]] +} diff --git a/tests/run/byname-implicits-17.scala b/tests/run/byname-implicits-17.scala new file mode 100644 index 000000000000..676f7fe82c8d --- /dev/null +++ b/tests/run/byname-implicits-17.scala @@ -0,0 +1,76 @@ +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +object Generic { + type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } +} + +object ListInstances { + type LRepr[T] = Either[::[T], Either[Nil.type, Unit]] + type CRepr[T] = (T, (List[T], Unit)) + type NRepr = Unit + + implicit def genList[T]: Generic.Aux[List[T], LRepr[T]] = new Generic[List[T]] { + type Repr = LRepr[T] + def to(t: List[T]): Repr = t match { + case hd :: tl => Left(::(hd, tl)) + case n@Nil => Right(Left(Nil)) + } + def from(r: Repr): List[T] = (r: @unchecked) match { + case Left(c) => c + case Right(Left(n)) => n + } + } + + implicit def genCons[T]: Generic.Aux[::[T], CRepr[T]] = new Generic[::[T]] { + type Repr = CRepr[T] + def to(t: ::[T]): Repr = (t.head, (t.tail, ())) + def from(r: Repr): ::[T] = ::(r._1, r._2._1) + } + + implicit def genNil: Generic.Aux[Nil.type, NRepr] = new Generic[Nil.type] { + type Repr = NRepr + def to(t: Nil.type): Repr = () + def from(r: Repr): Nil.type = Nil + } +} + +trait Show[T] { + def show(t: T): String +} + +object Show { + def apply[T](implicit st: => Show[T]): Show[T] = st + + implicit def showUnit: Show[Unit] = new Show[Unit] { + def show(u: Unit): String = "()" + } + + implicit def showInt: Show[Int] = new Show[Int] { + def show(i: Int): String = i.toString + } + + implicit def showPair[T, U](implicit st: Show[T], su: Show[U]): Show[(T, U)] = new Show[(T, U)] { + def show(t: (T, U)): String = s"(${st.show(t._1)}, ${su.show(t._2)}" + } + + implicit def showEither[T, U](implicit st: Show[T], su: Show[U]): Show[Either[T, U]] = new Show[Either[T, U]] { + def show(t: Either[T, U]): String = t match { + case Left(t) => s"Left(${st.show(t)})" + case Right(u) => s"Right(${su.show(u)})" + } + } + + implicit def showGen[T, R](implicit gen: Generic.Aux[T, R], sr: => Show[R]): Show[T] = new Show[T] { + def show(t: T) = sr.show(gen.to(t)) + } +} + +object Test extends App { + import ListInstances._ + val sl = Show[List[Int]] + assert(sl.show(List(1, 2, 3)) == "Left((1, (Left((2, (Left((3, (Right(Left(())), ()), ()), ())") +} diff --git a/tests/run/byname-implicits-28.scala b/tests/run/byname-implicits-28.scala new file mode 100644 index 000000000000..722952380fc3 --- /dev/null +++ b/tests/run/byname-implicits-28.scala @@ -0,0 +1,173 @@ +object Loop0 { + trait Link0 { + def next0: Link0 + } + object Link0 { + implicit def mkLink0(implicit l0: => Link0): Link0 = + new Link0 { lazy val next0 = l0 } + } + + def check: Boolean = { + val loop = implicitly[Link0] + loop eq loop.next0 + } +} + +object Loop1 { + trait Link0 { + def next0: Link0 + def next1: Link1 + } + object Link0 { + implicit def mkLink0(implicit l0: => Link0, l1: => Link1): Link0 = + new Link0 { lazy val next0 = l0 ; lazy val next1 = l1 } + } + + trait Link1 { + def next0: Link0 + def next1: Link1 + } + object Link1 { + implicit def mkLink1(implicit l0: => Link0, l1: => Link1): Link1 = + new Link1 { lazy val next0 = l0 ; lazy val next1 = l1 } + } + + def check: Boolean = { + val loop = implicitly[Link0] + loop eq loop.next0 + loop.next1 eq loop.next1.next1 + } +} + +object Loop2 { + trait Link0 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + } + object Link0 { + implicit def mkLink0(implicit l0: => Link0, l1: => Link1, l2: => Link2): Link0 = + new Link0 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 } + } + + trait Link1 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + } + object Link1 { + implicit def mkLink1(implicit l0: => Link0, l1: => Link1, l2: => Link2): Link1 = + new Link1 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 } + } + + trait Link2 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + } + object Link2 { + implicit def mkLink2(implicit l0: => Link0, l1: => Link1, l2: => Link2): Link2 = + new Link2 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 } + } + + def check: Boolean = { + val loop = implicitly[Link0] + loop eq loop.next0 + loop.next1 eq loop.next1.next1 + loop.next2 eq loop.next2.next2 + } +} + +object Loop3 { + trait Link0 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + def next3: Link3 + } + object Link0 { + implicit def mkLink0(implicit l0: => Link0, l1: => Link1, l2: => Link2, l3: => Link3): Link0 = + new Link0 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 ; lazy val next3 = l3 } + } + + trait Link1 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + def next3: Link3 + } + object Link1 { + implicit def mkLink1(implicit l0: => Link0, l1: => Link1, l2: => Link2, l3: => Link3): Link1 = + new Link1 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 ; lazy val next3 = l3 } + } + + trait Link2 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + def next3: Link3 + } + object Link2 { + implicit def mkLink2(implicit l0: => Link0, l1: => Link1, l2: => Link2, l3: => Link3): Link2 = + new Link2 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 ; lazy val next3 = l3 } + } + + trait Link3 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + def next3: Link3 + } + object Link3 { + implicit def mkLink3(implicit l0: => Link0, l1: => Link1, l2: => Link2, l3: => Link3): Link3 = + new Link3 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 ; lazy val next3 = l3 } + } + + def check: Boolean = { + val loop = implicitly[Link0] + loop eq loop.next0 + loop.next1 eq loop.next1.next1 + loop.next2 eq loop.next2.next2 + loop.next3 eq loop.next3.next3 + } +} + +object Loop2b { + trait Link0 { + def next0: Link0 + def next1: Link1 + def next2: Link2 + } + object Link0 { + implicit def mkLink0(implicit l0: => Link0, l1: => Link1, l2: => Link2): Link0 = + new Link0 { lazy val next0 = l0 ; lazy val next1 = l1 ; lazy val next2 = l2 } + } + + trait Link1 { + def next1: Link1 + def next2: Link2 + } + object Link1 { + implicit def mkLink1(implicit l1: => Link1, l2: => Link2): Link1 = + new Link1 { lazy val next1 = l1 ; lazy val next2 = l2 } + } + + trait Link2 { + def next2: Link2 + } + object Link2 { + implicit def mkLink2(implicit l2: => Link2): Link2 = + new Link2 { lazy val next2 = l2 } + } + + def check: Boolean = { + val loop = implicitly[Link0] + loop eq loop.next0 + loop.next1 eq loop.next1.next1 + loop.next2 eq loop.next2.next2 + } +} + +object Test extends App { + assert(Loop0.check && Loop1.check && Loop2.check && Loop3.check && Loop2b.check) +} diff --git a/tests/run/byname-implicits-30.scala b/tests/run/byname-implicits-30.scala new file mode 100644 index 000000000000..2dfe5e8978cb --- /dev/null +++ b/tests/run/byname-implicits-30.scala @@ -0,0 +1,16 @@ +object Test { + class Foo(val bar: Bar) + class Bar(baz0: => Baz) { + lazy val baz = baz0 + } + class Baz(val foo: Foo) + + implicit def mkFoo(implicit bar: Bar): Foo = new Foo(bar) + implicit def mkBar(implicit baz: => Baz): Bar = new Bar(baz) + implicit def mkBaz(implicit foo: Foo): Baz = new Baz(foo) + + def main(args: Array[String]): Unit = { + val foo: Foo = implicitly[Foo] + assert(foo.bar.baz.foo eq foo) + } +} diff --git a/tests/run/byname-implicits-4.scala b/tests/run/byname-implicits-4.scala new file mode 100644 index 000000000000..d7f3e4a9018d --- /dev/null +++ b/tests/run/byname-implicits-4.scala @@ -0,0 +1,11 @@ +trait Foo { def next: Foo } +object Foo { + implicit def foo(implicit rec: => Foo): Foo = new Foo { def next = rec } +} + +object Test extends App { + def implicitly2[T](implicit t: T): T = t + + val foo = implicitly2[Foo] + assert(foo eq foo.next) +} diff --git a/tests/run/byname-implicits-5.scala b/tests/run/byname-implicits-5.scala new file mode 100644 index 000000000000..ed5372b9bf10 --- /dev/null +++ b/tests/run/byname-implicits-5.scala @@ -0,0 +1,15 @@ +import scala.language.higherKinds + +object Test extends App { + var x = 0 + + def foo(implicit y0: => Int): Int = { + x = 0 + val y = y0 // side effect + y*x + } + + implicit lazy val bar: Int = { x = 1 ; 23 } + + assert(foo == 23) +} diff --git a/tests/run/byname-implicits-6.scala b/tests/run/byname-implicits-6.scala new file mode 100644 index 000000000000..22d40a3f8353 --- /dev/null +++ b/tests/run/byname-implicits-6.scala @@ -0,0 +1,131 @@ +/* + * Demo of using by name implicits to resolve (hidden) divergence issues when + * traversing recursive generic structures. + * + * See http://stackoverflow.com/questions/25923974 + */ +sealed trait HList +object HList { + implicit class Syntax[L <: HList](l: L) { + def ::[U](u: U): U :: L = new ::(u, l) + } +} + +sealed trait HNil extends HList +object HNil extends HNil +case class ::[+H, +T <: HList](head : H, tail : T) extends HList + +trait Generic[T] { + type Repr + def to(t: T): Repr + def from(r: Repr): T +} + +object Generic { + type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 } +} + +trait DeepHLister[R] { + type Out + def apply(r: R): Out +} + +object DeepHLister extends DeepHLister0 { + def apply[R](implicit dh: DeepHLister[R]): Aux[R, dh.Out] = dh + + implicit def consDeepHLister[H, OutH <: HList, T <: HList, OutT <: HList](implicit + dhh: DeepHLister.Aux[H, OutH], + dht: DeepHLister.Aux[T, OutT] + ): Aux[H :: T, OutH :: OutT] = new DeepHLister[H :: T] { + type Out = OutH :: OutT + def apply(r: H :: T) = dhh(r.head) :: dht(r.tail) + } + + implicit object hnilDeepHLister extends DeepHLister[HNil] { + type Out = HNil + def apply(r: HNil) = HNil + } +} + +trait DeepHLister0 extends DeepHLister1 { + implicit def genDeepHLister[T, R <: HList, OutR <: HList](implicit + gen: Generic.Aux[T, R], + dhr: => DeepHLister.Aux[R, OutR] + ): Aux[T, OutR] = new DeepHLister[T] { + type Out = OutR + def apply(r: T) = dhr(gen.to(r)) + } +} + +trait DeepHLister1 { + type Aux[R, Out0] = DeepHLister[R] { type Out = Out0 } + + implicit def default[T]: Aux[T, T] = new DeepHLister[T] { + type Out = T + def apply(r: T): T = r + } +} + +object Test extends App { +} + +object DeepHListerDemo extends App { + case class A(x: Int, y: String) + object A { + type ARepr = Int :: String :: HNil + implicit val aGen: Generic.Aux[A, ARepr] = new Generic[A] { + type Repr = ARepr + def to(t: A): Repr = t.x :: t.y :: HNil + def from(r: Repr): A = A(r.head, r.tail.head) + } + } + + case class B(x: A, y: A) + object B { + type BRepr = A :: A :: HNil + implicit val bGen: Generic.Aux[B, BRepr] = new Generic[B] { + type Repr = BRepr + def to(t: B): Repr = t.x :: t.y :: HNil + def from(r: Repr): B = B(r.head, r.tail.head) + } + } + + case class C(b: B, a: A) + object C { + type CRepr = B :: A :: HNil + implicit val cGen: Generic.Aux[C, CRepr] = new Generic[C] { + type Repr = CRepr + def to(t: C): Repr = t.b :: t.a :: HNil + def from(r: Repr): C = C(r.head, r.tail.head) + } + } + + case class D(a: A, b: B) + object D { + type DRepr = A :: B :: HNil + implicit val dGen: Generic.Aux[D, DRepr] = new Generic[D] { + type Repr = DRepr + def to(t: D): Repr = t.a :: t.b :: HNil + def from(r: Repr): D = D(r.head, r.tail.head) + } + } + + def typed[T](t : => T): Unit = {} + + type ARepr = Int :: String :: HNil + type BRepr = ARepr :: ARepr :: HNil + type CRepr = BRepr :: ARepr :: HNil + type DRepr = ARepr :: BRepr :: HNil + + val adhl = DeepHLister[A :: HNil] + typed[DeepHLister.Aux[A :: HNil, ARepr :: HNil]](adhl) + + val bdhl = DeepHLister[B :: HNil] + typed[DeepHLister.Aux[B :: HNil, BRepr :: HNil]](bdhl) + + val cdhl = DeepHLister[C :: HNil] + typed[DeepHLister.Aux[C :: HNil, CRepr :: HNil]](cdhl) + + val ddhl = DeepHLister[D :: HNil] + typed[DeepHLister.Aux[D :: HNil, DRepr :: HNil]](ddhl) +}