Skip to content

Handle opaque types in computeAsSeenFrom #6721

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 9 commits into from
Jun 22, 2019
Merged
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
27 changes: 25 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1118,8 +1118,31 @@ object Denotations {
case thisd: SymDenotation => thisd.owner
case _ => if (symbol.exists) symbol.owner else NoSymbol
}
if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this
else derivedSingleDenotation(symbol, symbol.info.asSeenFrom(pre, owner))
def derived(info: Type) = derivedSingleDenotation(symbol, info.asSeenFrom(pre, owner))
pre match {
case pre: ThisType if symbol.isOpaqueAlias && pre.cls == symbol.owner =>
// This code is necessary to compensate for a "window of vulnerability" with
// opaque types. The problematic sequence is as follows.
// 1. Type a selection `m.this.T` where `T` is an opaque type alias in `m`
// and this is the first access
// 2. `T` will normalize to an abstract type on completion.
// 3. At that time, the default logic in the second case is wrong: `T`'s new info
// is now an abstract type and running it through an asSeenFrom gives nothing.
// We fix this as follows:
// 1. Force opaque normalization as first step
// 2. Read the info from the enclosing object's refinement
symbol.normalizeOpaque()
def findRefined(tp: Type, name: Name): Type = tp match {
case RefinedType(parent, rname, rinfo) =>
if (rname == name) rinfo else findRefined(parent, name)
case _ =>
symbol.info
}
derived(findRefined(pre.underlying, symbol.name))
case _ =>
if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this
else derived(symbol.info)
}
}

/** Does this denotation have all the `required` flags but none of the `excluded` flags?
Expand Down
29 changes: 17 additions & 12 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -440,24 +440,29 @@ object Types {
NoSymbol
}

/** The least (wrt <:<) set of class symbols of which this type is a subtype
/** The least (wrt <:<) set of symbols satisfying the `include` prediacte of which this type is a subtype
*/
final def classSymbols(implicit ctx: Context): List[ClassSymbol] = this match {
final def parentSymbols(include: Symbol => Boolean)(implicit ctx: Context): List[Symbol] = this match {
case tp: ClassInfo =>
tp.cls :: Nil
case tp: TypeRef =>
val sym = tp.symbol
if (sym.isClass) sym.asClass :: Nil else tp.superType.classSymbols
if (include(sym)) sym :: Nil else tp.superType.parentSymbols(include)
case tp: TypeProxy =>
tp.underlying.classSymbols
tp.underlying.parentSymbols(include)
case AndType(l, r) =>
l.classSymbols | r.classSymbols
l.parentSymbols(include) | r.parentSymbols(include)
case OrType(l, r) =>
l.classSymbols intersect r.classSymbols // TODO does not conform to spec
l.parentSymbols(include) intersect r.parentSymbols(include) // TODO does not conform to spec
case _ =>
Nil
}

/** The least (wrt <:<) set of class symbols of which this type is a subtype
*/
final def classSymbols(implicit ctx: Context): List[ClassSymbol] =
parentSymbols(_.isClass).asInstanceOf

/** The term symbol associated with the type */
@tailrec final def termSymbol(implicit ctx: Context): Symbol = this match {
case tp: TermRef => tp.symbol
Expand Down Expand Up @@ -1244,9 +1249,6 @@ object Types {
case _ => if (isRepeatedParam) this.argTypesHi.head else this
}

/** If this is a FunProto or PolyProto, WildcardType, otherwise this. */
def notApplied: Type = this

// ----- Normalizing typerefs over refined types ----------------------------

/** If this normalizes* to a refinement type that has a refinement for `name` (which might be followed
Expand Down Expand Up @@ -1428,6 +1430,9 @@ object Types {
/** If this is an ignored proto type, its underlying type, otherwise the type itself */
def revealIgnored: Type = this

/** If this is a proto type, the ignored version, otherwise the type itself */
def dropIfProto: Type = this

// ----- Substitutions -----------------------------------------------------

/** Substitute all types that refer in their symbol attribute to
Expand Down Expand Up @@ -1728,6 +1733,8 @@ object Types {
* captures the given context `ctx`.
*/
def withContext(ctx: Context): ProtoType = this

override def dropIfProto = WildcardType
}

/** Implementations of this trait cache the results of `narrow`. */
Expand Down Expand Up @@ -1884,10 +1891,8 @@ object Types {
finish(memberDenot(symd.initial.name, allowPrivate = false))
else if (prefix.isArgPrefixOf(symd))
finish(argDenot(sym.asType))
else if (infoDependsOnPrefix(symd, prefix)) {
if (!symd.isClass && symd.is(Opaque, butNot = Deferred)) symd.normalizeOpaque()
else if (infoDependsOnPrefix(symd, prefix))
finish(memberDenot(symd.initial.name, allowPrivate = symd.is(Private)))
}
else
finish(symd.current)
}
Expand Down
30 changes: 18 additions & 12 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,20 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case _ => tree
}

def importText(deleg: Boolean, expr: Tree, selectors: List[Tree]) = {
def selectorText(sel: Tree): Text = sel match {
case Thicket(l :: r :: Nil) => toTextGlobal(l) ~ " => " ~ toTextGlobal(r)
case _: Ident => toTextGlobal(sel)
case TypeBoundsTree(_, tpt) => "for " ~ toTextGlobal(tpt)
}
val selectorsText: Text = selectors match {
case id :: Nil => toText(id)
case _ => "{" ~ Text(selectors map selectorText, ", ") ~ "}"
}
(keywordText("delegate ") provided deleg) ~
toTextLocal(expr) ~ "." ~ selectorsText
}

tree match {
case id: Trees.BackquotedIdent[_] if !homogenizedView =>
"`" ~ toText(id.name) ~ "`"
Expand Down Expand Up @@ -499,18 +513,10 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
typeDefText(tparamsTxt, optText(rhs)(" = " ~ _))
}
recur(rhs, "")
case Import(importImplied, expr, selectors) =>
def selectorText(sel: Tree): Text = sel match {
case Thicket(l :: r :: Nil) => toTextGlobal(l) ~ " => " ~ toTextGlobal(r)
case _: Ident => toTextGlobal(sel)
case TypeBoundsTree(_, tpt) => "for " ~ toTextGlobal(tpt)
}
val selectorsText: Text = selectors match {
case id :: Nil => toText(id)
case _ => "{" ~ Text(selectors map selectorText, ", ") ~ "}"
}
keywordText("import ") ~ (keywordText("delegate ") provided importImplied) ~
toTextLocal(expr) ~ "." ~ selectorsText
case Import(deleg, expr, selectors) =>
keywordText("import ") ~ importText(deleg, expr, selectors)
case Export(deleg, expr, selectors) =>
keywordText("export ") ~ importText(deleg, expr, selectors)
case packageDef: PackageDef =>
packageDefText(packageDef)
case tree: Template =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,9 @@ object messages {

val msg: String = {
val more = if (tree.isInstanceOf[tpd.Apply]) " more" else ""
em"${methodSymbol.showLocated} does not take$more parameters"
val meth = methodSymbol
val methStr = if (meth.exists) methodSymbol.showLocated else "expression"
em"$methStr does not take$more parameters"
}

val explanation: String = {
Expand Down
27 changes: 18 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ object Implicits {
*/
class OfTypeImplicits(tp: Type, val companionRefs: TermRefSet)(initctx: Context) extends ImplicitRefs(initctx) {
assert(initctx.typer != null)
implicits.println(i"implicits of type $tp = ${companionRefs.toList}%, %")
@threadUnsafe lazy val refs: List[ImplicitRef] = {
val buf = new mutable.ListBuffer[TermRef]
for (companion <- companionRefs) buf ++= companion.implicitMembers(ImplicitOrImpliedOrGiven)
Expand Down Expand Up @@ -491,13 +492,19 @@ trait ImplicitRunInfo { self: Run =>
object liftToClasses extends TypeMap {
override implicit protected val ctx: Context = liftingCtx
override def stopAtStatic = true

def apply(tp: Type) = tp match {
case tp: TypeRef if !tp.symbol.isClass =>
val pre = tp.prefix
def joinClass(tp: Type, cls: ClassSymbol) =
AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner))
val lead = if (pre eq NoPrefix) defn.AnyType else apply(pre)
(lead /: tp.classSymbols)(joinClass)
case tp: TypeRef =>
val sym = tp.symbol
if (sym.isClass || sym.isOpaqueAlias) tp
else {
val pre = tp.prefix
def joinClass(tp: Type, cls: Symbol) =
AndType.make(tp, cls.typeRef.asSeenFrom(pre, cls.owner))
val lead = if (pre eq NoPrefix) defn.AnyType else apply(pre)
def isLiftTarget(sym: Symbol) = sym.isClass || sym.isOpaqueAlias
(lead /: tp.parentSymbols(isLiftTarget))(joinClass)
}
case tp: TypeVar =>
apply(tp.underlying)
case tp: AppliedType if !tp.tycon.typeSymbol.isClass =>
Expand All @@ -516,7 +523,7 @@ trait ImplicitRunInfo { self: Run =>

// todo: compute implicits directly, without going via companionRefs?
def collectCompanions(tp: Type): TermRefSet = track("computeImplicitScope") {
trace(i"collectCompanions($tp)", implicits) {
trace(i"collectCompanions($tp)", implicitsDetailed) {

def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match {
case Some(is) =>
Expand Down Expand Up @@ -575,8 +582,10 @@ trait ImplicitRunInfo { self: Run =>
def computeIScope() = {
val liftedTp = if (isLifted) tp else liftToClasses(tp)
val refs =
if (liftedTp ne tp)
if (liftedTp ne tp) {
implicitsDetailed.println(i"lifted of $tp = $liftedTp")
iscope(liftedTp, isLifted = true).companionRefs
}
else
collectCompanions(tp)
val result = new OfTypeImplicits(tp, refs)(ctx)
Expand Down Expand Up @@ -673,7 +682,7 @@ trait Implicits { self: Typer =>
}

/** Synthesize the tree for `'[T]` for an implicit `scala.quoted.Type[T]`.
* `T` is deeply dealiassed to avoid references to local type aliases.
* `T` is deeply dealiased to avoid references to local type aliases.
*/
lazy val synthesizedTypeTag: SpecialHandler =
(formal, span) => implicit ctx => {
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,6 @@ object ProtoTypes {
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
else new FunProto(args, resultType)(typer, isGivenApply)

override def notApplied: Type = WildcardType

/** @return True if all arguments have types.
*/
def allArgTypesAreCurrent()(implicit ctx: Context): Boolean =
Expand Down Expand Up @@ -453,8 +451,6 @@ object ProtoTypes {
if ((targs eq this.targs) && (resType eq this.resType)) this
else PolyProto(targs, resType)

override def notApplied: Type = WildcardType

def map(tm: TypeMap)(implicit ctx: Context): PolyProto =
derivedPolyProto(targs, tm(resultType))

Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ class Typer extends Namer

def typedBlock(tree: untpd.Block, pt: Type)(implicit ctx: Context): Tree = track("typedBlock") {
val (exprCtx, stats1) = typedBlockStats(tree.stats)
val expr1 = typedExpr(tree.expr, pt.notApplied)(exprCtx)
val expr1 = typedExpr(tree.expr, pt.dropIfProto)(exprCtx)
ensureNoLocalRefs(
cpy.Block(tree)(stats1, expr1).withType(expr1.tpe), pt, localSyms(stats1))
}
Expand Down Expand Up @@ -766,7 +766,7 @@ class Typer extends Namer
}
else {
val thenp1 :: elsep1 :: Nil = harmonic(harmonize, pt)(
(tree.thenp :: tree.elsep :: Nil).map(typed(_, pt.notApplied)))
(tree.thenp :: tree.elsep :: Nil).map(typed(_, pt.dropIfProto)))
assignType(cpy.If(tree)(cond1, thenp1, elsep1), thenp1, elsep1)
}
}
Expand Down Expand Up @@ -1068,7 +1068,7 @@ class Typer extends Namer

// Overridden in InlineTyper for inline matches
def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(implicit ctx: Context): Tree = {
val cases1 = harmonic(harmonize, pt)(typedCases(cases, wideSelType, pt.notApplied))
val cases1 = harmonic(harmonize, pt)(typedCases(cases, wideSelType, pt.dropIfProto))
.asInstanceOf[List[CaseDef]]
assignType(cpy.Match(tree)(sel, cases1), sel, cases1)
}
Expand Down Expand Up @@ -1193,8 +1193,8 @@ class Typer extends Namer

def typedTry(tree: untpd.Try, pt: Type)(implicit ctx: Context): Try = track("typedTry") {
val expr2 :: cases2x = harmonic(harmonize, pt) {
val expr1 = typed(tree.expr, pt.notApplied)
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.notApplied)
val expr1 = typed(tree.expr, pt.dropIfProto)
val cases1 = typedCases(tree.cases, defn.ThrowableType, pt.dropIfProto)
expr1 :: cases1
}
val finalizer1 = typed(tree.finalizer, defn.UnitType)
Expand Down
11 changes: 11 additions & 0 deletions tests/pos/export-opaque.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
object A {

object opaques {
opaque type FlagSet = Long
def FlagSet(bits: Long): FlagSet = bits
}
//type FlagSet = opaques.FlagSet
//def FlagSet(bits: Long): FlagSet = opaques.FlagSet(bits)
export opaques.FlagSet

}
30 changes: 30 additions & 0 deletions tests/pos/implicit-scope.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
object A {

object opaques {
opaque type FlagSet = Long
def FlagSet(bits: Long): FlagSet = bits
def toBits(fs: FlagSet): Long = fs
}
val someFlag = FlagSet(1)
type FlagSet = opaques.FlagSet
def FlagSet(bits: Long): FlagSet = opaques.FlagSet(bits)

delegate FlagOps {
def (xs: FlagSet) bits: Long = opaques.toBits(xs)
def (xs: FlagSet) | (ys: FlagSet): FlagSet = FlagSet(xs.bits | ys.bits)
}
}

object B {
type Variance = A.FlagSet

val f: A.FlagSet = A.someFlag
f.bits // OK

val v: Variance = A.someFlag
v.bits // OK, used to fail with: value bits is not a member of B.Variance

A.someFlag.bits // OK
var x = 0
(if (x > 0) A.someFlag else A.someFlag).bits // OK, used to fail with: value bits is not a member of ?
}
2 changes: 1 addition & 1 deletion tests/run/implicitFuns.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ object Test {

def foo(s: String): Stringly[Int] = 42

(if ("".isEmpty) foo("") else foo("")).apply given ""
//(if ("".isEmpty) foo("") else foo("")).apply given "" // does not typecheck
}
}

Expand Down