Skip to content

Commit e0c2669

Browse files
committed
Make named tuples an experimental feature
1 parent 8514b17 commit e0c2669

File tree

15 files changed

+101
-47
lines changed

15 files changed

+101
-47
lines changed

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/parsing/Parsers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ object Parsers {
635635
else leading :: Nil
636636

637637
def maybeNamed(op: () => Tree): () => Tree = () =>
638-
if isIdent && in.lookahead.token == EQUALS then
638+
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
639639
atSpan(in.offset):
640640
val name = ident()
641641
in.nextToken()
@@ -2024,7 +2024,7 @@ object Parsers {
20242024

20252025
if namedOK && isIdent && in.lookahead.token == EQUALS then
20262026
commaSeparated(() => namedArgType())
2027-
else if tupleOK && isIdent && in.lookahead.isColon then
2027+
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
20282028
commaSeparated(() => namedElem())
20292029
else
20302030
commaSeparated(() => argType())

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
710710
val nameIdx = tupleElems.indexWhere:
711711
case defn.NamedTupleElem(name, _) => name == selName
712712
case _ => false
713-
if nameIdx >= 0 then
713+
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
714714
typed(
715715
untpd.Apply(
716716
untpd.Select(untpd.TypedSplice(qual), nme.apply),

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ i17149.scala
6161
tuple-fold.scala
6262
mt-redux-norm.perspective.scala
6363
i18211.scala
64+
named-tuples1.scala
6465

6566
# Opaque type
6667
i5720.scala

library/src/scala/Tuple.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,10 @@ object Tuple {
241241
*/
242242
type Union[T <: Tuple] = Fold[T, Nothing, [x, y] =>> x | y]
243243

244+
@experimental
244245
opaque type NamedValue[name <: String & Singleton, A] >: A = A
245246

247+
@experimental
246248
object NamedValue:
247249
def apply[S <: String & Singleton, A](name: S, x: A): NamedValue[name.type, A] = x
248250
def extract[S <: String & Singleton]: NameExtractor[S] = NameExtractor[S]()

library/src/scala/runtime/stdLibPatches/language.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ object language:
9191
@compileTimeOnly("`into` can only be used at compile time in import statements")
9292
object into
9393

94+
/** Experimental support for named tuples.
95+
*
96+
* @see [[https://dotty.epfl.ch/docs/reference/experimental/into-modifier]]
97+
*/
98+
@compileTimeOnly("`namedTuples` can only be used at compile time in import statements")
99+
object namedTuples
100+
94101
/** Was needed to add support for relaxed imports of extension methods.
95102
* The language import is no longer needed as this is now a standard feature since SIP was accepted.
96103
* @see [[http://dotty.epfl.ch/docs/reference/contextual/extension-methods]]
File renamed without changes.

tests/neg/named-tuples.check

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:12:25 ------------------------------------------------------
2-
12 | val y: (String, Int) = person // error
1+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:13:25 ------------------------------------------------------
2+
13 | val y: (String, Int) = person // error
33
| ^^^^^^
4-
| Found: (person : (name: String, age: Int))
4+
| Found: (Test.person : (name: String, age: Int))
55
| Required: (String, Int)
66
|
77
| longer explanation available when compiling with `-explain`
8-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:13:20 ------------------------------------------------------
9-
13 | val _: NameOnly = person // error
8+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:14:20 ------------------------------------------------------
9+
14 | val _: NameOnly = person // error
1010
| ^^^^^^
11-
| Found: (person : (name: String, age: Int))
12-
| Required: NameOnly
11+
| Found: (Test.person : (name: String, age: Int))
12+
| Required: Test.NameOnly
1313
|
1414
| longer explanation available when compiling with `-explain`
15-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:14:18 ------------------------------------------------------
16-
14 | val _: Person = nameOnly // error
15+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:15:18 ------------------------------------------------------
16+
15 | val _: Person = nameOnly // error
1717
| ^^^^^^^^
18-
| Found: (nameOnly : (name: String))
19-
| Required: Person
18+
| Found: (Test.nameOnly : (name: String))
19+
| Required: Test.Person
2020
|
2121
| longer explanation available when compiling with `-explain`
22-
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:16:36 ------------------------------------------------------
23-
16 | val _: (age: Int, name: String) = person // error
22+
-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:17:36 ------------------------------------------------------
23+
17 | val _: (age: Int, name: String) = person // error
2424
| ^^^^^^
25-
| Found: (person : (name: String, age: Int))
25+
| Found: (Test.person : (name: String, age: Int))
2626
| Required: (age: Int, name: String)
2727
|
2828
| longer explanation available when compiling with `-explain`

tests/neg/named-tuples.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
import annotation.experimental
2+
import language.experimental.namedTuples
13

2-
type Person = (name: String, age: Int)
3-
val person = (name = "Bob", age = 33): (name: String, age: Int)
4+
@experimental object Test:
45

5-
type NameOnly = (name: String)
6+
type Person = (name: String, age: Int)
7+
val person = (name = "Bob", age = 33): (name: String, age: Int)
68

7-
val nameOnly = (name = "Louis")
9+
type NameOnly = (name: String)
810

11+
val nameOnly = (name = "Louis")
912

10-
11-
def Test =
1213
val y: (String, Int) = person // error
1314
val _: NameOnly = person // error
1415
val _: Person = nameOnly // error

tests/pos/named-tuples.check

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/pos/named-tuples.scala

Lines changed: 0 additions & 18 deletions
This file was deleted.

tests/pos/named-tuples1.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import annotation.experimental
2+
import language.experimental.namedTuples
3+
import NamedTuple.dropNames
4+
5+
@main def Test =
6+
val bob = (name = "Bob", age = 33): (name: String, age: Int)
7+
val persons = List(
8+
bob,
9+
(name = "Bill", age = 40),
10+
(name = "Lucy", age = 45)
11+
)
12+
val ages = persons.map(_.age)
13+
// pickling failure: matchtype is reduced after pickling, unreduced before.
14+
assert(ages.sum == 118)

tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,15 @@ val experimentalDefinitionInLibrary = Set(
9797
"scala.Tuple$.Helpers$",
9898
"scala.Tuple$.Helpers$.ReverseImpl",
9999
"scala.Tuple$.Reverse",
100-
"scala.runtime.Tuples$.reverse"
100+
"scala.runtime.Tuples$.reverse",
101+
102+
// New feature: named tuples
103+
"scala.NamedTuple",
104+
"scala.NamedTuple$",
105+
"scala.Tuple$.NamedValue",
106+
"scala.Tuple$.NamedValue$",
107+
108+
101109
)
102110

103111

tests/run/named-tuples.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ Bob
44
(Bob,33,Lausanne,1003)
55
33
66
no match
7+
Bob is younger than Bill
8+
Bob is younger than Lucy
9+
Bill is younger than Lucy

tests/run/named-tuples.scala

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import annotation.experimental
2+
import language.experimental.namedTuples
13
import NamedTuple.dropNames
24

35
type Person = (name: String, age: Int)
@@ -40,4 +42,42 @@ val _: CombinedInfo = bob ++ addr
4042
assert(ageOf((name = "anon", age = 22)) == 22)
4143
assert(ageOf(("anon", 11)) == 11)
4244

45+
val persons = List(
46+
bob,
47+
(name = "Bill", age = 40),
48+
(name = "Lucy", age = 45)
49+
)
50+
for
51+
p <- persons
52+
q <- persons
53+
if p.age < q.age
54+
do
55+
println(s"${p.name} is younger than ${q.name}")
56+
57+
//persons.select(_.age, _.name)
58+
//persons.join(addresses).withCommon(_.name)
59+
60+
def minMax(elems: Int*): (min: Int, max: Int) =
61+
var min = elems(0)
62+
var max = elems(0)
63+
for elem <- elems do
64+
if elem < min then min = elem
65+
if elem > max then max = elem
66+
(min = min, max = max)
67+
68+
val mm = minMax(1, 3, 400, -3, 10)
69+
assert(mm.min == -3)
70+
assert(mm.max == 400)
71+
72+
val name1 = bob(0).value
73+
val age1 = bob(1).value
74+
75+
// should the .value above be inferred or maybe tuple indexing should strip names?
76+
// But then we could not do this:
77+
78+
def swap[A, B](x: (A, B)): (B, A) = (x(1), x(0))
79+
80+
val bobS = swap(bob)
81+
val _: (age: Int, name: String) = bobS
82+
4383

0 commit comments

Comments
 (0)