Skip to content

Commit a582b27

Browse files
authored
Fix .toTuple insertion (#22028)
SIP 58 postulates an implicit insertion ot `.toTuple` when the target is a tuple and the source is a named tuple. This was previously not fully implemented. The implementation did not follow aliases or upper bounds when deciding whether something was a named tuple. This is fixed in this PR.
2 parents 10bb2e6 + d7aceee commit a582b27

File tree

6 files changed

+34
-5
lines changed

6 files changed

+34
-5
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeUtils.scala

+9
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ class TypeUtils:
145145
case defn.NamedTuple(_, _) => true
146146
case _ => false
147147

148+
def derivesFromNamedTuple(using Context): Boolean = self match
149+
case defn.NamedTuple(_, _) => true
150+
case tp: MatchType =>
151+
tp.bound.derivesFromNamedTuple || tp.reduced.derivesFromNamedTuple
152+
case tp: TypeProxy => tp.superType.derivesFromNamedTuple
153+
case tp: AndType => tp.tp1.derivesFromNamedTuple || tp.tp2.derivesFromNamedTuple
154+
case tp: OrType => tp.tp1.derivesFromNamedTuple && tp.tp2.derivesFromNamedTuple
155+
case _ => false
156+
148157
/** Drop all named elements in tuple type */
149158
def stripNamedTuple(using Context): Type = self.normalized.dealias match
150159
case defn.NamedTuple(_, vals) =>

Diff for: compiler/src/dotty/tools/dotc/interactive/Completion.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ object Completion:
543543
.groupByName
544544

545545
val qualTpe = qual.typeOpt
546-
if qualTpe.isNamedTupleType then
546+
if qualTpe.derivesFromNamedTuple then
547547
namedTupleCompletionsFromType(qualTpe)
548548
else if qualTpe.derivesFrom(defn.SelectableClass) then
549549
val pre = if !TypeOps.isLegalPrefix(qualTpe) then Types.SkolemType(qualTpe) else qualTpe

Diff for: compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ object PatternMatcher {
107107
// TODO: Drop Case once we use everywhere else `isPatmatGenerated`.
108108

109109
private def dropNamedTuple(tree: Tree): Tree =
110-
val tpe = tree.tpe.widen
110+
val tpe = tree.tpe.widenDealias
111111
if tpe.isNamedTupleType then tree.cast(tpe.stripNamedTuple) else tree
112112

113113
/** The plan `let x = rhs in body(x)` where `x` is a fresh variable */

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -876,7 +876,7 @@ trait Implicits:
876876
|| inferView(dummyTreeOfType(from), to)
877877
(using ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess
878878
// TODO: investigate why we can't TyperState#test here
879-
|| from.widen.isNamedTupleType && to.derivesFrom(defn.TupleClass)
879+
|| from.widen.derivesFromNamedTuple && to.derivesFrom(defn.TupleClass)
880880
&& from.widen.stripNamedTuple <:< to
881881
)
882882

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -2706,7 +2706,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27062706
body1.isInstanceOf[RefTree] && !isWildcardArg(body1)
27072707
|| body1.isInstanceOf[Literal]
27082708
val symTp =
2709-
if isStableIdentifierOrLiteral || pt.isNamedTupleType then pt
2709+
if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt
27102710
// need to combine tuple element types with expected named type
27112711
else if isWildcardStarArg(body1)
27122712
|| pt == defn.ImplicitScrutineeTypeRef
@@ -4644,7 +4644,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
46444644
case _: SelectionProto =>
46454645
tree // adaptations for selections are handled in typedSelect
46464646
case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType =>
4647-
if tree.tpe.widen.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then
4647+
if tree.tpe.derivesFromNamedTuple && pt.derivesFrom(defn.TupleClass) then
46484648
readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple)))
46494649
else if pt.isRef(defn.AnyValClass, skipRefined = false)
46504650
|| pt.isRef(defn.ObjectClass, skipRefined = false)

Diff for: tests/pos/named-tuple-downcast.scala

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
type Person = (name: String, age: Int)
2+
3+
val Bob: Person = (name = "Bob", age = 33)
4+
5+
type SI = (String, Int)
6+
7+
def id[X](x: X): X = x
8+
val x: (String, Int) = Bob
9+
val y: SI = id(Bob)
10+
val and: Person & String = ???
11+
val _: SI = and
12+
val or: Person | (name: "Bob", age: 33) = ???
13+
val _: SI = or
14+
15+
class C[P <: Person](p: P):
16+
val x: (String, Int) = p
17+
18+
19+
20+

0 commit comments

Comments
 (0)