in scala/scala-impl/src/org/jetbrains/plugins/scala/lang/psi/impl/toplevel/imports/ScImportStmtImpl.scala [109:471]
def processDeclarationForImportExpr(
processor: PsiScopeProcessor,
state: ResolveState,
place: PsiElement,
project: Project,
importOrExportStmt: ScImportOrExportStmt,
importExpr: ScImportExpr,
isScala3: Boolean,
): Boolean = {
implicit val context: Context = Context(place)
val ref = importExpr.reference match {
case Some(element) => element
// `import foo as bar` in Scala 3
case _ if isScala3 && importExpr.selectors.exists(_.isScala3StyleAliasImport) =>
ScalaPsiElementFactory.createReferenceFromText("_root_")(project)
case _ =>
return true
}
val nameHint = processor.getHint(NameHint.KEY).nullSafe
val name = nameHint.fold("")(_.getName(state))
if (name != "" && !importExpr.hasWildcardSelector && !importExpr.hasGivenSelector) {
val decodedName = clean(name)
val importedNames = importExpr.importedNames.map(clean)
if (!importedNames.contains(decodedName)) return true
}
val checkWildcardImports = processor match {
case r: ResolveProcessor =>
if (!r.checkImports()) return false
r.checkWildcardImports()
case _ => true
}
val qualifier: ScStableCodeReference =
importExpr.qualifier match {
case Some(element) => element
// `import foo as bar` in Scala 3
case _ if isScala3 && importExpr.selectors.exists(_.isScala3StyleAliasImport) =>
ScalaPsiElementFactory.createReferenceFromText("_root_")(project)
case _ =>
return true
}
val resolve = processor match {
case p: ResolveProcessor =>
ref match {
// do not process method refs when importing a type from a type
case ref: ScStableCodeReference
if p.kinds.contains(ResolveTargets.CLASS) &&
ref.getKinds(incomplete = false).contains(ResolveTargets.CLASS) &&
ref.getKinds(incomplete = false).contains(ResolveTargets.METHOD) =>
ref.resolveTypesOnly(false)
case ref: ScStableCodeReference if p.kinds.contains(ResolveTargets.METHOD) =>
ref.resolveMethodsOnly(false)
case _ => ref.multiResolveScala(false)
}
case _ => ref.multiResolveScala(false)
}
def isInPackageObject(element: PsiNamedElement): Boolean =
PsiTreeUtil.getContextOfType(element, true, classOf[ScTypeDefinition]) match {
case obj: ScObject if obj.isPackageObject => true
case _ => false
}
def resolvedQualifier(): Option[PsiElement] = qualifier.bind().map(_.element)
def qualifierType(checkPackageObject: Boolean): Option[ScType] =
resolvedQualifier().flatMap {
case p: PsiPackage =>
if (!checkPackageObject) None
else
ScalaShortNamesCacheManager
.getInstance(project)
//NOTE: note sure whether we need to take resolveScope from importsStmt and can't take it from importExpr
.findPackageObjectByName(p.getQualifiedName, importOrExportStmt.resolveScope)
.flatMap(_.`type`().toOption)
case _ => ScSimpleTypeElementImpl.calculateReferenceType(qualifier).toOption
}
def wildcardProxyProcessor(bp: BaseProcessor,
hasWildcard: Boolean,
shadowed: mutable.HashSet[(ScImportSelector, PsiElement)] = mutable.HashSet.empty,
givenImports: GivenImports = GivenImports.empty): BaseProcessor = {
assert(bp == processor)
class MyBaseProcessor extends BaseProcessor(bp.kinds)(project) {
override def getHint[T](hintKey: Key[T]): T = bp.getHint(hintKey)
override def isImplicitProcessor: Boolean = bp.isImplicitProcessor
override def handleEvent(event: PsiScopeProcessor.Event, associated: Object): Unit =
bp.handleEvent(event, associated)
override def getClassKind: Boolean = bp.getClassKind
override def setClassKind(b: Boolean): Unit = bp.setClassKind(b)
override protected def execute(namedElement: PsiNamedElement)
(implicit state: ResolveState): Boolean = {
if (shadowed.exists(p => ScEquivalenceUtil.smartEquivalence(namedElement, p._2))) {
return true
}
var newState = state
val importedByGiven = if (isScala3) {
// given and implicit elements are handled in a special way by given imports
val namedTypeable = namedElement match {
case ScGiven.Original(givenElement) => Some(givenElement)
case typeable: Typeable =>
// treat implicits as imported from wildcard if there is one
// TODO(SCL-21608):
// Scala 3 compiler has some more complex logic in CheckUnused phase.
// This will lead to given selectors being incorrectly marked as used in some cases
Option.unless(hasWildcard)(typeable)
case _ => None
}
namedTypeable match {
case Some(implicitElement) if ScalaPsiUtil.isImplicit(implicitElement) =>
// check if there are any given imports
if (!givenImports.hasImports) {
// no given selector at all, so don't import, because a normal wildcard doesn't import givens
return true
}
val decodedName = clean(implicitElement.name)
val importedNames = importExpr.importedNames.map(clean)
if (importedNames.contains(decodedName)) {
// already imported explicitly
return true
}
val adjustedElementType = implicitElement.`type`().map(state.substitutor)
givenImports.conformingGivenSelector(adjustedElementType) match {
case Some(conformingGivenSelector) =>
val additionalImportsUsed = new ImportSelectorUsed(conformingGivenSelector)
newState = newState.withImportsUsed(newState.importsUsed + additionalImportsUsed)
true
case None =>
// no selector conforms, so do not import
return true
}
case _ => false
}
} else false
if (!(importedByGiven || hasWildcard)) {
return true
}
val refType = qualifierType(isInPackageObject(namedElement))
newState = newState.withFromType(refType)
bp.execute(namedElement, newState)
}
}
if (!isScala3 && shadowed.isEmpty && !givenImports.hasImports) bp
else new MyBaseProcessor
}
val resolveIterator = resolve.iterator
while (resolveIterator.hasNext) {
@tailrec
def getFirstReference(ref: ScStableCodeReference): ScStableCodeReference =
ref.qualifier match {
case Some(qual) => getFirstReference(qual)
case _ => ref
}
val next = resolveIterator.next()
val elem = next.getElement
val importsUsed = getFirstReference(qualifier).bind().fold(next.importsUsed)(r => r.importsUsed ++ next.importsUsed)
val subst = state.substitutor.followed(next.substitutor)
(elem, processor) match {
case (pack: PsiPackage, completionProcessor: CompletionProcessor) if completionProcessor.includePrefixImports =>
val settings: ScalaCodeStyleSettings = ScalaCodeStyleSettings.getInstance(project)
val prefixImports = settings.getImportsWithPrefix.filter(s =>
!s.startsWith(ScalaCodeStyleSettings.EXCLUDE_PREFIX) &&
s.substring(0, s.lastIndexOf(".")) == pack.getQualifiedName
)
val excludeImports = settings.getImportsWithPrefix.filter(s =>
s.startsWith(ScalaCodeStyleSettings.EXCLUDE_PREFIX) &&
s.substring(ScalaCodeStyleSettings.EXCLUDE_PREFIX.length, s.lastIndexOf(".")) == pack.getQualifiedName
)
val names = new mutable.HashSet[String]()
for (prefixImport <- prefixImports) {
names += prefixImport.substring(prefixImport.lastIndexOf('.') + 1)
}
val excludeNames = new mutable.HashSet[String]()
for (prefixImport <- excludeImports) {
excludeNames += prefixImport.substring(prefixImport.lastIndexOf('.') + 1)
}
val wildcard = names.contains("_")
def isOK(name: String): Boolean =
if (wildcard) !excludeNames.contains(name)
else names.contains(name)
val newImportsUsed = importsUsed + new ImportExprUsed(importExpr)
val newState = state.withPrefixCompletion.withImportsUsed(newImportsUsed)
val importsProcessor = new BaseProcessor(StdKinds.stableImportSelector)(project) {
override protected def execute(namedElement: PsiNamedElement)
(implicit state: ResolveState): Boolean =
if (isOK(namedElement.name)) completionProcessor.execute(namedElement, state)
else true
override def getHint[T](hintKey: Key[T]): T = completionProcessor.getHint(hintKey)
}
elem.processDeclarations(importsProcessor, newState, importOrExportStmt, place)
case _ =>
}
ProgressManager.checkCanceled()
importExpr.selectorSet match {
case None =>
val importHasWildcardSelector = importExpr.hasWildcardSelector
// Update the set of used imports
val newImportsUsed = importsUsed + new ImportExprUsed(importExpr)
val refType = qualifierType(isInPackageObject(next.element))
val newState: ResolveState = state
.withImportsUsed(newImportsUsed)
.withSubstitutor(subst)
.withFromType(refType)
if (importHasWildcardSelector) {
if (!checkWildcardImports)
return true
val processed = (elem, refType, processor) match {
case (cl: PsiClass, _, processor: BaseProcessor) if !cl.is[ScTemplateDefinition] =>
processor.processType(ScDesignatorType.static(cl), place, newState)
case (_, Some(value), processor: BaseProcessor) =>
wildcardProxyProcessor(processor, hasWildcard = true)
.processType(value, place, newState)
case _ =>
elem.processDeclarations(processor, newState, importOrExportStmt, place)
}
if (!processed)
return false
}
else if (!processor.execute(elem, newState))
return false
case Some(set) =>
val shadowed = mutable.HashSet.empty[(ScImportSelector, PsiElement)]
val givenImports = GivenImports(set)
val selectors = set.selectors.iterator //for reducing stacktrace
while (selectors.hasNext) {
val selector = selectors.next()
ProgressManager.checkCanceled()
selector.reference match {
case Some(reference) =>
val isImportAlias = selector.isAliasedImport && !selector.importedName.contains(reference.refName)
if (isImportAlias) {
for (result <- reference.multiResolveScala(false)) {
//Resolve the name imported by selector
//Collect shadowed and aliased elements
shadowed += ((selector, result.getElement))
val importedName = selector.importedName.map(clean)
//processor should skip hiding imports
val isHidingImport = selector.aliasNameWithIgnoredHidingImport.isEmpty
if (!isHidingImport) {
val refType = qualifierType(isInPackageObject(result.element))
val newImportsUsed =
importsUsed + new ImportSelectorUsed(selector)
val newState =
state
.withRename(importedName)
.withImportsUsed(newImportsUsed)
.withSubstitutor(subst.followed(result.substitutor))
.withFromType(refType)
if (!processor.execute(result.getElement, newState)) {
return false
}
}
}
}
case _ =>
}
}
// There is total import from stable id
// import a.b.c.{d=>e, f=>_, _}
val hasWildcard = set.hasWildcard
if (hasWildcard || givenImports.hasImports) {
if (!checkWildcardImports) return true
processor match {
case bp: BaseProcessor =>
ProgressManager.checkCanceled()
// TODO(SCL-21608):
// Wildcard selector is always marked as used here.
// However, from compiler's point of view, in some cases everything might be imported from given selector,
// leaving the wildcard unused.
val wildcardExprUsed =
if (hasWildcard) Some(new ImportWildcardSelectorUsed(importExpr))
else None
val newImportsUsed =
importsUsed ++ wildcardExprUsed
val newState =
state
.withImportsUsed(newImportsUsed)
.withSubstitutor(subst)
(elem, processor) match {
case (cl: PsiClass, processor: BaseProcessor) if hasWildcard && !cl.is[ScTemplateDefinition] =>
val qualType = qualifierType(isInPackageObject(next.element))
if (!processor.processType(ScDesignatorType.static(cl), place, newState.withFromType(qualType)))
return false
case _ =>
// In this case import optimizer should check for used selectors
if (!elem.processDeclarations(wildcardProxyProcessor(bp, hasWildcard = hasWildcard, shadowed, givenImports), newState, importOrExportStmt, place))
return false
}
case _ =>
}
}
//wildcard import first, to show that this imports are unused if they really are
for(selector <- set.selectors) {
if (!selector.isAliasedImport || selector.importedName == selector.reference.map(_.refName)) {
ProgressManager.checkCanceled()
for {
element <- selector.reference
result <- element.multiResolveScala(false)
} {
val rSubst = result.substitutor
val newImportsUsed =
importsUsed + new ImportSelectorUsed(selector)
val newState =
state
.withImportsUsed(newImportsUsed)
.withSubstitutor(subst.followed(rSubst))
.withFromType(qualifierType(isInPackageObject(result.element)))
if (!processor.execute(result.getElement, newState))
return false
}
}
}
}
}
true
}