diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index 66a75dca0110..d76cde8b15cb 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -8,6 +8,7 @@ import core.Contexts.Context import core.Decorators._ import core.Flags.{JavaDefined, Extension} import core.StdNames.nme +import ast.Trees.mods import annotation.constructorOnly import annotation.internal.sharable import reporting.Reporter diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index fc53e29ad117..a405562dfbce 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -333,6 +333,8 @@ object Trees { def namedType: NamedType = tpe.asInstanceOf[NamedType] } + def (mdef: untpd.DefTree).mods: untpd.Modifiers = mdef.rawMods + abstract class NamedDefTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends NameTree[T] with DefTree[T] { type ThisTree[-T >: Untyped] <: NamedDefTree[T] @@ -1538,6 +1540,7 @@ object Trees { receiver: tpd.Tree, method: TermName, args: List[Tree], targs: List[Type], expectedType: Type)(using parentCtx: Context): tpd.Tree = { given ctx as Context = parentCtx.retractMode(Mode.ImplicitsEnabled) + import dotty.tools.dotc.ast.tpd.TreeOps val typer = ctx.typer val proto = FunProto(args, expectedType) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 11d494ce039d..4f03f721ecdd 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -510,13 +510,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** A repeated argument such as `arg: _*` */ def repeated(arg: Tree)(implicit ctx: Context): Typed = Typed(arg, Ident(tpnme.WILDCARD_STAR)) -// ----- Accessing modifiers ---------------------------------------------------- - - abstract class ModsDecorator { def mods: Modifiers } - - implicit class modsDeco(val mdef: DefTree)(implicit ctx: Context) { - def mods: Modifiers = mdef.rawMods - } // --------- Copier/Transformer/Accumulator classes for untyped trees ----- diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index f66e9f35fae2..12ea35ba1c62 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -337,7 +337,7 @@ class TypeApplications(val self: Type) extends AnyVal { case dealiased: TypeBounds => dealiased.derivedTypeBounds(dealiased.lo.appliedTo(args), dealiased.hi.appliedTo(args)) case dealiased: LazyRef => - LazyRef(c => dealiased.ref(c).appliedTo(args)) + LazyRef(c => dealiased.ref(c).appliedTo(args)(using c)) case dealiased: WildcardType => WildcardType(dealiased.optBounds.orElse(TypeBounds.empty).appliedTo(args).bounds) case dealiased: TypeRef if dealiased.symbol == defn.NothingClass => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 031658e62409..fb050ee95ca9 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4862,7 +4862,7 @@ object Types { } } - abstract class TypeMap(implicit protected val mapCtx: Context) + abstract class TypeMap(implicit protected var mapCtx: Context) extends VariantTraversal with (Type => Type) { thisMap => protected def stopAtStatic: Boolean = true @@ -4979,7 +4979,16 @@ object Types { derivedSuperType(tp, this(thistp), this(supertp)) case tp: LazyRef => - LazyRef(_ => this(tp.ref)) + LazyRef { c => + val ref1 = tp.ref(using c) + if c.runId == mapCtx.runId then this(ref1) + else // splice in new run into map context + val saved = mapCtx + mapCtx = mapCtx.fresh + .setPeriod(Period(c.runId, mapCtx.phaseId)) + .setRun(c.run) + try this(ref1) finally mapCtx = saved + } case tp: ClassInfo => mapClassInfo(tp) @@ -5453,14 +5462,15 @@ object Types { def apply(x: Unit, tp: Type): Unit = foldOver(p(tp), tp) } + class TypeHashSet extends util.HashSet[Type](64): + override def hash(x: Type): Int = System.identityHashCode(x) + override def isEqual(x: Type, y: Type) = x.eq(y) + class NamedPartsAccumulator(p: NamedType => Boolean, excludeLowerBounds: Boolean = false) (implicit ctx: Context) extends TypeAccumulator[mutable.Set[NamedType]] { override def stopAtStatic: Boolean = false def maybeAdd(x: mutable.Set[NamedType], tp: NamedType): mutable.Set[NamedType] = if (p(tp)) x += tp else x - val seen: util.HashSet[Type] = new util.HashSet[Type](64) { - override def hash(x: Type): Int = System.identityHashCode(x) - override def isEqual(x: Type, y: Type) = x.eq(y) - } + val seen = TypeHashSet() def apply(x: mutable.Set[NamedType], tp: Type): mutable.Set[NamedType] = if (seen contains tp) x else { diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 19e24764867e..f10e6b737be0 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -279,7 +279,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } protected def toTextCore[T >: Untyped](tree: Tree[T]): Text = { - import untpd.{modsDeco => _, _} + import untpd._ def isLocalThis(tree: Tree) = tree.typeOpt match { case tp: ThisType => tp.cls == ctx.owner.enclosingClass @@ -647,7 +647,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } override def toText[T >: Untyped](tree: Tree[T]): Text = controlled { - import untpd.{modsDeco => _, _} + import untpd._ var txt = toTextCore(tree) @@ -722,11 +722,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } - /** Print modifiers from symbols if tree has type, overriding the untpd behavior. */ - private implicit def modsDeco(mdef: untpd.DefTree): untpd.ModsDecorator = - new untpd.ModsDecorator { - def mods = if (mdef.hasType) Modifiers(mdef.symbol) else mdef.rawMods - } + /** Print modifiers from symbols if tree has type, overriding the behavior in Trees. */ + def (mdef: untpd.DefTree).mods: untpd.Modifiers = + if mdef.hasType then Modifiers(mdef.symbol) else mdef.rawMods private def Modifiers(sym: Symbol): Modifiers = untpd.Modifiers( sym.flags & (if (sym.isType) ModifierFlags | VarianceFlags else ModifierFlags), @@ -770,7 +768,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { vparamss.foldLeft(leading)((txt, params) => txt ~ paramsText(params)) protected def valDefToText[T >: Untyped](tree: ValDef[T]): Text = { - import untpd.{modsDeco => _} + import untpd._ dclTextOr(tree) { modText(tree.mods, tree.symbol, keywordStr(if (tree.mods.is(Mutable)) "var" else "val"), isType = false) ~~ valDefText(nameIdText(tree)) ~ optAscription(tree.tpt) ~ @@ -784,7 +782,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { ~ toText(params, ", ") ~ ")" protected def defDefToText[T >: Untyped](tree: DefDef[T]): Text = { - import untpd.{modsDeco => _} + import untpd._ dclTextOr(tree) { val defKeyword = modText(tree.mods, tree.symbol, keywordStr("def"), isType = false) val isExtension = tree.hasType && tree.symbol.is(Extension) diff --git a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala index 29026bd4d175..29a2b8765dfb 100644 --- a/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala +++ b/compiler/src/dotty/tools/dotc/semanticdb/ExtractSemanticDB.scala @@ -5,6 +5,7 @@ package semanticdb import core._ import Phases._ import ast.tpd._ +import ast.Trees.mods import Contexts._ import Symbols._ import Flags._ diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index e66fd73ac8b1..ae424aee4319 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1111,7 +1111,6 @@ trait Checking { * 2. Check that case class `enum` cases do not extend java.lang.Enum. */ def checkEnum(cdef: untpd.TypeDef, cls: Symbol, firstParent: Symbol)(using Context): Unit = { - import untpd.modsDeco def isEnumAnonCls = cls.isAnonymousClass && cls.owner.isTerm && diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 9e687787cd95..3da72a6c680e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -31,6 +31,7 @@ import transform.TypeUtils._ import Hashable._ import util.{SourceFile, NoSource} import config.{Config, Feature} +import Feature.migrateTo3 import config.Printers.{implicits, implicitsDetailed} import collection.mutable import reporting.trace @@ -153,7 +154,7 @@ object Implicits { // // We keep the old behavior under -source 3.0-migration. val isFunctionInS2 = - Feature.migrateTo3 + migrateTo3 && tpw.derivesFrom(defn.FunctionClass(1)) && ref.symbol != defn.Predef_conforms val isImplicitConversion = tpw.derivesFrom(defn.ConversionClass) @@ -244,7 +245,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}%, %") + implicits.println(i"implicit scope of type $tp = ${companionRefs.toList}%, %") @threadUnsafe lazy val refs: List[ImplicitRef] = { val buf = new mutable.ListBuffer[TermRef] for (companion <- companionRefs) buf ++= companion.implicitMembers @@ -278,7 +279,7 @@ object Implicits { */ override val level: Int = if outerImplicits == null then 1 - else if Feature.migrateTo3(using irefCtx) + else if migrateTo3(using irefCtx) || (irefCtx.owner eq outerImplicits.irefCtx.owner) && (irefCtx.scope eq outerImplicits.irefCtx.scope) && !refs.head.implicitName.is(LazyImplicitName) @@ -473,173 +474,226 @@ object Implicits { import Implicits._ /** Info relating to implicits that is kept for one run */ -trait ImplicitRunInfo { +trait ImplicitRunInfo: self: Run => private val implicitScopeCache = mutable.AnyRefMap[Type, OfTypeImplicits]() private val EmptyTermRefSet = new TermRefSet(using NoContext) - /** The implicit scope of a type `tp`, defined as follows: - * - * The implicit scope of a type `tp` is the smallest set S of object references (i.e. TermRefs - * with Module symbol) such that - * - * - If `tp` is a class reference, S contains a reference to the companion object of the class, - * if it exists, as well as the implicit scopes of all of `tp`'s parent class references. - * - If `tp` is an opaque type alias `p.A` of type `tp'`, S contains a reference to an object `A` defined in the - * same scope as the opaque type, if it exists, as well as the implicit scope of `tp'`. - * - If `tp` is a reference `p.T` to a class or opaque type alias, S also contains all object references - * on the prefix path `p`. Under Scala-2 mode, package objects of package references on `p` also - * count towards the implicit scope. - * - If `tp` is a (non-opaque) alias of `tp'`, S contains the implicit scope of `tp'`. - * - If `tp` is a singleton type, S contains the implicit scope of its underlying type. - * - If `tp` is some other type, its implicit scope is the union of the implicit scopes of - * its parts (parts defined as in the spec). - * - * @param liftingCtx A context to be used when computing the class symbols of - * a type. Types may contain type variables with their instances - * recorded in the current context. To find out the instance of - * a type variable, we need the current context, the current - * runinfo context does not do. - */ - def implicitScope(rootTp: Type, liftingCtx: Context): OfTypeImplicits = { + private def isExcluded(sym: Symbol) = + if migrateTo3 then false else sym.is(Package) || sym.isPackageObject - val seen: mutable.Set[Type] = mutable.Set() - val incomplete: mutable.Set[Type] = mutable.Set() + /** Is `sym` an anchor type for which givens may exist? Anchor types are classes, + * opaque type aliases, match aliases and abstract types, but not type parameters + * or package objects. + */ + private def isAnchor(sym: Symbol) = + sym.isClass && !isExcluded(sym) + || sym.isOpaqueAlias + || sym.is(Deferred, butNot = Param) + || sym.info.isInstanceOf[MatchAlias] - /** Is `sym` an anchor type for which givens may exist? Anchor types are classes, - * opaque type aliases, and abstract types, but not type parameters or package objects. - */ - def isAnchor(sym: Symbol) = - sym.isClass && !sym.is(Package) && (!sym.isPackageObject || Feature.migrateTo3) - || sym.isOpaqueAlias - || sym.is(Deferred, butNot = Param) - - def anchors(tp: Type): List[Type] = tp match { - case tp: NamedType if isAnchor(tp.symbol) => tp :: Nil - case tp: TypeProxy => anchors(tp.superType) - case tp: AndOrType => anchors(tp.tp1) ++ anchors(tp.tp2) - case _ => Nil - } + private def computeIScope(rootTp: Type): OfTypeImplicits = - /** Replace every typeref that does not refer to a class by a conjunction of anchor types - * that has the same implicit scope as the original typeref. The motivation for applying - * this map is that it reduces the total number of types for which we need to - * compute and cache the implicit scope; all variations wrt type parameters or - * abstract types are eliminated. - */ - object liftToAnchors extends TypeMap { - override implicit protected val mapCtx: Context = liftingCtx - override def stopAtStatic = true + object collectParts extends TypeTraverser: - def apply(tp: Type) = tp.widenDealias match { - case tp: TypeRef => - anchors(tp).foldLeft(defn.AnyType: Type)(AndType.make(_, _)) - case tp: TypeVar => - apply(tp.underlying) - case tp: AppliedType if !tp.tycon.typeSymbol.isClass => - def applyArg(arg: Type) = arg match { - case TypeBounds(lo, hi) => AndType.make(lo, hi) - case WildcardType(TypeBounds(lo, hi)) => AndType.make(lo, hi) - case _ => arg - } - tp.args.foldLeft(apply(tp.tycon))((tc, arg) => AndType.make(tc, applyArg(arg))) - case tp: TypeLambda => - apply(tp.resType) - case _ => - mapOver(tp) - } - } + private var provisional: Boolean = _ + private var parts: mutable.LinkedHashSet[Type] = _ + private val partSeen = TypeHashSet() - def collectCompanions(tp: Type): TermRefSet = - trace(i"collectCompanions($tp)", implicitsDetailed) { - record("collectCompanions") - - def iscopeRefs(t: Type): TermRefSet = implicitScopeCache.get(t) match { + def traverse(t: Type) = + if partSeen.contains(t) then () + else if implicitScopeCache.contains(t) then parts += t + else + partSeen.addEntry(t) + t.dealias match + case t: TypeRef => + if isAnchor(t.symbol) then + parts += t + traverse(t.prefix) + else + traverse(t.underlying) + case t: TermRef => + if !isExcluded(t.symbol) then + traverse(t.info) + traverse(t.prefix) + case t: ThisType if t.cls.is(Module) && t.cls.isStaticOwner => + traverse(t.cls.sourceModule.termRef) + case t: ConstantType => + traverse(t.underlying) + case t: TypeParamRef => + traverse(t.underlying) + if ctx.typerState.constraint.contains(t) then provisional = true + case t: TermParamRef => + traverse(t.underlying) + case t => + traverseChildren(t) + + def apply(tp: Type): (collection.Set[Type], Boolean) = + provisional = false + parts = mutable.LinkedHashSet() + partSeen.clear() + traverse(tp) + (parts, provisional) + end collectParts + + val seen = TypeHashSet() + val incomplete = TypeHashSet() + + def collectCompanions(tp: Type, parts: collection.Set[Type]): TermRefSet = + val companions = new TermRefSet + + def iscopeRefs(t: Type): TermRefSet = + implicitScopeCache.get(t) match case Some(is) => is.companionRefs case None => - if (seen contains t) { - incomplete += tp // all references to rootTo will be accounted for in `seen` so we return `EmptySet`. - EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete. - } - else { - seen += t - val is = iscope(t) - if (!implicitScopeCache.contains(t)) incomplete += tp + if seen.contains(t) then + incomplete.addEntry(tp) // all references for `t` will be accounted for in `seen` so we return `EmptySet`. + EmptyTermRefSet // on the other hand, the refs of `tp` are now inaccurate, so `tp` is marked incomplete. + else + seen.addEntry(t) + val is = recur(t) + if !implicitScopeCache.contains(t) then incomplete.addEntry(tp) is.companionRefs - } - } - - val comps = new TermRefSet - def addCompanion(pre: Type, companion: Symbol) = - if (companion.exists && !companion.isAbsent()) comps += TermRef(pre, companion) - - def addPath(pre: Type): Unit = pre.dealias match - case pre: ThisType if pre.cls.is(Module) && pre.cls.isStaticOwner => - addPath(pre.cls.sourceModule.termRef) - case pre: TermRef => - if pre.symbol.is(Package) then - if Feature.migrateTo3 then - addCompanion(pre, pre.member(nme.PACKAGE).symbol) - addPath(pre.prefix) - else if !pre.symbol.isPackageObject || Feature.migrateTo3 then - comps += pre + end iscopeRefs + + def addCompanion(pre: Type, companion: Symbol) = + if companion.exists && !companion.isAbsent() then + companions += TermRef(pre, companion) + + def addCompanions(t: Type) = implicitScopeCache.get(t) match + case Some(iscope) => + companions ++= iscope.companionRefs + case None => t match + case t: TypeRef => + val sym = t.symbol + val pre = t.prefix + addPath(pre) + addCompanion(pre, + if sym.isClass then sym.companionModule + else pre.member(sym.name.toTermName) + .suchThat(companion => companion.is(Module) && companion.owner == sym.owner) + .symbol) + if sym.isClass then + for p <- t.parents do companions ++= iscopeRefs(p) + else + companions ++= iscopeRefs(t.underlying) + end addCompanions + + def addPath(pre: Type): Unit = pre.dealias match + case pre: ThisType if pre.cls.is(Module) && pre.cls.isStaticOwner => + addPath(pre.cls.sourceModule.termRef) + case pre: TermRef if !isExcluded(pre.symbol) => + pre.info match + case info: SingletonType => + addPath(info) + case info: TypeRef if info.symbol.is(Module) => + addCompanion(info.prefix, info.symbol.sourceModule) + addPath(info.prefix) + case _ => + companions += pre addPath(pre.prefix) - case _ => - - tp.widenDealias match { - case tp: TypeRef => - val sym = tp.symbol - if (isAnchor(sym)) { - val pre = tp.prefix - addPath(pre) - if (sym.isClass) addCompanion(pre, sym.companionModule) - else addCompanion(pre, - pre.member(sym.name.toTermName) - .suchThat(companion => companion.is(Module) && companion.owner == sym.owner) - .symbol) - } - val superAnchors = if (sym.isClass) tp.parents else anchors(tp.superType) - for (anchor <- superAnchors) comps ++= iscopeRefs(anchor) - case tp => - for (part <- tp.namedPartsWith(_.isType)) comps ++= iscopeRefs(part) - } - comps - } + case _ => - /** The implicit scope of type `tp` - * @param isLifted Type `tp` is the result of a `liftToAnchors` application - */ - def iscope(tp: Type, isLifted: Boolean = false): OfTypeImplicits = { - val canCache = Config.cacheImplicitScopes && tp.hash != NotCached && !tp.isProvisional - def computeIScope() = { - val liftedTp = if (isLifted) tp else liftToAnchors(tp) - val refs = - 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)(runContext) - if (canCache && - ((tp eq rootTp) || // first type traversed is always cached - !incomplete.contains(tp))) // other types are cached if they are not incomplete - implicitScopeCache(tp) = result - result - } - if (canCache) implicitScopeCache.getOrElse(tp, computeIScope()) - else computeIScope() - } + parts.foreach(addCompanions) + companions + end collectCompanions + + def recur(tp: Type): OfTypeImplicits = + val (parts, provisional) = collectParts(tp) + val companions = collectCompanions(tp, parts) + val result = OfTypeImplicits(tp, companions)(runContext) + if Config.cacheImplicitScopes + && tp.hash != NotCached + && !provisional + && (tp eq rootTp) // first type traversed is always cached + || !incomplete.contains(tp) // other types are cached if they are not incomplete + then implicitScopeCache(tp) = result + result + + record(i"computeIScope") + recur(rootTp) + end computeIScope + + /** The implicit scope of a type `tp`, which is specified by the following definitions. + * + * A reference is an _anchor_ if it refers to an object, a class, a trait, an + * abstract type, an opaque type alias, or a match type alias. References to + * packages and package objects are anchors only under -source:3.0-migration. + * + * The _anchors_ of a type `T` is a set of references defined as follows: + * + * - If `T` is a reference to an anchor, `T` itself plus, if `T` is of the form + * `P#A`, the anchors of `P`. + * - If `T` is an alias of `U`, the anchors of `U`. + * - If `T` is a reference to a type parameter, the union of the anchors of both of its bounds. + * - If `T` is a singleton reference, the anchors of its underlying type, plus, + * if `T` is of the form `(P#x).type`, the anchors of `P`. + * - If `T` is the this-type of a static object, the anchors of a term reference to that object. + * - If `T` is some other type, the union of the anchors of each constituent type of `T`. + * + * The _implicit scope_ of a type `tp` is the smallest set S of term references (i.e. TermRefs) + * such that + * + * - If `T` is a reference to a class, S includes a reference to the companion object + * of the class, if it exists, as well as the implicit scopes of all of `T`'s parent classes. + * - If `T` is a reference to an object, S includes `T` itself as well as + * the implicit scopes of all of `T`'s parent classes. + * - If `T` is a reference to an opaque type alias named `A`, S includes + * a reference to an object `A` defined in the same scope as the type, if it exists, + * as well as the implicit scope of `T`'s underlying type or bounds. + * - If `T` is a reference to an an abstract type or match type alias named `A`, + * S includes a reference to an object `A` defined in the same scope as the type, + * if it exists, as well as the implicit scopes of `T`'s lower and upper bound, + * if present. + * - If `T` is a reference to an anchor of the form `p.A` then S also includes + * all term references on the path `p`. + * - If `T` is some other type, S includes the implicit scopes of all anchors of `T`. + */ + def implicitScope(tp: Type)(using Context): OfTypeImplicits = + object liftToAnchors extends TypeMap: + override def stopAtStatic = true + private val seen = TypeHashSet() - iscope(rootTp) - } + def applyToUnderlying(t: TypeProxy) = + if seen.contains(t) then + WildcardType + else + seen.addEntry(t) + apply( + t.underlying match + case TypeBounds(lo, hi) => + if defn.isBottomTypeAfterErasure(lo) then hi + else AndType.make(lo, hi) + case u => u) + + def apply(t: Type) = t.dealias match + case t: TypeRef => + if isAnchor(t.symbol) then t else applyToUnderlying(t) + case t: TypeVar => apply(t.underlying) + case t: ParamRef => applyToUnderlying(t) + case t: ConstantType => apply(t.underlying) + case t => mapOver(t) + end liftToAnchors + + record(i"implicitScope") + implicitScopeCache.getOrElse(tp, { + val liftedTp = liftToAnchors(tp) + if liftedTp eq tp then + record(i"implicitScope unlifted") + computeIScope(tp) + else + record(i"implicitScope lifted") + val liftedIScope = implicitScopeCache.getOrElse(liftedTp, computeIScope(liftedTp)) + OfTypeImplicits(tp, liftedIScope.companionRefs)(runContext) + }) + end implicitScope protected def reset(): Unit = implicitScopeCache.clear() -} +end ImplicitRunInfo /** The implicit resolution part of type checking */ trait Implicits { self: Typer => @@ -906,9 +960,10 @@ trait Implicits { self: Typer => implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} in ${ctx.typerState}") result case result: SearchFailure if result.isAmbiguous => + //println(i"FAIL for $pt / $argument: $result0") val deepPt = pt.deepenProto if (deepPt ne pt) inferImplicit(deepPt, argument, span) - else if (Feature.migrateTo3 && !ctx.mode.is(Mode.OldOverloadingResolution)) + else if (migrateTo3 && !ctx.mode.is(Mode.OldOverloadingResolution)) inferImplicit(pt, argument, span)(using ctx.addMode(Mode.OldOverloadingResolution)) match { case altResult: SearchSuccess => ctx.migrationWarning( @@ -920,8 +975,10 @@ trait Implicits { self: Typer => } else result case NoMatchingImplicitsFailure => + //println(i"FAIL for $pt / $argument: $result0") SearchFailure(new NoMatchingImplicits(pt, argument, ctx.typerState.constraint)) case _ => + //println(i"FAIL for $pt / $argument: $result0") result0 } // If we are at the outermost implicit search then emit the implicit dictionary, if any. @@ -1104,7 +1161,7 @@ trait Implicits { self: Typer => negateIfNot(tryImplicit(cand, contextual)) match { case fail: SearchFailure => if (fail.isAmbiguous) - if Feature.migrateTo3 then + if migrateTo3 then val result = rank(remaining, found, NoMatchingImplicitsFailure :: rfailures) if (result.isSuccess) warnAmbiguousNegation(fail.reason.asInstanceOf[AmbiguousImplicits]) @@ -1153,28 +1210,27 @@ trait Implicits { self: Typer => |Consider using the scala.implicits.Not class to implement similar functionality.""", ctx.source.atSpan(span)) - /** A relation that imfluences the order in which implicits are tried. + /** A relation that influences the order in which implicits are tried. * We prefer (in order of importance) * 1. more deeply nested definitions - * 2. definitions in subclasses - * 3. definitions with fewer implicit parameters - * The reason for (3) is that we want to fail fast if the search type + * 2. definitions with fewer implicit parameters + * 3. definitions in subclasses + * The reason for (2) is that we want to fail fast if the search type * is underconstrained. So we look for "small" goals first, because that * will give an ambiguity quickly. */ - def prefer(cand1: Candidate, cand2: Candidate): Boolean = { + def prefer(cand1: Candidate, cand2: Candidate): Boolean = val level1 = cand1.level val level2 = cand2.level - if (level1 > level2) return true - if (level1 < level2) return false + if level1 > level2 then return true + if level1 < level2 then return false val sym1 = cand1.ref.symbol val sym2 = cand2.ref.symbol val arity1 = sym1.info.firstParamTypes.length val arity2 = sym2.info.firstParamTypes.length - if (arity1 < arity2) return true - if (arity1 > arity2) return false + if arity1 < arity2 then return true + if arity1 > arity2 then return false compareOwner(sym1, sym2) == 1 - } /** Sort list of implicit references according to `prefer`. * This is just an optimization that aims at reducing the average @@ -1246,7 +1302,7 @@ trait Implicits { self: Typer => } } - def implicitScope(tp: Type): OfTypeImplicits = ctx.run.implicitScope(tp, ctx) + def implicitScope(tp: Type): OfTypeImplicits = ctx.run.implicitScope(tp) /** All available implicits, without ranking */ def allImplicits: Set[TermRef] = { diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 7a284d0de03c..117e28f2b9b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -153,7 +153,7 @@ trait ImportSuggestions: val alreadyAvailableCandidates: Set[Symbol] = { val wildProto = wildApprox(pt) val contextualCandidates = ctx.implicits.eligible(wildProto) - val implicitScopeCandidates = ctx.run.implicitScope(wildProto, ctx).eligible + val implicitScopeCandidates = ctx.run.implicitScope(wildProto).eligible val allCandidates = contextualCandidates ++ implicitScopeCandidates allCandidates.map(_.implicitRef.underlyingRef.symbol).toSet } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0c326cfc3735..3802bea63f2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -854,7 +854,7 @@ class Namer { typer: Typer => protected def addAnnotations(sym: Symbol): Unit = original match { case original: untpd.MemberDef => lazy val annotCtx = annotContext(original, sym) - for (annotTree <- untpd.modsDeco(original).mods.annotations) { + for (annotTree <- original.mods.annotations) { val cls = typedAheadAnnotationClass(annotTree)(annotCtx) if (cls eq sym) ctx.error("An annotation class cannot be annotated with iself", annotTree.sourcePos) diff --git a/compiler/src/dotty/tools/dotc/typer/Nullables.scala b/compiler/src/dotty/tools/dotc/typer/Nullables.scala index 4834f3aa016d..3be662346197 100644 --- a/compiler/src/dotty/tools/dotc/typer/Nullables.scala +++ b/compiler/src/dotty/tools/dotc/typer/Nullables.scala @@ -14,6 +14,7 @@ import NullOpsDecorator._ import collection.mutable import config.Printers.nullables import ast.{tpd, untpd} +import ast.Trees.mods /** Operations for implementing a flow analysis for nullability */ object Nullables: diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b89d878e03c1..4f514a2bcf5a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1832,7 +1832,7 @@ class Typer extends Namer sym.annotations.foreach(_.ensureCompleted) lazy val annotCtx = annotContext(mdef, sym) // necessary in order to mark the typed ahead annotations as definitely typed: - for (annot <- untpd.modsDeco(mdef).mods.annotations) + for (annot <- mdef.mods.annotations) checkAnnotApplicable(typedAnnotation(annot)(using annotCtx), sym) } diff --git a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala index 97bd20e8d5a4..ef7264033a04 100644 --- a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala +++ b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala @@ -5,7 +5,7 @@ package parsing import org.junit.Test import org.junit.Assert._ -import ast.untpd.modsDeco +import ast.Trees.mods import ast.untpd._ import ast.{ Trees => d } import Parsers.Parser diff --git a/docs/docs/reference/changed-features/implicit-resolution.md b/docs/docs/reference/changed-features/implicit-resolution.md index bead6e6a01bb..6732e7e706d5 100644 --- a/docs/docs/reference/changed-features/implicit-resolution.md +++ b/docs/docs/reference/changed-features/implicit-resolution.md @@ -52,6 +52,37 @@ Both `a` and `b` are visible as implicits at the point of the definition of `type C`. However, a reference to `p.o.C` outside of package `p` will have only `b` in its implicit search scope but not `a`. +In more detail, here are the rules for what constitutes the implicit scope of +a type: + +**Definition:** A reference is an _anchor_ if it refers to an object, a class, a trait, an abstract type, an opaque type alias, or a match type alias. References to packages and package objects are anchors only under -source:3.0-migration. + +**Definition:** The _anchors_ of a type _T_ is a set of references defined as follows: + + 1. If _T_ is a reference to an anchor, _T_ itself plus, if _T_ is of the form _P#A_, the anchors of _P_. + 1. If _T_ is an alias of _U_, the anchors of _U_. + 1. If _T_ is a reference to a type parameter, the union of the anchors of both of its bounds. + 1. If _T_ is a singleton reference, the anchors of its underlying type, plus, + if _T_ is of the form _(P#x).type_, the anchors of _P_. + 1. If _T_ is the this-type _o.this_ of a static object _o_, the anchors of a term reference _o.type_ to that object. + 1. If _T_ is some other type, the union of the anchors of each constituent type of _T_. + + **Definition:** The _implicit scope_ of a type _T_ is the smallest set _S_ of term references such that + + 1. If _T_ is a reference to a class, _S_ includes a reference to the companion object + of the class, if it exists, as well as the implicit scopes of all of _T_'s parent classes. + 1. If _T_ is a reference to an object, _S_ includes _T_ itself as well as + the implicit scopes of all of _T_'s parent classes. + 1. If _T_ is a reference to an opaque type alias named _A_, _S_ includes + a reference to an object _A_ defined in the same scope as the type, if it exists, + as well as the implicit scope of _T_'s underlying type or bounds. + 1. If _T_ is a reference to an an abstract type or match type alias + named _A_, _S_ includes a reference to an object _A_ defined in the same scope as the type, if it exists, as well as the implicit scopes of _T_'s given bounds. + 1. If _T_ is a reference to an anchor of the form _p.A_ then _S_ also includes + all term references on the path _p_. + 1. If _T_ is some other type, _S_ includes the implicit scopes of all anchors of _T_. + + **4.** The treatment of ambiguity errors has changed. If an ambiguity is encountered in some recursive step of an implicit search, the ambiguity is propagated to the caller. Example: Say you have the following definitions: diff --git a/tests/pos/i9103.scala b/tests/pos/i9103.scala new file mode 100644 index 000000000000..03772227537d --- /dev/null +++ b/tests/pos/i9103.scala @@ -0,0 +1,28 @@ +object a: + type Foo[T] + given as Foo[Unit] = ??? + +val b = a + +def test1 = summon[b.Foo[Unit]] // no ambiguity between b.given_Foo and a.given_Foo + +val n: Long = 1 +val total: BigInt = 2 +val remainder = n % identity(total) // object BigInt is in implicit scope of `total.type` + +trait Show[T] {def show(a: T): String} + +object S extends LowPriorityInstances { + class Permissions +} + +sealed trait LowPriorityInstances +object LowPriorityInstances { + given S.Permissions = ??? + given Show[S.Permissions] = _ => "perms" +} + +def test = + println(implicitly[S.Permissions]) // companions of base classes of S are in implicit scope + println(implicitly[Show[S.Permissions]]) +