Skip to content

Commit 5cfcfa8

Browse files
authored
Reinstantiate restriction to transparent inline methods (#20371)
Reverts parts of #19922. Fixes #20342 Fixes #20297 The logic that we should ignore declared result types of inline methods really only applies to transparent inlines.
2 parents 82ac0dd + 6e8bea7 commit 5cfcfa8

File tree

10 files changed

+181
-10
lines changed

10 files changed

+181
-10
lines changed

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

+27-7
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import Constants.*
1111
import util.{Stats, SimpleIdentityMap, SimpleIdentitySet}
1212
import Decorators.*
1313
import Uniques.*
14-
import Flags.Method
14+
import Flags.{Method, Transparent}
1515
import inlines.Inlines
16+
import config.{Feature, SourceVersion}
1617
import config.Printers.typr
1718
import Inferencing.*
1819
import ErrorReporting.*
@@ -108,7 +109,7 @@ object ProtoTypes {
108109
res
109110

110111
/** Constrain result with two special cases:
111-
* 1. If `meth` is an inlineable method in an inlineable context,
112+
* 1. If `meth` is a transparent inlineable method in an inlineable context,
112113
* we should always succeed and not constrain type parameters in the expected type,
113114
* because the actual return type can be a subtype of the currently known return type.
114115
* However, we should constrain parameters of the declared return type. This distinction is
@@ -128,11 +129,30 @@ object ProtoTypes {
128129
case _ =>
129130
false
130131

131-
if Inlines.isInlineable(meth) then
132-
constrainResult(mt, wildApprox(pt))
133-
true
134-
else
135-
constFoldException(pt) || constrainResult(mt, pt)
132+
constFoldException(pt) || {
133+
if Inlines.isInlineable(meth) then
134+
// Stricter behavisour in 3.4+: do not apply `wildApprox` to non-transparent inlines
135+
// unless their return type is a MatchType. In this case there's no reason
136+
// not to constrain type variables in the expected type. For transparent inlines
137+
// we do not want to constrain type variables in the expected type since the
138+
// actual return type might be smaller after instantiation. For inlines returning
139+
// MatchTypes we do not want to constrain because the MatchType might be more
140+
// specific after instantiation. TODO: Should we also use Wildcards for non-inline
141+
// methods returning MatchTypes?
142+
if Feature.sourceVersion.isAtLeast(SourceVersion.`3.4`) then
143+
if meth.is(Transparent) || mt.resultType.isMatchAlias then
144+
constrainResult(mt, wildApprox(pt))
145+
// do not constrain the result type of transparent inline methods
146+
true
147+
else
148+
constrainResult(mt, pt)
149+
else
150+
// Best-effort to fix https://github.com/scala/scala3/issues/9685 in the 3.3.x series
151+
// while preserving source compatibility as much as possible
152+
constrainResult(mt, wildApprox(pt)) || meth.is(Transparent)
153+
else constrainResult(mt, pt)
154+
}
155+
136156
end constrainResult
137157
end Compatibility
138158

tests/neg/i18123.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E172] Type Error: tests/neg/i18123.scala:25:33 ---------------------------------------------------------------------
2+
25 | (charClassIntersection.rep() | classItem.rep()) // error
3+
| ^^^^^^^^^^^^^^^
4+
|No given instance of type pkg.Implicits.Repeater[pkg.RegexTree, V] was found.
5+
|I found:
6+
|
7+
| pkg.Implicits.Repeater.GenericRepeaterImplicit[T]
8+
|
9+
|But method GenericRepeaterImplicit in object Repeater does not match type pkg.Implicits.Repeater[pkg.RegexTree, V]
10+
|
11+
|where: V is a type variable with constraint <: Seq[pkg.CharClassIntersection]
12+
|.

tests/neg/i18123.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// may not compile anymore in Scala 3.4+
2+
package pkg
3+
4+
trait P[+T]
5+
6+
extension [T](inline parse0: P[T])
7+
inline def | [V >: T](inline other: P[V]): P[V] = ???
8+
9+
extension [T](inline parse0: => P[T])
10+
inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ???
11+
12+
object Implicits:
13+
trait Repeater[-T, R]
14+
object Repeater:
15+
implicit def GenericRepeaterImplicit[T]: Repeater[T, Seq[T]] = ???
16+
17+
sealed trait RegexTree
18+
abstract class Node extends RegexTree
19+
class CharClassIntersection() extends Node
20+
21+
def classItem: P[RegexTree] = ???
22+
def charClassIntersection: P[CharClassIntersection] = ???
23+
24+
def x =
25+
(charClassIntersection.rep() | classItem.rep()) // error

tests/pos/i18123.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ extension [T](inline parse0: P[T])
77
inline def | [V >: T](inline other: P[V]): P[V] = ???
88

99
extension [T](inline parse0: => P[T])
10-
inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ???
10+
// transparent needed to make this compile in 3.4+
11+
transparent inline def rep[V](inline min: Int = 0)(using repeater: Implicits.Repeater[T, V]): P[V] = ???
1112

1213
object Implicits:
1314
trait Repeater[-T, R]

tests/pos/i19415.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
def Test = {
2+
val left: Parser[String] = ???
3+
val right: Parser[Int] = ???
4+
val both = left && right
5+
6+
val works = both.map(Ior.Both.apply)
7+
val fails = (left && right).map(Ior.Both.apply)
8+
}
9+
10+
trait Parser[T]:
11+
final def &&[T2](other: Parser[T2])(implicit zip: Zip[T, T2]): Parser[zip.Out] = ???
12+
final def map[T2](f: T => T2): Parser[T2] = ???
13+
14+
infix trait Ior[+A, +B]
15+
object Ior:
16+
final case class Both[+A, +B](a: A, b: B) extends (A Ior B)
17+
18+
trait Zip[In1, In2]:
19+
type Out
20+
21+
object Zip {
22+
type Out[In1, In2, O] = Zip[In1, In2] { type Out = O }
23+
implicit def zip2[_1, _2]: Zip.Out[_1, _2, (_1, _2)] = ???
24+
}

tests/pos/i19479.scala

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
case class Person(id: Int)
2+
3+
class GeodeContinuousSourceSpec {
4+
summon[PdxEncoder[Person]]
5+
}
6+
7+
trait PdxEncoder[A] {
8+
def encode(a: A): Boolean
9+
}
10+
11+
object PdxEncoder extends ObjectEncoder {
12+
implicit def intEncoder: PdxEncoder[Int] = ???
13+
}
14+
15+
trait ObjectEncoder {
16+
given emptyTupleEncoder: PdxEncoder[EmptyTuple] = ???
17+
18+
given tupleEncoder[K <: String, H, T <: Tuple](using
19+
m: ValueOf[K],
20+
hEncoder: PdxEncoder[H],
21+
tEncoder: PdxEncoder[T]
22+
): PdxEncoder[FieldType[K, H] *: T] = ???
23+
24+
given objectEncoder[A, Repr <: Tuple](using
25+
gen: LabelledGeneric.Aux[A, Repr],
26+
tupleEncoder: PdxEncoder[Repr]
27+
): PdxEncoder[A] = ???
28+
}
29+
30+
import scala.deriving.Mirror
31+
32+
private type FieldType[K, +V] = V & KeyTag[K, V]
33+
private type KeyTag[K, +V]
34+
private type ZipWith[T1 <: Tuple, T2 <: Tuple, F[_, _]] <: Tuple = (T1, T2) match {
35+
case (h1 *: t1, h2 *: t2) => F[h1, h2] *: ZipWith[t1, t2, F]
36+
case (EmptyTuple, ?) => EmptyTuple
37+
case (?, EmptyTuple) => EmptyTuple
38+
case _ => Tuple
39+
}
40+
41+
private trait LabelledGeneric[A] {
42+
type Repr
43+
}
44+
45+
private object LabelledGeneric {
46+
type Aux[A, R] = LabelledGeneric[A] { type Repr = R }
47+
48+
transparent inline given productInst[A <: Product](using
49+
m: Mirror.ProductOf[A]
50+
): LabelledGeneric.Aux[A, ZipWith[m.MirroredElemLabels, m.MirroredElemTypes, FieldType]] =
51+
new LabelledGeneric[A] {
52+
type Repr = Tuple & ZipWith[m.MirroredElemLabels, m.MirroredElemTypes, FieldType]
53+
}
54+
}

tests/pos/i20297.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
sealed abstract class Kyo[+T, -S]
2+
opaque type <[+T, -S] >: T = T | Kyo[T, S]
3+
4+
extension [T, S](v: T < S)
5+
inline def map[U, S2](inline f: T => U < S2): U < (S & S2) = ???
6+
7+
class Streams[V]
8+
object Streams:
9+
def emitValue[V](v: V): Unit < Streams[V] = ???
10+
11+
opaque type Stream[+T, V, -S] = T < (Streams[V] & S)
12+
object Stream:
13+
extension [T, V, S](s: Stream[T, V, S])
14+
def reemit[S2, V2](f: V => Unit < (Streams[V2] & S2)): Stream[T, V2, S & S2] = ???
15+
def filter[S2](f: V => Boolean < S2): Stream[T, V, S & S2] = reemit { v =>
16+
f(v).map {
17+
case false => ()
18+
case true => Streams.emitValue(v)
19+
}
20+
}

tests/pos/i20342.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Repo[EC, E](using defaults: RepoDefaults[EC, E])
2+
trait RepoDefaults[EC, E]
3+
object RepoDefaults:
4+
inline given genImmutableRepo[E: DbCodec]: RepoDefaults[E, E] = ???
5+
inline given genRepo[EC: DbCodec, E: DbCodec]: RepoDefaults[EC, E] = ???
6+
7+
trait DbCodec[E]
8+
9+
case class PersonCreator(name: String)
10+
case class Person(id: Long)
11+
given DbCodec[Person] = ???
12+
given DbCodec[PersonCreator] = ???
13+
14+
@main def Test =
15+
val personRepo = Repo[PersonCreator, Person]

0 commit comments

Comments
 (0)