Skip to content

Implement for clauses for implied imports #6594

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 7 commits into from
Jun 3, 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
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ object Denotations {
*/
def suchThat(p: Symbol => Boolean)(implicit ctx: Context): SingleDenotation

override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation

/** If this is a SingleDenotation, return it, otherwise throw a TypeError */
def checkUnique(implicit ctx: Context): SingleDenotation = suchThat(alwaysTrue)

Expand Down Expand Up @@ -1253,6 +1255,8 @@ object Denotations {
else sd1
else sd2
}
override def filterWithPredicate(p: SingleDenotation => Boolean): Denotation =
derivedUnionDenotation(denot1.filterWithPredicate(p), denot2.filterWithPredicate(p))
def hasAltWith(p: SingleDenotation => Boolean): Boolean =
denot1.hasAltWith(p) || denot2.hasAltWith(p)
def accessibleFrom(pre: Type, superAccess: Boolean)(implicit ctx: Context): Denotation = {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,8 @@ object Flags {
final val ImplicitOrImpliedOrGiven = Implicit | Implied | Given
final val ImplicitOrGiven = Implicit | Given

final val ImpliedOrGiven = Implied | Given

final val ImplicitOrImpliedOrGivenTerm = ImplicitOrImpliedOrGiven.toTermFlags

/** Flags retained in export forwarders */
Expand Down
7 changes: 5 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Standard-Section: "ASTs" TopLevelStat*
Modifier* -- modifiers def name [typeparams] paramss : returnType (= rhs)?
Selector = IMPORTED name_NameRef -- name
RENAMED to_NameRef -- => name
BOUNDED type_Term? -- for type
TypeParam = TYPEPARAM Length NameRef type_Term Modifier* -- modifiers name bounds
Params = PARAMS Length Param*
Expand All @@ -80,7 +81,7 @@ Standard-Section: "ASTs" TopLevelStat*
APPLY Length fn_Term arg_Term* -- fn(args)
TYPEAPPLY Length fn_Term arg_Type* -- fn[args]
SUPER Length this_Term mixinTypeIdent_Tree? -- super[mixin]
TYPED Length expr_Term ascriptionType_Tern -- expr: ascription
TYPED Length expr_Term ascriptionType_Term -- expr: ascription
ASSIGN Length lhs_Term rhs_Term -- lhs = rhs
BLOCK Length expr_Term Stat* -- { stats; expr }
INLINED Length expr_Term call_Term? ValOrDefDef* -- Inlined code from call, with given body `expr` and given bindings
Expand Down Expand Up @@ -369,6 +370,7 @@ object TastyFormat {
final val RECtype = 91
final val TYPEALIAS = 92
final val SINGLETONtpt = 93
final val BOUNDED = 94

// Cat. 4: tag Nat AST

Expand Down Expand Up @@ -461,7 +463,7 @@ object TastyFormat {
def isLegalTag(tag: Int): Boolean =
firstSimpleTreeTag <= tag && tag <= EXPORTED ||
firstNatTreeTag <= tag && tag <= SYMBOLconst ||
firstASTTreeTag <= tag && tag <= SINGLETONtpt ||
firstASTTreeTag <= tag && tag <= BOUNDED ||
firstNatASTTreeTag <= tag && tag <= NAMEDARG ||
firstLengthTreeTag <= tag && tag <= MATCHtpt ||
tag == HOLE
Expand Down Expand Up @@ -601,6 +603,7 @@ object TastyFormat {
case PARAM => "PARAM"
case IMPORTED => "IMPORTED"
case RENAMED => "RENAMED"
case BOUNDED => "BOUNDED"
case APPLY => "APPLY"
case TYPEAPPLY => "TYPEAPPLY"
case NEW => "NEW"
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,10 @@ class TreePickler(pickler: TastyPickler) {
pickleSelector(RENAMED, to)
case id @ Ident(_) =>
pickleSelector(IMPORTED, id)
case bounded @ TypeBoundsTree(untpd.EmptyTree, untpd.TypedSplice(tpt)) =>
registerTreeAddr(bounded)
writeByte(BOUNDED)
pickleTree(tpt)
}

def pickleSelector(tag: Int, id: untpd.Ident)(implicit ctx: Context): Unit = {
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,11 @@ class TreeUnpickler(reader: TastyReader,
case _ =>
from :: readSelectors()
}
case BOUNDED =>
val start = currentAddr
readByte()
val bounded = setSpan(start, untpd.TypeBoundsTree(untpd.EmptyTree, untpd.TypedSplice(readTpt())))
bounded :: readSelectors()
case _ =>
Nil
}
Expand Down
89 changes: 50 additions & 39 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -627,9 +627,6 @@ object Parsers {
def wildcardIdent(): Ident =
atSpan(accept(USCORE)) { Ident(nme.WILDCARD) }

def termIdentOrWildcard(): Ident =
if (in.token == USCORE) wildcardIdent() else termIdent()

/** Accept identifier acting as a selector on given tree `t`. */
def selector(t: Tree): Tree =
atSpan(startOffset(t), in.offset) { Select(t, ident()) }
Expand Down Expand Up @@ -2310,8 +2307,57 @@ object Parsers {
*/
def importExpr(importImplied: Boolean, mkTree: ImportConstr): () => Tree = {

/** ImportSelectors ::= `{' {ImportSelector `,'} FinalSelector ‘}’
* FinalSelector ::= ImportSelector
* | ‘_’
* | ‘for’ InfixType {‘,’ InfixType}
*/
def importSelectors(): List[Tree] = in.token match {
case USCORE =>
wildcardIdent() :: Nil
case FOR =>
if (!importImplied)
syntaxError(em"`for` qualifier only allowed in `import implied`")
atSpan(in.skipToken()) {
var t = infixType()
while (in.token == COMMA) {
val op = atSpan(in.skipToken()) { Ident(tpnme.raw.BAR) }
t = InfixOp(t, op, infixType())
}
TypeBoundsTree(EmptyTree, t)
} :: Nil
case _ =>
importSelector() :: {
if (in.token == COMMA) {
in.nextToken()
importSelectors()
}
else Nil
}
}

/** ImportSelector ::= id [`=>' id | `=>' `_']
*/
def importSelector(): Tree = {
val from = termIdent()
if (in.token == ARROW)
atSpan(startOffset(from), in.skipToken()) {
val start = in.offset
val to = if (in.token == USCORE) wildcardIdent() else termIdent()
val toWithPos =
if (to.name == nme.ERROR)
// error identifiers don't consume any characters, so atSpan(start)(id) wouldn't set a span.
// Some testcases would then fail in Positioned.checkPos. Set a span anyway!
atSpan(start, start, in.lastOffset)(to)
else
to
Thicket(from, toWithPos)
}
else from
}

val handleImport: Tree => Tree = { tree: Tree =>
if (in.token == USCORE) mkTree(importImplied, tree, importSelector() :: Nil)
if (in.token == USCORE) mkTree(importImplied, tree, wildcardIdent() :: Nil)
else if (in.token == LBRACE) mkTree(importImplied, tree, inBraces(importSelectors()))
else tree
}
Expand All @@ -2333,41 +2379,6 @@ object Parsers {
}
}

/** ImportSelectors ::= `{' {ImportSelector `,'} (ImportSelector | `_') `}'
*/
def importSelectors(): List[Tree] = {
val sel = importSelector()
if (in.token == RBRACE) sel :: Nil
else {
sel :: {
if (!isWildcardArg(sel) && in.token == COMMA) {
in.nextToken()
importSelectors()
}
else Nil
}
}
}

/** ImportSelector ::= id [`=>' id | `=>' `_']
*/
def importSelector(): Tree = {
val from = termIdentOrWildcard()
if (from.name != nme.WILDCARD && in.token == ARROW)
atSpan(startOffset(from), in.skipToken()) {
val start = in.offset
val to = termIdentOrWildcard()
val toWithPos =
if (to.name == nme.ERROR)
// error identifiers don't consume any characters, so atSpan(start)(id) wouldn't set a span.
// Some testcases would then fail in Positioned.checkPos. Set a span anyway!
atSpan(start, start, in.lastOffset)(to)
else
to
Thicket(from, toWithPos)
}
else from
}

def posMods(start: Int, mods: Modifiers): Modifiers = {
in.nextToken()
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case Import(importImplied, expr, selectors) =>
def selectorText(sel: Tree): Text = sel match {
case Thicket(l :: r :: Nil) => toTextGlobal(l) ~ " => " ~ toTextGlobal(r)
case _ => toTextGlobal(sel)
case _: Ident => toTextGlobal(sel)
case TypeBoundsTree(_, tpt) => "for " ~ toTextGlobal(tpt)
}
val selectorsText: Text = selectors match {
case id :: Nil => toText(id)
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ object TypeUtils {
case _ => if (ctx.erasedTypes) MethodType(Nil, self) else ExprType(self)
}

def widenToParents(implicit ctx: Context): Type = self.parents match {
case Nil => self
case ps => ps.reduceLeft(AndType(_, _))
}

/** The arity of this tuple type, which can be made up of Unit, TupleX and `*:` pairs,
* or -1 if this is not a tuple type.
*/
Expand Down
17 changes: 11 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import StdNames._
import NameKinds.DefaultGetterName
import ProtoTypes._
import Inferencing._
import transform.TypeUtils._

import collection.mutable
import config.Printers.{overload, typr, unapp}
Expand Down Expand Up @@ -1332,6 +1333,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
* iff
*
* T => R <:s U => R
*
* Also: If a compared type refers to an implied object or its module class, use
* the intersection of its parent classes instead.
*/
def isAsSpecificValueType(tp1: Type, tp2: Type)(implicit ctx: Context) =
if (ctx.mode.is(Mode.OldOverloadingResolution))
Expand All @@ -1347,7 +1351,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case _ => mapOver(t)
}
}
(flip(tp1) relaxed_<:< flip(tp2)) || viewExists(tp1, tp2)
def prepare(tp: Type) = tp.stripTypeVar match {
case tp: NamedType if tp.symbol.is(Module) && tp.symbol.sourceModule.is(Implied) =>
flip(tp.widen.widenToParents)
case _ => flip(tp)
}
(prepare(tp1) relaxed_<:< prepare(tp2)) || viewExists(tp1, tp2)
}

/** Widen the result type of synthetic implied methods from the implementation class to the
Expand Down Expand Up @@ -1375,11 +1384,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic =>
case pt: PolyType =>
pt.derivedLambdaType(pt.paramNames, pt.paramInfos, widenImplied(pt.resultType, alt))
case _ =>
if (alt.symbol.is(SyntheticImpliedMethod))
tp.parents match {
case Nil => tp
case ps => ps.reduceLeft(AndType(_, _))
}
if (alt.symbol.is(SyntheticImpliedMethod)) tp.widenToParents
else tp
}

Expand Down
67 changes: 47 additions & 20 deletions compiler/src/dotty/tools/dotc/typer/ImportInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import util.SimpleIdentityMap
import Symbols._, Names._, Types._, Contexts._, StdNames._, Flags._
import Implicits.RenamedImplicitRef
import printing.Texts.Text
import ProtoTypes.NoViewsAllowed.normalizedCompatible
import Decorators._

object ImportInfo {
/** The import info for a root import from given symbol `sym` */
Expand Down Expand Up @@ -55,62 +57,87 @@ class ImportInfo(symf: Context => Symbol, val selectors: List[untpd.Tree],
/** The names that are excluded from any wildcard import */
def excluded: Set[TermName] = { ensureInitialized(); myExcluded }

/** A mapping from renamed to original names */
def reverseMapping: SimpleIdentityMap[TermName, TermName] = { ensureInitialized(); myMapped }
/** A mapping from original to renamed names */
def forwardMapping: SimpleIdentityMap[TermName, TermName] = { ensureInitialized(); myForwardMapping }

/** The original names imported by-name before renaming */
def originals: Set[TermName] = { ensureInitialized(); myOriginals }
/** A mapping from renamed to original names */
def reverseMapping: SimpleIdentityMap[TermName, TermName] = { ensureInitialized(); myReverseMapping }

/** Does the import clause end with wildcard? */
def isWildcardImport: Boolean = { ensureInitialized(); myWildcardImport }

private[this] var myExcluded: Set[TermName] = null
private[this] var myMapped: SimpleIdentityMap[TermName, TermName] = null
private[this] var myOriginals: Set[TermName] = null
private[this] var myForwardMapping: SimpleIdentityMap[TermName, TermName] = null
private[this] var myReverseMapping: SimpleIdentityMap[TermName, TermName] = null
private[this] var myWildcardImport: Boolean = false

/** Compute info relating to the selector list */
private def ensureInitialized(): Unit = if (myExcluded == null) {
myExcluded = Set()
myMapped = SimpleIdentityMap.Empty
myOriginals = Set()
myForwardMapping = SimpleIdentityMap.Empty
myReverseMapping = SimpleIdentityMap.Empty
def recur(sels: List[untpd.Tree]): Unit = sels match {
case sel :: sels1 =>
sel match {
case Thicket(Ident(name: TermName) :: Ident(nme.WILDCARD) :: Nil) =>
myExcluded += name
case Thicket(Ident(from: TermName) :: Ident(to: TermName) :: Nil) =>
myMapped = myMapped.updated(to, from)
myForwardMapping = myForwardMapping.updated(from, to)
myReverseMapping = myReverseMapping.updated(to, from)
myExcluded += from
myOriginals += from
case Ident(nme.WILDCARD) =>
myWildcardImport = true
case Ident(name: TermName) =>
myMapped = myMapped.updated(name, name)
myOriginals += name
myForwardMapping = myForwardMapping.updated(name, name)
myReverseMapping = myReverseMapping.updated(name, name)
case TypeBoundsTree(_, tpt) =>
myWildcardImport = true // details are handled separately in impliedBounds
}
recur(sels1)
case nil =>
}
recur(selectors)
}

private[this] var myImpliedBound: Type = null

def impliedBound(implicit ctx: Context): Type = {
if (myImpliedBound == null)
myImpliedBound = selectors.lastOption match {
case Some(TypeBoundsTree(_, untpd.TypedSplice(tpt))) => tpt.tpe
case Some(TypeBoundsTree(_, tpt)) =>
myImpliedBound = NoType
ctx.typer.typedAheadType(tpt).tpe
case _ => NoType
}
myImpliedBound
}

private def implicitFlag(implicit ctx: Context) =
if (importImplied || ctx.mode.is(Mode.FindHiddenImplicits)) ImplicitOrImpliedOrGiven
else Implicit

/** The implicit references imported by this import clause */
def importedImplicits(implicit ctx: Context): List[ImplicitRef] = {
val pre = site
if (isWildcardImport) {
val refs = pre.implicitMembers(implicitFlag)
if (excluded.isEmpty) refs
else refs filterNot (ref => excluded contains ref.name.toTermName)
} else
for {
if (isWildcardImport)
pre.implicitMembers(implicitFlag).flatMap { ref =>
val name = ref.name.toTermName
if (excluded.contains(name)) Nil
else {
val renamed = forwardMapping(ref.name)
if (renamed == ref.name) ref :: Nil
else if (renamed != null) new RenamedImplicitRef(ref, renamed) :: Nil
else if (!impliedBound.exists ||
normalizedCompatible(ref, impliedBound, keepConstraint = false)) ref :: Nil
else Nil
}
}
else
for
renamed <- reverseMapping.keys
denot <- pre.member(reverseMapping(renamed)).altsWith(_ is implicitFlag)
} yield {
yield {
val original = reverseMapping(renamed)
val ref = TermRef(pre, original, denot)
if (renamed == original) ref
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice for syntax 👍

Copy link
Member

Choose a reason for hiding this comment

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

Hum ... It doesn't look like valid Scala code to me, actually :-s Maybe with the indentation-based syntax, but that shouldn't be enabled there? Is the parser too lenient?

Expand Down Expand Up @@ -149,7 +176,7 @@ class ImportInfo(symf: Context => Symbol, val selectors: List[untpd.Tree],
def featureImported(feature: TermName, owner: Symbol)(implicit ctx: Context): Boolean = {
def compute = {
val isImportOwner = site.widen.typeSymbol.eq(owner)
if (isImportOwner && originals.contains(feature)) true
if (isImportOwner && forwardMapping.contains(feature)) true
else if (isImportOwner && excluded.contains(feature)) false
else {
var c = ctx.outer
Expand Down
Loading