Skip to content

Commit b9899b7

Browse files
committed
Support for named tuples with new representation
1 parent 1f79b87 commit b9899b7

40 files changed

+1175
-179
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
@@ -234,7 +234,7 @@ object desugar {
234234

235235
private def elimContextBounds(meth: DefDef, isPrimaryConstructor: Boolean)(using Context): DefDef =
236236
val DefDef(_, paramss, tpt, rhs) = meth
237-
val evidenceParamBuf = ListBuffer[ValDef]()
237+
val evidenceParamBuf = mutable.ListBuffer[ValDef]()
238238

239239
var seenContextBounds: Int = 0
240240
def desugarContextBounds(rhs: Tree): Tree = rhs match
@@ -1441,22 +1441,101 @@ object desugar {
14411441
AppliedTypeTree(
14421442
TypeTree(defn.throwsAlias.typeRef).withSpan(op.span), tpt :: excepts :: Nil)
14431443

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

14611540
private def isTopLevelDef(stat: Tree)(using Context): Boolean = stat match
14621541
case _: ValDef | _: PatDef | _: DefDef | _: Export | _: ExtMethods => true
@@ -1990,7 +2069,7 @@ object desugar {
19902069
* without duplicates
19912070
*/
19922071
private def getVariables(tree: Tree, shouldAddGiven: Context ?=> Bind => Boolean)(using Context): List[VarInfo] = {
1993-
val buf = ListBuffer[VarInfo]()
2072+
val buf = mutable.ListBuffer[VarInfo]()
19942073
def seenName(name: Name) = buf exists (_._1.name == name)
19952074
def add(named: NameTree, t: Tree): Unit =
19962075
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
@@ -528,15 +528,15 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
528528
def makeSelfDef(name: TermName, tpt: Tree)(using Context): ValDef =
529529
ValDef(name, tpt, EmptyTree).withFlags(PrivateLocal)
530530

531-
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match {
531+
def makeTupleOrParens(ts: List[Tree])(using Context): Tree = ts match
532+
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
532533
case t :: Nil => Parens(t)
533534
case _ => Tuple(ts)
534-
}
535535

536-
def makeTuple(ts: List[Tree])(using Context): Tree = ts match {
536+
def makeTuple(ts: List[Tree])(using Context): Tree = ts match
537+
case (t: NamedArg) :: Nil => Tuple(t :: Nil)
537538
case t :: Nil => t
538539
case _ => Tuple(ts)
539-
}
540540

541541
def makeAndType(left: Tree, right: Tree)(using Context): AppliedTypeTree =
542542
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
def experimentalAutoEnableFeatures(using Context): List[TermName] =
3839
defn.languageExperimentalFeatures

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

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

952+
@tu lazy val NamedTupleModule = requiredModule("scala.NamedTuple")
953+
@tu lazy val NamedTupleTypeRef: TypeRef = NamedTupleModule.termRef.select(tpnme.NamedTuple).asInstanceOf
954+
952955
@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")
953956

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

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

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"
@@ -649,6 +650,7 @@ object StdNames {
649650
val wildcardType: N = "wildcardType"
650651
val withFilter: N = "withFilter"
651652
val withFilterIfRefutable: N = "withFilterIfRefutable$"
653+
val withNames: N = "withNames"
652654
val WorksheetWrapper: N = "WorksheetWrapper"
653655
val wrap: N = "wrap"
654656
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
/** Is this type a methodic type that takes at least one parameter? */

0 commit comments

Comments
 (0)