From 42cf114774d49766cdef2d6195b030689d7ee19c Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 9 May 2024 14:18:01 +0200 Subject: [PATCH 1/5] Reinstantiate restriction to transparent inline methods Reverts parts of #19922. Fixes #20342, #20297 The logic that we should ignore declared result types of inline methods really only applies to transparent inlines. --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 7 +++--- tests/neg/i18123.check | 12 +++++++++ tests/neg/i18123.scala | 25 +++++++++++++++++++ tests/pos/i18123.scala | 3 ++- tests/pos/i20297.scala | 20 +++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i18123.check create mode 100644 tests/neg/i18123.scala create mode 100644 tests/pos/i20297.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index ecf1da30cac1..7bfe00aea13e 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -11,7 +11,7 @@ import Constants.* import util.{Stats, SimpleIdentityMap, SimpleIdentitySet} import Decorators.* import Uniques.* -import Flags.Method +import Flags.{Method, Transparent} import inlines.Inlines import config.Printers.typr import Inferencing.* @@ -108,7 +108,7 @@ object ProtoTypes { res /** Constrain result with two special cases: - * 1. If `meth` is an inlineable method in an inlineable context, + * 1. If `meth` is a transparent inlineable method in an inlineable context, * we should always succeed and not constrain type parameters in the expected type, * because the actual return type can be a subtype of the currently known return type. * However, we should constrain parameters of the declared return type. This distinction is @@ -128,11 +128,12 @@ object ProtoTypes { case _ => false - if Inlines.isInlineable(meth) then + if Inlines.isInlineable(meth) && meth.is(Transparent) then constrainResult(mt, wildApprox(pt)) true else constFoldException(pt) || constrainResult(mt, pt) + end constrainResult end Compatibility diff --git a/tests/neg/i18123.check b/tests/neg/i18123.check new file mode 100644 index 000000000000..d784c4d12673 --- /dev/null +++ b/tests/neg/i18123.check @@ -0,0 +1,12 @@ +-- [E172] Type Error: tests/neg/i18123.scala:25:33 --------------------------------------------------------------------- +25 | (charClassIntersection.rep() | classItem.rep()) // error + | ^^^^^^^^^^^^^^^ + |No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found. + |I found: + | + | pkg.Implicits.Repeater.GenericRepeaterImplicit[T] + | + |But method GenericRepeaterImplicit in object Repeater does not match type pkg.Implicits.Repeater[pkg.RegexTree, V] + | + |where: V is a type variable with constraint <: Seq[pkg.CharClassIntersection] + |. diff --git a/tests/neg/i18123.scala b/tests/neg/i18123.scala new file mode 100644 index 000000000000..bb220dc78e93 --- /dev/null +++ b/tests/neg/i18123.scala @@ -0,0 +1,25 @@ +// may not compile anymore in Scala 3.4+ +package pkg + +trait P[+T] + +extension [T](inline parse0: P[T]) + inline def | [V >: T](inline other: P[V]): P[V] = ??? + +extension [T](inline parse0: => P[T]) + inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ??? + +object Implicits: + trait Repeater[-T, R] + object Repeater: + implicit def GenericRepeaterImplicit[T]: Repeater[T, Seq[T]] = ??? + +sealed trait RegexTree +abstract class Node extends RegexTree +class CharClassIntersection() extends Node + +def classItem: P[RegexTree] = ??? +def charClassIntersection: P[CharClassIntersection] = ??? + +def x = + (charClassIntersection.rep() | classItem.rep()) // error diff --git a/tests/pos/i18123.scala b/tests/pos/i18123.scala index 714850004d2c..2b18b3fc73c3 100644 --- a/tests/pos/i18123.scala +++ b/tests/pos/i18123.scala @@ -7,7 +7,8 @@ extension [T](inline parse0: P[T]) inline def | [V >: T](inline other: P[V]): P[V] = ??? extension [T](inline parse0: => P[T]) - inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ??? + // transparent needed to make this compile in 3.4+ + transparent inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ??? object Implicits: trait Repeater[-T, R] diff --git a/tests/pos/i20297.scala b/tests/pos/i20297.scala new file mode 100644 index 000000000000..ee7ee57045ae --- /dev/null +++ b/tests/pos/i20297.scala @@ -0,0 +1,20 @@ +sealed abstract class Kyo[+T, -S] +opaque type <[+T, -S] >: T = T | Kyo[T, S] + +extension [T, S](v: T < S) + inline def map[U, S2](inline f: T => U < S2): U < (S & S2) = ??? + +class Streams[V] +object Streams: + def emitValue[V](v: V): Unit < Streams[V] = ??? + +opaque type Stream[+T, V, -S] = T < (Streams[V] & S) +object Stream: + extension [T, V, S](s: Stream[T, V, S]) + def reemit[S2, V2](f: V => Unit < (Streams[V2] & S2)): Stream[T, V2, S & S2] = ??? + def filter[S2](f: V => Boolean < S2): Stream[T, V, S & S2] = reemit { v => + f(v).map { + case false => () + case true => Streams.emitValue(v) + } + } From 25b733e9c4c2e853d00c21b94f5270bb0490799e Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 9 May 2024 15:19:50 +0200 Subject: [PATCH 2/5] Re-instantiate previous behavior also for LTS --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 7bfe00aea13e..aae795277136 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -13,6 +13,7 @@ import Decorators.* import Uniques.* import Flags.{Method, Transparent} import inlines.Inlines +import config.{Feature, SourceVersion} import config.Printers.typr import Inferencing.* import ErrorReporting.* @@ -128,11 +129,22 @@ object ProtoTypes { case _ => false - if Inlines.isInlineable(meth) && meth.is(Transparent) then - constrainResult(mt, wildApprox(pt)) - true - else - constFoldException(pt) || constrainResult(mt, pt) + constFoldException(pt) || { + if Inlines.isInlineable(meth) then + // Stricter behaviour in 3.4+: do not apply `wildApprox` to non-transparent inlines + if Feature.sourceVersion.isAtLeast(SourceVersion.`3.4`) then + if meth.is(Transparent) then + constrainResult(mt, wildApprox(pt)) + // do not constrain the result type of transparent inline methods + true + else + constrainResult(mt, pt) + else + // Best-effort to fix https://github.com/scala/scala3/issues/9685 in the 3.3.x series + // while preserving source compatibility as much as possible + constrainResult(mt, wildApprox(pt)) || meth.is(Transparent) + else constrainResult(mt, pt) + } end constrainResult end Compatibility From d8a3d3b0182206626b9d2923ef335c017dcc79cb Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 14 May 2024 14:30:16 +0200 Subject: [PATCH 3/5] Revert changes in CB to #17924 --- community-build/community-projects/munit | 2 +- community-build/community-projects/specs2 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/community-build/community-projects/munit b/community-build/community-projects/munit index 5c77d7316fc6..c5d6f474fa0d 160000 --- a/community-build/community-projects/munit +++ b/community-build/community-projects/munit @@ -1 +1 @@ -Subproject commit 5c77d7316fc66adaed64e9532ee0a45a668b01ec +Subproject commit c5d6f474fa0d481e2c29f15d6a67d10ef2099e78 diff --git a/community-build/community-projects/specs2 b/community-build/community-projects/specs2 index a618330aa808..005c5847ecf9 160000 --- a/community-build/community-projects/specs2 +++ b/community-build/community-projects/specs2 @@ -1 +1 @@ -Subproject commit a618330aa80833787859dae805d02e45d4304c42 +Subproject commit 005c5847ecf9439691505f0628d318b0fed9d341 From 07e9c40533c3ca1af6238941289ab803afa19c57 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Tue, 14 May 2024 16:55:58 +0200 Subject: [PATCH 4/5] Add test for #20342 --- tests/pos/i20342.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 tests/pos/i20342.scala diff --git a/tests/pos/i20342.scala b/tests/pos/i20342.scala new file mode 100644 index 000000000000..250839680174 --- /dev/null +++ b/tests/pos/i20342.scala @@ -0,0 +1,15 @@ +class Repo[EC, E](using defaults: RepoDefaults[EC, E]) +trait RepoDefaults[EC, E] +object RepoDefaults: + inline given genImmutableRepo[E: DbCodec]: RepoDefaults[E, E] = ??? + inline given genRepo[EC: DbCodec, E: DbCodec]: RepoDefaults[EC, E] = ??? + +trait DbCodec[E] + +case class PersonCreator(name: String) +case class Person(id: Long) +given DbCodec[Person] = ??? +given DbCodec[PersonCreator] = ??? + +@main def Test = + val personRepo = Repo[PersonCreator, Person] From 6e8bea7d15d062a142533c28c07e4d078635d675 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 11 Jun 2024 10:35:21 +0200 Subject: [PATCH 5/5] Refine constraining scheme for result types - Take MatchTypes into account - Add test cases for tests that failed originally and would otherwise fail again after this PR. i19415.scala is fixed by the MatchType extension. i19749.scala was fixed by adding a `transparent`. --- .../dotty/tools/dotc/typer/ProtoTypes.scala | 11 +++- tests/pos/i19415.scala | 24 +++++++++ tests/pos/i19479.scala | 54 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i19415.scala create mode 100644 tests/pos/i19479.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index aae795277136..5909cda8c428 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -131,9 +131,16 @@ object ProtoTypes { constFoldException(pt) || { if Inlines.isInlineable(meth) then - // Stricter behaviour in 3.4+: do not apply `wildApprox` to non-transparent inlines + // Stricter behavisour in 3.4+: do not apply `wildApprox` to non-transparent inlines + // unless their return type is a MatchType. In this case there's no reason + // not to constrain type variables in the expected type. For transparent inlines + // we do not want to constrain type variables in the expected type since the + // actual return type might be smaller after instantiation. For inlines returning + // MatchTypes we do not want to constrain because the MatchType might be more + // specific after instantiation. TODO: Should we also use Wildcards for non-inline + // methods returning MatchTypes? if Feature.sourceVersion.isAtLeast(SourceVersion.`3.4`) then - if meth.is(Transparent) then + if meth.is(Transparent) || mt.resultType.isMatchAlias then constrainResult(mt, wildApprox(pt)) // do not constrain the result type of transparent inline methods true diff --git a/tests/pos/i19415.scala b/tests/pos/i19415.scala new file mode 100644 index 000000000000..3d9c40127cb0 --- /dev/null +++ b/tests/pos/i19415.scala @@ -0,0 +1,24 @@ +def Test = { + val left: Parser[String] = ??? + val right: Parser[Int] = ??? + val both = left && right + + val works = both.map(Ior.Both.apply) + val fails = (left && right).map(Ior.Both.apply) +} + +trait Parser[T]: + final def &&[T2](other: Parser[T2])(implicit zip: Zip[T, T2]): Parser[zip.Out] = ??? + final def map[T2](f: T => T2): Parser[T2] = ??? + +infix trait Ior[+A, +B] +object Ior: + final case class Both[+A, +B](a: A, b: B) extends (A Ior B) + +trait Zip[In1, In2]: + type Out + +object Zip { + type Out[In1, In2, O] = Zip[In1, In2] { type Out = O } + implicit def zip2[_1, _2]: Zip.Out[_1, _2, (_1, _2)] = ??? +} \ No newline at end of file diff --git a/tests/pos/i19479.scala b/tests/pos/i19479.scala new file mode 100644 index 000000000000..a12bd378a490 --- /dev/null +++ b/tests/pos/i19479.scala @@ -0,0 +1,54 @@ +case class Person(id: Int) + +class GeodeContinuousSourceSpec { + summon[PdxEncoder[Person]] +} + +trait PdxEncoder[A] { + def encode(a: A): Boolean +} + +object PdxEncoder extends ObjectEncoder { + implicit def intEncoder: PdxEncoder[Int] = ??? +} + +trait ObjectEncoder { + given emptyTupleEncoder: PdxEncoder[EmptyTuple] = ??? + + given tupleEncoder[K <: String, H, T <: Tuple](using + m: ValueOf[K], + hEncoder: PdxEncoder[H], + tEncoder: PdxEncoder[T] + ): PdxEncoder[FieldType[K, H] *: T] = ??? + + given objectEncoder[A, Repr <: Tuple](using + gen: LabelledGeneric.Aux[A, Repr], + tupleEncoder: PdxEncoder[Repr] + ): PdxEncoder[A] = ??? +} + +import scala.deriving.Mirror + +private type FieldType[K, +V] = V & KeyTag[K, V] +private type KeyTag[K, +V] +private type ZipWith[T1 <: Tuple, T2 <: Tuple, F[_, _]] <: Tuple = (T1, T2) match { + case (h1 *: t1, h2 *: t2) => F[h1, h2] *: ZipWith[t1, t2, F] + case (EmptyTuple, ?) => EmptyTuple + case (?, EmptyTuple) => EmptyTuple + case _ => Tuple +} + +private trait LabelledGeneric[A] { + type Repr +} + +private object LabelledGeneric { + type Aux[A, R] = LabelledGeneric[A] { type Repr = R } + + transparent inline given productInst[A <: Product](using + m: Mirror.ProductOf[A] + ): LabelledGeneric.Aux[A, ZipWith[m.MirroredElemLabels, m.MirroredElemTypes, FieldType]] = + new LabelledGeneric[A] { + type Repr = Tuple & ZipWith[m.MirroredElemLabels, m.MirroredElemTypes, FieldType] + } +} \ No newline at end of file