Skip to content

Commit 49d39d9

Browse files
authored
Make named tuples a standard feature (#21680)
- Deprecate experimental language import - Make named tuple features conditional on -source >= 3.6 instead - Make the NamedTuple object non-experimental. - Move NamedTuple it to src-bootstrapped since it relies on clause interleaving which is only standard in 3.6 as well. - Drop the experimental.namedTuple import from tests
2 parents a672e05 + 6828fe7 commit 49d39d9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+164
-187
lines changed

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

-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ object Feature:
3434
val pureFunctions = experimental("pureFunctions")
3535
val captureChecking = experimental("captureChecking")
3636
val into = experimental("into")
37-
val namedTuples = experimental("namedTuples")
3837
val modularity = experimental("modularity")
3938
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
4039
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
@@ -66,7 +65,6 @@ object Feature:
6665
(pureFunctions, "Enable pure functions for capture checking"),
6766
(captureChecking, "Enable experimental capture checking"),
6867
(into, "Allow into modifier on parameter types"),
69-
(namedTuples, "Allow named tuples"),
7068
(modularity, "Enable experimental modularity features"),
7169
(betterMatchTypeExtractors, "Enable better match type extractors"),
7270
(betterFors, "Enable improvements in `for` comprehensions")

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ object ScalaSettingsProperties:
2525
ScalaRelease.values.toList.map(_.show)
2626

2727
def supportedSourceVersions: List[String] =
28-
SourceVersion.values.toList.map(_.toString)
28+
(SourceVersion.values.toList.diff(SourceVersion.illegalSourceVersionNames)).toList.map(_.toString)
2929

3030
def supportedLanguageFeatures: List[ChoiceWithHelp[String]] =
3131
Feature.values.map((n, d) => ChoiceWithHelp(n.toString, d))

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

+6
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,12 @@ class Inliner(val call: tpd.Tree)(using Context):
957957
case None => tree
958958
case _ =>
959959
tree
960+
961+
/** For inlining only: Given `(x: T)` with expected type `x.type`, replace the tree with `x`.
962+
*/
963+
override def healAdapt(tree: Tree, pt: Type)(using Context): Tree = (tree, pt) match
964+
case (Typed(tree1, _), pt: SingletonType) if tree1.tpe <:< pt => tree1
965+
case _ => tree
960966
end InlineTyper
961967

962968
/** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings.

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ object Parsers {
651651
else leading :: Nil
652652

653653
def maybeNamed(op: () => Tree): () => Tree = () =>
654-
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
654+
if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then
655655
atSpan(in.offset):
656656
val name = ident()
657657
in.nextToken()
@@ -2137,7 +2137,7 @@ object Parsers {
21372137

21382138
if namedOK && isIdent && in.lookahead.token == EQUALS then
21392139
commaSeparated(() => namedArgType())
2140-
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
2140+
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then
21412141
commaSeparated(() => namedElem())
21422142
else
21432143
commaSeparated(() => argType())

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

+11-2
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
789789
def tryNamedTupleSelection() =
790790
val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes
791791
val nameIdx = namedTupleElems.indexWhere(_._1 == selName)
792-
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
792+
if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then
793793
typed(
794794
untpd.Apply(
795795
untpd.Select(untpd.TypedSplice(qual), nme.apply),
@@ -4601,7 +4601,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
46014601

46024602
def recover(failure: SearchFailureType) =
46034603
if canDefineFurther(wtp) || canDefineFurther(pt) then readapt(tree)
4604-
else err.typeMismatch(tree, pt, failure)
4604+
else
4605+
val tree1 = healAdapt(tree, pt)
4606+
if tree1 ne tree then readapt(tree1)
4607+
else err.typeMismatch(tree, pt, failure)
46054608

46064609
pt match
46074610
case _: SelectionProto =>
@@ -4750,6 +4753,12 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
47504753
}
47514754
}
47524755

4756+
/** Hook for inheriting Typers to do a last-effort adaptation. If a different
4757+
* tree is returned, we will re-adapt that one, otherwise we issue a type error afterwards.
4758+
``
4759+
*/
4760+
protected def healAdapt(tree: Tree, pt: Type)(using Context): Tree = tree
4761+
47534762
/** True if this inline typer has already issued errors */
47544763
def hasInliningErrors(using Context): Boolean = false
47554764

docs/_docs/reference/experimental/named-tuples.md renamed to docs/_docs/reference/other-new-features/named-tuples.md

+74-72
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
---
22
layout: doc-page
33
title: "Named Tuples"
4-
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html
4+
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html
55
---
66

7-
The elements of a tuple can now be named. Example:
7+
Starting in Scala 3.6, the elements of a tuple can be named. Example:
88
```scala
99
type Person = (name: String, age: Int)
1010
val Bob: Person = (name = "Bob", age = 33)
@@ -94,6 +94,24 @@ Bob match
9494
case (age = x, name = y) => ...
9595
```
9696

97+
### Pattern Matching with Named Fields in General
98+
99+
We allow named patterns not just for named tuples but also for case classes. For instance:
100+
```scala
101+
city match
102+
case c @ City(name = "London") => println(c.population)
103+
case City(name = n, zip = 1026, population = pop) => println(pop)
104+
```
105+
106+
Named constructor patterns are analogous to named tuple patterns. In both cases
107+
108+
- every name must match the name some field of the selector,
109+
- names can come in any order,
110+
- not all fields of the selector need to be matched.
111+
112+
Named patterns are compatible with extensible pattern matching simply because
113+
`unapply` results can be named tuples.
114+
97115
### Expansion
98116

99117
Named tuples are in essence just a convenient syntax for regular tuples. In the internal representation, a named tuple type is represented at compile time as a pair of two tuples. One tuple contains the names as literal constant string types, the other contains the element types. The runtime representation of a named tuples consists of just the element values, whereas the names are forgotten. This is achieved by declaring `NamedTuple`
@@ -119,6 +137,47 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe
119137
- All tuple operations also work with named tuples "out of the box".
120138
- Macro libraries can rely on this expansion.
121139

140+
### Computed Field Names
141+
142+
The `Selectable` trait now has a `Fields` type member that can be instantiated
143+
to a named tuple.
144+
145+
```scala
146+
trait Selectable:
147+
type Fields <: NamedTuple.AnyNamedTuple
148+
```
149+
150+
If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type,
151+
then the available fields and their types will be defined by that type. Assume `n: T`
152+
is an element of the `Fields` type in some class `C` that implements `Selectable`,
153+
that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`.
154+
Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`.
155+
156+
It is the task of the implementation of `selectDynamic` in `C` to ensure that its
157+
computed result conforms to the predicted type `T`
158+
159+
As an example, assume we have a query type `Q[T]` defined as follows:
160+
161+
```scala
162+
trait Q[T] extends Selectable:
163+
type Fields = NamedTuple.Map[NamedTuple.From[T], Q]
164+
def selectDynamic(fieldName: String) = ...
165+
```
166+
167+
Assume in the user domain:
168+
```scala
169+
case class City(zipCode: Int, name: String, population: Int)
170+
val city: Q[City]
171+
```
172+
Then
173+
```scala
174+
city.zipCode
175+
```
176+
has type `Q[Int]` and it expands to
177+
```scala
178+
city.selectDynamic("zipCode").asInstanceOf[Q[Int]]
179+
```
180+
122181
### The NamedTuple.From Type
123182

124183
The `NamedTuple` object contains a type definition
@@ -137,33 +196,36 @@ then `NamedTuple.From[City]` is the named tuple
137196
(zip: Int, name: String, population: Int)
138197
```
139198
The same works for enum cases expanding to case classes, abstract types with case classes as upper bound, alias types expanding to case classes
140-
and singleton types with case classes as underlying type.
199+
and singleton types with case classes as underlying type (in terms of the implementation, the `classSymbol` of a type must be a case class).
141200

142201
`From` is also defined on named tuples. If `NT` is a named tuple type, then `From[NT] = NT`.
143202

144203

204+
### Operations on Named Tuples
205+
206+
The operations on named tuples are defined in object [scala.NamedTuple](https://www.scala-lang.org/api/3.x/scala/NamedTuple$.html).
207+
145208
### Restrictions
146209

147-
The following restrictions apply to named tuple elements:
210+
The following restrictions apply to named tuples and named pattern arguments:
148211

149-
1. Either all elements of a tuple are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error:
212+
1. Either all elements of a tuple or constructor pattern are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error:
150213
```scala
151214
val illFormed1 = ("Bob", age = 33) // error
152215
```
153-
2. Each element name in a named tuple must be unique. For instance, the following is in error:
216+
2. Each element name in a named tuple or constructor pattern must be unique. For instance, the following is in error:
154217
```scala
155218
val illFormed2 = (name = "", age = 0, name = true) // error
156219
```
157-
3. Named tuples can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error:
220+
3. Named tuples and case classes can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error:
158221
```scala
159222
(tuple: Tuple) match
160223
case (age = x) => // error
161224
```
162-
4. Regular selector names `_1`, `_2`, ... are not allowed as names in named tuples.
225+
## Syntax Changes
163226

164-
### Syntax
165-
166-
The syntax of Scala is extended as follows to support named tuples:
227+
The syntax of Scala is extended as follows to support named tuples and
228+
named constructor arguments:
167229
```
168230
SimpleType ::= ...
169231
| ‘(’ NameAndType {‘,’ NameAndType} ‘)’
@@ -178,31 +240,11 @@ Patterns ::= Pattern {‘,’ Pattern}
178240
NamedPattern ::= id '=' Pattern
179241
```
180242
181-
### Named Pattern Matching
182-
183-
We allow named patterns not just for named tuples but also for case classes.
184-
For instance:
185-
```scala
186-
city match
187-
case c @ City(name = "London") => println(p.population)
188-
case City(name = n, zip = 1026, population = pop) => println(pop)
189-
```
190-
191-
Named constructor patterns are analogous to named tuple patterns. In both cases
192-
193-
- either all fields are named or none is,
194-
- every name must match the name some field of the selector,
195-
- names can come in any order,
196-
- not all fields of the selector need to be matched.
197-
198-
This revives SIP 43, with a much simpler desugaring than originally proposed.
199-
Named patterns are compatible with extensible pattern matching simply because
200-
`unapply` results can be named tuples.
201-
202243
### Source Incompatibilities
203244
204245
There are some source incompatibilities involving named tuples of length one.
205246
First, what was previously classified as an assignment could now be interpreted as a named tuple. Example:
247+
206248
```scala
207249
var age: Int
208250
(age = 1)
@@ -221,43 +263,3 @@ c f (age = 1)
221263
```
222264
will now construct a tuple as second operand instead of passing a named parameter.
223265

224-
### Computed Field Names
225-
226-
The `Selectable` trait now has a `Fields` type member that can be instantiated
227-
to a named tuple.
228-
229-
```scala
230-
trait Selectable:
231-
type Fields <: NamedTuple.AnyNamedTuple
232-
```
233-
234-
If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type,
235-
then the available fields and their types will be defined by that type. Assume `n: T`
236-
is an element of the `Fields` type in some class `C` that implements `Selectable`,
237-
that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`.
238-
Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`.
239-
240-
It is the task of the implementation of `selectDynamic` in `C` to ensure that its
241-
computed result conforms to the predicted type `T`
242-
243-
As an example, assume we have a query type `Q[T]` defined as follows:
244-
245-
```scala
246-
trait Q[T] extends Selectable:
247-
type Fields = NamedTuple.Map[NamedTuple.From[T], Q]
248-
def selectDynamic(fieldName: String) = ...
249-
```
250-
251-
Assume in the user domain:
252-
```scala
253-
case class City(zipCode: Int, name: String, population: Int)
254-
val city: Q[City]
255-
```
256-
Then
257-
```scala
258-
city.zipCode
259-
```
260-
has type `Q[Int]` and it expands to
261-
```scala
262-
city.selectDynamic("zipCode").asInstanceOf[Q[Int]]
263-
```

docs/sidebar.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ subsection:
6868
- page: reference/other-new-features/export.md
6969
- page: reference/other-new-features/opaques.md
7070
- page: reference/other-new-features/opaques-details.md
71+
- page: reference/other-new-features/named-tuples.md
7172
- page: reference/other-new-features/open-classes.md
7273
- page: reference/other-new-features/parameter-untupling.md
7374
- page: reference/other-new-features/parameter-untupling-spec.md
@@ -154,7 +155,6 @@ subsection:
154155
- page: reference/experimental/cc.md
155156
- page: reference/experimental/purefuns.md
156157
- page: reference/experimental/tupled-function.md
157-
- page: reference/experimental/named-tuples.md
158158
- page: reference/experimental/modularity.md
159159
- page: reference/experimental/typeclasses.md
160160
- page: reference/experimental/runtimeChecked.md

library/src/scala/NamedTuple.scala renamed to library/src-bootstrapped/scala/NamedTuple.scala

-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package scala
2-
import scala.language.experimental.clauseInterleaving
3-
import annotation.experimental
42
import compiletime.ops.boolean.*
53

6-
@experimental
74
object NamedTuple:
85

96
/** The type to which named tuples get mapped to. For instance,
@@ -133,7 +130,6 @@ object NamedTuple:
133130
end NamedTuple
134131

135132
/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
136-
@experimental
137133
object NamedTupleDecomposition:
138134
import NamedTuple.*
139135
extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V])

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

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ object language:
9797
* @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]]
9898
*/
9999
@compileTimeOnly("`namedTuples` can only be used at compile time in import statements")
100+
@deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.6")
100101
object namedTuples
101102

102103
/** Experimental support for new features for better modularity, including

presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala

+3-5
Original file line numberDiff line numberDiff line change
@@ -1988,8 +1988,7 @@ class CompletionSuite extends BaseCompletionSuite:
19881988

19891989
@Test def `namedTuple completions` =
19901990
check(
1991-
"""|import scala.language.experimental.namedTuples
1992-
|import scala.NamedTuple.*
1991+
"""|import scala.NamedTuple.*
19931992
|
19941993
|val person = (name = "Jamie", city = "Lausanne")
19951994
|
@@ -2000,8 +1999,7 @@ class CompletionSuite extends BaseCompletionSuite:
20001999

20012000
@Test def `Selectable with namedTuple Fields member` =
20022001
check(
2003-
"""|import scala.language.experimental.namedTuples
2004-
|import scala.NamedTuple.*
2002+
"""|import scala.NamedTuple.*
20052003
|
20062004
|class NamedTupleSelectable extends Selectable {
20072005
| type Fields <: AnyNamedTuple
@@ -2091,7 +2089,7 @@ class CompletionSuite extends BaseCompletionSuite:
20912089
|""".stripMargin
20922090
)
20932091

2094-
@Test def `conflict-3` =
2092+
@Test def `conflict-3` =
20952093
check(
20962094
"""|package a
20972095
|object A {

tests/neg/i20517.check

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------
2-
10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
3-
| ^^^^^^^^^^^
4-
| Found: (elem : String)
5-
| Required: NamedTuple.From[(foo : Foo[Any])]
6-
|
7-
| longer explanation available when compiling with `-explain`
1+
-- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 -------------------------------------------------------------
2+
9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error
3+
| ^^^^^^^^^^^
4+
| Found: (elem : String)
5+
| Required: NamedTuple.From[(foo : Foo[Any])]
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg/i20517.scala

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import scala.language.experimental.namedTuples
21
import NamedTuple.From
32

43
case class Foo[+T](elem: T)

tests/neg/named-tuple-selectable.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import scala.language.experimental.namedTuples
2-
31
class FromFields extends Selectable:
42
type Fields = (i: Int)
53
def selectDynamic(key: String) =

0 commit comments

Comments
 (0)