Skip to content

Commit cb9ba23

Browse files
committed
Add FieldsOf type
1 parent 5ca28dd commit cb9ba23

File tree

8 files changed

+123
-7
lines changed

8 files changed

+123
-7
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,9 @@ class Definitions {
13201320
final def isCompiletime_S(sym: Symbol)(using Context): Boolean =
13211321
sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass
13221322

1323+
final def isNamedTuple_FieldsOf(sym: Symbol)(using Context): Boolean =
1324+
sym.name == tpnme.FieldsOf && sym.owner == NamedTupleModule.moduleClass
1325+
13231326
private val compiletimePackageAnyTypes: Set[Name] = Set(
13241327
tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString
13251328
)
@@ -1348,7 +1351,7 @@ class Definitions {
13481351
tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt
13491352
)
13501353
private val compiletimePackageOpTypes: Set[Name] =
1351-
Set(tpnme.S)
1354+
Set(tpnme.S, tpnme.FieldsOf)
13521355
++ compiletimePackageAnyTypes
13531356
++ compiletimePackageIntTypes
13541357
++ compiletimePackageLongTypes
@@ -1361,6 +1364,7 @@ class Definitions {
13611364
compiletimePackageOpTypes.contains(sym.name)
13621365
&& (
13631366
isCompiletime_S(sym)
1367+
|| isNamedTuple_FieldsOf(sym)
13641368
|| sym.owner == CompiletimeOpsAnyModuleClass && compiletimePackageAnyTypes.contains(sym.name)
13651369
|| sym.owner == CompiletimeOpsIntModuleClass && compiletimePackageIntTypes.contains(sym.name)
13661370
|| sym.owner == CompiletimeOpsLongModuleClass && compiletimePackageLongTypes.contains(sym.name)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ object StdNames {
361361
val Eql: N = "Eql"
362362
val EnumValue: N = "EnumValue"
363363
val ExistentialTypeTree: N = "ExistentialTypeTree"
364+
val FieldsOf: N = "FieldsOf"
364365
val Flag : N = "Flag"
365366
val Ident: N = "Ident"
366367
val Import: N = "Import"

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import Types.*, Contexts.*, Symbols.*, Constants.*, Decorators.*
66
import config.Printers.typr
77
import reporting.trace
88
import StdNames.tpnme
9+
import Flags.CaseClass
10+
import TypeOps.nestedPairs
911

1012
object TypeEval:
1113

1214
def tryCompiletimeConstantFold(tp: AppliedType)(using Context): Type = tp.tycon match
1315
case tycon: TypeRef if defn.isCompiletimeAppliedType(tycon.symbol) =>
16+
1417
extension (tp: Type) def fixForEvaluation: Type =
1518
tp.normalized.dealias match
1619
// enable operations for constant singleton terms. E.g.:
@@ -94,6 +97,21 @@ object TypeEval:
9497
throw TypeError(em"${e.getMessage.nn}")
9598
ConstantType(Constant(result))
9699

100+
def fieldsOf: Option[Type] =
101+
expectArgsNum(1)
102+
val arg = tp.args.head
103+
val cls = arg.classSymbol
104+
if cls.is(CaseClass) then
105+
val fields = cls.caseAccessors
106+
val fieldLabels = fields.map: field =>
107+
ConstantType(Constant(field.name.toString))
108+
val fieldTypes = fields.map(arg.memberInfo)
109+
Some:
110+
defn.NamedTupleTypeRef.appliedTo:
111+
nestedPairs(fieldLabels) :: nestedPairs(fieldTypes) :: Nil
112+
else
113+
None
114+
97115
def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] =
98116
expectArgsNum(1)
99117
extractor(tp.args.head).map(a => runConstantOp(op(a)))
@@ -122,11 +140,14 @@ object TypeEval:
122140
yield runConstantOp(op(a, b, c))
123141

124142
trace(i"compiletime constant fold $tp", typr, show = true) {
125-
val name = tycon.symbol.name
126-
val owner = tycon.symbol.owner
143+
val sym = tycon.symbol
144+
val name = sym.name
145+
val owner = sym.owner
127146
val constantType =
128-
if defn.isCompiletime_S(tycon.symbol) then
147+
if defn.isCompiletime_S(sym) then
129148
constantFold1(natValue, _ + 1)
149+
else if defn.isNamedTuple_FieldsOf(sym) then
150+
fieldsOf
130151
else if owner == defn.CompiletimeOpsAnyModuleClass then name match
131152
case tpnme.Equals => constantFold2(constValue, _ == _)
132153
case tpnme.NotEquals => constantFold2(constValue, _ != _)

docs/_docs/reference/experimental/named-tuples.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe
100100
- All tuple operations also work with named tuples "out of the box".
101101
- Macro libraries can rely on this expansion.
102102

103+
### The FieldsOf Type
104+
105+
The `NamedTuple` object contains a type definition
106+
```scala
107+
type FieldsOf[T] <: AnyNamedTuple
108+
```
109+
`FieldsOf` is treated specially by the compiler. When `FieldsOf` is applied to
110+
an argument type that is an instance of a case class, the type expands to the named
111+
tuple consisting of all the fields of that case class. Here, fields means: elements of the first parameter section. For instance, assuming
112+
```scala
113+
case class City(zip: Int, name: String, population: Int)
114+
```
115+
then `FieldsOf[City]` is the named tuple
116+
```scala
117+
(zip: Int, name: String, population: Int)
118+
```
119+
The same works for enum cases expanding to case classes.
120+
103121
### Restrictions
104122

105123
The following restrictions apply to named tuple elements:
@@ -130,7 +148,29 @@ SimpleExpr ::= ...
130148
| '(' NamedExprInParens {‘,’ NamedExprInParens} ')'
131149
NamedExprInParens ::= id '=' ExprInParens
132150

133-
SimplePattern ::= ...
134-
| '(' NamedPattern {‘,’ NamedPattern} ')'
151+
Patterns ::= Pattern {‘,’ Pattern}
152+
| NamedPattern {‘,’ NamedPattern}
135153
NamedPattern ::= id '=' Pattern
136154
```
155+
156+
### Named Pattern Matching
157+
158+
We allow named patterns not just for named tuples but also for case classes.
159+
For instance:
160+
```scala
161+
city match
162+
case c @ City(name = "London") => println(p.population)
163+
case City(name = n, zip = 1026, population = pop) => println(pop)
164+
```
165+
166+
Named constructor patterns are analogous to named tuple patterns. In both cases
167+
168+
- either all fields are named or none is,
169+
- every name must match the name some field of the selector,
170+
- names can come in any order,
171+
- not all fields of the selector need to be matched.
172+
173+
This revives SIP 43, with a much simpler desugaring than originally proposed.
174+
Named patterns are compatible with extensible pattern matching simply because
175+
`unapply` results can be named tuples.
176+

library/src/scala/NamedTuple.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,12 @@ object NamedTuple:
175175
case true =>
176176
NamedTuple[Names[X], Tuple.Zip[DropNames[X], DropNames[Y]]]
177177

178+
type FieldsOf[T] <: AnyNamedTuple
179+
178180
end NamedTuple
179181

180-
@experimental
181182
/** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */
183+
@experimental
182184
object NamedTupleDecomposition:
183185
import NamedTuple.*
184186

tests/neg/fieldsOf.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import NamedTuple.FieldsOf
2+
3+
case class Person(name: String, age: Int)
4+
class Anon(name: String, age: Int)
5+
def foo[T](): FieldsOf[T] = ???
6+
7+
def test =
8+
var x: FieldsOf[Person] = ???
9+
x = foo[Person]() // ok
10+
x = foo[Anon]() // error
11+
x = foo() // error
12+
13+

tests/pos/fieldsOf.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import NamedTuple.FieldsOf
2+
3+
case class Person(name: String, age: Int)
4+
5+
type PF = FieldsOf[Person]
6+
7+
def foo[T]: FieldsOf[T] = ???
8+
9+
class Anon(name: String, age: Int)
10+
11+
def test =
12+
var x: FieldsOf[Person] = ???
13+
val y: (name: String, age: Int) = x
14+
x = y
15+
x = foo[Person]
16+
//x = foo[Anon] // error
17+
18+

tests/run/fieldsOf.check

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- [E007] Type Mismatch Error: ../neg/fieldsOf.scala:10:15 ---------------------
2+
10 | x = foo[Anon]() // error
3+
 | ^^^^^^^^^^^
4+
 | Found: NamedTuple.FieldsOf[Anon]
5+
 | Required: (name : String, age : Int)
6+
 |
7+
 | longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: ../neg/fieldsOf.scala:11:9 ----------------------
9+
11 | x = foo() // error
10+
 | ^^^^^
11+
 | Found: NamedTuple.FieldsOf[T]
12+
 | Required: (name : String, age : Int)
13+
 |
14+
 | where: T is a type variable
15+
 |
16+
 | longer explanation available when compiling with `-explain`
17+
2 errors found

0 commit comments

Comments
 (0)