From f5cba469f40bdd5c210692ecc496b6543c9a0675 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Wed, 22 Jan 2025 07:06:51 +0100 Subject: [PATCH] Add IsNamedTuple compiletime op --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 5 ++- .../dotty/tools/dotc/core/Definitions.scala | 14 +++--- .../src/dotty/tools/dotc/core/StdNames.scala | 2 + .../dotty/tools/dotc/core/SymbolLoaders.scala | 3 +- .../src/dotty/tools/dotc/core/TypeEval.scala | 43 ++++++++++++++++++- library/src/scala/NamedTuple.scala | 6 +++ 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 830d9ad0a4d4..cfd99741036d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -23,6 +23,7 @@ import CCState.* import StdNames.nme import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} import reporting.{trace, Message, OverrideError} +import scala.util.boundary /** The capture checker */ object CheckCaptures: @@ -1290,7 +1291,7 @@ class CheckCaptures extends Recheck, SymTransformer: */ def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, covariant: Boolean, alwaysConst: Boolean, boxErrors: BoxErrors)(using Context): Type = - def recur(actual: Type, expected: Type, covariant: Boolean): Type = + def recur(actual: Type, expected: Type, covariant: Boolean): Type = boundary: /** Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation * @param boxed if true we adapt to a boxed expected type @@ -1384,7 +1385,7 @@ class CheckCaptures extends Recheck, SymTransformer: if boxErrors != null then boxErrors += msg if ctx.settings.YccDebug.value then println(i"cannot box/unbox $actual vs $expected") - return actual + boundary.break(actual) // Disallow future addition of `cap` to `criticalSet`. criticalSet.disallowRootCapability: () => report.error(msg, pos) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 27ea5771c30b..401c0afd7500 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -962,7 +962,8 @@ class Definitions { def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator") def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq) - @tu lazy val NamedTupleModule = requiredModule("scala.NamedTuple") + @tu lazy val NamedTupleModule: Symbol = requiredModule("scala.NamedTuple") + @tu lazy val NamedTupleModuleClass: Symbol = NamedTupleModule.moduleClass @tu lazy val NamedTupleTypeRef: TypeRef = NamedTupleModule.termRef.select(tpnme.NamedTuple).asInstanceOf @tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror") @@ -1360,9 +1361,9 @@ class Definitions { final def isCompiletime_S(sym: Symbol)(using Context): Boolean = sym.name == tpnme.S && sym.owner == CompiletimeOpsIntModuleClass - final def isNamedTuple_From(sym: Symbol)(using Context): Boolean = - sym.name == tpnme.From && sym.owner == NamedTupleModule.moduleClass - + private val namedTupleTypes: Set[Name] = Set( + tpnme.From, tpnme.OptFrom, tpnme.IsNamedTuple + ) private val compiletimePackageAnyTypes: Set[Name] = Set( tpnme.Equals, tpnme.NotEquals, tpnme.IsConst, tpnme.ToString ) @@ -1391,7 +1392,8 @@ class Definitions { tpnme.Plus, tpnme.Length, tpnme.Substring, tpnme.Matches, tpnme.CharAt ) private val compiletimePackageOpTypes: Set[Name] = - Set(tpnme.S, tpnme.From) + Set(tpnme.S) + ++ namedTupleTypes ++ compiletimePackageAnyTypes ++ compiletimePackageIntTypes ++ compiletimePackageLongTypes @@ -1404,7 +1406,7 @@ class Definitions { compiletimePackageOpTypes.contains(sym.name) && ( isCompiletime_S(sym) - || isNamedTuple_From(sym) + || sym.owner == NamedTupleModuleClass && namedTupleTypes.contains(sym.name) || sym.owner == CompiletimeOpsAnyModuleClass && compiletimePackageAnyTypes.contains(sym.name) || sym.owner == CompiletimeOpsIntModuleClass && compiletimePackageIntTypes.contains(sym.name) || sym.owner == CompiletimeOpsLongModuleClass && compiletimePackageLongTypes.contains(sym.name) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 56d71c7fb57e..e82d1d520ee7 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -367,6 +367,7 @@ object StdNames { val Flag : N = "Flag" val Fields: N = "Fields" val From: N = "From" + val IsNamedTuple: N = "IsNamedTuple" val Ident: N = "Ident" val Import: N = "Import" val Literal: N = "Literal" @@ -385,6 +386,7 @@ object StdNames { val NoPrefix: N = "NoPrefix" val NoSymbol: N = "NoSymbol" val NoType: N = "NoType" + val OptFrom: N = "OptFrom" val Pair: N = "Pair" val Ref: N = "Ref" val RootPackage: N = "RootPackage" diff --git a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala index 68f2e350c3e4..87cbfe073466 100644 --- a/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala +++ b/compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala @@ -27,6 +27,7 @@ import parsing.Parsers.OutlineParser import dotty.tools.tasty.{TastyHeaderUnpickler, UnpickleException, UnpicklerConfig, TastyVersion} import dotty.tools.dotc.core.tasty.TastyUnpickler import dotty.tools.tasty.besteffort.BestEffortTastyHeaderUnpickler +import dotty.tools.dotc.util.SourceFile object SymbolLoaders { import ast.untpd.* @@ -450,7 +451,7 @@ class TastyLoader(val tastyFile: AbstractFile) extends SymbolLoader { val (classRoot, moduleRoot) = rootDenots(root.asClass) if (!isBestEffortTasty || ctx.withBestEffortTasty) then val tastyBytes = tastyFile.toByteArray - unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource)) + unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule))(using ctx.withSource(util.NoSource : SourceFile)) if mayLoadTreesFromTasty || isBestEffortTasty then classRoot.classSymbol.rootTreeOrProvider = unpickler moduleRoot.classSymbol.rootTreeOrProvider = unpickler diff --git a/compiler/src/dotty/tools/dotc/core/TypeEval.scala b/compiler/src/dotty/tools/dotc/core/TypeEval.scala index 4d5496cff880..43cd283fe066 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeEval.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeEval.scala @@ -54,6 +54,25 @@ object TypeEval: case ConstantType(Constant(n: String)) => Some(n) case _ => None + /** is the type concrete? if not then the compiletime op should not reduce */ + def isKnown(tp: Type): Boolean = tp.dealias match + // currently not a concrete known type + case TypeRef(NoPrefix,_) => false + // currently not a concrete known type + case _: TypeParamRef => false + // constant if the term is constant + case t: TermRef => + if t.denot.symbol.flagsUNSAFE.is(Flags.Param) then + // might be substituted later + false + else + isKnown(t.underlying) + // an operation type => recursively check all argument compositions + case applied: AppliedType if defn.isCompiletimeAppliedType(applied.typeSymbol) => + applied.args.forall(isKnown) + // all other types are considered known + case _ => true + // Returns Some(true) if the type is a constant. // Returns Some(false) if the type is not a constant. // Returns None if there is not enough information to determine if the type is a constant. @@ -113,6 +132,23 @@ object TypeEval: case arg @ defn.NamedTuple(_, _) => Some(arg) case _ => None + def optFieldsOf: Option[Type] = + if isKnown(tp) then fieldsOf match + case Some(tp) => Some(defn.SomeClass.typeRef.appliedTo(tp)) + case None => Some(defn.NoneModule.termRef) + else + None + + def isNamedTupleType: Option[Type] = + if isKnown(tp) then + expectArgsNum(1) + val arg = tp.args.head + arg.widenDealias match + case arg @ defn.NamedTuple(_, _) => Some(ConstantType(Constant(true))) + case _ => Some(ConstantType(Constant(false))) + else + None + def constantFold1[T](extractor: Type => Option[T], op: T => Any): Option[Type] = expectArgsNum(1) extractor(tp.args.head).map(a => runConstantOp(op(a))) @@ -147,8 +183,11 @@ object TypeEval: val constantType = if defn.isCompiletime_S(sym) then constantFold1(natValue, _ + 1) - else if defn.isNamedTuple_From(sym) then - fieldsOf + else if owner == defn.NamedTupleModuleClass then name match + case tpnme.From => fieldsOf + case tpnme.OptFrom => optFieldsOf + case tpnme.IsNamedTuple => isNamedTupleType + case _ => None else if owner == defn.CompiletimeOpsAnyModuleClass then name match case tpnme.Equals => constantFold2(constValue, _ == _) case tpnme.NotEquals => constantFold2(constValue, _ != _) diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index 0d1deffce513..e8e57a279931 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -124,6 +124,12 @@ object NamedTuple: */ type From[T] <: AnyNamedTuple + @experimental // extra experimental for now + type OptFrom[T] <: Option[AnyNamedTuple] + + @experimental // extra experimental for now + type IsNamedTuple[T] <: Boolean + /** The type of the empty named tuple */ type Empty = NamedTuple[EmptyTuple, EmptyTuple]