diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 04a3d3e7493e..7fb446a028fc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -89,8 +89,10 @@ object Inferencing { } /** If `tp` is top-level type variable with a lower bound in the current constraint, - * instantiate it from below. We also look for TypeVars whereever their instantiation - * could uncover new type members. + * instantiate it from below. We also look for TypeVars in other places where + * their instantiation could uncover new type members. However that search is best + * effort only. It might miss type variables that appear in structures involving + * alias types and type projections. */ def couldInstantiateTypeVar(tp: Type)(using Context): Boolean = tp.dealias match case tvar: TypeVar diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 2d6311f76ebe..ea702e47e673 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -12,7 +12,6 @@ import config.Printers.typr import ast.Trees._ import NameOps._ import ProtoTypes._ -import Inferencing.couldInstantiateTypeVar import collection.mutable import reporting._ import Checking.{checkNoPrivateLeaks, checkNoWildcard} diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 214872788942..cf215f71155d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -574,13 +574,17 @@ class Typer extends Namer checkLegalValue(select, pt) ConstFold(select) else if couldInstantiateTypeVar(qual.tpe.widen) then - // try again with more defined qualifier type + // there's a simply visible type variable in the result; try again with a more defined qualifier type + // There's a second trial where we try to instantiate all type variables in `qual.tpe.widen`, + // but that is done only after we search for extension methods or conversions. typedSelect(tree, pt, qual) else val tree1 = tryExtensionOrConversion( - tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, privateOK = true) + tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true) if !tree1.isEmpty then tree1 + else if canDefineFurther(qual.tpe.widen) then + typedSelect(tree, pt, qual) else if qual.tpe.derivesFrom(defn.DynamicClass) && selName.isTermName && !isDynamicExpansion(tree) then @@ -3037,7 +3041,7 @@ class Typer extends Namer if selProto.isMatchedBy(qual.tpe) then None else tryEither { - val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, privateOK = false) + val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, inSelect = false) if tree1.isEmpty then None else Some(adapt(tree1, pt, locked)) } { (_, _) => None @@ -3051,10 +3055,10 @@ class Typer extends Namer * @return The converted tree, or `EmptyTree` is not successful. */ def tryExtensionOrConversion - (tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, privateOK: Boolean) + (tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, inSelect: Boolean) (using Context): Tree = - def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK) + def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect) def tryExtension(using Context): Tree = findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos) match @@ -3092,12 +3096,13 @@ class Typer extends Namer return typedSelect(tree, pt, found) case failure: SearchFailure => if failure.isAmbiguous then - return ( - if canDefineFurther(qual.tpe.widen) then - tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, privateOK) + return + if !inSelect // in a selection we will do the canDefineFurther afterwards + && canDefineFurther(qual.tpe.widen) + then + tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, inSelect) else err.typeMismatch(qual, selProto, failure.reason) // TODO: report NotAMember instead, but need to be aware of failure - ) rememberSearchFailure(qual, failure) } catch case ex: TypeError => nestedFailure(ex) diff --git a/tests/pos/i12373.scala b/tests/pos/i12373.scala new file mode 100644 index 000000000000..61ac1e085892 --- /dev/null +++ b/tests/pos/i12373.scala @@ -0,0 +1,40 @@ +sealed case class Column[A](name: String) + +sealed trait ColumnSet { + type Append[That <: ColumnSet] <: ColumnSet + def ++[That <: ColumnSet](that: That): Append[That] +} + +object ColumnSet { + type Empty = Empty.type + type Singleton[A] = Cons[A, Empty] + + case object Empty extends ColumnSet { + type Append[That <: ColumnSet] = That + override def ++[That <: ColumnSet](that: That): Append[That] = that + } + + sealed case class Cons[A, B <: ColumnSet](head: Column[A], tail: B) extends ColumnSet { self => + type Append[That <: ColumnSet] = Cons[A, tail.Append[That]] + override def ++[That <: ColumnSet](that: That): Append[That] = Cons(head, tail ++ that) + } + + def long(name: String): Singleton[Long] = Cons(Column[Long](name), Empty) + def string(name: String): Singleton[String] = Cons(Column[String](name), Empty) +} + +object Example { + import ColumnSet._ + val schema0 = long("id") ++ string("first_name") + + // inferred type 3.0.0-RC3: Singleton[Long]#Append[Cons[String, Empty]]#Append[Singleton[String]] + // inferred type 2.13.5 : Cons[Long,Cons[String,Singleton[String]]] + val schema1 = long("id") ++ string("first_name") ++ string("last_name") + + // inferred type 3.0.0-RC3: error + // inferred type 2.13.5 : Cons[Long,Cons[String,Cons[String,Singleton[Long]]]] + val schema2 = long("id") ++ string("first_name") ++ string("last_name") ++ long("age") + + // inferred type 3.0.0-RC3: Singleton[Long]#Append[Cons[String, Empty]]#Append[Singleton[String]]#Append[Cons[Long, Empty]] + val schema3 = ((long("id") ++ string("first_name") ++ string("last_name")): Singleton[Long]#Append[ColumnSet.Cons[String, ColumnSet.Empty]]#Append[ColumnSet.Singleton[String]]) ++ long("age") +}