Skip to content
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
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/SourceVersion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ enum SourceVersion:
def enablesNewGivens = isAtLeast(`3.6`)
def enablesNamedTuples = isAtLeast(`3.7`)
def enablesBetterFors(using Context) = isAtLeast(`3.8`) || (isAtLeast(`3.7`) && isPreviewEnabled)
/** See PR #23441 and tests/neg/i23435-min */
def enablesDistributeAnd = !isAtLeast(`3.8`)

def requiresNewSyntax = isAtLeast(future)

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ class Definitions {
@tu lazy val CBCompanion: TypeSymbol = // type `<context-bound-companion>`[-Refs]
enterPermanentSymbol(tpnme.CBCompanion,
TypeBounds(NothingType,
HKTypeLambda(tpnme.syntheticTypeParamName(0) :: Nil, Contravariant :: Nil)(
HKTypeLambda(tpnme.syntheticTypeParamName(0) :: Nil)(
tl => TypeBounds.empty :: Nil,
tl => AnyType))).asType

Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ object NamerOps:
* The context-bound companion has as name the name of `tsym` translated to
* a term name. We create a synthetic val of the form
*
* val A: `<context-bound-companion>`[witnessRef1 | ... | witnessRefN]
* val A: `<context-bound-companion>`[witnessRef1] & ... & `<context-bound-companion>`[witnessRefN]
*
* where
*
Expand All @@ -325,8 +325,7 @@ object NamerOps:
prefix.select(params.find(_.name == witnessName).get)
else
witnessNames.map(TermRef(prefix, _))
val cbtype = defn.CBCompanion.typeRef.appliedTo:
witnessRefs.reduce[Type](OrType(_, _, soft = false))
val cbtype = witnessRefs.map(defn.CBCompanion.typeRef.appliedTo).reduce(AndType.apply)
val cbc = newSymbol(
ctx.owner, companionName,
(tsym.flagsUNSAFE & (AccessFlags)).toTermFlags | Synthetic,
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class SymUtils:
}

def isContextBoundCompanion(using Context): Boolean =
self.is(Synthetic) && self.infoOrCompleter.typeSymbol == defn.CBCompanion
self.is(Synthetic) && self.infoOrCompleter.isContextBoundCompanion

def isDummyCaptureParam(using Context): Boolean =
self.is(PhantomSymbol) && self.infoOrCompleter.typeSymbol != defn.CBCompanion
Expand Down
12 changes: 10 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Symbols.*
import SymDenotations.LazyType
import Decorators.*
import util.Stats.*
import config.Feature.sourceVersion
import Names.*
import StdNames.nme
import Flags.{Module, Provisional}
Expand Down Expand Up @@ -475,13 +476,20 @@ class TypeApplications(val self: Type) extends AnyVal {
self.derivedExprType(tp.translateParameterized(from, to))
case _ =>
if (self.derivesFrom(from)) {
// NOTE: we assume the `To` class is covariant s.t.
// `To[T] X To[U] <:< To[T | U]` where X ::= `&` | `|`
def elemType(tp: Type): Type = tp.widenDealias match
case tp: OrType =>
if tp.tp1.isBottomType then elemType(tp.tp2)
else if tp.tp2.isBottomType then elemType(tp.tp1)
else tp.derivedOrType(elemType(tp.tp1), elemType(tp.tp2))
case tp: AndType => tp.derivedAndType(elemType(tp.tp1), elemType(tp.tp2))
case _ => tp.baseType(from).argInfos.headOption.getOrElse(defn.NothingType)
case tp @ AndType(tp1, tp2) =>
if sourceVersion.enablesDistributeAnd
then tp.derivedAndType(elemType(tp1), elemType(tp2))
else OrType(elemType(tp1), elemType(tp2), soft = false)
case _ =>
tp.baseType(from).argInfos.headOption.getOrElse(defn.NothingType)
end elemType
val arg = elemType(self)
val arg1 = if (wildcardArg) TypeBounds.upper(arg) else arg
to.typeRef.appliedTo(arg1)
Expand Down
14 changes: 8 additions & 6 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2460,7 +2460,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling

/** If the range `tp1..tp2` consist of a single type, that type, otherwise NoType`.
* This is the case if `tp1 =:= tp2`, but also if `tp1 <:< tp2`, `tp1` is a singleton type,
* and `tp2` derives from `scala.Singleton` (or vice-versa). Examples of the latter case:
* and `tp2` derives from `scala.Singleton` and `sourceVersion.enablesDistributeAnd` (or vice-versa).
* Examples of the latter case:
*
* "name".type .. Singleton
* "name".type .. String & Singleton
Expand All @@ -2473,8 +2474,10 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
def isSingletonBounds(lo: Type, hi: Type) =
lo.isSingleton && hi.derivesFrom(defn.SingletonClass) && isSubTypeWhenFrozen(lo, hi)
if (isSameTypeWhenFrozen(tp1, tp2)) tp1
else if (isSingletonBounds(tp1, tp2)) tp1
else if (isSingletonBounds(tp2, tp1)) tp2
else if sourceVersion.enablesDistributeAnd then
if (isSingletonBounds(tp1, tp2)) tp1
else if (isSingletonBounds(tp2, tp1)) tp2
else NoType
else NoType
}

Expand Down Expand Up @@ -2771,7 +2774,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
* @pre !(tp1 <: tp2) && !(tp2 <:< tp1) -- these cases were handled before
*/
private def distributeAnd(tp1: Type, tp2: Type): Type = tp1 match {
case tp1 @ AppliedType(tycon1, args1) =>
case tp1 @ AppliedType(tycon1, args1) if sourceVersion.enablesDistributeAnd =>
tp2 match {
case AppliedType(tycon2, args2)
if tycon1.typeSymbol == tycon2.typeSymbol && tycon1 =:= tycon2 =>
Expand Down Expand Up @@ -2819,8 +2822,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
}

/** Try to distribute `|` inside type, detect and handle conflicts
* Note that, unlike for `&`, a disjunction cannot be pushed into
* a refined or applied type. Example:
* Note that a disjunction cannot be pushed into a refined or applied type. Example:
*
* List[T] | List[U] is not the same as List[T | U].
*
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ object Types extends TypeUtils {
case AppliedType(tycon: TypeRef, arg :: Nil) => defn.isInto(tycon.symbol)
case _ => false

/** Is this type of the form `<context-bound-companion>[Ref1] & ... & <context-bound-companion>[RefN]`?
* Where the intersection may be introduced by `NamerOps.addContextBoundCompanionFor`
* or by inheriting multiple context bound companions for the same name.
*/
def isContextBoundCompanion(using Context): Boolean = this.widen match
case AndType(tp1, tp2) => tp1.isContextBoundCompanion.ensuring(_ == tp2.isContextBoundCompanion)
case tp => tp.typeSymbol == defn.CBCompanion

/** Is this type a legal target type for an implicit conversion, so that
* no `implicitConversions` language import is necessary?
*/
Expand Down
34 changes: 16 additions & 18 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
// Otherwise, if the qualifier is a context bound companion, handle
// by selecting a witness in typedCBSelect
def tryCBCompanion() =
if qual.tpe.typeSymbol == defn.CBCompanion then
if qual.tpe.isContextBoundCompanion then
typedCBSelect(tree0, pt, qual)
else EmptyTree

Expand Down Expand Up @@ -1001,13 +1001,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* alternatives referred to by `witnesses`.
* @param prevs a list of (ref tree, typer state, term ref) tripls that
* represents previously identified alternatives
* @param witnesses a type of the form ref_1 | ... | ref_n containing references
* @param witnesses a type of the form `isContextBoundCompanion` containing references
* still to be considered.
*/
def tryAlts(prevs: Alts, witnesses: Type): Alts = witnesses match
case OrType(wit1, wit2) =>
def tryAlts(prevs: Alts, witnesses: Type): Alts = witnesses.widen match
case AndType(wit1, wit2) =>
tryAlts(tryAlts(prevs, wit1), wit2)
case witness: TermRef =>
case AppliedType(_, List(witness: TermRef)) =>
val altQual = tpd.ref(witness).withSpan(qual.span)
val altCtx = ctx.fresh.setNewTyperState()
val alt = typedSelectWithAdapt(tree, pt, altQual)(using altCtx)
Expand All @@ -1019,19 +1019,17 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
if comparisons.exists(_ == 1) then prevs
else current :: prevs.zip(comparisons).collect{ case (prev, cmp) if cmp != -1 => prev }

qual.tpe.widen match
case AppliedType(_, arg :: Nil) =>
tryAlts(Nil, arg) match
case Nil => EmptyTree
case (best @ (bestTree, bestState, _)) :: Nil =>
bestState.commit()
bestTree
case multiAlts =>
report.error(
em"""Ambiguous witness reference. None of the following alternatives is more specific than the other:
|${multiAlts.map((alt, _, witness) => i"\n $witness.${tree.name}: ${alt.tpe.widen}")}""",
tree.srcPos)
EmptyTree
tryAlts(Nil, qual.tpe) match
case Nil => EmptyTree
case (best @ (bestTree, bestState, _)) :: Nil =>
bestState.commit()
bestTree
case multiAlts =>
report.error(
em"""Ambiguous witness reference. None of the following alternatives is more specific than the other:
|${multiAlts.map((alt, _, witness) => i"\n $witness.${tree.name}: ${alt.tpe.widen}")}""",
tree.srcPos)
EmptyTree
end typedCBSelect

def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
Expand Down
5 changes: 0 additions & 5 deletions docs/_docs/reference/new-types/intersection-types-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ A & B <: B A & B <: A
In another word, `A & B` is the same type as `B & A`, in the sense that the two types
have the same values and are subtypes of each other.

If `C` is a co- or contravariant type constructor, then `C[A] & C[B]` can be simplified using the following rules:

- If `C` is covariant, `C[A] & C[B] ~> C[A & B]`
- If `C` is contravariant, `C[A] & C[B] ~> C[A | B]`

When `C` is covariant, `C[A & B] <: C[A] & C[B]` can be derived:

```
Expand Down
24 changes: 24 additions & 0 deletions docs/_docs/reference/new-types/union-types-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ case _: (A | B) => ...
A & (B | C) =:= A & B | A & C
```

When `C` is covariant, `C[A] | C[B] <: C[A | B]` can be derived:

```
A <: A B <: B
---------- ---------
A <: A | B B <: A | B
---------------- ----------------
C[A] <: C[A | B] C[B] <: C[A | B]
-----------------------------------------
C[A] | C[B] <: C[A | B]
```

When `C` is contravariant, `C[A] | C[B] <: C[A & B]` can be derived:

```
A <: A B <: B
---------- ----------
A & B <: A A & B <: B
---------------- ----------------
C[A] <: C[A & B] C[B] <: C[A & B]
-----------------------------------------
C[A] | C[B] <: C[A & B]
```

From these rules it follows that the _least upper bound_ (LUB) of a set of types
is the union of these types. This replaces the
[definition of least upper bound in the Scala 2 specification](https://www.scala-lang.org/files/archive/spec/2.13/03-types.html#least-upper-bounds-and-greatest-lower-bounds).
Expand Down
4 changes: 2 additions & 2 deletions library/src/scala/collection/Iterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ trait EvidenceIterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]], E
trait SortedSetFactoryDefaults[+A,
+CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]],
+WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Set[x]] extends SortedSetOps[A @uncheckedVariance, CC, CC[A @uncheckedVariance]] {
self: IterableOps[A, WithFilterCC, _] =>
self: IterableOps[A, WithFilterCC, CC[A @uncheckedVariance]] =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked both changes here with @sjrd and they should be fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted the PR as I had doubt over this change and it was breaking everything.

Copy link
Member

@sjrd sjrd Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I'm getting the following, but in unclear situations (clean build worked; recompiled with a clean scala3-libary-nonbootstrapped does not work).

[error] -- [E046] Cyclic Error: /localhome/doeraene/projects/dotty/library/src/scala/collection/Iterable.scala:992:11 
[error] 992 |    +CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]],
[error]     |           ^
[error]     |           Cyclic reference involving trait SortedSet
[error]     |
[error]     |            Run with -explain-cyclic for more details.s
[error]     |
[error]     | longer explanation available when compiling with `-explain`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hum, no, cleaning the library project fixes it. But still, it means it's appearing under some separate/incremental compilation scenarios. :s


override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance] = sortedIterableFactory.from(coll)(using ordering)
override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](using ordering)
Expand Down Expand Up @@ -1046,7 +1046,7 @@ trait SortedMapFactoryDefaults[K, +V,
+CC[x, y] <: Map[x, y] with SortedMapOps[x, y, CC, CC[x, y]] with UnsortedCC[x, y],
+WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Iterable[x],
+UnsortedCC[x, y] <: Map[x, y]] extends SortedMapOps[K, V, CC, CC[K, V @uncheckedVariance]] with MapOps[K, V, UnsortedCC, CC[K, V @uncheckedVariance]] with caps.Pure {
self: IterableOps[(K, V), WithFilterCC, _] =>
self: IterableOps[(K, V), WithFilterCC, CC[K, V @uncheckedVariance]] =>

override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(using ordering)
override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering)
Expand Down
9 changes: 0 additions & 9 deletions tests/neg-deep-subtype/i11064.scala

This file was deleted.

7 changes: 7 additions & 0 deletions tests/neg/conflicting-inst-basetypes.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

object Test:
trait A[T]
trait B1 extends A[Int]
trait B2 extends A[String]
class D extends B1, B2 // error: cannot be instantiated since it has conflicting base types Test.A[Int] and Test.A[String]
// NOTE this is not accepted in Scala 2 either
7 changes: 0 additions & 7 deletions tests/neg/i10256.scala

This file was deleted.

4 changes: 0 additions & 4 deletions tests/neg/i11103.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,4 @@
case p: P =>
new Foo // error
}

class UpBndAndB extends UpBnd[Bar] with P
// ClassCastException: Foo cannot be cast to Bar
val x = pmatch(new UpBndAndB)
}
7 changes: 7 additions & 0 deletions tests/neg/i23435-min.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//> using options -source:3.8

type Or[+A, +B] = A | B

val x: Or[Int, String] & Or[String, Int] = 3
val y: Or[Int & String, String & Int] = x // error
val z: String = y
17 changes: 17 additions & 0 deletions tests/neg/i23435.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//> using options -source:3.8

trait L[+A]{val a:A}
trait R[+B]{val b: B}

class LR(val a: Int, val b: String) extends L[Int] with R[String]

type E[+A] = L[A] | R[A]

val x: E[Int] & E[String] = LR(4, "hi")
val y: E[Int&String] = x // error

val z = y match
case l : L[Int&String] => l.a
case r : R[Int&String] => r.b

val _ = z:String // was: java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
4 changes: 3 additions & 1 deletion tests/neg/i3989e.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//> using options -source:3.8

object Test extends App {
trait A[+X](val x: X)
class B extends A(5) with A("hello") // error: A is extended twice
class B extends A(5) with A("hello") // error: A is extended twice // error: class B cannot be instantiated since it has conflicting base types Test.A[Int] and Test.A[String]

def f(a: A[Int]): Int = a match {
case b: B => b.x
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/lucre-23441-min.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

trait Txn[T]
trait Expr[X <: Txn[X], +Y]
trait BinOp[T, Repr[X <: Txn[X]] <: Expr[X, T]]

trait IntObj[T] extends Expr[T, Int]
trait IntBinOp extends BinOp[Int, IntObj]
object IntEq extends IntBinOp

object Test:
val r: BinOp[?, ?] = IntEq : BinOp[Int, IntObj] // error: Required: BinOp[?, ?[X] <: Expr[X, BinOp[?, ?]#T]]
// We would need the second wildcard to "depend" on the 1st one,
// e.g. have SomeBinop[?] where `type SomeBinop[X] = BinOp[X, ? <: [Y] =>> Expr[Y, X]]`,
// but this is an instance of an unreducible application of higher-kinded type to a wildcard argument.
// Also note there would be no error if we made BinOp covariant in T.
17 changes: 17 additions & 0 deletions tests/neg/singletonInterval.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//> using options -source:3.8

/** Why the singletonInterval logic cannot be applied for lubArgs and glbArgs in TypeComparer. */

type Or[+A, +B] = A | B

object TestGlb:
val x: Or["3", Singleton] & Or[Singleton, "3"] = 3
val y: Or["3", "3"] = x // error
val z: String = y

object TestLub:
def f[P[_, _]](x1: P["3", Singleton], x2: P[Singleton, "3"]): P["3", "3"] =
val x = if true then x1 else x2
x // error, was accepted because inferred type of x was `P["3", "3"]`
// by going through Types.join, TypeOps.mergeRefinedOrApplied, TypeComparer#lubArgs, TypeComparer#singletonInterval
val z: String = f[Or](3, 3)
2 changes: 1 addition & 1 deletion tests/pos-macros/quoted-pattern-type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object Lib {
e

case e @ '{ Some($x: Int) } =>
e: Expr[T & Some[Int]]
e: Expr[T] & Expr[Some[Int]]
x: Expr[Int]
e

Expand Down
Loading
Loading