Skip to content

Fix calculation to drop transparent classes #16344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 19 additions & 17 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -556,9 +556,12 @@ trait ConstraintHandling {
inst
end approximation

private def isTransparent(tp: Type)(using Context): Boolean = tp match
case AndType(tp1, tp2) => isTransparent(tp1) && isTransparent(tp2)
case _ => tp.typeSymbol.isTransparentClass && !tp.isLambdaSub
private def isTransparent(tp: Type, traitOnly: Boolean)(using Context): Boolean = tp match
case AndType(tp1, tp2) =>
isTransparent(tp1, traitOnly) && isTransparent(tp2, traitOnly)
case _ =>
val cls = tp.underlyingClassRef(refinementOK = false).typeSymbol
cls.isTransparentClass && (!traitOnly || cls.is(Trait))

/** If `tp` is an intersection such that some operands are transparent trait instances
* and others are not, replace as many transparent trait instances as possible with Any
Expand All @@ -567,28 +570,27 @@ trait ConstraintHandling {
* types (since in this case the type was not a true intersection of transparent traits
* and other types to start with).
*/
def dropTransparentClasses(tp: Type, bound: Type)(using Context): Type =
def dropTransparentTraits(tp: Type, bound: Type)(using Context): Type =
var kept: Set[Type] = Set() // types to keep since otherwise bound would not fit
var dropped: List[Type] = List() // the types dropped so far, last one on top

def dropOneTransparentClass(tp: Type): Type =
val tpd = tp.dealias
if isTransparent(tpd) && !kept.contains(tpd) then
dropped = tpd :: dropped
def dropOneTransparentTrait(tp: Type): Type =
if isTransparent(tp, traitOnly = true) && !kept.contains(tp) then
dropped = tp :: dropped
defn.AnyType
else tpd match
else tp match
case AndType(tp1, tp2) =>
val tp1w = dropOneTransparentClass(tp1)
val tp1w = dropOneTransparentTrait(tp1)
if tp1w ne tp1 then tp1w & tp2
else
val tp2w = dropOneTransparentClass(tp2)
val tp2w = dropOneTransparentTrait(tp2)
if tp2w ne tp2 then tp1 & tp2w
else tpd
else tp
case _ =>
tp

def recur(tp: Type): Type =
val tpw = dropOneTransparentClass(tp)
val tpw = dropOneTransparentTrait(tp)
if tpw eq tp then tp
else if tpw <:< bound then recur(tpw)
else
Expand All @@ -605,7 +607,7 @@ trait ConstraintHandling {
tp
else
tpw
end dropTransparentClasses
end dropTransparentTraits

/** If `tp` is an applied match type alias which is also an unreducible application
* of a higher-kinded type to a wildcard argument, widen to the match type's bound,
Expand All @@ -631,7 +633,7 @@ trait ConstraintHandling {
* union type (except for unions | Null, which are kept in the state they were).
* 3. Widen some irreducible applications of higher-kinded types to wildcard arguments
* (see @widenIrreducible).
* 4. Drop transparent traits from intersections (see @dropTransparentClasses).
* 4. Drop transparent traits from intersections (see @dropTransparentTraits).
*
* Don't do these widenings if `bound` is a subtype of `scala.Singleton`.
* Also, if the result of these widenings is a TypeRef to a module class,
Expand Down Expand Up @@ -662,10 +664,10 @@ trait ConstraintHandling {
val widenedFromSingle = widenSingle(inst)
val widenedFromUnion = widenOr(widenedFromSingle)
val widened =
if (widenedFromUnion ne widenedFromSingle) && isTransparent(widenedFromUnion) then
if (widenedFromUnion ne widenedFromSingle) && isTransparent(widenedFromUnion, traitOnly = false) then
widenedFromSingle
else
dropTransparentClasses(widenedFromUnion, bound)
dropTransparentTraits(widenedFromUnion, bound)
widenIrreducible(widened)

wideInst match
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3011,8 +3011,8 @@ object TypeComparer {
def widenInferred(inst: Type, bound: Type, widenUnions: Boolean)(using Context): Type =
comparing(_.widenInferred(inst, bound, widenUnions))

def dropTransparentClasses(tp: Type, bound: Type)(using Context): Type =
comparing(_.dropTransparentClasses(tp, bound))
def dropTransparentTraits(tp: Type, bound: Type)(using Context): Type =
comparing(_.dropTransparentTraits(tp, bound))

def constrainPatternType(pat: Type, scrut: Type, forceInvariantRefinement: Boolean = false)(using Context): Boolean =
comparing(_.constrainPatternType(pat, scrut, forceInvariantRefinement))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ trait Applications extends Compatibility {
&& tree.tpe.classSymbol.isEnumCase
&& tree.tpe.widen.isValueType
then
val widened = TypeComparer.dropTransparentClasses(
val widened = TypeComparer.dropTransparentTraits(
tree.tpe.parents.reduceLeft(TypeComparer.andType(_, _)),
pt)
if widened <:< pt then Typed(tree, TypeTree(widened))
Expand Down
8 changes: 4 additions & 4 deletions docs/_docs/reference/other-new-features/transparent-traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ declared transparent.

Transparent traits and classes can be given as explicit types as usual. But they are often elided when types are inferred. Roughly, the rules for type inference imply the following.

- Transparent traits and classes are dropped from intersections where possible.
- Transparent traits are dropped from intersections where possible.
- Union types are not widened if widening would result in only transparent supertypes.

The precise rules are as follows:
Expand All @@ -94,8 +94,8 @@ The precise rules are as follows:
- where that type is not higher-kinded,
- and where `B` is its known upper bound or `Any` if none exists:
- If the type inferred so far is of the form `T1 & ... & Tn` where
`n >= 1`, replace the maximal number of transparent `Ti`s by `Any`, while ensuring that
`n >= 1`, replace the maximal number of transparent traits `Ti`s by `Any`, while ensuring that
the resulting type is still a subtype of the bound `B`.
- However, do not perform this widening if all transparent types `Ti` can get replaced in that way. This clause ensures that a single transparent trait instance such as [`Product`](https://scala-lang.org/api/3.x/scala/Product.html) is not widened to [`Any`](https://scala-lang.org/api/3.x/scala/Any.html). Transparent trait instances are only dropped when they appear in conjunction with some other type.
- However, do not perform this widening if all types `Ti` can get replaced in that way. This clause ensures that a single transparent trait instance such as [`Product`](https://scala-lang.org/api/3.x/scala/Product.html) is not widened to [`Any`](https://scala-lang.org/api/3.x/scala/Any.html). Transparent trait instances are only dropped when they appear in conjunction with some other type.

- If the original type was a is union type that got widened in a previous step to a product consisting only of transparent types, keep the original union type instead of its widened form.
- If the original type was a is union type that got widened in a previous step to a product consisting only of transparent traits and classes, keep the original union type instead of its widened form.
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions tests/pos/i16338.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package de.sciss.kollflitz

import scala.collection.*

type Tagged[U] = { type Tag = U }
type @@ [+T, U] = T with Tagged[U]
private val anyTagger = new Tagger[Any]
final class Tagger[U] private[kollflitz] {
def apply[T](t : T): T @@ U = t.asInstanceOf[T @@ U]
}
def tag[U]: Tagger[U] = anyTagger.asInstanceOf[Tagger[U]]

sealed trait Sorted


/** Enrichment methods for random access collections. */
implicit final class KollFlitzSortedIndexedSeq[A, CC[_], Repr](val self: SeqOps[A, CC, Repr] @@ Sorted)
extends AnyVal {

/** Nearest percentile (rounded index, no interpolation). */
def percentile(n: Int): A = self((self.size * n - 50) / 100)

/** Median found by rounding the index (no interpolation). */
def median: A = percentile(50)
}
18 changes: 18 additions & 0 deletions tests/pos/i16342.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type Opaque = Base with Tag

type Base = Any {
type Hack
}

trait Tag

object Opaque {
def apply(value: String): Opaque = value.asInstanceOf[Opaque]

def unapply(userId: Opaque): Option[String] = Option(userId).map(_.value)
def unappy2(userId: Base with Tag): Option[String] = Option(userId).map(_.value)
}

final implicit class Ops(private val userId: Opaque) extends AnyVal {
def value: String = userId.asInstanceOf[String]
}
6 changes: 6 additions & 0 deletions tests/pos/transparent-intersect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
object Test:

val x: Any { type T = Int } & Object = ???
val y = if ??? then x else x
val _ : Object { type T = Int } = y