From d4968e7805b9280df5a7073a2b9d42b161e8325e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 12:54:45 +0100 Subject: [PATCH 1/2] Fix .toTuple insertion It previously did not follow aliases or upper bounds when deciding whether something was a named tuple. --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 9 +++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/pos/named-tuple-downcast.scala | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/pos/named-tuple-downcast.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index f343d7227bf8..0a219fa6ddfd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -145,6 +145,15 @@ class TypeUtils: case defn.NamedTuple(_, _) => true case _ => false + def derivesFromNamedTuple(using Context): Boolean = self match + case defn.NamedTuple(_, _) => true + case tp: MatchType => + tp.bound.derivesFromNamedTuple || tp.reduced.derivesFromNamedTuple + case tp: TypeProxy => tp.superType.derivesFromNamedTuple + case tp: AndType => tp.tp1.derivesFromNamedTuple || tp.tp2.derivesFromNamedTuple + case tp: OrType => tp.tp1.derivesFromNamedTuple && tp.tp2.derivesFromNamedTuple + case _ => false + /** Drop all named elements in tuple type */ def stripNamedTuple(using Context): Type = self.normalized.dealias match case defn.NamedTuple(_, vals) => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 13f7b4eb1726..1816d81ebdad 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4641,7 +4641,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _: SelectionProto => tree // adaptations for selections are handled in typedSelect case _ if ctx.mode.is(Mode.ImplicitsEnabled) && tree.tpe.isValueType => - if tree.tpe.widen.isNamedTupleType && pt.derivesFrom(defn.TupleClass) then + if tree.tpe.derivesFromNamedTuple && pt.derivesFrom(defn.TupleClass) then readapt(typed(untpd.Select(untpd.TypedSplice(tree), nme.toTuple))) else if pt.isRef(defn.AnyValClass, skipRefined = false) || pt.isRef(defn.ObjectClass, skipRefined = false) diff --git a/tests/pos/named-tuple-downcast.scala b/tests/pos/named-tuple-downcast.scala new file mode 100644 index 000000000000..239089b60c3d --- /dev/null +++ b/tests/pos/named-tuple-downcast.scala @@ -0,0 +1,20 @@ +type Person = (name: String, age: Int) + +val Bob: Person = (name = "Bob", age = 33) + +type SI = (String, Int) + +def id[X](x: X): X = x +val x: (String, Int) = Bob +val y: SI = id(Bob) +val and: Person & String = ??? +val _: SI = and +val or: Person | (name: "Bob", age: 33) = ??? +val _: SI = or + +class C[P <: Person](p: P): + val x: (String, Int) = p + + + + From d7aceee85a634ba5a867f4af686bf6aa800b6120 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 26 Nov 2024 14:36:46 +0100 Subject: [PATCH 2/2] Tweak other occurrences of `isNamedTuple` --- compiler/src/dotty/tools/dotc/interactive/Completion.scala | 2 +- compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 7a0a19552f48..ff5716b227ca 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -543,7 +543,7 @@ object Completion: .groupByName val qualTpe = qual.typeOpt - if qualTpe.isNamedTupleType then + if qualTpe.derivesFromNamedTuple then namedTupleCompletionsFromType(qualTpe) else if qualTpe.derivesFrom(defn.SelectableClass) then val pre = if !TypeOps.isLegalPrefix(qualTpe) then Types.SkolemType(qualTpe) else qualTpe diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 9750c41b7252..ee608a4297bf 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -107,7 +107,7 @@ object PatternMatcher { // TODO: Drop Case once we use everywhere else `isPatmatGenerated`. private def dropNamedTuple(tree: Tree): Tree = - val tpe = tree.tpe.widen + val tpe = tree.tpe.widenDealias if tpe.isNamedTupleType then tree.cast(tpe.stripNamedTuple) else tree /** The plan `let x = rhs in body(x)` where `x` is a fresh variable */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0727c83d8469..228206d8fb1e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -876,7 +876,7 @@ trait Implicits: || inferView(dummyTreeOfType(from), to) (using ctx.fresh.addMode(Mode.ImplicitExploration).setExploreTyperState()).isSuccess // TODO: investigate why we can't TyperState#test here - || from.widen.isNamedTupleType && to.derivesFrom(defn.TupleClass) + || from.widen.derivesFromNamedTuple && to.derivesFrom(defn.TupleClass) && from.widen.stripNamedTuple <:< to ) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 1816d81ebdad..96d57c644142 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2706,7 +2706,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer body1.isInstanceOf[RefTree] && !isWildcardArg(body1) || body1.isInstanceOf[Literal] val symTp = - if isStableIdentifierOrLiteral || pt.isNamedTupleType then pt + if isStableIdentifierOrLiteral || pt.dealias.isNamedTupleType then pt // need to combine tuple element types with expected named type else if isWildcardStarArg(body1) || pt == defn.ImplicitScrutineeTypeRef