diff --git a/community-build/community-projects/specs2 b/community-build/community-projects/specs2 index e42f7987b4ce..ba01cca013d9 160000 --- a/community-build/community-projects/specs2 +++ b/community-build/community-projects/specs2 @@ -1 +1 @@ -Subproject commit e42f7987b4ce30d95fca3f30b9d508021f2fdac7 +Subproject commit ba01cca013d9d99e390d17619664bdedd716e0d7 diff --git a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala index f6db0bac0452..33b946ed173f 100644 --- a/compiler/src/dotty/tools/dotc/config/SourceVersion.scala +++ b/compiler/src/dotty/tools/dotc/config/SourceVersion.scala @@ -10,6 +10,7 @@ enum SourceVersion: case `3.2-migration`, `3.2` case `3.3-migration`, `3.3` case `3.4-migration`, `3.4` + case `3.5-migration`, `3.5` // !!! Keep in sync with scala.runtime.stdlibPatches.language !!! case `future-migration`, `future` diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ff23e8180f1c..389669beff01 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -26,8 +26,8 @@ import Scopes.newScope import Typer.BindingPrec, BindingPrec.* import Hashable.* import util.{EqHashMap, Stats} -import config.{Config, Feature} -import Feature.migrateTo3 +import config.{Config, Feature, SourceVersion} +import Feature.{migrateTo3, sourceVersion} import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.* @@ -93,7 +93,7 @@ object Implicits: if (initctx eq NoContext) initctx else initctx.retractMode(Mode.ImplicitsEnabled) protected given Context = irefCtx - /** The nesting level of this context. Non-zero only in ContextialImplicits */ + /** The nesting level of this context. Non-zero only in ContextualImplicits */ def level: Int = 0 /** The implicit references */ @@ -324,7 +324,7 @@ object Implicits: /** Is this the outermost implicits? This is the case if it either the implicits * of NoContext, or the last one before it. */ - private def isOuterMost = { + private def isOutermost = { val finalImplicits = NoContext.implicits (this eq finalImplicits) || (outerImplicits eqn finalImplicits) } @@ -356,7 +356,7 @@ object Implicits: Stats.record("uncached eligible") if monitored then record(s"check uncached eligible refs in irefCtx", refs.length) val ownEligible = filterMatching(tp) - if isOuterMost then ownEligible + if isOutermost then ownEligible else combineEligibles(ownEligible, outerImplicits.nn.uncachedEligible(tp)) /** The implicit references that are eligible for type `tp`. */ @@ -383,7 +383,7 @@ object Implicits: private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ { if (monitored) record(s"check eligible refs in irefCtx", refs.length) val ownEligible = filterMatching(tp) - if isOuterMost then ownEligible + if isOutermost then ownEligible else combineEligibles(ownEligible, outerImplicits.nn.eligible(tp)) } @@ -392,7 +392,7 @@ object Implicits: override def toString: String = { val own = i"(implicits: $refs%, %)" - if (isOuterMost) own else own + "\n " + outerImplicits + if (isOutermost) own else own + "\n " + outerImplicits } /** This context, or a copy, ensuring root import from symbol `root` @@ -408,6 +408,13 @@ object Implicits: } } + /** Search mode to use for possibly avoiding looping givens */ + enum SearchMode: + case Old, // up to 3.3, old mode w/o protection + CompareWarn, // from 3.4, old mode, warn if new mode would change result + CompareErr, // from 3.5, old mode, error if new mode would change result + New // from future, new mode where looping givens are avoided + /** The result of an implicit search */ sealed abstract class SearchResult extends Showable { def tree: Tree @@ -1550,35 +1557,113 @@ trait Implicits: case _ => tp.isAny || tp.isAnyRef - private def searchImplicit(contextual: Boolean): SearchResult = + /** Search implicit in context `ctxImplicits` or else in implicit scope + * of expected type if `ctxImplicits == null`. + */ + private def searchImplicit(ctxImplicits: ContextualImplicits | Null, mode: SearchMode): SearchResult = if isUnderspecified(wildProto) then SearchFailure(TooUnspecific(pt), span) else - val eligible = - if contextual then + val contextual = ctxImplicits != null + val preEligible = // the eligible candidates, ignoring positions + if ctxImplicits != null then if ctx.gadt.isNarrowing then withoutMode(Mode.ImplicitsEnabled) { - ctx.implicits.uncachedEligible(wildProto) + ctxImplicits.uncachedEligible(wildProto) } - else ctx.implicits.eligible(wildProto) + else ctxImplicits.eligible(wildProto) else implicitScope(wildProto).eligible - searchImplicit(eligible, contextual) match - case result: SearchSuccess => - result - case failure: SearchFailure => - failure.reason match - case _: AmbiguousImplicits => failure - case reason => - if contextual then - searchImplicit(contextual = false).recoverWith { - failure2 => failure2.reason match - case _: AmbiguousImplicits => failure2 - case _ => - reason match - case (_: DivergingImplicit) => failure - case _ => List(failure, failure2).maxBy(_.tree.treeSize) - } - else failure + + /** Does candidate `cand` come too late for it to be considered as an + * eligible candidate? This is the case if `cand` appears in the same + * scope as a given definition of the form `given ... = ...` that + * encloses the search point and `cand` comes later in the source or + * coincides with that given definition. + */ + def comesTooLate(cand: Candidate): Boolean = + val candSym = cand.ref.symbol + def candSucceedsGiven(sym: Symbol): Boolean = + val owner = sym.owner + if owner == candSym.owner then + sym.is(GivenVal) && sym.span.exists && sym.span.start <= candSym.span.start + else if owner.isClass then false + else candSucceedsGiven(owner) + + ctx.isTyper + && !candSym.isOneOf(TermParamOrAccessor | Synthetic) + && candSym.span.exists + && candSucceedsGiven(ctx.owner) + end comesTooLate + + val eligible = // the eligible candidates that come before the search point + if contextual && mode != SearchMode.Old + then preEligible.filterNot(comesTooLate) + else preEligible + + def checkResolutionChange(result: SearchResult) = + if (eligible ne preEligible) && mode != SearchMode.New then + searchImplicit(preEligible, contextual) match + case prevResult: SearchSuccess => + def remedy = pt match + case _: SelectionProto => + "conversion,\n - use an import to get extension method into scope" + case _: ViewProto => + "conversion" + case _ => + "argument" + + def showResult(r: SearchResult) = r match + case r: SearchSuccess => ctx.printer.toTextRef(r.ref).show + case r => r.show + + result match + case result: SearchSuccess if prevResult.ref frozen_=:= result.ref => + // OK + case _ => + val msg = + em"""Result of implicit search for $pt will change. + |Current result ${showResult(prevResult)} will be no longer eligible + | because it is not defined before the search position. + |Result with new rules: ${showResult(result)}. + |To opt into the new rules, compile with `-source future` or use + |the `scala.language.future` language import. + | + |To fix the problem without the language import, you could try one of the following: + | - use a `given ... with` clause as the enclosing given, + | - rearrange definitions so that ${showResult(prevResult)} comes earlier, + | - use an explicit $remedy.""" + if mode == SearchMode.CompareErr + then report.error(msg, srcPos) + else report.warning(msg.append("\nThis will be an error in Scala 3.5 and later."), srcPos) + prevResult + case prevResult: SearchFailure => result + else result + end checkResolutionChange + + checkResolutionChange: + searchImplicit(eligible, contextual).recoverWith: + case failure: SearchFailure => + failure.reason match + case _: AmbiguousImplicits => failure + case reason => + if contextual then + // If we filtered out some candidates for being too late, we should + // do another contextual search further out, since the dropped candidates + // might have shadowed an eligible candidate in an outer level. + // Otherwise, proceed with a search of the implicit scope. + val newCtxImplicits = + if eligible eq preEligible then null + else ctxImplicits.nn.outerImplicits: ContextualImplicits | Null + // !!! Dotty problem: without the ContextualImplicits | Null type ascription + // we get a Ycheck failure after arrayConstructors due to "Types differ" + searchImplicit(newCtxImplicits, SearchMode.New).recoverWith: + failure2 => failure2.reason match + case _: AmbiguousImplicits => failure2 + case _ => + reason match + case (_: DivergingImplicit) => failure + case _ => List(failure, failure2).maxBy(_.tree.treeSize) + else failure end searchImplicit /** Find a unique best implicit reference */ @@ -1595,7 +1680,11 @@ trait Implicits: case ref: TermRef => SearchSuccess(tpd.ref(ref).withSpan(span.startPos), ref, 0)(ctx.typerState, ctx.gadt) case _ => - searchImplicit(contextual = true) + searchImplicit(ctx.implicits, + if sourceVersion.isAtLeast(SourceVersion.future) then SearchMode.New + else if sourceVersion.isAtLeast(SourceVersion.`3.5`) then SearchMode.CompareErr + else if sourceVersion.isAtLeast(SourceVersion.`3.4`) then SearchMode.CompareWarn + else SearchMode.Old) end bestImplicit def implicitScope(tp: Type): OfTypeImplicits = ctx.run.nn.implicitScope(tp) diff --git a/docs/_docs/reference/changed-features/implicit-resolution.md b/docs/_docs/reference/changed-features/implicit-resolution.md index 6a898690b565..1396ed04b6d3 100644 --- a/docs/_docs/reference/changed-features/implicit-resolution.md +++ b/docs/_docs/reference/changed-features/implicit-resolution.md @@ -163,8 +163,36 @@ The new rules are as follows: An implicit `a` defined in `A` is more specific th Condition (*) is new. It is necessary to ensure that the defined relation is transitive. +[//]: # todo: expand with precise rules +**9.** The following change is currently enabled in `-source future`: +Implicit resolution now avoids generating recursive givens that can lead to an infinite loop at runtime. Here is an example: +```scala +object Prices { + opaque type Price = BigDecimal + + object Price{ + given Ordering[Price] = summon[Ordering[BigDecimal]] // was error, now avoided + } +} +``` + +Previously, implicit resolution would resolve the `summon` to the given in `Price`, leading to an infinite loop (a warning was issued in that case). We now use the underlying given in `BigDecimal` instead. We achieve that by adding the following rule for implicit search: + + - When doing an implicit search while checking the implementation of a `given` definition `G` of the form + ``` + given ... = .... + ``` + discard all search results that lead back to `G` or to a given with the same owner as `G` that comes later in the source than `G`. + +The new behavior is currently enabled in `source.future` and will be enabled at the earliest in Scala 3.6. For earlier source versions, the behavior is as +follows: + + - Scala 3.3: no change + - Scala 3.4: A warning is issued where the behavior will change in 3.future. + - Scala 3.5: An error is issued where the behavior will change in 3.future. + +Old-style implicit definitions are unaffected by this change. -[//]: # todo: expand with precise rules diff --git a/docs/_docs/reference/experimental/given-loop-prevention.md b/docs/_docs/reference/experimental/given-loop-prevention.md new file mode 100644 index 000000000000..e306ba977d45 --- /dev/null +++ b/docs/_docs/reference/experimental/given-loop-prevention.md @@ -0,0 +1,31 @@ +--- +layout: doc-page +title: Given Loop Prevention +redirectFrom: /docs/reference/other-new-features/into-modifier.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/into-modifier.html +--- + +Implicit resolution now avoids generating recursive givens that can lead to an infinite loop at runtime. Here is an example: + +```scala +object Prices { + opaque type Price = BigDecimal + + object Price{ + given Ordering[Price] = summon[Ordering[BigDecimal]] // was error, now avoided + } +} +``` + +Previously, implicit resolution would resolve the `summon` to the given in `Price`, leading to an infinite loop (a warning was issued in that case). We now use the underlying given in `BigDecimal` instead. We achieve that by adding the following rule for implicit search: + + - When doing an implicit search while checking the implementation of a `given` definition `G` of the form + ``` + given ... = .... + ``` + discard all search results that lead back to `G` or to a given with the same owner as `G` that comes later in the source than `G`. + +The new behavior is enabled with the `experimental.givenLoopPrevention` language import. If no such import or setting is given, a warning is issued where the behavior would change under that import (for source version 3.4 and later). + +Old-style implicit definitions are unaffected by this change. + diff --git a/tests/neg/i15474.check b/tests/neg/i15474.check new file mode 100644 index 000000000000..3205f703cd50 --- /dev/null +++ b/tests/neg/i15474.check @@ -0,0 +1,31 @@ +-- Error: tests/neg/i15474.scala:6:39 ---------------------------------------------------------------------------------- +6 | given c: Conversion[ String, Int ] = _.toInt // error + | ^ + | Result of implicit search for ?{ toInt: ? } will change. + | Current result Test2.c will be no longer eligible + | because it is not defined before the search position. + | Result with new rules: augmentString. + | To opt into the new rules, compile with `-source future` or use + | the `scala.language.future` language import. + | + | To fix the problem without the language import, you could try one of the following: + | - use a `given ... with` clause as the enclosing given, + | - rearrange definitions so that Test2.c comes earlier, + | - use an explicit conversion, + | - use an import to get extension method into scope. + | This will be an error in Scala 3.5 and later. +-- Error: tests/neg/i15474.scala:12:56 --------------------------------------------------------------------------------- +12 | given Ordering[Price] = summon[Ordering[BigDecimal]] // error + | ^ + | Result of implicit search for Ordering[BigDecimal] will change. + | Current result Prices.Price.given_Ordering_Price will be no longer eligible + | because it is not defined before the search position. + | Result with new rules: scala.math.Ordering.BigDecimal. + | To opt into the new rules, compile with `-source future` or use + | the `scala.language.future` language import. + | + | To fix the problem without the language import, you could try one of the following: + | - use a `given ... with` clause as the enclosing given, + | - rearrange definitions so that Prices.Price.given_Ordering_Price comes earlier, + | - use an explicit argument. + | This will be an error in Scala 3.5 and later. diff --git a/tests/neg/i15474.scala b/tests/neg/i15474.scala index 8edf97a1e55a..b196d1b400ef 100644 --- a/tests/neg/i15474.scala +++ b/tests/neg/i15474.scala @@ -2,12 +2,8 @@ import scala.language.implicitConversions -object Test1: - given c: Conversion[ String, Int ] with - def apply(from: String): Int = from.toInt // error - object Test2: - given c: Conversion[ String, Int ] = _.toInt // loop not detected, could be used as a fallback to avoid the warning. + given c: Conversion[ String, Int ] = _.toInt // error object Prices { opaque type Price = BigDecimal @@ -15,4 +11,6 @@ object Prices { object Price{ given Ordering[Price] = summon[Ordering[BigDecimal]] // error } -} \ No newline at end of file +} + + diff --git a/tests/neg/i15474b.check b/tests/neg/i15474b.check new file mode 100644 index 000000000000..73ef720af7e3 --- /dev/null +++ b/tests/neg/i15474b.check @@ -0,0 +1,5 @@ +-- Error: tests/neg/i15474b.scala:7:40 --------------------------------------------------------------------------------- +7 | def apply(from: String): Int = from.toInt // error: infinite loop in function body + | ^^^^^^^^^^ + | Infinite loop in function body + | Test1.c.apply(from).toInt diff --git a/tests/neg/i15474b.scala b/tests/neg/i15474b.scala new file mode 100644 index 000000000000..9d496c37ef00 --- /dev/null +++ b/tests/neg/i15474b.scala @@ -0,0 +1,8 @@ +//> using options -Xfatal-warnings + +import scala.language.implicitConversions + +object Test1: + given c: Conversion[ String, Int ] with + def apply(from: String): Int = from.toInt // error: infinite loop in function body + diff --git a/tests/neg/i6716.check b/tests/neg/i6716.check new file mode 100644 index 000000000000..cdf655710452 --- /dev/null +++ b/tests/neg/i6716.check @@ -0,0 +1,15 @@ +-- Error: tests/neg/i6716.scala:12:39 ---------------------------------------------------------------------------------- +12 | given Monad[Bar] = summon[Monad[Foo]] // error + | ^ + | Result of implicit search for Monad[Foo] will change. + | Current result Bar.given_Monad_Bar will be no longer eligible + | because it is not defined before the search position. + | Result with new rules: Foo.given_Monad_Foo. + | To opt into the new rules, compile with `-source future` or use + | the `scala.language.future` language import. + | + | To fix the problem without the language import, you could try one of the following: + | - use a `given ... with` clause as the enclosing given, + | - rearrange definitions so that Bar.given_Monad_Bar comes earlier, + | - use an explicit argument. + | This will be an error in Scala 3.5 and later. diff --git a/tests/neg/i6716.scala b/tests/neg/i6716.scala new file mode 100644 index 000000000000..bbbd9d6d6cd0 --- /dev/null +++ b/tests/neg/i6716.scala @@ -0,0 +1,18 @@ +//> using options -Xfatal-warnings + +trait Monad[T]: + def id: String +class Foo +object Foo { + given Monad[Foo] with { def id = "Foo" } +} + +opaque type Bar = Foo +object Bar { + given Monad[Bar] = summon[Monad[Foo]] // error +} + +object Test extends App { + println(summon[Monad[Foo]].id) + println(summon[Monad[Bar]].id) +} \ No newline at end of file diff --git a/tests/neg/i7294-a.check b/tests/neg/i7294-a.check new file mode 100644 index 000000000000..2fe260fcf99a --- /dev/null +++ b/tests/neg/i7294-a.check @@ -0,0 +1,26 @@ +-- [E007] Type Mismatch Error: tests/neg/i7294-a.scala:10:20 ----------------------------------------------------------- +10 | case x: T => x.g(10) // error // error + | ^^^^^^^ + | Found: Any + | Required: T + | + | where: T is a type in given instance f with bounds <: foo.Foo + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg/i7294-a.scala:10:12 -------------------------------------------------------------------------------- +10 | case x: T => x.g(10) // error // error + | ^ + | Result of implicit search for scala.reflect.TypeTest[Nothing, T] will change. + | Current result foo.Test.f will be no longer eligible + | because it is not defined before the search position. + | Result with new rules: No Matching Implicit. + | To opt into the new rules, compile with `-source future` or use + | the `scala.language.future` language import. + | + | To fix the problem without the language import, you could try one of the following: + | - use a `given ... with` clause as the enclosing given, + | - rearrange definitions so that foo.Test.f comes earlier, + | - use an explicit argument. + | This will be an error in Scala 3.5 and later. + | + | where: T is a type in given instance f with bounds <: foo.Foo diff --git a/tests/neg/i7294-a.scala b/tests/neg/i7294-a.scala index 13981fa4d375..3453e88cf741 100644 --- a/tests/neg/i7294-a.scala +++ b/tests/neg/i7294-a.scala @@ -1,9 +1,13 @@ +//> using options -Xfatal-warnings + package foo trait Foo { def g(x: Int): Any } -inline given f[T <: Foo]: T = ??? match { - case x: T => x.g(10) // error -} +object Test: + + inline given f[T <: Foo]: T = ??? match { + case x: T => x.g(10) // error // error + } -@main def Test = f + @main def Test = f diff --git a/tests/neg/i7294-b.scala b/tests/neg/i7294-b.scala index 423d5037db96..8c6f9328cc20 100644 --- a/tests/neg/i7294-b.scala +++ b/tests/neg/i7294-b.scala @@ -1,9 +1,11 @@ +//> using options -Xfatal-warnings + package foo trait Foo { def g(x: Any): Any } inline given f[T <: Foo]: T = ??? match { - case x: T => x.g(10) // error + case x: T => x.g(10) // error // error } @main def Test = f diff --git a/tests/neg/looping-givens.scala b/tests/neg/looping-givens.scala new file mode 100644 index 000000000000..357a417f0ed9 --- /dev/null +++ b/tests/neg/looping-givens.scala @@ -0,0 +1,11 @@ +//> using options -Xfatal-warnings + +class A +class B + +given joint(using a: A, b: B): (A & B) = ??? + +def foo(using a: A, b: B) = + given aa: A = summon // error + given bb: B = summon // error + given ab: (A & B) = summon // error diff --git a/tests/pos/given-loop-prevention.scala b/tests/pos/given-loop-prevention.scala new file mode 100644 index 000000000000..0bae0bb24fed --- /dev/null +++ b/tests/pos/given-loop-prevention.scala @@ -0,0 +1,14 @@ +//> using options -Xfatal-warnings + +class Foo + +object Bar { + given Foo with {} + given List[Foo] = List(summon[Foo]) // ok +} + +object Baz { + @annotation.nowarn + given List[Foo] = List(summon[Foo]) // gives a warning, which is suppressed + given Foo with {} +} diff --git a/tests/pos/i15474.scala b/tests/pos/i15474.scala new file mode 100644 index 000000000000..f2c85120e4b2 --- /dev/null +++ b/tests/pos/i15474.scala @@ -0,0 +1,16 @@ +//> using options -Xfatal-warnings +import scala.language.implicitConversions +import scala.language.future + +object Test2: + given c: Conversion[ String, Int ] = _.toInt // now avoided, was loop not detected, could be used as a fallback to avoid the warning. + +object Prices { + opaque type Price = BigDecimal + + object Price{ + given Ordering[Price] = summon[Ordering[BigDecimal]] // was error, now avoided + } +} + + diff --git a/tests/pos/i19404.scala b/tests/pos/i19404.scala new file mode 100644 index 000000000000..8d6d4406ebb2 --- /dev/null +++ b/tests/pos/i19404.scala @@ -0,0 +1,13 @@ +given ipEncoder[IP <: IpAddress]: Encoder[IP] = Encoder[String].contramap(_.toString) + +class Encoder[A] { + final def contramap[B](f: B => A): Encoder[B] = new Encoder[B] +} + +object Encoder { + final def apply[A](implicit instance: Encoder[A]): Encoder[A] = instance + implicit final val encodeString: Encoder[String] = new Encoder[String] +} + +trait Json +trait IpAddress \ No newline at end of file diff --git a/tests/pos/i19407.scala b/tests/pos/i19407.scala new file mode 100644 index 000000000000..b7440a53540d --- /dev/null +++ b/tests/pos/i19407.scala @@ -0,0 +1,11 @@ +trait GeneratedEnum +trait Decoder[A] + +object Decoder: + given Decoder[Int] = ??? + +object GeneratedEnumDecoder: + + given [A <: GeneratedEnum]: Decoder[A] = + summon[Decoder[Int]] + ??? \ No newline at end of file diff --git a/tests/pos/i19417/defs_1.scala b/tests/pos/i19417/defs_1.scala new file mode 100644 index 000000000000..92dc10990d90 --- /dev/null +++ b/tests/pos/i19417/defs_1.scala @@ -0,0 +1,5 @@ +trait QueryParamDecoder[E]: + def emap[T](fn: E => Either[Throwable, T]): QueryParamDecoder[T] +object QueryParamDecoder: + def apply[T](implicit ev: QueryParamDecoder[T]): QueryParamDecoder[T] = ev + implicit lazy val stringQueryParamDecoder: QueryParamDecoder[String] = ??? \ No newline at end of file diff --git a/tests/pos/i19417/usage_2.scala b/tests/pos/i19417/usage_2.scala new file mode 100644 index 000000000000..c686f46280d7 --- /dev/null +++ b/tests/pos/i19417/usage_2.scala @@ -0,0 +1,2 @@ +given[E](using e: EnumOf[E]): QueryParamDecoder[E] = QueryParamDecoder[String].emap(_ => Right(???)) +trait EnumOf[E] \ No newline at end of file diff --git a/tests/pos/i6716.scala b/tests/pos/i6716.scala index 446cd49c9214..f02559af1e82 100644 --- a/tests/pos/i6716.scala +++ b/tests/pos/i6716.scala @@ -1,15 +1,14 @@ -trait Monad[T] +//> using options -Xfatal-warnings -source 3.4 + class Foo -object Foo { - given Monad[Foo] with {} -} -opaque type Bar = Foo object Bar { - given Monad[Bar] = summon[Monad[Foo]] + given Foo with {} + given List[Foo] = List(summon[Foo]) // ok } -object Test { - val mf = summon[Monad[Foo]] - val mb = summon[Monad[Bar]] -} \ No newline at end of file +object Baz { + @annotation.nowarn + given List[Foo] = List(summon[Foo]) // gives a warning, which is suppressed + given Foo with {} +} diff --git a/tests/pos/looping-givens.scala b/tests/pos/looping-givens.scala new file mode 100644 index 000000000000..0e615c8251df --- /dev/null +++ b/tests/pos/looping-givens.scala @@ -0,0 +1,11 @@ +import language.future + +class A +class B + +given joint(using a: A, b: B): (A & B) = ??? + +def foo(using a: A, b: B) = + given aa: A = summon // error + given bb: B = summon // error + given ab: (A & B) = summon // error diff --git a/tests/run/i17115.check b/tests/run/i17115.check new file mode 100644 index 000000000000..61c83cba41ce --- /dev/null +++ b/tests/run/i17115.check @@ -0,0 +1,2 @@ +4 +5 diff --git a/tests/pos/i17115.scala b/tests/run/i17115.scala similarity index 100% rename from tests/pos/i17115.scala rename to tests/run/i17115.scala diff --git a/tests/run/i6716.check b/tests/run/i6716.check new file mode 100644 index 000000000000..bb85bd267288 --- /dev/null +++ b/tests/run/i6716.check @@ -0,0 +1,2 @@ +Foo +Foo diff --git a/tests/run/i6716.scala b/tests/run/i6716.scala new file mode 100644 index 000000000000..3bef45ac7465 --- /dev/null +++ b/tests/run/i6716.scala @@ -0,0 +1,18 @@ +//> using options -Xfatal-warnings -source future + +trait Monad[T]: + def id: String +class Foo +object Foo { + given Monad[Foo] with { def id = "Foo" } +} + +opaque type Bar = Foo +object Bar { + given Monad[Bar] = summon[Monad[Foo]] // was error, fixed by given loop prevention +} + +object Test extends App { + println(summon[Monad[Foo]].id) + println(summon[Monad[Bar]].id) +} \ No newline at end of file