Skip to content

Check flags that are not supposed to be mutated by completion #7427

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
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
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ object Flags {
def (x: FlagSet) ^ (y: FlagSet) =
FlagSet((x.bits | y.bits) & KINDFLAGS | (x.bits ^ y.bits) & ~KINDFLAGS)

def (x: FlagSet) partitionByIntersection(y: FlagSet): (FlagSet, FlagSet) =
val intersection = x & y
val complement = x &~ y
(intersection, complement)


/** Does the given flag set contain the given flag?
* This means that both the kind flags and the carrier bits have non-empty intersection.
*/
Expand Down Expand Up @@ -439,18 +445,18 @@ object Flags {
val FromStartFlags: FlagSet = commonFlags(
Module, Package, Deferred, Method, Case,
HigherKinded, Param, ParamAccessor,
Scala2ExistentialCommon, Mutable, Opaque, Touched, JavaStatic,
Scala2ExistentialCommon, Mutable, Opaque, JavaStatic,
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
SuperAccessorOrScala2x, Inline, Macro)
Extension, NonMember, Implicit, Given, Permanent, Inline)

/** Flags that are not (re)set when completing the denotation, or, if symbol is
* a top-level class or object, when completing the denotation once the class
* file defining the symbol is loaded (which is generally before the denotation
* is completed)
*/
val AfterLoadFlags: FlagSet = commonFlags(
FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined)
FromStartFlags, AccessFlags, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined,
SuperAccessorOrScala2x, Synthetic)


/** A value that's unstable unless complemented with a Stable flag */
Expand Down
61 changes: 43 additions & 18 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ object SymDenotations {

/** Update the flag set */
final def flags_=(flags: FlagSet): Unit =
myFlags = adaptFlags(flags)
setMyFlags(adaptFlags(flags))

/** Set given flags(s) of this denotation */
final def setFlag(flags: FlagSet): Unit = { myFlags |= flags }
final def setFlag(flags: FlagSet): Unit = setMyFlags(myFlags | flags)

/** Unset given flags(s) of this denotation */
final def resetFlag(flags: FlagSet): Unit = { myFlags &~= flags }
final def resetFlag(flags: FlagSet): Unit = setMyFlags(myFlags &~ flags)

/** Set applicable flags in {NoInits, PureInterface}
* @param parentFlags The flags that match the class or trait's parents
Expand All @@ -184,43 +184,68 @@ object SymDenotations {
if (myFlags.is(Trait)) NoInitsInterface & bodyFlags // no parents are initialized from a trait
else NoInits & bodyFlags & parentFlags)

/** Flags that are immutable at the current stage of
* this denotation's lifecycle.
*/
final def immutableFlags: FlagSet =
var result: FlagSet = AfterLoadFlags
if (myInfo.isInstanceOf[SymbolLoaders.LoadingCompleter]) result = FromStartFlags
result

private def setMyFlags(fs: FlagSet) =
val changedImmutableFlags = (myFlags ^ fs) & immutableFlags
assert(changedImmutableFlags.isEmpty, s"Illegal mutation of flags ${changedImmutableFlags.flagsString} on denotation $this")
myFlags = fs

private def isCurrent(fs: FlagSet) =
fs <= (
if (myInfo.isInstanceOf[SymbolLoader]) FromStartFlags
else AfterLoadFlags)
fs <= immutableFlags

final def relevantFlagsFor(fs: FlagSet)(implicit ctx: Context) =
if (isCurrent(fs)) myFlags else flags

/** Has this denotation one of given flag set? */
final def is(flag: Flag)(implicit ctx: Context): Boolean =
(if (isCurrent(flag)) myFlags else flags).is(flag)

/** Has this denotation one of the flags in `fs` set? */
final def isOneOf(fs: FlagSet)(implicit ctx: Context): Boolean =
(if (isCurrent(fs)) myFlags else flags).isOneOf(fs)
final inline def is(flag: Flag)(implicit ctx: Context): Boolean =
is(flag, EmptyFlags)

/** Has this denotation the given flag set, whereas none of the flags
* in `butNot` are set?
*/
final def is(flag: Flag, butNot: FlagSet)(implicit ctx: Context): Boolean =
(if (isCurrent(flag) && isCurrent(butNot)) myFlags else flags).is(flag, butNot)
final inline def is(flag: Flag, butNot: FlagSet)(implicit ctx: Context): Boolean =
isAllOf(flag, butNot)

/** Has this denotation one of the flags in `fs` set? */
final inline def isOneOf(fs: FlagSet)(implicit ctx: Context): Boolean =
isOneOf(fs, EmptyFlags)

/** Has this denotation one of the flags in `fs` set, whereas none of the flags
* in `butNot` are set?
*/
final def isOneOf(fs: FlagSet, butNot: FlagSet)(implicit ctx: Context): Boolean =
(if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags).isOneOf(fs, butNot)
val immutable = immutableFlags
val (fsImmut, fsMut) = fs.partitionByIntersection(immutable)
val (butNotImmut, butNotMut) = butNot.partitionByIntersection(immutable)

(butNotImmut & myFlags).isEmpty &&
(!(fsImmut & myFlags).isEmpty || (!fsMut.isEmpty && !(fsMut & flags).isEmpty)) &&
(butNotMut.isEmpty || (butNotMut & flags).isEmpty)

/** Has this denotation all of the flags in `fs` set? */
final def isAllOf(fs: FlagSet)(implicit ctx: Context): Boolean =
(if (isCurrent(fs)) myFlags else flags).isAllOf(fs)
final inline def isAllOf(fs: FlagSet)(implicit ctx: Context): Boolean =
isAllOf(fs, EmptyFlags)

/** Has this denotation all of the flags in `fs` set, whereas none of the flags
* in `butNot` are set?
*/
final def isAllOf(fs: FlagSet, butNot: FlagSet)(implicit ctx: Context): Boolean =
(if (isCurrent(fs) && isCurrent(butNot)) myFlags else flags).isAllOf(fs, butNot)
val immutable = immutableFlags
val (fsImmut, fsMut) = fs.partitionByIntersection(immutable)
val (butNotImmut, butNotMut) = butNot.partitionByIntersection(immutable)

fsImmut <= myFlags &&
(butNotImmut & myFlags).isEmpty &&
(fsMut.isEmpty || fsMut <= flags) &&
(butNotMut.isEmpty || (butNotMut & flags).isEmpty)


/** The type info, or, if symbol is not yet completed, the completer */
final def infoOrCompleter: Type = myInfo
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ object SymbolLoaders {
*/
trait SecondCompleter

/** A marker trait for a completer that loads information from
* an external source such as a TASTy or a class file.
*/
trait LoadingCompleter

private def enterNew(
owner: Symbol, member: Symbol,
completer: SymbolLoader, scope: Scope = EmptyScope)(implicit ctx: Context): Symbol = {
Expand Down Expand Up @@ -302,7 +307,7 @@ object SymbolLoaders {
/** A lazy type that completes itself by calling parameter doComplete.
* Any linked modules/classes or module classes are also initialized.
*/
abstract class SymbolLoader extends LazyType { self =>
abstract class SymbolLoader extends LazyType with SymbolLoaders.LoadingCompleter { self =>
/** Load source or class file for `root`, return */
def doComplete(root: SymDenotation)(implicit ctx: Context): Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class TreeUnpickler(reader: TastyReader,
}
}

class Completer(reader: TastyReader)(implicit @constructorOnly ctx: Context) extends LazyType {
class Completer(reader: TastyReader)(implicit @constructorOnly ctx: Context) extends LazyType with SymbolLoaders.LoadingCompleter {
import reader._
val owner = ctx.owner
val source = ctx.source
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,13 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas

import Scala2Unpickler._

val moduleRoot: SymDenotation = moduleClassRoot.sourceModule(ictx).denot(ictx)
val (moduleRoot: SymDenotation, moduleRootCompleter: ModuleCompleter) = {
val denot = moduleClassRoot.sourceModule(ictx).denot(ictx)
val completer = denot.completer
denot.info = new NoLoader // This denotation is still loading, mark it as such with this completer
(denot, completer)
}

assert(moduleRoot.isTerm)

checkVersion(ictx)
Expand Down Expand Up @@ -548,6 +554,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
case MODULEsym =>
if (isModuleRoot) {
moduleRoot setFlag flags
moduleRoot.info = moduleRootCompleter
moduleRoot.symbol
} else ctx.newSymbol(owner, name.asTermName, flags,
new LocalUnpickler() withModuleClass(implicit ctx =>
Expand All @@ -559,7 +566,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
})
}

class LocalUnpickler extends LazyType {
class LocalUnpickler extends LazyType with SymbolLoaders.LoadingCompleter {
def startCoord(denot: SymDenotation): Coord = denot.symbol.coord
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = try {
def parseToCompletion(denot: SymDenotation)(implicit ctx: Context) = {
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -762,10 +762,8 @@ class Namer { typer: Typer =>
else bound
}

def missingType(sym: Symbol, modifier: String)(implicit ctx: Context): Unit = {
def missingType(sym: Symbol, modifier: String)(implicit ctx: Context): Unit =
ctx.error(s"${modifier}type of implicit definition needs to be given explicitly", sym.sourcePos)
sym.resetFlag(GivenOrImplicit)
}

/** The completer of a symbol defined by a member def or import (except ClassSymbols) */
class Completer(val original: Tree)(implicit ctx: Context) extends LazyType with SymbolLoaders.SecondCompleter {
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i3067.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ object o {

implicit def y = "abc" // error

implicit object a extends Test(_ map identity) // error // error: no implicit argument found
implicit object a extends Test(_ map identity) // error
implicit object b extends Test(_ map identity) // error // error: cyclic reference
}
2 changes: 1 addition & 1 deletion tests/neg/i7407.scala
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
def qc(given ctx: scala.quoted.QuoteContext) = println(ctx)
inline def g = qc // error: no implicit argument
inline def g = qc // error: cyclic reference