Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
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