[FIR JS] Report CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION

This commit is contained in:
Nikolay Lunyak
2023-01-04 16:23:42 +02:00
committed by Space Team
parent 4cf8d9ffb9
commit 56285bb2d6
16 changed files with 128 additions and 38 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.checkers.generator.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy
@@ -38,6 +39,7 @@ object JS_DIAGNOSTICS_LIST : DiagnosticList("FirJsErrors") {
val OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<FirNamedFunctionSymbol>("function")
}
val CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION by error<PsiElement>()
}
val DYNAMICS by object : DiagnosticGroup("Dynamics") {
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.fir.analysis.diagnostics.js
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
@@ -35,6 +36,7 @@ object FirJsErrors {
// External
val OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS by error0<KtElement>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val OVERRIDING_EXTERNAL_FUN_WITH_OPTIONAL_PARAMS_WITH_FAKE by error1<KtElement, FirNamedFunctionSymbol>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION by error0<PsiElement>()
// Dynamics
val DELEGATION_BY_DYNAMIC by error0<KtElement>()
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers
import org.jetbrains.kotlin.fir.analysis.diagnostics.checkMissingMessages
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.DELEGATION_BY_DYNAMIC
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.IMPLEMENTING_FUNCTION_INTERFACE
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors.JS_MODULE_PROHIBITED_ON_NON_NATIVE
@@ -42,6 +43,7 @@ object FirJsErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
"Overriding `external` function with optional parameters by declaration from superclass: {0}",
FirDiagnosticRenderers.SYMBOL
)
map.put(CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION, "This property can only be used from external declarations")
map.checkMissingMessages(FirJsErrors)
}
@@ -14,12 +14,14 @@ import org.jetbrains.kotlin.fir.analysis.checkers.directOverriddenFunctions
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.hasAnnotationOrInsideAnnotatedClass
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
import org.jetbrains.kotlin.fir.declarations.utils.modality
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.js.PredefinedAnnotation
import org.jetbrains.kotlin.name.JsStandardClassIds
private val FirBasedSymbol<*>.isExternal
@@ -46,6 +48,10 @@ fun FirBasedSymbol<*>.isEffectivelyExternal(session: FirSession): Boolean {
return getContainingClassSymbol(session)?.isEffectivelyExternal(session) == true
}
fun FirBasedSymbol<*>.isEffectivelyExternalMember(session: FirSession): Boolean {
return fir is FirMemberDeclaration && isEffectivelyExternal(session)
}
fun FirBasedSymbol<*>.isEffectivelyExternal(context: CheckerContext) = isEffectivelyExternal(context.session)
fun FirFunctionSymbol<*>.isOverridingExternalWithOptionalParams(context: CheckerContext): Boolean {
@@ -66,10 +72,33 @@ fun FirBasedSymbol<*>.isNativeObject(session: FirSession): Boolean {
}
if (this is FirPropertyAccessorSymbol) {
return propertySymbol.hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session)
val property = propertySymbol
return property.hasAnnotationOrInsideAnnotatedClass(JsStandardClassIds.Annotations.JsNative, session)
}
return false
}
private val FirBasedSymbol<*>.isExpect
get() = when (this) {
is FirCallableSymbol<*> -> isExpect
is FirClassSymbol<*> -> isExpect
else -> false
}
fun FirBasedSymbol<*>.isPredefinedObject(session: FirSession): Boolean {
if (fir is FirMemberDeclaration && isExpect) return true
if (isEffectivelyExternalMember(session)) return true
for (annotation in PredefinedAnnotation.values()) {
if (hasAnnotationOrInsideAnnotatedClass(annotation.classId, session)) {
return true
}
}
return false
}
fun FirBasedSymbol<*>.isNativeObject(context: CheckerContext) = isNativeObject(context.session)
fun FirBasedSymbol<*>.isPredefinedObject(context: CheckerContext) = isPredefinedObject(context.session)
@@ -6,11 +6,16 @@
package org.jetbrains.kotlin.fir.analysis.js.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.expression.*
import org.jetbrains.kotlin.fir.analysis.js.checkers.expression.FirJsQualifierChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.expression.*
object JsExpressionCheckers : ExpressionCheckers() {
override val annotationCallCheckers: Set<FirAnnotationCallChecker>
get() = setOf(
FirJsQualifierChecker,
)
override val qualifiedAccessCheckers: Set<FirQualifiedAccessChecker>
get() = setOf(
FirJsDefinedExternallyCallChecker,
)
}
@@ -0,0 +1,34 @@
/*
* 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.js.checkers.expression
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.closestNonLocal
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessChecker
import org.jetbrains.kotlin.fir.analysis.js.checkers.isNativeObject
import org.jetbrains.kotlin.fir.analysis.js.checkers.isPredefinedObject
import org.jetbrains.kotlin.fir.analysis.diagnostics.js.FirJsErrors
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.name.JsStandardClassIds
object FirJsDefinedExternallyCallChecker : FirQualifiedAccessChecker() {
override fun check(expression: FirQualifiedAccess, context: CheckerContext, reporter: DiagnosticReporter) {
val symbol = expression.calleeReference.toResolvedCallableSymbol() ?: return
if (symbol.callableId !in JsStandardClassIds.Callables.definedExternallyPropertyNames) {
return
}
val container = context.closestNonLocal?.symbol ?: return
if (!container.isNativeObject(context) && !container.isPredefinedObject(context)) {
reporter.reportOn(expression.source, FirJsErrors.CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION, context)
}
}
}
@@ -713,6 +713,8 @@ fun FirNamedFunctionSymbol.directOverriddenFunctions(session: FirSession, scopeS
fun FirNamedFunctionSymbol.directOverriddenFunctions(context: CheckerContext) =
directOverriddenFunctions(context.session, context.sessionHolder.scopeSession)
val CheckerContext.closestNonLocal get() = containingDeclarations.takeWhile { it.isNonLocal }.lastOrNull()
fun CheckerContext.closestNonLocalWith(declaration: FirDeclaration) =
(containingDeclarations + declaration).takeWhile { it.isNonLocal }.lastOrNull()
@@ -1,29 +0,0 @@
// !DIAGNOSTICS: -UNUSED_ANONYMOUS_PARAMETER -UNUSED_PARAMETER, -UNREACHABLE_CODE
val prop: String = definedExternally
val prop2: String
get() = definedExternally
fun foo(x: Int, y: String = definedExternally) {
println("Hello")
println("world")
object {
fun bar(): Any = definedExternally
}
listOf<String>()
.map<String, String> { definedExternally }
.filter(fun(x: String): Boolean { definedExternally })
definedExternally
}
open class A(val x: Int)
open class B() : A(definedExternally) {
constructor(y: String) : this()
constructor(y: String, z: String) : this(y + z + definedExternally)
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNUSED_ANONYMOUS_PARAMETER -UNUSED_PARAMETER, -UNREACHABLE_CODE
val prop: String = <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!>
@@ -27,3 +28,12 @@ open class B() : A(<!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>d
constructor(y: String, z: String) : this(y + z + <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!>)
}
private fun exposeClassS() = run {
class S {
inner class W {
val v: String = <!CALL_TO_DEFINED_EXTERNALLY_FROM_NON_EXTERNAL_DECLARATION!>definedExternally<!>
}
}
S()
}
@@ -2,6 +2,7 @@ package
public val prop: kotlin.String
public val prop2: kotlin.String
private fun exposeClassS(): exposeClassS.`<anonymous>`.S
public fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.String = ...): kotlin.Unit
public open class A {
@@ -21,3 +22,4 @@ public open class B : A {
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}