Skip to content

Commit 1f3c652

Browse files
authored
Typeclass experiments refactored (#20061)
A refactoring of the typeclass-experiments branch according to topics. DISCLAIMER: This is the same as the previous typeclass-experiments Pre-pre SIP. It's just that the commits are now in a more logical order instead of the chronological order of the first PR. Some part of this is currently under consideration as SIP-64. Other parts might be proposed as Pre-SIPs in the future. The order of exposition described in the docs of this PR is different from the planned proposals of SIPs. I concentrate here not on how to sequence details, but instead want to present a vision of what is possible. For instance, the docs in this PR start with Self types and `is` syntax, which have turned out to be controversial and that will probably be proposed only late in the sequence of SIPs. The PR needs a minor release since it adds experimental language imports, which did not exist before. Everything covered is under experimental. Baseline Scala is not affected.
2 parents 17c8766 + 3c78ada commit 1f3c652

File tree

184 files changed

+8551
-564
lines changed

Some content is hidden

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

184 files changed

+8551
-564
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+190-97
Large diffs are not rendered by default.

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

+25
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ package ast
55
import core.*
66
import Flags.*, Trees.*, Types.*, Contexts.*
77
import Names.*, StdNames.*, NameOps.*, Symbols.*
8+
import Annotations.Annotation
9+
import NameKinds.ContextBoundParamName
810
import typer.ConstFold
911
import reporting.trace
1012

@@ -380,6 +382,29 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
380382
case _ =>
381383
tree.tpe.isInstanceOf[ThisType]
382384
}
385+
386+
/** Under x.modularity: Extractor for `annotation.internal.WitnessNames(name_1, ..., name_n)`
387+
* represented as an untyped or typed tree.
388+
*/
389+
object WitnessNamesAnnot:
390+
def apply(names: List[TermName])(using Context): untpd.Tree =
391+
untpd.TypedSplice(tpd.New(
392+
defn.WitnessNamesAnnot.typeRef,
393+
tpd.SeqLiteral(names.map(n => tpd.Literal(Constant(n.toString))), tpd.TypeTree(defn.StringType)) :: Nil
394+
))
395+
396+
def unapply(tree: Tree)(using Context): Option[List[TermName]] =
397+
unsplice(tree) match
398+
case Apply(Select(New(tpt: tpd.TypeTree), nme.CONSTRUCTOR), SeqLiteral(elems, _) :: Nil) =>
399+
tpt.tpe match
400+
case tp: TypeRef if tp.name == tpnme.WitnessNames && tp.symbol == defn.WitnessNamesAnnot =>
401+
Some:
402+
elems.map:
403+
case Literal(Constant(str: String)) =>
404+
ContextBoundParamName.unmangle(str.toTermName.asSimpleName)
405+
case _ => None
406+
case _ => None
407+
end WitnessNamesAnnot
383408
}
384409

385410
trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] =>

compiler/src/dotty/tools/dotc/ast/untpd.scala

+11
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
118118
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
119119
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
120120
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
121+
case class ContextBoundTypeTree(tycon: Tree, paramName: TypeName, ownName: TermName)(implicit @constructorOnly src: SourceFile) extends Tree
122+
// `paramName: tycon as ownName`, ownName != EmptyTermName only under x.modularity
121123
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree
122124

123125
case class ImportSelector(imported: Ident, renamed: Tree = EmptyTree, bound: Tree = EmptyTree)(implicit @constructorOnly src: SourceFile) extends Tree {
@@ -230,6 +232,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
230232

231233
case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)
232234

235+
case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)
236+
233237
/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
234238
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
235239
}
@@ -675,6 +679,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
675679
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
676680
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
677681
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
682+
def ContextBoundTypeTree(tree: Tree)(tycon: Tree, paramName: TypeName, ownName: TermName)(using Context): Tree = tree match
683+
case tree: ContextBoundTypeTree if (tycon eq tree.tycon) && paramName == tree.paramName && ownName == tree.ownName => tree
684+
case _ => finalize(tree, untpd.ContextBoundTypeTree(tycon, paramName, ownName)(tree.source))
678685
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {
679686
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
680687
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
@@ -740,6 +747,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
740747
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
741748
case ExtMethods(paramss, methods) =>
742749
cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods))
750+
case ContextBoundTypeTree(tycon, paramName, ownName) =>
751+
cpy.ContextBoundTypeTree(tree)(transform(tycon), paramName, ownName)
743752
case ImportSelector(imported, renamed, bound) =>
744753
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
745754
case Number(_, _) | TypedSplice(_) =>
@@ -795,6 +804,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
795804
this(this(this(x, pats), tpt), rhs)
796805
case ExtMethods(paramss, methods) =>
797806
this(paramss.foldLeft(x)(apply), methods)
807+
case ContextBoundTypeTree(tycon, paramName, ownName) =>
808+
this(x, tycon)
798809
case ImportSelector(imported, renamed, bound) =>
799810
this(this(this(x, imported), renamed), bound)
800811
case Number(_, _) =>

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

+8
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,12 @@ object Config {
235235
*/
236236
inline val checkLevelsOnConstraints = false
237237
inline val checkLevelsOnInstantiation = true
238+
239+
/** Under x.modularity:
240+
* If a type parameter `X` has a single context bound `X: C`, should the
241+
* witness parameter be named `X`? This would prevent the creation of a
242+
* context bound companion.
243+
*/
244+
inline val nameSingleContextBounds = false
238245
}
246+

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

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ object Feature:
3434
val captureChecking = experimental("captureChecking")
3535
val into = experimental("into")
3636
val namedTuples = experimental("namedTuples")
37+
val modularity = experimental("modularity")
3738

3839
def experimentalAutoEnableFeatures(using Context): List[TermName] =
3940
defn.languageExperimentalFeatures

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -647,9 +647,9 @@ trait ConstraintHandling {
647647
* At this point we also drop the @Repeated annotation to avoid inferring type arguments with it,
648648
* as those could leak the annotation to users (see run/inferred-repeated-result).
649649
*/
650-
def widenInferred(inst: Type, bound: Type, widenUnions: Boolean)(using Context): Type =
650+
def widenInferred(inst: Type, bound: Type, widen: Widen)(using Context): Type =
651651
def widenOr(tp: Type) =
652-
if widenUnions then
652+
if widen == Widen.Unions then
653653
val tpw = tp.widenUnion
654654
if tpw ne tp then
655655
if tpw.isTransparent() then
@@ -667,14 +667,10 @@ trait ConstraintHandling {
667667
val tpw = tp.widenSingletons(skipSoftUnions)
668668
if (tpw ne tp) && (tpw <:< bound) then tpw else tp
669669

670-
def isSingleton(tp: Type): Boolean = tp match
671-
case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi)
672-
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)
673-
674670
val wideInst =
675-
if isSingleton(bound) then inst
671+
if widen == Widen.None || bound.isSingletonBounded(frozen = true) then inst
676672
else
677-
val widenedFromSingle = widenSingle(inst, skipSoftUnions = widenUnions)
673+
val widenedFromSingle = widenSingle(inst, skipSoftUnions = widen == Widen.Unions)
678674
val widenedFromUnion = widenOr(widenedFromSingle)
679675
val widened = dropTransparentTraits(widenedFromUnion, bound)
680676
widenIrreducible(widened)
@@ -713,18 +709,18 @@ trait ConstraintHandling {
713709
* The instance type is not allowed to contain references to types nested deeper
714710
* than `maxLevel`.
715711
*/
716-
def instanceType(param: TypeParamRef, fromBelow: Boolean, widenUnions: Boolean, maxLevel: Int)(using Context): Type = {
712+
def instanceType(param: TypeParamRef, fromBelow: Boolean, widen: Widen, maxLevel: Int)(using Context): Type = {
717713
val approx = approximation(param, fromBelow, maxLevel).simplified
718714
if fromBelow then
719-
val widened = widenInferred(approx, param, widenUnions)
715+
val widened = widenInferred(approx, param, widen)
720716
// Widening can add extra constraints, in particular the widened type might
721717
// be a type variable which is now instantiated to `param`, and therefore
722718
// cannot be used as an instantiation of `param` without creating a loop.
723719
// If that happens, we run `instanceType` again to find a new instantiation.
724720
// (we do not check for non-toplevel occurrences: those should never occur
725721
// since `addOneBound` disallows recursive lower bounds).
726722
if constraint.occursAtToplevel(param, widened) then
727-
instanceType(param, fromBelow, widenUnions, maxLevel)
723+
instanceType(param, fromBelow, widen, maxLevel)
728724
else
729725
widened
730726
else

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

+8-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Symbols.*
1212
import Scopes.*
1313
import Uniques.*
1414
import ast.Trees.*
15+
import Flags.ParamAccessor
1516
import ast.untpd
1617
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance}
1718
import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
@@ -399,7 +400,8 @@ object Contexts {
399400
*
400401
* - as owner: The primary constructor of the class
401402
* - as outer context: The context enclosing the class context
402-
* - as scope: The parameter accessors in the class context
403+
* - as scope: type parameters, the parameter accessors, and
404+
* the context bound companions in the class context,
403405
*
404406
* The reasons for this peculiar choice of attributes are as follows:
405407
*
@@ -413,10 +415,11 @@ object Contexts {
413415
* context see the constructor parameters instead, but then we'd need a final substitution step
414416
* from constructor parameters to class parameter accessors.
415417
*/
416-
def superCallContext: Context = {
417-
val locals = newScopeWith(owner.typeParams ++ owner.asClass.paramAccessors*)
418-
superOrThisCallContext(owner.primaryConstructor, locals)
419-
}
418+
def superCallContext: Context =
419+
val locals = owner.typeParams
420+
++ owner.asClass.unforcedDecls.filter: sym =>
421+
sym.is(ParamAccessor) || sym.isContextBoundCompanion
422+
superOrThisCallContext(owner.primaryConstructor, newScopeWith(locals*))
420423

421424
/** The context for the arguments of a this(...) constructor call.
422425
* The context is computed from the local auxiliary constructor context.

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ class Definitions {
5959
private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope) =
6060
newCompleteClassSymbol(owner, name, flags | Permanent | NoInits | Open, parents, decls).entered
6161

62-
private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) =
62+
private def enterTypeField(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope): TypeSymbol =
6363
scope.enter(newPermanentSymbol(cls, name, flags, TypeBounds.empty))
6464

65-
private def enterTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope) =
65+
private def enterTypeParam(cls: ClassSymbol, name: TypeName, flags: FlagSet, scope: MutableScope): TypeSymbol =
6666
enterTypeField(cls, name, flags | ClassTypeParamCreationFlags, scope)
6767

6868
private def enterSyntheticTypeParam(cls: ClassSymbol, paramFlags: FlagSet, scope: MutableScope, suffix: String = "T0") =
@@ -240,6 +240,7 @@ class Definitions {
240240
@tu lazy val Compiletime_codeOf: Symbol = CompiletimePackageClass.requiredMethod("codeOf")
241241
@tu lazy val Compiletime_erasedValue : Symbol = CompiletimePackageClass.requiredMethod("erasedValue")
242242
@tu lazy val Compiletime_uninitialized: Symbol = CompiletimePackageClass.requiredMethod("uninitialized")
243+
@tu lazy val Compiletime_deferred : Symbol = CompiletimePackageClass.requiredMethod("deferred")
243244
@tu lazy val Compiletime_error : Symbol = CompiletimePackageClass.requiredMethod(nme.error)
244245
@tu lazy val Compiletime_requireConst : Symbol = CompiletimePackageClass.requiredMethod("requireConst")
245246
@tu lazy val Compiletime_constValue : Symbol = CompiletimePackageClass.requiredMethod("constValue")
@@ -458,6 +459,13 @@ class Definitions {
458459
@tu lazy val andType: TypeSymbol = enterBinaryAlias(tpnme.AND, AndType(_, _))
459460
@tu lazy val orType: TypeSymbol = enterBinaryAlias(tpnme.OR, OrType(_, _, soft = false))
460461

462+
@tu lazy val CBCompanion: TypeSymbol = // type `<context-bound-companion>`[-Refs]
463+
enterPermanentSymbol(tpnme.CBCompanion,
464+
TypeBounds(NothingType,
465+
HKTypeLambda(tpnme.syntheticTypeParamName(0) :: Nil, Contravariant :: Nil)(
466+
tl => TypeBounds.empty :: Nil,
467+
tl => AnyType))).asType
468+
461469
/** Method representing a throw */
462470
@tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw,
463471
MethodType(List(ThrowableType), NothingType))
@@ -527,12 +535,16 @@ class Definitions {
527535
def ConsType: TypeRef = ConsClass.typeRef
528536
@tu lazy val SeqFactoryClass: Symbol = requiredClass("scala.collection.SeqFactory")
529537

538+
@tu lazy val PreciseClass: ClassSymbol = requiredClass("scala.Precise")
539+
530540
@tu lazy val SingletonClass: ClassSymbol =
531541
// needed as a synthetic class because Scala 2.x refers to it in classfiles
532542
// but does not define it as an explicit class.
533-
enterCompleteClassSymbol(
534-
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
535-
List(AnyType), EmptyScope)
543+
val cls = enterCompleteClassSymbol(
544+
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final | Erased,
545+
List(AnyType))
546+
enterTypeField(cls, tpnme.Self, Deferred, cls.info.decls.openForMutations)
547+
cls
536548
@tu lazy val SingletonType: TypeRef = SingletonClass.typeRef
537549

538550
@tu lazy val MaybeCapabilityAnnot: ClassSymbol =
@@ -1061,6 +1073,7 @@ class Definitions {
10611073
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
10621074
@tu lazy val RetainsArgAnnot: ClassSymbol = requiredClass("scala.annotation.retainsArg")
10631075
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
1076+
@tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames")
10641077

10651078
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
10661079

@@ -2157,6 +2170,7 @@ class Definitions {
21572170
NullClass,
21582171
NothingClass,
21592172
SingletonClass,
2173+
CBCompanion,
21602174
MaybeCapabilityAnnot)
21612175

21622176
@tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List(

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ object Flags {
377377
/** Symbol cannot be found as a member during typer */
378378
val (Invisible @ _, _, _) = newFlags(45, "<invisible>")
379379

380+
/** Tracked modifier for class parameter / a class with some tracked parameters */
381+
val (Tracked @ _, _, Dependent @ _) = newFlags(46, "tracked")
382+
380383
// ------------ Flags following this one are not pickled ----------------------------------
381384

382385
/** Symbol is not a member of its owner */
@@ -452,7 +455,7 @@ object Flags {
452455
CommonSourceModifierFlags.toTypeFlags | Abstract | Sealed | Opaque | Open
453456

454457
val TermSourceModifierFlags: FlagSet =
455-
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy
458+
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked
456459

457460
/** Flags representing modifiers that can appear in trees */
458461
val ModifierFlags: FlagSet =
@@ -466,7 +469,7 @@ object Flags {
466469
val FromStartFlags: FlagSet = commonFlags(
467470
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
468471
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
469-
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
472+
OuterOrCovariant, LabelOrContravariant, CaseAccessor, Tracked,
470473
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
471474
SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible)
472475

@@ -477,7 +480,7 @@ object Flags {
477480
*/
478481
val AfterLoadFlags: FlagSet = commonFlags(
479482
FromStartFlags, AccessFlags, Final, AccessorOrSealed,
480-
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent)
483+
Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent, Tracked)
481484

482485
/** A value that's unstable unless complemented with a Stable flag */
483486
val UnstableValueFlags: FlagSet = Mutable | Method
@@ -543,8 +546,6 @@ object Flags {
543546
/** Flags retained in type export forwarders */
544547
val RetainedExportTypeFlags = Infix
545548

546-
val MandatoryExportTypeFlags = Exported | Final
547-
548549
/** Flags that apply only to classes */
549550
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags
550551

@@ -572,6 +573,7 @@ object Flags {
572573
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
573574
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
574575
val DeferredOrTypeParam: FlagSet = Deferred | TypeParam // type symbols without right-hand sides
576+
val DeferredGivenFlags: FlagSet = Deferred | Given | HasDefault
575577
val EnumValue: FlagSet = Enum | StableRealizable // A Scala enum value
576578
val FinalOrInline: FlagSet = Final | Inline
577579
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ object Mode {
104104
val CheckBoundsOrSelfType: Mode = newMode(14, "CheckBoundsOrSelfType")
105105

106106
/** Use previous Scheme for implicit resolution. Currently significant
107-
* in 3.0-migration where we use Scala-2's scheme instead and in 3.5-migration
108-
* where we use the previous scheme up to 3.4 instead.
107+
* in 3.0-migration where we use Scala-2's scheme instead and in 3.5 and 3.6-migration
108+
* where we use the previous scheme up to 3.4 for comparison with the new scheme.
109109
*/
110110
val OldImplicitResolution: Mode = newMode(15, "OldImplicitResolution")
111111

0 commit comments

Comments
 (0)