From 7fc3e60854497fbf11189b18eba2efcf37a0cbf2 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Sat, 29 Jul 2023 00:56:16 +0200 Subject: [PATCH] K2: add JVM checker for NO_REFLECTION_IN_CLASS_PATH warning It's based on the existing K1 checker `JvmReflectionAPICallChecker`. #KT-60587 Fixed --- .../diagnostics/KtFirDataClassConverters.kt | 6 ++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 4 + .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 5 ++ .../diagnostics/FirJvmDiagnosticsList.kt | 1 + .../analysis/diagnostics/jvm/FirJvmErrors.kt | 1 + .../jvm/FirJvmErrorsDefaultMessages.kt | 6 ++ .../jvm/checkers/JvmExpressionCheckers.kt | 3 +- .../FirJvmReflectionApiCallChecker.kt | 24 +++++ .../AbstractFirReflectionApiCallChecker.kt | 89 +++++++++++++++++++ .../AbstractReflectionApiCallChecker.kt | 5 +- .../reflection/noReflectionInClassPath.fir.kt | 62 ------------- .../reflection/noReflectionInClassPath.kt | 2 + .../effects/callsInPlace/pos/3.kt | 2 +- .../src/org/jetbrains/kotlin/name/FqName.java | 4 + .../jetbrains/kotlin/name/FqNameUnsafe.java | 11 +++ 15 files changed, 158 insertions(+), 67 deletions(-) create mode 100644 compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/expression/FirJvmReflectionApiCallChecker.kt create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/AbstractFirReflectionApiCallChecker.kt delete mode 100644 compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.fir.kt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt index db0a18209a9..89394ee9398 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -5055,6 +5055,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert token, ) } + add(FirJvmErrors.NO_REFLECTION_IN_CLASS_PATH) { firDiagnostic -> + NoReflectionInClassPathImpl( + firDiagnostic as KtPsiDiagnostic, + token, + ) + } add(FirJsErrors.IMPLEMENTING_FUNCTION_INTERFACE) { firDiagnostic -> ImplementingFunctionInterfaceImpl( firDiagnostic as KtPsiDiagnostic, diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 902e383ce0f..ab932b4c7ab 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -3523,6 +3523,10 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = JavaSamInterfaceConstructorReference::class } + interface NoReflectionInClassPath : KtFirDiagnostic { + override val diagnosticClass get() = NoReflectionInClassPath::class + } + interface ImplementingFunctionInterface : KtFirDiagnostic { override val diagnosticClass get() = ImplementingFunctionInterface::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 25400985940..89393a99353 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -4257,6 +4257,11 @@ internal class JavaSamInterfaceConstructorReferenceImpl( token: KtLifetimeToken, ) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.JavaSamInterfaceConstructorReference +internal class NoReflectionInClassPathImpl( + firDiagnostic: KtPsiDiagnostic, + token: KtLifetimeToken, +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.NoReflectionInClassPath + internal class ImplementingFunctionInterfaceImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt index 903241692d1..fb73a56edea 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt @@ -173,5 +173,6 @@ object JVM_DIAGNOSTICS_LIST : DiagnosticList("FirJvmErrors") { PositioningStrategy.SPREAD_OPERATOR ) val JAVA_SAM_INTERFACE_CONSTRUCTOR_REFERENCE by error() + val NO_REFLECTION_IN_CLASS_PATH by warning() } } diff --git a/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt b/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt index 744b105ae12..4654f69f900 100644 --- a/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt +++ b/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt @@ -119,6 +119,7 @@ object FirJvmErrors { val CONCURRENT_HASH_MAP_CONTAINS_OPERATOR by deprecationError0(ProhibitConcurrentHashMapContains) val SPREAD_ON_SIGNATURE_POLYMORPHIC_CALL by deprecationError0(ProhibitSpreadOnSignaturePolymorphicCall, SourceElementPositioningStrategies.SPREAD_OPERATOR) val JAVA_SAM_INTERFACE_CONSTRUCTOR_REFERENCE by error0() + val NO_REFLECTION_IN_CLASS_PATH by warning0() init { RootDiagnosticRendererFactory.registerFactory(FirJvmErrorsDefaultMessages) diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt index 2dd70adf4ec..c7d2032b408 100644 --- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt @@ -52,6 +52,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.LOCAL_JVM_ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_DATA_CLASS_JVM_RECORD import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_FINAL_JVM_RECORD import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NON_SOURCE_REPEATED_ANNOTATION +import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.NO_REFLECTION_IN_CLASS_PATH import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_ABSTRACT import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_ANNOTATION_CLASS_CONSTRUCTOR import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.OVERLOADS_INTERFACE @@ -257,5 +258,10 @@ object FirJvmErrorsDefaultMessages : BaseDiagnosticRendererFactory() { TO_STRING, TO_STRING, ) + map.put( + NO_REFLECTION_IN_CLASS_PATH, + "Call uses reflection API which is not found in compilation classpath. " + + "Make sure you have kotlin-reflect.jar in the classpath" + ) } } diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmExpressionCheckers.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmExpressionCheckers.kt index e2764bbd6ac..f527bd5156e 100644 --- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmExpressionCheckers.kt +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmExpressionCheckers.kt @@ -11,7 +11,8 @@ import org.jetbrains.kotlin.fir.analysis.jvm.checkers.expression.* object JvmExpressionCheckers : ExpressionCheckers() { override val basicExpressionCheckers: Set get() = setOf( - FirJvmProtectedInSuperClassCompanionCallChecker + FirJvmProtectedInSuperClassCompanionCallChecker, + FirJvmReflectionApiCallChecker, ) override val qualifiedAccessExpressionCheckers: Set diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/expression/FirJvmReflectionApiCallChecker.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/expression/FirJvmReflectionApiCallChecker.kt new file mode 100644 index 00000000000..24c4d9aba61 --- /dev/null +++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/expression/FirJvmReflectionApiCallChecker.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2010-2023 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.analysis.jvm.checkers.expression + +import org.jetbrains.kotlin.KtSourceElement +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.expression.AbstractFirReflectionApiCallChecker +import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors +import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider +import org.jetbrains.kotlin.load.java.JvmAbi + +object FirJvmReflectionApiCallChecker : AbstractFirReflectionApiCallChecker() { + override fun isWholeReflectionApiAvailable(context: CheckerContext): Boolean = + context.session.symbolProvider.getClassLikeSymbolByClassId(JvmAbi.REFLECTION_FACTORY_IMPL) != null + + override fun report(source: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter) { + reporter.reportOn(source, FirJvmErrors.NO_REFLECTION_IN_CLASS_PATH, context) + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/AbstractFirReflectionApiCallChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/AbstractFirReflectionApiCallChecker.kt new file mode 100644 index 00000000000..16ad0a225ef --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/AbstractFirReflectionApiCallChecker.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2023 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.analysis.checkers.expression + +import org.jetbrains.kotlin.KtSourceElement +import org.jetbrains.kotlin.builtins.StandardNames +import org.jetbrains.kotlin.config.AnalysisFlags +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.expressions.calleeReference +import org.jetbrains.kotlin.fir.packageFqName +import org.jetbrains.kotlin.fir.references.resolved +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name + +// TODO (KT-60899): implement this checker for JS, similarly to K1's JsReflectionAPICallChecker. +abstract class AbstractFirReflectionApiCallChecker : FirBasicExpressionChecker() { + protected abstract fun isWholeReflectionApiAvailable(context: CheckerContext): Boolean + protected abstract fun report(source: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter) + + protected open fun isAllowedKClassMember(name: Name, context: CheckerContext): Boolean = when (name) { + K_CLASS_SIMPLE_NAME, K_CLASS_IS_INSTANCE -> true + K_CLASS_QUALIFIED_NAME -> context.languageVersionSettings.getFlag(AnalysisFlags.allowFullyQualifiedNameInKClass) + else -> false + } + + final override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { + if (isWholeReflectionApiAvailable(context)) return + + // Do not report the diagnostic on kotlin-reflect sources. + if (isReflectionSource(context)) return + + val resolvedReference = expression.calleeReference?.resolved ?: return + val referencedSymbol = resolvedReference.resolvedSymbol as? FirCallableSymbol ?: return + + val containingClassId = + (expression as? FirQualifiedAccessExpression)?.dispatchReceiver?.typeRef?.toClassLikeSymbol(context.session)?.classId + if (containingClassId == null || containingClassId.packageFqName != StandardNames.KOTLIN_REFLECT_FQ_NAME) return + + if (!isAllowedReflectionApi(referencedSymbol.name, containingClassId, context)) { + report(resolvedReference.source, context, reporter) + } + } + + private fun isAllowedReflectionApi(name: Name, containingClassId: ClassId, context: CheckerContext): Boolean = + name in ALLOWED_MEMBER_NAMES || + containingClassId == K_CLASS && isAllowedKClassMember(name, context) || + (name.asString() == "get" || name.asString() == "set") && containingClassId in K_PROPERTY_CLASSES || + containingClassId in ALLOWED_CLASSES + + private fun isReflectionSource(context: CheckerContext): Boolean { + val containingFile = context.containingFile + return containingFile != null && containingFile.packageFqName.startsWith(StandardNames.KOTLIN_REFLECT_FQ_NAME) + } + + companion object { + private val K_CLASS = ClassId.topLevel(StandardNames.FqNames.kClass.toSafe()) + + private val K_CLASS_SIMPLE_NAME = Name.identifier("simpleName") + private val K_CLASS_IS_INSTANCE = Name.identifier("isInstance") + private val K_CLASS_QUALIFIED_NAME = Name.identifier("qualifiedName") + + private val K_PROPERTY_CLASSES: Set = + listOf( + StandardNames.FqNames.kProperty0, + StandardNames.FqNames.kProperty1, + StandardNames.FqNames.kProperty2, + StandardNames.FqNames.kMutableProperty0, + StandardNames.FqNames.kMutableProperty1, + StandardNames.FqNames.kMutableProperty2, + ).mapTo(HashSet()) { ClassId.topLevel(it.toSafe()) } + + private val ALLOWED_MEMBER_NAMES: Set = + listOf("equals", "hashCode", "toString", "invoke", "name").mapTo(HashSet(), Name::identifier) + + private val ALLOWED_CLASSES: Set = + listOf("KType", "KTypeParameter", "KTypeProjection", "KTypeProjection.Companion", "KVariance").mapTo(HashSet()) { + ClassId(StandardNames.KOTLIN_REFLECT_FQ_NAME, FqName(it), false) + } + } +} diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt index 9c8fb9010f3..8db2cc66f7b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt @@ -17,8 +17,8 @@ package org.jetbrains.kotlin.resolve.calls.checkers import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.builtins.StandardNames.KOTLIN_REFLECT_FQ_NAME import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.builtins.StandardNames.KOTLIN_REFLECT_FQ_NAME import org.jetbrains.kotlin.config.AnalysisFlags.allowFullyQualifiedNameInKClass import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -96,7 +96,6 @@ abstract class AbstractReflectionApiCallChecker( private fun isReflectionSource(reportOn: PsiElement): Boolean { val file = reportOn.containingFile as? KtFile ?: return false - val fqName = file.packageFqName.toUnsafe() - return fqName == KOTLIN_REFLECT_FQ_NAME.toUnsafe() || fqName.asString().startsWith(KOTLIN_REFLECT_FQ_NAME.asString() + ".") + return file.packageFqName.startsWith(KOTLIN_REFLECT_FQ_NAME) } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.fir.kt deleted file mode 100644 index 0eb9f3505de..00000000000 --- a/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.fir.kt +++ /dev/null @@ -1,62 +0,0 @@ -import kotlin.reflect.* - -class Foo(val prop: Any) { - fun func() {} -} - -fun n01() = Foo::prop -fun n02() = Foo::func -fun n03() = Foo::class -fun n04(p: KProperty0) = p.get() -fun n05(p: KMutableProperty0) = p.set("") -fun n07(p: KFunction) = p.name -fun n08(p: KProperty1) = p.get("") -fun n09(p: KProperty2) = p.get("", "") -fun n10() = (Foo::func).invoke(Foo("")) -fun n11() = (Foo::func)(Foo("")) - -fun y01() = Foo::prop.getter -fun y02() = Foo::class.members -fun y03() = Foo::class.simpleName -fun y04() = Foo::class.properties - -fun kclass(k: KClass<*>, kt: KClass) { - k.simpleName - k.qualifiedName - k.members - k.constructors - k.nestedClasses - k.objectInstance - k.typeParameters - k.supertypes - k.visibility - k.isFinal - k.isOpen - k.isAbstract - k.isSealed - k.isData - k.isInner - k.isCompanion - - k.annotations - k.isInstance(42) - - k == kt - k.hashCode() - k.toString() -} - -fun ktype(t: KType, t2: KType) { - t.classifier - t.arguments - t.isMarkedNullable - t.annotations - - t == t2 - t.hashCode() - t.toString() - - KTypeProjection.Companion.covariant(t) - KTypeProjection.STAR - KTypeProjection(KVariance.IN, t) -} diff --git a/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.kt b/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.kt index 5aa23848308..d990500e468 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/reflection/noReflectionInClassPath.kt @@ -1,3 +1,5 @@ +// FIR_IDENTICAL + import kotlin.reflect.* class Foo(val prop: Any) { diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/callsInPlace/pos/3.kt b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/callsInPlace/pos/3.kt index c20552500b9..ab3fb110a46 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/callsInPlace/pos/3.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/contracts/declarations/contractBuilder/effects/callsInPlace/pos/3.kt @@ -1,5 +1,5 @@ // FIR_IDENTICAL -// !DIAGNOSTICS: -UNUSED_VARIABLE +// !DIAGNOSTICS: -UNUSED_VARIABLE -NO_REFLECTION_IN_CLASS_PATH // !OPT_IN: kotlin.contracts.ExperimentalContracts /* diff --git a/core/compiler.common/src/org/jetbrains/kotlin/name/FqName.java b/core/compiler.common/src/org/jetbrains/kotlin/name/FqName.java index 6bd261e87d7..d0d921cc155 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/name/FqName.java +++ b/core/compiler.common/src/org/jetbrains/kotlin/name/FqName.java @@ -102,6 +102,10 @@ public final class FqName { return fqName.startsWith(segment); } + public boolean startsWith(@NotNull FqName other) { + return fqName.startsWith(other.fqName); + } + @NotNull public static FqName topLevel(@NotNull Name shortName) { return new FqName(FqNameUnsafe.topLevel(shortName)); diff --git a/core/compiler.common/src/org/jetbrains/kotlin/name/FqNameUnsafe.java b/core/compiler.common/src/org/jetbrains/kotlin/name/FqNameUnsafe.java index a47eda38547..4833d702133 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/name/FqNameUnsafe.java +++ b/core/compiler.common/src/org/jetbrains/kotlin/name/FqNameUnsafe.java @@ -167,6 +167,17 @@ public final class FqNameUnsafe { return fqName.regionMatches(0, segmentAsString, 0, firstDot == -1 ? Math.max(fqName.length(), segmentAsString.length()) : firstDot); } + public boolean startsWith(@NotNull FqNameUnsafe other) { + if (isRoot()) return false; + + int thisLength = fqName.length(); + int otherLength = other.fqName.length(); + if (thisLength < otherLength) return false; + + return (thisLength == otherLength || fqName.charAt(otherLength) == '.') && + fqName.regionMatches(0, other.fqName, 0, otherLength); + } + @NotNull public static FqNameUnsafe topLevel(@NotNull Name shortName) { return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName);