Skip to content

Pre SIP: Improvements to Modularity #18958

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 24 additions & 11 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,13 @@ object desugar {
private def toDefParam(tparam: TypeDef, keepAnnotations: Boolean): TypeDef = {
var mods = tparam.rawMods
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
tparam.withMods(mods & (EmptyFlags | Sealed) | Param)
tparam.withMods(mods & EmptyFlags | Param)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is equivalent to just Param?

}
private def toDefParam(vparam: ValDef, keepAnnotations: Boolean, keepDefault: Boolean): ValDef = {
var mods = vparam.rawMods
if (!keepAnnotations) mods = mods.withAnnotations(Nil)
val hasDefault = if keepDefault then HasDefault else EmptyFlags
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault) | Param)
vparam.withMods(mods & (GivenOrImplicit | Erased | hasDefault | Tracked) | Param)
}

def mkApply(fn: Tree, paramss: List[ParamClause])(using Context): Tree =
Expand Down Expand Up @@ -535,7 +535,7 @@ object desugar {
// but not on the constructor parameters. The reverse is true for
// annotations on class _value_ parameters.
val constrTparams = impliedTparams.map(toDefParam(_, keepAnnotations = false))
val constrVparamss =
def defVparamss =
if (originalVparamss.isEmpty) { // ensure parameter list is non-empty
if (isCaseClass)
report.error(CaseClassMissingParamList(cdef), namePos)
Expand All @@ -546,6 +546,10 @@ object desugar {
ListOfNil
}
else originalVparamss.nestedMap(toDefParam(_, keepAnnotations = true, keepDefault = true))
val constrVparamss = defVparamss
// defVparamss also needed as separate tree nodes in implicitWrappers below.
// Need to be separate because they are `watch`ed in addParamRefinements.
// See parsercombinators-givens.scala for a test case.
val derivedTparams =
constrTparams.zipWithConserve(impliedTparams)((tparam, impliedParam) =>
derivedTypeParam(tparam).withAnnotations(impliedParam.mods.annotations))
Expand Down Expand Up @@ -623,6 +627,11 @@ object desugar {
case _ => false
}

def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
case _ => false
}

def appliedRef(tycon: Tree, tparams: List[TypeDef] = constrTparams, widenHK: Boolean = false) = {
val targs = for (tparam <- tparams) yield {
val targ = refOfDef(tparam)
Expand All @@ -639,10 +648,13 @@ object desugar {
appliedTypeTree(tycon, targs)
}

def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
case _ => false
}
def addParamRefinements(core: Tree, paramss: List[List[ValDef]]): Tree =
val refinements =
for params <- paramss; param <- params; if param.mods.is(Tracked) yield
ValDef(param.name, SingletonTypeTree(TermRefTree().watching(param)), EmptyTree)
.withSpan(param.span)
if refinements.isEmpty then core
else RefinedTypeTree(core, refinements).showing(i"refined result: $result", Printers.desugar)

// a reference to the class type bound by `cdef`, with type parameters coming from the constructor
val classTypeRef = appliedRef(classTycon)
Expand Down Expand Up @@ -864,18 +876,17 @@ object desugar {
Nil
}
else {
val defParamss = constrVparamss match {
val defParamss = defVparamss match
case Nil :: paramss =>
paramss // drop leading () that got inserted by class
// TODO: drop this once we do not silently insert empty class parameters anymore
case paramss => paramss
}
val finalFlag = if ctx.settings.YcompileScala2Library.value then EmptyFlags else Final
// implicit wrapper is typechecked in same scope as constructor, so
// we can reuse the constructor parameters; no derived params are needed.
DefDef(
className.toTermName, joinParams(constrTparams, defParamss),
classTypeRef, creatorExpr)
addParamRefinements(classTypeRef, defParamss), creatorExpr)
.withMods(companionMods | mods.flags.toTermFlags & (GivenOrImplicit | Inline) | finalFlag)
.withSpan(cdef.span) :: Nil
}
Expand Down Expand Up @@ -904,7 +915,9 @@ object desugar {
}
if mods.isAllOf(Given | Inline | Transparent) then
report.error("inline given instances cannot be trasparent", cdef)
val classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
var classMods = if mods.is(Given) then mods &~ (Inline | Transparent) | Synthetic else mods
if vparamAccessors.exists(_.mods.is(Tracked)) then
classMods |= Dependent
cpy.TypeDef(cdef: TypeDef)(
name = className,
rhs = cpy.Template(impl)(constr, parents1, clsDerived, self1,
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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
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 @@ -32,6 +32,7 @@ object Feature:
val pureFunctions = experimental("pureFunctions")
val captureChecking = experimental("captureChecking")
val into = experimental("into")
val modularity = experimental("modularity")

val globalOnlyImports: Set[TermName] = Set(pureFunctions, captureChecking)

Expand Down
9 changes: 6 additions & 3 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
32 changes: 29 additions & 3 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package core
import Contexts.*, Symbols.*, Types.*, Flags.*, Scopes.*, Decorators.*, Names.*, NameOps.*
import SymDenotations.{LazyType, SymDenotation}, StdNames.nme
import TypeApplications.EtaExpansion
import collection.mutable

/** Operations that are shared between Namer and TreeUnpickler */
object NamerOps:
Expand All @@ -14,9 +15,34 @@ object NamerOps:
* @param ctor the constructor
*/
def effectiveResultType(ctor: Symbol, paramss: List[List[Symbol]])(using Context): Type =
paramss match
case TypeSymbols(tparams) :: _ => ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef))
case _ => ctor.owner.typeRef
val (resType, termParamss) = paramss match
case TypeSymbols(tparams) :: rest =>
(ctor.owner.typeRef.appliedTo(tparams.map(_.typeRef)), rest)
case _ =>
(ctor.owner.typeRef, paramss)
termParamss.flatten.foldLeft(resType): (rt, param) =>
if param.is(Tracked) then RefinedType(rt, param.name, param.termRef)
else rt

/** Split dependent class refinements off parent type. Add them to `refinements`,
* unless it is null.
*/
extension (tp: Type)
def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type =
tp match
case RefinedType(tp1, rname, rinfo) =>
try tp1.separateRefinements(cls, refinements)
finally
if refinements != null then
refinements(rname) = refinements.get(rname) match
case Some(tp) => tp & rinfo
case None => rinfo
case tp @ AnnotatedType(tp1, ann) =>
tp.derivedAnnotatedType(tp1.separateRefinements(cls, refinements), ann)
case tp: RecType =>
tp.parent.substRecThis(tp, cls.thisType).separateRefinements(cls, refinements)
case tp =>
tp

/** If isConstructor, make sure it has at least one non-implicit parameter list
* This is done by adding a () in front of a leading old style implicit parameter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ trait PatternTypeConstrainer { self: TypeComparer =>
}
}

def stripRefinement(tp: Type): Type = tp match {
case tp: RefinedOrRecType => stripRefinement(tp.parent)
case tp => tp
}

def tryConstrainSimplePatternType(pat: Type, scrut: Type) = {
val patCls = pat.classSymbol
val scrCls = scrut.classSymbol
Expand Down Expand Up @@ -181,14 +176,14 @@ trait PatternTypeConstrainer { self: TypeComparer =>
case AndType(scrut1, scrut2) =>
constrainPatternType(pat, scrut1) && constrainPatternType(pat, scrut2)
case scrut: RefinedOrRecType =>
constrainPatternType(pat, stripRefinement(scrut))
constrainPatternType(pat, scrut.stripRefinement)
case scrut => dealiasDropNonmoduleRefs(pat) match {
case OrType(pat1, pat2) =>
either(constrainPatternType(pat1, scrut), constrainPatternType(pat2, scrut))
case AndType(pat1, pat2) =>
constrainPatternType(pat1, scrut) && constrainPatternType(pat2, scrut)
case pat: RefinedOrRecType =>
constrainPatternType(stripRefinement(pat), scrut)
constrainPatternType(pat.stripRefinement, scrut)
case pat =>
tryConstrainSimplePatternType(pat, scrut)
|| classesMayBeCompatible && constrainUpcasted(scrut)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ object StdNames {
val toString_ : N = "toString"
val toTypeConstructor: N = "toTypeConstructor"
val tpe : N = "tpe"
val tracked: N = "tracked"
val transparent : N = "transparent"
val tree : N = "tree"
val true_ : N = "true"
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1190,13 +1190,17 @@ object SymDenotations {
final def isExtensibleClass(using Context): Boolean =
isClass && !isOneOf(FinalOrModuleClass) && !isAnonymousClass

/** A symbol is effectively final if it cannot be overridden in a subclass */
/** A symbol is effectively final if it cannot be overridden */
final def isEffectivelyFinal(using Context): Boolean =
isOneOf(EffectivelyFinalFlags)
|| is(Inline, butNot = Deferred)
|| is(JavaDefinedVal, butNot = Method)
|| isConstructor
|| !owner.isExtensibleClass
|| !owner.isExtensibleClass && !is(Deferred)
// Deferred symbols can arise through parent refinements.
// For them, the overriding relationship reverses anyway, so
// being in a final class does not mean the symbol cannot be
// implemented concretely in a superclass.

/** A class is effectively sealed if has the `final` or `sealed` modifier, or it
* is defined in Scala 3 and is neither abstract nor open.
Expand Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import TypeErasure.ErasedValueType
import Types.*, Contexts.*, Symbols.*, Flags.*, Decorators.*
import Names.Name

class TypeUtils {
class TypeUtils:
/** A decorator that provides methods on types
* that are needed in the transformer pipeline.
*/
extension (self: Type) {
extension (self: Type)

def isErasedValueType(using Context): Boolean =
self.isInstanceOf[ErasedValueType]
Expand Down Expand Up @@ -150,5 +150,11 @@ class TypeUtils {
case _ =>
val cls = self.underlyingClassRef(refinementOK = false).typeSymbol
cls.isTransparentClass && (!traitOnly || cls.is(Trait))
}
}

/** Strip all outer refinements off this type */
def stripRefinement: Type = self match
case self: RefinedOrRecType => self.parent.stripRefinement
case seld => self

end TypeUtils

1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,7 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
if (flags.is(Exported)) writeModTag(EXPORTED)
if (flags.is(Given)) writeModTag(GIVEN)
if (flags.is(Implicit)) writeModTag(IMPLICIT)
if (flags.is(Tracked)) writeModTag(TRACKED)
if (isTerm) {
if (flags.is(Lazy, butNot = Module)) writeModTag(LAZY)
if (flags.is(AbsOverride)) { writeModTag(ABSTRACT); writeModTag(OVERRIDE) }
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@ class TreeUnpickler(reader: TastyReader,
case INVISIBLE => addFlag(Invisible)
case TRANSPARENT => addFlag(Transparent)
case INFIX => addFlag(Infix)
case TRACKED => addFlag(Tracked)
case PRIVATEqualified =>
readByte()
privateWithin = readWithin
Expand Down Expand Up @@ -1054,7 +1055,7 @@ class TreeUnpickler(reader: TastyReader,
}
val parentReader = fork
val parents = readParents(withArgs = false)(using parentCtx)
val parentTypes = parents.map(_.tpe.dealias)
val parentTypes = parents.map(_.tpe.dealiasKeepAnnots.separateRefinements(cls, null))
Copy link
Contributor

Choose a reason for hiding this comment

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

Compare this with pickling, it seems we loose symmetry?

if cls.is(JavaDefined) && parentTypes.exists(_.derivesFrom(defn.JavaAnnotationClass)) then
cls.setFlag(JavaAnnotation)
val self =
Expand Down
Loading