Skip to content

Commit 10830da

Browse files
committed
Support for named tuples with new representation
1 parent d89ce43 commit 10830da

40 files changed

+1190
-209
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 93 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import Decorators.*
99
import Annotations.Annotation
1010
import NameKinds.{UniqueName, ContextBoundParamName, ContextFunctionParamName, DefaultGetterName, WildcardParamName}
1111
import typer.{Namer, Checking}
12-
import util.{Property, SourceFile, SourcePosition, Chars}
12+
import util.{Property, SourceFile, SourcePosition, SrcPos, Chars}
1313
import config.Feature.{sourceVersion, migrateTo3, enabled}
1414
import config.SourceVersion.*
15-
import collection.mutable.ListBuffer
15+
import collection.mutable
1616
import reporting.*
1717
import annotation.constructorOnly
1818
import printing.Formatting.hl
@@ -248,7 +248,7 @@ object desugar {
248248

249249
private def elimContextBounds(meth: DefDef, isPrimaryConstructor: Boolean)(using Context): DefDef =
250250
val DefDef(_, paramss, tpt, rhs) = meth
251-
val evidenceParamBuf = ListBuffer[ValDef]()
251+
val evidenceParamBuf = mutable.ListBuffer[ValDef]()
252252

253253
var seenContextBounds: Int = 0
254254
def desugarContextBounds(rhs: Tree): Tree = rhs match
@@ -1454,22 +1454,101 @@ object desugar {
14541454
AppliedTypeTree(
14551455
TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil)
14561456

1457+
private def checkWellFormedTupleElems(elems: List[Tree])(using Context): List[Tree] =
1458+
val seen = mutable.Set[Name]()
1459+
for case arg @ NamedArg(name, _) <- elems do
1460+
if seen.contains(name) then
1461+
report.error(em"Duplicate tuple element name", arg.srcPos)
1462+
seen += name
1463+
if name.startsWith("_") && name.toString.tail.toIntOption.isDefined then
1464+
report.error(
1465+
em"$name cannot be used as the name of a tuple element because it is a regular tuple selector",
1466+
arg.srcPos)
1467+
1468+
elems match
1469+
case elem :: elems1 =>
1470+
val mismatchOpt =
1471+
if elem.isInstanceOf[NamedArg]
1472+
then elems1.find(!_.isInstanceOf[NamedArg])
1473+
else elems1.find(_.isInstanceOf[NamedArg])
1474+
mismatchOpt match
1475+
case Some(misMatch) =>
1476+
report.error(em"Illegal combination of named and unnamed tuple elements", misMatch.srcPos)
1477+
elems.mapConserve(dropNamedArg)
1478+
case None => elems
1479+
case _ => elems
1480+
end checkWellFormedTupleElems
1481+
14571482
/** Translate tuple expressions of arity <= 22
14581483
*
14591484
* () ==> ()
14601485
* (t) ==> t
14611486
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
14621487
*/
1463-
def smallTuple(tree: Tuple)(using Context): Tree = {
1464-
val ts = tree.trees
1465-
val arity = ts.length
1466-
assert(arity <= Definitions.MaxTupleArity)
1467-
def tupleTypeRef = defn.TupleType(arity).nn
1468-
if (arity == 0)
1469-
if (ctx.mode is Mode.Type) TypeTree(defn.UnitType) else unitLiteral
1470-
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
1471-
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
1472-
}
1488+
def tuple(tree: Tuple, pt: Type)(using Context): Tree =
1489+
var elems = checkWellFormedTupleElems(tree.trees)
1490+
if ctx.mode.is(Mode.Pattern) then elems = adaptPatternArgs(elems, pt)
1491+
val elemValues = elems.mapConserve(dropNamedArg)
1492+
val tup =
1493+
val arity = elems.length
1494+
if arity <= Definitions.MaxTupleArity then
1495+
def tupleTypeRef = defn.TupleType(arity).nn
1496+
val tree1 =
1497+
if arity == 0 then
1498+
if ctx.mode is Mode.Type then TypeTree(defn.UnitType) else unitLiteral
1499+
else if ctx.mode is Mode.Type then AppliedTypeTree(ref(tupleTypeRef), elemValues)
1500+
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), elemValues)
1501+
tree1.withSpan(tree.span)
1502+
else
1503+
cpy.Tuple(tree)(elemValues)
1504+
val names = elems.collect:
1505+
case NamedArg(name, arg) => name
1506+
if names.isEmpty || ctx.mode.is(Mode.Pattern) then
1507+
tup
1508+
else
1509+
def namesTuple = inMode(ctx.mode &~ Mode.Pattern | Mode.Type):
1510+
tuple(Tuple(
1511+
names.map: name =>
1512+
SingletonTypeTree(Literal(Constant(name.toString))).withSpan(tree.span)),
1513+
WildcardType)
1514+
if ctx.mode.is(Mode.Type) then
1515+
AppliedTypeTree(ref(defn.NamedTupleTypeRef), namesTuple :: tup :: Nil)
1516+
else
1517+
TypeApply(
1518+
Apply(Select(ref(defn.NamedTupleModule), nme.withNames), tup),
1519+
namesTuple :: Nil)
1520+
1521+
/** When desugaring a list pattern arguments `elems` adapt them and the
1522+
* expected type `pt` to each other. This means:
1523+
* - If `elems` are named pattern elements, rearrange them to match `pt`.
1524+
* This requires all names in `elems` to be also present in `pt`.
1525+
* - If `elems` are unnamed elements, and `pt` is a named tuple, drop all
1526+
* tuple element names from `pt`.
1527+
*/
1528+
def adaptPatternArgs(elems: List[Tree], pt: Type)(using Context): List[Tree] =
1529+
1530+
def reorderedNamedArgs(wildcardSpan: Span): List[untpd.Tree] =
1531+
var selNames = pt.namedTupleElementTypes.map(_(0))
1532+
if selNames.isEmpty && pt.classSymbol.is(CaseClass) then
1533+
selNames = pt.classSymbol.caseAccessors.map(_.name.asTermName)
1534+
val nameToIdx = selNames.zipWithIndex.toMap
1535+
val reordered = Array.fill[untpd.Tree](selNames.length):
1536+
untpd.Ident(nme.WILDCARD).withSpan(wildcardSpan)
1537+
for case arg @ NamedArg(name: TermName, _) <- elems do
1538+
nameToIdx.get(name) match
1539+
case Some(idx) =>
1540+
if reordered(idx).isInstanceOf[Ident] then
1541+
reordered(idx) = arg
1542+
else
1543+
report.error(em"Duplicate named pattern", arg.srcPos)
1544+
case _ =>
1545+
report.error(em"No element named `$name` is defined in selector type $pt", arg.srcPos)
1546+
reordered.toList
1547+
1548+
elems match
1549+
case (first @ NamedArg(_, _)) :: _ => reorderedNamedArgs(first.span.startPos)
1550+
case _ => elems
1551+
end adaptPatternArgs
14731552

14741553
private def isTopLevelDef(stat: Tree)(using Context): Boolean = stat match
14751554
case _: ValDef | _: PatDef | _: DefDef | _: Export | _: ExtMethods => true
@@ -1986,7 +2065,7 @@ object desugar {
19862065
* without duplicates
19872066
*/
19882067
private def getVariables(tree: Tree, shouldAddGiven: Context ?=> Bind => Boolean)(using Context): List[VarInfo] = {
1989-
val buf = ListBuffer[VarInfo]()
2068+
val buf = mutable.ListBuffer[VarInfo]()
19902069
def seenName(name: Name) = buf exists (_._1.name == name)
19912070
def add(named: NameTree, t: Tree): Unit =
19922071
if (!seenName(named.name) && named.name.isTermName) buf += ((named, t))

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
244244
def hasNamedArg(args: List[Any]): Boolean = args exists isNamedArg
245245
val isNamedArg: Any => Boolean = (arg: Any) => arg.isInstanceOf[Trees.NamedArg[?]]
246246

247+
def dropNamedArg(arg: Tree) = arg match
248+
case NamedArg(_, arg1) => arg1
249+
case arg => arg
250+
247251
/** Is this pattern node a catch-all (wildcard or variable) pattern? */
248252
def isDefaultCase(cdef: CaseDef): Boolean = cdef match {
249253
case CaseDef(pat, EmptyTree, _) => isWildcardArg(pat)

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -530,15 +530,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
530530
def makeSelfDef(name: TermName, tpt: Tree)(using Context): ValDef =
531531
ValDef(name, tpt, EmptyTree).withFlags(PrivateLocal)
532532

533-
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match {
533+
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match
534+
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
534535
case t :: Nil => Parens(t)
535536
case _ => Tuple(ts)
536-
}
537537

538-
def makeTuple(ts: List[Tree])(using Context): Tree = ts match {
538+
def makeTuple(ts: List[Tree])(using Context): Tree = ts match
539+
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
539540
case t :: Nil => t
540541
case _ => Tuple(ts)
541-
}
542542

543543
def makeAndType(left: Tree, right: Tree)(using Context): AppliedTypeTree =
544544
AppliedTypeTree(ref(defn.andType.typeRef), left :: right :: Nil)

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ object Feature:
3333
val pureFunctions = experimental("pureFunctions")
3434
val captureChecking = experimental("captureChecking")
3535
val into = experimental("into")
36+
val namedTuples = experimental("namedTuples")
3637

3738
val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)
3839

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,9 @@ class Definitions {
954954
def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
955955
def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq)
956956

957+
@tu lazy val NamedTupleModule = requiredModule("scala.NamedTuple")
958+
@tu lazy val NamedTupleTypeRef: TypeRef = NamedTupleModule.termRef.select(tpnme.NamedTuple).asInstanceOf
959+
957960
@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")
958961

959962
@tu lazy val RuntimeTuplesModule: Symbol = requiredModule("scala.runtime.Tuples")
@@ -1305,6 +1308,14 @@ class Definitions {
13051308
case ByNameFunction(_) => true
13061309
case _ => false
13071310

1311+
object NamedTuple:
1312+
def apply(nmes: Type, vals: Type)(using Context): Type =
1313+
AppliedType(NamedTupleTypeRef, nmes :: vals :: Nil)
1314+
def unapply(t: Type)(using Context): Option[(Type, Type)] = t match
1315+
case AppliedType(tycon, nmes :: vals :: Nil) if tycon.typeSymbol == NamedTupleTypeRef.symbol =>
1316+
Some((nmes, vals))
1317+
case _ => None
1318+
13081319
final def isCompiletime_S(sym: Symbol)(using Context): Boolean =
13091320
sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass
13101321

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ object StdNames {
374374
val MirroredMonoType: N = "MirroredMonoType"
375375
val MirroredType: N = "MirroredType"
376376
val Modifiers: N = "Modifiers"
377+
val NamedTuple: N = "NamedTuple"
377378
val NestedAnnotArg: N = "NestedAnnotArg"
378379
val NoFlags: N = "NoFlags"
379380
val NoPrefix: N = "NoPrefix"
@@ -647,6 +648,7 @@ object StdNames {
647648
val wildcardType: N = "wildcardType"
648649
val withFilter: N = "withFilter"
649650
val withFilterIfRefutable: N = "withFilterIfRefutable$"
651+
val withNames: N = "withNames"
650652
val WorksheetWrapper: N = "WorksheetWrapper"
651653
val wrap: N = "wrap"
652654
val writeReplace: N = "writeReplace"

compiler/src/dotty/tools/dotc/core/TypeUtils.scala

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ package core
44

55
import TypeErasure.ErasedValueType
66
import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
7-
import Names.Name
7+
import Names.{Name, TermName}
8+
import Constants.Constant
89

910
class TypeUtils {
1011
/** A decorator that provides methods on types
@@ -65,8 +66,12 @@ class TypeUtils {
6566
case tp: AppliedType if defn.isTupleNType(tp) && normalize =>
6667
Some(tp.args) // if normalize is set, use the dealiased tuple
6768
// otherwise rely on the default case below to print unaliased tuples.
69+
case tp: SkolemType =>
70+
recur(tp.underlying, bound)
6871
case tp: SingletonType =>
69-
if tp.termSymbol == defn.EmptyTupleModule then Some(Nil) else None
72+
if tp.termSymbol == defn.EmptyTupleModule then Some(Nil)
73+
else if normalize then recur(tp.widen, bound)
74+
else None
7075
case _ =>
7176
if defn.isTupleClass(tp.typeSymbol) && !normalize then Some(tp.dealias.argInfos)
7277
else None
@@ -114,6 +119,33 @@ class TypeUtils {
114119
case Some(types) => TypeOps.nestedPairs(types)
115120
case None => throw new AssertionError("not a tuple")
116121

122+
def namedTupleElementTypesUpTo(bound: Int, normalize: Boolean = true)(using Context): List[(TermName, Type)] =
123+
(if normalize then self.normalized else self).dealias match
124+
case defn.NamedTuple(nmes, vals) =>
125+
val names = nmes.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil).map:
126+
case ConstantType(Constant(str: String)) => str.toTermName
127+
val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil)
128+
names.zip(values)
129+
case t =>
130+
Nil
131+
132+
def namedTupleElementTypes(using Context): List[(TermName, Type)] =
133+
namedTupleElementTypesUpTo(Int.MaxValue)
134+
135+
def isNamedTupleType(using Context): Boolean = self match
136+
case defn.NamedTuple(_, _) => true
137+
case _ => false
138+
139+
/** Drop all named elements in tuple type */
140+
def stripNamedTuple(using Context): Type = self.normalized.dealias match
141+
case defn.NamedTuple(_, vals) =>
142+
vals
143+
case self @ AnnotatedType(tp, annot) =>
144+
val tp1 = tp.stripNamedTuple
145+
if tp1 ne tp then AnnotatedType(tp1, annot) else self
146+
case _ =>
147+
self
148+
117149
def refinedWith(name: Name, info: Type)(using Context) = RefinedType(self, name, info)
118150

119151
/** The TermRef referring to the companion of the underlying class reference

0 commit comments

Comments
 (0)