Skip to content

Commit

Permalink
Typeclass experiments refactored (#20061)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
odersky authored May 7, 2024
2 parents 17c8766 + 3c78ada commit 1f3c652
Show file tree
Hide file tree
Showing 184 changed files with 8,551 additions and 564 deletions.
287 changes: 190 additions & 97 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package ast
import core.*
import Flags.*, Trees.*, Types.*, Contexts.*
import Names.*, StdNames.*, NameOps.*, Symbols.*
import Annotations.Annotation
import NameKinds.ContextBoundParamName
import typer.ConstFold
import reporting.trace

Expand Down Expand Up @@ -380,6 +382,29 @@ trait TreeInfo[T <: Untyped] { self: Trees.Instance[T] =>
case _ =>
tree.tpe.isInstanceOf[ThisType]
}

/** Under x.modularity: Extractor for `annotation.internal.WitnessNames(name_1, ..., name_n)`
* represented as an untyped or typed tree.
*/
object WitnessNamesAnnot:
def apply(names: List[TermName])(using Context): untpd.Tree =
untpd.TypedSplice(tpd.New(
defn.WitnessNamesAnnot.typeRef,
tpd.SeqLiteral(names.map(n => tpd.Literal(Constant(n.toString))), tpd.TypeTree(defn.StringType)) :: Nil
))

def unapply(tree: Tree)(using Context): Option[List[TermName]] =
unsplice(tree) match
case Apply(Select(New(tpt: tpd.TypeTree), nme.CONSTRUCTOR), SeqLiteral(elems, _) :: Nil) =>
tpt.tpe match
case tp: TypeRef if tp.name == tpnme.WitnessNames && tp.symbol == defn.WitnessNamesAnnot =>
Some:
elems.map:
case Literal(Constant(str: String)) =>
ContextBoundParamName.unmangle(str.toTermName.asSimpleName)
case _ => None
case _ => None
end WitnessNamesAnnot
}

trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped] =>
Expand Down
11 changes: 11 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class ContextBounds(bounds: TypeBoundsTree, cxBounds: List[Tree])(implicit @constructorOnly src: SourceFile) extends TypTree
case class PatDef(mods: Modifiers, pats: List[Tree], tpt: Tree, rhs: Tree)(implicit @constructorOnly src: SourceFile) extends DefTree
case class ExtMethods(paramss: List[ParamClause], methods: List[Tree])(implicit @constructorOnly src: SourceFile) extends Tree
case class ContextBoundTypeTree(tycon: Tree, paramName: TypeName, ownName: TermName)(implicit @constructorOnly src: SourceFile) extends Tree
// `paramName: tycon as ownName`, ownName != EmptyTermName only under x.modularity
case class MacroTree(expr: Tree)(implicit @constructorOnly src: SourceFile) extends Tree

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

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

case class Tracked()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Tracked)

/** Used under pureFunctions to mark impure function types `A => B` in `FunctionWithMods` */
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
}
Expand Down Expand Up @@ -675,6 +679,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def ExtMethods(tree: Tree)(paramss: List[ParamClause], methods: List[Tree])(using Context): Tree = tree match
case tree: ExtMethods if (paramss eq tree.paramss) && (methods == tree.methods) => tree
case _ => finalize(tree, untpd.ExtMethods(paramss, methods)(tree.source))
def ContextBoundTypeTree(tree: Tree)(tycon: Tree, paramName: TypeName, ownName: TermName)(using Context): Tree = tree match
case tree: ContextBoundTypeTree if (tycon eq tree.tycon) && paramName == tree.paramName && ownName == tree.ownName => tree
case _ => finalize(tree, untpd.ContextBoundTypeTree(tycon, paramName, ownName)(tree.source))
def ImportSelector(tree: Tree)(imported: Ident, renamed: Tree, bound: Tree)(using Context): Tree = tree match {
case tree: ImportSelector if (imported eq tree.imported) && (renamed eq tree.renamed) && (bound eq tree.bound) => tree
case _ => finalize(tree, untpd.ImportSelector(imported, renamed, bound)(tree.source))
Expand Down Expand Up @@ -740,6 +747,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs))
case ExtMethods(paramss, methods) =>
cpy.ExtMethods(tree)(transformParamss(paramss), transformSub(methods))
case ContextBoundTypeTree(tycon, paramName, ownName) =>
cpy.ContextBoundTypeTree(tree)(transform(tycon), paramName, ownName)
case ImportSelector(imported, renamed, bound) =>
cpy.ImportSelector(tree)(transformSub(imported), transform(renamed), transform(bound))
case Number(_, _) | TypedSplice(_) =>
Expand Down Expand Up @@ -795,6 +804,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(this(this(x, pats), tpt), rhs)
case ExtMethods(paramss, methods) =>
this(paramss.foldLeft(x)(apply), methods)
case ContextBoundTypeTree(tycon, paramName, ownName) =>
this(x, tycon)
case ImportSelector(imported, renamed, bound) =>
this(this(this(x, imported), renamed), bound)
case Number(_, _) =>
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,12 @@ object Config {
*/
inline val checkLevelsOnConstraints = false
inline val checkLevelsOnInstantiation = true

/** Under x.modularity:
* If a type parameter `X` has a single context bound `X: C`, should the
* witness parameter be named `X`? This would prevent the creation of a
* context bound companion.
*/
inline val nameSingleContextBounds = false
}

1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ object Feature:
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val namedTuples = experimental("namedTuples")
val modularity = experimental("modularity")

def experimentalAutoEnableFeatures(using Context): List[TermName] =
defn.languageExperimentalFeatures
Expand Down
18 changes: 7 additions & 11 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -647,9 +647,9 @@ trait ConstraintHandling {
* At this point we also drop the @Repeated annotation to avoid inferring type arguments with it,
* as those could leak the annotation to users (see run/inferred-repeated-result).
*/
def widenInferred(inst: Type, bound: Type, widenUnions: Boolean)(using Context): Type =
def widenInferred(inst: Type, bound: Type, widen: Widen)(using Context): Type =
def widenOr(tp: Type) =
if widenUnions then
if widen == Widen.Unions then
val tpw = tp.widenUnion
if tpw ne tp then
if tpw.isTransparent() then
Expand All @@ -667,14 +667,10 @@ trait ConstraintHandling {
val tpw = tp.widenSingletons(skipSoftUnions)
if (tpw ne tp) && (tpw <:< bound) then tpw else tp

def isSingleton(tp: Type): Boolean = tp match
case WildcardType(optBounds) => optBounds.exists && isSingleton(optBounds.bounds.hi)
case _ => isSubTypeWhenFrozen(tp, defn.SingletonType)

val wideInst =
if isSingleton(bound) then inst
if widen == Widen.None || bound.isSingletonBounded(frozen = true) then inst
else
val widenedFromSingle = widenSingle(inst, skipSoftUnions = widenUnions)
val widenedFromSingle = widenSingle(inst, skipSoftUnions = widen == Widen.Unions)
val widenedFromUnion = widenOr(widenedFromSingle)
val widened = dropTransparentTraits(widenedFromUnion, bound)
widenIrreducible(widened)
Expand Down Expand Up @@ -713,18 +709,18 @@ trait ConstraintHandling {
* The instance type is not allowed to contain references to types nested deeper
* than `maxLevel`.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean, widenUnions: Boolean, maxLevel: Int)(using Context): Type = {
def instanceType(param: TypeParamRef, fromBelow: Boolean, widen: Widen, maxLevel: Int)(using Context): Type = {
val approx = approximation(param, fromBelow, maxLevel).simplified
if fromBelow then
val widened = widenInferred(approx, param, widenUnions)
val widened = widenInferred(approx, param, widen)
// Widening can add extra constraints, in particular the widened type might
// be a type variable which is now instantiated to `param`, and therefore
// cannot be used as an instantiation of `param` without creating a loop.
// If that happens, we run `instanceType` again to find a new instantiation.
// (we do not check for non-toplevel occurrences: those should never occur
// since `addOneBound` disallows recursive lower bounds).
if constraint.occursAtToplevel(param, widened) then
instanceType(param, fromBelow, widenUnions, maxLevel)
instanceType(param, fromBelow, widen, maxLevel)
else
widened
else
Expand Down
13 changes: 8 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Symbols.*
import Scopes.*
import Uniques.*
import ast.Trees.*
import Flags.ParamAccessor
import ast.untpd
import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance}
import typer.{Implicits, ImportInfo, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables}
Expand Down Expand Up @@ -399,7 +400,8 @@ object Contexts {
*
* - as owner: The primary constructor of the class
* - as outer context: The context enclosing the class context
* - as scope: The parameter accessors in the class context
* - as scope: type parameters, the parameter accessors, and
* the context bound companions in the class context,
*
* The reasons for this peculiar choice of attributes are as follows:
*
Expand All @@ -413,10 +415,11 @@ object Contexts {
* context see the constructor parameters instead, but then we'd need a final substitution step
* from constructor parameters to class parameter accessors.
*/
def superCallContext: Context = {
val locals = newScopeWith(owner.typeParams ++ owner.asClass.paramAccessors*)
superOrThisCallContext(owner.primaryConstructor, locals)
}
def superCallContext: Context =
val locals = owner.typeParams
++ owner.asClass.unforcedDecls.filter: sym =>
sym.is(ParamAccessor) || sym.isContextBoundCompanion
superOrThisCallContext(owner.primaryConstructor, newScopeWith(locals*))

/** The context for the arguments of a this(...) constructor call.
* The context is computed from the local auxiliary constructor context.
Expand Down
24 changes: 19 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ class Definitions {
private def enterCompleteClassSymbol(owner: Symbol, name: TypeName, flags: FlagSet, parents: List[TypeRef], decls: Scope) =
newCompleteClassSymbol(owner, name, flags | Permanent | NoInits | Open, parents, decls).entered

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

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

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

@tu lazy val CBCompanion: TypeSymbol = // type `<context-bound-companion>`[-Refs]
enterPermanentSymbol(tpnme.CBCompanion,
TypeBounds(NothingType,
HKTypeLambda(tpnme.syntheticTypeParamName(0) :: Nil, Contravariant :: Nil)(
tl => TypeBounds.empty :: Nil,
tl => AnyType))).asType

/** Method representing a throw */
@tu lazy val throwMethod: TermSymbol = enterMethod(OpsPackageClass, nme.THROWkw,
MethodType(List(ThrowableType), NothingType))
Expand Down Expand Up @@ -527,12 +535,16 @@ class Definitions {
def ConsType: TypeRef = ConsClass.typeRef
@tu lazy val SeqFactoryClass: Symbol = requiredClass("scala.collection.SeqFactory")

@tu lazy val PreciseClass: ClassSymbol = requiredClass("scala.Precise")

@tu lazy val SingletonClass: ClassSymbol =
// needed as a synthetic class because Scala 2.x refers to it in classfiles
// but does not define it as an explicit class.
enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final,
List(AnyType), EmptyScope)
val cls = enterCompleteClassSymbol(
ScalaPackageClass, tpnme.Singleton, PureInterfaceCreationFlags | Final | Erased,
List(AnyType))
enterTypeField(cls, tpnme.Self, Deferred, cls.info.decls.openForMutations)
cls
@tu lazy val SingletonType: TypeRef = SingletonClass.typeRef

@tu lazy val MaybeCapabilityAnnot: ClassSymbol =
Expand Down Expand Up @@ -1061,6 +1073,7 @@ class Definitions {
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
@tu lazy val RetainsArgAnnot: ClassSymbol = requiredClass("scala.annotation.retainsArg")
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
@tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames")

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

Expand Down Expand Up @@ -2157,6 +2170,7 @@ class Definitions {
NullClass,
NothingClass,
SingletonClass,
CBCompanion,
MaybeCapabilityAnnot)

@tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List(
Expand Down
12 changes: 7 additions & 5 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,9 @@ object Flags {
/** Symbol cannot be found as a member during typer */
val (Invisible @ _, _, _) = newFlags(45, "<invisible>")

/** Tracked modifier for class parameter / a class with some tracked parameters */
val (Tracked @ _, _, Dependent @ _) = newFlags(46, "tracked")

// ------------ Flags following this one are not pickled ----------------------------------

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

val TermSourceModifierFlags: FlagSet =
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy
CommonSourceModifierFlags.toTermFlags | Inline | AbsOverride | Lazy | Tracked

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

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

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

val MandatoryExportTypeFlags = Exported | Final

/** Flags that apply only to classes */
val ClassOnlyFlags = Sealed | Open | Abstract.toTypeFlags

Expand Down Expand Up @@ -572,6 +573,7 @@ object Flags {
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
val DeferredOrTypeParam: FlagSet = Deferred | TypeParam // type symbols without right-hand sides
val DeferredGivenFlags: FlagSet = Deferred | Given | HasDefault
val EnumValue: FlagSet = Enum | StableRealizable // A Scala enum value
val FinalOrInline: FlagSet = Final | Inline
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ object Mode {
val CheckBoundsOrSelfType: Mode = newMode(14, "CheckBoundsOrSelfType")

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

Expand Down
Loading

0 comments on commit 1f3c652

Please sign in to comment.