Skip to content

Typeclass experiments refactored #20061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
34f17b7
New modularity language import
odersky Jan 7, 2024
31c9e8a
Allow vals in using clauses of givens
odersky Nov 18, 2023
84655ca
A relaxation concerning exported type aliases
odersky Dec 15, 2023
4894414
Allow class parents to be refined types.
odersky Dec 13, 2023
5189e68
Introduce tracked class parameters
odersky Apr 1, 2024
ea3c688
Make explicit arguments for context bounds an error from 3.5
odersky Apr 1, 2024
f96a769
Drop restriction against typedefs at level * only
odersky Apr 1, 2024
ef71dcb
Allow types in given definitions to be infix types
odersky Apr 1, 2024
2f58cbc
New syntax for given defs
odersky Apr 1, 2024
598c6ad
Allow multiple context bounds in `{...}`
odersky Apr 1, 2024
a61d2bc
Allow renamings `as N` in context bounds
odersky Apr 2, 2024
b48fb99
Implement `deferred` givens
odersky Apr 2, 2024
600293e
FIX: Allow ContextBoundParamNames to be unmangled.
odersky Apr 2, 2024
48000ee
Change rules for given prioritization
odersky Dec 21, 2023
d923cac
Allow context bounds in type declarations
odersky Apr 2, 2024
4d62692
Make some context bound evidence params tracked
odersky Apr 2, 2024
11d7fa3
FIX: Fix typing of RefinedTypes with watching parents
odersky Apr 2, 2024
96fbf29
Also reduce term projections
odersky Jan 6, 2024
ce09ef3
Implement context bound companions
odersky Apr 2, 2024
c6388c2
Allow contecxt bounds with abstract `Self` types
odersky Apr 2, 2024
f444b46
Add a doc page
odersky Apr 3, 2024
f713652
Fix Singleton
odersky Apr 5, 2024
1f2e735
Tweaks to doc pages
odersky Apr 6, 2024
94bc6fe
Add Precise type class for precise type inference
odersky Apr 6, 2024
887fbc4
Fix rebase breakage
odersky Apr 14, 2024
1e72282
Delay roll-out of new prioritization scheme:
odersky Apr 17, 2024
9d0ca20
Fix rebase breakage again
odersky Apr 28, 2024
fd072dc
Make best effort compilation work with context bound companions
odersky Apr 28, 2024
21f5e67
Tweaks after review
odersky Apr 30, 2024
d3e6a95
Fix rebase breakage
odersky May 6, 2024
b2f0791
Make Singleton an erased class only under modularity import
odersky May 6, 2024
4f28d8a
Address review comments
odersky May 6, 2024
0dddcb7
Adress review comments with changed docs and new tests
odersky May 6, 2024
62e0244
Update warn check files
odersky May 7, 2024
9959f28
Update InlayHints
odersky May 7, 2024
3c78ada
Fix typo
odersky May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs have not been updated with the renaming (see L633 and L636).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also expect the text describing the semantics of the widening. Reading ahead there seems to be some new interaction between widen and bound when the latter is a singleton.

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