Skip to content

Fully define qualifier type before giving up in selections #12389

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 2 commits into from
May 10, 2021
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
6 changes: 4 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Inferencing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
23 changes: 14 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
40 changes: 40 additions & 0 deletions tests/pos/i12373.scala
Original file line number Diff line number Diff line change
@@ -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")
}