private[this] def findImplicitObjectsImpl()

in scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/implicits/ImplicitProcessor.scala [207:496]


  private[this] def findImplicitObjectsImpl(
    `type`:               ScType,
    includePackagePrefix: Boolean
  )(implicit
    elementScope: ElementScope,
    context: Context
  ): Seq[ScType] = {
    val visited   = mutable.HashSet.empty[ScType]
    val parts     = mutable.Queue.empty[ScType]
    val pathTerms = mutable.HashSet.empty[ScType]

    def collectPartsIterable(iterable: IterableOnce[ScType]): Unit = {
      val iterator = iterable.iterator
      while (iterator.hasNext) {
        collectParts(iterator.next())
      }
    }

    def collectPartsTypeResult(tr: TypeResult): Unit =
      tr.foreach(collectParts(_))

    // Java Raw types are converted to F[ScExistentialArgument.Deferred("A", .....), ...]
    // In combination with F-Bounds this can lead to different instantiations that are not ==,
    // but would not reveal further parts of the type.
    //
    // Here, we convert such existential arguments to stand-in types that have a useful
    // equals/hashCode implementation, and use this as the marker in the `visitedType` set.
    def convertRawArgs(tp: ScType): ScType = {
      def rawArgToDummy(typeArgType: ScType): ScType = typeArgType match {
        case existentialArgument: ScExistentialArgument =>
          existentialArgument.typeParamOfRawArg match {
            case Some(typeParamRaw) =>
              ScAbstractType(typeParamRaw, lower = api.Nothing, upper = api.Any)
            case None =>
              typeArgType
          }
        case tp => tp
      }

      def isRawArg(tp: ScType): Boolean = tp match {
        case existentialArgument: ScExistentialArgument =>
          existentialArgument.typeParamOfRawArg.isDefined
        case _ =>
          false
      }

      tp match {
        case ParameterizedType(des, targs) =>
          if (targs.exists(isRawArg)) {
            val targsNew = targs.map(rawArgToDummy)
            val tpNew = ScParameterizedType(des, targsNew)
            tpNew
          } else
            tp
        case _ =>
          tp
      }
    }

    def collectPartsFromSuperTypes(clazz: PsiClass, subst: ScSubstitutor): Unit =
      clazz match {
        case td: ScTemplateDefinition =>
          collectPartsIterable(td.superTypes.map(subst))
          td.selfType.foreach(stpe => collectParts(subst(stpe)))
        case clazz: PsiClass =>
          collectPartsIterable(clazz.getSuperTypes.map(t => subst(t.toScType())))
      }

    /**
     * In scala 3 references to packages and package objects are anchors only under -source:3.0-migration.
     * https://dotty.epfl.ch/3.0.0/docs/reference/changed-features/implicit-resolution.html
     */
    def processPackagePrefix(pack: ScPackageLike): Unit =
      if (includePackagePrefix) {
        for {
          packageObject <- pack.findPackageObject(elementScope.scope)
          designator     = ScDesignatorType(packageObject)
        } parts += designator
        pack.parentScalaPackage.foreach(processPackagePrefix)
      }

    /**
     * See: [[https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html#:~:text=of%20a%20type%3A-,Definition,-%3A%20A%20reference]]
     */
    def isAnchor(element: PsiNamedElement): Boolean =
      if (!element.isInScala3File) false
      else element match {
        case _: PsiClass                  => true
        case alias: ScTypeAliasDefinition => alias.isEffectivelyOpaque || alias.isMatchTypeAlias
        case _: ScTypeAliasDeclaration    => true
        case _                            => false
      }

    @tailrec
    def collectTermsFromPath(path: ScType): Unit = {
      def isValueAlias(pat: ScBindingPattern): Boolean = pat.`type`().exists {
        case downer: DesignatorOwner => downer.isSingleton
        case _                       => false
      }

      path match {
        case des @ ScDesignatorType(elem) => elem match {
          case pat: ScBindingPattern if isValueAlias(pat)          => ()
          case _: ScBindingPattern | _: ScFieldId | _: ScParameter => pathTerms.add(des)
          case _                                                   => ()
        }
        case proj @ ScProjectionType(prefix, elem) =>
          elem match {
            case pat: ScBindingPattern if isValueAlias(pat)          => ()
            case _: ScBindingPattern | _: ScFieldId | _: ScParameter => pathTerms.add(proj)
            case _                                                   => ()
          }
          collectTermsFromPath(prefix)
        case _ => ()
      }
    }

    def collectTypeAliasDefinitionParts(tp: ScType, tdef: ScTypeAliasDefinition): Unit =
      if (tdef.isEffectivelyOpaque) parts += tp
      else if (tdef.isMatchTypeAlias) {
        val matchType = tdef.aliasedType.map(_.asInstanceOf[ScMatchType])
        val upperBound = matchType.toOption.flatMap(_.upperBound)
        upperBound.foreach(collectParts(_))
      }

    def collectParts(tp: ScType, dealias: Boolean = true): Unit = {
      ProgressManager.checkCanceled()

      val tpWithRawTypesConverted = convertRawArgs(tp)
      if (!visited.add(tpWithRawTypesConverted))
        return

      if (dealias) {
        tp match {
          case AliasType(_, _, Right(t), _) => collectParts(t)
          case _                            => ()
        }
      }

      tp match {
        case ScDesignatorType(v: ScBindingPattern) => collectPartsTypeResult(v.`type`())
        case ScDesignatorType(v: ScFieldId)        => collectPartsTypeResult(v.`type`())
        case ScDesignatorType(p: ScParameter)      => collectPartsTypeResult(p.insideParamType)
        case ScCompoundType(comps, _, _)           => collectPartsIterable(comps)
        case ScAndType(lhs, rhs)                   => collectParts(lhs); collectParts(rhs)
        case ScOrType(lhs, rhs)                    => collectParts(lhs); collectParts(rhs)
        case ScDesignatorType(alias: ScTypeAliasDefinition) => collectTypeAliasDefinitionParts(tp, alias)
        case ScDesignatorType(alias: ScTypeAliasDeclaration) if alias.isInScala3File => parts += tp
        case ParameterizedType(a: ScAbstractType, args) =>
          collectParts(a)
          collectPartsIterable(args)
        case p @ ParameterizedType(des, args) =>
          val dealias = des match {
            //In scala 3 you can have parameterless type aliases designated to classes with
            //type parameters:
            //type Foo = ClassWithTypeParams; Foo[Int]
            //if tp = Foo[Int] we should not try to further
            //expand Foo into [T] ClassWithTypeParams[T]
            case DesignatorOwner(_: ScTypeAlias) => false
            case _                               => true
          }

          p.extractClassType match {
            case Some((clazz, subst)) =>
              parts += des
              collectParts(des, dealias = dealias)
              collectPartsIterable(args)
              collectPartsFromSuperTypes(clazz, subst)
            case _ =>
              collectParts(des, dealias = dealias)
              collectPartsIterable(args)
          }
        case j: JavaArrayType =>
          val parameterizedType = j.getParameterizedType
          collectParts(
            parameterizedType.getOrElse(
              return
            )
          )
        case proj @ ScProjectionType(projected, _) =>
          collectParts(projected)
          val element = proj.actualElement

          if (isAnchor(element)) collectTermsFromPath(projected)

          element match {
            case v: ScBindingPattern         => collectPartsTypeResult(v.`type`().map(proj.actualSubst))
            case v: ScFieldId                => collectPartsTypeResult(v.`type`().map(proj.actualSubst))
            case v: ScParameter              => collectPartsTypeResult(v.insideParamType.map(proj.actualSubst))
            case tdef: ScTypeAliasDefinition => collectTypeAliasDefinitionParts(tp, tdef)
            case v: ScTypeAliasDeclaration if v.isInScala3File => parts += tp
            case _                                             => ()
          }

          tp.extractClassType match {
            case Some((clazz, subst)) =>
              parts += tp
              collectPartsFromSuperTypes(clazz, subst)
            case _ =>
          }
        case ScAbstractType(_, _, upper) => collectParts(upper)
        case ScExistentialType(quant, _) => collectParts(quant)
        case tpt: TypeParameterType      => collectParts(tpt.upperType)
        case _ =>
          tp.extractClassType match {
            case Some((clazz, subst)) =>
              parts += tp
              val packagePrefix = clazz.parentOfType(classOf[ScPackageLike], strict = false)
              packagePrefix.foreach(processPackagePrefix)
              collectPartsFromSuperTypes(clazz, subst)
            case _ =>
          }
      }
    }

    collectParts(`type`)
    val res = mutable.HashMap.empty[String, Seq[ScType]]

    def addResult(fqn: String, tp: ScType)(implicit context: Context): Unit = {
      res.get(fqn) match {
        case Some(s) =>
          if (s.forall(!_.equiv(tp))) {
            res.remove(fqn)
            res += ((fqn, s :+ tp))
          }
        case None => res += ((fqn, Seq(tp)))
      }
    }

    def workWithTypeAlias(alias: ScTypeAlias, subst: ScSubstitutor = ScSubstitutor.empty)(implicit context: Context): Unit = alias match {
      case alias: ScTypeAliasDefinition =>
        if (alias.isEffectivelyOpaque) {
          for (fqn <- alias.qualifiedNameOpt;
               companionObject <- elementScope.getCachedObject(fqn)) {
            addResult(fqn, ScDesignatorType(companionObject))
          }
        } else {
          collectObjects(subst(alias.aliasedType.getOrAny))
        }
      case declaration: ScTypeAliasDeclaration if declaration.isInScala3File =>
        for (fqn <- alias.qualifiedNameOpt;
             companionObject <- elementScope.getCachedObject(fqn)) {
          addResult(fqn, ScDesignatorType(companionObject))
        }
      case _ =>
    }

    def collectObjects(tp: ScType)(implicit context: Context): Unit =
      tp match {
        case _ if tp.isAny =>
        case tp: StdType if stdTypes.contains(tp.name) =>
          elementScope
            .getCachedObject("scala." + tp.name)
            .foreach(o => addResult(o.qualifiedName, ScDesignatorType(o)))
        case ScDesignatorType(ta: ScTypeAlias) => workWithTypeAlias(ta)
        case ScProjectionType.withActual(ta: ScTypeAlias, actualSubst) => workWithTypeAlias(ta, actualSubst)
        case ParameterizedType(ScDesignatorType(ta: ScTypeAlias), args) =>
          val genericSubst = ScSubstitutor.bind(ta.typeParameters, args)
          workWithTypeAlias(ta, genericSubst)
        case ParameterizedType(ScProjectionType.withActual(ta: ScTypeAliasDefinition, actualSubst), args) =>
          val genericSubst = ScSubstitutor.bind(ta.typeParameters, args)
          val subst        = actualSubst.followed(genericSubst)
          workWithTypeAlias(ta, subst)
        case _ =>
          tp.extractClass match {
            case Some(obj: ScObject) => addResult(obj.qualifiedName, tp)
            case Some(clazz) =>
              getCompanionModule(clazz) match {
                case Some(obj: ScObject) =>
                  tp match {
                    case ScProjectionType(proj, _) =>
                      addResult(obj.qualifiedName, ScProjectionType(proj, obj))
                    case ParameterizedType(ScProjectionType(proj, _), _) =>
                      addResult(obj.qualifiedName, ScProjectionType(proj, obj))
                    case _ =>
                      addResult(obj.qualifiedName, ScDesignatorType(obj))
                  }
                case _ =>
              }
            case _ =>
          }
      }

    while (parts.nonEmpty) {
      collectObjects(parts.dequeue())
    }

    val objects = res.values.flatten.toSeq
    pathTerms.addAll(objects).toSeq
  }