diff --git a/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala index 758e5d7f5cc6..47f65f04cff7 100644 --- a/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CheckLoopingImplicits.scala @@ -6,13 +6,28 @@ import MegaPhase.MiniPhase import Contexts.*, Types.*, Symbols.*, SymDenotations.*, Flags.* import ast.* import Decorators.* - +import StdNames.* object CheckLoopingImplicits: val name: String = "checkLoopingImplicits" val description: String = "check that implicit defs do not call themselves in an infinite loop" -/** Checks that implicit defs do not call themselves in an infinite loop */ +/** Checks that some definitions do not call themselves in an infinite loop + * This is an incomplete check, designed to catch some likely bugs instead + * of being exhaustive. The situations where infinite loops are diagnosed are + * 1. A given method should not directly call itself + * 2. An apply method in a given object should not directly call itself + * 3. A lazy val should not directly force itself + * 4. An extension method should not directly call itself + * + * In all these cases, there are some situations which would not lead to + * an infinite loop at runtime. For instance, the call could go at runtime to an + * overriding version of the method or val which breaks the loop. That's why + * this phase only issues warnings, not errors, and also why we restrict + * checks to the 4 cases above, where a recursion is somewhat hidden. + * There are also other more complicated calling patterns that could also + * be diagnosed as loops with more effort. This could be improved in the future. + */ class CheckLoopingImplicits extends MiniPhase: thisPhase => import tpd._ @@ -60,6 +75,9 @@ class CheckLoopingImplicits extends MiniPhase: case Block(stats, expr) => stats.foreach(checkNotLooping) checkNotLooping(expr) + case Inlined(_, bindings, expr) => + bindings.foreach(checkNotLooping) + checkNotLooping(expr) case Typed(expr, _) => checkNotLooping(expr) case Assign(lhs, rhs) => @@ -84,7 +102,9 @@ class CheckLoopingImplicits extends MiniPhase: checkNotLooping(t.rhs) case _ => - if sym.isOneOf(GivenOrImplicit | Lazy | ExtensionMethod) then + if sym.isOneOf(GivenOrImplicit | Lazy | ExtensionMethod) + || sym.name == nme.apply && sym.owner.is(Module) && sym.owner.sourceModule.isOneOf(GivenOrImplicit) + then checkNotLooping(mdef.rhs) mdef end transform diff --git a/tests/neg-custom-args/fatal-warnings/i15474.scala b/tests/neg-custom-args/fatal-warnings/i15474.scala new file mode 100644 index 000000000000..86b01eb28ce6 --- /dev/null +++ b/tests/neg-custom-args/fatal-warnings/i15474.scala @@ -0,0 +1,16 @@ +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. + +object Prices { + opaque type Price = BigDecimal + + object Price{ + given Ordering[Price] = summon[Ordering[BigDecimal]] // error + } +} \ No newline at end of file diff --git a/tests/neg/i13044.check b/tests/neg/i13044.check index c5584aadf767..437e84c0f480 100644 --- a/tests/neg/i13044.check +++ b/tests/neg/i13044.check @@ -1,3 +1,135 @@ +-- Warning: tests/neg/i13044.scala:50:40 ------------------------------------------------------------------------------- +50 | implicit def typeSchema: Schema[A] = Schema.gen // error // error + | ^^^^^^^^^^ + |Infinite loop in function body + |{ + | val SchemaDerivation_this: Schema.type = Schema + | { + | val SchemaDerivation_this: (SchemaDerivation_this : Schema.type) = SchemaDerivation_this + | { + | val $scrutinee1: + | scala.deriving.Mirror.Product{ + | MirroredMonoType = A; MirroredType = A; MirroredLabel = ("A" : String); MirroredElemTypes = (A, B); + | MirroredElemLabels = (("a" : String), ("b" : String)) + | } + | = + | A.$asInstanceOf[ + | scala.deriving.Mirror.Product{ + | MirroredMonoType = A; MirroredType = A; MirroredLabel = ("A" : String); MirroredElemTypes = (A, B); + | MirroredElemLabels = (("a" : String), ("b" : String)) + | } + | ] + | val m: + | scala.deriving.Mirror.Product{ + | MirroredMonoType = A; MirroredType = A; MirroredLabel = ("A" : String); MirroredElemTypes = (A, B); + | MirroredElemLabels = (("a" : String), ("b" : String)) + | } + | = $scrutinee1 + | lazy val fields: List[Schema[Any]] = + | { + | val SchemaDerivation_this: (SchemaDerivation_this : (SchemaDerivation_this : Schema.type)) = + | SchemaDerivation_this + | { + | val builder: Schema[Any] = TestApp.typeSchema.asInstanceOf[Schema[Any]] + | { + | val SchemaDerivation_this: + | (SchemaDerivation_this : (SchemaDerivation_this : (SchemaDerivation_this : Schema.type))) + | = SchemaDerivation_this + | ( + | { + | val builder: Schema[Any] = + | { + | val SchemaDerivation_this: Schema.type = Schema + | ( + | { + | val SchemaDerivation_this: (SchemaDerivation_this : Schema.type) = SchemaDerivation_this + | { + | val $scrutinee4: + | scala.deriving.Mirror.Product{ + | MirroredMonoType = B; MirroredType = B; MirroredLabel = ("B" : String); + | MirroredElemTypes = C *: EmptyTuple.type + | ; MirroredElemLabels = ("c" : String) *: EmptyTuple.type + | } + | = + | B.$asInstanceOf[ + | scala.deriving.Mirror.Product{ + | MirroredMonoType = B; MirroredType = B; MirroredLabel = ("B" : String); + | MirroredElemTypes = C *: EmptyTuple.type + | ; MirroredElemLabels = ("c" : String) *: EmptyTuple.type + | } + | ] + | val m: + | scala.deriving.Mirror.Product{ + | MirroredMonoType = B; MirroredType = B; MirroredLabel = ("B" : String); + | MirroredElemTypes = C *: EmptyTuple.type + | ; MirroredElemLabels = ("c" : String) *: EmptyTuple.type + | } + | = $scrutinee4 + | lazy val fields: List[Schema[Any]] = + | { + | val SchemaDerivation_this: + | (SchemaDerivation_this : (SchemaDerivation_this : Schema.type)) + | = SchemaDerivation_this + | { + | val builder: Schema[Any] = + | { + | val SchemaDerivation_this: Schema.type = Schema + | ( + | { + | val SchemaDerivation_this: (...SchemaDerivation_this : ....type) = + | SchemaDerivation_this + | { + | val $scrutinee6: + | ...{ + | MirroredMonoType...; MirroredType...; MirroredLabel...; + | MirroredElemTypes... + | ; MirroredElemLabels... + | } + | = ....$asInstanceOf[...] + | val m: ... = ...$scrutinee6 + | lazy val fields: ... = + | { + | val SchemaDerivation_this: ... = ... + | ...:... + | } + | { + | final class $anon() extends ...(), ... { + | def build: ... = ... + | } + | ...():... + | } + | }:...[...] + | } + | :Schema[C]) + | }.asInstanceOf[Schema[Any]] + | SchemaDerivation_this.recurse[EmptyTuple.type].::[Schema[Any]](builder) + | }:List[Schema[Any]] + | } + | { + | final class $anon() extends Object(), Schema[B] { + | def build: B = ??? + | } + | new Object with Schema[B] {...}():Schema[B] + | } + | }:Schema[B] + | } + | :Schema[B]) + | }.asInstanceOf[Schema[Any]] + | SchemaDerivation_this.recurse[EmptyTuple.type].::[Schema[Any]](builder) + | } + | :List[Schema[Any]]) + | }.::[Schema[Any]](builder) + | }:List[Schema[Any]] + | } + | { + | final class $anon() extends Object(), Schema[A] { + | def build: A = ??? + | } + | new Object with Schema[A] {...}():Schema[A] + | } + | }:Schema[A] + | }:Schema[A] + |} -- Error: tests/neg/i13044.scala:50:40 --------------------------------------------------------------------------------- 50 | implicit def typeSchema: Schema[A] = Schema.gen // error // error | ^^^^^^^^^^