Introduce FIR resolution API with lazy resolve to use in IDE
Now lazy resolve atomic element is a file (declaration-level resolve) or a callable declaration (expression-level resolve) #KT-24351 In Progress
This commit is contained in:
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.fir.resolve.FirSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirCompositeSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirDependenciesSymbolProviderImpl
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirLibrarySymbolProviderImpl
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirProviderImpl
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirClassDeclaredMemberScopeProvider
|
||||
import org.jetbrains.kotlin.fir.types.FirCorrespondingSupertypesCache
|
||||
import org.jetbrains.kotlin.load.java.JavaClassFinder
|
||||
@@ -33,6 +34,8 @@ class FirJavaModuleBasedSession(
|
||||
|
||||
init {
|
||||
sessionProvider.sessionCache[moduleInfo] = this
|
||||
val firProvider = FirProviderImpl(this)
|
||||
registerComponent(FirProvider::class, firProvider)
|
||||
|
||||
registerComponent(
|
||||
FirSymbolProvider::class,
|
||||
|
||||
@@ -169,7 +169,8 @@ class RawFirBuilder(val session: FirSession, val stubMode: Boolean) {
|
||||
}
|
||||
else -> {
|
||||
val result = { bodyExpression }.toFirExpression("Function has no body (but should)")
|
||||
FirSingleExpressionBlock(result.toReturn())
|
||||
// basePsi is null, because 'return' is synthetic & should not be bound to some PSI
|
||||
FirSingleExpressionBlock(result.toReturn(basePsi = null))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,8 @@ import org.jetbrains.kotlin.fir.references.FirResolvedCallableReferenceImpl
|
||||
import org.jetbrains.kotlin.fir.references.FirSimpleNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.*
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.FirBodyResolveTransformer
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.*
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.StoreNameReference
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.firUnsafe
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.resultType
|
||||
import org.jetbrains.kotlin.fir.resolve.typeForQualifier
|
||||
import org.jetbrains.kotlin.fir.resolve.typeFromCallee
|
||||
@@ -226,8 +225,10 @@ class FirCallResolver(
|
||||
val coneSymbol = candidate.symbol
|
||||
when {
|
||||
coneSymbol is FirBackingFieldSymbol -> FirBackingFieldReferenceImpl(psi, coneSymbol)
|
||||
coneSymbol is FirVariableSymbol &&
|
||||
(coneSymbol !is FirPropertySymbol || coneSymbol.firUnsafe<FirMemberDeclaration>().typeParameters.isEmpty()) ->
|
||||
coneSymbol is FirVariableSymbol && (
|
||||
coneSymbol !is FirPropertySymbol ||
|
||||
(coneSymbol.phasedFir(session) as FirMemberDeclaration).typeParameters.isEmpty()
|
||||
) ->
|
||||
FirResolvedCallableReferenceImpl(psi, name, coneSymbol)
|
||||
else -> FirNamedReferenceWithCandidate(psi, name, candidate)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirClassDeclaredMemberScopeProvider
|
||||
abstract class FirModuleBasedSession(override val moduleInfo: ModuleInfo, sessionProvider: FirSessionProvider?) :
|
||||
FirSessionBase(sessionProvider) {
|
||||
init {
|
||||
val firProvider = FirProviderImpl(this)
|
||||
registerComponent(FirProvider::class, firProvider)
|
||||
registerComponent(FirQualifierResolver::class, FirQualifierResolverImpl(this))
|
||||
registerComponent(FirTypeResolver::class, FirTypeResolverImpl(this))
|
||||
registerComponent(FirClassDeclaredMemberScopeProvider::class, FirClassDeclaredMemberScopeProvider())
|
||||
|
||||
@@ -178,7 +178,7 @@ fun BodyResolveComponents.typeForQualifier(resolvedQualifier: FirResolvedQualifi
|
||||
val resultType = resolvedQualifier.resultType
|
||||
if (classId != null) {
|
||||
val classSymbol = symbolProvider.getClassLikeSymbolByFqName(classId)!!
|
||||
val declaration = classSymbol.fir
|
||||
val declaration = classSymbol.phasedFir
|
||||
if (declaration is FirClass) {
|
||||
if (declaration.classKind == ClassKind.OBJECT) {
|
||||
return resultType.resolvedTypeFromPrototype(
|
||||
@@ -235,7 +235,7 @@ fun <T : FirQualifiedAccess> BodyResolveComponents.typeFromCallee(access: T): Fi
|
||||
private fun BodyResolveComponents.typeFromSymbol(symbol: ConeSymbol, makeNullable: Boolean): FirResolvedTypeRef {
|
||||
return when (symbol) {
|
||||
is FirCallableSymbol<*> -> {
|
||||
val returnType = returnTypeCalculator.tryCalculateReturnType(symbol.fir)
|
||||
val returnType = returnTypeCalculator.tryCalculateReturnType(symbol.phasedFir)
|
||||
if (makeNullable) {
|
||||
returnType.withReplacedConeType(
|
||||
returnType.coneTypeUnsafe<ConeKotlinType>().withNullability(ConeNullability.NULLABLE)
|
||||
@@ -245,7 +245,7 @@ private fun BodyResolveComponents.typeFromSymbol(symbol: ConeSymbol, makeNullabl
|
||||
}
|
||||
}
|
||||
is ConeClassifierSymbol -> {
|
||||
val fir = (symbol as? AbstractFirBasedSymbol<*>)?.fir
|
||||
val fir = (symbol as? AbstractFirBasedSymbol<*>)?.phasedFir
|
||||
// TODO: unhack
|
||||
if (fir is FirEnumEntry) {
|
||||
(fir.superTypeRefs.firstOrNull() as? FirResolvedTypeRef) ?: FirErrorTypeRefImpl(
|
||||
|
||||
@@ -7,7 +7,12 @@ package org.jetbrains.kotlin.fir.resolve
|
||||
|
||||
import com.google.common.collect.SetMultimap
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.phasedFir
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -21,4 +26,7 @@ interface BodyResolveComponents : SessionHolder {
|
||||
val labels: SetMultimap<Name, ConeKotlinType>
|
||||
val noExpectedType: FirTypeRef
|
||||
val symbolProvider: FirSymbolProvider
|
||||
|
||||
val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
|
||||
get() = phasedFir(session, FirResolvePhase.DECLARATIONS)
|
||||
}
|
||||
+11
-18
@@ -30,26 +30,10 @@ abstract class FirAbstractPhaseTransformer<D>(
|
||||
}
|
||||
}
|
||||
|
||||
val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
|
||||
open val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
|
||||
get() {
|
||||
val result = this.fir
|
||||
val requiredPhase = transformerPhase.prev
|
||||
val availablePhase = result.resolvePhase
|
||||
if (availablePhase < requiredPhase) {
|
||||
var resolvePhase = availablePhase.next
|
||||
val provider = FirProvider.getInstance(session)
|
||||
val containingFile = when (this) {
|
||||
is ConeCallableSymbol -> provider.getFirCallableContainerFile(this)
|
||||
is ConeClassLikeSymbol -> provider.getFirClassifierContainerFile(this)
|
||||
else -> null
|
||||
} ?: throw AssertionError("Cannot get container file by symbol: $this (${result.render()})")
|
||||
do {
|
||||
val phaseTransformer = resolvePhase.createTransformerByPhase()
|
||||
containingFile.transform<FirFile, Nothing?>(phaseTransformer, null)
|
||||
resolvePhase = resolvePhase.next
|
||||
} while (resolvePhase <= requiredPhase)
|
||||
}
|
||||
return result
|
||||
return phasedFir(session, requiredPhase)
|
||||
}
|
||||
|
||||
override fun transformDeclaration(declaration: FirDeclaration, data: D): CompositeTransformResult<FirDeclaration> {
|
||||
@@ -57,4 +41,13 @@ abstract class FirAbstractPhaseTransformer<D>(
|
||||
|
||||
return super.transformDeclaration(declaration, data)
|
||||
}
|
||||
}
|
||||
|
||||
fun FirFile.runResolve(toPhase: FirResolvePhase, fromPhase: FirResolvePhase = FirResolvePhase.RAW_FIR) {
|
||||
var currentPhase = fromPhase
|
||||
while (currentPhase < toPhase) {
|
||||
currentPhase = currentPhase.next
|
||||
val phaseTransformer = currentPhase.createTransformerByPhase()
|
||||
transform<FirFile, Nothing?>(phaseTransformer, null)
|
||||
}
|
||||
}
|
||||
+18
-7
@@ -21,10 +21,7 @@ import org.jetbrains.kotlin.fir.scopes.addImportingScopes
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirLocalScope
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirTopLevelDeclaredMemberScope
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.withReplacedConeType
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
|
||||
import org.jetbrains.kotlin.fir.symbols.invoke
|
||||
import org.jetbrains.kotlin.fir.symbols.*
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.fir.types.impl.*
|
||||
import org.jetbrains.kotlin.fir.visitors.CompositeTransformResult
|
||||
@@ -83,6 +80,12 @@ open class FirBodyResolveTransformer(
|
||||
resolutionStageRunner
|
||||
)
|
||||
|
||||
override val <D> AbstractFirBasedSymbol<D>.phasedFir: D where D : FirDeclaration, D : FirSymbolOwner<D>
|
||||
get() {
|
||||
val requiredPhase = transformerPhase.prev
|
||||
return phasedFir(session, requiredPhase)
|
||||
}
|
||||
|
||||
override fun transformFile(file: FirFile, data: Any?): CompositeTransformResult<FirFile> {
|
||||
packageFqName = file.packageFqName
|
||||
this.file = file
|
||||
@@ -778,9 +781,17 @@ private fun inferenceComponents(session: FirSession, returnTypeCalculator: Retur
|
||||
}, session, returnTypeCalculator, scopeSession)
|
||||
|
||||
|
||||
class FirDesignatedBodyResolveTransformer(val designation: Iterator<FirElement>, session: FirSession, scopeSession: ScopeSession) :
|
||||
FirBodyResolveTransformer(session, phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE, implicitTypeOnly = true, scopeSession = scopeSession) {
|
||||
|
||||
class FirDesignatedBodyResolveTransformer(
|
||||
private val designation: Iterator<FirElement>,
|
||||
session: FirSession,
|
||||
scopeSession: ScopeSession = ScopeSession(),
|
||||
implicitTypeOnly: Boolean = true
|
||||
) : FirBodyResolveTransformer(
|
||||
session,
|
||||
phase = FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE,
|
||||
implicitTypeOnly = implicitTypeOnly,
|
||||
scopeSession = scopeSession
|
||||
) {
|
||||
override fun <E : FirElement> transformElement(element: E, data: Any?): CompositeTransformResult<E> {
|
||||
if (designation.hasNext()) {
|
||||
designation.next().visitNoTransform(this, data)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.resolve.transformers
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirSymbolOwner
|
||||
|
||||
fun <D> AbstractFirBasedSymbol<D>.phasedFir(
|
||||
session: FirSession,
|
||||
requiredPhase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): D where D : FirDeclaration, D : FirSymbolOwner<D> {
|
||||
val result = this.fir
|
||||
val availablePhase = result.resolvePhase
|
||||
if (availablePhase < requiredPhase) {
|
||||
val provider = FirProvider.getInstance(session)
|
||||
val containingFile = when (this) {
|
||||
is ConeCallableSymbol -> provider.getFirCallableContainerFile(this)
|
||||
is ConeClassLikeSymbol -> provider.getFirClassifierContainerFile(this)
|
||||
else -> null
|
||||
} ?: throw AssertionError("Cannot get container file by symbol: $this (${result.render()})")
|
||||
containingFile.runResolve(toPhase = requiredPhase, fromPhase = availablePhase)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import org.jetbrains.kotlin.idea.editor.AbstractMultiLineStringIndentTest
|
||||
import org.jetbrains.kotlin.idea.editor.backspaceHandler.AbstractBackspaceHandlerTest
|
||||
import org.jetbrains.kotlin.idea.editor.quickDoc.AbstractQuickDocProviderTest
|
||||
import org.jetbrains.kotlin.idea.filters.AbstractKotlinExceptionFilterTest
|
||||
import org.jetbrains.kotlin.idea.fir.AbstractFirLazyResolveTest
|
||||
import org.jetbrains.kotlin.idea.fir.AbstractFirMultiModuleResolveTest
|
||||
import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest
|
||||
import org.jetbrains.kotlin.idea.hierarchy.AbstractHierarchyTest
|
||||
@@ -817,6 +818,10 @@ fun main(args: Array<String>) {
|
||||
testClass<AbstractFirMultiModuleResolveTest> {
|
||||
model("fir/multiModule", recursive = false, extension = null)
|
||||
}
|
||||
|
||||
testClass<AbstractFirLazyResolveTest> {
|
||||
model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
testGroup("idea/idea-maven/test", "idea/idea-maven/testData") {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.analyzer.ModuleInfo
|
||||
import org.jetbrains.kotlin.fir.FirModuleBasedSession
|
||||
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
|
||||
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
|
||||
import org.jetbrains.kotlin.fir.java.JavaSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.FirSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirCompositeSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirDependenciesSymbolProviderImpl
|
||||
import org.jetbrains.kotlin.fir.service
|
||||
import org.jetbrains.kotlin.fir.types.FirCorrespondingSupertypesCache
|
||||
|
||||
|
||||
class FirIdeJavaModuleBasedSession(
|
||||
project: Project,
|
||||
moduleInfo: ModuleInfo,
|
||||
sessionProvider: FirProjectSessionProvider,
|
||||
scope: GlobalSearchScope,
|
||||
dependenciesProvider: FirSymbolProvider? = null
|
||||
) : FirModuleBasedSession(moduleInfo, sessionProvider) {
|
||||
|
||||
|
||||
init {
|
||||
sessionProvider.sessionCache[moduleInfo] = this
|
||||
registerComponent(
|
||||
FirProvider::class,
|
||||
IdeFirProvider(project, scope, RawFirBuilder(this, stubMode = false), this)
|
||||
)
|
||||
registerComponent(
|
||||
FirSymbolProvider::class,
|
||||
FirCompositeSymbolProvider(
|
||||
listOf(
|
||||
service<FirProvider>(),
|
||||
JavaSymbolProvider(this, sessionProvider.project, scope),
|
||||
dependenciesProvider ?: FirDependenciesSymbolProviderImpl(this)
|
||||
)
|
||||
) as FirSymbolProvider
|
||||
)
|
||||
|
||||
registerComponent(
|
||||
FirCorrespondingSupertypesCache::class,
|
||||
FirCorrespondingSupertypesCache(this)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirReference
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.*
|
||||
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
|
||||
import org.jetbrains.kotlin.fir.scopes.impl.FirTopLevelDeclaredMemberScope
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
private val FirResolvePhase.stubMode: Boolean
|
||||
get() = this <= FirResolvePhase.DECLARATIONS
|
||||
|
||||
private fun KtClassOrObject.relativeFqName(): FqName {
|
||||
val className = this.nameAsSafeName
|
||||
val parentFqName = this.containingClassOrObject?.relativeFqName()
|
||||
return parentFqName?.child(className) ?: FqName.topLevel(className)
|
||||
}
|
||||
|
||||
private fun FirFile.findCallableMember(
|
||||
provider: FirProvider, callableMember: KtCallableDeclaration,
|
||||
packageFqName: FqName, klassFqName: FqName?, declName: Name
|
||||
): FirCallableMemberDeclaration<*> {
|
||||
val memberScope =
|
||||
if (klassFqName == null) FirTopLevelDeclaredMemberScope(this, session)
|
||||
else provider.getClassDeclaredMemberScope(ClassId(packageFqName, klassFqName, false))!!
|
||||
var result: FirCallableMemberDeclaration<*>? = null
|
||||
val processor = { symbol: ConeCallableSymbol ->
|
||||
val firSymbol = symbol as? FirBasedSymbol<*>
|
||||
val fir = firSymbol?.fir as? FirCallableMemberDeclaration<*>
|
||||
if (fir?.psi == callableMember) {
|
||||
result = fir
|
||||
ProcessorAction.STOP
|
||||
} else {
|
||||
ProcessorAction.NEXT
|
||||
}
|
||||
}
|
||||
if (callableMember is KtNamedFunction) {
|
||||
memberScope.processFunctionsByName(declName, processor)
|
||||
} else {
|
||||
memberScope.processPropertiesByName(declName, processor)
|
||||
}
|
||||
|
||||
return result!!
|
||||
}
|
||||
|
||||
fun KtCallableDeclaration.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): FirCallableMemberDeclaration<*> {
|
||||
val session = state.getSession(this)
|
||||
|
||||
val file = this.containingKtFile
|
||||
val packageFqName = file.packageFqName
|
||||
val klassFqName = this.containingClassOrObject?.relativeFqName()
|
||||
val declName = this.nameAsSafeName
|
||||
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getOrBuildFile(file)
|
||||
val memberSymbol = firFile.findCallableMember(firProvider, this, packageFqName, klassFqName, declName).symbol
|
||||
memberSymbol.fir.runResolve(firFile, firProvider, phase, state)
|
||||
return memberSymbol.fir
|
||||
}
|
||||
|
||||
fun KtClassOrObject.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.DECLARATIONS
|
||||
): FirRegularClass {
|
||||
val session = state.getSession(this)
|
||||
|
||||
val file = this.containingKtFile
|
||||
val packageFqName = file.packageFqName
|
||||
val klassFqName = this.relativeFqName()
|
||||
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getOrBuildFile(file)
|
||||
val firClass = firProvider.getFirClassifierByFqName(ClassId(packageFqName, klassFqName, false)) as FirRegularClass
|
||||
firClass.runResolve(firFile, firProvider, phase, state)
|
||||
return firClass
|
||||
}
|
||||
|
||||
private fun FirDeclaration.runResolve(
|
||||
file: FirFile,
|
||||
firProvider: IdeFirProvider,
|
||||
toPhase: FirResolvePhase,
|
||||
state: FirResolveState
|
||||
) {
|
||||
val nonLazyPhase = minOf(toPhase, FirResolvePhase.DECLARATIONS)
|
||||
file.runResolve(toPhase = nonLazyPhase, fromPhase = this.resolvePhase)
|
||||
if (toPhase > nonLazyPhase) {
|
||||
val designation = mutableListOf<FirElement>()
|
||||
designation += file
|
||||
val id = when (this) {
|
||||
is FirCallableDeclaration<*> -> {
|
||||
this.symbol.callableId.classId
|
||||
}
|
||||
is FirRegularClass -> {
|
||||
this.symbol.classId
|
||||
}
|
||||
else -> error("Unsupported: ${render()}")
|
||||
}
|
||||
val outerClasses = generateSequence(id) { classId ->
|
||||
classId.outerClassId
|
||||
}.mapTo(mutableListOf()) { firProvider.getFirClassifierByFqName(it)!! }
|
||||
designation += outerClasses.asReversed()
|
||||
if (this is FirCallableDeclaration<*>) {
|
||||
designation += this
|
||||
}
|
||||
val transformer = FirDesignatedBodyResolveTransformer(
|
||||
designation.iterator(), state.getSession(psi as KtElement),
|
||||
implicitTypeOnly = toPhase == FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE
|
||||
)
|
||||
file.transform<FirFile, Nothing?>(transformer, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE
|
||||
): FirElement {
|
||||
val containerFir: FirDeclaration =
|
||||
when (val container = this.containingDeclarationForPseudocode ?: error("No containing declaration: $text")) {
|
||||
is KtCallableDeclaration -> container.getOrBuildFir(state, phase)
|
||||
is KtClassOrObject -> container.getOrBuildFir(state, phase)
|
||||
else -> error("Unsupported: ${container.text}")
|
||||
}
|
||||
|
||||
val psi = when (this) {
|
||||
is KtPropertyDelegate -> this.expression ?: this
|
||||
else -> this
|
||||
}
|
||||
return state[this] ?: run {
|
||||
containerFir.accept(object : FirVisitorVoid() {
|
||||
override fun visitElement(element: FirElement) {
|
||||
(element.psi as? KtElement)?.let {
|
||||
state.record(it, element)
|
||||
}
|
||||
element.acceptChildren(this)
|
||||
}
|
||||
|
||||
override fun visitReference(reference: FirReference) {}
|
||||
|
||||
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef) {}
|
||||
})
|
||||
var current: PsiElement? = psi
|
||||
while (current is KtElement) {
|
||||
val mappedFir = state[current]
|
||||
if (mappedFir != null) {
|
||||
if (current != this) {
|
||||
state.record(current, mappedFir)
|
||||
}
|
||||
return mappedFir
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
error("FirElement is not found for: $text")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSessionProvider
|
||||
import org.jetbrains.kotlin.fir.dependenciesWithoutSelf
|
||||
import org.jetbrains.kotlin.fir.java.FirLibrarySession
|
||||
import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider
|
||||
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.ModuleSourceInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.isLibraryClasses
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.IDEPackagePartProvider
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
|
||||
private fun createLibrarySession(moduleInfo: IdeaModuleInfo, project: Project, provider: FirProjectSessionProvider): FirLibrarySession {
|
||||
val contentScope = moduleInfo.contentScope()
|
||||
return FirLibrarySession.create(moduleInfo, provider, contentScope, project, IDEPackagePartProvider(contentScope))
|
||||
}
|
||||
|
||||
interface FirResolveState {
|
||||
val sessionProvider: FirSessionProvider
|
||||
|
||||
fun getSession(psi: KtElement): FirSession {
|
||||
val sessionProvider = sessionProvider as FirProjectSessionProvider
|
||||
val moduleInfo = psi.getModuleInfo() as ModuleSourceInfo
|
||||
return sessionProvider.getSession(moduleInfo) ?: FirIdeJavaModuleBasedSession(
|
||||
psi.project, moduleInfo, sessionProvider, moduleInfo.contentScope()
|
||||
).also {
|
||||
val ideaModuleInfo = moduleInfo.cast<IdeaModuleInfo>()
|
||||
ideaModuleInfo.dependenciesWithoutSelf().forEach {
|
||||
if (it is IdeaModuleInfo && it.isLibraryClasses()) {
|
||||
createLibrarySession(it, psi.project, sessionProvider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(psi: KtElement): FirElement?
|
||||
|
||||
fun record(psi: KtElement, fir: FirElement)
|
||||
}
|
||||
|
||||
class FirResolveStateImpl(override val sessionProvider: FirSessionProvider) : FirResolveState {
|
||||
private val cache = mutableMapOf<KtElement, FirElement>()
|
||||
|
||||
override fun get(psi: KtElement): FirElement? = cache[psi]
|
||||
|
||||
override fun record(psi: KtElement, fir: FirElement) {
|
||||
cache[psi] = fir
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: caching
|
||||
object FirIdeResolveFactory {
|
||||
fun initiate(psi: KtElement): FirResolveState {
|
||||
val provider = FirProjectSessionProvider(psi.project)
|
||||
return FirResolveStateImpl(provider)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtElement.firResolveState(): FirResolveState = FirIdeResolveFactory.initiate(this)
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.builder.RawFirBuilder
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.fir.resolve.ScopeSession
|
||||
import org.jetbrains.kotlin.fir.resolve.impl.FirProviderImpl
|
||||
import org.jetbrains.kotlin.fir.scopes.FirScope
|
||||
import org.jetbrains.kotlin.fir.symbols.ConeCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.idea.stubindex.*
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class IdeFirProvider(
|
||||
val project: Project,
|
||||
val scope: GlobalSearchScope,
|
||||
val builder: RawFirBuilder,
|
||||
val session: FirSession
|
||||
) : FirProvider() {
|
||||
private val cacheProvider = FirProviderImpl(session)
|
||||
// TODO: invalidation?
|
||||
private val files = mutableMapOf<KtFile, FirFile>()
|
||||
|
||||
override fun getFirClassifierByFqName(fqName: ClassId): FirClassLikeDeclaration<*>? {
|
||||
return cacheProvider.getFirClassifierByFqName(fqName) ?: run {
|
||||
|
||||
val classes = KotlinFullClassNameIndex.getInstance().get(fqName.asSingleFqName().asString(), project, scope)
|
||||
val ktClass = classes.firstOrNull {
|
||||
fqName.packageFqName == it.containingKtFile.packageFqName
|
||||
} ?: return null // TODO: what if two of them?
|
||||
val ktFile = ktClass.containingKtFile
|
||||
|
||||
getOrBuildFile(ktFile)
|
||||
|
||||
cacheProvider.getFirClassifierByFqName(fqName)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrBuildFile(ktFile: KtFile): FirFile {
|
||||
return files.getOrPut(ktFile) {
|
||||
val file = builder.buildFirFile(ktFile)
|
||||
cacheProvider.recordFile(file)
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
fun getFile(ktFile: KtFile): FirFile? = files[ktFile]
|
||||
|
||||
override fun getClassLikeSymbolByFqName(classId: ClassId): FirClassLikeSymbol<*>? {
|
||||
return getFirClassifierByFqName(classId)?.symbol
|
||||
}
|
||||
|
||||
override fun getTopLevelCallableSymbols(packageFqName: FqName, name: Name): List<FirCallableSymbol<*>> {
|
||||
val packagePrefix = if (packageFqName.isRoot) "" else "$packageFqName."
|
||||
val topLevelFunctions = KotlinTopLevelFunctionFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
|
||||
val topLevelProperties = KotlinTopLevelPropertyFqnNameIndex.getInstance()["$packagePrefix$name", project, scope]
|
||||
topLevelFunctions.forEach { getOrBuildFile(it.containingKtFile) }
|
||||
topLevelProperties.forEach { getOrBuildFile(it.containingKtFile) }
|
||||
return cacheProvider.getTopLevelCallableSymbols(packageFqName, name)
|
||||
}
|
||||
|
||||
override fun getFirClassifierContainerFile(fqName: ClassId): FirFile {
|
||||
getFirClassifierByFqName(fqName)
|
||||
return cacheProvider.getFirClassifierContainerFile(fqName)
|
||||
}
|
||||
|
||||
override fun getFirCallableContainerFile(symbol: ConeCallableSymbol): FirFile? {
|
||||
return cacheProvider.getFirCallableContainerFile(symbol)
|
||||
}
|
||||
|
||||
override fun getFirFilesByPackage(fqName: FqName): List<FirFile> {
|
||||
val files = KotlinExactPackagesIndex.getInstance()[fqName.asString(), project, scope]
|
||||
files.forEach { getOrBuildFile(it) }
|
||||
return cacheProvider.getFirFilesByPackage(fqName)
|
||||
}
|
||||
|
||||
override fun getClassDeclaredMemberScope(classId: ClassId): FirScope? {
|
||||
getFirClassifierByFqName(classId)
|
||||
return cacheProvider.getClassDeclaredMemberScope(classId)
|
||||
}
|
||||
|
||||
override fun getClassUseSiteMemberScope(classId: ClassId, useSiteSession: FirSession, scopeSession: ScopeSession): FirScope? {
|
||||
getFirClassifierByFqName(classId)
|
||||
return cacheProvider.getClassUseSiteMemberScope(classId, useSiteSession, scopeSession)
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@ dependencies {
|
||||
compile(project(":idea:idea-jps-common"))
|
||||
compile(project(":plugins:android-extensions-compiler"))
|
||||
compile(project(":kotlin-scripting-compiler-impl"))
|
||||
compile(project(":compiler:fir:psi2fir"))
|
||||
compile(project(":compiler:fir:fir2ir"))
|
||||
compile(project(":compiler:fir:resolve"))
|
||||
compile(project(":compiler:fir:java"))
|
||||
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
|
||||
compile(commonDep("org.jetbrains.kotlinx", "kotlinx-coroutines-jdk8")) { isTransitive = false }
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
fun callMe() = 42
|
||||
|
||||
fun other() {
|
||||
val x = 1
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
FILE: callMe.kt
|
||||
public final fun callMe(): R|kotlin/Int| {
|
||||
^callMe Int(42)
|
||||
}
|
||||
public final fun other(): R|kotlin/Unit| {
|
||||
lval x: <implicit> = Int(1)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fun main() {
|
||||
<caret>callMe()
|
||||
foo()
|
||||
bar(1, 2)
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
val y = 2.0
|
||||
}
|
||||
|
||||
fun bar(x: Int, y: Int) = x + y
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
FILE: main.kt
|
||||
public final fun main(): R|kotlin/Unit| {
|
||||
R|/callMe|()
|
||||
R|/foo|()
|
||||
R|/bar|(Int(1), Int(2))
|
||||
}
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lval y: <implicit> = Double(2.0)
|
||||
}
|
||||
public final fun bar(x: R|kotlin/Int|, y: R|kotlin/Int|): R|kotlin/Int| {
|
||||
^bar R|<local>/x|.R|kotlin/Int.plus|(R|<local>/y|)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
R|/callMe|()
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mainFile": "main.kt"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package root
|
||||
|
||||
fun foo() = <caret>myProperty
|
||||
@@ -0,0 +1,4 @@
|
||||
FILE: main.kt
|
||||
public final fun foo(): R|kotlin/Int| {
|
||||
^foo R|root/myProperty|
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package root
|
||||
|
||||
val myProperty get() = 42
|
||||
@@ -0,0 +1,5 @@
|
||||
FILE: myProperty.kt
|
||||
public final val myProperty: R|kotlin/Int|
|
||||
public get(): R|kotlin/Int| {
|
||||
^ Int(42)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
R|root/myProperty|
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mainFile": "main.kt"
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.search.FileTypeIndex
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.fir.render
|
||||
import org.jetbrains.kotlin.fir.resolve.FirProvider
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.project.IdeaModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.productionSourceInfo
|
||||
import org.jetbrains.kotlin.idea.jsonUtils.getString
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractFirLazyResolveTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
if (KotlinTestUtils.isAllFilesPresentTest(getTestName(false))) return super.getProjectDescriptor()
|
||||
val testFile = File(testDataPath, fileName())
|
||||
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
|
||||
val withRuntime = config["withRuntime"]?.asBoolean ?: false
|
||||
return if (withRuntime)
|
||||
KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
else
|
||||
KotlinLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
|
||||
fun doTest(path: String) {
|
||||
val testFile = File(path)
|
||||
val config = JsonParser().parse(FileUtil.loadFile(testFile, true)) as JsonObject
|
||||
|
||||
val mainFilePath = config.getString("mainFile")
|
||||
val mainFile = File(testFile.parent, mainFilePath)
|
||||
val mainFileText = FileUtil.loadFile(mainFile, true)
|
||||
TestCase.assertTrue("\"<caret>\" is missing in file \"$mainFilePath\"", mainFileText.contains("<caret>"))
|
||||
|
||||
val projectDir = path.removePrefix(testDataPath).substringBeforeLast('/')
|
||||
myFixture.copyDirectoryToProject(projectDir, "")
|
||||
PsiDocumentManager.getInstance(myFixture.project).commitAllDocuments()
|
||||
|
||||
myFixture.configureFromTempProjectFile(mainFilePath)
|
||||
val referenceToResolve = myFixture.getReferenceAtCaretPositionWithAssertion(mainFilePath)
|
||||
val elementToResolve = referenceToResolve.element
|
||||
val expressionToResolve = when {
|
||||
elementToResolve.parent is KtCallExpression -> elementToResolve.parent
|
||||
else -> elementToResolve
|
||||
} as KtExpression
|
||||
|
||||
val resolveState = expressionToResolve.firResolveState()
|
||||
val firExpression = expressionToResolve.getOrBuildFir(resolveState)
|
||||
|
||||
val resultsDump = firExpression.render()
|
||||
KotlinTestUtils.assertEqualsToFile(File(testFile.parent, "results.txt"), resultsDump)
|
||||
|
||||
fun expectedTxtPath(virtualFile: VirtualFile): String {
|
||||
val virtualPath = virtualFile.path.substringAfter("/src/")
|
||||
var result: String? = null
|
||||
val root = File(path).parentFile
|
||||
for (file in root.walkTopDown()) {
|
||||
if (!file.isDirectory && virtualPath in file.absolutePath) {
|
||||
result = file.absolutePath.replace(".kt", ".txt")
|
||||
}
|
||||
}
|
||||
return result!!
|
||||
}
|
||||
|
||||
val moduleInfo = project.allModules().single().productionSourceInfo() as IdeaModuleInfo
|
||||
val contentScope = moduleInfo.contentScope()
|
||||
val files = FileTypeIndex.getFiles(KotlinFileType.INSTANCE, contentScope)
|
||||
for (file in files) {
|
||||
val psiFile = psiManager.findFile(file) as KtFile
|
||||
val session = resolveState.getSession(psiFile)
|
||||
val firProvider = FirProvider.getInstance(session) as IdeFirProvider
|
||||
val firFile = firProvider.getFile(psiFile) ?: continue
|
||||
KotlinTestUtils.assertEqualsToFile(File(expectedTxtPath(file)), firFile.render())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.fir;
|
||||
|
||||
import com.intellij.testFramework.TestDataPath;
|
||||
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TargetBackend;
|
||||
import org.jetbrains.kotlin.test.TestMetadata;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/fir/lazyResolve")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public class FirLazyResolveTestGenerated extends AbstractFirLazyResolveTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInLazyResolve() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/fir/lazyResolve"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY);
|
||||
}
|
||||
|
||||
@TestMetadata("simple/simple.test")
|
||||
public void testSimple_Simple() throws Exception {
|
||||
runTest("idea/testData/fir/lazyResolve/simple/simple.test");
|
||||
}
|
||||
|
||||
@TestMetadata("simpleProperty/simpleProperty.test")
|
||||
public void testSimpleProperty_SimpleProperty() throws Exception {
|
||||
runTest("idea/testData/fir/lazyResolve/simpleProperty/simpleProperty.test");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user