From 3d11e5e462b86ffa37c1945896faad38edcec1de Mon Sep 17 00:00:00 2001 From: aherlihy Date: Wed, 8 Jan 2025 15:52:05 -0500 Subject: [PATCH 1/6] Handle TypeProxy of Named Tuples, minimal fix without refactoring --- .../src/dotty/tools/dotc/ast/Desugar.scala | 2 +- .../src/dotty/tools/dotc/core/TypeUtils.scala | 20 +++++++++++--- .../tools/dotc/interactive/Completion.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 4 +-- tests/run/i22150.check | 3 +++ tests/run/i22150.scala | 26 +++++++++++++++++++ 7 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 tests/run/i22150.check create mode 100644 tests/run/i22150.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 67e1885b511f..dca2fbeb0dea 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1744,7 +1744,7 @@ object desugar { def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] = def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] = - var selNames = pt.namedTupleElementTypes.map(_(0)) + var selNames = pt.namedTupleElementTypes(false).map(_(0)) if selNames.isEmpty && pt.classSymbol.is(CaseClass) then selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName) val nameToIdx = selNames.zipWithIndex.toMap diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 0a219fa6ddfd..e272c96c9d39 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,7 +127,7 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = (if normalize then self.normalized else self).dealias match case defn.NamedTuple(nmes, vals) => val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: @@ -135,11 +135,25 @@ class TypeUtils: case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) + case tp: TypeProxy if derived => + tp.superType.namedTupleElementTypesUpTo(bound - 1, normalize) + case tp: OrType if derived => + val lhs = tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize) + val rhs = tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize) + if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Union Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") + lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 | rhs._2)) + case tp: AndType if derived => + (tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize), tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize)) match + case (Nil, rhs) => rhs + case (lhs, Nil) => lhs + case (lhs, rhs) => + if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Intersection Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") + lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 & rhs._2)) case t => Nil - def namedTupleElementTypes(using Context): List[(TermName, Type)] = - namedTupleElementTypesUpTo(Int.MaxValue) + def namedTupleElementTypes(derived: Boolean)(using Context): List[(TermName, Type)] = + namedTupleElementTypesUpTo(Int.MaxValue, derived) def isNamedTupleType(using Context): Boolean = self match case defn.NamedTuple(_, _) => true diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index ff5716b227ca..6655998d026f 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -532,7 +532,7 @@ object Completion: def namedTupleCompletionsFromType(tpe: Type): CompletionMap = val freshCtx = ctx.fresh.setExploreTyperState() inContext(freshCtx): - tpe.namedTupleElementTypes + tpe.namedTupleElementTypes(true) .map { (name, tpe) => val symbol = newSymbol(owner = NoSymbol, name, EmptyFlags, tpe) val denot = SymDenotation(symbol, NoSymbol, name, EmptyFlags, tpe) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index d460cec75115..6ced4f075abc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,7 +248,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, normalize = false) + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) // TODO: should the printer use derived or not? catch case ex: TypeError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 76b853c4aabd..6072c496e1bd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -799,7 +799,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // Otherwise, try to expand a named tuple selection def tryNamedTupleSelection() = - val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes + val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes(true) val nameIdx = namedTupleElems.indexWhere(_._1 == selName) if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then typed( @@ -875,7 +875,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer then val pre = if !TypeOps.isLegalPrefix(qual.tpe) then SkolemType(qual.tpe) else qual.tpe val fieldsType = pre.select(tpnme.Fields).widenDealias.simplified - val fields = fieldsType.namedTupleElementTypes + val fields = fieldsType.namedTupleElementTypes(true) typr.println(i"try dyn select $qual, $selName, $fields") fields.find(_._1 == selName) match case Some((_, fieldType)) => diff --git a/tests/run/i22150.check b/tests/run/i22150.check new file mode 100644 index 000000000000..4539bbf2d22d --- /dev/null +++ b/tests/run/i22150.check @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala new file mode 100644 index 000000000000..6a01e5da85ba --- /dev/null +++ b/tests/run/i22150.scala @@ -0,0 +1,26 @@ +//> using options -experimental -language:experimental.namedTuples +import language.experimental.namedTuples + +val directionsNT = IArray( + (dx = 0, dy = 1), // up + (dx = 1, dy = 0), // right + (dx = 0, dy = -1), // down + (dx = -1, dy = 0), // left +) +val IArray(UpNT @ _, _, _, _) = directionsNT + +object NT: +// def foo[T <: (x: Int, y: String)](tup: T): Int = +// tup.x + + def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = + tup.x + + def intersect[T](tup: (x: Int, y: String) & T): Int = + tup.x + + +@main def Test = + println(UpNT.dx) + println(NT.union((1, "a"))) + println(NT.intersect((2, "b"))) From 5b31eda1b2ef24d16dd195eb5c60642d59f6b0bf Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 15:16:07 -0500 Subject: [PATCH 2/6] Refactor check into NamedTuple.unapply for consistency --- .../dotty/tools/dotc/core/Definitions.scala | 23 ++++++++++--- .../src/dotty/tools/dotc/core/TypeUtils.scala | 32 ++++++------------- .../tools/dotc/interactive/Completion.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 5 +-- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- tests/run/i22150.scala | 2 +- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2890bdf306be..dd20c2db9192 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1337,10 +1337,25 @@ class Definitions { object NamedTuple: def apply(nmes: Type, vals: Type)(using Context): Type = AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil) - def unapply(t: Type)(using Context): Option[(Type, Type)] = t match - case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => - Some((nmes, vals)) - case _ => None + def unapply(t: Type)(using Context): Option[(Type, Type)] = + t match + case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol => + Some((nmes, vals)) + case tp: TypeProxy => + val t = unapply(tp.superType); t + case tp: OrType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal | rhsVal) + case _ => None + case tp: AndType => + (unapply(tp.tp1), unapply(tp.tp2)) match + case (Some(lhsName, lhsVal), Some(rhsName, rhsVal)) if lhsName == rhsName => + Some(lhsName, lhsVal & rhsVal) + case (lhs, None) => lhs + case (None, rhs) => rhs + case _ => None + case _ => None final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index e272c96c9d39..14ccf32c7787 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -129,26 +129,21 @@ class TypeUtils: def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = (if normalize then self.normalized else self).dealias match + // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply + case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case ConstantType(Constant(str: String)) => str.toTermName + case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + names.zip(values) + case t if !derived => Nil + // default cause, used for post-typing case defn.NamedTuple(nmes, vals) => val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) - case tp: TypeProxy if derived => - tp.superType.namedTupleElementTypesUpTo(bound - 1, normalize) - case tp: OrType if derived => - val lhs = tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize) - val rhs = tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize) - if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Union Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") - lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 | rhs._2)) - case tp: AndType if derived => - (tp.tp1.namedTupleElementTypesUpTo(bound - 1, normalize), tp.tp2.namedTupleElementTypesUpTo(bound - 1, normalize)) match - case (Nil, rhs) => rhs - case (lhs, Nil) => lhs - case (lhs, rhs) => - if (lhs.map(_._1) != rhs.map(_._1)) throw TypeError(em"Malformed Intersection Type: Named Tuple elements must be the same, but $lhs and $rhs were found.") - lhs.zip(rhs).map((lhs, rhs) => (lhs._1, lhs._2 & rhs._2)) case t => Nil @@ -159,15 +154,6 @@ 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/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 6655998d026f..333af6a26b3b 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.derivesFromNamedTuple then + if qualTpe.isNamedTupleType 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/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 6ced4f075abc..27ab73f0fe4d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,8 +248,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, false, normalize = false) // TODO: should the printer use derived or not? - catch case ex: TypeError => Nil + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) + catch + case ex: TypeError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 193cc443b4ae..9d273ebca866 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.derivesFromNamedTuple && to.derivesFrom(defn.TupleClass) + || from.widen.isNamedTupleType && 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 6072c496e1bd..9b7e4fe36668 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4663,7 +4663,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.derivesFromNamedTuple && pt.derivesFrom(defn.TupleClass) then + if tree.tpe.isNamedTupleType && 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/run/i22150.scala b/tests/run/i22150.scala index 6a01e5da85ba..7c89b1de57c5 100644 --- a/tests/run/i22150.scala +++ b/tests/run/i22150.scala @@ -10,7 +10,7 @@ val directionsNT = IArray( val IArray(UpNT @ _, _, _, _) = directionsNT object NT: -// def foo[T <: (x: Int, y: String)](tup: T): Int = +// def foo[T <: (x: Int, y: String)](tup: T): Int = // TODO 3: this fails with similar error to https://github.com/scala/scala3/issues/22324 not sure if related? // tup.x def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = From be88246a292c4c54e55bfe4e44410253b6eb4e24 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 16:55:08 -0500 Subject: [PATCH 3/6] remove normalize and rely on stackoverflow --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 17 ++++++++--------- .../tools/dotc/printing/RefinedPrinter.scala | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index 14ccf32c7787..bcd8ac42d9cc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,22 +127,21 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = - (if normalize then self.normalized else self).dealias match - // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean)(using Context): List[(TermName, Type)] = + self.normalized.dealias match + // for desugaring, ignore derived types to avoid infinite recursion in NamedTuple.unapply case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, true).getOrElse(Nil) names.zip(values) - case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) => - val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: + case defn.NamedTuple(nmes, vals) if derived => + val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, derived).getOrElse(Nil) names.zip(values) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 27ab73f0fe4d..3d6077da24bc 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,9 +248,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, false, normalize = false) + try tp.namedTupleElementTypesUpTo(200, true) catch case ex: TypeError => Nil + case ex: StackOverflowError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match From f54f5d32b39c59d546c21937e99141ec12c19647 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 16:56:48 -0500 Subject: [PATCH 4/6] Revert "remove normalize and rely on stackoverflow" because test slowdown This reverts commit 2f03f86ba82b61e199c45eb4d4b7d04b7aa5f237. --- .../src/dotty/tools/dotc/core/TypeUtils.scala | 17 +++++++++-------- .../tools/dotc/printing/RefinedPrinter.scala | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index bcd8ac42d9cc..14ccf32c7787 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -127,21 +127,22 @@ class TypeUtils: case Some(types) => TypeOps.nestedPairs(types) case None => throw new AssertionError("not a tuple") - def namedTupleElementTypesUpTo(bound: Int, derived: Boolean)(using Context): List[(TermName, Type)] = - self.normalized.dealias match - // for desugaring, ignore derived types to avoid infinite recursion in NamedTuple.unapply + def namedTupleElementTypesUpTo(bound: Int, derived: Boolean, normalize: Boolean = true)(using Context): List[(TermName, Type)] = + (if normalize then self.normalized else self).dealias match + // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply case AppliedType(tycon, nmes :: vals :: Nil) if !derived && tycon.typeSymbol == defn.NamedTupleTypeRef.symbol => - val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, true).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) + case t if !derived => Nil // default cause, used for post-typing - case defn.NamedTuple(nmes, vals) if derived => - val names = nmes.tupleElementTypesUpTo(bound).getOrElse(Nil).map(_.dealias).map: + case defn.NamedTuple(nmes, vals) => + val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map(_.dealias).map: case ConstantType(Constant(str: String)) => str.toTermName case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") - val values = vals.tupleElementTypesUpTo(bound, derived).getOrElse(Nil) + val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) case t => Nil diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3d6077da24bc..27ab73f0fe4d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -248,10 +248,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val namedElems = - try tp.namedTupleElementTypesUpTo(200, true) + try tp.namedTupleElementTypesUpTo(200, false, normalize = false) catch case ex: TypeError => Nil - case ex: StackOverflowError => Nil if namedElems.nonEmpty then toTextNamedTuple(namedElems) else tp.tupleElementTypesUpTo(200, normalize = false) match From d42347f523c496a24a49e9dee05012e2320f6106 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Fri, 10 Jan 2025 17:10:38 -0500 Subject: [PATCH 5/6] Re-add test that will pass when #22340 is merged --- tests/run/i22150.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run/i22150.scala b/tests/run/i22150.scala index 7c89b1de57c5..80c2222a98e7 100644 --- a/tests/run/i22150.scala +++ b/tests/run/i22150.scala @@ -10,8 +10,8 @@ val directionsNT = IArray( val IArray(UpNT @ _, _, _, _) = directionsNT object NT: -// def foo[T <: (x: Int, y: String)](tup: T): Int = // TODO 3: this fails with similar error to https://github.com/scala/scala3/issues/22324 not sure if related? -// tup.x + def foo[T <: (x: Int, y: String)](tup: T): Int = + tup.x def union[T](tup: (x: Int, y: String) | (x: Int, y: String)): Int = tup.x From 83ae00dded56624fb840942825fc763bf8c5a008 Mon Sep 17 00:00:00 2001 From: aherlihy Date: Mon, 13 Jan 2025 09:00:12 -0500 Subject: [PATCH 6/6] Update rebased --- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c2864093ff70..164df6aae5b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -110,7 +110,7 @@ object Applications { } def namedTupleOrProductTypes(tp: Type)(using Context): List[Type] = - if tp.isNamedTupleType then tp.namedTupleElementTypes.map(_(1)) + if tp.isNamedTupleType then tp.namedTupleElementTypes(true).map(_(1)) else productSelectorTypes(tp, NoSourcePosition) def productSelectorTypes(tp: Type, errorPos: SrcPos)(using Context): List[Type] = {