Skip to content

Commit f5cba46

Browse files
committed
Add IsNamedTuple compiletime op
1 parent db23c08 commit f5cba46

File tree

6 files changed

+62
-11
lines changed

6 files changed

+62
-11
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
2525
import reporting.{trace, Message, OverrideError}
26+
import scala.util.boundary
2627

2728
/** The capture checker */
2829
object CheckCaptures:
@@ -1290,7 +1291,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12901291
*/
12911292
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type =
12921293

1293-
def recur(actual: Type, expected: Type, covariant: Boolean): Type =
1294+
def recur(actual: Type, expected: Type, covariant: Boolean): Type = boundary:
12941295

12951296
/** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
12961297
* @param boxed if true we adapt to a boxed expected type
@@ -1384,7 +1385,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13841385
if boxErrors != null then boxErrors += msg
13851386
if ctx.settings.YccDebug.value then
13861387
println(i"cannot box/unbox $actual vs $expected")
1387-
return actual
1388+
boundary.break(actual)
13881389
// Disallow future addition of `cap` to `criticalSet`.
13891390
criticalSet.disallowRootCapability: () =>
13901391
report.error(msg, pos)

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,8 @@ class Definitions {
962962
def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
963963
def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq)
964964

965-
@tu lazy val NamedTupleModule = requiredModule("scala.NamedTuple")
965+
@tu lazy val NamedTupleModule: Symbol = requiredModule("scala.NamedTuple")
966+
@tu lazy val NamedTupleModuleClass: Symbol = NamedTupleModule.moduleClass
966967
@tu lazy val NamedTupleTypeRef: TypeRef = NamedTupleModule.termRef.select(tpnme.NamedTuple).asInstanceOf
967968

968969
@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")
@@ -1360,9 +1361,9 @@ class Definitions {
13601361
final def isCompiletime_S(sym: Symbol)(using Context): Boolean =
13611362
sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass
13621363

1363-
final def isNamedTuple_From(sym: Symbol)(using Context): Boolean =
1364-
sym.name == tpnme.From && sym.owner == NamedTupleModule.moduleClass
1365-
1364+
private val namedTupleTypes: Set[Name] = Set(
1365+
tpnme.From, tpnme.OptFrom, tpnme.IsNamedTuple
1366+
)
13661367
private val compiletimePackageAnyTypes: Set[Name] = Set(
13671368
tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString
13681369
)
@@ -1391,7 +1392,8 @@ class Definitions {
13911392
tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt
13921393
)
13931394
private val compiletimePackageOpTypes: Set[Name] =
1394-
Set(tpnme.S, tpnme.From)
1395+
Set(tpnme.S)
1396+
++ namedTupleTypes
13951397
++ compiletimePackageAnyTypes
13961398
++ compiletimePackageIntTypes
13971399
++ compiletimePackageLongTypes
@@ -1404,7 +1406,7 @@ class Definitions {
14041406
compiletimePackageOpTypes.contains(sym.name)
14051407
&& (
14061408
isCompiletime_S(sym)
1407-
|| isNamedTuple_From(sym)
1409+
|| sym.owner == NamedTupleModuleClass && namedTupleTypes.contains(sym.name)
14081410
|| sym.owner == CompiletimeOpsAnyModuleClass && compiletimePackageAnyTypes.contains(sym.name)
14091411
|| sym.owner == CompiletimeOpsIntModuleClass && compiletimePackageIntTypes.contains(sym.name)
14101412
|| sym.owner == CompiletimeOpsLongModuleClass && compiletimePackageLongTypes.contains(sym.name)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ object StdNames {
367367
val Flag : N = "Flag"
368368
val Fields: N = "Fields"
369369
val From: N = "From"
370+
val IsNamedTuple: N = "IsNamedTuple"
370371
val Ident: N = "Ident"
371372
val Import: N = "Import"
372373
val Literal: N = "Literal"
@@ -385,6 +386,7 @@ object StdNames {
385386
val NoPrefix: N = "NoPrefix"
386387
val NoSymbol: N = "NoSymbol"
387388
val NoType: N = "NoType"
389+
val OptFrom: N = "OptFrom"
388390
val Pair: N = "Pair"
389391
val Ref: N = "Ref"
390392
val RootPackage: N = "RootPackage"

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import parsing.Parsers.OutlineParser
2727
import dotty.tools.tasty.{TastyHeaderUnpickler, UnpickleException, UnpicklerConfig, TastyVersion}
2828
import dotty.tools.dotc.core.tasty.TastyUnpickler
2929
import dotty.tools.tasty.besteffort.BestEffortTastyHeaderUnpickler
30+
import dotty.tools.dotc.util.SourceFile
3031

3132
object SymbolLoaders {
3233
import ast.untpd.*
@@ -450,7 +451,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader {
450451
val (classRoot, moduleRoot) = rootDenots(root.asClass)
451452
if (!isBestEffortTasty || ctx.withBestEffortTasty) then
452453
val tastyBytes = tastyFile.toByteArray
453-
unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource))
454+
unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource : SourceFile))
454455
if mayLoadTreesFromTasty || isBestEffortTasty then
455456
classRoot.classSymbol.rootTreeOrProvider = unpickler
456457
moduleRoot.classSymbol.rootTreeOrProvider = unpickler

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ object TypeEval:
5454
case ConstantType(Constant(n: String)) => Some(n)
5555
case _ => None
5656

57+
/** is the type concrete? if not then the compiletime op should not reduce */
58+
def isKnown(tp: Type): Boolean = tp.dealias match
59+
// currently not a concrete known type
60+
case TypeRef(NoPrefix,_) => false
61+
// currently not a concrete known type
62+
case _: TypeParamRef => false
63+
// constant if the term is constant
64+
case t: TermRef =>
65+
if t.denot.symbol.flagsUNSAFE.is(Flags.Param) then
66+
// might be substituted later
67+
false
68+
else
69+
isKnown(t.underlying)
70+
// an operation type => recursively check all argument compositions
71+
case applied: AppliedType if defn.isCompiletimeAppliedType(applied.typeSymbol) =>
72+
applied.args.forall(isKnown)
73+
// all other types are considered known
74+
case _ => true
75+
5776
// Returns Some(true) if the type is a constant.
5877
// Returns Some(false) if the type is not a constant.
5978
// Returns None if there is not enough information to determine if the type is a constant.
@@ -113,6 +132,23 @@ object TypeEval:
113132
case arg @ defn.NamedTuple(_, _) => Some(arg)
114133
case _ => None
115134

135+
def optFieldsOf: Option[Type] =
136+
if isKnown(tp) then fieldsOf match
137+
case Some(tp) => Some(defn.SomeClass.typeRef.appliedTo(tp))
138+
case None => Some(defn.NoneModule.termRef)
139+
else
140+
None
141+
142+
def isNamedTupleType: Option[Type] =
143+
if isKnown(tp) then
144+
expectArgsNum(1)
145+
val arg = tp.args.head
146+
arg.widenDealias match
147+
case arg @ defn.NamedTuple(_, _) => Some(ConstantType(Constant(true)))
148+
case _ => Some(ConstantType(Constant(false)))
149+
else
150+
None
151+
116152
def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] =
117153
expectArgsNum(1)
118154
extractor(tp.args.head).map(a => runConstantOp(op(a)))
@@ -147,8 +183,11 @@ object TypeEval:
147183
val constantType =
148184
if defn.isCompiletime_S(sym) then
149185
constantFold1(natValue, _ + 1)
150-
else if defn.isNamedTuple_From(sym) then
151-
fieldsOf
186+
else if owner == defn.NamedTupleModuleClass then name match
187+
case tpnme.From => fieldsOf
188+
case tpnme.OptFrom => optFieldsOf
189+
case tpnme.IsNamedTuple => isNamedTupleType
190+
case _ => None
152191
else if owner == defn.CompiletimeOpsAnyModuleClass then name match
153192
case tpnme.Equals => constantFold2(constValue, _ == _)
154193
case tpnme.NotEquals => constantFold2(constValue, _ != _)

library/src/scala/NamedTuple.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ object NamedTuple:
124124
*/
125125
type From[T] <: AnyNamedTuple
126126

127+
@experimental // extra experimental for now
128+
type OptFrom[T] <: Option[AnyNamedTuple]
129+
130+
@experimental // extra experimental for now
131+
type IsNamedTuple[T] <: Boolean
132+
127133
/** The type of the empty named tuple */
128134
type Empty = NamedTuple[EmptyTuple, EmptyTuple]
129135

0 commit comments

Comments
 (0)