Skip to content

Commit c3e4c40

Browse files
committed
Fully define qualifier type before giving up in selections
Fixes #12373
1 parent 69c800b commit c3e4c40

File tree

4 files changed

+58
-12
lines changed

4 files changed

+58
-12
lines changed

compiler/src/dotty/tools/dotc/typer/Inferencing.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ object Inferencing {
8989
}
9090

9191
/** If `tp` is top-level type variable with a lower bound in the current constraint,
92-
* instantiate it from below. We also look for TypeVars whereever their instantiation
93-
* could uncover new type members.
92+
* instantiate it from below. We also look for TypeVars in other places where
93+
* their instantiation could uncover new type members. However that search is best
94+
* effort only. It might miss type variables that appear in structures involving
95+
* alias types and type projections.
9496
*/
9597
def couldInstantiateTypeVar(tp: Type)(using Context): Boolean = tp.dealias match
9698
case tvar: TypeVar

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import config.Printers.typr
1212
import ast.Trees._
1313
import NameOps._
1414
import ProtoTypes._
15-
import Inferencing.couldInstantiateTypeVar
1615
import collection.mutable
1716
import reporting._
1817
import Checking.{checkNoPrivateLeaks, checkNoWildcard}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -574,13 +574,17 @@ class Typer extends Namer
574574
checkLegalValue(select, pt)
575575
ConstFold(select)
576576
else if couldInstantiateTypeVar(qual.tpe.widen) then
577-
// try again with more defined qualifier type
577+
// there's a simply visible type variable in the result; try again with a more defined qualifier type
578+
// There's a second trial wherw we try to instantiate all type variables in `qual.tpe.widen`,
579+
// but that is done only after we search for extension methods or conversions.
578580
typedSelect(tree, pt, qual)
579581
else
580582
val tree1 = tryExtensionOrConversion(
581-
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, privateOK = true)
583+
tree, pt, IgnoredProto(pt), qual, ctx.typerState.ownedVars, this, inSelect = true)
582584
if !tree1.isEmpty then
583585
tree1
586+
else if canDefineFurther(qual.tpe.widen) then
587+
typedSelect(tree, pt, qual)
584588
else if qual.tpe.derivesFrom(defn.DynamicClass)
585589
&& selName.isTermName && !isDynamicExpansion(tree)
586590
then
@@ -3037,7 +3041,7 @@ class Typer extends Namer
30373041
if selProto.isMatchedBy(qual.tpe) then None
30383042
else
30393043
tryEither {
3040-
val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, privateOK = false)
3044+
val tree1 = tryExtensionOrConversion(tree, pt, pt, qual, locked, NoViewsAllowed, inSelect = false)
30413045
if tree1.isEmpty then None
30423046
else Some(adapt(tree1, pt, locked))
30433047
} { (_, _) => None
@@ -3051,10 +3055,10 @@ class Typer extends Namer
30513055
* @return The converted tree, or `EmptyTree` is not successful.
30523056
*/
30533057
def tryExtensionOrConversion
3054-
(tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, privateOK: Boolean)
3058+
(tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, inSelect: Boolean)
30553059
(using Context): Tree =
30563060

3057-
def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK)
3061+
def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect)
30583062

30593063
def tryExtension(using Context): Tree =
30603064
findRef(tree.name, WildcardType, ExtensionMethod, EmptyFlags, qual.srcPos) match
@@ -3092,12 +3096,13 @@ class Typer extends Namer
30923096
return typedSelect(tree, pt, found)
30933097
case failure: SearchFailure =>
30943098
if failure.isAmbiguous then
3095-
return (
3096-
if canDefineFurther(qual.tpe.widen) then
3097-
tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, privateOK)
3099+
return
3100+
if !inSelect // in a selection we will do the canDefineFurther afterwards
3101+
&& canDefineFurther(qual.tpe.widen)
3102+
then
3103+
tryExtensionOrConversion(tree, pt, mbrProto, qual, locked, compat, inSelect)
30983104
else
30993105
err.typeMismatch(qual, selProto, failure.reason) // TODO: report NotAMember instead, but need to be aware of failure
3100-
)
31013106
rememberSearchFailure(qual, failure)
31023107
}
31033108
catch case ex: TypeError => nestedFailure(ex)

tests/pos/i12373.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
sealed case class Column[A](name: String)
2+
3+
sealed trait ColumnSet {
4+
type Append[That <: ColumnSet] <: ColumnSet
5+
def ++[That <: ColumnSet](that: That): Append[That]
6+
}
7+
8+
object ColumnSet {
9+
type Empty = Empty.type
10+
type Singleton[A] = Cons[A, Empty]
11+
12+
case object Empty extends ColumnSet {
13+
type Append[That <: ColumnSet] = That
14+
override def ++[That <: ColumnSet](that: That): Append[That] = that
15+
}
16+
17+
sealed case class Cons[A, B <: ColumnSet](head: Column[A], tail: B) extends ColumnSet { self =>
18+
type Append[That <: ColumnSet] = Cons[A, tail.Append[That]]
19+
override def ++[That <: ColumnSet](that: That): Append[That] = Cons(head, tail ++ that)
20+
}
21+
22+
def long(name: String): Singleton[Long] = Cons(Column[Long](name), Empty)
23+
def string(name: String): Singleton[String] = Cons(Column[String](name), Empty)
24+
}
25+
26+
object Example {
27+
import ColumnSet._
28+
val schema0 = long("id") ++ string("first_name")
29+
30+
// inferred type 3.0.0-RC3: Singleton[Long]#Append[Cons[String, Empty]]#Append[Singleton[String]]
31+
// inferred type 2.13.5 : Cons[Long,Cons[String,Singleton[String]]]
32+
val schema1 = long("id") ++ string("first_name") ++ string("last_name")
33+
34+
// inferred type 3.0.0-RC3: error
35+
// inferred type 2.13.5 : Cons[Long,Cons[String,Cons[String,Singleton[Long]]]]
36+
val schema2 = long("id") ++ string("first_name") ++ string("last_name") ++ long("age")
37+
38+
// inferred type 3.0.0-RC3: Singleton[Long]#Append[Cons[String, Empty]]#Append[Singleton[String]]#Append[Cons[Long, Empty]]
39+
val schema3 = ((long("id") ++ string("first_name") ++ string("last_name")): Singleton[Long]#Append[ColumnSet.Cons[String, ColumnSet.Empty]]#Append[ColumnSet.Singleton[String]]) ++ long("age")
40+
}

0 commit comments

Comments
 (0)