From 5bc95a0c6d4433b41e739ba7a0720be44a2d59cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 11:43:58 +0200 Subject: [PATCH 1/7] Widen implied objects to their parent types If we have a situation like this: ``` type A type B <: A implied a for A implied b for B ``` we should recognize `b` to be more specific than `a`. Before this commit that was not the case, since `a: A$`, `b: B$` and `B$` is not a subtype of `A$`. --- .../dotty/tools/dotc/transform/TypeUtils.scala | 5 +++++ .../dotty/tools/dotc/typer/Applications.scala | 17 +++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala index ee6938b78248..2f34a1005031 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -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. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index cc9500d395ab..98c587671de5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -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} @@ -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)) @@ -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 @@ -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 } From 0f7e9395ce4902ce8a7ec8a60d90a16c650738a1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 11:45:41 +0200 Subject: [PATCH 2/7] Implement `for` clauses for implied imports --- .../dotty/tools/dotc/core/Denotations.scala | 4 + .../src/dotty/tools/dotc/core/Flags.scala | 2 + .../tools/dotc/core/tasty/TastyFormat.scala | 7 +- .../tools/dotc/core/tasty/TreePickler.scala | 4 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 5 ++ .../dotty/tools/dotc/parsing/Parsers.scala | 82 ++++++++++--------- .../tools/dotc/printing/RefinedPrinter.scala | 3 +- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../dotty/tools/dotc/typer/ImportInfo.scala | 65 ++++++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 21 +++-- docs/docs/internals/syntax.md | 7 +- tests/run/implied-for.scala | 21 +++++ 12 files changed, 151 insertions(+), 72 deletions(-) create mode 100644 tests/run/implied-for.scala diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 1dc99bb8550f..e992a53a4d1b 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -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) @@ -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 = { diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index cceebe50b219..9223e06d4724 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -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 */ diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 3bb382967280..cf3352ac6c4c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -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* @@ -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 @@ -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 @@ -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 @@ -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" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index a0413437eea8..dbb24b42076c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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 = { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index aa3993ff9b01..1a0b052682d2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ddd9e2a6cb20..73e0356d95ba 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -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()) } @@ -2310,8 +2307,50 @@ object Parsers { */ def importExpr(importImplied: Boolean, mkTree: ImportConstr): () => Tree = { + /** ImportSelectors ::= `{' {ImportSelector `,'} FinalSelector ‘}’ + * FinalSelector ::= ImportSelector + * | ‘_’ + * | ‘for’ 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()) { TypeBoundsTree(EmptyTree, infixType()) } :: 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 } @@ -2333,41 +2372,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() diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 21612a1e32bb..5f614501edeb 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98c587671de5..c9b930b37bbb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1353,7 +1353,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } def prepare(tp: Type) = tp.stripTypeVar match { case tp: NamedType if tp.symbol.is(Module) && tp.symbol.sourceModule.is(Implied) => - flip(tp.widen.widenToParents + flip(tp.widen.widenToParents) case _ => flip(tp) } (prepare(tp1) relaxed_<:< prepare(tp2)) || viewExists(tp1, tp2) diff --git a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala index 7518c49e234e..68cc74b86908 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -10,6 +10,7 @@ import util.SimpleIdentityMap import Symbols._, Names._, Types._, Contexts._, StdNames._, Flags._ import Implicits.RenamedImplicitRef import printing.Texts.Text +import Decorators._ object ImportInfo { /** The import info for a root import from given symbol `sym` */ @@ -55,39 +56,41 @@ 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 => @@ -95,6 +98,20 @@ class ImportInfo(symf: Context => Symbol, val selectors: List[untpd.Tree], 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 @@ -102,15 +119,23 @@ class ImportInfo(symf: Context => Symbol, val selectors: List[untpd.Tree], /** 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 || (ref <:< impliedBound)) 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 @@ -149,7 +174,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 985dd512fa15..3e1b2e6ccc39 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -177,7 +177,7 @@ class Typer extends Namer previous } - def selection(imp: ImportInfo, name: Name) = + def selection(imp: ImportInfo, name: Name, checkBounds: Boolean) = if (imp.sym.isCompleting) { ctx.warning(i"cyclic ${imp.sym}, ignored", posd.sourcePos) NoType @@ -188,7 +188,10 @@ class Typer extends Namer var reqd = required var excl = EmptyFlags if (imp.importImplied) reqd |= Implied else excl |= Implied - val denot = pre.memberBasedOnFlags(name, reqd, excl).accessibleFrom(pre)(refctx) + var denot = pre.memberBasedOnFlags(name, reqd, excl).accessibleFrom(pre)(refctx) + if (checkBounds && imp.impliedBound.exists) + denot = denot.filterWithPredicate(_.info <:< imp.impliedBound) + // Pass refctx so that any errors are reported in the context of the // reference instead of the if (reallyExists(denot)) pre.select(name, denot) else NoType @@ -209,11 +212,10 @@ class Typer extends Namer } def unambiguousSelection(name: Name) = - checkUnambiguous(selection(imp, name)) + checkUnambiguous(selection(imp, name, checkBounds = false)) selector match { - case Thicket(fromId :: Ident(Name) :: _) => - val Ident(from) = fromId + case Thicket(Ident(from) :: Ident(Name) :: _) => unambiguousSelection(if (name.isTypeName) from.toTypeName else from) case Ident(Name) => unambiguousSelection(name) @@ -231,7 +233,7 @@ class Typer extends Namer */ def wildImportRef(imp: ImportInfo)(implicit ctx: Context): Type = if (imp.isWildcardImport && !imp.excluded.contains(name.toTermName) && name != nme.CONSTRUCTOR) - selection(imp, name) + selection(imp, name, checkBounds = imp.importImplied) else NoType /** Is (some alternative of) the given predenotation `denot` @@ -1785,7 +1787,12 @@ class Typer extends Namer def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkLegalImportPath(expr1) - assignType(cpy.Import(imp)(imp.importImplied, expr1, imp.selectors), sym) + val selectors1: List[untpd.Tree] = imp.selectors map { + case sel @ TypeBoundsTree(_, tpt) => + untpd.cpy.TypeBoundsTree(sel)(sel.lo, untpd.TypedSplice(typedType(tpt))) + case sel => sel + } + assignType(cpy.Import(imp)(imp.importImplied, expr1, selectors1), sym) } def typedPackageDef(tree: untpd.PackageDef)(implicit ctx: Context): Tree = track("typedPackageDef") { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index a8a02c5fe03e..edb1c0ef147f 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -335,8 +335,11 @@ Annotation ::= ‘@’ SimpleType {ParArgumentExprs} Import ::= ‘import’ [‘implied’] ImportExpr {‘,’ ImportExpr} ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors) Import(expr, sels) -ImportSelectors ::= ‘{’ {ImportSelector ‘,’} (ImportSelector | ‘_’) ‘}’ -ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’] Ident(name), Pair(id, id) +ImportSelectors ::= ‘{’ {ImportSelector ‘,’} FinalSelector ‘}’ +FinalSelector ::= ImportSelector Ident(name) + | ‘_’ Pair(id, id) + | ‘for’ InfixType TypeBoundsTree(EmptyTree, tpt) +ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’] Export ::= ‘export’ [‘implied’] ImportExpr {‘,’ ImportExpr} ``` diff --git a/tests/run/implied-for.scala b/tests/run/implied-for.scala new file mode 100644 index 000000000000..9642b024d51b --- /dev/null +++ b/tests/run/implied-for.scala @@ -0,0 +1,21 @@ +trait T + +object A { + + class B extends T + class C extends T + + implied b for B + implied c for C + implied d for T +} + +object Test extends App { + import A._ + import implied A.{d, for B} + + println(d) + val x: B = b + + assert(the[T].isInstanceOf[B]) +} \ No newline at end of file From 0e401cad0ac0e7e1d07ffa53c8307194fa8cc5b9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 12:04:24 +0200 Subject: [PATCH 3/7] Allow several `for` type-bounds In a `for` clause of an implied imports, allow several bounds, separated by commas. Writing ``` import a.b.{for C, D} ``` is equivalent to ``` import a.b.{for C | D} ``` but feels more natural. --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 11 +++++++++-- docs/docs/internals/syntax.md | 2 +- tests/run/implied-for.scala | 12 ++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 73e0356d95ba..a7603acd963b 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2310,7 +2310,7 @@ object Parsers { /** ImportSelectors ::= `{' {ImportSelector `,'} FinalSelector ‘}’ * FinalSelector ::= ImportSelector * | ‘_’ - * | ‘for’ InfixType + * | ‘for’ InfixType {‘,’ InfixType} */ def importSelectors(): List[Tree] = in.token match { case USCORE => @@ -2318,7 +2318,14 @@ object Parsers { case FOR => if (!importImplied) syntaxError(em"`for` qualifier only allowed in `import implied`") - atSpan(in.skipToken()) { TypeBoundsTree(EmptyTree, infixType()) } :: Nil + 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) { diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index edb1c0ef147f..278dea2d4d41 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -338,7 +338,7 @@ ImportExpr ::= StableId ‘.’ (id | ‘_’ | ImportSelectors) ImportSelectors ::= ‘{’ {ImportSelector ‘,’} FinalSelector ‘}’ FinalSelector ::= ImportSelector Ident(name) | ‘_’ Pair(id, id) - | ‘for’ InfixType TypeBoundsTree(EmptyTree, tpt) + | ‘for’ InfixType {‘,’ InfixType} TypeBoundsTree(EmptyTree, tpt) ImportSelector ::= id [‘=>’ id | ‘=>’ ‘_’] Export ::= ‘export’ [‘implied’] ImportExpr {‘,’ ImportExpr} ``` diff --git a/tests/run/implied-for.scala b/tests/run/implied-for.scala index 9642b024d51b..a5abd92cfdd9 100644 --- a/tests/run/implied-for.scala +++ b/tests/run/implied-for.scala @@ -4,18 +4,22 @@ object A { class B extends T class C extends T + class D implied b for B implied c for C - implied d for T + implied t for T + implied d for D } object Test extends App { import A._ - import implied A.{d, for B} + import implied A.{t, for B, D} - println(d) - val x: B = b + val x1: B = b + val x2: T = t + val x3: D = d assert(the[T].isInstanceOf[B]) + assert(the[D].isInstanceOf[D]) } \ No newline at end of file From c8ad8ecd90c7a9408dd23a8b437e198d443b39cc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 12:40:40 +0200 Subject: [PATCH 4/7] Add docs Explain by-type imports on the doc page. Also, add a test for wildcards in bounds. --- .../reference/contextual/import-implied.md | 30 +++++++++++++++++++ tests/run/implied-for.scala | 10 +++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/docs/reference/contextual/import-implied.md b/docs/docs/reference/contextual/import-implied.md index edf7f77dbcbd..56c3d0b90344 100644 --- a/docs/docs/reference/contextual/import-implied.md +++ b/docs/docs/reference/contextual/import-implied.md @@ -8,6 +8,7 @@ A special form of import is used to import implied instances. Example: object A { class TC implied tc for TC + implied def f given TC = ??? } object B { @@ -28,6 +29,35 @@ There are two main benefits arising from these rules: instances can be anonymous, so the usual recourse of using named imports is not practical. +### Importing By Type + +Since implied instances can be anonymous it is not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it clearer what is imported. Example: + +```scala +import implied A.{for TC} +``` +This imports any implied instance in `A` that has a type which conforms tp `TC`. There can be several bounding types following a `for` and bounding types can contain wildcards. +For instance, assuming the object +```scala +object Instances { + implied intOrd for Ordering[Int] + implied [T: Ordering] listOrd for Ordering[List[T]] + implied ec for ExecutionContext = ... + implied im for Monoid[Int] +} +``` +the import +``` +import implied Instances.{for Ordering[_], ExecutionContext} +``` +would import the `intOrd`, `listOrd`, and `ec` instances but leave out the `im` instance, since it fits none of the specified bounds. + +By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, the import clause +``` +import implied Instances.{im, for Ordering[_]} +``` +would import `im`, `intOrd`, and `listOrd` but leave out `ec`. By-type imports cannot be mixed with a wildcard import in the same import clause. + ### Migration The rules for `import implied` above have the consequence that a library diff --git a/tests/run/implied-for.scala b/tests/run/implied-for.scala index a5abd92cfdd9..44ea11cd00af 100644 --- a/tests/run/implied-for.scala +++ b/tests/run/implied-for.scala @@ -4,22 +4,22 @@ object A { class B extends T class C extends T - class D + class D[T] implied b for B implied c for C implied t for T - implied d for D + implied d for D[Int] } object Test extends App { import A._ - import implied A.{t, for B, D} + import implied A.{t, for B, D[_]} val x1: B = b val x2: T = t - val x3: D = d + val x3: D[Int] = d assert(the[T].isInstanceOf[B]) - assert(the[D].isInstanceOf[D]) + assert(the[D[Int]].isInstanceOf[D[_]]) } \ No newline at end of file From f5b7c971e1fcfa0cf528a68f4283b791b8bb9b83 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 13:15:59 +0200 Subject: [PATCH 5/7] Fix handling of parameterized implicits in import bounds Need to use normalizedCompatible instead of `<:<`. --- .../dotty/tools/dotc/typer/ImportInfo.scala | 4 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 3 +- tests/run/implied-for.scala | 34 ++++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala index 68cc74b86908..678f4f8c0791 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportInfo.scala @@ -10,6 +10,7 @@ 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 { @@ -127,7 +128,8 @@ class ImportInfo(symf: Context => Symbol, val selectors: List[untpd.Tree], val renamed = forwardMapping(ref.name) if (renamed == ref.name) ref :: Nil else if (renamed != null) new RenamedImplicitRef(ref, renamed) :: Nil - else if (!impliedBound.exists || (ref <:< impliedBound)) ref :: Nil + else if (!impliedBound.exists || + normalizedCompatible(ref, impliedBound, keepConstraint = false)) ref :: Nil else Nil } } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3e1b2e6ccc39..feec8de3ad4f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -190,7 +190,8 @@ class Typer extends Namer if (imp.importImplied) reqd |= Implied else excl |= Implied var denot = pre.memberBasedOnFlags(name, reqd, excl).accessibleFrom(pre)(refctx) if (checkBounds && imp.impliedBound.exists) - denot = denot.filterWithPredicate(_.info <:< imp.impliedBound) + denot = denot.filterWithPredicate(mbr => + NoViewsAllowed.normalizedCompatible(mbr.info, imp.impliedBound, keepConstraint = false)) // Pass refctx so that any errors are reported in the context of the // reference instead of the diff --git a/tests/run/implied-for.scala b/tests/run/implied-for.scala index 44ea11cd00af..aa16e3f65d8f 100644 --- a/tests/run/implied-for.scala +++ b/tests/run/implied-for.scala @@ -22,4 +22,36 @@ object Test extends App { assert(the[T].isInstanceOf[B]) assert(the[D[Int]].isInstanceOf[D[_]]) -} \ No newline at end of file +} + +class Ordering[T] +class ExecutionContext +class Monoid[T] + +object Instances { + implied intOrd for Ordering[Int] + implied listOrd[T] for Ordering[List[T]] given Ordering[T] + implied ec for ExecutionContext + implied im for Monoid[Int] +} + +object Test2 { + import implied Instances.{for Ordering[_], ExecutionContext} + val x = intOrd + val y = listOrd[Int] + val z = ec + the[Ordering[Int]] + the[Ordering[List[Int]]] + the[ExecutionContext] +} + +object Test3 { + import implied Instances.{for Ordering[_]} + val x = intOrd + val y = listOrd[Int] + val z = im + the[Ordering[Int]] + the[Ordering[List[Int]]] + the[Monoid[Int]] +} + From 4a488c85904370081e8f7a4adfbce5ca7b2fc26c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Jun 2019 13:18:29 +0200 Subject: [PATCH 6/7] Fix typos --- docs/docs/reference/contextual/import-implied.md | 1 - tests/run/implied-for.scala | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs/reference/contextual/import-implied.md b/docs/docs/reference/contextual/import-implied.md index 56c3d0b90344..137384baf7a7 100644 --- a/docs/docs/reference/contextual/import-implied.md +++ b/docs/docs/reference/contextual/import-implied.md @@ -8,7 +8,6 @@ A special form of import is used to import implied instances. Example: object A { class TC implied tc for TC - implied def f given TC = ??? } object B { diff --git a/tests/run/implied-for.scala b/tests/run/implied-for.scala index aa16e3f65d8f..9b4486ec33a7 100644 --- a/tests/run/implied-for.scala +++ b/tests/run/implied-for.scala @@ -46,7 +46,7 @@ object Test2 { } object Test3 { - import implied Instances.{for Ordering[_]} + import implied Instances.{im, for Ordering[_]} val x = intOrd val y = listOrd[Int] val z = im From 498f0810c9f31dca3778721ca76cd9ac458dedb4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 Jun 2019 22:30:52 +0200 Subject: [PATCH 7/7] Add neg test --- tests/neg/implied-for.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/neg/implied-for.scala diff --git a/tests/neg/implied-for.scala b/tests/neg/implied-for.scala new file mode 100644 index 000000000000..a5aa18bb59ef --- /dev/null +++ b/tests/neg/implied-for.scala @@ -0,0 +1,19 @@ +trait T +class B extends T +class C extends T + +object A { + implied b for B + implied c for C +} + +object Test extends App { + import A._ + import implied A.{for B} + + val x: B = b // OK + println(c) // error: not found + + the[C] // error + +} \ No newline at end of file