Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Adapt context-bound-companion logic to use AndType of AppliedTypes
Instead of AppliedType of OrTypes, which relied on the distribution of AndTypes when inheriting multiple context bound companions of the same name
  • Loading branch information
EugeneFlesselle committed Jul 18, 2025
commit a08477935dc5a10df9611f3524dcdcdb29eec8f5
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.isAllOf(CaptureParam) && !(self.isClass || self.is(Method))
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 @@ -928,7 +928,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 @@ -997,13 +997,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 @@ -1015,19 +1015,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
47 changes: 36 additions & 11 deletions tests/pos/cb-companion-joins.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,39 @@ trait M[Self]:
trait Num[Self]:
def zero: Self

trait A extends M[A]
trait B extends M[A]

trait AA:
type X: M
trait BB:
type X: Num
class CC[X1: {M, Num}] extends AA, BB:
type X = X1
X.zero
X.unit
object Test1:
trait X extends M[X]
trait Y extends M[Y]

object Test2:
trait A[X: Num]:
X.zero
trait B[X: {M, Num}]:
X.unit
X.zero

object Test3:

trait A:
type X: M
X.unit

trait B:
type X: Num
X.zero

trait C extends A, B:
X.zero
X.unit

class AA[Y: M] extends A:
type X = Y
X.unit
Y.unit

class CC[Y: {M, Num}] extends C:
type X = Y
X.zero
X.unit
Y.zero
Y.unit