All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.ast.tpd.scala Maven / Gradle / Ivy

There is a newer version: 3.6.4-RC1-bin-20241220-0bfa1af-NIGHTLY
Show newest version
package dotty.tools
package dotc
package ast

import dotty.tools.dotc.transform.{ExplicitOuter, Erasure}
import typer.ProtoTypes
import core.*
import Scopes.newScope
import util.Spans.*, Types.*, Contexts.*, Constants.*, Names.*, Flags.*, NameOps.*
import Symbols.*, StdNames.*, Annotations.*, Trees.*, Symbols.*
import Decorators.*, DenotTransformers.*
import collection.{immutable, mutable}
import util.{Property, SourceFile}
import config.Printers.typr
import NameKinds.{TempResultName, OuterSelectName}
import typer.ConstFold

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import scala.compiletime.uninitialized

/** Some creators for typed trees */
object tpd extends Trees.Instance[Type] with TypedTreeInfo {

  private def ta(using Context) = ctx.typeAssigner

  def Ident(tp: NamedType)(using Context): Ident =
    ta.assignType(untpd.Ident(tp.name), tp)

  def Select(qualifier: Tree, name: Name)(using Context): Select =
    ta.assignType(untpd.Select(qualifier, name), qualifier)

  def Select(qualifier: Tree, tp: NamedType)(using Context): Select =
    untpd.Select(qualifier, tp.name).withType(tp)

  def This(cls: ClassSymbol)(using Context): This =
    untpd.This(untpd.Ident(cls.name)).withType(cls.thisType)

  def Super(qual: Tree, mix: untpd.Ident, mixinClass: Symbol)(using Context): Super =
    ta.assignType(untpd.Super(qual, mix), qual, mixinClass)

  def Super(qual: Tree, mixName: TypeName, mixinClass: Symbol = NoSymbol)(using Context): Super =
    Super(qual, if (mixName.isEmpty) untpd.EmptyTypeIdent else untpd.Ident(mixName), mixinClass)

  def Apply(fn: Tree, args: List[Tree])(using Context): Apply = fn match
    case Block(Nil, expr) =>
      Apply(expr, args)
    case _: RefTree | _: GenericApply | _: Inlined | _: Hole =>
      ta.assignType(untpd.Apply(fn, args), fn, args)
    case _ =>
      assert(ctx.reporter.errorsReported || ctx.tolerateErrorsForBestEffort)
      ta.assignType(untpd.Apply(fn, args), fn, args)

  def TypeApply(fn: Tree, args: List[Tree])(using Context): TypeApply = fn match
    case Block(Nil, expr) =>
      TypeApply(expr, args)
    case _: RefTree | _: GenericApply =>
      ta.assignType(untpd.TypeApply(fn, args), fn, args)
    case _ =>
      assert(ctx.reporter.errorsReported || ctx.tolerateErrorsForBestEffort, s"unexpected tree for type application: $fn")
      ta.assignType(untpd.TypeApply(fn, args), fn, args)

  def Literal(const: Constant)(using Context): Literal =
    ta.assignType(untpd.Literal(const))

  def unitLiteral(using Context): Literal =
    Literal(Constant(())).withAttachment(SyntheticUnit, ())

  def nullLiteral(using Context): Literal =
    Literal(Constant(null))

  def New(tpt: Tree)(using Context): New =
    ta.assignType(untpd.New(tpt), tpt)

  def New(tp: Type)(using Context): New = New(TypeTree(tp))

  def Typed(expr: Tree, tpt: Tree)(using Context): Typed =
    ta.assignType(untpd.Typed(expr, tpt), tpt)

  def NamedArg(name: Name, arg: Tree)(using Context): NamedArg =
    ta.assignType(untpd.NamedArg(name, arg), arg)

  def Assign(lhs: Tree, rhs: Tree)(using Context): Assign =
    ta.assignType(untpd.Assign(lhs, rhs))

  def Block(stats: List[Tree], expr: Tree)(using Context): Block =
    ta.assignType(untpd.Block(stats, expr), stats, expr)

  /** Join `stats` in front of `expr` creating a new block if necessary */
  def seq(stats: List[Tree], expr: Tree)(using Context): Tree =
    if (stats.isEmpty) expr
    else expr match {
      case Block(_, _: Closure) =>
        Block(stats, expr)  // leave closures in their own block
      case Block(estats, eexpr) =>
        cpy.Block(expr)(stats ::: estats, eexpr).withType(ta.avoidingType(eexpr, stats))
      case _ =>
        Block(stats, expr)
    }

  def If(cond: Tree, thenp: Tree, elsep: Tree)(using Context): If =
    ta.assignType(untpd.If(cond, thenp, elsep), thenp, elsep)

  def InlineIf(cond: Tree, thenp: Tree, elsep: Tree)(using Context): If =
    ta.assignType(untpd.InlineIf(cond, thenp, elsep), thenp, elsep)

  def Closure(env: List[Tree], meth: Tree, tpt: Tree)(using Context): Closure =
    ta.assignType(untpd.Closure(env, meth, tpt), meth, tpt)

  /** A function def
   *
   *    vparams => expr
   *
   *  gets expanded to
   *
   *    { def $anonfun(vparams) = expr; Closure($anonfun) }
   *
   *  where the closure's type is the target type of the expression (FunctionN, unless
   *  otherwise specified).
   */
  def Closure(meth: TermSymbol, rhsFn: List[List[Tree]] => Tree, targs: List[Tree] = Nil, targetType: Type = NoType)(using Context): Block = {
    val targetTpt = if (targetType.exists) TypeTree(targetType, inferred = true) else EmptyTree
    val call =
      if (targs.isEmpty) Ident(TermRef(NoPrefix, meth))
      else TypeApply(Ident(TermRef(NoPrefix, meth)), targs)
    var mdef0 = DefDef(meth, rhsFn)
    val mdef = cpy.DefDef(mdef0)(tpt = TypeTree(mdef0.tpt.tpe, inferred = true))
    Block(mdef :: Nil, Closure(Nil, call, targetTpt))
  }

  /** A closure whose anonymous function has the given method type */
  def Lambda(tpe: MethodType, rhsFn: List[Tree] => Tree)(using Context): Block = {
    val meth = newAnonFun(ctx.owner, tpe)
    Closure(meth, tss => rhsFn(tss.head).changeOwner(ctx.owner, meth))
  }

  def CaseDef(pat: Tree, guard: Tree, body: Tree)(using Context): CaseDef =
    ta.assignType(untpd.CaseDef(pat, guard, body), pat, body)

  def Match(selector: Tree, cases: List[CaseDef])(using Context): Match =
    ta.assignType(untpd.Match(selector, cases), selector, cases)

  def InlineMatch(selector: Tree, cases: List[CaseDef])(using Context): Match =
    ta.assignType(untpd.InlineMatch(selector, cases), selector, cases)

  def Labeled(bind: Bind, expr: Tree)(using Context): Labeled =
    ta.assignType(untpd.Labeled(bind, expr))

  def Labeled(sym: TermSymbol, expr: Tree)(using Context): Labeled =
    Labeled(Bind(sym, EmptyTree), expr)

  def Return(expr: Tree, from: Tree)(using Context): Return =
    ta.assignType(untpd.Return(expr, from))

  def Return(expr: Tree, from: Symbol)(using Context): Return =
    Return(expr, Ident(from.termRef))

  def WhileDo(cond: Tree, body: Tree)(using Context): WhileDo =
    ta.assignType(untpd.WhileDo(cond, body))

  def Try(block: Tree, cases: List[CaseDef], finalizer: Tree)(using Context): Try =
    ta.assignType(untpd.Try(block, cases, finalizer), block, cases)

  def SeqLiteral(elems: List[Tree], elemtpt: Tree)(using Context): SeqLiteral =
    ta.assignType(untpd.SeqLiteral(elems, elemtpt), elems, elemtpt)

  def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(using Context): JavaSeqLiteral =
    ta.assignType(untpd.JavaSeqLiteral(elems, elemtpt), elems, elemtpt).asInstanceOf[JavaSeqLiteral]

  def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
    ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion)

  def Quote(body: Tree, tags: List[Tree])(using Context): Quote =
    untpd.Quote(body, tags).withBodyType(body.tpe)

  def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree, proto: Type)(using Context): QuotePattern =
    ta.assignType(untpd.QuotePattern(bindings, body, quotes), proto)

  def Splice(expr: Tree, tpe: Type)(using Context): Splice =
    untpd.Splice(expr).withType(tpe)

  def Splice(expr: Tree)(using Context): Splice =
    ta.assignType(untpd.Splice(expr), expr)

  def SplicePattern(pat: Tree, targs: List[Tree], args: List[Tree], tpe: Type)(using Context): SplicePattern =
    untpd.SplicePattern(pat, targs, args).withType(tpe)

  def Hole(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpe: Type)(using Context): Hole =
    untpd.Hole(isTerm, idx, args, content).withType(tpe)

  def TypeTree(tp: Type, inferred: Boolean = false)(using Context): TypeTree =
    (if inferred then untpd.InferredTypeTree() else untpd.TypeTree()).withType(tp)

  def SingletonTypeTree(ref: Tree)(using Context): SingletonTypeTree =
    ta.assignType(untpd.SingletonTypeTree(ref), ref)

  def RefinedTypeTree(parent: Tree, refinements: List[Tree], refineCls: ClassSymbol)(using Context): Tree =
    ta.assignType(untpd.RefinedTypeTree(parent, refinements), parent, refinements, refineCls)

  def AppliedTypeTree(tycon: Tree, args: List[Tree])(using Context): AppliedTypeTree =
    ta.assignType(untpd.AppliedTypeTree(tycon, args), tycon, args)

  def ByNameTypeTree(result: Tree)(using Context): ByNameTypeTree =
    ta.assignType(untpd.ByNameTypeTree(result), result)

  def LambdaTypeTree(tparams: List[TypeDef], body: Tree)(using Context): LambdaTypeTree =
    ta.assignType(untpd.LambdaTypeTree(tparams, body), tparams, body)

  def MatchTypeTree(bound: Tree, selector: Tree, cases: List[CaseDef])(using Context): MatchTypeTree =
    ta.assignType(untpd.MatchTypeTree(bound, selector, cases), bound, selector, cases)

  def TypeBoundsTree(lo: Tree, hi: Tree, alias: Tree = EmptyTree)(using Context): TypeBoundsTree =
    ta.assignType(untpd.TypeBoundsTree(lo, hi, alias), lo, hi, alias)

  def Bind(sym: Symbol, body: Tree)(using Context): Bind =
    ta.assignType(untpd.Bind(sym.name, body), sym)

  /** A pattern corresponding to `sym: tpe` */
  def BindTyped(sym: TermSymbol, tpe: Type)(using Context): Bind =
    Bind(sym, Typed(Underscore(tpe), TypeTree(tpe)))

  def Alternative(trees: List[Tree])(using Context): Alternative =
    ta.assignType(untpd.Alternative(trees), trees)

  def UnApply(fun: Tree, implicits: List[Tree], patterns: List[Tree], proto: Type)(using Context): UnApply = {
    assert(fun.isInstanceOf[RefTree] || fun.isInstanceOf[GenericApply])
    ta.assignType(untpd.UnApply(fun, implicits, patterns), proto)
  }

  def ValDef(sym: TermSymbol, rhs: LazyTree = EmptyTree, inferred: Boolean = false)(using Context): ValDef =
    ta.assignType(untpd.ValDef(sym.name, TypeTree(sym.info, inferred), rhs), sym)

  def SyntheticValDef(name: TermName, rhs: Tree, flags: FlagSet = EmptyFlags)(using Context): ValDef =
    ValDef(newSymbol(ctx.owner, name, Synthetic | flags, rhs.tpe.widen, coord = rhs.span), rhs)

  def DefDef(sym: TermSymbol, paramss: List[List[Symbol]],
             resultType: Type, rhs: Tree)(using Context): DefDef =
    sym.setParamss(paramss)
    ta.assignType(
      untpd.DefDef(
        sym.name,
        paramss.map {
          case TypeSymbols(params) => params.map(param => TypeDef(param).withSpan(param.span))
          case TermSymbols(params) => params.map(param => ValDef(param).withSpan(param.span))
          case _ => unreachable()
        },
        TypeTree(resultType),
        rhs),
      sym)

  def DefDef(sym: TermSymbol, rhs: Tree = EmptyTree)(using Context): DefDef =
    ta.assignType(DefDef(sym, Function.const(rhs) _), sym)

  /** A DefDef with given method symbol `sym`.
   *  @rhsFn  A function from parameter references
   *          to the method's right-hand side.
   *  Parameter symbols are taken from the `rawParamss` field of `sym`, or
   *  are freshly generated if `rawParamss` is empty.
   */
  def DefDef(sym: TermSymbol, rhsFn: List[List[Tree]] => Tree)(using Context): DefDef =

    // Map method type `tp` with remaining parameters stored in rawParamss to
    // final result type and all (given or synthesized) parameters
    def recur(tp: Type, remaining: List[List[Symbol]]): (Type, List[List[Symbol]]) = tp match
      case tp: PolyType =>
        val (tparams: List[TypeSymbol], remaining1) = remaining match
          case tparams :: remaining1 =>
            assert(tparams.hasSameLengthAs(tp.paramNames) && tparams.head.isType)
            (tparams.asInstanceOf[List[TypeSymbol]], remaining1)
          case nil =>
            (newTypeParams(sym, tp.paramNames, EmptyFlags, tp.instantiateParamInfos(_)), Nil)
        val (rtp, paramss) = recur(tp.instantiate(tparams.map(_.typeRef)), remaining1)
        (rtp, tparams :: paramss)
      case tp: MethodType =>
        val isParamDependent = tp.isParamDependent
        val previousParamRefs: ListBuffer[TermRef] =
          // It is ok to assign `null` here.
          // If `isParamDependent == false`, the value of `previousParamRefs` is not used.
          if isParamDependent then mutable.ListBuffer[TermRef]() else (null: ListBuffer[TermRef] | Null).uncheckedNN

        def valueParam(name: TermName, origInfo: Type, isErased: Boolean): TermSymbol =
          val maybeImplicit =
            if tp.isContextualMethod then Given
            else if tp.isImplicitMethod then Implicit
            else EmptyFlags
          val maybeErased = if isErased then Erased else EmptyFlags

          def makeSym(info: Type) = newSymbol(sym, name, TermParam | maybeImplicit | maybeErased, info, coord = sym.coord)

          if isParamDependent then
            val sym = makeSym(origInfo.substParams(tp, previousParamRefs.toList))
            previousParamRefs += sym.termRef
            sym
          else makeSym(origInfo)
        end valueParam

        val (vparams: List[TermSymbol], remaining1) =
          if tp.paramNames.isEmpty then (Nil, remaining)
          else remaining match
            case vparams :: remaining1 =>
              assert(vparams.hasSameLengthAs(tp.paramNames) && vparams.head.isTerm)
              (vparams.asInstanceOf[List[TermSymbol]], remaining1)
            case nil =>
              (tp.paramNames.lazyZip(tp.paramInfos).lazyZip(tp.erasedParams).map(valueParam), Nil)
        val (rtp, paramss) = recur(tp.instantiate(vparams.map(_.termRef)), remaining1)
        (rtp, vparams :: paramss)
      case _ =>
        assert(remaining.isEmpty)
        (tp.widenExpr, Nil)
    end recur

    val (rtp, paramss) = recur(sym.info, sym.rawParamss)
    DefDef(sym, paramss, rtp, rhsFn(paramss.nestedMap(ref)))
  end DefDef

  def TypeDef(sym: TypeSymbol)(using Context): TypeDef =
    ta.assignType(untpd.TypeDef(sym.name, TypeTree(sym.info)), sym)

  /** Create a class definition
   *  @param cls          the class symbol of the created class
   *  @param constr       its primary constructor
   *  @param body         the statements in its template
   *  @param superArgs    the arguments to pass to the superclass constructor
   *  @param adaptVarargs if true, allow matching a vararg superclass constructor
   *                      with a missing argument in superArgs, and synthesize an
   *                      empty repeated parameter in the supercall in this case
   */
  def ClassDef(cls: ClassSymbol, constr: DefDef, body: List[Tree],
      superArgs: List[Tree] = Nil, adaptVarargs: Boolean = false)(using Context): TypeDef =
    val firstParent :: otherParents = cls.info.parents: @unchecked

    def adaptedSuperArgs(ctpe: Type): List[Tree] = ctpe match
      case ctpe: PolyType =>
        adaptedSuperArgs(ctpe.instantiate(firstParent.argTypes))
      case ctpe: MethodType
      if ctpe.paramInfos.length == superArgs.length + 1 =>
        // last argument must be a vararg, otherwise isApplicable would have failed
        superArgs :+
          repeated(Nil, TypeTree(ctpe.paramInfos.last.argInfos.head, inferred = true))
      case _ =>
        superArgs

    val superRef =
      if cls.is(Trait) then TypeTree(firstParent)
      else
        val parentConstr = firstParent.applicableConstructors(superArgs.tpes, adaptVarargs) match
          case Nil => assert(false, i"no applicable parent constructor of $firstParent for supercall arguments $superArgs")
          case constr :: Nil => constr
          case _ => assert(false, i"multiple applicable parent constructors of $firstParent for supercall arguments $superArgs")
        New(firstParent, parentConstr.asTerm, adaptedSuperArgs(parentConstr.info))

    ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body)
  end ClassDef

  def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(using Context): TypeDef = {
    val selfType =
      if (cls.classInfo.selfInfo ne NoType) ValDef(newSelfSym(cls))
      else EmptyValDef
    def isOwnTypeParam(stat: Tree) =
      stat.symbol.is(TypeParam) && stat.symbol.owner == cls
    val bodyTypeParams = body filter isOwnTypeParam map (_.symbol)
    val newTypeParams =
      for (tparam <- cls.typeParams if !(bodyTypeParams contains tparam))
      yield TypeDef(tparam)
    val findLocalDummy = FindLocalDummyAccumulator(cls)
    val localDummy = body.foldLeft(NoSymbol: Symbol)(findLocalDummy.apply)
      .orElse(newLocalDummy(cls))
    val impl = untpd.Template(constr, parents, Nil, selfType, newTypeParams ++ body)
      .withType(localDummy.termRef)
    ta.assignType(untpd.TypeDef(cls.name, impl), cls)
  }

  /** An anonymous class
   *
   *      new parents { termForwarders; typeAliases }
   *
   *  @param parents        a non-empty list of class types
   *  @param termForwarders a non-empty list of forwarding definitions specified by their name and the definition they forward to.
   *  @param typeMembers    a possibly-empty list of type members specified by their name and their right hand side.
   *  @param adaptVarargs   if true, allow matching a vararg superclass constructor
   *                        with a missing argument in superArgs, and synthesize an
   *                        empty repeated parameter in the supercall in this case
   *
   *  The class has the same owner as the first function in `termForwarders`.
   *  Its position is the union of all symbols in `termForwarders`.
   */
  def AnonClass(parents: List[Type],
      termForwarders: List[(TermName, TermSymbol)],
      typeMembers: List[(TypeName, TypeBounds)],
      adaptVarargs: Boolean)(using Context): Block = {
    AnonClass(termForwarders.head._2.owner, parents, termForwarders.map(_._2.span).reduceLeft(_ union _), adaptVarargs) { cls =>
      def forwarder(name: TermName, fn: TermSymbol) = {
        val fwdMeth = fn.copy(cls, name, Synthetic | Method | Final).entered.asTerm
        for overridden <- fwdMeth.allOverriddenSymbols do
          if overridden.is(Extension) then fwdMeth.setFlag(Extension)
          if !overridden.is(Deferred) then fwdMeth.setFlag(Override)
        DefDef(fwdMeth, ref(fn).appliedToArgss(_))
      }
      termForwarders.map((name, sym) => forwarder(name, sym)) ++
      typeMembers.map((name, info) => TypeDef(newSymbol(cls, name, Synthetic, info).entered))
    }
  }

  /** An anonymous class
   *
   *      new parents { body }
   *
   * with the specified owner and position.
   */
  def AnonClass(owner: Symbol, parents: List[Type], coord: Coord)(body: ClassSymbol => List[Tree])(using Context): Block =
    AnonClass(owner, parents, coord, adaptVarargs = false)(body)

  private def AnonClass(owner: Symbol, parents: List[Type], coord: Coord, adaptVarargs: Boolean)(body: ClassSymbol => List[Tree])(using Context): Block =
    val parents1 =
      if (parents.head.classSymbol.is(Trait)) {
        val head = parents.head.parents.head
        if (head.isRef(defn.AnyClass)) defn.AnyRefType :: parents else head :: parents
      }
      else parents
    val cls = newNormalizedClassSymbol(owner, tpnme.ANON_CLASS, Synthetic | Final, parents1, coord = coord)
    val constr = newConstructor(cls, Synthetic, Nil, Nil).entered
    val cdef = ClassDef(cls, DefDef(constr), body(cls), Nil, adaptVarargs)
    Block(cdef :: Nil, New(cls.typeRef, Nil))

  def Import(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Import =
    ta.assignType(untpd.Import(expr, selectors), newImportSymbol(ctx.owner, expr))

  def Export(expr: Tree, selectors: List[untpd.ImportSelector])(using Context): Export =
    ta.assignType(untpd.Export(expr, selectors))

  def PackageDef(pid: RefTree, stats: List[Tree])(using Context): PackageDef =
    ta.assignType(untpd.PackageDef(pid, stats), pid)

  def Annotated(arg: Tree, annot: Tree)(using Context): Annotated =
    ta.assignType(untpd.Annotated(arg, annot), arg, annot)

  def Throw(expr: Tree)(using Context): Tree =
    ref(defn.throwMethod).appliedTo(expr)

  // ------ Making references ------------------------------------------------------

  def prefixIsElidable(tp: NamedType)(using Context): Boolean = {
    val typeIsElidable = tp.prefix match {
      case pre: ThisType =>
        tp.isType ||
        pre.cls.isStaticOwner ||
        tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && !tp.symbol.owner.is(Trait) && ctx.owner.enclosingClass == pre.cls
          // was ctx.owner.enclosingClass.derivesFrom(pre.cls) which was not tight enough
          // and was spuriously triggered in case inner class would inherit from outer one
          // eg anonymous TypeMap inside TypeMap.andThen
      case pre: TermRef =>
        pre.symbol.is(Module) && pre.symbol.isStatic
      case pre =>
        pre `eq` NoPrefix
    }
    typeIsElidable ||
    tp.symbol.is(JavaStatic) ||
    tp.symbol.hasAnnotation(defn.ScalaStaticAnnot)
  }

  def needsSelect(tp: Type)(using Context): Boolean = tp match {
    case tp: TermRef => !prefixIsElidable(tp)
    case _ => false
  }

  def needsIdent(tp: Type)(using Context): Boolean = tp match
    case tp: TermRef => tp.prefix eq NoPrefix
    case _ => false

  /** A tree representing the same reference as the given type */
  def ref(tp: NamedType, needLoad: Boolean = true)(using Context): Tree =
    if (tp.isType) TypeTree(tp)
    else if (prefixIsElidable(tp)) Ident(tp)
    else if (tp.symbol.is(Module) && ctx.owner.isContainedIn(tp.symbol.moduleClass))
      followOuterLinks(This(tp.symbol.moduleClass.asClass))
    else if (tp.symbol hasAnnotation defn.ScalaStaticAnnot)
      Ident(tp)
    else
      val pre = tp.prefix
      if (pre.isSingleton) followOuterLinks(singleton(pre.dealias, needLoad)).select(tp)
      else
        val res = Select(TypeTree(pre), tp)
        if needLoad && !res.symbol.isStatic then
          throw TypeError(em"cannot establish a reference to $res")
        res

  def ref(sym: Symbol)(using Context): Tree =
    ref(NamedType(sym.owner.thisType, sym.name, sym.denot))

  private def followOuterLinks(t: Tree)(using Context) = t match {
    case t: This if ctx.erasedTypes && !(t.symbol == ctx.owner.enclosingClass || t.symbol.isStaticOwner) =>
      // after erasure outer paths should be respected
      ExplicitOuter.OuterOps(ctx).path(toCls = t.tpe.classSymbol)
    case t =>
      t
  }

  def singleton(tp: Type, needLoad: Boolean = true)(using Context): Tree = tp.dealias match {
    case tp: TermRef => ref(tp, needLoad)
    case tp: ThisType => This(tp.cls)
    case tp: SkolemType => singleton(tp.narrow, needLoad)
    case SuperType(qual, _) => singleton(qual, needLoad)
    case ConstantType(value) => Literal(value)
  }

  /** A tree representing a `newXYZArray` operation of the right
   *  kind for the given element type in `elemTpe`. No type arguments or
   *  `length` arguments are given.
   */
  def newArray(elemTpe: Type, returnTpe: Type, span: Span, dims: JavaSeqLiteral)(using Context): Tree = {
    val elemClass = elemTpe.classSymbol
    def newArr =
      ref(defn.DottyArraysModule).select(defn.newArrayMethod).withSpan(span)

    if (!ctx.erasedTypes) {
      assert(!TypeErasure.isGeneric(elemTpe), elemTpe) //needs to be done during typer. See Applications.convertNewGenericArray
      newArr.appliedToTypeTrees(TypeTree(returnTpe) :: Nil).appliedToTermArgs(clsOf(elemTpe) :: clsOf(returnTpe) :: dims :: Nil).withSpan(span)
    }
    else  // after erasure
      newArr.appliedToTermArgs(clsOf(elemTpe) :: clsOf(returnTpe) :: dims :: Nil).withSpan(span)
  }

  /** The wrapped array method name for an array of type elemtp */
  def wrapArrayMethodName(elemtp: Type)(using Context): TermName = {
    val elemCls = elemtp.classSymbol
    if (elemCls.isPrimitiveValueClass) nme.wrapXArray(elemCls.name)
    else if (elemCls.derivesFrom(defn.ObjectClass) && !elemCls.isNotRuntimeClass) nme.wrapRefArray
    else nme.genericWrapArray
  }

  /** A tree representing a `wrapXYZArray(tree)` operation of the right
   *  kind for the given element type in `elemTpe`.
   */
  def wrapArray(tree: Tree, elemtp: Type)(using Context): Tree =
    val wrapper = ref(defn.getWrapVarargsArrayModule)
      .select(wrapArrayMethodName(elemtp))
      .appliedToTypes(if (elemtp.isPrimitiveValueType) Nil else elemtp :: Nil)
    val actualElem = wrapper.tpe.widen.firstParamTypes.head
    wrapper.appliedTo(tree.ensureConforms(actualElem))

  // ------ Creating typed equivalents of trees that exist only in untyped form -------

  /** new C(args), calling the primary constructor of C */
  def New(tp: Type, args: List[Tree])(using Context): Apply =
    New(tp, tp.dealias.typeSymbol.primaryConstructor.asTerm, args)

  /** new C(args), calling given constructor `constr` of C */
  def New(tp: Type, constr: TermSymbol, args: List[Tree])(using Context): Apply = {
    val targs = tp.argTypes
    val tycon = tp.typeConstructor
    New(tycon)
      .select(TermRef(tycon, constr))
      .appliedToTypes(targs)
      .appliedToTermArgs(args)
  }

  /** An object def
   *
   *     object obs extends parents { decls }
   *
   *  gets expanded to
   *
   *      val obj = new obj$
   *      class obj$ extends parents { this: obj.type => decls }
   *
   *  (The following no longer applies:
   *  What's interesting here is that the block is well typed
   *  (because class obj$ is hoistable), but the type of the `obj` val is
   *  not expressible. What needs to happen in general when
   *  inferring the type of a val from its RHS, is: if the type contains
   *  a class that has the val itself as owner, then that class
   *  is remapped to have the val's owner as owner. Remapping could be
   *  done by cloning the class with the new owner and substituting
   *  everywhere in the tree. We know that remapping is safe
   *  because the only way a local class can appear in the RHS of a val is
   *  by being hoisted outside of a block, and the necessary checks are
   *  done at this point already.
   *
   *  On the other hand, for method result type inference, if the type of
   *  the RHS of a method contains a class owned by the method, this would be
   *  an error.)
   */
  def ModuleDef(sym: TermSymbol, body: List[Tree])(using Context): tpd.Thicket = {
    val modcls = sym.moduleClass.asClass
    val constrSym = modcls.primaryConstructor orElse newDefaultConstructor(modcls).entered
    val constr = DefDef(constrSym.asTerm, EmptyTree)
    val clsdef = ClassDef(modcls, constr, body)
    val valdef = ValDef(sym, New(modcls.typeRef).select(constrSym).appliedToNone)
    Thicket(valdef, clsdef)
  }

  /** A `_` with given type */
  def Underscore(tp: Type)(using Context): Ident = untpd.Ident(nme.WILDCARD).withType(tp)

  def defaultValue(tpe: Type)(using Context): Tree = {
    val tpw = tpe.widen

    if (tpw isRef defn.IntClass) Literal(Constant(0))
    else if (tpw isRef defn.LongClass) Literal(Constant(0L))
    else if (tpw isRef defn.BooleanClass) Literal(Constant(false))
    else if (tpw isRef defn.CharClass) Literal(Constant('\u0000'))
    else if (tpw isRef defn.FloatClass) Literal(Constant(0f))
    else if (tpw isRef defn.DoubleClass) Literal(Constant(0d))
    else if (tpw isRef defn.ByteClass) Literal(Constant(0.toByte))
    else if (tpw isRef defn.ShortClass) Literal(Constant(0.toShort))
    else nullLiteral.select(defn.Any_asInstanceOf).appliedToType(tpe)
  }

  private class FindLocalDummyAccumulator(cls: ClassSymbol)(using Context) extends TreeAccumulator[Symbol] {
    def apply(sym: Symbol, tree: Tree)(using Context) =
      if (sym.exists) sym
      else if (tree.isDef) {
        val owner = tree.symbol.owner
        if (owner.isLocalDummy && owner.owner == cls) owner
        else if (owner == cls) foldOver(sym, tree)
        else sym
      }
      else foldOver(sym, tree)
  }

  /** The owner to be used in a local context when traversing a tree */
  def localOwner(tree: Tree)(using Context): Symbol =
    val sym = tree.symbol
    (if sym.is(PackageVal) then sym.moduleClass else sym).orElse(ctx.owner)

  /** The local context to use when traversing trees */
  def localCtx(tree: Tree)(using Context): Context = ctx.withOwner(localOwner(tree))

  override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none)
    TypedTreeCopier()

  val cpyBetweenPhases: TimeTravellingTreeCopier = TimeTravellingTreeCopier()

  class TypedTreeCopier extends TreeCopier {
    def postProcess(tree: Tree, copied: untpd.Tree): copied.ThisTree[Type] =
      copied.withTypeUnchecked(tree.tpe)
    def postProcess(tree: Tree, copied: untpd.MemberDef): copied.ThisTree[Type] =
      copied.withTypeUnchecked(tree.tpe)

    protected val untpdCpy = untpd.cpy

    override def Select(tree: Tree)(qualifier: Tree, name: Name)(using Context): Select = {
      val tree1 = untpdCpy.Select(tree)(qualifier, name)
      tree match {
        case tree: Select if qualifier.tpe eq tree.qualifier.tpe =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ =>
          val tree2: Select = tree.tpe match {
            case tpe: NamedType =>
              val qualType = qualifier.tpe.widenIfUnstable
              if qualType.isExactlyNothing then tree1.withTypeUnchecked(tree.tpe)
              else tree1.withType(tpe.derivedSelect(qualType))
            case _ => tree1.withTypeUnchecked(tree.tpe)
          }
          ConstFold.Select(tree2)
      }
    }

    override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply = {
      val tree1 = untpdCpy.Apply(tree)(fun, args)
      tree match {
        case tree: Apply
        if (fun.tpe eq tree.fun.tpe) && sameTypes(args, tree.args) =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, fun, args)
      }
    }

    override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): TypeApply = {
      val tree1 = untpdCpy.TypeApply(tree)(fun, args)
      tree match {
        case tree: TypeApply
        if (fun.tpe eq tree.fun.tpe) && sameTypes(args, tree.args) =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, fun, args)
      }
    }

    override def Literal(tree: Tree)(const: Constant)(using Context): Literal =
      ta.assignType(untpdCpy.Literal(tree)(const))

    override def New(tree: Tree)(tpt: Tree)(using Context): New =
      ta.assignType(untpdCpy.New(tree)(tpt), tpt)

    override def Typed(tree: Tree)(expr: Tree, tpt: Tree)(using Context): Typed =
      ta.assignType(untpdCpy.Typed(tree)(expr, tpt), tpt)

    override def NamedArg(tree: Tree)(name: Name, arg: Tree)(using Context): NamedArg =
      ta.assignType(untpdCpy.NamedArg(tree)(name, arg), arg)

    override def Assign(tree: Tree)(lhs: Tree, rhs: Tree)(using Context): Assign =
      ta.assignType(untpdCpy.Assign(tree)(lhs, rhs))

    override def Block(tree: Tree)(stats: List[Tree], expr: Tree)(using Context): Block = {
      val tree1 = untpdCpy.Block(tree)(stats, expr)
      tree match {
        case tree: Block if (expr.tpe eq tree.expr.tpe) && (expr.tpe eq tree.tpe) =>
          // The last guard is a conservative check: if `tree.tpe` is different from `expr.tpe`, then
          // it was computed from widening `expr.tpe`, and tree transforms might cause `expr.tpe.widen`
          // to change even if `expr.tpe` itself didn't change, e.g:
          //     { val s = ...;  s }
          // If the type of `s` changed, then the type of the block might have changed, even though `expr.tpe`
          // will still be `TermRef(NoPrefix, s)`
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, stats, expr)
      }
    }

    override def If(tree: Tree)(cond: Tree, thenp: Tree, elsep: Tree)(using Context): If = {
      val tree1 = untpdCpy.If(tree)(cond, thenp, elsep)
      tree match {
        case tree: If if (thenp.tpe eq tree.thenp.tpe) && (elsep.tpe eq tree.elsep.tpe) &&
          ((tree.tpe eq thenp.tpe) || (tree.tpe eq elsep.tpe)) =>
          // The last guard is a conservative check similar to the one done in `Block` above,
          // if `tree.tpe` is not identical to the type of one of its branch, it might have been
          // computed from the widened type of the branches, so the same reasoning than
          // in `Block` applies.
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, thenp, elsep)
      }
    }

    override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(using Context): Closure = {
      val tree1 = untpdCpy.Closure(tree)(env, meth, tpt)
      tree match {
        case tree: Closure if sameTypes(env, tree.env) && (meth.tpe eq tree.meth.tpe) && (tpt.tpe eq tree.tpt.tpe) =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, meth, tpt)
      }
    }

    override def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(using Context): Match = {
      val tree1 = untpdCpy.Match(tree)(selector, cases)
      tree match {
        case tree: Match if sameTypes(cases, tree.cases) => tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, selector, cases)
      }
    }

    override def CaseDef(tree: Tree)(pat: Tree, guard: Tree, body: Tree)(using Context): CaseDef = {
      val tree1 = untpdCpy.CaseDef(tree)(pat, guard, body)
      tree match {
        case tree: CaseDef if body.tpe eq tree.body.tpe => tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, pat, body)
      }
    }

    override def Labeled(tree: Tree)(bind: Bind, expr: Tree)(using Context): Labeled =
      ta.assignType(untpdCpy.Labeled(tree)(bind, expr))

    override def Return(tree: Tree)(expr: Tree, from: Tree)(using Context): Return =
      ta.assignType(untpdCpy.Return(tree)(expr, from))

    override def WhileDo(tree: Tree)(cond: Tree, body: Tree)(using Context): WhileDo =
      ta.assignType(untpdCpy.WhileDo(tree)(cond, body))

    override def Try(tree: Tree)(expr: Tree, cases: List[CaseDef], finalizer: Tree)(using Context): Try = {
      val tree1 = untpdCpy.Try(tree)(expr, cases, finalizer)
      tree match {
        case tree: Try if (expr.tpe eq tree.expr.tpe) && sameTypes(cases, tree.cases) => tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, expr, cases)
      }
    }

    override def Inlined(tree: Inlined)(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = {
      val tree1 = untpdCpy.Inlined(tree)(call, bindings, expansion)
      tree match {
        case tree: Inlined if sameTypes(bindings, tree.bindings) && (expansion.tpe eq tree.expansion.tpe) =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, bindings, expansion)
      }
    }

    override def SeqLiteral(tree: Tree)(elems: List[Tree], elemtpt: Tree)(using Context): SeqLiteral = {
      val tree1 = untpdCpy.SeqLiteral(tree)(elems, elemtpt)
      tree match {
        case tree: SeqLiteral
        if sameTypes(elems, tree.elems) && (elemtpt.tpe eq tree.elemtpt.tpe) =>
          tree1.withTypeUnchecked(tree.tpe)
        case _ =>
          ta.assignType(tree1, elems, elemtpt)
      }
    }

    override def Annotated(tree: Tree)(arg: Tree, annot: Tree)(using Context): Annotated = {
      val tree1 = untpdCpy.Annotated(tree)(arg, annot)
      tree match {
        case tree: Annotated if (arg.tpe eq tree.arg.tpe) && (annot eq tree.annot) => tree1.withTypeUnchecked(tree.tpe)
        case _ => ta.assignType(tree1, arg, annot)
      }
    }

    override def If(tree: If)(cond: Tree = tree.cond, thenp: Tree = tree.thenp, elsep: Tree = tree.elsep)(using Context): If =
      If(tree: Tree)(cond, thenp, elsep)
    override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(using Context): Closure =
      Closure(tree: Tree)(env, meth, tpt)
    override def CaseDef(tree: CaseDef)(pat: Tree = tree.pat, guard: Tree = tree.guard, body: Tree = tree.body)(using Context): CaseDef =
      CaseDef(tree: Tree)(pat, guard, body)
    override def Try(tree: Try)(expr: Tree = tree.expr, cases: List[CaseDef] = tree.cases, finalizer: Tree = tree.finalizer)(using Context): Try =
      Try(tree: Tree)(expr, cases, finalizer)
  }

  class TimeTravellingTreeCopier extends TypedTreeCopier {
    override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply =
      tree match
        case tree: Apply
        if (tree.fun eq fun) && (tree.args eq args)
           && tree.tpe.isInstanceOf[ConstantType]
           && isPureExpr(tree) => tree
        case _ =>
          ta.assignType(untpdCpy.Apply(tree)(fun, args), fun, args)
      // Note: Reassigning the original type if `fun` and `args` have the same types as before
      // does not work here in general: The computed type depends on the widened function type, not
      // the function type itself. A tree transform may keep the function type the
      // same but its widened type might change.
      // However, we keep constant types of pure expressions. This uses the underlying assumptions
      // that pure functions yielding a constant will not change in later phases.

    override def TypeApply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): TypeApply =
      ta.assignType(untpdCpy.TypeApply(tree)(fun, args), fun, args)
      // Same remark as for Apply

    override def Closure(tree: Tree)(env: List[Tree], meth: Tree, tpt: Tree)(using Context): Closure =
            ta.assignType(untpdCpy.Closure(tree)(env, meth, tpt), meth, tpt)

    override def Closure(tree: Closure)(env: List[Tree] = tree.env, meth: Tree = tree.meth, tpt: Tree = tree.tpt)(using Context): Closure =
      Closure(tree: Tree)(env, meth, tpt)
  }

  // This is a more fault-tolerant copier that does not cause errors when
  // function types in applications are undefined.
  // This was called `Inliner.InlineCopier` before 3.6.3.
  class ConservativeTreeCopier() extends TypedTreeCopier:
    override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply =
      if fun.tpe.widen.exists then super.Apply(tree)(fun, args)
      else untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe)

  override def skipTransform(tree: Tree)(using Context): Boolean = tree.tpe.isError

  implicit class TreeOps[ThisTree <: tpd.Tree](private val tree: ThisTree) extends AnyVal {

    def isValue(using Context): Boolean =
      tree.isTerm && tree.tpe.widen.isValueType

    def isValueOrPattern(using Context): Boolean =
      tree.isValue || tree.isPattern

    def isValueType: Boolean =
      tree.isType && tree.tpe.isValueType

    def isInstantiation: Boolean = tree match {
      case Apply(Select(New(_), nme.CONSTRUCTOR), _) => true
      case _ => false
    }

    def shallowFold[T](z: T)(op: (T, tpd.Tree) => T)(using Context): T =
      ShallowFolder(op).apply(z, tree)

    def deepFold[T](z: T)(op: (T, tpd.Tree) => T)(using Context): T =
      DeepFolder(op).apply(z, tree)

    def find[T](pred: (tpd.Tree) => Boolean)(using Context): Option[tpd.Tree] =
      shallowFold[Option[tpd.Tree]](None)((accum, tree) => if (pred(tree)) Some(tree) else accum)

    def subst(from: List[Symbol], to: List[Symbol])(using Context): ThisTree =
      TreeTypeMap(substFrom = from, substTo = to).apply(tree)

    /** Change owner from `from` to `to`. If `from` is a weak owner, also change its
     *  owner to `to`, and continue until a non-weak owner is reached.
     */
    def changeOwner(from: Symbol, to: Symbol)(using Context): ThisTree = {
      @tailrec def loop(from: Symbol, froms: List[Symbol], tos: List[Symbol]): ThisTree =
        if (from.isWeakOwner && !from.owner.isClass)
          loop(from.owner, from :: froms, to :: tos)
        else
          //println(i"change owner ${from :: froms}%, % ==> $tos of $tree")
          TreeTypeMap(oldOwners = from :: froms, newOwners = tos).apply(tree)
      if (from == to) tree else loop(from, Nil, to :: Nil)
    }

    /**
     * Set the owner of every definition in this tree which is not itself contained in this
     * tree to be `newowner`
     */
    def changeNonLocalOwners(newOwner: Symbol)(using Context): Tree = {
      val ownerAcc = new TreeAccumulator[immutable.Set[Symbol]] {
        def apply(ss: immutable.Set[Symbol], tree: Tree)(using Context) = tree match {
          case tree: DefTree =>
            val sym = tree.symbol
            if sym.exists && !sym.owner.is(Package) then ss + sym.owner else ss
          case _ =>
            foldOver(ss, tree)
        }
      }
      val owners = ownerAcc(immutable.Set.empty[Symbol], tree).toList
      val newOwners = List.fill(owners.size)(newOwner)
      TreeTypeMap(oldOwners = owners, newOwners = newOwners).apply(tree)
    }

    /** After phase `trans`, set the owner of every definition in this tree that was formerly
     *  owned by `from` to `to`.
     */
    def changeOwnerAfter(from: Symbol, to: Symbol, trans: DenotTransformer)(using Context): ThisTree =
      if (ctx.phase == trans.next) {
        val traverser = new TreeTraverser {
          def traverse(tree: Tree)(using Context) = tree match {
            case tree: DefTree =>
              val sym = tree.symbol
              val prevDenot = atPhase(trans)(sym.denot)
              if (prevDenot.effectiveOwner == from.skipWeakOwner) {
                val d = sym.copySymDenotation(owner = to)
                d.installAfter(trans)
                d.transformAfter(trans, d => if (d.owner eq from) d.copySymDenotation(owner = to) else d)
              }
              if (sym.isWeakOwner) traverseChildren(tree)
            case _ =>
              traverseChildren(tree)
          }
        }
        traverser.traverse(tree)
        tree
      }
      else atPhase(trans.next)(changeOwnerAfter(from, to, trans))

    /** A select node with the given selector name and a computed type */
    def select(name: Name)(using Context): Select =
      Select(tree, name)

    /** A select node with the given selector name such that the designated
     *  member satisfies predicate `p`. Useful for disambiguating overloaded members.
     */
    def select(name: Name, p: Symbol => Boolean)(using Context): Select =
      select(tree.tpe.member(name).suchThat(p).symbol)

    /** A select node with the given type */
    def select(tp: NamedType)(using Context): Select =
      untpd.Select(tree, tp.name).withType(tp)

    /** A select node that selects the given symbol. Note: Need to make sure this
     *  is in fact the symbol you would get when you select with the symbol's name,
     *  otherwise a data race may occur which would be flagged by -Yno-double-bindings.
     */
    def select(sym: Symbol)(using Context): Select = {
      val tp =
        if (sym.isType) {
          assert(!sym.is(TypeParam))
          TypeRef(tree.tpe, sym.asType)
        }
        else
          TermRef(tree.tpe, sym.name.asTermName, sym.denot.asSeenFrom(tree.tpe))
      untpd.Select(tree, sym.name).withType(tp)
    }

    /** A select node with the given selector name and signature and a computed type */
    def selectWithSig(name: Name, sig: Signature, target: Name)(using Context): Tree =
      untpd.SelectWithSig(tree, name, sig).withType(tree.tpe.select(name.asTermName, sig, target))

    /** A select node with selector name and signature taken from `sym`.
     *  Note: Use this method instead of select(sym) if the referenced symbol
     *  might be overridden in the type of the qualifier prefix. See note
     *  on select(sym: Symbol).
     */
    def selectWithSig(sym: Symbol)(using Context): Tree =
      selectWithSig(sym.name, sym.signature, sym.targetName)

    /** A unary apply node with given argument: `tree(arg)` */
    def appliedTo(arg: Tree)(using Context): Apply =
      appliedToTermArgs(arg :: Nil)

    /** An apply node with given arguments: `tree(arg, args0, ..., argsN)` */
    def appliedTo(arg: Tree, args: Tree*)(using Context): Apply =
      appliedToTermArgs(arg :: args.toList)

    /** An apply node with given argument list `tree(args(0), ..., args(args.length - 1))` */
    def appliedToTermArgs(args: List[Tree])(using Context): Apply =
      Apply(tree, args)

    /** An applied node that accepts only varargs as arguments */
    def appliedToVarargs(args: List[Tree], tpt: Tree)(using Context): Apply =
      appliedTo(repeated(args, tpt))

    /** An apply or type apply node with given argument list */
    def appliedToArgs(args: List[Tree])(using Context): GenericApply = args match
      case arg :: args1 if arg.isType => TypeApply(tree, args)
      case _ => Apply(tree, args)

      /** The current tree applied to given argument lists:
     *  `tree (argss(0)) ... (argss(argss.length -1))`
     */
    def appliedToArgss(argss: List[List[Tree]])(using Context): Tree =
      argss.foldLeft(tree: Tree)(_.appliedToArgs(_))

    /** The current tree applied to (): `tree()` */
    def appliedToNone(using Context): Apply = Apply(tree, Nil)

    /** The current tree applied to given type argument: `tree[targ]` */
    def appliedToType(targ: Type)(using Context): Tree =
      appliedToTypes(targ :: Nil)

    /** The current tree applied to given type arguments: `tree[targ0, ..., targN]` */
    def appliedToTypes(targs: List[Type])(using Context): Tree =
      appliedToTypeTrees(targs map (TypeTree(_)))

    /** The current tree applied to given type argument: `tree[targ]` */
    def appliedToTypeTree(targ: Tree)(using Context): Tree =
      appliedToTypeTrees(targ :: Nil)

    /** The current tree applied to given type argument list: `tree[targs(0), ..., targs(targs.length - 1)]` */
    def appliedToTypeTrees(targs: List[Tree])(using Context): Tree =
      if targs.isEmpty then tree else TypeApply(tree, targs)

    /** Apply to `()` unless tree's widened type is parameterless */
    def ensureApplied(using Context): Tree =
      if (tree.tpe.widen.isParameterless) tree else tree.appliedToNone

    /** `tree == that` */
    def equal(that: Tree)(using Context): Tree =
      if (that.tpe.widen.isRef(defn.NothingClass))
        Literal(Constant(false))
      else
        applyOverloaded(tree, nme.EQ, that :: Nil, Nil, defn.BooleanType)

    /** `tree.isInstanceOf[tp]`, with special treatment of singleton types */
    def isInstance(tp: Type)(using Context): Tree = tp.dealias match {
      case ConstantType(c) if c.tag == StringTag =>
        singleton(tp).equal(tree)
      case tp: SingletonType =>
        if tp.widen.derivesFrom(defn.ObjectClass) then
          tree.ensureConforms(defn.ObjectType).select(defn.Object_eq).appliedTo(singleton(tp))
        else
          singleton(tp).equal(tree)
      case _ =>
        tree.select(defn.Any_isInstanceOf).appliedToType(tp)
    }

    /** tree.asInstanceOf[`tp`] */
    def asInstance(tp: Type)(using Context): Tree = {
      assert(tp.isValueType, i"bad cast: $tree.asInstanceOf[$tp]")
      tree.select(defn.Any_asInstanceOf).appliedToType(tp)
    }

    /** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */
    def cast(tp: Type)(using Context): Tree = cast(TypeTree(tp))

    /** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */
    def cast(tpt: TypeTree)(using Context): Tree =
      assert(tpt.tpe.isValueType, i"bad cast: $tree.asInstanceOf[$tpt]")
      tree.select(if (ctx.erasedTypes) defn.Any_asInstanceOf else defn.Any_typeCast)
        .appliedToTypeTree(tpt)

    /** cast `tree` to `tp` (or its box/unbox/cast equivalent when after
     *  erasure and value and non-value types are mixed),
     *  unless tree's type already conforms to `tp`.
     */
    def ensureConforms(tp: Type)(using Context): Tree =
      if (tree.tpe <:< tp) tree
      else if (!ctx.erasedTypes) cast(tp)
      else Erasure.Boxing.adaptToType(tree, tp)

    /** `tree ne null` (might need a cast to be type correct) */
    def testNotNull(using Context): Tree = {
      // If the receiver is of type `Nothing` or `Null`, add an ascription or cast
      // so that the selection succeeds.
      // e.g. `null.ne(null)` doesn't type, but `(null: AnyRef).ne(null)` does.
      val receiver =
        if tree.tpe.isBottomType then
          if ctx.explicitNulls then tree.cast(defn.AnyRefType)
          else Typed(tree, TypeTree(defn.AnyRefType))
        else tree.ensureConforms(defn.ObjectType)
      // also need to cast the null literal to AnyRef in explicit nulls
      val nullLit = if ctx.explicitNulls then nullLiteral.cast(defn.AnyRefType) else nullLiteral
      receiver.select(defn.Object_ne).appliedTo(nullLit).withSpan(tree.span)
    }

    /** If inititializer tree is `_`, the default value of its type,
     *  otherwise the tree itself.
     */
    def wildcardToDefault(using Context): Tree =
      if (isWildcardArg(tree)) defaultValue(tree.tpe) else tree

    /** `this && that`, for boolean trees `this`, `that` */
    def and(that: Tree)(using Context): Tree =
      tree.select(defn.Boolean_&&).appliedTo(that)

    /** `this || that`, for boolean trees `this`, `that` */
    def or(that: Tree)(using Context): Tree =
      tree.select(defn.Boolean_||).appliedTo(that)

    /** The translation of `tree = rhs`.
     *  This is either the tree as an assignment, or a setter call.
     */
    def becomes(rhs: Tree)(using Context): Tree = {
      val sym = tree.symbol
      if (sym.is(Method)) {
        val setter = sym.setter.orElse {
          assert(sym.name.isSetterName && sym.info.firstParamTypes.nonEmpty, sym)
          sym
        }
        val qual = tree match {
          case id: Ident => desugarIdentPrefix(id)
          case Select(qual, _) => qual
        }
        qual.select(setter).appliedTo(rhs)
      }
      else Assign(tree, rhs)
    }

    /** tree @annot
     *
     *  works differently for type trees and term trees
     */
    def annotated(annot: Tree)(using Context): Tree =
      if (tree.isTerm)
        Typed(tree, TypeTree(AnnotatedType(tree.tpe.widenIfUnstable, Annotation(annot))))
      else
        Annotated(tree, annot)

    /** A synthetic select with that will be turned into an outer path by ExplicitOuter.
     *  @param levels  How many outer levels to select
     *  @param tp      The type of the destination of the outer path.
     */
    def outerSelect(levels: Int, tp: Type)(using Context): Tree =
      untpd.Select(tree, OuterSelectName(EmptyTermName, levels)).withType(SkolemType(tp))

    /** Replace Inlined nodes and InlineProxy references to underlying arguments */
    def underlyingArgument(using Context): Tree = {
      val mapToUnderlying = new MapToUnderlying {
        /** Should get the rhs of this binding
         *  Returns true if the symbol is a val or def generated by eta-expansion/inline
         */
        override protected def skipLocal(sym: Symbol): Boolean =
          sym.isOneOf(InlineProxy | Synthetic)
      }
      mapToUnderlying.transform(tree)
    }

    /** Replace Ident nodes references to the underlying tree that defined them */
    def underlying(using Context): Tree = MapToUnderlying().transform(tree)

    // --- Higher order traversal methods -------------------------------

    /** Apply `f` to each subtree of this tree */
    def foreachSubTree(f: Tree => Unit)(using Context): Unit = {
      val traverser = new TreeTraverser {
        def traverse(tree: Tree)(using Context) = foldOver(f(tree), tree)
      }
      traverser.traverse(tree)
    }

    /** Is there a subtree of this tree that satisfies predicate `p`? */
    def existsSubTree(p: Tree => Boolean)(using Context): Boolean = {
      val acc = new TreeAccumulator[Boolean] {
        def apply(x: Boolean, t: Tree)(using Context) = x || p(t) || foldOver(x, t)
      }
      acc(false, tree)
    }

    /** All subtrees of this tree that satisfy predicate `p`. */
    def filterSubTrees(f: Tree => Boolean)(using Context): List[Tree] = {
      val buf = mutable.ListBuffer[Tree]()
      foreachSubTree { tree => if (f(tree)) buf += tree }
      buf.toList
    }

    def collectSubTrees[A](f: PartialFunction[Tree, A])(using Context): List[A] =
      val buf = mutable.ListBuffer[A]()
      foreachSubTree(f.runWith(buf += _)(_))
      buf.toList

    /** Set this tree as the `defTree` of its symbol and return this tree */
    def setDefTree(using Context): ThisTree = {
      val sym = tree.symbol
      if (sym.exists) sym.defTree = tree
      tree
    }

    /** Make sure tree has given symbol. This is called when typing or unpickling
     *  a ValDef or DefDef. It turns out that under very rare circumstances the symbol
     *  computed for a tree is not correct. The only known test case is i21755.scala.
     *  Here we have a self type that mentions a supertype as well as a type parameter
     *  upper-bounded by the current class and it turns out that we compute the symbol
     *  for a member method (named `root` in this case) in a subclass to be the
     *  corresponding symbol in the superclass. It is not known what are the precise
     *  conditions where this happens, but my guess would be that it's connected to the
     *  recursion in the self type.
     */
    def ensureHasSym(sym: Symbol)(using Context): Unit =
      if sym.exists && sym != tree.symbol then
        typr.println(i"correcting definition symbol from ${tree.symbol.showLocated} to ${sym.showLocated}")
        tree.overwriteType(NamedType(sym.owner.thisType, sym.asTerm.name, sym.denot))

    def etaExpandCFT(using Context): Tree =
      def expand(target: Tree, tp: Type)(using Context): Tree = tp match
        case defn.ContextFunctionType(argTypes, resType) =>
          val anonFun = newAnonFun(
            ctx.owner,
            MethodType.companion(isContextual = true)(argTypes, resType),
            coord = ctx.owner.coord)
          def lambdaBody(refss: List[List[Tree]]) =
            expand(target.select(nme.apply).appliedToArgss(refss), resType)(
              using ctx.withOwner(anonFun))
          Closure(anonFun, lambdaBody)
        case _ =>
          target
      expand(tree, tree.tpe.widen)
  }

  extension (trees: List[Tree])

    /** Equivalent (but faster) to
     *
     *    flatten(trees.mapConserve(op))
     *
     *  assuming that `trees` does not contain `Thicket`s to start with.
     */
    inline def flattenedMapConserve(inline f: Tree => Tree): List[Tree] =
      @tailrec
      def loop(mapped: ListBuffer[Tree] | Null, unchanged: List[Tree], pending: List[Tree]): List[Tree] =
        if pending.isEmpty then
          if mapped == null then unchanged
          else mapped.prependToList(unchanged)
        else
          val head0 = pending.head
          val head1 = f(head0)

          if head1 eq head0 then
            loop(mapped, unchanged, pending.tail)
          else
            val buf = if mapped == null then new ListBuffer[Tree] else mapped
            var xc = unchanged
            while xc ne pending do
              buf += xc.head
              xc = xc.tail
            head1 match
              case Thicket(elems1) => buf ++= elems1
              case _ => buf += head1
            val tail0 = pending.tail
            loop(buf, tail0, tail0)
      loop(null, trees, trees)

    /** Transform statements while maintaining import contexts and expression contexts
     *  in the same way as Typer does. The code addresses additional concerns:
     *   - be tail-recursive where possible
     *   - don't re-allocate trees where nothing has changed
     */
    inline def mapStatements[T](
        exprOwner: Symbol,
        inline op: Tree => Context ?=> Tree,
        inline wrapResult: List[Tree] => Context ?=> T)(using Context): T =
      @tailrec
      def loop(mapped: mutable.ListBuffer[Tree] | Null, unchanged: List[Tree], pending: List[Tree])(using Context): T =
        pending match
          case stat :: rest =>
            val statCtx = stat match
              case _: DefTree | _: ImportOrExport => ctx
              case _ => ctx.exprContext(stat, exprOwner)
            val stat1 = op(stat)(using statCtx)
            val restCtx = stat match
              case stat: Import => ctx.importContext(stat, stat.symbol)
              case _ => ctx
            if stat1 eq stat then
              loop(mapped, unchanged, rest)(using restCtx)
            else
              val buf = if mapped == null then new mutable.ListBuffer[Tree] else mapped
              var xc = unchanged
              while xc ne pending do
                buf += xc.head
                xc = xc.tail
              stat1 match
                case Thicket(stats1) => buf ++= stats1
                case _ => buf += stat1
              loop(buf, rest, rest)(using restCtx)
          case nil =>
            wrapResult(
              if mapped == null then unchanged
              else mapped.prependToList(unchanged))

      loop(null, trees, trees)
    end mapStatements
  end extension

  /** A treemap that generates the same contexts as the original typer for statements.
   *  This means:
   *    - statements that are not definitions get the exprOwner as owner
   *    - imports are reflected in the contexts of subsequent statements
   */
  class TreeMapWithPreciseStatContexts(cpy: TreeCopier = tpd.cpy) extends TreeMap(cpy):
    def transformStats[T](trees: List[Tree], exprOwner: Symbol, wrapResult: List[Tree] => Context ?=> T)(using Context): T =
      trees.mapStatements(exprOwner, transform(_), wrapResult)
    final override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] =
      transformStats(trees, exprOwner, sameStats)
    override def transformBlock(blk: Block)(using Context) =
      transformStats(blk.stats, ctx.owner,
        stats1 => ctx ?=> cpy.Block(blk)(stats1, transform(blk.expr)))

  val sameStats: List[Tree] => Context ?=> List[Tree] = stats => stats

  /** Map Inlined nodes, NamedArgs, Blocks with no statements and local references to underlying arguments.
   *  Also drops Inline and Block with no statements.
   */
  private class MapToUnderlying extends TreeMap {
    override def transform(tree: Tree)(using Context): Tree = tree match {
      case tree: Ident if isBinding(tree.symbol) && skipLocal(tree.symbol) && !tree.symbol.is(Module) =>
        tree.symbol.defTree match {
          case defTree: ValOrDefDef =>
            val rhs = defTree.rhs
            assert(!rhs.isEmpty)
            transform(rhs)
          case _ => tree
        }
      case Inlined(_, Nil, arg) => transform(arg)
      case Block(Nil, arg) => transform(arg)
      case NamedArg(_, arg) => transform(arg)
      case tree => super.transform(tree)
    }

    /** Should get the rhs of this binding */
    protected def skipLocal(sym: Symbol): Boolean = true

    /** Is this a symbol that of a local val or parameterless def for which we could get the rhs */
    private def isBinding(sym: Symbol)(using Context): Boolean =
      sym.isTerm && !sym.is(Param) && !sym.owner.isClass &&
      !(sym.is(Method) && sym.info.isInstanceOf[MethodOrPoly]) // if is a method it is parameterless
  }

  /** A tree traverser that generates the same import contexts as original typer for statements.
   *  TODO: Should we align TreeMapWithPreciseStatContexts and also keep track of exprOwners?
   */
  abstract class TreeTraverserWithPreciseImportContexts extends TreeTraverser:
    override def apply(x: Unit, trees: List[Tree])(using Context): Unit =
      def recur(trees: List[Tree]): Unit = trees match
        case (imp: Import) :: rest =>
          traverse(rest)(using ctx.importContext(imp, imp.symbol))
        case tree :: rest =>
          traverse(tree)
          traverse(rest)
        case Nil =>
      recur(trees)
  end TreeTraverserWithPreciseImportContexts

  extension (xs: List[tpd.Tree])
    def tpes: List[Type] = xs match {
      case x :: xs1 => x.tpe :: xs1.tpes
      case nil => Nil
    }

  /** A trait for loaders that compute trees. Currently implemented just by DottyUnpickler. */
  trait TreeProvider {
    protected def computeRootTrees(using Context): List[Tree]

    private var myTrees: List[Tree] | Null = uninitialized

    /** Get trees defined by this provider. Cache them if -Yretain-trees is set. */
    def rootTrees(using Context): List[Tree] =
      if (ctx.settings.YretainTrees.value) {
        if (myTrees == null) myTrees = computeRootTrees
        myTrees.uncheckedNN
      }
      else computeRootTrees

    /** Get first tree defined by this provider, or EmptyTree if none exists */
    def tree(using Context): Tree =
      rootTrees.headOption.getOrElse(EmptyTree)

    /** Is it possible that the tree to load contains a definition of or reference to `id`? */
    def mightContain(id: String)(using Context): Boolean = true
  }

  // convert a numeric with a toXXX method
  def primitiveConversion(tree: Tree, numericCls: Symbol)(using Context): Tree = {
    val mname      = "to".concat(numericCls.name)
    val conversion = tree.tpe member(mname)
    if (conversion.symbol.exists)
      tree.select(conversion.symbol.termRef).ensureApplied
    else if (tree.tpe.widen isRef numericCls)
      tree
    else {
      report.warning(em"conversion from ${tree.tpe.widen} to ${numericCls.typeRef} will always fail at runtime.")
      Throw(New(defn.ClassCastExceptionClass.typeRef, Nil)).withSpan(tree.span)
    }
  }

  /** A tree that corresponds to `Predef.classOf[$tp]` in source */
  def clsOf(tp: Type)(using Context): Tree =
    if ctx.erasedTypes && !tp.isRef(defn.UnitClass) then
      Literal(Constant(TypeErasure.erasure(tp)))
    else
      Literal(Constant(tp))

  @tailrec
  def sameTypes(trees: List[tpd.Tree], trees1: List[tpd.Tree]): Boolean =
    if (trees.isEmpty) trees.isEmpty
    else if (trees1.isEmpty) trees.isEmpty
    else (trees.head.tpe eq trees1.head.tpe) && sameTypes(trees.tail, trees1.tail)

  /** If `tree`'s purity level is less than `level`, let-bind it so that it gets evaluated
   *  only once. I.e. produce a
   *
   *     { val x = 'tree ;  ~within('x) }
   *
   *  instead of otherwise
   *
   *     ~within('tree)
   */
  def letBindUnless(level: TreeInfo.PurityLevel, tree: Tree)(within: Tree => Tree)(using Context): Tree =
    if (exprPurity(tree) >= level) within(tree)
    else {
      val vdef = SyntheticValDef(TempResultName.fresh(), tree)
      Block(vdef :: Nil, within(Ident(vdef.namedType)))
    }

  /** Let bind `tree` unless `tree` is at least idempotent */
  def evalOnce(tree: Tree)(within: Tree => Tree)(using Context): Tree =
    letBindUnless(TreeInfo.Idempotent, tree)(within)

  def runtimeCall(name: TermName, args: List[Tree])(using Context): Tree =
    Ident(defn.ScalaRuntimeModule.requiredMethod(name).termRef).appliedToTermArgs(args)

  /** An extractor that pulls out type arguments */
  object MaybePoly:
    def unapply(tree: Tree): Option[(Tree, List[Tree])] = tree match
      case TypeApply(tree, targs) => Some(tree, targs)
      case _ => Some(tree, Nil)

  object TypeArgs:
    def unapply(ts: List[Tree]): Option[List[Tree]] =
      if ts.nonEmpty && ts.head.isType then Some(ts) else None

  /** Split argument clauses into a leading type argument clause if it exists and
   *  remaining clauses
   */
  def splitArgs(argss: List[List[Tree]]): (List[Tree], List[List[Tree]]) = argss match
    case TypeArgs(targs) :: argss1 => (targs, argss1)
    case _ => (Nil, argss)

  def joinArgs(targs: List[Tree], argss: List[List[Tree]]): List[List[Tree]] =
    if targs.isEmpty then argss else targs :: argss

  /** A key to be used in a context property that tracks enclosing inlined calls */
  private val InlinedCalls = Property.Key[List[Tree]]()

  /** A key to be used in a context property that tracks the number of inlined trees */
  private val InlinedTrees = Property.Key[Counter]()
  final class Counter {
    var count: Int = 0
  }

  /** Record an enclosing inlined call.
   *  EmptyTree calls (for parameters) cancel the next-enclosing call in the list instead of being added to it.
   *  We assume parameters are never nested inside parameters.
   */
  override def inlineContext(tree: Inlined)(using Context): Context = {
    // We assume enclosingInlineds is already normalized, and only process the new call with the head.
    val oldIC = enclosingInlineds

    val newIC =
      if tree.inlinedFromOuterScope then
        oldIC match
          case t1 :: ts2 => ts2
          case _ => oldIC
      else
        tree.call :: oldIC

    val ctx1 = ctx.fresh.setProperty(InlinedCalls, newIC)
    if oldIC.isEmpty then ctx1.setProperty(InlinedTrees, new Counter) else ctx1
  }

  /** All enclosing calls that are currently inlined, from innermost to outermost.
   */
  def enclosingInlineds(using Context): List[Tree] =
    ctx.property(InlinedCalls).getOrElse(Nil)

  /** Record inlined trees */
  def addInlinedTrees(n: Int)(using Context): Unit =
    ctx.property(InlinedTrees).foreach(_.count += n)

  /** Check if the limit on the number of inlined trees has been reached */
  def reachedInlinedTreesLimit(using Context): Boolean =
    ctx.property(InlinedTrees) match
      case Some(c) => c.count > ctx.settings.XmaxInlinedTrees.value
      case None => false

  /** The source file where the symbol of the `inline` method referred to by `call`
   *  is defined
   */
  def sourceFile(call: Tree)(using Context): SourceFile = call.symbol.source

  /** Desugar identifier into a select node. Return the tree itself if not possible */
  def desugarIdent(tree: Ident)(using Context): RefTree = {
    val qual = desugarIdentPrefix(tree)
    if (qual.isEmpty) tree
    else qual.select(tree.symbol)
  }

  /** Recover identifier prefix (e.g. this) if it exists */
  def desugarIdentPrefix(tree: Ident)(using Context): Tree = tree.tpe match {
    case TermRef(prefix: TermRef, _) =>
      prefix.info match
        case mt: MethodType if mt.paramInfos.isEmpty && mt.resultType.typeSymbol.is(Module) =>
          ref(mt.resultType.typeSymbol.sourceModule)
        case _ =>
          ref(prefix)
    case TermRef(prefix: ThisType, _) =>
      This(prefix.cls)
    case _ =>
      EmptyTree
  }

  /**
   * The symbols that are imported with `expr.name`
   *
   * @param expr The base of the import statement
   * @param name The name that is being imported.
   * @return All the symbols that would be imported with `expr.name`.
   */
  def importedSymbols(expr: Tree, name: Name)(using Context): List[Symbol] = {
    def lookup(name: Name): Symbol = expr.tpe.member(name).symbol
    val symbols =
      List(lookup(name.toTermName),
           lookup(name.toTypeName),
           lookup(name.moduleClassName),
           lookup(name.sourceModuleName))

    symbols.map(_.sourceSymbol).filter(_.exists).distinct
  }

  /**
   * All the symbols that are imported by the first selector of `imp` that matches
   * `selectorPredicate`.
   *
   * @param imp The import statement to analyze
   * @param selectorPredicate A test to find the selector to use.
   * @return The symbols imported.
   */
  def importedSymbols(imp: Import,
                      selectorPredicate: untpd.ImportSelector => Boolean = util.common.alwaysTrue)
                     (using Context): List[Symbol] =
    imp.selectors.find(selectorPredicate) match
      case Some(sel) => importedSymbols(imp.expr, sel.name)
      case _ => Nil

  /**
   * The list of select trees that resolve to the same symbols as the ones that are imported
   * by `imp`.
   */
  def importSelections(imp: Import)(using Context): List[Select] = {
    def imported(sym: Symbol, id: untpd.Ident, rename: Option[untpd.Ident]): List[Select] = {
      // Give a zero-extent position to the qualifier to prevent it from being included several
      // times in results in the language server.
      val noPosExpr = focusPositions(imp.expr)
      val selectTree = Select(noPosExpr, sym.name).withSpan(id.span)
      rename match {
        case None =>
          selectTree :: Nil
        case Some(rename) =>
          // Get the type of the symbol that is actually selected, and construct a select
          // node with the new name and the type of the real symbol.
          val name = if (sym.name.isTypeName) rename.name.toTypeName else rename.name
          val actual = Select(noPosExpr, sym.name)
          val renameTree = Select(noPosExpr, name).withSpan(rename.span).withType(actual.tpe)
          selectTree :: renameTree :: Nil
      }
    }

    imp.selectors.flatMap { sel =>
      if sel.isWildcard then Nil
      else
        val renamedOpt = sel.renamed match
          case renamed: untpd.Ident => Some(renamed)
          case untpd.EmptyTree => None
        importedSymbols(imp.expr, sel.name).flatMap { sym =>
          imported(sym, sel.imported, renamedOpt)
        }
    }
  }

  /** Creates the tuple containing the given elements */
  def tupleTree(elems: List[Tree])(using Context): Tree = {
    val arity = elems.length
    if arity == 0 then
      ref(defn.EmptyTupleModule)
    else if arity <= Definitions.MaxTupleArity then
      // TupleN[elem1Tpe, ...](elem1, ...)
      ref(defn.TupleType(arity).nn.typeSymbol.companionModule)
        .select(nme.apply)
        .appliedToTypes(elems.map(_.tpe.widenIfUnstable))
        .appliedToArgs(elems)
    else
      // TupleXXL.apply(elems*) // TODO add and use Tuple.apply(elems*) ?
      ref(defn.TupleXXLModule)
        .select(nme.apply)
        .appliedToVarargs(elems.map(_.asInstance(defn.ObjectType)), TypeTree(defn.ObjectType))
        .asInstance(defn.tupleType(elems.map(elem => elem.tpe.widenIfUnstable)))
  }

  /** Creates the tuple type tree representation of the type trees in `ts` */
  def tupleTypeTree(elems: List[Tree])(using Context): Tree = {
    val arity = elems.length
    if arity <= Definitions.MaxTupleArity then
      val tupleTp = defn.TupleType(arity)
      if tupleTp != null then
        AppliedTypeTree(TypeTree(tupleTp), elems)
      else nestedPairsTypeTree(elems)
    else nestedPairsTypeTree(elems)
  }

  /** Creates the nested pairs type tree representation of the type trees in `ts` */
  def nestedPairsTypeTree(ts: List[Tree])(using Context): Tree =
    ts.foldRight[Tree](TypeTree(defn.EmptyTupleModule.termRef))((x, acc) => AppliedTypeTree(TypeTree(defn.PairClass.typeRef), x :: acc :: Nil))

  /** Creates the nested higher-kinded pairs type tree representation of the type trees in `ts` */
  def hkNestedPairsTypeTree(ts: List[Tree])(using Context): Tree =
    ts.foldRight[Tree](TypeTree(defn.QuoteMatching_KNil.typeRef))((x, acc) => AppliedTypeTree(TypeTree(defn.QuoteMatching_KCons.typeRef), x :: acc :: Nil))

  /** Replaces all positions in `tree` with zero-extent positions */
  private def focusPositions(tree: Tree)(using Context): Tree = {
    val transformer = new tpd.TreeMap {
      override def transform(tree: Tree)(using Context): Tree =
        super.transform(tree).withSpan(tree.span.focus)
    }
    transformer.transform(tree)
  }

  /** Convert a list of trees to a vararg-compatible tree.
   *  Used to make arguments for methods that accept varargs.
   */
  def repeated(trees: List[Tree], tpt: Tree)(using Context): Tree =
    ctx.typeAssigner.arrayToRepeated(JavaSeqLiteral(trees, tpt))

  /** Create a tree representing a list containing all
   *  the elements of the argument list. A "list of tree to
   *  tree of list" conversion.
   *
   *  @param trees  the elements the list represented by
   *                the resulting tree should contain.
   *  @param tpt    the type of the elements of the resulting list.
   *
   */
  def mkList(trees: List[Tree], tpt: Tree)(using Context): Tree =
    ref(defn.ListModule).select(nme.apply)
      .appliedToTypeTree(tpt)
      .appliedToVarargs(trees, tpt)


  protected def FunProto(args: List[Tree], resType: Type)(using Context) =
    ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy