Skip to content

Commit

Permalink
Reinstantiate restriction to transparent inline methods (#20371)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odersky authored Jun 12, 2024
2 parents 82ac0dd + 6e8bea7 commit 5cfcfa8
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 10 deletions.
2 changes: 1 addition & 1 deletion community-build/community-projects/munit
34 changes: 27 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import Constants.*
import util.{Stats, SimpleIdentityMap, SimpleIdentitySet}
import Decorators.*
import Uniques.*
import Flags.Method
import Flags.{Method, Transparent}
import inlines.Inlines
import config.{Feature, SourceVersion}
import config.Printers.typr
import Inferencing.*
import ErrorReporting.*
Expand Down Expand Up @@ -108,7 +109,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
Expand All @@ -128,11 +129,30 @@ object ProtoTypes {
case _ =>
false

if Inlines.isInlineable(meth) then
constrainResult(mt, wildApprox(pt))
true
else
constFoldException(pt) || constrainResult(mt, pt)
constFoldException(pt) || {
if Inlines.isInlineable(meth) then
// 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) || mt.resultType.isMatchAlias 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

Expand Down
12 changes: 12 additions & 0 deletions tests/neg/i18123.check
Original file line number Diff line number Diff line change
@@ -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]
|.
25 changes: 25 additions & 0 deletions tests/neg/i18123.scala
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion tests/pos/i18123.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
24 changes: 24 additions & 0 deletions tests/pos/i19415.scala
Original file line number Diff line number Diff line change
@@ -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)] = ???
}
54 changes: 54 additions & 0 deletions tests/pos/i19479.scala
Original file line number Diff line number Diff line change
@@ -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]
}
}
20 changes: 20 additions & 0 deletions tests/pos/i20297.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
15 changes: 15 additions & 0 deletions tests/pos/i20342.scala
Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit 5cfcfa8

Please sign in to comment.