Skip to content

Commit

Permalink
Drop unnamed tuple <: named tuple, but allow literal tuple to conform
Browse files Browse the repository at this point in the history
We can drop the unnamed tuple <: named tuple relationship, but still
allow tuple syntax to conform to a named tuple prototype, with a small
change in Desugar#tuple.
  • Loading branch information
dwijnand committed Jan 26, 2024
1 parent 800aa0e commit c367a50
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 12 deletions.
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1502,8 +1502,11 @@ object desugar {
tree1.withSpan(tree.span)
else
cpy.Tuple(tree)(elemValues)
val names = elems.collect:
var names = elems.collect:
case NamedArg(name, arg) => name
if names.isEmpty then
typer.Inferencing.isFullyDefined(pt, typer.ForceDegree.failBottom)
names = pt.namedTupleNames
if names.isEmpty || ctx.mode.is(Mode.Pattern) then
tup
else
Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ class TypeUtils {
def namedTupleElementTypes(using Context): List[(TermName, Type)] =
namedTupleElementTypesUpTo(Int.MaxValue)

def namedTupleNames(using Context): List[Name] =
self.normalized.dealias match
case defn.NamedTuple(nmes, _) =>
nmes.tupleElementTypes.getOrElse(Nil).map:
case ConstantType(Constants.Constant(str: String)) => str.toTermName
case _ => Nil

def isNamedTupleType(using Context): Boolean = self match
case defn.NamedTuple(_, _) => true
case _ => false
Expand Down
2 changes: 1 addition & 1 deletion library/src/scala/NamedTuple.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import compiletime.ops.boolean.*
object NamedTuple:

opaque type AnyNamedTuple = Any
opaque type NamedTuple[N <: Tuple, +V <: Tuple] >: V <: AnyNamedTuple = V
opaque type NamedTuple[N <: Tuple, +V <: Tuple] <: AnyNamedTuple = V

def apply[N <: Tuple, V <: Tuple](x: V): NamedTuple[N, V] = x

Expand Down
30 changes: 20 additions & 10 deletions tests/neg/named-tuples.check
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,24 @@
28 | case (name = n, age = a) => () // error // error
| ^^^^^^^
| No element named `age` is defined in selector type (String, Int)
-- [E172] Type Error: tests/neg/named-tuples.scala:30:27 ---------------------------------------------------------------
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:30:21 ------------------------------------------------------
30 | val pp = person ++ (1, 2) // error
| ^
| Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean).
-- [E172] Type Error: tests/neg/named-tuples.scala:33:18 ---------------------------------------------------------------
| ^^^^^^
| Found: (Int, Int)
| Required: NamedTuple.NamedTuple[N2, Tuple]
|
| where: N2 is a type variable with constraint <: Tuple
|
| longer explanation available when compiling with `-explain`
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:33:12 ------------------------------------------------------
33 | person ++ (1, 2) match // error
| ^
| Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean).
| ^^^^^^
| Found: (Int, Int)
| Required: NamedTuple.NamedTuple[N2, Tuple]
|
| where: N2 is a type variable with constraint <: Tuple
|
| longer explanation available when compiling with `-explain`
-- Error: tests/neg/named-tuples.scala:36:17 ---------------------------------------------------------------------------
36 | val bad = ("", age = 10) // error
| ^^^^^^^^
Expand Down Expand Up @@ -103,8 +113,8 @@
-- Warning: tests/neg/named-tuples.scala:25:29 -------------------------------------------------------------------------
25 | val (name = x, agee = y) = person // error
| ^^^^^^
|pattern's type (String, Int) is more specialized than the right hand side expression's type (name : String, age : Int)
| pattern's type (String, Int) does not match the right hand side expression's type (name : String, age : Int)
|
|If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
|which may result in a MatchError at runtime.
|This patch can be rewritten automatically under -rewrite -source 3.2-migration.
| If the narrowing is intentional, this can be communicated by adding `: @unchecked` after the expression,
| which may result in a MatchError at runtime.
| This patch can be rewritten automatically under -rewrite -source 3.2-migration.

0 comments on commit c367a50

Please sign in to comment.